shahryarjb
Need advise to update nested list 3-level
Hello friends, I have a list that I want to update it (3 level nested), and I need your suggestion to create better code
The list
elements = [
%{
children: [
%{
children: [
%{
children: [],
class: ["text-black", "w-full", "p-2"],
id: "c9ea0fff-1ee0-407e-8b2a-64afc954c40b",
index: 0,
parent: "section",
parent_id: "62197198-e3a1-46de-8f18-f2c6843f646f",
type: "text"
},
%{
children: [],
class: ["text-black", "w-full", "p-2"],
id: "b2a6b171-26e1-4e8a-8cd3-c41f388de6e8",
index: 1,
parent: "section",
parent_id: "62197198-e3a1-46de-8f18-f2c6843f646f",
type: "text"
}
],
class: ["flex", "flex-col", "justify-between", "items-stretch",
"min-h-[200px]", "w-full", "border", "border-dashed",
"border-gray-400", "p-1"],
id: "62197198-e3a1-46de-8f18-f2c6843f646f",
index: 0,
parent: "layout",
parent_id: "64a2c3f2-d71f-464a-8318-be072d7b6624",
type: "section"
}
],
class: ["flex", "flex-row", "justify-start", "items-center", "w-full",
"space-x-3", "px-3", "py-10"],
id: "64a2c3f2-d71f-464a-8318-be072d7b6624",
index: 0,
parent: "dragLocation",
parent_id: "dragLocation",
type: "layout"
}
]
for updating and adding tag to the text element I did like this, but it is very dirty I think:
def add_tag(elements, id, parent_id, layout_id, tag, type) when type in @elements do
Enum.map(elements, fn
%{type: "layout", id: ^layout_id, children: children} = selected_layout ->
edited_list =
children
|> Enum.map(fn
%{type: "section", id: ^parent_id, children: children} = selected_section ->
element_edited_list =
Enum.map(children, fn
%{type: ^type, id: ^id} = selected_element ->
Map.merge(selected_element, %{tag: tag})
element ->
element
end)
%{selected_section | children: element_edited_list}
section ->
section
end)
%{selected_layout | children: edited_list}
layout ->
layout
end)
end
MishkaTemplateCreatorWeb.MishkaCoreComponent.add_tag(elements, "c9ea0fff-1ee0-407e-8b2a-64afc954c40b", "62197198-e3a1-46de-8f18-f2c6843f646f", "64a2c3f2-d71f-464a-8318-be072d7b6624", "test1", "text")
By the way, for finding I created this code:
def find_element(elements, id, parent_id, layout_id, type) when type in @elements do
Enum.flat_map(elements, fn
%{type: "layout", id: ^layout_id, children: children} ->
case Enum.find(children, &(&1.id == parent_id)) do
nil ->
[]
%{type: "section", id: ^parent_id, children: children} ->
if is_nil(data = Enum.find(children, &(&1.id == id))), do: [], else: [data]
end
_layout ->
[]
end)
|> List.first()
end
Thank you in advance
Most Liked
jegaxd26
Have you looked into Access.at/1 ?
shahryarjb
hst337
Pathex is just like Elixir’s builtin Access, but structure-friendly, user-friendly, more performant and with some other cool features. They both use the same approach called “Functional optics”. Long story short, this approach is about writing fn functions which can set/get/update value in a structure (like getters or setter in OOP languages), and libraries like Pathex or Access are providing interface to create these functions and compose them together.
This approach is really useful and I suggest everyone to learn some optics, because it is universal way to traverse any nested structure. There are XPaths for XML, there are CSS selectors for HTML, there is dot-notation for structures, there is Access for primitive structures, but these approaches are format-specific, which is strange since everything in Elixir is represented as a combination of maps, lists and tuples. So, Pathex just leverages this feature and significantly reduces amount of stuff one must know to traverse nested data in Elixir. Just master Pathex and you can traverse XML, HTML, nested JSON or anything else using one tool.
So, there’s no magic, there are just functions, hehe
NduatiK
It looks like you might be doing a lot of data access.
You might get a huge boost in simplicity and performance by using maps instead of lists.
If you were willing to separate storing and sorting of children, you could store a map of children and an order list that has ordered child ids:
elements = %{
children: %{
"62197198-e3a1-46de-8f18-f2c6843f646f" => %{
children: %{
"b2a6b171-26e1-4e8a-8cd3-c41f388de6e8" => %{
children: [],
class: ["text-black", "w-full", "p-2"],
id: "b2a6b171-26e1-4e8a-8cd3-c41f388de6e8",
# index: 1,
parent: "section",
parent_id: "62197198-e3a1-46de-8f18-f2c6843f646f",
type: "text"
},
"c9ea0fff-1ee0-407e-8b2a-64afc954c40b" => %{
children: [],
class: ["text-black", "w-full", "p-2"],
id: "c9ea0fff-1ee0-407e-8b2a-64afc954c40b",
# index: 0,
parent: "section",
parent_id: "62197198-e3a1-46de-8f18-f2c6843f646f",
type: "text"
}
},
class: ["flex", "flex-col", "justify-between", "items-stretch",
"min-h-[200px]", "w-full", "border", "border-dashed", "border-gray-400",
"p-1"],
id: "62197198-e3a1-46de-8f18-f2c6843f646f",
# index: 0,
order: ["c9ea0fff-1ee0-407e-8b2a-64afc954c40b",
"b2a6b171-26e1-4e8a-8cd3-c41f388de6e8"],
parent: "layout",
parent_id: "64a2c3f2-d71f-464a-8318-be072d7b6624",
type: "section"
}
},
order: ["62197198-e3a1-46de-8f18-f2c6843f646f"],
class: ["flex", "flex-row", "justify-start", "items-center", "w-full",
"space-x-3", "px-3", "py-10"],
id: "64a2c3f2-d71f-464a-8318-be072d7b6624",
# index: 0,
order: "62197198-e3a1-46de-8f18-f2c6843f646f",
parent: "dragLocation",
parent_id: "dragLocation",
type: "layout"
}
Once you do that, you can use @codeanpeace’s Access module functions at a cost of O(n) instead of O(n^3):
Look up would be:
def find(elements, id, parent_id, layout_id) do
get_in(elements, [:children, layout_id,:children, parent_id, :children, id])
end
Updating an element
def add_tag(elements, id, parent_id, layout_id, tag, type) when type in @elements do
update_in(elements, [:children, layout_id,:children, parent_id, :children, id], fn selected_element ->
if selected_element.type == type do
Map.merge(selected_element, %{tag: tag})
end
end)
end
Removing an element:
def delete(elements, id, parent_id, layout_id) do
# Remove the child
{_,elements} = pop_in(elements, [:children, layout_id,:children, parent_id, :children, id])
# Remove it from the order
elements = update_in(elements, [:children, layout_id,:children, parent_id, :order], fn order ->
Enum.reject(order, &(&1 == id))
end)
elements
end
Adding is similar to deleting
hst337
I see you want to put tag into type: "layout" ~> type: "section".
With pathex this would be something like
use Pathex
defp element(type, id) do
Pathex.Lenses.star() ~> matching(%{type: ^type, id: ^id})
end
defp children do
path(:children)
end
def add_tag(elements, id, parent_id, layout_id, tag, type) do
lens = element("layout", layout_id) ~> children ~> element("section", parent_id) ~> children ~> element(type, id)
Pathex.over(elements, lens, fn element -> Map.put(element, :tag, tag) end)
end
You can learn how to create you own lenses with Pathex using these 5min tutorials:







