amedeo
Build a Tree of Structs
Hi,
I’m working on a Elixir/Phoenix small project in my spare time and I found myself writing code to build a tree structure starting from a list of nodes.
Here is the input:
[
%{id: 1, children: [], parent_id: nil},
%{id: 2, children: [3], parent_id: nil},
%{id: 3, children: [], parent_id: 2}
]
and the expected output:
%TreeNode{
id: "ROOT",
children: [
%TreeNode{
id: 1,
parent_id: "ROOT"
children: []
},
%TreeNode{
id: 2,
parent_id: "ROOT"
children: [
%TreeNode{
id: 3,
parent_id: 2
children: []
}
]
}
],
parent_id: nil
}
I’ve come to the following implementation but it is quite long and I’ve the feeling that it could be written in a different way.
Do you have some idea or suggestions on how to write it?
defmodule TestTree do
defmodule TreeNode do
defstruct id: nil, children: [], parent_id: nil
end
@_ROOT_NODE "ROOT"
def build_tree(list) do
# use the build_hierarchy to get two Maps as support data for the next step
{id_to_nodes, id_to_children} = build_hierarchy(list, %{@_ROOT_NODE=>%TreeNode{id: @_ROOT_NODE}}, %{@_ROOT_NODE=>[]})
# build the tree
build_tree(Map.keys(id_to_nodes), id_to_nodes, id_to_children)
end
# Creates two Maps:
# one Map keeps the information on Id -> %TreeNode
# the other Map is an Id -> [ /child id 1/, /child id 2/, ...]
defp build_hierarchy([], a, b), do: {a, b}
defp build_hierarchy([m | nodes], id_to_nodes, id_to_children) do
parent_key = m.parent_id || @_ROOT_NODE
new_node = %TreeNode{ id: m.id, parent_id: parent_key}
id_to_nodes = Map.put_new(id_to_nodes, m.id, new_node)
# update the parent children
id_to_children = Map.put_new_lazy(id_to_children, parent_key, fn -> [] end)
children = id_to_children[parent_key]
id_to_children = %{id_to_children | parent_key => [new_node.id | children]}
build_hierarchy(nodes, id_to_nodes, id_to_children)
end
# the function will build the tree (tail) recursively examining each node_id
# if the current node has no children then it is added to the parent node
# if the node has children then its processing is postponed, we need first to take care of the ones without children
defp build_tree([@_ROOT_NODE], id_to_nodes, _), do: id_to_nodes[@_ROOT_NODE]
defp build_tree([node_id | node_ids], id_to_nodes, id_to_children) do
node = id_to_nodes[node_id]
parent_id = node.parent_id
children = Map.get(id_to_children, node_id, [])
if length(children) > 0 do
# node not ready yet, it has some children
build_tree(node_ids ++ [node_id], id_to_nodes, id_to_children)
else
# the node has no children let's add it to its parent children and remove it from the id_to_children
parent_node = id_to_nodes[parent_id]
id_to_nodes = %{id_to_nodes | parent_id => %{parent_node | children: [node | parent_node.children]}}
id_to_children = %{id_to_children | parent_id => List.delete(id_to_children[parent_id], node_id)}
build_tree(node_ids, id_to_nodes, id_to_children)
end
end
end
Most Liked
michalmuskala
That depends largely on the data. If the data is sorted (as in the sample you provided), it can be transformed using the following:
def build_tree(elements) do
{tree, []} = build_tree(elements, %TreeNode{id: nil, children: []})
tree
end
defp build_tree([%{parent_id: parent, id: id, children: _} | rest],
%{id: parent, children: children} = tree) do
{node, rest} = build_tree(rest, %TreeNode{id: id, children: []})
build_tree(rest, %{tree | children: [node | children]})
end
defp build_tree(rest, tree), do: {tree, rest}
7
amedeo
awesome! and beautiful.
Sry, I forgot about the ordering, I have it on the parent_id, nil firsts, but I’ll do a pass of sorting before calling the new build_tree ![]()
Thanks!
1
Popular in Questions
What is the proper way to load a module from a file in to IEX?
In the python world, doing something like this pretty standard:
from ....
New
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
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
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
Credo is smart enough to check for (something like) this:
assert length(the_list) == 0
with this response:
Checking if an enum is empt...
New
Hello all!
I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
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
Other popular topics
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
I would like to know what is the best IDE for elixir development?
New
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something…
Haskell reminds me of Java, and e...
New
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









