【问题标题】:How to implement this foldl0 function without helper method?如何在没有辅助方法的情况下实现这个 foldl0 函数?
【发布时间】:2017-11-17 12:28:21
【问题描述】:

我有以下代码:

function foldr0(list, func) {
  if (list.length == 0) {
    return 0;
  } else {
    return func(list[0], foldr0(list.slice(1), func));
  }
}

function foldl0(list, func) {
  if (list.length == 0) {
    return 0;
  } else {
    return ?
  }
}

我知道通过定义辅助方法iter(list, func, part_result) 并将结果存储为参数,很容易实现具有迭代逻辑的递归foldl0。但是如何像foldr0的实现一样在没有辅助方法的情况下实现foldl0呢?


注意:为了方便起见,我用 Javascript 编写了这个问题。请用carcdr解决,谢谢。

【问题讨论】:

  • 这是什么编程语言,JavaScript?

标签: functional-programming implementation fold foldleft


【解决方案1】:

我们将研究由每个通用 foldrfoldl 演变而来的计算过程。看下面的实现,对比一下f是如何得到foldr的结果,而foldl是如何得到f的结果

const foldr = ( f , acc , xs ) =>
  isEmpty (xs)
    ? acc
    : f ( foldr ( f , acc , tail ( xs ) )
        , head ( xs )
        )

const foldl = ( f , acc , xs ) =>
  isEmpty (xs)
    ? acc
    : foldl ( f
            , f ( acc , head ( xs ) )
            , tail ( xs )
            )

现在让我们仔细看看这个过程 - 在 foldr 中,您可以看到 acc (0) 在计算任何 f 之前如何一直沿调用堆栈向下传递

// example call
foldr ( f , 0 , [ 1 , 2 , 3 ] )

// notice we can't run f yet, still waiting for foldr
f ( foldr ( f , 0 , [ 2 , 3 ] )
  , 1
  )


// now 2 f calls pending; still waiting on foldr
f ( f ( foldr ( f , 0 , [ 3 ] )
      , 2
      )
  , 1
  )

// now 3 f calls pending, still waiting on foldr
f ( f ( f ( foldr ( f , 0 , [] )
          , 3
          )
      , 2
      )
  , 1
  )

// now we can compute the inner most f, working our way out
f ( f ( f ( 0
          , 3
          )
      , 2
      )
  , 1
  )

// in other words, foldr traverses your input and creates a stack of f calls
f ( f ( f ( 0 , 3 ) , 2 ) , 1 )

foldl 的调用堆栈大不相同——注意 acc 是如何立即使用的

// pretend f = ( x , y ) => x + y
foldl ( f 
      , 0
      , [ 1 , 2 , 3 ]
      ) 

// this time f is called first, and the result becomes the next acc
foldl ( f
      , f ( 0 , 1 ) // next acc = 1
      , [ 2 , 3 ]
      )

// f is always computed before recurring
foldl ( f
      , f ( 1 , 2 ) // next acc = 3
      , [ 3 ]
      )

// notice how foldl stays nice and flat (foldl uses a tail call, foldr doesn't)
foldl ( f
      , f ( 3 , 3 ) // next acc = 6
      , []
      )

// when the input is empty, the acc is just returned
foldl ( f
      , 6
      , [] // empty input
      )

// result
6

那么我们为什么要研究这些泛型呢?那么重点是向您展示计算过程的样子。在流程的可视化中,您可以看到数据如何在程序中移动。

foldr 中,您可以看到acc 是如何立即沿调用堆栈向下传递的——因此您可以有效地删除acc 参数并将acc 替换为0,然后您的@987654340 @ - 这正是你所拥有的

const foldr0 = ( f , xs ) =>
  isEmpty (xs)
    ? 0
    : f ( foldr0 ( f , tail ( xs ) )
        , head ( xs )
        )

但是,foldl 的情况并非如此——在每个步骤中都会计算一个 new acc,并且需要计算 next 步骤,所以我们不能只需删除参数并替换为0,就像我们对foldr 所做的那样。相反,最简单(也是最聪明)的实现变成了

const foldl0 = ( f , xs ) => 
  foldl ( f , 0 , xs )

const foldr0 = ( f , xs ) =>
  foldr ( f , 0 , xs )

这不符合您“无辅助方法”的标准,但这并不是真的。该练习可能旨在向您展示为什么在没有辅助助手的情况下实现左折叠具有挑战性;作为帮助您可视化流程的一种方式。


tl:dr;

不过,JavaScript 充满了各种各样的技巧,所以你可以作弊以通过你的课程并阻止自己学习任何东西!

const isEmpty = xs =>
  xs.length === 0
  
const head = ( [ x , ... xs ] ) =>
  x
  
const tail = ( [ x , ... xs ] ) =>
  xs

const foldl0 = ( f , xs , acc = 0 ) =>
  isEmpty (xs)
    ? acc
    : foldl0 ( f
             , tail ( xs )
             , f ( acc , head ( xs ) )
             )
        
const myFunc = ( x , y ) =>
  `( ${x} + ${y} )`
  
console.log ( foldl0 ( myFunc , [ 1 , 2 , 3 ] ) ) 
// ( ( ( 0 + 1 ) + 2 ) + 3 )

【讨论】:

    【解决方案2】:

    只需使用与foldr0 完全相同的方法,但拆分数组to the other side

    function foldl0(list, func) {
      if (list.length == 0) {
        return 0;
      } else {
        return func(list[list.length-1], foldr0(list.slice(0, -1), func));
    //                   ^^^^^^^^^^^^^                     ^^^^^
    //                       last                       without last
      }
    }
    

    当然,如果您有带有初始累加器值参数的 foldl/foldr 函数,这会容易得多。

    【讨论】:

    • 很抱歉,在我的情况下,我无法像数组一样访问列表。问题已编辑。谢谢。
    • 在这种情况下,我认为可能没有适当的三元 fold 函数。
    【解决方案3】:

    一种非常简单的方法,通过使用由 rest 运算符匹配的 Haskellesque 模式,可以如下完成;

    var foldl0 = ([x0,x1,...xs],f) => xs.length ? foldl0([f(x0,x1)].concat(xs),f)
                                                : f(x0,x1);
    
    console.log(foldl0([1,2,3,4], (x,y) => x + y));

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-29
      • 2022-01-08
      • 1970-01-01
      • 2019-01-09
      • 1970-01-01
      • 2022-01-14
      • 1970-01-01
      • 2016-08-11
      相关资源
      最近更新 更多