Wie kann man die redu () -Methode frühzeitig unterbrechen?

91

Wie kann ich die Iteration der reduce()Methode unterbrechen ?

for::

for (var i = Things.length - 1; i >= 0; i--) {
  if(Things[i] <= 0){
    break;
  }
};

reduce()

Things.reduce(function(memo, current){
  if(current <= 0){
    //break ???
    //return; <-- this will return undefined to memo, which is not what I want
  }
}, 0)
Julio Marins
quelle
Was steht currentim obigen Code? Ich sehe nicht, wie diese das Gleiche tun können. In jedem Fall gibt es Methoden , die früh wie brechen some, every,find
elclanrs
someund everyBoolesche Werte zurückgeben und findeinen einzelnen Datensatz zurückgeben. Ich möchte Operationen ausführen, um ein Memo zu generieren. currentist der aktuelle Wert. Referenz
Julio Marins
Ich meine, was steht currentim ersten Code?
Elclanrs
aktualisiert, danke für die Antwort
Julio Marins
2
Die Antwort ist, dass Sie nicht frühzeitig abbrechen reducekönnen. Sie müssen einen anderen Weg mit integrierten Funktionen finden, die vorzeitig beendet werden oder Ihren eigenen Helfer erstellen oder lodash oder ähnliches verwenden. Können Sie ein vollständiges Beispiel dafür veröffentlichen, was Sie tun möchten?
Elclanrs

Antworten:

90

AKTUALISIEREN

Einige der Kommentatoren weisen darauf hin, dass das ursprüngliche Array mutiert wird, um frühzeitig in die .reduce()Logik einzudringen.

Daher habe ich die Antwort geringfügig geändert, indem ich .slice(0)vor dem Aufrufen eines Folgeschritts ein a hinzugefügt habe .reduce(), um eine Kopie des ursprünglichen Arrays zu erhalten. HINWEIS : Ähnliche Operationen, die dieselbe Aufgabe ausführen, sind slice()(weniger explizit) und Spread-Operatoren [...array]( etwas weniger performant ). Beachten Sie, dass all dies einen zusätzlichen konstanten Faktor der linearen Zeit zur Gesamtlaufzeit + 1 * (O (1)) hinzufügt.

Die Kopie dient dazu, das ursprüngliche Array vor der eventuellen Mutation zu bewahren, die zum Auswerfen aus der Iteration führt.

const array = ['9', '91', '95', '96', '99'];
const x = array
    .slice(0)                         // create copy of "array" for iterating
    .reduce((acc, curr, i, arr) => {
       if (i === 2) arr.splice(1);    // eject early by mutating iterated copy
       return (acc += curr);
    }, '');

console.log("x: ", x, "\noriginal Arr: ", array);
// x:  99195
// original Arr:  [ '9', '91', '95', '96', '99' ]


ALT

Sie können jede Iteration eines .reduce () -Aufrufs unterbrechen, indem Sie das 4. Argument der Reduktionsfunktion mutieren: "array". Keine benutzerdefinierte Reduzierungsfunktion erforderlich. Siehe Text & Tabellen für vollständige Liste der .reduce()Parameter.

Array.prototype.reduce ((acc, curr, i, array))

Das vierte Argument ist das Array , über das iteriert wird.

const array = ['9', '91', '95', '96', '99'];
const x = array
.reduce((acc, curr, i, arr) => {
    if(i === 2) arr.splice(1);  // eject early
    return acc += curr;
  }, '');
console.log('x: ', x);  // x:  99195

WARUM?:

Der einzige Grund, warum ich mir vorstellen kann, dies anstelle der vielen anderen vorgestellten Lösungen zu verwenden, ist, wenn Sie eine funktionale Programmiermethode für Ihren Algorithmus beibehalten möchten und einen möglichst deklarativen Ansatz wünschen, um dies zu erreichen. Wenn Ihr gesamtes Ziel darin besteht, ein Array buchstäblich auf ein alternatives Nicht-Falsey-Primitiv (Zeichenfolge, Zahl, Boolescher Wert, Symbol) zu reduzieren, würde ich argumentieren, dass dies tatsächlich der beste Ansatz ist.

WARUM NICHT?

Es gibt eine ganze Liste von Argumenten, um Funktionsparameter NICHT zu mutieren, da dies eine schlechte Praxis ist.

