我正在尝试编写一个在JavaScript中实现foldl的函数。我试图在函数中使用递归但不能实现它。
var foldl = function(f, acc, array) {
if (array.length == 0) {
return acc;
} else {
return f(array[0], foldl(f, acc, array.slice(-1)));
}
}
console.log(foldl(function(x, y) {
return x + y
}, 0, [1, 2, 3]));
console.log(foldl(function(x,y){return x+y}, 0, [1,2,3]));
错误消息 ..
RangeError: Maximum call stack size exceeded
答案 0 :(得分:1)
此:
array.slice(-1)
应该是:
array.slice(1)
slice(-1)
返回包含最后一个元素的数组。当您使用数组的第一个元素时,您需要不使用该元素的数组。 slice(1)
将返回没有第一个元素的数组。
答案 1 :(得分:1)
如上所述,您的挑战是您返回最后一个元素的数组。而且你总是返回最后一个元素的数组。
上述答案中缺少的是它们只适合折叠到右边。
对于正确的情况,你可以使用.slice(1)
,这将把所有内容都拉到头后。
对于左侧折叠,您需要指定需要走多远.slice(0, arr.length - 1)
。
const foldr = (f, acc, arr) => {
if (!arr.length) {
return acc;
} else {
const head = arr[0];
const tail = arr.slice(1);
return foldr(f, f(acc, head), tail);
}
};
foldr((x, y) => x + y, 0, [1, 2, 3])// 6
const foldl = (f, acc, arr) => {
if (!arr.length) {
return acc;
} else {
const head = arr[arr.length - 1];
const tail = arr.slice(0, arr.length - 1);
return foldl(f, f(acc, head), tail);
}
};
foldl((x, y) => x + y, 0, [3, 2, 1]); // 6
答案 2 :(得分:0)
slice(-1)
仅返回最后一个元素。如果要递归数组,请改用slice(1)
,这将返回除第一个之外的所有元素:
var foldl = function(f, acc, array) {
if (array.length == 0) {
return acc;
} else {
return f(array[0], foldl(f, acc, array.slice(1)));
}
}
console.log(foldl(function(x, y) {
return x + y
}, 0, [1, 2, 3]));

答案 3 :(得分:0)
请注意,foldl
和foldr
可以两者以一种从头到尾(从左到右)的顺序通过输入列表进行迭代的方式实现。不需要尴尬的负面指数或计算精确的slice
位置。
const Empty =
Symbol ()
const foldl = (f, acc, [ x = Empty, ...xs ]) =>
x === Empty
? acc
: foldl (f, f (acc, x), xs)
const foldr = (f, acc, [ x = Empty, ...xs ]) =>
x === Empty
? acc
: f (foldr (f, acc, xs), x)
const pair = (a,b) =>
`(${a} ${b})`
const data =
[ 1, 2, 3 ]
console.log (foldl (pair, 0, data))
// (((0 1) 2) 3)
console.log (foldr (pair, 0, data))
// (((0 3) 2) 1)
如果您不想使用解构分配,可以使用xs[0]
和xs.slice(1)
const foldl = (f, acc, xs) =>
xs.length === 0
? acc
: foldl (f, f (acc, xs[0]), xs.slice (1))
const foldr = (f, acc, xs) =>
xs.length === 0
? acc
: f (foldr (f, acc, xs.slice (1)), xs [0])
const pair = (a,b) =>
`(${a} ${b})`
const data =
[ 1, 2, 3 ]
console.log (foldl (pair, 0, data))
// (((0 1) 2) 3)
console.log (foldr (pair, 0, data))
// (((0 3) 2) 1)
...xs
通过第一个解决方案中使用的解构分配和第二个解决方案中使用的slice
创建中间值,如果xs
具有相当大的规模,则可能会受到影响。下面是第三个避免这个问题的解决方案
const foldl = (f, acc, xs, i = 0) =>
i >= xs.length
? acc
: foldl (f, f (acc, xs[i]), xs, i + 1)
const foldr = (f, acc, xs, i = 0) =>
i >= xs.length
? acc
: f (foldr (f, acc, xs, i + 1), xs [i])
const pair = (a,b) =>
`(${a} ${b})`
const data =
[ 1, 2, 3 ]
console.log (foldl (pair, 0, data))
// (((0 1) 2) 3)
console.log (foldr (pair, 0, data))
// (((0 3) 2) 1)
我们的foldl
和foldr
上方几乎是本地Array.prototype.reduce
和Array.prototype.reduceRight
的完美替代品。通过将i
和xs
传递给回调,我们会更加接近
const foldl = (f, acc, xs, i = 0) =>
i >= xs.length
? acc
: foldl (f, f (acc, xs[i], i, xs), xs, i + 1)
const foldr = (f, acc, xs, i = 0) =>
i >= xs.length
? acc
: f (foldr (f, acc, xs, i + 1), xs[i], i, xs)
const pair = (acc, value, i, self) =>
{
console.log (acc, value, i, self)
return acc + value
}
console.log (foldl (pair, 'a', [ 'b', 'c', 'd' ]))
// a b 0 [ b, c, d ]
// ab c 1 [ b, c, d ]
// abc d 2 [ b, c, d ]
// => abcd
console.log (foldr (pair, 'z', [ 'w', 'x', 'y' ]))
// z y 2 [ x, y, z ]
// zy x 1 [ x, y, z ]
// zyx w 0 [ x, y, z ]
// => zyxw
最后,reduce
和reduceRight
接受 context 参数。如果折叠函数f
引用this
,这一点很重要。如果您想在自己的折叠中支持可配置的上下文,这很容易
const foldl = (f, acc, xs, context = null, i = 0) =>
i >= xs.length
? acc
: foldl ( f
, f.call (context, acc, xs[i], i, xs)
, xs
, context
, i + 1
)
const foldr = (f, acc, xs, context = null, i = 0) =>
i >= xs.length
? acc
: f.call ( context
, foldr (f, acc, xs, context, i + 1)
, xs[i]
, i
, xs
)
const obj =
{ a: 1, b: 2, c: 3, d: 4, e: 5 }
// some function that uses `this`
const picker = function (acc, key) {
return [ ...acc, { [key]: this[key] } ]
}
console.log (foldl (picker, [], [ 'b', 'd', 'e' ], obj))
// [ { b: 2 }, { d: 4 }, { e: 5 } ]
console.log (foldr (picker, [], [ 'b', 'd', 'e' ], obj))
// [ { e: 5 }, { d: 4 }, { b: 2 } ]