问题描述
我想搜索一个深层嵌套的对象数组,并返回所有匹配对象的路径。我有部分解决问题的方法,但是代码仅返回第一个匹配对象的路径。请查看输入,预期输出和代码本身。我在预期的输出部分中评论了所需的逻辑。
先谢谢了。请帮帮我。
输入数据
[
{
"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",{
"label":"Lamp","key":"lamp","level":3
}
]
}
]
},{
"label":"Outdoor","key":"outdoor","children":[
{
"label":"Outdoor Furniture","children":[
{
"label":"Trampoline","key":"trampoline",{
"label":"Swing","key":"swing",{
"label":"Large sofa","key":"large sofa",{
"label":"Medium Sofa","key":"mediumSofa",{
"label":"Small Sofa Wooden","key":"smallSofaWooden","level":3
}
]
},{
"label":"Games","key":"games","children":[
]
}
]
},{
"label":"Refurbrished Items","key":"refurbrished items","children":[
]
},{
"label":"Indoor","key":"indoor","children":[
{
"label":"Electicity","key":"electicity","children":[
]
},{
"label":"Living Room Sofa","key":"livingRoomSofa","children":[
]
}
]
}
]
[
// Remove the entire object if label of the object itself or any of its children doesn't include sofa
{
"label":"Outdoor","children":[
{
"label":"Indoor Furniture","key":"indoorFurniture","children":[
// Remove unmatched siblings
{ `// Child node matched,hence return its path from root (Outdoor -> Indoor Furniture)`
"label":"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",hence return its path from root (Outdoor -> Indoor Furniture) and all its children if any
"label":"Small Sofa Wooden","level":3
}
]
}
]
},{
"label":"Indoor","children":[
{ // Child node matched,hence return its path from root (Indoor) and all its children if any
"label":"Living Room Sofa","children":[
]
}
]
}
]
预期产量-如果搜索家具
[ // Remove the entire object if label of the object itself or any of its children doesn't include furniture
{
"label":"Home","children":[
{ // Child node matched,hence return its path from root (Home) and all its children if any
"label":"Indoor Furniture","children":[
{
"label":"Chair","level":3
},{
"label":"Table",{
"label":"Lamp","level":3
}
]
}
]
},{
"label":"Outdoor",hence return its path from root (Outdoor) and all its children if any
"label":"Outdoor Furniture","key":"outdoorFurniture","children":[
{
"label":"Trampoline",{
"label":"Swing",{
"label":"Large sofa",{
"label":"Medium Sofa",{
"label":"Small Sofa Wooden","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
解决方法
这看起来可以做到:
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",{label: "Lamp",key: "lamp",level: 3}]}]},{label: "Outdoor",key: "outdoor",children: [{label: "Outdoor Furniture",children: [{label: "Trampoline",key: "trampoline",{label: "Swing",key: "swing",{label: "Large sofa",key: "large sofa",{label: "Medium Sofa",key: "mediumSofa",{label: "Small Sofa Wooden",key: "smallSofaWooden",level: 3}]},{label: "Games",key: "games",children: []}]},{label: "Refurbrished Items",key: "refurbrished items",children: []},{label: "Indoor",key: "indoor",children: [{label: "Electicity",key: "electicity",{label: "Living Room Sofa",key: "livingRoomSofa",children: []}]}]
console .log ('sofa:',filterMatches (input,{label: 'sofa'}))
console .log ('furniture:',{label: 'furniture'}))
.as-console-wrapper {max-height: 100% !important; top: 0}
我们分离出递归过滤机制以及对象匹配部分,将它们放回到filterMatches
中。这个想法是,我们可能希望通过多种方式进行过滤,因此该函数采用可以测试当前节点的任意谓词函数。 testIncludes
接受键值对的对象,并返回接受对象的函数,并报告该对象的对应键是否每个都包含相关值。 (我根据您的输入/请求的输出组合在此处添加了不区分大小写的检查。)
请注意,我用单词filter
而不是find
来命名中央函数,因为find
通常意味着返回第一个匹配项,而filter
应该返回所有匹配项。
对于我自己的用途,我将主要功能的结构略有不同:
const filterMatches = (conditions) => (obj) =>
filterDeep (testIncludes (conditions)) (obj)
console .log ('sofa:',filterMatches ({label: 'sofa'}) (input))
我非常喜欢这些咖喱函数,按照这种顺序的参数,我觉得它们最有用。但是YMMV。
更新
一条评论指出主要功能出现了皮棉故障。这是可以理解的,因为在条件表达式中使用赋值确实有些棘手。所以这是一些可行的变体:
-
将分配移动到默认参数:
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 [] })
优点:
- 不再有任何麻烦
- 更适合初学者使用
缺点:
-
if
和return
是语句,与使用纯表达式相比,语句导致的模块化代码更少。 -
使用
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}]
和返回[]
的最后两个子句组合成一个函数
我对最后一个版本有些偏爱。但是他们中的任何一个都可以。
,也许这足够了:
const findTerm = (arr,term) => arr.filter(e => JSON.stringify(e).indexOf(term) >= 0)