Find a specific string in an unstructured and nested list

Hi friends, I have a nested list that is unstructured, and I really know how much it has nested list or map in itself.

I just need to search and get all :tag key in this list and check the selected name by my user exists there or it is unique.

For example:

[
  %{
    children: [
      %{
        children: [],
        id: "fcfb1066-6e16-4a84-81e0-7746f12b7e76",
        tag: "test1",
        type: "section"
      },
      %{
        id: "035ca737-2cc2-488b-83f5-fe7cc2becc5e",
        parent_id: "a9963499-f6a4-4a32-bafa-aa22a9b059bc",
      },
      %{
        children: [
          %{
            other: [
              %{
                id: "035ca737-2cc2-488b-83f5-fe7cc2becc5e",
                parent_id: "a9963499-f6a4-4a32-bafa-aa22a9b059bc",
              },
            ],
            tag: "test4",
          },
        ],
        id: "8a9f0126-33a0-4c03-8c4a-5507f0562c8a",
        index: 2,
        parent: "layout",
        parent_id: "a9963499-f6a4-4a32-bafa-aa22a9b059bc",
        tag: "test2",
        type: "section"
      }
    ],
    parent: "dragLocation",
    parent_id: "dragLocation",
    tag: "test3",
  }
]

This list has no a structure that helps me to use Enum.

So I try to use macro, but based on my little knowledge I could not to find all :tag, see this code please

defmacro get_tags(data, selected_name) do
  quote do
    data = unquote(data)
    data_string = inspect(data, charlists: :as_strings)
    ast = Code.string_to_quoted(data_string)
    
    ....
  end
end

the ast = Code.string_to_quoted(data_string) passes me a nested list again, and I don’t know how to create a pattern to get tag, so after that I use to List.flatten ast but I got this error:
** (FunctionClauseError) no function clause matching in :lists.flatten/1

because It has maps in its list

Please help me to learn more about macro and search like this in a list, Thank you in advance.

I don’t understand why you want to use a macro to solve this.

What I’d suggest is a recursive search.

1 Like

Hi @tcoopman, Thank you for response

The first reason is to learn more about macros and second one is I try recursive function to find all tag in a list, but it needs so much condition to find it is map? Or list of it is so enumerable it and find map or list again so get tag etc.

Because the list I get from a user is not a structured list, and maybe it has multiple layers in only one parameter

do you have any suggestion about it, like sample code or sth?

This is something that should work:

  def find([], tags), do: tags

  def find([hd | tl], tags) do
    tag = Map.get(hd, :tag)
    tags = if tag, do: [tag | tags], else: tags

    child = Map.get(hd, :children)
    tags = if child, do: find(child, tags), else: tags

    find(tl, tags)
  end
1 Like

Yes, I wrote something before like this, but as I said it has more complicity for me because maybe we have no children sometimes we have a single map into another map that has tag or childrent name is something like other

  def get_tags(data) do
    get_tags_helper(data, %{})
    |> Map.values()
    |> Enum.uniq()
  end

  defp get_tags_helper(data, tags) do
    case data do
      [] -> tags
      [head | tail] ->
        case head[:tag] do
          nil -> get_tags_helper(head[:children] ++ tail, tags)
          tag ->
            new_tags = Map.update(tags, tag, 1, &(&1 + 1))
            get_tags_helper(head[:children] ++ tail, new_tags)
        end
      _ -> tags
    end
  end

But thank you, the recursive is my selected option, at least. If you have an idea with macro, please let me know, I really want to improve my level in meta programming. :rose:

I’m not very fluent with macros either. Having said that, I really don’t see how macros are going to help you with the problem at hand.
Macros are not something you should just reach to, they are powerful but also add complexity. So only use them when necessary.

Back to your problem:
If there are no children, or no tags, my solution handles that.
If the children is not named children, how do you know it’s something you should decent into?
What is the actual problem you’re trying to solve?

2 Likes

Yes, I pointed out exactly this problem at the beginning of the project, but now this decision has been made at higher levels than me
Unfortunately, we have a list that different people in different organizations add a series of parameters to this list based on their taste.
All I have to do is find all the tags and check if the new name I want to insert has already been inserted.

It should be noted, sometimes some added list has no tag that should be avoided or skipped. This is true for children.
I tried to do it with the search string and check it is included whether there is, for example, tag: name or not, but sometimes I need to take this tag and make some changes on it.

That’s why I tried to move this whole list of strings to the macro and check each and every line of it.

Macros are not magic. They won’t lower the complexity of the problem.
What I would do is write test cases for the different kinds of inputs you need to handle and then implement code for them.

2 Likes

Macros are for turning one piece of “pseudo” code into another piece of code. That’s useful if you know some information at compile time, which you can then turn into more/different code. You don’t seem to have any information at runtime, which would help you solve the problem at hand.

1 Like

Use Pathex

import Pathex.Combinator
import Pathex.Lenses
import Pathex

tag_lens =
  combine(fn rec ->
    path(:tag, :map) ||| (star() ~> rec)
  end)

Pathex.view(nested_stuff, tag_lens ~> matching("test2"))
2 Likes

For the future questions, if you want to traverse nested structures, just use lenses. This is a very powerful tool which solves the problems in a few lines

It is like writing a filesystem path, but for elixir structures

1 Like