Necesito filtrar una estructura anidada, que se ve así, según la consulta. Necesito devolver todos los objetos, incluido el objeto principal del subárbol, que coinciden con la cadena de consulta en el nombre del objeto. Por favor ayuda, estoy atascado.

[
   {
     name: 'bob',
     type: 1,
     children: [
       {
         name: 'bob',
         type: 2,
         children: [
           {
             name: 'mike',
             type: 3,
             children: [ 
               {
                 name:'bob',
                 type: 7,
                 children: []
               },
               {
                 name: 'mike',
                 type: 9,
                 children: []
               }
             ]
           }
         ]
       },
       {
         name: 'mike',
         type: 2
       }
     ]
   }
 ]

En este momento puedo encontrar una coincidencia en el árbol de forma recursiva, pero la función devuelve el objeto en la primera coincidencia y no busca más profundamente en los subniveles en el mismo objeto. ¿Alguna sugerencia, cómo puedo modificar el código para buscar todos los niveles de forma recursiva?

  return tree.map(copy).filter(function filterNested(node) {
    if (node.name.toLowerCase().indexOf(query) !== -1) {
      return true;
    }

    if (node.children) {
      return (node.children = node.children.map(copy).filter(filterNested))
        .length;
    }
  });

Si estoy buscando la consulta 'bob', el resultado esperado debería ser,

 const arr = [
   {
     name: 'bob',
     type: 1,
     children: [
       {
         name: 'bob',
         type: 2,
         children: [
           {
             name: 'mike',
             type: 3,
             children: [ 
               {
                 name:'bob',
                 type: 7
               },
  
             ]
           }
         ]
       },
     ]
   }
 ]

4
leonard0 29 oct. 2019 a las 23:51

4 respuestas

La mejor respuesta

Podría reducir la matriz y construir nuevos objetos con hijos opcionales, si tienen una longitud que no sea cero.

function filter(array, fn) {
    return array.reduce((r, o) => {
        var children = filter(o.children || [], fn);
        if (fn(o) || children.length) r.push(Object.assign({}, o, children.length && { children }));
        return r;
    }, []);
}

var data = [{ name: 'bob', type: 1, children: [{ name: 'bob', type: 2, children: [{ name: 'mike', type: 3, children: [{ name: 'bob', type: 7 }, { name: 'same', typ: 9 }] }] }, { name: 'mike', type: 2 }] }],
    result = filter(data, ({ name }) => name.toLowerCase() === 'bob');

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
2
Nina Scholz 29 oct. 2019 a las 21:52

Simplemente usaría un buen patrón de visitante antiguo para atravesar y construir un nuevo árbol.

class Visitor {
    constructor(predicate) {
        this.predicate = predicate;
    }

    visit(item) {
        if (Array.isArray(item)) {
            return this.visitArray(item);
        } else if (item) {
            return this.visitItem(item);
        }
    }

    visitArray(item) {
        const result = [];
        for (let e of item) {
            const item = this.visit(e);
            if (item) {
                result.push(item);
            }
        }
        return result;
    }

    visitItem(item) {
        const children = this.visit(item.children);
        const hasChildren = (children && children.length > 0);

        if (hasChildren || this.predicate(item)) {
            return {
                name: item.name,
                type: item.type,
                children: children
            }
        }
        return null;
    }
}


const visitor = new Visitor((item) => item.name === "bob");
const result = visitor.visit(data);

console.log(result);
1
Edin Omeragic 29 oct. 2019 a las 21:35

Puede usar una reducción recursiva para esto, donde verifica si hay hijos o si el nombre coincide antes de acumular el objeto:

const example = [{
  name: 'bob',
  type: 1,
  children: [{
      name: 'bob',
      type: 2,
      children: [{
        name: 'mike',
        type: 3,
        children: [{
            name: 'bob',
            type: 7,
            children: []
          },
          {
            name: 'mike',
            type: 9,
            children: []
          }
        ]
      }]
    },
    {
      name: 'mike',
      type: 2
    }
  ]
}];

function reduceName(accum, item, matcher) {
  item.children = (item.children || []).reduce((a,i)=>reduceName(a,i,matcher),[]);
  if (!item.children.length) delete item.children;
  if (matcher(item) || item.children) accum.push(item);
  return accum;
}

console.log(example.reduce((a,i)=>reduceName(a,i,x=>x.name==='bob'),[]));
1
Ruzihm 29 oct. 2019 a las 21:18

¿Algo así, tal vez?

function nestedFilter(query, nodes) {
  return nodes.reduce((result, node) => {
    const filteredChildren = node.children && nestedFilter(query, node.children);
    const nameMatches = node.name.toLowerCase().indexOf(query) !== -1;
    const childrenMatch = filteredChildren && filteredChildren.length;
    const shouldKeep = nameMatches || childrenMatch;
    return shouldKeep 
      ? result.concat({ ...node, children: filteredChildren }) 
      : result;    
  }, []);
}
1
Jacob 29 oct. 2019 a las 21:10