shahryarjb

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

jegaxd26

Have you looked into Access.at/1 ?

hst337

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

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

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:

  1. basics
  2. lenses
  3. cheatsheet

Where Next?

Popular in Questions Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
Brian
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
bsollish-terakeet
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
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
quazar
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
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
ovidiubadita
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
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

Other popular topics Top

dotdotdotPaul
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
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

We're in Beta

About us Mission Statement