reduce()メソッドを早期に中断する方法は?


94

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

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)

current上記のコードには何が含まれていますか?これらが同じことをどのように行うことができるのかわかりません。いずれの場合も、早期のように破る方法があるsomeeveryfind
elclanrs

someそして、everyブール値を返し、find単一のレコードを返す、私がしたいことはメモを生成するための操作を実行することです。currentcurrentValueです。参照
Julio Marins 2016年

currentつまり、最初のコードには何が含まれているのでしょうか。
elclanrs 2016年

更新、返信ありがとう
Julio Marins 2016年

2
答えはreduce、早期に中断することはできません。早期に終了するか、独自のヘルパーを作成するか、lodashなどを使用する組み込み関数を使用して別の方法を見つける必要があります。あなたがやりたいことの完全な例を投稿できますか?
elclanrs 2016年

回答:


94

更新

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

したがって、後続のステップを呼び出す前にを追加して、元の配列のコピーを生成することにより、回答を少し変更しました。 :同じタスクを実行する同様の操作は(明示的ではありません)、スプレッド演算子(パフォーマンスがわずかに低くなります)です。これらはすべて、ランタイム全体に線形時間の定数係数+ 1 *(O(1))を追加することに注意してください。.slice(0).reduce()slice()[...array]

コピーは、反復からの排出を引き起こす最終的な突然変異から元の配列を保持するのに役立ちます。

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' ]


古い

.reduce()呼び出しの反復は、reduce関数の4番目の引数「array」を変更することで中断できます。カスタムのreduce関数は必要ありません。パラメータの完全なリストについては、ドキュメントを参照してください.reduce()

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

4番目の引数は、繰り返される配列です。

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

なぜ?:

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

何故なの?

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


3
+1。これは受け入れられた答えでなければなりません。それでも、「WHY NOT」に記載されている理由により、このソリューションは絶対に使用しないでください。
johndodo 2018

3
splice目に見える突然変異を実行するため、これは本当に悪いアドバイスarrayです()。関数型パラダイムによれば、継続渡しスタイルのリデュースを使用するか、右結合リデュースを使用した遅延評価を利用します。または、より単純な代替手段として、単純な再帰です。

つかまっている! reduce関数の4番目の引数を変更することにより、「array」は正しいステートメントではありません。この場合、それは起こっています(答えの例)。なぜなら、すでにインデックス2に到達している間に、配列を単一の長さの配列(最初の要素)にカットするからです。明らかに次回は、インデックス3の場合、アイテムを反復処理しません(長さ1の配列への元の参照を変更しています)。ソース配列も変更するが、その間に停止しないポップを実行する場合(最後から2番目のインデックスにいない場合)。
Koushik Chatterjee 2018

@KoushikChatterjee私の発言は私の暗黙の意味に対して正しいです。それはあなたの明確な意味に対して正しくありません。あなたはあなたのポイントを含むようにステートメントを修正することについての提案を提供するべきです、そしてそれが全体的な答えを改善するので私は編集をします。
Tobiah Rex 2018

1
不要な突然変異を避けるために、スプレッド演算子に手を伸ばすことを好みます。[... array] .reduce()
eballeste19年

16

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


58
これの楽しみはどこにありますか?:)
アレクサンダーミルズ

2
@AlexanderMillsおそらく彼は大将軍になるのが好きです!
dimpiax 2018

3
この回答の値はここでは0です
fedeghe

なぜこれが多くの賛成を得たのかわからない... OPがreduce()から早期に中断する方法を尋ねたのでこれは答えではありません。それはあなたがかがむときに痛みがあるときに医者に行くようなものですそして医者は言いますかがまないように。
ricosrealm

12

あなたのような機能を使用することができますいくつか、すべてをあなたが戻り値を気にしない限り。すべてのコールバックがfalseを返し休憩、いくつかのそれがtrueを返すとき:

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

25
しかし、彼がやろうとしているのであればreduce、定義上、彼戻り値を気にます。

1
@ torazaburo —確かに、OPで使用されているとは思わないので、結果を得る方法は他にもあります。;-)
RobG 2016年

6

もちろん、の組み込みバージョンを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

1
あなたは使用することができ、遅延評価やCPSを望ましい行動を達成するために:
scriptum

この回答の最初の文は正しくありません。あなたは壊れることができます、詳細については以下の私の答えを見てください。
TobiahRex18年

4

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


1

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

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
}

6
これはおそらく、すべての答えの中で最も効率の悪い実行です。try / catchは、既存の実行コンテキストを中断し、実行の「スローパス」にフォールバックします。V8が内部で行う最適化に別れを告げます。
Evan Plaice 2018

5
極端ではありません。これはどう:if (current <= 0) window.top.close()
user56reinstatemonica8

0

promisesが持っているresolvereject、コールバックの引数、私が作成し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

ブレーク付きの機能バージョンの削減は、「変換」として実装できます。アンダースコアで。

configフラグを使用して実装しようとしました。これにより、実装のreduceで、現在使用しているデータ構造を変更する必要がなくなります。

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

使用法1、単純なもの

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

Usage2、内部変数としてconfigを使用

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

Usage3、構成を外部変数としてキャプチャ

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)

0

reduceメソッドの内部から抜け出すことはできません。あなたが達成しようとしていることに応じて、あなたは最終結果を変えることができます(これはあなたがこれをしたいと思うかもしれない1つの理由です)

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

注意:配列パラメータを直接再割り当てすることはできません

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

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

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


1
Re「後続の計算に影響を与える方法で引数値を直接変更することはできません」、それは真実ではありません。ECMA-262によると:配列の既存の要素が変更された場合、callbackfnに渡されるそれらの値は、reduceがそれらにアクセスしたときの値になります。元の配列を変更するのではなく、dに新しい値を割り当てているため、この例は機能しません。に置き換えd = [1, 1, 2]d[2] = 6、何が起こるかを確認してください。;-)
RobG 2017

-1

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

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
}

-1

以下のパターンを使用してreduceを使用してpromiseを順番にチェーンする場合:

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の内部または外部で発生した何かに応じて中断する必要があります。これは、最初のpromiseが実行される前にreduceループが終了し、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);
  });
}

-1

私は次のようにそれを解決しました、例えば、some短絡が大いに節約できる方法で:

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)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.