amedeo
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
Trending in Questions
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
Hello!
Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app.
I creat...
New
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
So my question is quite simple and i have found no conclusive answer on forum, google or AI.
Should we use :erlang.float for Integer to ...
New
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
I’m trying to set up Emacs with elixir-ls via lsp-mode and credo via Flycheck. This should mostly be preconfigured as Flycheck picks up c...
New
Other Trending Topics
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hobbes is a low-level distributed database for the Elixir programming language.
Hobbes provides a simple, safe, and scalable storage lay...
New
Hi there! We created Gust: A task orchestrator inspired by Airflow.
For those who have never heard about Aiflow, it’s a Python-based wor...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve.
They are GUI (Emerge) and State management (S...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixirconf-us
- #elixir-ls
- #ai
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 2- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
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:
amedeo
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!