Convert ruby while loop into elixir

Hi to all
I’m new to elixir and I’m learning it. I have a question maybe noob question but I did not find a solution.

p = [1.01, 1.02, 1.04]
i= 1.01
while p.any? {|x| i == x } do
     i += 0.01
    puts i
end

sh-4.3$ ruby main.rb                                                                                                                                
1.02                                                                                                                                                
1.03 

Thanks in advance

Probably the most literal translation would be:

defmodule Looper do
  def loop(p, i) do
    case i in p do
      true -> loop(p, i + 0.01)
      false -> i
    end
  end
end

p = [1.01, 1.02, 1.04]
i = 1.01

Looper.loop(p, i)

There’s likely other ways to do it. Recursion generally solves most scenarios where you’d use a while loop.

2 Likes

The obvious ‘other way’ to do it would be using streams:

defmodule Looper do
  def loop(p, i) do
    i
    |> Stream.iterate(fn x -> x + 0.01 end)
    |> Stream.take_while(&Kernel.in(&1, p))
    |> Enum.to_list
    |> List.last
  end
end

but in this simple case it would generate a lot more overhead than the plain recursive version, thus being slower.

1 Like

It does not work as expected:

Looper.loop(p,  i)                          
1.02

Add a IO.puts i before the loop call in the case expression so that you get the following:

true ->
  IO.puts i
  loop p, i + 0.01

I would use a Map.reduce to solve this.

p = [1.01, 1.02, 1.04]
i = 1.01

Enum.reduce(p, i, fn 
  x, acc when x == acc -> 
    acc = acc + 0.01 
    IO.puts acc
    acc
  _, acc -> acc + 0.01
end) 

If your not familiar with multi clause anonymous functions, here is another solution:

p = [1.01, 1.02, 1.04]
i = 1.01

Enum.reduce(p, i, fn x, acc -> 
  new_acc = acc + 0.01
  if x == acc do
    IO.puts new_acc
  end
  new_acc
end)

Here is the output from iex

iex(20)> p = [1.01, 1.02, 1.04]
[1.01, 1.02, 1.04]
iex(21)> i = 1.01
1.01
iex(22)> Enum.reduce(p, i, fn
...(22)>   x, acc when x == acc ->
...(22)>     acc = acc + 0.01
...(22)>     IO.puts acc
...(22)>     acc
...(22)>   _, acc -> acc + 0.01
...(22)> end) ; nil
1.02
1.03
nil
iex(23)>

Note the ; nil at the end is just to stop the result of the reduce from printing in iex…

This isn’t equivalent to the original though. The original iterated over the value not the list.

Your right. My bad.

I think in this case it is definitely better to follow the KISS principle and go for the straightforward recursive loop which is way simpler than the other alternatives. :grin:

2 Likes

I’ve been playing with a while macro here https://hex.pm/packages/while , it can make the code look very very similar to the ruby version:

    import While

    p = [1.01, 1.02, 1.04]
    i = 1.01
    while_with i, Enum.any?(p, &(i == &1)) do
      IO.puts i
      i + 0.01
    end

This while macro can be installed by adding while to your list of dependencies in mix.exs:

def deps do
  [
    {:while, "~> 0.1.0"}
  ]
end

I think it’s interesting that that can even work, I’m curious what the macro is doing. Definitely not a recommended solution though. Functional code is a lot more idiomatic and clear.

EDIT:

Ah in looking at the examples I see that if you want i to carry on with the value it has to be rebound, that makes more sense:

  cnt = while cnt, cnt < 10 do
    cnt + 1
  end
1 Like