如何提前终止reduce()方法?

159

我如何打破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)

1
以上代码中的current是什么?我看不出它们如何执行相同的操作。无论如何,还有一些可能会提前跳出循环的方法,如someeveryfind - elclanrs
someevery返回布尔值,而find返回单个记录,我想要的是运行操作以生成备忘录。current是当前值。参考 - Julio Marins
我的意思是第一段代码中的 current 是什么? - elclanrs
3
回答是你无法在 reduce 中提前退出,你需要使用内置函数来提前退出或者创建自己的帮助函数,或者使用lodash或其他库。请问您能否提供一个完整的示例说明您想要做什么? - elclanrs
@Cristy,当然可以,但是数组元素可以以任何顺序添加并且可以移动。此外,数组只是对象,因此它们的属性之所以有顺序的概念,是因为它们的索引值,例如for..in可能会以任何顺序返回值(IE曾经按照添加顺序返回)。;-) - RobG
显示剩余6条评论
17个回答

0
你可以编写自己的 reduce 方法。像这样调用它,这样它就遵循相同的逻辑,并且你可以控制自己的 escape/break 解决方案。它保留了函数式风格并允许中断。

const reduce = (arr, fn, accum) => {
  const len = arr.length;
  let result = null;
  for(let i = 0; i < len; i=i+1) {
    result = fn(accum, arr[i], i)
    if (accum.break === true) {
      break;
    }
  }
  return result
}

const arr = ['a', 'b', 'c', 'shouldnotgethere']
const myResult = reduce(arr, (accum, cur, ind) => {
  accum.result = accum.result + cur;
  if(ind === 2) {
    accum.break = true
  }
  return accum
}, {result:'', break: false}).result

console.log({myResult})

或者创建您自己的reduce递归方法:

const rcReduce = (arr, accum = '', ind = 0) => {
  const cur = arr.shift();
  accum += cur;
  const isBreak = ind > 1
  return arr.length && !isBreak ? rcReduce(arr, accum, ind + 1) : accum
}

const myResult = rcReduce(['a', 'b', 'c', 'shouldngethere'])
console.log({myResult})


0

所以,为了更早地终止,使用的习惯用法将是arr.splice(0)。 这引发了一个问题,为什么不能在这种情况下只使用arr = []? 我试过了,reduce忽略了赋值,继续不变。 Reduce习惯用法似乎响应splice等形式,但不响应赋值运算符之类的形式??? - 完全不直观 - 必须作为函数式编程信条中的预先学习的准则...

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


2
使用arr.splice()将会影响到当前数组。而通过赋值,原数组仍然存在于内存中且未被修改,你只是将变量的引用改为一个全新的数组。reduce()不会在意引用的改变,因为它在自己的引用中持有未经修改的数组(例如你的示例中的acccurr)。 - Randy Hall
@RandyHall acccurr不是对数组的引用 - 因为acc是返回值,而curr是当前元素。arr是对未被篡改的数组的引用 - 当然,在内部它会使用this(或引擎的非JS等效方式)。 - somebody

0
问题在于,在累加器内部无法仅停止整个过程。因此,按设计,必须操作外部范围中的某些内容,这总是导致必要的变异。
正如许多其他人已经提到的那样,使用try...catch的throw不是真正可以称之为“解决方案”的方法。它更像是一个带有许多不良副作用的黑客。
唯一的无需任何变异就能实现的方法是使用第二个比较函数,该函数决定是否继续或停止。为了仍然避免for循环,必须使用递归来解决。
代码:
function reduceCompare(arr, cb, cmp, init) {
    return (function _(acc, i) {
        return i < arr.length && cmp(acc, arr[i], i, arr) === true ? _(cb(acc, arr[i], i, arr), i + 1) : acc;
    })(typeof init !== 'undefined' ? init : arr[0], 0);
}

这可以像这样使用:

var arr = ['a', 'b', 'c', 'd'];

function join(acc, curr) {
    return acc + curr;
}

console.log(
    reduceCompare(
        arr,
        join,
        function(acc) { return acc.length < 1; },
        ''
    )
); // logs 'a'

console.log(
    reduceCompare(
        arr,
        join,
        function(acc, curr) { return curr !== 'c'; },
        ''
    )
); // logs 'ab'

console.log(
    reduceCompare(
        arr,
        join,
        function(acc, curr, i) { return i < 3; },
        ''
    )
); // logs 'abc'

我将其制作成了一个npm库,同时包含TypeScript和ES6版本。随意使用:

https://www.npmjs.com/package/array-reduce-compare

或在GitHub上:

https://github.com/StefanJelner/array-reduce-compare


0

可以使用“transform”来实现带有中断的简化功能版本,例如在underscore中。

我尝试使用配置标志来停止它,以便实现简化的实现不必更改您当前正在使用的数据结构。

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,将配置作为内部变量使用

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)

-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

我解决了它,例如在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)

更新

更通用的答案可能是以下内容

const escReduce = (arr, fn, init, exitFn) => {
    try {
      return arr.reduce((...args) => {
          if (exitFn && exitFn(...args)) {
              throw args[0]
          }
          return fn(...args)
        }, init)
    } catch(e){ return e }
}

escReduce(
  Array.from({length: 100}, (_, i) => i+1),
  (acc, e, i) => acc * e,
    1,
    acc => acc > 1E9 
); // 6227020800

我们可以传递一个可选的exitFn,它决定是否中断。


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接