Recursief filterarray van objecten

Hiermee raakte ik een muur en dacht ik post het hier voor het geval een vriendelijke ziel een soortgelijk exemplaar is tegengekomen. Ik heb gegevens die er ongeveer zo uitzien:

const input = [
  {
    value: 'Miss1',
    children: [
      { value: 'Miss2' },
      { value: 'Hit1', children: [ { value: 'Miss3' } ] }
    ]
  },
  {
    value: 'Miss4',
    children: [
      { value: 'Miss5' },
      { value: 'Miss6', children: [ { value: 'Hit2' } ] }
    ]
  },
  {
    value: 'Miss7',
    children: [
      { value: 'Miss8' },
      { value: 'Miss9', children: [ { value: 'Miss10' } ] }
    ]
  },
  {
    value: 'Hit3',
    children: [
      { value: 'Miss11' },
      { value: 'Miss12', children: [ { value: 'Miss13' } ] }
    ]
  },
  {
    value: 'Miss14',
    children: [
      { value: 'Hit4' },
      { value: 'Miss15', children: [ { value: 'Miss16' } ] }
    ]
  },
];

Ik weet tijdens runtime niet hoe diep de hiërarchie zal zijn, d.w.z. hoeveel niveaus van objecten een onderliggende array zullen hebben. Ik heb het voorbeeld enigszins vereenvoudigd, ik zal de waarde-eigenschappen eigenlijk moeten matchen met een reeks zoektermen. Laten we voorlopig aannemen dat ik match met waar value.includes('Hit').

Ik heb een functie nodig die een nieuwe array retourneert, zodanig dat:

  • Elk niet-overeenkomend object zonder kinderen, of geen overeenkomsten in de onderliggende hiërarchie, zou niet moeten bestaan ​​in het uitvoerobject

  • Elk object met een afstammeling dat een overeenkomend object bevat, moet blijven

  • Alle nakomelingen van overeenkomende objecten moeten blijven

Ik beschouw een ‘overeenkomstig object’ als een object met een value-eigenschap die in dit geval de tekenreeks Hitbevat, en vice versa.

De uitvoer zou er ongeveer als volgt uit moeten zien:

const expected = [
  {
    value: 'Miss1',
    children: [
      { value: 'Hit1', children: [ { value: 'Miss3' } ] }
    ]
  },
  {
    value: 'Miss4',
    children: [
      { value: 'Miss6', children: [ { value: 'Hit2' } ] }
    ]
  },
  {
    value: 'Hit3',
    children: [
      { value: 'Miss11' },
      { value: 'Miss12', children: [ { value: 'Miss13' } ] }
    ]
  },
  {
    value: 'Miss14',
    children: [
      { value: 'Hit4' },
    ]
  }
];

Veel dank aan iedereen die de tijd heeft genomen om tot hier te lezen, zal mijn oplossing posten als ik daar als eerste ben.


Antwoord 1, autoriteit 100%

Het gebruik van .filter()en het maken van een recursieve aanroep zoals ik in de bovenstaande opmerking heb beschreven, is eigenlijk wat je nodig hebt. U hoeft alleen elke eigenschap van .childrenbij te werken met het resultaat van de recursieve aanroep voordat u terugkeert.

De geretourneerde waarde is gewoon de .lengthvan de resulterende verzameling .children, dus als er minstens één is, blijft het object behouden.

var res = input.filter(function f(o) {
  if (o.value.includes("Hit")) return true
  if (o.children) {
    return (o.children = o.children.filter(f)).length
  }
})

Codefragment weergeven


Antwoord 2, autoriteit 20%

Hier is een functie die doet wat u zoekt. In wezen zal het elk item in arrtesten op een overeenkomst, en vervolgens recursief het filter aanroepen op zijn children. Ook wordt Object.assigngebruikt, zodat het onderliggende object niet wordt gewijzigd.