Tobiah Rex
quelle
3
+1. Dies sollte die akzeptierte Antwort sein. Und dennoch sollte diese Lösung aus den unter "WARUM NICHT" genannten Gründen niemals verwendet werden.
Johndodo
3
Dies ist wirklich ein schlechter Rat, da spliceeine sichtbare Mutation durchgeführt wird ( array). Entsprechend dem Funktionsparadigma würden Sie entweder einen Reduktionsstil für die Weitergabe verwenden oder eine verzögerte Bewertung mit einer rechtsassoziativen Reduktion verwenden. Oder als einfachere Alternative einfach nur Rekursion.
Warten Sie mal! durch Mutation des 4. Arguments der Reduktionsfunktion: "Array" ist keine korrekte Aussage. In diesem Fall geschieht dies (das Beispiel in der Antwort), weil das Array in ein Array mit einfacher Länge (erstes Element) geschnitten wird, während es bereits Index 2 erreicht hat , offensichtlich beim nächsten Mal, für Index 3 wird kein Element zum Iterieren erhalten (as Sie mutieren den ursprünglichen Verweis auf ein Array der Länge 1 ). Falls Sie einen Pop ausführen, der auch das Quellarray mutiert, aber nicht dazwischen stoppt (wenn Sie nicht am vorletzten Index sind).
Koushik Chatterjee
@KoushikChatterjee Meine Aussage ist korrekt für meine implizite Bedeutung. Es ist nicht korrekt für Ihre explizite Bedeutung. Sie sollten einen Vorschlag zum Ändern der Aussage machen, um Ihre Punkte einzuschließen, und ich werde die Bearbeitung vornehmen, da dies die Gesamtantwort verbessern würde.
Tobiah Rex
1
Ich greife lieber nach dem Spread-Operator, um unerwünschte Mutationen zu vermeiden. [... array] .reduce ()
eballeste
17

Verwenden Sie nicht reduzieren. Iterieren Sie das Array einfach mit normalen Iteratoren (für usw.) und brechen Sie aus, wenn Ihre Bedingung erfüllt ist.

AndroidDev
quelle
49
Wo ist der Spaß dabei? :)
Alexander Mills
1
@AlexanderMills wahrscheinlich mag er es, ein Imperator zu sein!
Dimpiax
2
Diese Antwort hat hier den Wert 0
Fedeghe
12

Sie können Funktionen wie verwenden einige und jeder , solange Sie nicht über den Rückgabewert ist egal. Jede Unterbrechung, wenn der Rückruf false zurückgibt, einige, wenn true zurückgegeben wird:

things.every(function(v, i, o) {
  // do stuff 
  if (timeToBreak) {
    return false;
  } else {
    return true;
  }
}, thisArg);
RobG
quelle
24
Aber wenn er es versucht, reducedann kümmert er sich per Definition um den Rückgabewert.
1
@ torazaburo - sicher, aber ich sehe nicht, dass es im OP verwendet wird, und es gibt andere Möglichkeiten, ein Ergebnis zu erzielen. ;-)
RobG
6

Es gibt natürlich keine Möglichkeit, die integrierte Version von reducevorzeitig zu beenden.

Sie können jedoch Ihre eigene Version von redu schreiben, die ein spezielles Token verwendet, um zu identifizieren, wann die Schleife unterbrochen werden sollte.

var EXIT_REDUCE = {};

function reduce(a, f, result) {
  for (let i = 0; i < a.length; i++) {
    let val = f(result, a[i], i, a);
    if (val === EXIT_REDUCE) break;
    result = val;
  }
  return result;
}

Verwenden Sie es so, um ein Array zu summieren, aber beenden Sie es, wenn Sie 99 drücken:

reduce([1, 2, 99, 3], (a, b) => b === 99 ? EXIT_REDUCE : a + b, 0);

> 3

quelle
1
Sie können Lazy Evaluation oder CPS verwenden , um das gewünschte Verhalten zu erreichen:
Scriptum
Der erste Satz dieser Antwort ist falsch. Sie können brechen, siehe meine Antwort unten für Details.
Tobiah Rex
4

Array.every kann einen sehr natürlichen Mechanismus zum Ausbrechen von Iterationen höherer Ordnung bereitstellen.

const product = function(array) {
    let accumulator = 1;
    array.every( factor => {
        accumulator *= factor;
        return !!factor;
    });
    return accumulator;
}
console.log(product([2,2,2,0,2,2]));
// 0

Doug Coburn
quelle
1

Sie können jeden Code - und damit jeden eingebauten Iterator - brechen, indem Sie eine Ausnahme auslösen:

