在 Javascript 中实现 Foldl 功能

Implement Foldl function in Javascript

我正在尝试编写一个在 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

这个:

array.slice(-1)

应该是:

array.slice(1)

slice(-1) returns 包含最后一个元素的数组。当您使用数组的第一个元素时,您希望数组不包含该元素。 slice(1) 将 return 没有第一个元素的数组。

slice(-1) return 只有最后一个元素。如果要递归数组,请改用 slice(1),这将 return 除第一个元素之外的所有元素:

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

如上所述,您面临的挑战是要返回最后一个元素的数组。而且你总是返回最后一个元素的数组。

上面的答案中缺少的是它们只适合向右弃牌。 对于正确的情况,您可以只使用 .slice(1),这将把所有内容都拉到头部之后。 对于 fold left 的情况,你需要指定你需要走多远 .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

请注意 foldlfoldr 可以 both 以先到先得的方式遍历输入列表的方式实现(左-向右)顺序。不需要笨拙的负索引或计算精确的 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)

我们的 foldlfoldr 分别是本地人 Array.prototype.reduceArray.prototype.reduceRight 的几乎完美的替代品。通过将 ixs 传递给回调,我们更接近

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

最后,reducereduceRight 接受一个 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 } ]