function filter(arr, term) {
    var matches = [];
    if (!Array.isArray(arr)) return matches;
    arr.forEach(function(i) {
        if (i.value.includes(term)) {
            matches.push(i);
        } else {
            let childResults = filter(i.children, term);
            if (childResults.length)
                matches.push(Object.assign({}, i, { children: childResults }));
        }
    })
    return matches;
}

Antwoord 3, autoriteit 3%

Ik denk dat het een recursieve oplossing zal zijn. Hier is er een die ik heb geprobeerd.

function find(obj, key) {
  if (obj.value && obj.value.indexOf(key) > -1){
    return true;
  }
  if (obj.children && obj.children.length > 0){
    return obj.children.reduce(function(obj1, obj2){
      return find(obj1, key) || find(obj2, key);
    }, {}); 
  } 
  return false;
}
var output = input.filter(function(obj){
     return find(obj, 'Hit');
 });
console.log('Result', output);

Antwoord 4, autoriteit 2%

const input = [
  {
    value: 'Miss1',
    children: [
      { value: 'Miss1' },
      { value: 'Hit1', children: [ { value: 'Miss3' } ] }
    ]
  },
  {
    value: 'Miss4',
    children: [
      { value: 'Miss5' },
      { value: 'Miss6', children: [ { value: 'Hit2' } ] }
    ]
  },
  {
    value: 'Miss7',
    children: [
      { value: 'Miss8' },
      { value: 'Miss9', children: [ { value: 'Miss10' } ] }
    ]
  },
  {
    value: 'Hit3',
    children: [
      { value: 'Miss11' },
      { value: 'Miss12', children: [ { value: 'Miss13' } ] }
    ]
  },
  {
    value: 'Miss14asds',
    children: [
      { value: 'Hit4sdas' },
      { value: 'Miss15', children: [ { value: 'Miss16' } ] }
    ]
  },
];
function filter(arr, term) {
    var matches = [];
    if (!Array.isArray(arr)) return matches;
    arr.forEach(function(i) {
        if (i.value === term) {
         const filterData =  (i.children && Array.isArray(i.children))? i.children.filter(values => values.value ===term):[];
         console.log(filterData)
         i.children =filterData;
            matches.push(i);
        } else {
       // console.log(i.children)
            let childResults = filter(i.children, term);
            if (childResults.length)
     matches.push(Object.assign({}, i, { children: childResults }));
        }
    })
    return matches;
}
const filterData= filter(input,'Miss1');
console.log(filterData)

Snippet uitvouwen


Antwoord 5

Was op zoek naar een andere manier om dit probleem op te lossen zonder de kinderarray direct te muteren en kwam met dit:

const input = [
  {
    value: 'Miss1',
    children: [
      { value: 'Miss2' },
      { value: 'Hit1', children: [ { value: 'Miss3' } ] }
    ]
  },
  {
    value: 'Miss4',
    children: [
      { value: 'Miss5' },
      { value: 'Miss6', children: [ { value: 'Hit2' } ] }
    ]
  },
  {
    value: 'Miss7',
    children: [
      { value: 'Miss8' },
      { value: 'Miss9', children: [ { value: 'Miss10' } ] }
    ]
  },
  {
    value: 'Hit3',
    children: [
      { value: 'Miss11' },
      { value: 'Miss12', children: [ { value: 'Miss13' } ] }
    ]
  },
  {
    value: 'Miss14',
    children: [
      { value: 'Hit4' },
      { value: 'Miss15', children: [ { value: 'Miss16' } ] }
    ]
  },
];
const filtered = input.reduce(function fr(acc, curr) {
  if (curr.children) {
    const children = curr.children.reduce(fr, []);
    if (children.length) {
        return acc.concat({ ...curr, children: children });
    }
  }
  if (curr.value.includes('Hit')) {
    return acc.concat(curr);
  } else {
    return acc;
  }
}, []);
console.log(filtered);

Other episodes