Replace keys from list of maps

I have a list of tuple like this

  tuple =   [{"id", "provider_id"}, {"cost", "price"}] .  

And i have list of maps:

    list_of_maps =[
    %{cost: 211.0, id: 1},
    %{cost: 149.0, id: 2},
    %{cost: 247.0, id: 3},
    %{cost: 137.0, id: 4},
    %{cost: 272.0, id: 5},
    %{cost: 150.0, id: 6}

I want to replace idwith provider_id and cost with price by using the the list of tuples in this map.

How can it be done?


1 Like

To solve (any) issue the best is to divide it into smaller steps.
I’ve created a function replacement_for which returns the corresponding string from tuple, ie. for :id it will return "provider_id" and so on.
Then, the function replace_keys which replaces the keys for the single map.

defmodule R do
  defp replacement_for(key, tuple) do
    |> Enum.find(fn {x, _} -> x == to_string(key) end)
    |> elem(1)

  def replace_keys(map, tuple) do
    for {k, v} <- map, into: %{}, do: {replacement_for(k, tuple), v}

All you need to do now, is just use the replace_keys in (or for comprehension, as you wish):, fn m -> R.replace_keys(m, tuple) end)


Is the key mapping something that needs to change dynamically, because can be done like so:

for %{id: id, cost: cost} <- list_of_maps, do: %{provider_id: id, price: cost}

@script: Here is another and also shortest solution I know about:, & %{price: &1.cost, provider_id: &})

For more information, see:

  1. Kernel.SpecialForms.&/1

FYI: This topic is a continuation of Why doesn't ecto support dynamic map query

In particular:

I want dynamic solution.


query = from p in Qber.V1.JobModel, where: == 1, select: %{provider_id:, price: p.cost}

would have been an acceptable solution in the first place.

Another alternative:

defmodule Demo do

  # 1) "tuple" is already in a format ready to
  #    be converted to a Map which can be used repeatedly
  #    over multiple rows/maps for the purpose of a key lookup
  # 2) Return a function that "knows" the key mappings
  #    via the to_new_key map in the function closure.
  # 3) We're avoiding turning strings into atoms as
  #    atoms aren't garbage collected. So we prefer
  #    ways where we turn atoms into strings.
  #    Note: Kernel.to_string/1 relies on the
  #          String.Chars protocol.
  #          If we know that we will always be using
  #          atoms, Atom.to_string seems more appropriate.
  # 4) We are not really "replacing" keys. We are only
  #    selecting values found under a key specified
  #    in the to_new_key lookup - and only if we find
  #    it, do be store it in new_map.
  #    4a) the original key is found, the value is stored
  #        under new_key in new_map.
  #    4b) the original key IS NOT FOUND
  #        SO THE value IS IGNORED - we simply
  #        return the existing new_map.
  #        FOR "replacement" we would either need
  #            Map.put(new_map, key, value)
  #        or possibly
  #            Map.put(new_map, Atom.to_string(key), value)
  def make_mapper(key_list) do
    to_new_key =

    fn ({key, value}, new_map) ->
      case Map.fetch(to_new_key, Atom.to_string(key)) do
        {:ok, new_key} ->
          Map.put(new_map, new_key, value) # (4a)
        _ ->
          new_map                          # (4b)

  def transform(row_list, key_list) do
    # 5) create key_mapper function to reuse on each map/row in row_list
    key_mapper = make_mapper(key_list)
    for row <- row_list,
      do: Enum.reduce(row, %{}, key_mapper)


tuple =   [{"id", "provider_id"}, {"cost", "price"}]
list_of_maps =[
  %{cost: 211.0, id: 1},
  %{cost: 149.0, id: 2},
  %{cost: 247.0, id: 3},
  %{cost: 137.0, id: 4},
  %{cost: 272.0, id: 5},
  %{cost: 150.0, id: 6}

result = Demo.transform(list_of_maps, tuple)

$ elixir demo.exs
  %{"price" => 211.0, "provider_id" => 1},
  %{"price" => 149.0, "provider_id" => 2},
  %{"price" => 247.0, "provider_id" => 3},
  %{"price" => 137.0, "provider_id" => 4},
  %{"price" => 272.0, "provider_id" => 5},
  %{"price" => 150.0, "provider_id" => 6}