【问题标题】:Implement Foldl function in Javascript在Javascript中实现Foldl函数
【发布时间】:2018-09-25 01:17:18
【问题描述】:

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

【问题讨论】:

    标签: javascript recursion functional-programming fold


    【解决方案1】:

    请注意,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 } ]

    【讨论】:

    • 在严格评估的语言中,右折叠并不是一个有意义的概念,因为它给我们的唯一好处就是破坏堆栈。在 Haskell 中,foldr 可以短路(除其他外),前提是可以忽略acc。我们可以在 JS 中使用显式 thunk 来获得类似的行为,但是,这需要 foldr 是柯里化形式,并且 accf 的最后一个参数。 See more。 +1
    • @ftor 我还没有机会阅读该链接,但foldr 只有在f可交换 时才会产生相同的结果。考虑熟悉的示例 foldl (comp, ...)foldr (comp, ...)。稍后我回家时会查看该链接^^
    • 如果只有交换性很重要,你可以随时求助于flip,对吧?
    • @ftor,你的意思是问foldr (f, ...)foldl (flip (f), ...)一样吗?
    【解决方案2】:

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

    上面的答案缺少的是它们只适合向右折叠。 对于正确的情况,您可以只使用.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
    

    【讨论】:

    • 非常感谢您对 foldl 和 foldr 案例的解释!!!我的知识存在差距。
    • foldr 是右关联的,这意味着折叠函数是在递归调用的结果上调用的,即return f(head, foldr(f, acc, tail))foldl 是左关联的,这意味着首先计算列表的头部。通常f 中的参数顺序会改变以反映这一点。
    • 很好的答案,它有助于理解差异。但是拥有foldr 我会把foldl 改成foldl = (f, acc, arr) => foldr(f, acc, [...arr].reverse())
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      这个:

      array.slice(-1)
      

      应该是:

      array.slice(1)
      

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

      【讨论】:

      • 但是制作 Slice(-1) 会让代码按照 Foldr 逻辑运行对吧?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-08-26
      • 2021-06-05
      • 1970-01-01
      • 2016-09-04
      • 1970-01-01
      • 2012-01-07
      • 1970-01-01
      相关资源
      最近更新 更多