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
Hello, I get Persian date from my client and convert it to normal calendar like this:
def jalali_string_to_miladi_english_number(persi...
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
This should be a simple problem but I just can’t seem to figure it out. I have a standalone Elixir app that won’t find the database.
Dep...
New
Hi, I’m using Ubuntu 18.04 and after updating to OTP-24.0 yesterday i have this warning when I run “mix local.hex”:
14:57:30.512 [warn] ...
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’m a new one to elixir
which editor can i use
vs code? or atom?
Thanks! :smiley:
New
Student & New to elixir. Nice language.
I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
New
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible.
total = 10
while total != 0
...
New
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
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
Other popular topics
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...
New
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
Hi there,
I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch.
This project took far...
New
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
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
Hello everybody,
usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
Hey all,
I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
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
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








