【问题标题】:Search nested array of objects and return whole path of all matching items搜索嵌套的对象数组并返回所有匹配项的完整路径
【发布时间】:2021-02-20 08:32:09
【问题描述】:

我想搜索一个深度嵌套的对象数组并返回所有匹配对象的路径。我有问题的部分解决方案,但代码只返回第一个匹配对象的路径。请查看输入、预期输出和代码本身。我已在预期输出部分中注释了所需的逻辑。

提前致谢。请帮帮我。

输入数据

   [
   {
      "label":"Home",
      "key":"home",
      "level":1,
      "children":[
         {
            "label":"Indoor Furniture",
            "key":"furniture",
            "level":2,
            "children":[
               {
                  "label":"Chair",
                  "key":"chair",
                  "level":3
               },
               {
                  "label":"Table",
                  "key":"table",
                  "level":3
               },
               {
                  "label":"Lamp",
                  "key":"lamp",
                  "level":3
               }
            ]
         }
      ]
   },
   {
      "label":"Outdoor",
      "key":"outdoor",
      "level":1,
      "children":[
         {
            "label":"Outdoor Furniture",
            "key":"furniture",
            "level":2,
            "children":[
               {
                  "label":"Trampoline",
                  "key":"trampoline",
                  "level":3
               },
               {
                  "label":"Swing",
                  "key":"swing",
                  "level":3
               },
               {
                  "label":"Large sofa",
                  "key":"large sofa",
                  "level":3
               },
               {
                  "label":"Medium Sofa",
                  "key":"mediumSofa",
                  "level":3
               },
               {
                  "label":"Small Sofa Wooden",
                  "key":"smallSofaWooden",
                  "level":3
               }
            ]
         },
         {
            "label":"Games",
            "key":"games",
            "level":2,
            "children":[
               
            ]
         }
      ]
   },
   {
      "label":"Refurbrished Items",
      "key":"refurbrished items",
      "level":1,
      "children":[
         
      ]
   },
   {
      "label":"Indoor",
      "key":"indoor",
      "level":1,
      "children":[
         {
            "label":"Electicity",
            "key":"electicity",
            "level":2,
            "children":[
               
            ]
         },
         {
            "label":"Living Room Sofa",
            "key":"livingRoomSofa",
            "level":2,
            "children":[
               
            ]
         }
      ]
   }
]

预期输出 - 如果搜索沙发

[
// Remove the entire object if label of the object itself or any of its children doesn't include sofa
      {
         "label":"Outdoor",
         "key":"outdoor",
         "level":1,
         "children":[
            {
               "label":"Indoor Furniture",
               "key":"indoorFurniture",
               "level":2,
               "children":[
// Remove unmatched siblings
                  { `// Child node matched, hence return its path from root (Outdoor -> Indoor Furniture)`  
                     "label":"Large sofa",
                     "key":"large sofa",
                     "level":3
                  },
                  { // Child node matched, hence return its path from root (Outdoor -> Indoor Furniture) and all its children if any
                     "label":"Medium Sofa",
                     "key":"mediumSofa",
                     "level":3
                  },
                  { // Child node matched, hence return its path from root (Outdoor -> Indoor Furniture) and all its children if any
                     "label":"Small Sofa Wooden",
                     "key":"smallSofaWooden",
                     "level":3
                  }
               ]
            }
         ]
      },
      {
         "label":"Indoor",
         "key":"indoor",
         "level":1,
         "children":[
            { // Child node matched, hence return its path from root (Indoor) and all its children if any
               "label":"Living Room Sofa",
               "key":"livingRoomSofa",
               "level":2,
               "children":[
                  
               ]
            }
         ]
      }
   ]

预期输出 - 如果搜索家具

