如何提早打破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返回一条记录,我想要的是运行操作以生成备忘录。current是currentValue。参考
Julio Marins

我的意思是current第一段代码是什么?
elclanrs

更新,感谢您的回复
Julio Marins

2
答案是您不能早日摆脱reduce困境,您将不得不找到另一种方法,即内置函数可以提前退出或创建您自己的助手,或使用lodash或其他工具。您可以张贴您想做的完整示例吗?
Elclanrs

Answers:


93

更新

一些注释者很好地指出了要对原始数组进行变异,以便在.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函数的第4个参数“ array”中断.reduce()调用的任何迭代。无需自定义的reduce函数。有关参数的完整列表,请参阅文档.reduce()

Array.prototype.reduce((acc,curr,i,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

为什么?:

我可以考虑使用此方法而不是提出的许多其他解决方案的唯一原因是,如果您想为算法维护一种功能性的编程方法,并且希望采用最具说明性的方法来实现这一目标。如果您的整个目标是从字面上减少一个数组到另一个非伪原语(字符串,数字,布尔值,符号),那么我认为这实际上是最好的方法。

为什么不?

对于不改变函数参数的情况,有很多参数可供选择,因为这是一种不好的做法。


3
+1。这应该是公认的答案。但是,出于“为什么不”中所述的原因,永远不要使用此解决方案。
johndodo

3
这确实是错误的建议,因为splice执行了可见的突变(array)。根据功能范式,您可以使用减少连续传递样式,也可以使用带有右关联减少的惰性评估。或者,作为一种更简单的选择,只需进行简单的递归即可。

坚持,稍等! 通过更改reduce函数的第4个参数:“ array”不是正确的语句。在这种情况下,它之所以会发生(答案中的示例)是因为在将其切到单个长度数组(第一个元素)的同时,它已经达到了索引2,显然是下一次,对于索引3,它不会迭代一个项(因为您正在将原始引用更改为长度为1的数组。如果您执行的弹出操作也会使源数组也发生变异,但不会在两者之间停下来(如果您不在倒数第二个索引处)。
Koushik Chatterjee

@KoushikChatterjee我的陈述对我的隐含含义是正确的。您的明确含义不正确。您应该就修改语句提出建议,以包括您的观点,我将进行修改,因为这将改善总体答案。
Tobiah Rex

1
我喜欢联系点差算子来避免任何不必要的突变,[... array]
.reduce

16

不要使用reduce。只需使用普通的迭代器(例如for)对数组进行迭代,并在满足条件时爆发。


58
有趣的地方在哪里?:)
亚历山大·米尔斯

2
@AlexanderMills可能他喜欢当一名指挥官!
dimpiax

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

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

该答案的第一句话不正确。您可以休息,请参阅下面的我的答案以获取详细信息。
Tobiah Rex

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
这可能是所有答案中效率最低的执行方式。尝试/捕获会破坏现有的执行上下文,并退回到执行的“慢路径”。告别V8进行的所有优化。
伊万·普赖斯

5
还不够极端。怎么样:if (current <= 0) window.top.close()
user56reinstatemonica8

0

由于promise具有resolvereject回调参数,我reduce使用break回调参数创建了变通方法。它采用与本机reduce方法相同的所有参数,除了第一个是要处理的数组(避免猴子打补丁)。第三个[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;
}

这是reducerArraymethod修改后的脚本:

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

用法2,使用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')
})

用法3,将配置捕获为外部变量

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方法内部中断。根据您要完成的工作,您可以更改最终结果(这是您可能要这样做的原因之一)

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
关于“您不能以影响后续计算的方式直接更改参数值”,这是不正确的。ECMA-262说:如果更改了数组的现有元素,则传递给callbackfn的值将是reduce访问它们时的值。您的示例不起作用,因为您要为d分配一个新值,而不修改原始数组。替换d = [1, 1, 2]d[2] = 6,看看会发生什么。;-)
RobG '17

-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内部或外部发生的事情进行中断,事情变得更加复杂,因为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);
  });
}

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.