Enum.reduce_while vs Enum.reduce

Dear Team,
Below are two functions. And i am beginner in ELIXIR
a) The first one uses Enum_reduce_while
b) the second function uses Enum_reduce

I am observing the following
a) in the first function teh count goes from 0 , 1 , 2, 3 and gets reset (Basically for each row, the count gets reset) when i see the values using print. I was expecting the count to keep incrementing.
b) However in the 2nd function the count keeps incremeting (which is expected). The only difference is that the first one is reduce_while.

Questions
a) We may say that the 2nd Enum’s return is not assigned in Enum_reduce_while. But that’s the same i have done in the next function.

Why is this so and what am i missing?

Sincerely,
Arvind
def is_power_pattern_present_in_grid(grid,pattern,max_row,max_col) when length(pattern) <= max_row*max_col do
count = 0
result = 0
{chk, chk1} =
Enum.reduce_while(0…max_row-1, {result,count}, fn row,{result,count} →
Enum.reduce_while(0…max_col-1, {result,count}, fn col,{result,count} →
IO.puts(“looping throughat #{inspect(row)}, #{inspect(col)},#{inspect(count)}”)
count = count + 1
result = 0
{:cont,{result,count}}
end)
{:cont,{result,count}}
end)
end

def is_power1_pattern_present_in_grid(grid,pattern,max_row,max_col) when length(pattern) <= max_row*max_col do
count = 0
result = 0
{chk, chk1} =
Enum.reduce(0…max_row-1, {result,count}, fn row,{result,count} →
Enum.reduce(0…max_col-1, {result,count}, fn col,{result,count} →
IO.puts(“looping throughat #{inspect(row)}, #{inspect(col)},#{inspect(count)}”)
count = count + 1
result = 0
{result,count}
end)
end)
end

Hi, @SudarsanD you can use
```
your code
```
to format your code example like this:

def foo(x) do
  x * 2
end
3 Likes

In the first function the outer Enum.reduce does not get the result from the inner Enum.reduce. To align the functions, the first function would have to look like this:

  def is_power_pattern_present_in_grid(_grid, pattern, max_row, max_col)
      when length(pattern) <= max_row * max_col do
    count = 0
    result = 0

      Enum.reduce_while(0..(max_row - 1), {result, count}, fn row, {result, count} ->
        {result, count} = Enum.reduce_while(0..(max_col - 1), {result, count}, fn col, {result, count} ->
          IO.puts("looping throughat #{inspect(row)}, #{inspect(col)},#{inspect(count)}")
          count = count + 1
          result = 0
          {:cont, {result, count}}
        end)

        {:cont, {result, count}}
      end)
  end

Another tip: take a look in the docs for Comprehensions - The Elixir programming language

3 Likes

Building on this you’re re-using the same variable names in both loops, which can lead to confusion when coming from a mutable language because you may be expecting the inner loop to affect the outer loop’s variables, but Elixir sees your loop as the way I renamed the acc values:

Enum.reduce_while(0..(max_row - 1), {result, count}, fn row, {result_out, count_out} ->
  Enum.reduce_while(0..(max_col - 1), {result_out, count_out}, fn col, {result_in, count_in} ->
    IO.puts("looping throughat #{inspect(row)}, #{inspect(col)},#{inspect(count_in)}")

    count_in = count_in + 1
    result_in = 0
    {:cont, {result_in, count_in}}
  end)

  {:cont, {result_out, count_out}}
end)

That makes it clear that you’re not re-binding the _out values with the result of the inner loop. Every time through, the inner loop gets the original {:cont, {0, 0}} unless you re-bind the return of the inner loop to the outer’s accumulator variables.

Enum.reduce_while(0..(max_row - 1), {result, count}, fn row, {result_out, count_out} ->
  {result_out, count_out} =
    Enum.reduce_while(0..(max_col - 1), {result_out, count_out}, fn col, {result_in, count_in} ->
      IO.puts("looping throughat #{inspect(row)}, #{inspect(col)},#{inspect(count_in)}")

      count_in = count_in + 1
      result_in = 0
      {:cont, {result_in, count_in}}
    end)

  {:cont, {result_out, count_out}}
end)

Whereas in the reduce function, the inner loop’s return value gets set as the outer loop’s accumulator automatically because it is the last value in the loop. Hope that makes sense.

3 Likes

Thanks a lot for the replies. However what i dont understand is that even in Enum.reduce_while, the last line executed is either {:cont, {result,count}}. Why in that case the inner loop’s return value is not set as the outer loop’s accumulator?.

Sincerely,
Arvind

From the docs:

The return value for fun is expected to be

  • {:cont, acc} to continue the reduction with acc as the new accumulator or
  • {:halt, acc} to halt the reduction

If fun returns {:halt, acc} the reduction is halted and the function returns acc. Otherwise, if the enumerable is exhausted, the function returns the accumulator of the last {:cont, acc}.

Whether you :halt or let it run its course through the whole loop by only returning :cont, it’s still only going to output the acc to the outer loop.

You must catch the acc value that it returns and explicitly include it in the outer loop’s {:cont, ...} so that the outer loop can also know to continue.

The inner Enum.reduce_while returns {:cont, {result,count}. However we simply take the last 2 values of it. Is it a special case? . I tried to write a function which returns 3 values, but assign the functions’ output to two values and the compilation throws an error.

how come in this case, this is not shown as an error?

Sincerely,
Sudarsan.D

It does not return the :cont part, you use that as part of the implementation of reduce_while to tell it to keep looping. Since you never tell it to :halt it gets to the end of the loop and returns only the accumulator {result_in, count_in} to the outside loop. At that point you want to bind the returned tuple to the outer variables by pattern matching, which happens at the {result_out, count_out} = Enum.reduce_while line

Thanks a lot. This helps me to understand Enum.reduce_while. But why this kind of binding the returned tuple to the outer variables is not needed when we use Enum.reduce?.

Specifically you mentioned “Whereas in the reduce function, the inner loop’s return value gt set as the outer loops accumulator automatically because it is the last value in the loop”. Now even with reduce_while, the inner_loop return value is {result_out,count_out}. This should have been set automatically to the outer loop’s accumulator?. Why this is not happening with reduce_while.

Sincerely,
Sudarsan.D

Sincerely,
Sudarsan.D

I’m sorry that I couldn’t explain that better. It happens that the inner reduce already returns the same shape expected by the outer one, but reduce_while is different because you have to set either :halt or :cont explicitly so you mist capture the return values of the inner loop and create the :cont tuple.

I’ll see if I can provide some better examples tomorrow (I’m on mobile now) this all has to do with the fundamental concept of immutability, as well as understanding how these two functions differ in implementation.

Hi @SudarsanD , the result of a function is the last expression. In your Enum.reduce example the last expression is the result from the inner Enum.reduce. In your Enum.reduce_while example the last expression is {:cont, {result, count}} and result and count are exactly the same bindings as the inputs of the anonymous function for the outer Enum.reduce_while. The anonymous function for the inner Enum.reduce_while shadows the vars result and count. That means the vars result and count for the inner function and the outer function are different bindings.

I have also simplified your example:

defmodule Foo do
  def bar1(as, bs) do
    Enum.reduce(as, 0, fn a, count ->
      IO.inspect([count: count, a: a], label: :outer)

      Enum.reduce(bs, count, fn b, count ->
        IO.inspect([count: count, a: a, b: b], label: :inner)
        count = count + 1
        count
      end)
    end)
  end

  def bar2(as, bs) do
    Enum.reduce(as, 0, fn a, count ->
      IO.inspect([count: count, a: a], label: :outer)

      Enum.reduce_while(bs, count, fn b, count ->
        IO.inspect([count: count, a: a, b: b], label: :inner)
        count = count + 1
        {:cont, count}
      end)
    end)
  end

  def bar3(as, bs) do
    Enum.reduce_while(as, 0, fn a, count ->
      IO.inspect([count: count, a: a], label: :outer)

      Enum.reduce_while(bs, count, fn b, count ->
        # The "inner" count shadows the "outer" count
        IO.inspect([count: count, a: a, b: b], label: :inner)
        count = count + 1
        {:cont, count}
      end)

      {:cont, count}
    end)
  end

  def bar4(as, bs) do
    Enum.reduce_while(as, 0, fn a, count ->
      IO.inspect([count: count, a: a], label: :outer)

      count = Enum.reduce_while(bs, count, fn b, count ->
        # The "inner" count shadows the "outer" count
        IO.inspect([count: count, a: a, b: b], label: :inner)
        count = count + 1
        {:cont, count}
      end)

      {:cont, count}
    end)
  end
end
iex(1)> Foo.bar1(0..1, 0..1)
outer: [count: 0, a: 0]
inner: [count: 0, a: 0, b: 0]
inner: [count: 1, a: 0, b: 1]
outer: [count: 2, a: 1]
inner: [count: 2, a: 1, b: 0]
inner: [count: 3, a: 1, b: 1]
4
iex(2)> Foo.bar2(0..1, 0..1)
outer: [count: 0, a: 0]
inner: [count: 0, a: 0, b: 0]
inner: [count: 1, a: 0, b: 1]
outer: [count: 2, a: 1]
inner: [count: 2, a: 1, b: 0]
inner: [count: 3, a: 1, b: 1]
4
iex(3)> Foo.bar3(0..1, 0..1)
outer: [count: 0, a: 0]
inner: [count: 0, a: 0, b: 0]
inner: [count: 1, a: 0, b: 1]
outer: [count: 0, a: 1]
inner: [count: 0, a: 1, b: 0]
inner: [count: 1, a: 1, b: 1]
0
iex(4)> Foo.bar4(0..1, 0..1)
outer: [count: 0, a: 0]
inner: [count: 0, a: 0, b: 0]
inner: [count: 1, a: 0, b: 1]
outer: [count: 2, a: 1]
inner: [count: 2, a: 1, b: 0]
inner: [count: 3, a: 1, b: 1]
4

In Foo.bar3 you can see that the accumulator for the outer Enum.reduce_while is not changed because the result of the inner Enum.reduce_while is lost. In Foo.bar4 the accumulator is updated with the result of the inner Enum.reduce_while.

2 Likes