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 #8

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.

al2o3cr

al2o3cr

put_in can manipulate anything that Access supports, not just maps. For instance:

iex(1)> a = {1, 2, :x}
{1, 2, :x}
iex(2)> put_in a, [Access.elem(2)], :foo
{1, 2, :foo}

Where Next?

Popular in Questions Top

belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
New
kostonstyle
Hi all I want to have a unix time, from the current time plus 1 hour. DateTime.now + 1 hour How to get it in elixir? Thanks
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
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
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
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
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

We're in Beta

About us Mission Statement