wake-up-neo.com

Rekursives Filtern von Arrays von Objekten

Wenn ich mit diesem eine Mauer traf, dachte ich, ich würde sie hier posten, falls eine freundliche Seele auf eine ähnliche stößt. Ich habe einige Daten, die ungefähr so ​​aussehen:

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' } ] }
    ]
  },
];

Ich weiß zur Laufzeit nicht, wie tief die Hierarchie sein wird, d. H. Wie viele Objektebenen ein untergeordnetes Array haben. Ich habe das Beispiel etwas vereinfacht, ich muss tatsächlich die Werteigenschaften mit einem Array von Suchbegriffen abgleichen. Lassen Sie uns im Moment davon ausgehen, dass ich mit value.includes('Hit') übereinstimme. 

Ich brauche eine Funktion, die ein neues Array zurückgibt.

  • Jedes nicht übereinstimmende Objekt ohne untergeordnete Objekte oder keine Übereinstimmungen in untergeordneten Hierarchien sollte im Ausgabeobjekt nicht vorhanden sein

  • Jedes Objekt mit einem Nachkommen, das ein übereinstimmendes Objekt enthält, sollte erhalten bleiben

  • Alle Nachkommen von übereinstimmenden Objekten sollten bleiben

Ich betrachte ein "übereinstimmendes Objekt" als ein Objekt mit einer value-Eigenschaft, die in diesem Fall die Zeichenfolge Hit enthält, und umgekehrt. 

Die Ausgabe sollte ungefähr wie folgt aussehen: 

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' },
    ]
  }
];

Vielen Dank an alle, die sich die Zeit genommen haben, bis hierher zu lesen. Ich werde meine Lösung veröffentlichen, wenn ich zuerst dort bin.

26
Nathan Power

Verwenden Sie .filter() und führen Sie einen rekursiven Aufruf aus, wie im obigen Kommentar beschrieben. Sie müssen lediglich jede .children-Eigenschaft mit dem Ergebnis des rekursiven Aufrufs aktualisieren, bevor Sie zurückkehren. 

Der Rückgabewert ist nur der .length der resultierenden .children-Sammlung. Wenn also mindestens eine vorhanden ist, wird das Objekt beibehalten.

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
  }
})

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' } ] }
    ]
  },
];

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
  }
})
console.log(JSON.stringify(res, null, 2))


Beachten Sie, dass es sich bei .includes() für einen String um ES7 handelt. Für ältere Browser muss daher möglicherweise ein Patch installiert werden. Sie können stattdessen die traditionelle .indexOf("Hit") != -1 verwenden.


Um das Original nicht zu mutieren, erstellen Sie eine Map-Funktion, die ein Objekt kopiert, und verwenden Sie diese vor dem Filter.

function copy(o) {
  return Object.assign({}, o)
}

var res = input.map(copy).filter(function f(o) {
  if (o.value.includes("Hit")) return true

  if (o.children) {
    return (o.children = o.children.map(copy).filter(f)).length
  }
})

Um den Code wirklich zu komprimieren, können Sie Folgendes tun:

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

Obwohl es etwas schwer zu lesen ist.

28
user1106925

Hier ist eine Funktion, die das tut, wonach Sie suchen. Im Wesentlichen wird es jedes Element in arr auf Übereinstimmung prüfen und dann rekursiv den Filter für seine children aufrufen. Auch Object.assign wird verwendet, damit das zugrunde liegende Objekt nicht geändert wird. 

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;
}
4
jj689

Ich denke, es wird eine rekursive Lösung sein. Hier ist einer, den ich ausprobiert habe.

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);
2
Zohaib Ijaz

Alternativ können Sie die _.filterDeep-Methode aus deepdash extension für lodash verwenden:

// We will need 2 passes, first - to collect needed nodes with 'Hit' value:
var foundHit = _.filterDeep(input,
  function(value) {
    if (value.value && value.value.includes('Hit')) return true;
  },{ condense: false, // keep empty slots in array to preserve correct paths
      leafsOnly: false }
);
// second pass - to add missed fields both for found 'Hit' nodes and their parents.
var filtrate = _.filterDeep(input, function(value,key,path,depth,parent,parentKey,parentPath) {
  if (_.get(foundHit, path) !== undefined ||
    _.get(foundHit, parentPath) !== undefined) {
    return true;
  }
});

Hier ist ein vollständiger Test für Ihren Fall

0
Yuri Gor