function breakReduceException(value) {
    this.value = value
}

try {
    Things.reduce(function(memo, current) {
        ...
        if (current <= 0) throw new breakReduceException(memo)
        ...
    }, 0)
} catch (e) {
    if (e instanceof breakReduceException) var memo = e.value
    else throw e
}
Koudela
quelle
6
Dies ist wahrscheinlich die am wenigsten effiziente Ausführung aller Antworten. Try / catch unterbricht den vorhandenen Ausführungskontext und greift auf den "langsamen Pfad" der Ausführung zurück. Verabschieden Sie sich von allen Optimierungen, die V8 unter der Decke vornimmt.
Evan Plaice
4
Nicht extrem genug. Wie wäre es damit:if (current <= 0) window.top.close()
user56reinstatemonica8
0

Wie die Argumente promises have resolveund rejectcallback habe ich die reduceWorkaround-Funktion mit dem breakArgument callback erstellt. Es werden dieselben Argumente wie bei der nativen reduceMethode verwendet, außer dass das erste ein Array ist, an dem gearbeitet werden muss (vermeiden Sie das Patchen von Affen). Das dritte [2] initialValueArgument ist optional. Siehe den Ausschnitt unten für den functionReduzierer.

var list = ["w","o","r","l","d"," ","p","i","e","r","o","g","i"];

var result = reducer(list,(total,current,index,arr,stop)=>{
  if(current === " ") stop(); //when called, the loop breaks
  return total + current;
},'hello ');

console.log(result); //hello world

function reducer(arr, callback, initial) {
  var hasInitial = arguments.length >= 3;
  var total = hasInitial ? initial : arr[0];
  var breakNow = false;
  for (var i = hasInitial ? 0 : 1; i < arr.length; i++) {
    var currentValue = arr[i];
    var currentIndex = i;
    var newTotal = callback(total, currentValue, currentIndex, arr, () => breakNow = true);
    if (breakNow) break;
    total = newTotal;
  }
  return total;
}

Und hier ist das reducerals Array methodmodifizierte Skript:

Array.prototype.reducer = function(callback,initial){
  var hasInitial = arguments.length >= 2;
  var total = hasInitial ? initial : this[0];
  var breakNow = false;
  for (var i = hasInitial ? 0 : 1; i < this.length; i++) {
    var currentValue = this[i];
    var currentIndex = i;
    var newTotal = callback(total, currentValue, currentIndex, this, () => breakNow = true);
    if (breakNow) break;
    total = newTotal;
  }
  return total;
};

var list = ["w","o","r","l","d"," ","p","i","e","r","o","g","i"];

var result = list.reducer((total,current,index,arr,stop)=>{
  if(current === " ") stop(); //when called, the loop breaks
  return total + current;
},'hello ');


console.log(result);
Paweł
quelle
0

Funktionsversion mit Pause reduzieren kann als 'transform' implementiert werden, z. im Unterstrich.

Ich habe versucht, es mit einem Konfigurationsflag zu implementieren, um es zu stoppen, damit die Implementierungsreduzierung nicht die Datenstruktur ändern muss, die Sie derzeit verwenden.

const transform = (arr, reduce, init, config = {}) => {
  const result = arr.reduce((acc, item, i, arr) => {
    if (acc.found) return acc

    acc.value = reduce(config, acc.value, item, i, arr)

    if (config.stop) {
      acc.found = true
    }

    return acc
  }, { value: init, found: false })

  return result.value
}

module.exports = transform

Verwendung1, einfach

const a = [0, 1, 1, 3, 1]

console.log(transform(a, (config, acc, v) => {
  if (v === 3) { config.stop = true }
  if (v === 1) return ++acc
  return acc
}, 0))

Verwendung2, verwenden Sie config als interne Variable

const pixes = Array(size).fill(0)
const pixProcessed = pixes.map((_, pixId) => {
  return transform(pics, (config, _, pic) => {
    if (pic[pixId] !== '2') config.stop = true 
    return pic[pixId]
  }, '0')
})

Verwendung3, Capture-Konfiguration als externe Variable

const thrusts2 = permute([9, 8, 7, 6, 5]).map(signals => {
  const datas = new Array(5).fill(_data())
  const ps = new Array(5).fill(0)

  let thrust = 0, config
  do {

    config = {}
    thrust = transform(signals, (_config, acc, signal, i) => {
      const res = intcode(
        datas[i], signal,
        { once: true, i: ps[i], prev: acc }
      )

      if (res) {
        [ps[i], acc] = res 
      } else {
        _config.stop = true
      }

      return acc
    }, thrust, config)

  } while (!config.stop)

  return thrust
}, 0)
Windmaomao
quelle
0

