如何在没有帮助方法的情况下实现这个foldl0函数?

时间:2017-11-17 12:28:21

标签: functional-programming implementation fold foldleft

我有以下代码:

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 ?
  }
}

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

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

3 个答案:

答案 0 :(得分: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中,您可以看到acc0)如何在任何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替换为{ {1}}并且有0 - 这正是你所拥有的

foldr0

然而,const foldr0 = ( f , xs ) => isEmpty (xs) ? 0 : f ( foldr0 ( f , tail ( xs ) ) , head ( xs ) ) 不是这种情况 - 在每个步骤中计算 new acc,并且需要计算 next 步骤,因此我们可以不要像我们使用foldl那样删除参数并替换为0。相反,最简单(最智能)的实现变为

foldr

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

TL:博士;

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

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

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

答案 1 :(得分:0)

使用与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函数带有初始累加器值的参数,这将会容易得多。

答案 2 :(得分:0)

通过使用其余运算符的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));