Home > OS >  Filter with different levels of depth
Filter with different levels of depth

Time:01-27

I have a menu with this structure

item1
item2
     childrenOfItem2
                    childrenOfchildren1
                    childrenOfchildren2
                    HELLOchildrenOfchildren3
     childrenOfItem2
     childrenOfItem2
HELLOitem3
item4
     childrenOfItem4
     HELLOchildrenOfItem4
item5
     childrenOfItem5

So, Id' like to get all the items that have the word "HELLO" and what I'm doing is a loop over the first items, then, another loop and then another loop, is there any other way of doing it? Since let's say that if we add another level of depth in the menu it will not work,

Thank you!

Edited: adding JS for better understanding

const matchName = (item, word) =>
  item?.title?.toLowerCase()?.includes(word?.toLowerCase());

const filter = (word = "", arr = []) => {
  const listOfItems = [];
  arr.forEach((item) => {
    if (matchName(item, word)) {
      listOfItems.push(item);
    } else if (item?.children?.length > 0) {
      const newSubItem = [];
      item.children.forEach((subItem) => {
        if (matchName(subItem, word)) {
          newSubItem.push(subItem);
        } else if (subItem?.children?.length > 0) {
          const newSubSubItems = [];
          subItem.children.forEach((subsubItem) => {
            if (matchName(subsubItem, word)) {
              newSubSubItems.push(subsubItem);
            }
          });
          if (newSubSubItems?.length > 0) {
            newSubItem.push({ ...subItem, children: newSubSubItems });
          }
        }
      });
      if (newSubItem?.length > 0) {
        listOfItems.push({ ...item, children: newSubItem });
      }
    }
  });
  return listOfItems;
};

Sample of arr received as parameter in the fnc:

const list = [
  {
    id: "41",
    title: "sample",
    children: [
      {
        id: "42",
        title: "sample",

        children: [
          {
            id: "43",
            title: "sample",

            children: [],
          },
          {
            id: "44",
            title: "sample",

            children: [],
          },
          {
            id: "45",
            title: "sample",

            children: [],
          },
        ],
      },
      {
        id: "46",
        title: "sample",
        children: [
          {
            id: "47",
            title: "sample",

            children: [],
          },
          {
            id: "48",
            title: "sample",

            children: [],
          },
        ],
      },
    ],
  },
  {
    id: "29",
    title: "sample",

    children: [
      {
        id: "30",
        title: "sample",

        children: [],
      },
      {
        id: "49",
        title: "sample",

        children: [],
      },
      {
        id: "31",
        title: "sample",

        children: [],
      },
    ],
  },
];

CodePudding user response:

If you really don't want to change your structure and flatten your list, you can loop them dynamically, just use a function and call it within itself.

Here's an example using the const list you provided:

let found = false,
    loop = (items, filter) => {
        found = false;

        let results = items.filter(a => filter(a));
        if(results.length > 0) {
            found = true;
            return results;
        }

        if(!found && items && items.length > 0) {
            for(let i = 0; i < items.length && !found; i  ) {
                if(items[i] && items[i].children && items[i].children.length > 0)
                    results = loop(items[i].children, filter);
            }
        }
        return results;
    };

let items = loop(list, item => item.id && item.id == "48");

In the example above we filter the list dynamically, iterating each and every item in the list and return the first item we find that matches a provided filter (2nd parameter).

You can use this to do pretty much whatever you'd like and can add arguments to add the menu "level" you're currently on, the parents, etc.

Note that this might get a bit slow if the list goes very deep, wrote it quickly and didn't tested outside of your provided list.

Ideally I would change the structure in order to make it easier to work with but if you must keep it this way, this works.

CodePudding user response:

You could find the object (without children) and get a flat array.

const
    find = (array, title) => array.flatMap(({ children, ...o }) => [
        ...(o.title.includes(title) ? [o]: []),
        ...find(children, title)
    ]),
    list = [{ id: "41", title: "sample", children: [{ id: "42", title: "sample", children: [{ id: "43", title: "sample", children: [] }, { id: "44", title: "sample", children: [] }, { id: "45", title: "sample", children: [] }] }, { id: "46", title: "no sample", children: [{ id: "47", title: "sample", children: [] }, { id: "48", title: "sample", children: [] }] }] }, { id: "29", title: "sample", children: [{ id: "30", title: "sample", children: [] }, { id: "49", title: "no sample", children: [] }, { id: "31", title: "sample", children: [] }] }];

console.log(find(list, 'no sample'));
console.log(find(list, 'ample'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

  • Related