[ // Remove the entire object if label of the object itself or any of its children doesn't include furniture
          {
             "label":"Home",
             "key":"home",
             "level":1,
             "children":[
                { // Child node matched, hence return its path from root (Home) and all its children if any
                   "label":"Indoor Furniture",
                   "key":"indoorFurniture",
                   "level":2,
                   "children":[
                      {
                         "label":"Chair",
                         "key":"chair",
                         "level":3
                      },
                      {
                         "label":"Table",
                         "key":"table",
                         "level":3
                      },
                      {
                         "label":"Lamp",
                         "key":"lamp",
                         "level":3
                      }
                   ]
                }
             ]
          },
          {
             "label":"Outdoor",
             "key":"outdoor",
             "level":1,
             "children":[
                { // Child node matched, hence return its path from root (Outdoor) and all its children if any
                   "label":"Outdoor Furniture",
                   "key":"outdoorFurniture",
                   "level":2,
                   "children":[
                      {
                         "label":"Trampoline",
                         "key":"trampoline",
                         "level":3
                      },
                      {
                         "label":"Swing",
                         "key":"swing",
                         "level":3
                      },
                      {
                         "label":"Large sofa",
                         "key":"large sofa",
                         "level":3
                      },
                      {
                         "label":"Medium Sofa",
                         "key":"mediumSofa",
                         "level":3
                      },
                      {
                         "label":"Small Sofa Wooden",
                         "key":"smallSofaWooden",
                         "level":3
                      }
                   ]
                }
             ]
          }
       ]

代码

function findChild(obj, condition) {
if (Object.entries(condition).every( ([k,v]) => (obj[k].toLowerCase()).includes(v.toLowerCase()))) {
    return obj;
}
for (const child of obj.children || []) {
    const found = findChild(child, condition);
    // If found, then add this node to the ancestors of the result
    if (found) return Object.assign({}, obj, { children: [found] });
 }
}
var search = { label: 'sofa' };
console.log(findChild(input, search)); // It returns only the first matched item path, i would like to get all matched items path

