web-dev-qa-db-ja.com

reduceメソッドでブレークする方法

Reduceメソッドで反復を中断するにはどうすればよいですか?

for

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

減らす

Things.reduce(function(memo, current){
  if(current <= 0){
    //break ???
    //return; <-- this will return undefined to memo, which is not what I want
  }
}, 0)
60
Julio Marins

UPDATE

一部のコメンテーターは、.reduce()ロジック内で早期にブレークするために、元の配列が変更されていることを指摘しています。

そのため、後続の.slice(0)ステップを呼び出す前に.reduce()を追加することにより、回答をわずかに変更しました

これは、その内容を線形時間-O(n)でコピーすることにより、元の配列を保持することです。元の配列は、保存された証拠としてコンソールにも記録されます。

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

OLD

Reduce関数の4番目の引数「array」を変更することにより、.reduce()呼び出しの反復で中断することができます。カスタム削減機能は不要です。 .reduce()パラメータの完全なリストについては、 Docs を参照してください。

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

4番目の引数は、反復されるarrayです。

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

なぜ?:

提示された他の多くのソリューションの代わりにこれを使用すると考えることができる唯一の唯一の理由は、アルゴリズムに対する関数型プログラミング方法論を維持したい場合、そしてそれを達成するために可能な限り宣言的なアプローチが必要な場合です。あなたの全体の目標が、文字通り配列を代替の偽でないプリミティブ(文字列、数値、ブール、シンボル)に削減することである場合、実際にはこれISが最善のアプローチであると主張します。

理由は何ですか?

悪い習慣なので、関数パラメーターを変更しないようにするための引数のリストがあります。

65
Tobiah Rex

戻り値を気にしない限り、someeveryなどの関数を使用できます。 everyコールバックがfalseを返すとブレークします。some trueを返すとブレークします。

things.every(function(v, i, o) {
  // do stuff 
  if (timeToBreak) {
    return false;
  } else {
    return true;
  }
}, thisArg);
9
RobG

Reduceを使用しないでください。通常のイテレータ(forなど)を使用して配列を反復処理し、条件が満たされたらブレークアウトします。

6
AndroidDev

もちろん、組み込みバージョンのreduceを取得して途中で終了する方法はありません。

ただし、特殊なトークンを使用してループをいつ中断するかを識別する独自のバージョンのreduceを作成できます。

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;
}

次のように使用して、配列を合計しますが、99を押すと終了します。

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

> 3
6
user663031

例外をスローすることで、すべてのコード、つまりイテレーターのすべてのビルドを壊すことができます。

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
}
4
Koudela

Array.everyは、高次の反復から抜け出すための非常に自然なメカニズムを提供できます。

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
2
Doug Coburn

promiseにはresolveおよびrejectコールバック引数があるため、reduceコールバック引数を使用してbreak回避策関数を作成しました。ネイティブのreduceメソッドと同じ引数をすべて取りますが、最初の引数が作業対象の配列であることを除きます(モンキーパッチの適用を避けます)。 3番目の[2] initialValue引数はオプションです。 functionレデューサーについては、以下のスニペットを参照してください。

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;
}

そして、ここにreducerが配列としてmethod変更されたスクリプトがあります:

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);
0
Paweł

Reduceメソッドの内部からブレークすることはできません。何を達成しようとしているかに応じて、最終結果を変更することができます(これが理由の1つです)

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

留意してください:配列パラメーターを直接再割り当てすることはできません

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

ただし(以下で指摘するように)配列の内容を変更することで結果に影響を与えることができます:

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

以下のパターンを使用して、reduceでプロミスを順番に連鎖させたい場合:

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());

しかし、最初のpromiseが実行される前にreduceループが終了し、promiseコールバック内の配列の切り捨てが役に立たないため、promiseの内部または外部で発生した何かに応じて中断する必要があるため、少し複雑になります。

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;
    }
  })
}

その後、次のようなことができます:

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);
  });
}
0
luxigo

同じ問題を解決するために私が来た別の簡単な実装:

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
}
0
alun