OK. I will start with an “.exs” file and declare a module to solve the problem. I’ll go ahead and put the data points into a module attribute and we’ll declare a function solve_problem()
that we hope will solve the exercise (but for now we’ll just inspect the list).
defmodule Solution do
@data_points [
["2018-12-01", "AM", "ID123", 5000],
["2018-12-01", "AM", "ID545", 7000],
["2018-12-01", "PM", "ID545", 3000],
["2018-12-02", "AM", "ID545", 7000]
]
def solve_problem do
@data_points
end
end
IO.inspect(Solution.solve_problem())
First we want to separate the data points out and group them by date. We can use the Enum.group_by
function to accomplish that to group the list of lists. The element we want to group by is the first item in the list so I’ll go ahead and use the List.first
function to get at it. group_by
can also transform the result so let’s drop the date from the collected data points:
def data_points_by_date(data_points) do
Enum.group_by(data_points, &List.first/1, &Enum.drop(&1, 1))
end
The result of passing the data points to that function is:
%{
"2018-12-01" => [
["AM", "ID123", 5000],
["AM", "ID545", 7000],
["PM", "ID545", 3000]
],
"2018-12-02" => [["AM", "ID545", 7000]]
}
A map is a collection of key-value pairs. In this case the key is the date and the value is a list of data points. We want to group the data points in each value by their “meridians” (AM or PM). We can repeat the same use of Enum.group_by
and List.first
again on the values. In our group_by
transform we might also collect the numbers from the list. Let’s do that with pattern matching on the lists of three values.
…
def group_by_meridian({date, data_points}) do
{date, Enum.group_by(data_points, &List.first/1, fn [_, _, number] -> number end}
end
def solve_problem do
@data_points
|> data_points_by_date()
|> Enum.into(%{}, &group_by_meridian/1)
end
yields:
%{
"2018-12-01" => %{"AM" => [5000, 7000], "PM" => [3000]},
"2018-12-02" => %{"AM" => [7000]}
}
And we’re pretty close. We just need to sum up the values in the lists of numbers instead of returning the lists themselves. We can change group_by_meridian
to do that using techniques we’ve already seen. We can build the map of meridians by pulling the expression out into a variable:
def group_by_meridian({date, data_points}) do
numbers_by_meridian = Enum.group_by(data_points, &List.first/1, fn([_, _, number]) -> number end)
{date, numbers_by_meridian}
end
(so numbers_by_meridian
will look something like %{"AM" => [5000, 7000], "PM" => [3000]
)
Now we can use the trick of doing a Enum.map
over the key value pairs in numbers_by_meridian
where we change the list of numbers into sums. (I actually use Enum.into
again which does the Enum.map
under the covers). To calculate the sums we use Enum.reduce
and pass in the binary function “+” (or Kernel.+
). The function looks like this at first blush:
def group_by_meridian({date, data_points}) do
numbers_by_meridian = Enum.group_by(data_points, &List.first/1, fn([_, _, number]) -> number end)
sums_by_meridian = Enum.into(numbers_by_meridian, %{}, fn {meridian, numbers} -> {meridian, Enum.reduce(numbers, 0, &+/2)} end)
{date, sums_by_meridian}
end
And we can clean up things a bit:
defmodule Solution do
@data_points [
["2018-12-01", "AM", "ID123", 5000],
["2018-12-01", "AM", "ID545", 7000],
["2018-12-01", "PM", "ID545", 3000],
["2018-12-02", "AM", "ID545", 7000]
]
# Take the data points and create a map grouping them by date
def data_points_by_date(data_points) do
Enum.group_by(data_points, &List.first/1, &Enum.drop(&1,1))
end
def group_by_meridian({date, data_points}) do
sums_by_meridian = data_points
|> Enum.group_by(&List.first/1, &extract_number/1)
|> Enum.into(%{}, &sum_numbers/1)
{date, sums_by_meridian}
end
def solve_problem do
@data_points
|> data_points_by_date()
|> Enum.into(%{}, &group_by_meridian/1)
end
defp extract_number([_, _, number]), do: number
defp sum_numbers({meridian, numbers_list}), do: {meridian, Enum.reduce(numbers_list, &+/2)}
end
IO.inspect(Solution.solve_problem())
yielding:
%{
"2018-12-01" => %{"AM" => 12000, "PM" => 3000},
"2018-12-02" => %{"AM" => 7000}
}