如何早破减少()方法?

如何打破 reduce()方法的迭代?

返回文章页面

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)
104800 次浏览

只要不关心返回值,就可以使用像 一些每个这样的函数。返回 false 时 每个中断,返回 true 时 一些中断:

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

剪辑

一些评论说“ this doesn’t do what reduce does”,这是真的,但它可以。下面是一个以类似于 减少的方式使用 每个的示例,减少在达到中断条件时立即返回。

// Soruce data
let data = [0,1,2,3,4,5,6,7,8];


// Multiple values up to 5 by 6,
// create a new array and stop processing once
// 5 is reached


let result = [];


data.every(a => a < 5? result.push(a*6) : false);


console.log(result);

这样可以工作,因为来自 用力的返回值是推送新元素之后 结果数组的长度,这个长度总是1或更大(因此为 true) ,否则返回 false,循环停止。

当然,没有办法让内置版本的 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

不能从 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);

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

不要使用还原剂。只需使用普通迭代器(for 等)对数组进行迭代,当满足条件时就会中断。

我带来的另一个解决同样问题的简单实现:

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
}

您可以通过抛出一个异常来破坏每个代码——因此也破坏迭代器中的每个构建:

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
}

对象的任何迭代都可以中断。通过更改 reduce 函数的第4个参数: “ array”来调用 reduce ()。不需要定制的 reduce 函数。有关 .reduce()参数的完整列表,请参见 医生

Reduce (((acc,curr,i,array))

第4个参数是正在迭代的 数组

const array = ['apple', '-pen', '-pineapple', '-pen'];
const x = array
.reduce((acc, curr, i, arr) => {
if(i === 2) arr.splice(1);  // eject early
return acc += curr;
}, '');
console.log('x: ', x);  // x:  apple-pen-pineapple

为什么:

我能想到的使用它而不是其他许多解决方案的唯一原因是,如果您希望在算法中维护函数式编程方法,并且希望使用最具声明性的方法来实现这一点。如果您的整个目标是逐字地将一个数组减少到一个替代的非虚假原语(字符串、数字、布尔值、符号) ,那么我认为这实际上是最好的方法。

为什么不呢?

有一个完整的参数列表,使不变化的函数参数,因为这是一个坏的做法。


更新

一些评论家提出了一个很好的观点,即原始数组正在发生变异,以便在 .reduce()逻辑的早期就被打破。

因此,我修改了答案 有点,在调用后续的 .reduce()步骤之前添加了一个 .slice(0),生成了原始数组的一个副本。 注意 : 完成相同任务的类似操作是 slice()(不太明确)和扩展操作符 [...array](表现稍逊一筹)。请记住,所有这些都为整个运行时 ... + O(n)增加了一个额外的线性时间常数因子。

该副本用于保护原始数组不受最终导致迭代弹出的突变的影响。

const array = ['apple', '-pen', '-pineapple', '-pen'];
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:  apple-pen-pineapple
// original Arr:  ['apple', '-pen', '-pineapple', '-pen']

由于 promise具有 resolvereject回调参数,因此我使用 break回调参数创建了 reduce变通函数。它使用与本机 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;
}

下面是 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);

如果你想连锁承诺与减少使用下面的模式:

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

但是需要根据一个诺言里面或外面发生的事情来打破 事情变得有点复杂,因为 reduce 循环在执行第一个允诺之前就被终止了,这使得截断允诺回调中的数组变得毫无用处,我最终得到了这个实现:

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

具有中断的减少功能版本可以实现为“转换”,例如在下划线中。

我尝试用一个配置标志来实现它,这样实现 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')
})

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

我解决它如下,例如在 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,它决定是否中断

如果不需要返回数组,也许可以使用 some()

使用 some而不是当你想自动中断。送一个 this蓄电池。当创建箭头函数时,测试和累积函数 不能是箭头函数,因为它们的 this被设置。

const array = ['a', 'b', 'c', 'd', 'e'];
var accum = {accum: ''};
function testerAndAccumulator(curr, i, arr){
this.tot += arr[i];
return curr==='c';
};
accum.tot = "";
array.some(testerAndAccumulator, accum);


var result = accum.tot;

在我看来,如果你不需要返回一个数组(例如在一个数组运算符链中) ,这是一个更好的解决方案,因为你不需要改变原始数组,你也不需要复制它,这对大型数组来说是不利的。

因此,要更早地终止使用的习惯用法应该是 arr.splice (0)。 这就提出了一个问题,为什么在这种情况下不能使用 arr = []呢? 我试了一下,reduce 忽略了任务,继续不变的工作。 Reduce 习惯用法似乎对拼接之类的表单有响应,但对赋值运算符? ? ? 之类的表单没有响应?- 完全不直观-而且必须死记硬背,作为函数式编程信条中的戒律..。

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

问题是,在蓄电池内部不可能停止整个过程。因此,通过设计,外部范围内的某些东西必须被操纵,这总是导致必要的突变。

正如许多其他人已经提到的 throwtry...catch并不是一个真正的方法,可以称为 “解决方案”。它更像是一种带有许多不必要副作用的黑客行为。

完成这个 没有任何变异的唯一方法是使用第二个比较函数,该函数决定是继续还是停止。为了避免 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

您可以使用 try... catch 退出循环。

try {
Things.reduce(function(memo, current){
if(current <= 0){
throw 'exit loop'
//break ???
//return; <-- this will return undefined to memo, which is not what I want
}
}, 0)
} catch {
// handle logic
}

您可以编写自己的 reduce 方法。像这样调用它,所以它遵循相同的逻辑,并且您可以控制自己的转义/中断解决方案。它保留了功能风格,并允许打破。

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