small medium large xlarge

Foto_profilo_pragsmall
17 Aug 2017, 06:24
Massimiliano Bertinetti (5 posts)

Hi, I’m doing exercises and I find this solution for the Matchsticks problem:

defmodule MatchstickFactory do
    def boxes(number) do
        big = div(number, 50)
        medium = div( rem(number, 50), 20)
        small = div(number - ((big*50) + (medium*20)), 5)
        remaining = rem(number - ((big*50) + (medium*20)), 5)
        %{big: big, medium: medium, small: small, remaining_matchsticks: remaining}
    end
end

But I think is very ugly and not functional oriented…..

Kim_pragsmall
24 Aug 2017, 01:43
Kim Shrier (5 posts)

Your solution is not much different from the one in the code download in file code/work_with_functions/answers/exercise_5.ex. The main difference is that the number was updated between each box calculation. Rewriting your code to resemble the supplied answer would look something like this:

defmodule MatchstickFactory do
    def boxes(number) do
        big = div(number, 50)
        number = rem(number, 50)
        medium = div(number, 20)
        number = rem(number, 20)
        small = div(number, 5)
        number = rem(number, 5)

        %{big: big, medium: medium, small: small, remaining_matchsticks: number}
    end
end

This does essentially the same as your original code but is easier on the eyes.

I approached the problem a little differently. I am not saying that my approach is better but it is a different way to look at the problem.

I am a big fan of the pipeline operator |> and I tend to think in terms of transforming the data I have into the data I want. So the problem is, how do I transform 98 into %{big: 1, medium: 2, remaining_matchsticks: 3, small: 1}

My first stab at the solution used a pipeline of anonymous functions and looked like this:

defmodule MatchstickFactory do
  def boxes(matchsticks) do
    {%{}, matchsticks}
    |> (fn {acc, ms} -> {Map.put(acc, :big, div(ms, 50)), rem(ms, 50)} end).()
    |> (fn {acc, ms} -> {Map.put(acc, :medium, div(ms, 20)), rem(ms, 20)} end).()
    |> (fn {acc, ms} -> {Map.put(acc, :small, div(ms, 5)), rem(ms, 5)} end).()
    |> (fn {acc, ms} -> Map.put(acc, :remaining_matchsticks, ms) end).()
  end
end

I create a 2 element tuple with an empty map and a number of matchsticks. I pipe the tuple to a function that transforms the input tuple into a tuple containing a map with the number of big boxes, and the second element is the number of remaining matchsticks.

{ %{}, 89 } -> { %{big: 1}, 39 }

I have similar anonymous functions for medium and small.

The final anonymous function takes the map and adds the : remaining_matchsticks key with whatever the incoming matchstick count is and returns the updated map.

Like you, I think my original solution doesn’t look very good and is hard to follow.

I have 2 problems with my code. The anonymous functions are a little hard to follow and the syntax for invoking an anonymous function in a pipeline is (fn … end).(). The added parenthesis and dot clutter the code somewhat and having a list of anonymous functions gets hard to follow. I would define functions for big, medium, small, and remaining so that the code reads better even though more lines of code need to be written. I would make these functions private as they are particular to my module implementation and probably not useful outside the module.

The second problem is that I dislike constants that get used in multiple places that don’t have a name. Is the 50 in the div expression related to the 50 in the rem expression? In this case they are but that may not always be the case if the value 50 shows up in other places in the code. I like to use module attributes to clarify the use of constants.

After making the modifications, I have the following:

defmodule MatchstickFactory do

  @size_big 50
  @size_medium 20
  @size_small 5

  def boxes(matchsticks) do
    {%{}, matchsticks}
    |> big
    |> medium
    |> small
    |> remaining
  end

  defp big({acc, ms}), do: {Map.put(acc, :big, div(ms, @size_big)), rem(ms, @size_big)}

  defp medium({acc, ms}), do: {Map.put(acc, :medium, div(ms, @size_medium)), rem(ms, @size_medium)}

  defp small({acc, ms}), do: {Map.put(acc, :small, div(ms, @size_small)), rem(ms, @size_small)}

  defp remaining({acc, ms}), do: Map.put(acc, :remaining_matchsticks, ms)
end

This is more code than the original but I think it is easier to read.

Avatar_pragsmall
27 Aug 2017, 19:01
Ulisses Herrera Freire de Almeida (2 posts)

Great post Kim Shrier.

I liked you different way of implementing. In Chatper 02, we didn’t reached advanced usage of maps and |> operator, it’s nice to see how these techniques can be used in the first exercises. I really liked your suggestions about the module attributes like constants, make total sense. I’ll update the book with it. I came up with a third solution like this:

defmodule MatchstickFactory do
  @size_big 50
  @size_medium 20
  @size_small 5

  def boxes(matchsticks) do
    {big_boxes, remaining} = put_in_boxes(matchsticks, @size_big)
    {medium_boxes, remaining} = put_in_boxes(remaining, @size_medium)
    {small_boxes, remaining} = put_in_boxes(remaining, @size_small)

    %{
      big: big_boxes,
      medium: medium_boxes,
      small: small_boxes,
      remaining_matchsticks: remaining
    }
  end
  
  # I didn't use a private function because we'll see only in chapter 03
  def put_in_boxes(matchsticks, box_size) do
    {div(matchsticks, box_size), rem(matchsticks, box_size)}
  end
end

What do you think?

Foto_profilo_pragsmall
30 Aug 2017, 12:06
Massimiliano Bertinetti (5 posts)

Thank you Kim….I don’t see the code so I don’t see the solutions to the exercises lol………

Thank you Ulisses for this book I find really clear and different from other.

Kim_pragsmall
05 Sep 2017, 03:10
Kim Shrier (5 posts)

Ulisses, your solution looks good. Given what you have introduced about Elixir at this point in the book, it is a better solution than what I proposed.

You must be logged in to comment