1

I am trying to count how many times a number there are in each range from range1 to range4. Range 1 is 1-10, range 2 is 11-20, range 3 is 21-30, and range 4 is 31-40. What I have below is:

range1=0
range2=0
range3=0
range4=0
arr=[1,11,2,22,33,23]
Enum.each arr, fn(x) ->
  if x >=1 and x<=10 do
    range1=range1+1
  end
  if x>=11 and x<=20 do
    range2=range2+1
  end
  if x>=21 and x<=30 do
    range3=range3+1
  end
  if x>=31 and x<=40 do
    range4=range4+1
  end
end 

IO.puts range1
IO.puts range2
IO.puts range3
IO.puts range4

However when I run it, it'll say that they're all 0. Why is it doing that?

1
  • 1
    As Aleksei details below - you need to radically change programming idiom to benefit from Elixir Commented Mar 17, 2021 at 10:37

3 Answers 3

7

is an immutable language. One cannot change the value of a variable. Ever.

One might rebind a variable, but it would never leak to the outer scope, so what are you doing is basically binding rangeN in an internal scope to immediately discard it. The variable (having the same name by coincidence) from the outer scope never gets changed.

We don’t have loops in either. We use map/reduce instead.

That said, what you need is Enum.reduce/3

Enum.reduce(
    [1,11,2,22,33,23], # input
    {0, 0, 0, 0},      # initial accumulator
    fn x, {acc1, acc2, acc3, acc4} ->
  cond do
    x>=1 and x<=10 -> {acc1 + 1, acc2, acc3, acc4}
    x>=11 and x<=20 -> {acc1, acc2 + 1, acc3, acc4}
    x>=21 and x<=30 -> {acc1, acc2, acc3 + 1, acc4}
    x>=31 and x<=40 -> {acc1, acc2, acc3, acc4 + 1}
  end
end)
#⇒ {2, 1, 2, 1}

The function always returns the result of the last expression, hence we are to use cond/1 here. But the idiomatic way would be to have multiple clauses of the accumulating function with guards.

Enum.reduce [1,11,2,22,33,23], {0, 0, 0, 0}, fn 
  x, {acc1, acc2, acc3, acc4} when x>=1 and x<=10 ->
    {acc1 + 1, acc2, acc3, acc4}
  x, {acc1, acc2, acc3, acc4} when x>=11 and x<=20 ->
    {acc1, acc2 + 1, acc3, acc4}
  x, {acc1, acc2, acc3, acc4} when x>=21 and x<=30 ->
    {acc1, acc2, acc3 + 1, acc4}
  x, {acc1, acc2, acc3, acc4} when x>=31 and x<=40 ->
    {acc1, acc2, acc3, acc4 + 1}
end 

To get to the values one might either pattern match the result or use Kernel.elem/2.

{less_that_10, _, _, _} =
  Enum.reduce(...)
less_than_10
#⇒ 2

input
|> Enum.reduce(...)
|> elem(0)
#⇒ 2
Sign up to request clarification or add additional context in comments.

5 Comments

How can I access the value of acc1 after its been changed?
I am not sure I understood the question. Assign the value to a local variable, perhaps.
So what I wanted to do after was, I want to print the new value of acc1, which would now be 2
After reduce is done, or each time it gets increased?
Pattern match the result or use |> elem(0).
1

when I run it, it'll say that they're all 0. Why is it doing that?

Variables in Elixir are bound to the current scope. If you bind to a variable inside a function (such as the anonymous function you passed to Enum.each/2 in your code), that variable is only available inside that function. Although you assign to range1 inside the anonymous function, that's a completely different variable to the range1 you defined at the top of the file, which is always 0.

How to increment a value in a loop in elixir

This is the wrong way of thinking about it. What you need to think about is processing lists. For your case, I think combining Enum.frequencies_by/2 with guards and ranges is a good solution:

Enum.frequencies_by([1, 11, 2, 22, 33, 23], fn
  i when i in 1..10 -> :range1
  i when i in 11..20 -> :range2
  i when i in 21..30 -> :range3
  i when i in 31..40 -> :range4
end)

This produces:

%{range1: 2, range2: 1, range3: 2, range4: 1}

2 Comments

Enum.frequencies_by([1, 11, 2, 22, 33, 23], &div(&1, 10)) #⇒ %{0 => 2, 1 => 1, 2 => 2, 3 => 1} :)
We also can have a macro that gets ranges and produces the clauses in compile-time :))
1

If I could make the assumption that your ranges are fixed, and that your inputs are greater than zero, I propose this solution:

[range1,range2,range3,range4] =
   for elem <- arr, reduce: [0,0,0,0] do
     answer -> 
       update_in(answer, [Access.at!(div((elem - 1), 10))], &(&1 + 1))
   end

Access.at!() conveniently will throw an exception for indices outside of the range of the output array. In other words, an input of 45 will generate an index of 4, which is outside of the range of indices for [0,0,0,0]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.