shahryarjb

shahryarjb

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

Showing Posts 1 to 10

jegaxd26

jegaxd26

Have you looked into Access.at/1 ?

pmangalakader

pmangalakader

Not sure if this is what you’re looking for Floki - HTML Parser

This one makes it easier to handle HTML parsing.

Also, I wouldn’t prefer to nest too much Enum.map as, it makes code reading a bit trickier, normally I would use recursion for clean code if it is more than 2 nesting to make it easier for maintenance…

Sebb

Sebb

I’d try to find a better data-structure first.
Flattening is a good idea oftentimes.
Why is data and CSS mixed?
What are you trying to accomplish?

codeanpeace

codeanpeace

The combination of the Access module with the Kernel functions update_in/3 and put_in/3 is very useful for updating nested data structures.

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

Sebb

Sebb

wow that looks really cool.

If I understand correctly you have an arbitrary nesting depth.
And you want to change an attribute in an arbitrary node, right?

So you can’t use a nested structure and a non-recursive algorithm to do that.

I would think about flattening the data in sth like %{node_id => node} where each node has an id and a ref to the parent’s id (see the test below for an example).

Here is some code I use to build a tree from a flat list:

defmodule Tools.TreeFromList do
  def build_tree(nodes, config) do
    by_parent = Enum.group_by(nodes, & &1[config.parent_id_key])
    Enum.map(by_parent[config.root_parent], &build_tree_(&1, by_parent, config))
  end

  defp build_tree_(node, nodes_by_parent, config) do
    children =
      Enum.map(
        Map.get(nodes_by_parent, node[config.node_id_key], []),
        &build_tree_(&1, nodes_by_parent, config)
      )

    config.build_tree_node.(node, children)
  end
end
defmodule TreeFromListTest do
  use ExUnit.Case

  @tag :build_tree
  test "tree from list" do
    data = [
      %{id: 1, name: "F1", parent_id: nil},
      %{id: 2, name: "F2", parent_id: nil},
      %{id: 6, name: "F6", parent_id: 3},
      %{id: 4, name: "F4", parent_id: 2},
      %{id: 5, name: "F5", parent_id: 3},
      %{id: 3, name: "F3", parent_id: 1}
    ]

    config = %{
      build_tree_node: &Map.put(&1, :children, &2),
      parent_id_key: :parent_id,
      node_id_key: :id,
      root_parent: nil
    }

    assert [
             %{
               children: [
                 %{
                   children: [
                     %{children: [], name: "F6", parent_id: 3, id: 6},
                     %{children: [], name: "F5", parent_id: 3, id: 5}
                   ],
                   name: "F3",
                   parent_id: 1,
                   id: 3
                 }
               ],
               name: "F1",
               parent_id: nil,
               id: 1
             },
             %{
               children: [%{children: [], name: "F4", parent_id: 2, id: 4}],
               name: "F2",
               parent_id: nil,
               id: 2
             }
           ] == Tools.TreeFromList.build_tree(data, config)
  end
end
shahryarjb

shahryarjb OP

I think I will have a big problem with changing the order of keys in children! when I use map Am I right?
Because my user can change the order of elements in a section!

I think the only way to change the sort or re-order is regenerate new UUID, it is works when I have just 2 elements is a section more than 2 and change by side 2 element, again it is a problem

For example:

<div id="1">
  <p>text-1</p>
  <p>text-2</p>
  <p>text-3</p>
</div>

// TO

<div id="1">
  <p>text-2</p>
  <p>text-1</p>
  <p>text-3</p>
</div>
hst337

hst337

Just use Pathex. It is universal solution for nested structures

If you can describe what you’re trying to achieve in a human language, I can write you a pathex lens

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? Top

Trending in Questions Top

RSP87
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
kszambelanczyk
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
RemyXRenard
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
velrest
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
samoloth
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
FlyingNoodle
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
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
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
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
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
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews