Home > database >  How to fix the error when working with recursion and array?
How to fix the error when working with recursion and array?

Time:07-15

I'm trying to solve a problem - need to return an array after entering a value to search. The depth of an array can be theoretically infinite. So I'm recursing to find suitable elements among any depth level and add them to the array and then return this array. But this code does not work as expected. I can't figure out what exactly I did wrong here.

const newList = [
  {
      role: "role111",
      title: "title1",

  },
  {
      role: "role222",
      title: "title2",
  },
  {
      role: "role333",
      title: "title3",
  },
  {
      role: "role444",
      title: "title4",
      items: [{
          role: "role555",
          title: "title5",
      }, {
          role: "role666",
          title: "title6",
      }, {
          role: "role777",
          title: "title7",
          items: [{
              role: "role888",
              title: "title888",
          },{
              role: "role8888",
              title: "title8888",
          },]
      },]
  },
  {
      role: "role999",
      title: "title999",
  },
];

const text = "role888";


const testFunction = (
  list,
  text,
  emptyArray
) => {
  let arrayForRender = emptyArray;

  return list?.filter((item) => {
      if (item.title.toLowerCase().includes(text.toLowerCase())) {
          arrayForRender = [...arrayForRender, item];
          return arrayForRender;
      }
      if (item.items && item.items?.length > 0) {
          testFunction(item.items, text, arrayForRender);
      }
  });
};

console.log(testFunction(newList, text, []));

P.S. I'm so sorry, but my question was originally formulated incorrectly and it is no longer relevant. But thanks to everyone who suggested and will tell you how to work with recursion correctly!

CodePudding user response:

I don't think the filter method will work here, instead try the forEach method to loop through the array and push found items into your arrayForRender. Also I noticed in your code that your search text is a role but your condition looks at the title.

const testFunction = (
  list,
  text,
  arrayForRender
 ) => {
 list.forEach((item) => {
    if (item.items && item.items?.length > 0) {
       testFunction(item.items, text, arrayForRender);
     } 
    if (item.role.toLowerCase().includes(text.toLowerCase())) {
       arrayForRender.push(item);  
       //arrayForRender.push({'role':item.role, 'title':item.title})
     } 
  });
  return(arrayForRender)
};

console.log("search results:",testFunction(newList, text,[]));

based on your comment below, here is code that returns only the top level node if it matches the search or any of its children match the search:

const testFunction = (
  list,
  text
) => {
  return list?.filter((item) => {
    if (item.items && item.items?.length > 0) {
        return testFunction(item.items, text);
    }

    return item.role.toLowerCase().includes(text.toLowerCase());
  });
};


console.log("search results:",testFunction(newList, text));

CodePudding user response:

If I understand your updated requirements, then we can write a simple generic deep filtering function that shows all the elements that match a predicate or which have (recusively nested) descendants that match it.

We can configure this with a function that tests whether the title matches a query string. It might look like this:

const filterDeep = (pred) => (xs) =>
  xs .filter (x => pred (x) || filterDeep (pred) (x .items || []) .length > 0)

const titleMatch = (query) => filterDeep (
  ({title}) => title .toLowerCase () .includes (query .toLowerCase ())
)


const newList = [{role: "role111", title: "title1"}, {role: "role222", title: "title2"}, {role: "role333", title: "title3"}, {role: "role444", title: "title4", items: [{role: "role555", title: "title5"}, {role: "role666", title: "title6"}, {role: "role777", title: "title7", items: [{role: "role888", title: "title888"}, {role: "role8888", title: "title8888"}]}]}, {role: "role999", title: "title999"}];

['title2', '888', 'itl'] .forEach (
  query => console .log (`"${query}"`, ':', titleMatch (query) (newList) .map (x => x .title))
)
.as-console-wrapper {max-height: 100% !important; top: 0}

(Note that the result is the actual original nodes, but here we display only their titles.)

I think this is simple code, and filterDeep is quite likely usable elsewhere.

Update

So clearly, I didn't get the requirement. If I'm now correct, and you want the full object hierarchy for any nodes that match the predicate, then this version should do, which simply has a different implementation of filterDeep:

const filterDeep = (pred) => (xs) =>
  xs .flatMap (({items = [], kids = filterDeep (pred) (items), ...rest}) =>
    pred (rest) || kids.length
      ? [{...rest, ...(kids.length ? {items: kids} : {})}]
      : []
  )

const titleMatch = (query) => filterDeep (
  ({title}) => title .toLowerCase () .includes (query .toLowerCase ())
)


const newList = [{role: "role111", title: "title1"}, {role: "role222", title: "title2"}, {role: "role333", title: "title3"}, {role: "role444", title: "title4", items: [{role: "role555", title: "title5"}, {role: "role666", title: "title6"}, {role: "role777", title: "title7", items: [{role: "role888", title: "title888"}, {role: "role8888", title: "title8888"}]}]}, {role: "role999", title: "title999"}];

['title2', '888', 'itl'] .forEach (
  query => console .log (`"${query}"`, ':', titleMatch (query) (newList))
)
.as-console-wrapper {max-height: 100% !important; top: 0}

We use flatMap here as a way to filter and map in one go. If the item does not match, we return an empty array; if it does, then we return an array containing just its transformed value.

  • Related