Sie können nicht innerhalb einer reduceMethode brechen . Je nachdem, was Sie erreichen möchten, können Sie das Endergebnis ändern (was ein Grund ist, warum Sie dies tun möchten).

const result = [1, 1, 1].reduce((a, b) => a + b, 0); // returns 3

console.log(result);

const result = [1, 1, 1].reduce((a, b, c, d) => {
  if (c === 1 && b < 3) {
    return a + b + 1;
  } 
  return a + b;
}, 0); // now returns 4

console.log(result);

Beachten Sie: Sie können den Array-Parameter nicht direkt neu zuweisen

const result = [1, 1, 1].reduce( (a, b, c, d) => {
  if (c === 0) {
    d = [1, 1, 2];
  } 
  return a + b;
}, 0); // still returns 3

console.log(result);

Sie können jedoch (wie unten ausgeführt) das Ergebnis beeinflussen, indem Sie den Inhalt des Arrays ändern:

const result = [1, 1, 1].reduce( (a, b, c, d) => {
  if (c === 0) {
    d[2] = 100;
  } 
  return a + b;
}, 0); // now returns 102

console.log(result);

Erik Waters
quelle
1
Betreff " Sie können die Argumentwerte nicht direkt in einer Weise mutieren, die sich auf nachfolgende Berechnungen auswirkt ", das ist nicht wahr. ECMA-262 sagt: Wenn vorhandene Elemente des Arrays geändert werden, ist ihr Wert, der an callbackfn übergeben wird, der Wert zu dem Zeitpunkt, zu dem die Anzahl der Besuche verringert wird . Ihr Beispiel funktioniert nicht, weil Sie d einen neuen Wert zuweisen und das ursprüngliche Array nicht ändern. Ersetzen Sie d = [1, 1, 2]durch d[2] = 6und sehen Sie, was passiert. ;-)
RobG
-1

Eine weitere einfache Implementierung, mit der ich das gleiche Problem gelöst habe:

function reduce(array, reducer, first) {
  let result = first || array.shift()

  while (array.length > 0) {
    result = reducer(result, array.shift())
    if (result && result.reduced) {
      return result.reduced
    }
  }

  return result
}
alun
quelle
-1

Wenn Sie Versprechen nacheinander mit Reduzieren verketten möchten, verwenden Sie das folgende Muster:

return [1,2,3,4].reduce(function(promise,n,i,arr){
   return promise.then(function(){
       // this code is executed when the reduce loop is terminated,
       // so truncating arr here or in the call below does not works
       return somethingReturningAPromise(n);
   });
}, Promise.resolve());

Wenn Sie jedoch nach einem Ereignis innerhalb oder außerhalb eines Versprechens brechen müssen, werden die Dinge etwas komplizierter, da die Reduzierungsschleife vor der Ausführung des ersten Versprechens beendet wird und das Abschneiden des Arrays in den Versprechungsrückrufen unbrauchbar wird. Am Ende kam ich zu folgender Implementierung:

function reduce(array, promise, fn, i) {
  i=i||0;
  return promise
  .then(function(){
    return fn(promise,array[i]);
  })
  .then(function(result){
    if (!promise.break && ++i<array.length) {
      return reduce(array,promise,fn,i);
    } else {
      return result;
    }
  })
}

Dann können Sie so etwas tun:

var promise=Promise.resolve();
reduce([1,2,3,4],promise,function(promise,val){
  return iter(promise, val);
}).catch(console.error);

function iter(promise, val) {
  return new Promise(function(resolve, reject){
    setTimeout(function(){
      if (promise.break) return reject('break');
      console.log(val);
      if (val==3) {promise.break=true;}
      resolve(val);
    }, 4000-1000*val);
  });
}
Luxigo
quelle
-1

Ich habe es wie folgt gelöst, zum Beispiel bei der someMethode, bei der ein Kurzschluss viel sparen kann:

const someShort = (list, fn) => {
  let t;
  try {
    return list.reduce((acc, el) => {
      t = fn(el);
      console.log('found ?', el, t)
      if (t) {
        throw ''
      }
      return t
    }, false)
  } catch (e) {
    return t
  }
}

const someEven = someShort([1, 2, 3, 1, 5], el => el % 2 === 0)

console.log(someEven)

fedeghe
quelle