【问题讨论】:

    标签: javascript reactjs recursion data-structures nested


    【解决方案1】:

    也许这就足够了:

    const findTerm = (arr, term) => arr.filter(e => JSON.stringify(e).indexOf(term) >= 0)
    

    【讨论】:

    • 感谢您的回答,但它返回整个匹配的对象,但要求是删除不匹配的孩子
    【解决方案2】:

    看起来它会做到这一点:

    const filterDeep = (pred) => (xs, kids) =>
      xs .flatMap (
        x => 
          pred (x)
            ? [x]
          : (kids = filterDeep (pred) (x .children || [])) && kids.length
            ? [{... x, children: kids}] 
          : []
        
      )
    
    const testIncludes = (condition) => (obj) =>
      Object .entries (condition) .every (
        ([k, v]) => (obj [k] || '') .toLowerCase () .includes (v .toLowerCase ())
      )
    
    const filterMatches = (obj, conditions) =>
      filterDeep (testIncludes (conditions)) (obj)
    
    
    const input = [{label: "Home", key: "home", level: 1, children: [{label: "Indoor Furniture", key: "furniture", level: 2, children: [{label: "Chair", key: "chair", level: 3}, {label: "Table", key: "table", level: 3}, {label: "Lamp", key: "lamp", level: 3}]}]}, {label: "Outdoor", key: "outdoor", level: 1, children: [{label: "Outdoor Furniture", key: "furniture", level: 2, children: [{label: "Trampoline", key: "trampoline", level: 3}, {label: "Swing", key: "swing", level: 3}, {label: "Large sofa", key: "large sofa", level: 3}, {label: "Medium Sofa", key: "mediumSofa", level: 3}, {label: "Small Sofa Wooden", key: "smallSofaWooden", level: 3}]}, {label: "Games", key: "games", level: 2, children: []}]}, {label: "Refurbrished Items", key: "refurbrished items", level: 1, children: []}, {label: "Indoor", key: "indoor", level: 1, children: [{label: "Electicity", key: "electicity", level: 2, children: []}, {label: "Living Room Sofa", key: "livingRoomSofa", level: 2, children: []}]}]
    
    
    console .log ('sofa:', filterMatches (input, {label: 'sofa'}))
    console .log ('furniture:', filterMatches (input, {label: 'furniture'}))
    .as-console-wrapper {max-height: 100% !important; top: 0}

    我们将递归过滤机制和对象匹配部分分开,将它们重新组合到filterMatches。这个想法是我们可能想要通过多种方式进行过滤,因此该函数采用可以测试当前节点的任意谓词函数。 testIncludes 接受一个键值对对象并返回一个函数,该函数接受一个对象并报告该对象的对应键是否每个都包含相关值。 (我根据您的输入/请求的输出组合在此处添加了不区分大小写的检查。)

    请注意,我将中心函数命名为 filter 而不是 find,因为 find 通常意味着返回第一个匹配项,而 filter 应该返回 所有 个匹配项.


    为了我自己的使用,我会稍微不同地构造 main 函数:

    const filterMatches = (conditions) => (obj) =>
      filterDeep (testIncludes (conditions)) (obj)
    
    console .log ('sofa:', filterMatches ({label: 'sofa'}) (input))
    

    我非常喜欢这些柯里化函数,并且按顺序排列的参数我觉得它们是最有用的。但是YMMV。

    更新

    评论指出主函数的 lint 失败。这是可以理解的,因为这在条件表达式中使用赋值时有些棘手。所以这里有一些工作变体:

    • 将赋值移动到默认参数:

      const filterDeep = (pred) => (xs, kids) =>
        xs .flatMap (
          (x, _, __, kids = filterDeep (pred) (x .children || [])) => 
            pred (x)
              ? [x]
            : kids.length
              ? [{... x, children: kids}] 
            : [] 
        )
      

      优点:

      • 这使我们的纯表达式风格保持活力,并避免了上述棘手问题。
      • 相当容易阅读

      缺点:

      • 它使用默认参数,但存在问题。
      • 它需要从flatMat 中命名两个未使用的参数(这里是___。)
    • 使用语句样式:

      const filterDeep = (pred) => (xs, kids) =>
        xs .flatMap ((x) => {
          if (pred (x)) {
            return [x]
          }
          const kids = filterDeep (pred) (x .children || [])
          if (kids.length > 0) {
            return [{... x, children: kids}] 
          }
          return []
        })
      

      优点:

      • 不再有任何诡计
      • 更适合初学者使用

      缺点:

      • ifreturn语句,与使用纯表达式相比,语句导致的模块化代码更少。
    • 使用 call 辅助函数:

      const call = (fn, ...args) => fn (...args)
      
      const filterDeep = (pred) => (xs, kids) =>
        xs .flatMap (
          (x) => 
            pred (x)
              ? [x]
            : call (
              (kids) => kids.length ? [{... x, children: kids}] : [], 
              filterDeep (pred) (x .children || [])
            )
        )
      

      优点:

      • call 辅助函数非常有用,可以在许多地方重复使用。
      • 它避免了对参数的任何摆弄

      缺点:

      • 这将真正的三部分测试的最后两个子句(返回 [x]、返回 [{... x, children: kids}] 和返回 [])组合成一个函数

    我对最后一个版本有一点偏好。但他们中的任何一个都可以。

    【讨论】:

    • 多么杰作!太感谢了。正是我想要的。
    • ESLinter 不喜欢这一行 (kids = filterDeep (pred) (x .children || [])) && kids.length 它在条件语句中抛出错误赋值运算符(no-cond-分配)
    • 过于棘手。这条 linter 规则可能有一个很好的理由。我们可以将该赋值作为参数中的默认值:const filterDeep = (pred) => (xs, kids = filterDeep (pred) (x .children || [])) => ...,然后在条件中使用kids.length(或更好的kids.length > 0)。这有点效率低下,这并不让我担心。或者,您可以使用一些 if 语句重写,并且仅在您已经超出第一个测试的情况下声明和计算 kids
    • 谢谢! const filterDeep = (pred) => (xs, kids = filterDeep (pred) (x .children || [])) => ... 我相信这里 x 是未定义的。不是吗?
    • 是的,对不起。这就是我在没有测试的情况下编码所得到的!我会看看能不能找到更好的干净的东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-06
    • 1970-01-01
    • 2022-01-26
    • 2021-05-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多