skosch

skosch

Put/update deep inside nested maps (and auto-create intermediate keys)

To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for example, put_in(%{a: %{}}, [:a, :b, :c], 42) will fail). But – despite all the risks – this would be useful to have: for instance, I often use reduce to create a nested, aggregate representation of a list of complex objects (say, counting occurrences by categories and sub-categories), which is a one-liner when you don’t have to worry about intermediate keys (think mkdir -p).

The implementation is simple enough (see deep_update example below, which I’m already using all over my projects). I have a nagging feeling this must already be part of Elixir’s standard library and I just don’t know about it? If not, is there a particular reason it isn’t?

  %spec deep_update(map(), [any()], any(), any() -> any()) :: map()
  def deep_update(map, [leaf_key], default, update_func) do
    updated_leaf = if is_map(map) and Map.has_key?(map, leaf_key) do
      update_func.(map[leaf_key])
    else
      default
    end
    Map.put(map, leaf_key, updated_leaf)
  end
  def deep_update(map, [key | key_tail], default, update_func) do
    if is_map(map) and Map.has_key?(map, key) do
      new_branch = deep_update(map[key], key_tail, default, update_func)
      Map.put(map, key, new_branch)
    else
      new_branch = deep_update(%{}, key_tail, default, update_func)
      Map.put(map, key, new_branch)
    end
  end

Marked As Solved

michalmuskala

michalmuskala

You can achieve this today using Access.key/2:

put_in(%{a: %{}}, Enum.map([:a, :b, :c], &Access.key(&1, %{})), 42)

If you use it often, you could wrap it in a function to do this automatically for you.

39
Post #7

Also Liked

jonericcook

jonericcook

@skosch from reading the solutions here i did this (thougt id share for the next person)

  def map_put(data, keys, value) do
    # data = %{} or non empty map
    # keys = [:a, :b, :c]
    # value = 3
    put_in(data, Enum.map(keys, &Access.key(&1, %{})), value)
  end

  def many_map_puts(data, keys_values) do
    # data = %{} or non empty map
    # keys_values = [[keys: [:a, :b, :c], value: 4],[keys: [:z, :y, :x], value: 90]]
    Enum.reduce(keys_values, data, fn x, data ->
      map_put(data, x[:keys], x[:value])
    end)
  end
iex(1)> m = [[keys: [:a, :b, :c], value: 4],[keys: [:z, :y, :x], value: 90]]
[[keys: [:a, :b, :c], value: 4], [keys: [:z, :y, :x], value: 90]]
iex(2)> many_map_puts(%{}, m)
%{a: %{b: %{c: 4}}, z: %{y: %{x: 90}}}
peerreynders

peerreynders

Not entirely sure if this addresses what you are trying to accomplish but have you had a look at Kernel.put_in/3, Kernel.get_and_update_in/3 and the like - and Access behavior?

peerreynders

peerreynders

So if I understand you correctly you want

put_in(%{a: %{}}, [:a, :b, :c], 42)

to result in

%{a: %{b: %{c: 42)} } }

One problem I see is that you are assuming that :b should refer to a Map value i.e. that the nested structure is a homogenous Map. The Access data[key] syntax is right out of the box also supported by Keyword lists - and any other structure that cares to implement the Access behaviour so there is some ambiguity right there - just because there is a key doesn’t automatically mean it should be a Map.

Where Next?

Popular in Questions Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36820 110
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

We're in Beta

About us Mission Statement