Iterating over a list with a certain condition (sorry, I am very new to functional programming)

Hello everyone,

I am new to elixir and had questions regarding iterating over a list using Enum.each(). I want to iterate over a list and append items that satisfy a certain condition to another list.

I provided the code below and commented using pseudocode on what I mean by this.

def test do
        list = [1,2,3,4,5,6,7,8,9]
        p1 = []
        p2 = []
        p3 = []
        Enum.each(list, fn(i) -> 
        #if (i) is at index 0 or 2 add (i) to [p1]
        #if (i) is at index 1 or 3 add (i) to [p2]
        #else add (i) to [p3]
        end)
    end

Is this possible to do using Enum.each() or is there another preferred way of doing such a problem?

Variables in Elixir are immutable, so you won’t be able to modify a list from Enum.each/2 like you would in an imperative programming language like C or Java.

What you’re looking for can be achieved with Enum.reduce/3 instead.

I recommend you go through the guides at Elixir School to get a little more familiar with Elixir and functional programming in general before you continue:

They also have a segment specifically for the Enum module:

2 Likes

Hello and welcome,

list = [1,2,3,4,5,6,7,8,9]

result = list
|> Enum.with_index()
|> Enum.reduce({[], [], []}, fn {el, i}, {p1, p2, p3} ->
  cond do
    i == 0 || i == 2 -> {[el | p1], p2, p3}
    i == 1 || i == 3 -> {p1, [el | p2], p3}
    true -> {p1, p2, [el | p3]}
  end
end)

IO.inspect result
{[3, 1], [4, 2], [9, 8, 7, 6, 5]}

You even can remove the ugly cond with

list
|> Enum.with_index()
|> Enum.reduce({[], [], []}, fn
  {el, i}, {p1, p2, p3} when i == 0 or i == 2 ->
    {[el | p1], p2, p3}
  {el, i}, {p1, p2, p3} when i == 1 or i == 3 ->
    {p1, [el | p2], p3}
  {el, _}, {p1, p2, p3} ->
    {p1, p2, [el | p3]}
end)

As mentionned, You need to think differently :slight_smile:

Mainly because of immutability, but also scope.

You might need to reverse list if needed.

1 Like

thanks!!

If you really only need special treatment for the first 4 elements of list then there’s no need to even iterate. You can do one pattern match and then build the p1 and p2.

iex(106)> list = [1,2,3,4,5,6,7,8,9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
iex(107)> [p10, p21, p12, p23 | p3] = list
[1, 2, 3, 4, 5, 6, 7, 8, 9]
iex(108)> p1 = [p10, p12]
[1, 3]
iex(109)> p2 = [p21, p23]
[2, 4]
iex(110)> p3
[5, 6, 7, 8, 9]

2 Likes

didnt even think of this, thanks!

Welcome to the world of Elixir! Without answering your question directly, I see where you are coming from. So let’s take a step back and explain immutability as you will need to understand that first before anything else.

A variable is a binding between a name and a memory location. By executing echo $name the computer does actually echo #memory_location_17458

The code you have written assumes mutability. That is: you can change the value of a variable (=memory location). In many languages that can be done the way you wrote the function. But it has side effects.

Say p1 is bound to #memory_location_17458. What if function2() runs at the same time and also puts values in variable p1 (= #memory_location_17458)? Once p1 returns the end result stored in #memory_location_17458 , you are surprised to find numbers of function1() and function2() in your list! They both wrote to the same memory location.

Elixir is based on immutability. Once a memory location has a value, it can’t be changed. However, what we can do is store a new value in a new memory address and let name p1 point to the new memory location! With one important fact: the new p1 is only bound to the new memory location within the current function scope.

So in the example with two functions, here is some pseudo code, using curly braces to make the scopes easier to spot.

p1 = [ ] 

execute function1(p1) -> { p1 = p1 + 3 }
execute function2(p1) -> { p1 = p1 + 8 }

echo p1
  • both receive p1 as #memory_location_17458.
  • as soon as function1() changes the value of p1 by adding ‘3’ to the list is does not change #memory_location_17458 but it stores the new value in another memory address (say: #memory_location_11111) and points the p1 in it’s own scope to the new address.
  • if function2() now would look at ‘his’ p1 it would still be #memory_location_17458 which is an empty list.
  • as soon as function2() changes the value of p1 by adding ‘8’ is does not change #memory_location_17458 but it it stores the new value in another memory address (say: #memory_location_222222) and points the p1 in it’s own scope to the new address.

We now have 3 times the p1 name bound to 3 different memory addresses.

The original p1 in global scope bound to #memory_location_17458 which has [ ]
The p1 in scope of function1 bound to #memory_location_11111 which has [ 3 ]
The p1 in scope of function2 bound to #memory_location_22222 which has [ 8 ]

  • When we print p1 at the last line, we are in the same scope as the first time we created p1 so it still references #memory_location_17458 and prints [ ] cause that is the value of that memory address.

Advantage
Every function can run at the same time as they will never(!!!) influence other variables (=memory address).

Conclusion
Keep reading about this until you understand it well. As understanding immutability is key to understanding Elixir. Once you understand how immutability works, start reading some blogs about tail recusion (the technique that makes for-loops in Elixir without influencing memory addresses :wink: ).

Hope this helps a bit. I will edit my post when the Elixir gods correct me :smiley:

4 Likes
def test do
        list = [1,2,3,4,5,6,7,8,9]
        {first4, p3} = Enum.split(list, 4)
        #if (i) is at index 0 or 2 add (i) to [p1]
        p1 = Enum.take_every(first4, 2) 
        #if (i) is at index 1 or 3 add (i) to [p2]
        p2 = Enum.drop_every(first4, 2)
        #else add (i) to [p3]
end

You need to unlearn loops.

The same but easier to understand (I think)

iex(106)> list = [1,2,3,4,5,6,7,8,9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
iex(107)> [a, b, c, d | rest] = list
[1, 2, 3, 4, 5, 6, 7, 8, 9]
iex(108)> p1 = [a, c]
[1, 3]
iex(109)> p2 = [b, d]
[2, 4]
iex(110)> p3 = rest
[5, 6, 7, 8, 9]
1 Like