Building map from list of columns and list of values

I’m trying to implement an efficient generic function to map the results from a Postgrex call into a map. Any suggestions?

  • Example result from postgrex:
%Postgrex.Result{
   columns: ["first_column", "second_column"],
   command: :select,
   num_rows: 4,
   rows: [["a", 1], ["b", 2], ["c", 3], ["d", 4]]
 }
  • Desired output
[
%{first_column: "a", second_column: 1},
%{first_column, "b", second_column: 2},
%{first_column, "c", second_column: 3},
%{first_column, "d", second_column: 4}
]

What have you tried?

1 Like

I’ve been trying to use Enum.map with an inner Enum.with_index fetching the columns but it doesn’t make sense when I need to build a map and it also seems quite inefficient…
(I’m a noob)

You do not need to index the lists. They already fit together.

iex(1)> Enum.zip(["first_column", "second_column"], ["a", 1])
[{"first_column", "a"}, {"second_column", 1}]
2 Likes

No worries about being a noob, but as a general note for future questions it’s also best to show the code that you’ve tried so that we can help you learn.

The most succinct way to do this is with Enum.zip. Assuming you have the struct you listed as a result variable you can do:

Enum.map(result.rows, fn row ->
  result.columns
  |> Enum.zip(row)
  |> Map.new()
end)
1 Like

Thank you!

Yeah I know, my brain is just still wired to thinking in for i loops so my code never made any sense…

I’d highly recommend starting with a simpler question and seeing if you can build it from scratch without Enum, it’ll help with those for i cobwebs. Given:

columns = ["first_column", "second_column"]
values = ["a", 1]

How would you build a function that returned:

[{["first_column", "a"}, {"second_column", 1}]

without using Enum or List or anything like that? (Hint, recursion!)

2 Likes

I can only second that. To cleanse yourself from the sins of the imperative world you should reimplement the Enum functions. This is one of the exercises in the Exercism Elixir Track. This is very much recommended! Reimplmenting Enum is not easy, so there are a lot of very good execises to lead you there.

3 Likes

Here are 2 examples:

defmodule Example do
  def sample1(%Postgrex.Result{columns: columns, rows: rows}) do
    Enum.map(rows, fn row ->
      Enum.zip_reduce(columns, row, %{}, &Map.put(&3, String.to_atom(&1), &2))
    end)
  end

  def sample2(%Postgrex.Result{columns: _columns, rows: []}), do: []

  def sample2(%Postgrex.Result{columns: columns, rows: [row | rows]}) do
    [sample2(columns, row, %{}) | sample2(%Postgrex.Result{columns: columns, rows: rows})]
  end

  defp sample2([], [], acc), do: acc

  defp sample2([column | columns], [value | row], acc) do
    acc = Map.put(acc, String.to_atom(column), value)
    sample2(columns, row, acc)
  end
end

The first example is really short as it uses a helper Enum functions. On the other hand the second example uses just a simple pattern-matching.

Helpful resources:

  1. Enum.map/2
  2. Enum.zip_reduce/4
  3. Map.put/3
  4. String.to_atom/3
  5. Kernel.SpecialForms.&/1 (Capture operator)
  6. Patterns and Guards

Please keep in mind below every time you want to convert String to Atom:

Warning: this function creates atoms dynamically and atoms are not garbage-collected. Therefore, string should not be an untrusted value, such as input received from a socket or during a web request. Consider using to_existing_atom/1 instead.

Source: String.to_atom/3 — Elixir documentation

1 Like

This is actually a great resource as well, even though it’s gasp for PHP.

1 Like

Thank you everyone for the great resources, I will definitely be looking into this!

Starting to love this language more and more every day

1 Like

I had the same question when start with Postgrex:

I made my set of helpers to receive maps, and also made queries with named parameters:

Working with-Geospatial data in Elixir Postgrex