put_in and update_in can be very useful for deeply nested values in maps, but if you're trying to add keys that don't exist yet, these commands fail. Use Access.key for this.

Let's work through a reduce example.

Input

input = [
    {"Martin", :age, 31},
    {"Eva", :height, 182},
    {"Martin", :height, 192}
]

Target output

%{
    "Martin" => %{
        age: 31,
        height: 192
    },
    "Eva" => %{
        height: 182
    }
}

Reduce function

Enum.reduce(input, %{}, fn {name, key, val}, acc ->
    put_in(acc, [name, key], val)
end)

This will end up with a simple error: (ArgumentError) could not put/update key :age on a nil value.

Solution

Enum.reduce(input, %{}, fn {name, key, val}, acc ->
    put_in(acc, [Access.key(name, %{}), key], val)
end)
# %{"Eva" => %{height: 182}, "Martin" => %{age: 31, height: 192}}

Explanation

The Access.key command will return a function (as opposed to a calculated value) that the put_in can later use. It hides some complexity, where it checks if acc has the key and if not, puts in a default value (an empty map).

Access.key for keyword lists

There's no Access.key for keyword lists though. But you can write your own function for it. Or of course - just copy paste from here:

access_nil = fn key ->
  fn
    :get, data, next ->
      next.(Keyword.get(data, key, []))
    :get_and_update, data, next ->
      value = Keyword.get(data, key, [])
      case next.(value) do
        {get, update} -> {get, Keyword.put(data, key, update)}
        :pop -> {value, Keyword.delete(data, key)}
      end
  end
end
All credit goes to @ericmj on Elixir Slack

Let's try this on the same reduce with keyword lists:

input = [
    {:Martin, :age, 31},
    {:Eva, :height, 182},
    {:Martin, :height, 192}
]

Enum.reduce(input, [], fn {name, key, val}, acc ->
    put_in(acc, [access_nil.(name), key], val)
end)

Notice the dot between access_nil and (name) - we're using an anonymous function in this example (meaning the function isn't defined in a module like generally it's supposed to).

Notice also that I've converted the strings in the input to atoms. Do not convert user input to atoms. Problem is this access_nil function only works with atoms, because keyword lists only work with atoms. In this case you implement an algorithm manually that uses stuff like List.keytake(). But this could be very processor intensive so perhaps you can just convert your lists into maps and later back to lists?