How to transform this Ruby code into Elixir code

I am new to Elixir and to learn it I try to convert a Ruby application (Genetic Algorithm) in Elixir.
A typical part of this code is:

def week_trend(days, histogram)
  weeks              =  days/5
  week_nr           =  1
  while week_nr <=  weeks - 1
    week_trend      =  histogram[week_nr] - histogram[week_nr - 1] # <-- This is line 5
    week_nr         += 1
  end
end

The main problem for me is line 5

Thanks in advance

1 Like

First a warning, I am not a Ruby programmer, barely ever touched it, I find it a horrid language that mutates all willy-nilly with monkey-patching (to use a python term) running utterly rampant in every ruby project Iā€™ve seen, so following thatā€¦ :slight_smile:

Greetings, first of all this forum uses Discourse as its hosting software, thus it supports github-like markdown, so your code block could appear like this by surrounding it with ``` fences. :slight_smile:

def week_trend(days, histogram)
  weeks = days/5
  week_nr = 1
  while week_nr <= weeks - 1
   week_trend = histogram[week_nr] - histogram[week_nr - 1]
   week_nr += 1
  end
end

And since Elixir is immutable the problem will need to be reformed a touch, so letā€™s start. :slight_smile:

def week_trend(days, histogram) do
  weeks = round(days/5)
  week_nr = 1
  # snip
end

We are good up until the snip part. Now the ā€˜whileā€™ loop is just iterating from weeks_nr to weeks-1, which can be shown in elixir as week_nr .. (weeks-1) to make a list (technically range) of integers starting with week_nr and ending with and on weeks-1. I will assume histogram is a map. Next, I have no clue what the week_trend variable is or where it comes from, but letā€™s assume it is a zero and will be returned, however it looks like it is being looped over and thus only the last iteration is being returned, Iā€™m not sure that was intended so for example purposes at least I will assume that it will be adding to it each time, thus it might be best to carry it ā€˜forwardā€™ in a calculation via Enum.reduce. Now reduce is defined as reduce(enumerable, acc, fun), where enumerable is something that can be iterated over, like a list/range. The acc is the accumulator, it is passed from call to call to ā€˜hold stateā€™, so we will put the week_trend into it. And lastly the ā€˜funā€™ is the function that will be called with two arguments, the accumulator from the last iteration or the initial one if the first run, and the value in the enumerable that is being enumerated over at that time, basically this is a standard loop (internally implemented via recursion, which you could do here too but that is not usual and far more wordy and verbose and often harms clarity), so letā€™s make the code now this:

def week_trend(days, histogram) do
  weeks = round(days/5)
  week_nr = 1
  week_trend = 0
  Enum.reduce(week_nr .. (weeks-1), week_trend, fn(week, week_trend) ->
    # 'loop' body
  end)
end

Now in the 'loop' body section we will alter the week_trend and return it, of which the last iteration will return the result from reduce directly, which since it is in the last place it will be returned from the function too:

def week_trend(days, histogram) do
  weeks = round(days/5)
  week_nr = 1
  week_trend = 0
  Enum.reduce(week_nr .. (weeks-1), week_trend, fn(week_nr, week_trend) ->
    week_trend + histogram[week_nr] - histogram[week_nr-1]
  end)
end

And putting that into a module named Tester and running it in iex I get:

iex> histogram = Enum.zip(0..30, 0..30) |> Enum.into(%{})
%{0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, 8 => 8,
  9 => 9, 10 => 10, 11 => 11, 12 => 12, 13 => 13, 14 => 14, 15 => 15, 16 => 16,
  17 => 17, 18 => 18, 19 => 19, 20 => 20, 21 => 21, 22 => 22, 23 => 23,
  24 => 24, 25 => 25, 26 => 26, 27 => 27, 28 => 28, 29 => 29, 30 => 30}
iex> Tester.week_trend(30, histogram)
5

So if I read the ruby right (entirely possible that I did not), then the above should be good I hope? :slight_smile:

4 Likes

I repaired markup a bit for you, this way it is much easier to read.

Also, what exactly is your problem with line 5? There should also be problems with line 1 (missing do), 4 (there is no while in elixir) and 6 (there is no += in elixir).

BUT! line 5 should be really no problem, assuming that the type that is used for histogram implements Access-behaviour.

1 Like

I think it is ruby code. :slight_smile:

I would have done so if I had access, but meh. Thanks! ^.^

2 Likes

how about:

def week_trend(days, histogram) do
  weeks = round(days/5)
  case Enum.slice(histogram, (weeks - 1)..weeks) do
    [a, b] ->
      b - a
    _ ->
      nil
    end
end

note: I havenĀ“t compiled this code

1 Like

Hi Tallakt,

I compiled and ran your solution. It did well.

Many Thanks

Thiel

1 Like

Hi Mr. OverminDL1,

By the way did you know Discourse were written in Ruby and Rails according to their web site.

I really appriciate your instructions to solve my problem. The ā€œonly thingā€ to learn is to make errors/mistakes. As a Elixir starter this is my way to learn by trial and error. But I enjoy it with guys like you.

Many thanks.

Thiel

Yep, I have fought with it on a site I used to run for a couple of years. ^.^

That is why I try to show how I build up to the solution, not just the solution itself. Personally I learn more by seeing how someone thinks through a problem instead of just seeing the results of their thought processes. :slight_smile:

Feel free to ask more questions as well, it grows the knowledge-base here. ^.^

I was looking at ruby syntax and Iā€™m not able to figure out what this is ā€˜supposedā€™ to do? It takes 2 arguments, an integer and an array/list/dict/whatever, and it returns a nil as the while loop is the last statement and without a break/return or such things it by default returns a nil, thus the function would be returning a nil, is this correct? Is the function supposed to do nothing?

As I do read it, it is basically mutating the histogram as a side-effect.

I donā€™t see it mutating the histogram, however it does indeed seem to return nil, I suppose that there are stuff happening after the while loop that Thiel omitted to keep things short.

See how it mutates histogram in this small and simplified example (irb session):

irb(main):001:0> def foo(s)
irb(main):002:1>   s[:a] = :b
irb(main):003:1> end
=> :foo
irb(main):004:0> m = {}
=> {}
irb(main):005:0> foo(m)
=> :b
irb(main):006:0> m
=> {:a=>:b}

But histogram is never on the LHS of =.

Yes but on the example above the histogram is never on the left side of =

That was precisely what was confusing me, I never saw mutation happening except for that one variable that was never used. I was kind of scared it was some ruby magic or something (I really do not like implictness, has bit me in the butt way too many times ^.^). :slight_smile:

Oh, I missed that :wink:

So the only possibility that remains is, that some of the variables on the LHS are in fact method calls to foo= of the class that the method belongs to, but that is again only a guess (but a more educated one)

Hi mr OverminDL1,

You are right the ruby code is not correct!

As I mentioned I just started to learn the elixir language and to
practice I choose to convert a Ruby program I wrote before. (But on my
age (74) and as a retired software engineer I had to choose a learning
strategy.)

This program is a Genetic Algorithm and it also involves the
manipulation of time series. In that context a basic problem is the
calculation of moving averages.

For example, how should I tranlate line 5 into elixir code?

I certainly will meet other transformation problems, but at the end of
the day I will have mastered Elixir.

Best regards,

Thiel

HI Nobbz,

You are right. See my reply to OverminDL1.

As long as your code uses constructs like side effects, mutation and the various loop-constructs, it will be hard to translate that ruby code.

Not only that you are fighting OOP vs. FP, but also you have the fight mutation vs immutability.

The looping stuff is only a minor problem, and will solve itself during the proposed process of learning elixir on top of ruby:

  1. Try to grasp the concepts of rubys Enumerable module.
  2. Rewrite all loops which mutate some outer variable into calls to #map, #reduce, or whatever feels appropriate from Enumerable. Do try to not mutate anything from outside of the block in it.
  3. Make all your objects methods which currently mutate your object return a new object which is basically a copy of your original one but having the mutations applied.
  4. Now it should be very easy and straightforward, to translate the ruby code to elixir code. Switching objects by structs and method calls by function calls.

Steps 1 to 3 will not only give you some deeper insight in ruby, but also into some core elixir concepts and idioms.

1 Like

Hello Thiel,

(Look bellow dotted line for your answer)

So in your Ruby program you have an Array type as a histogram.
In Elixir we have no Arrays instead we have two other types for similar purposes.
Lists and Tuples.

List is a collection of values and each one points to the next one in the collection.
We often use this when we want to iterate over all the values in our collection.
And look like this [1,2,3,4,5] # => This is a linked list.

Tuple is a collection of values similar to Array in ruby. We often use this when we want a collection where position matters.
Tuples look like this {1,2,3,4,5} # => This is a 5 item tuple.

Both Tuples and Lists have a number of advantages and disadvantages, if you want to use something closest to the Ruby Array then I think a Tuple is better suited.

You can already see that someone might get confused with Ruby and use a List somewhere where a Tuple is more appropriate.

So given the histogram is an Elixir Tuple you can write line five as follows.

# => week_nr  = 2
# => histogram = { 1,2,3,4,5 }
week_trend = elem(histogram, week_nr) - elem(histogram, week_nr - 1)


As I am writing this I realize that histogram is probably a ruby Hash, the equivalent in elixir is a Map.
Map is just a specialized tuple, you can read about it here Maps guide

With maps the above would be written as such

# => week_nr  = 2
# => histogram = %{ 1 => 2, 2 => 3, 3 => 5, 4 => 8, 5 => 42 }
week_trend = Map.fetch(histogram, week_nr) - Map.fetch(histogram, week_nr - 1)
# or simply
week_trend = histogram[week_nr] - histogram[week_nr - 1] # => Looks a lot like your code right? :smiley: 

So the problem was not with the code but with datatypes, you can learn about elixir types here Elixir Types

As @NobbZ noted though, you have to change the way you think about programs, FP is a whole different beast from OOP, so no matter what you learn about a language, if you keep the imperative/OO mindset you will find it really hard or impossible to solve many problems.

1 Like