n4thyra
Build a tree from a flat structure recursively
There are similar questions such as Build a Tree of Structs but I haven’t found any that would deal with exactly the same issue that I need to solve.
Let’s say we have an (unsorted) set of data that we load from DB
[
%{id: 2, name: "Child 2", parent_id: nil},
%{id: 1 ,name: "Child 1", parent_id: nil},
%{id: 3, name: "GrandChild 1", parent_id: 1},
%{id: 5, name: "Child 3", parent_id: nil},
%{id: 6, name: "GrandGrandChild 1", parent_id: 3},
]
Desired result
[
%{
id: 2,
name: "Child 2",
children: [],
},
%{
id: 1,
name: "Child 1",
children: [
%{
id: 3,
name: "GrandChild 1",
children: [
%{
id: 6,
name: "GrandGrandChild 1",
children: []
},
]
},
],
},
%{
id: 5,
name: "Child 3",
children: [],
},
]
There can be N levels, not just 3
Most Liked
stefanchrobot
kokolegorille
I would start by selecting root nodes, where parent_id is nil. Then use a recursive constructor to build children’s field.
iex> list = [
%{id: 2, name: "Child 2", parent_id: nil},
%{id: 1, name: "Child 1", parent_id: nil},
%{id: 3, name: "GrandChild 1", parent_id: 1},
%{id: 5, name: "Child 3", parent_id: nil},
%{id: 6, name: "GrandGrandChild 1", parent_id: 3}
]
iex> new_node = fn node -> %{id: node.id, name: node.name, children: Enum.filter(list, & &1.parent_id == node.id) |> Enum.map(& new_node.(&1))} end
#Function<44.65746770/1 in :erl_eval.expr/5>
iex> Enum.filter(list, & is_nil(&1.parent_id)) |> Enum.map(& new_node.(&1))
[
%{children: [], id: 2, name: "Child 2"},
%{
children: [
%{
children: [%{children: [], id: 6, name: "GrandGrandChild 1"}],
id: 3,
name: "GrandChild 1"
}
],
id: 1,
name: "Child 1"
},
%{children: [], id: 5, name: "Child 3"}
]
3
stefanchrobot
Here’s how you can do it with :digraph:
input = [
%{id: 2, name: "Child 2", parent_id: nil},
%{id: 1 ,name: "Child 1", parent_id: nil},
%{id: 3, name: "GrandChild 1", parent_id: 1},
%{id: 5, name: "Child 3", parent_id: nil},
%{id: 6, name: "GrandGrandChild 1", parent_id: 3},
]
graph = :digraph.new()
fake_root = %{id: 0, name: "Root", parent_id: nil}
# Add vertices + the fake root vertex.
for %{id: id} = node <- [fake_root | input] do
:digraph.add_vertex(graph, id, Map.delete(node, :parent_id))
end
# Add all edges; if no parent set, add an edge to the fake root.
for %{id: id, parent_id: parent_id} <- input do
:digraph.add_edge(graph, id, parent_id || fake_root.id)
end
# I needed recursion, so I introduced a module.
defmodule TreeBuilder do
def build(graph, vertex) do
children =
for child <- :digraph.in_neighbours(graph, vertex) do
build(graph, child)
end
{^vertex, label} = :digraph.vertex(graph, vertex)
Map.put(label, :children, children)
end
end
%{children: children} = TreeBuilder.build(graph, fake_root.id)
# Get rid of the fake root.
children
The idea here is that the input represents the vertices (id and name) and the edges (parent_id). I introduced a fake root node since working with a tree is easier. I first build the graph/tree and then traverse it. Since :digraph is based on ETS, the code does not need to carry the state around.
3
Popular in Questions
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
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
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
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: )
Hello all, this is ...
New
Using vs code and installed ElixirLS: support and debugger.
And I got an error popped up on start up says
Failed to run ‘elixir’ comma...
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
Other popular topics
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine)
This is a plugin that adds support for Elixir to JetBrains IntelliJ...
New
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum.
...
New
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
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
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 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
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
- #elixirconf-us
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









