Rich_Morin
How to get a list of access lists for use with get_in/2?
I’d like to process a tree of maps to get a list of access lists for the leaf nodes (eg, for use with get_in/2). So, for example, if the tree is %{k1: %{k11: 11}, k2: %{k21: 21} }, the list should be [[:k1, :k11],[:k2,:k21]].
Before I hurt my brain hacking recursive functions, can anyone supply a clean and simple way to do this?
-r
Most Liked
peerreynders
defmodule Demo do
def paths(tree) do
paths(tree, [], [])
end
defp paths(tree, parent_path, paths) do
{_,paths} = Enum.reduce(tree, {parent_path, paths}, &descend/2)
paths
end
defp descend({key, value}, {parent_path, paths}) do
paths =
if is_map(value) do
paths(value, [key|parent_path], paths)
else
[:lists.reverse([key|parent_path])|paths]
end
{parent_path, paths}
end
def run(tree) do
tree
|> Demo.paths()
|> IO.inspect()
end
end
Demo.run(%{})
Demo.run(%{k11: 11})
Demo.run(%{k1: %{k11: 11}})
Demo.run(%{k1: %{k11: 11}, k2: 2})
Demo.run(%{k1: %{k11: 11}, k2: %{k21: 21} })
Demo.run(%{k1: %{k11: 11}, k2: %{k21: 21, k22: 22}})
$ elixir demo.exs
[]
[[:k11]]
[[:k1, :k11]]
[[:k2], [:k1, :k11]]
[[:k2, :k21], [:k1, :k11]]
[[:k2, :k22], [:k2, :k21], [:k1, :k11]]
defmodule Demo do
def paths(tree),
do: paths(Map.to_list(tree), [], [], [])
defp paths([], _, [], paths),
do: paths
defp paths([], _, [{parent, others} | rest], paths),
do: paths(others, parent, rest, paths)
defp paths([{key, value} | others], parent, rest, paths) when is_map(value),
do: paths(Map.to_list(value), [key | parent], [{parent, others} | rest], paths)
defp paths([{key, _} | others], parent, rest, paths),
do: paths(others, parent, rest, [:lists.reverse([key | parent]) | paths])
def run(tree) do
tree
|> Demo.paths()
|> IO.inspect()
end
end
Demo.run(%{})
Demo.run(%{k1: 1})
Demo.run(%{k1: %{k11: 11}})
Demo.run(%{k1: %{k11: 11}, k2: 2})
Demo.run(%{k1: %{k11: 11}, k2: %{k21: 21}})
Demo.run(%{k1: %{k11: 11}, k2: %{k21: 21, k22: 22}})
Demo.run(%{k1: %{k11: %{k111: 111, k112: 112}, k12: 12}, k2: %{k21: 21, k22: 22}})
$ elixir demo.exs
[]
[[:k1]]
[[:k1, :k11]]
[[:k2], [:k1, :k11]]
[[:k2, :k21], [:k1, :k11]]
[[:k2, :k22], [:k2, :k21], [:k1, :k11]]
[[:k2, :k22], [:k2, :k21], [:k1, :k12], [:k1, :k11, :k112], [:k1, :k11, :k111]]
$
1
alco
Here’s a compact, non-tail-recursive implementation:
defmodule Nmap do
def trace_map(map) when is_map(map) do
# In a simple case the result of this function will be a list of one-element lists. For example,
#
# trace_map(%{k1: 1, k2: 2}) == [[:k1], [:k2]]
#
# However, if there are nested maps, the top-level key will be prepended to all subpaths returned from the recursive
# calls of this function:
#
# trace_map(%{k1: %{k11: 11, k12: 12}, k2: 2}) == [[:k1, :k11], [:k1, :k12], [:k2]]
#
# This is why we're using Enum.flat_map for the iteration here: to allow recursive function calls to return a list
# that will then be spliced into the parent list instead of being added to the parent as a single list of lists. If
# we were to use just Enum.map, we'd get the following result instead:
#
# trace_map(%{k1: %{k11: 11, k12: 12}, k2: 2}) ==
# [
# [[:k1, :k11], [:k1, :k12]], # return value of the first recursive call
# [[:k2]]
# ]
#
Enum.flat_map(map, fn
{key, nested_map} when is_map(nested_map) ->
# If the value is a nested map, get its list of paths recursively and prepend the current key to each of them.
for key_list <- trace_map(nested_map) do
[key | key_list]
end
{key, _val} ->
# For plain values return the key to end the recursion. The key is wrapped in a list twice because flat_map
# will unwrap the outermost list and the remainder will be used as a list tail by the calling function (the one
# immediately up the stack).
[[key]]
end)
end
end
Usage example:
iex(21)> m = %{k1: %{k11: 11, k12: %{k121: 121}, k13: 13}, k2: 2}
%{k1: %{k11: 11, k12: %{k121: 121}, k13: 13}, k2: 2}
iex(22)> paths = Nmap.trace_map(m)
[[:k1, :k11], [:k1, :k12, :k121], [:k1, :k13], [:k2]]
iex(23)> for path <- paths do
...(23)> get_in(m, path)
...(23)> end
[11, 121, 13, 2]
1
Rich_Morin
Thanks, guys! FWIW, I opted to use a slight variation on Peer’s first version:
def leaf_paths(tree), do: leaf_paths(tree, [], [])
defp leaf_paths(tree, parent_path, paths) do
{_, paths} = Enum.reduce(tree, {parent_path, paths}, &descend/2)
paths
end
defp descend({key, value}, {parent_path, paths}) when is_map(value), do:
{parent_path, leaf_paths(value, [ key | parent_path ], paths) }
defp descend({key, _value}, {parent_path, paths}), do:
{parent_path, [ :lists.reverse( [ key | parent_path ] ) | paths ] }
-r
1
Popular in Questions
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it.
I’m very interested in Elixir,...
New
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this:
...
New
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
I tried installing
elixir 1.11.2
erlang 23.3.4
via asdf in my zsh shell. Enabled the versions locally and globally.
When I list them ...
New
Hi guys, i’m new in the Elixir world, and i have to say, that i love it!
i’m having some problem to understand anonymous functions with ...
New
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
Expanding on this topic: https://forum.elixirforum.net/t/map-typespec-question/19217
Let’s say I have a map with required and optional k...
New
Hi,
I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors:
[WARN] - (starship::utils): Executing command ...
New
Hi everyone!
I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
Other popular topics
Hello!
tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability.
After spen...
New
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
Hi all,
Trying to get some more clarity over utc_datetime and naive_datetime for Ecto:
https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode.
The solution seems to be, in a hyphena...
New
I would like to know what is the best IDE for elixir development?
New
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
After calling mix ecto.create I get this error:
17:00:32.162 [error] GenServer #PID<0.412.0> terminating
** (Postgrex.Error) FATAL...
New
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
by Lance Halvorsen
Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
New
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New








