web-dev-qa-db-ja.com

オブジェクトの配列を再帰的にフィルター処理する

これで壁にぶつかり、ある種の魂が似たような魂に出会った場合にここに投稿すると思いました。次のようなデータがあります。

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

実行時に、階層の深さ、つまり子配列を持つオブジェクトのレベルの数がわかりません。この例を少し簡略化しましたが、実際には、値のプロパティを検索語の配列と照合する必要があります。ここで、value.includes('Hit')が一致するものと仮定します。

次のような新しい配列を返す関数が必要です。

  • 子のない、または子階層に一致しないすべての不一致オブジェクトは、出力オブジェクトに存在してはなりません

  • 一致するオブジェクトを含む子孫を持つすべてのオブジェクトはそのままにしておく必要があります

  • 一致するオブジェクトのすべての子孫が残っている必要があります

「一致するオブジェクト」は、この場合文字列valueを含むHitプロパティを持つものと考えています。逆もまた同様です。

出力は次のようになります。

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

ここまで読んでくれた人に感謝します。最初にそこに着いたら私の解決策を投稿します。

33
Nathan Power

上記のコメントで説明したように、.filter()を使用して再帰呼び出しを行うことが基本的に必要です。戻る前に、再帰呼び出しの結果で各_.children_プロパティを更新する必要があります。

戻り値は、結果の_.length_コレクションの_.children_であるため、少なくとも1つあれば、オブジェクトは保持されます。

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

Stringの.includes()はES7であるため、レガシーブラウザー用にパッチを適用する必要がある場合があります。その代わりに従来の.indexOf("Hit") != -1を使用できます。


オリジナルを変更しないようにするには、オブジェクトをコピーするマップ関数を作成し、フィルターの前にそれを使用します。

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

本当にコードを絞るには、これを行うことができます:

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

読むのが少し難しくなりますが。

36
user1106925

探しているものを実行する関数を次に示します。基本的に、arr内のすべてのアイテムが一致するかどうかをテストし、childrenで再帰的にフィルターを呼び出します。また、Object.assignは、基になるオブジェクトが変更されないように使用されます。

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

再帰的な解決策になると思います。ここに私が試したものがあります。

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

または、 deepdashlodashの拡張から_.filterDeepメソッドを使用できます。

var keyword = 'Hit';
var foundHit = _.filterDeep(
  input,
  function(value) {
    return value.value.includes(keyword);
  },
  {
    tree: true,
    onTrue: { skipChildren: true },
  }
);

ここに 完全なテスト があります

0
Yuri Gor