在Javascript中实现Foldl函数

时间:2018-04-15 02:42:52

标签: javascript recursion functional-programming fold

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

4 个答案:

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

请注意,foldlfoldr可以两者以一种从头到尾(从左到右)的顺序通过输入列表进行迭代的方式实现。不需要尴尬的负面指数或计算精确的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 } ]