Q:

Transform data structure

I'm new to Elixir and functional programming and I'm trying to map data from an Ecto query into a map to be used as data for Chart.js. This is what I have so far:
Ecto query:
result = Repo.all(
    from s in SaleWithDays,
    group_by: [s.year, s.month],
    order_by: [s.year, s.month],
    select: %{
      year: s.year,
      month: s.month,
      revenue: fragment("round(sum(revenue)::numeric, 0)::integer")
    }
  )

The map should have this structure: 
%{
   labels = [01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12],
   datasets: [
   %{
     label: "2012",
     data: [12365, 123259, 843202, 812132, 532326, ...]
   },
   %{
     label: "2013",
     data: [12365, 123259, 843202, 812132, 532326, ...]
   }]
 }

And this is my attempt:
years = Enum.uniq(Enum.map(result, fn(sale) ->
  sale.year
end))

Enum.each(years, fn(year) ->
  dataset = %{
    label: year,
    data: []
  }

  Enum.each(1..12, fn(month) ->
    current_sale = Enum.filter(result, fn(sale) ->
      sale.year == year && sale.month == month
    end)

    if length(current_sale) == 0 do
      dataset.data ++ [0]
    else
      dataset.data ++ [Enum.at(current_sale, 0).revenue]
    end
  end)

  IO.puts inspect(dataset)
end)

Every month needs to be present in the list so if I don't get a value from the database the value for that month should be 0.
I guess my problem is that data is immutable, but I'm out of ideas.
Thanks

A:

The problem you are having is with the scoping rules which I explain in Fill a List with a for-loop.
I haven't changed your original code too much in order to answer your question. To fix your issue you can change your Enum.each to Enum.map/2 in both cases to build up your dataset.
output = Enum.map(years, fn(year) ->

  data = Enum.map(1..12, fn(month) ->
    current_sale = Enum.filter(result, fn(sale) ->
      sale.year == year && sale.month == month
    end)

    if length(current_sale) == 0 do
      0
    else
      Enum.at(current_sale, 0).revenue
    end
  end)

  %{
    label: year,
    data: data
  }
end)

IO.inspect output

It is worth noting that your solution makes several traversals through your dataset and I am sure optimal solution exists. If I get some time later I will have a look at implementing a solution with fewer passes.

