1

I have following code:

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

I know it is easy to implement a recursive foldl0 with an iterative logic by defining a helper method iter(list, func, part_result) and store the result as an argument. But how to implement foldl0 without helper method just like foldr0's implementation?


NOTICE: I wrote this problem with Javascript for convenience. Please solve it with car and cdr, thanks.

Cowsay
  • 71
  • 4

3 Answers3

1

We're going to look at the computational process evolved by each generic foldr and foldl. Seeing the implementations below, compare how f gets the result of foldr, but foldl gets the result of 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 )
            )

Let's look closer at the process now – in foldr, you can see how acc (0) gets passed all the way down the call stack before any f is ever computed

// 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 )

The call stack for foldl is much different – notice how the acc is used immediately

// 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

So why are we looking at these generics? Well the point is to show you what the computational process looks like. It's in the visualization of the process that you can see how data moves through the program.

In foldr you can see how acc is immediately passed all the way down the call stack – so you could effectively drop the acc parameter and replace acc with 0 and there is your foldr0 – which is precisely what you have

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

However, that's not the case with foldl – a new acc is computed in each step and it's needed to compute the next step, so we can't just drop the parameter and replace with 0 like we did with foldr. Instead, the easiest (and smartest) implementation becomes

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

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

This doesn't meet your criteria of "no helper method", but that's not really a real thing. The exercise is likely meant to show you why it's challenging to implement a left fold without an auxiliary helper; as a means of helping you visualize process.


tl:dr;

JavaScript is full of all sorts of tricks though, so you can cheat to pass your class and prevent yourself from learning anything!

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 )
Mulan
  • 129,518
  • 31
  • 228
  • 259
0

Just use exactly the same approach as in foldr0 but split the array 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
  }
}

Of course this would be much easier if you had foldl/foldr functions with a parameter for the initial accumulator value.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I'm sorry that I cannot access the list like an array in my situation. The problem is edited. Thanks. – Cowsay Nov 17 '17 at 16:12
  • In that case, I think it's not probably without a proper ternary `fold` function. – Bergi Nov 17 '17 at 19:28
0

A very simplistic approach by using an Haskellesque pattern matching by the rest operator could be done as follows;

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));
Redu
  • 25,060
  • 6
  • 56
  • 76