Grading (with) Elixir

The last Elixir exercise from exercism.io was short but sweet. It involved working with maps and an interesting problem of transforming one map to another, while changing the inner structure of map value(s).

To make things concrete, the task was to, starting with people grouped by grades, build the same grade grouped list of people, but this time sort them alphabetically. So, one would start with:

%{4 => ["Christopher","Jennifer", "Bart"], 6 => ["Kareem", "Anna"]}

and expect the sorted map:

%{4 => ["Bart", "Christopher","Jennifer"], 6 => ["Anna", "Kareem"]}

Now, this isn’t much of an issue code wise, but I expected there to be a method like Enum.map/2 that would “remap” the values easily, but there isn’t. So, the only viable solution I could find was to actually use Enum.reduce/3:

@spec sort(Dict) :: Dict.t
def sort(db) do
  db |> Enum.reduce(%{}, fn({grade, names}, acc) ->
    Map.put(acc, grade, Enum.sort(names))
  end)
end

This essentially introduces an accumulator map that gets filled with same keys and transformed values. Nice, but kind of disappointed it didn’t support direct mapping.

EDIT: Per Martin’s hint, there is a way to use Enum.map/2, but one needs to use it in combination with Enum.into/2, like this:

@spec sort(Dict) :: Dict.t
def sort(db) do
 db |> Enum.map(fn({grade, names}) -> {grade, Enum.sort(names)} end) |> Enum.into(%{})
end

Another thing that became obvious today was that the ternary operator macro is a function, and you can use it like:

if(condition, do: x, else: y)

or

if condition, do: x, else: y

A subtle difference, but can help with readability. E.g. the last one is going to need () around it in some complex expressions.

And finally, another nice thing (comming from Javascript and Ruby I really like it) is that you can use “||” like this:

@spec grade(Dict.t, pos_integer) :: [String]
def grade(db, grade) do
  db[grade] || []
end

So, if there is a key in the map, get it’s value, otherwise return some default value, an empty list in this case.

And, as always, you can find the entire code sample at Github project.

Advertisements
Tagged , ,

5 thoughts on “Grading (with) Elixir

  1. There is an `Enum.map/2`. If your map is in `db`, then `Enum.map(db, fn {grade, names} -> {grade, Enum.sort(names)} end)` will sort the names.
    http://elixir-lang.org/docs/stable/elixir/Enum.html#map/2

    • elvanja says:

      Hey Martin, thanks for the tip!

      I tried it, but the output is not as expected:

           Assertion with == failed
           code: expected == actual
           lhs:  %{3 => ["Kyle"], 4 => ["Bart", "Christopher", "Jennifer"], 6 => ["Kareem"]}
           rhs:  [{3, ["Kyle"]}, {4, ["Bart", "Christopher", "Jennifer"]}, {6, ["Kareem"]}]
           stacktrace:
             grade_school_test.exs:66
      

      Maybe there is a way to convert the Enum.map output to expected map format, but couldn’t find any.

      • You can pipe the result of `Enum,map/2` on to `Enum.into(%{})`, which uses the collectable protocol.

        Another nice way of doing this is with a for-comprehension:

        for {grade, names} <- input_map, do: {grade, Enum.sort(names)}, into: %{}

      • elvanja says:

        Cool, works like a charm. Thank you for the tip!

  2. […] In “Grading (with) Elixir” I complained that using Enum.reduce/3 was a bit cumbersome for a simple transform of map values. Funny enough, the last exercism.io problem I’ve been playing with, the Transform part of ETL kind of hints why Enum.reduce/3 is just the right tool for the job after all. Let see why. […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: