3 回答
TA贡献1805条经验 获得超10个赞
在开始之前,我们必须首先解决输入数据的不规则形状 -
const data2 =
{ name:"root"
, children:
Array.from
( Object.entries(data.root)
, ([ country, _ ]) =>
Object.assign({ name:country }, _)
)
}
console.log(JSON.stringify(data2, null, 2))
现在我们看到的data2是一个统一的{ name, children: [ ... ]}形状——
{
"name": "root",
"children": [
{
"name": "Europe",
"children": [
{ "name": "Germany" },
{
"name": "England",
"children": [
{
"name": "London",
"search_words": [ "city", "capital" ],
"children": [
{
"name": "Westminster",
"search_words": [ "borough" ]
}
]
},
{
"name": "Manchester",
"search_words": [ "city" ]
}
]
},
{
"name": "France",
"children": [
{
"name": "Paris",
"search_words": [ "city", "capital" ]
}
]
}
]
},
{
"name": "North America",
"children": [
{
"name": "Canada",
"children": [
{ "name": "Toronto" },
{ "name": "Ottawa" }
]
},
{ "name": "United States" }
]
}
]
}
现在我们编写一个通用的深度优先遍历函数,dft-
function* dft (t, path = [])
{ for (const _ of t.children ?? [])
yield* dft(_, [...path, t.name ])
yield [path, t]
}
我们的dft函数为我们的输入树中的path每个元素提供了一个-et
["root","Europe"]
{"name":"Germany"}
["root","Europe","England","London"]
{name:"Westminster", search_words:["borough"]}
["root","Europe","England"]
{name:"London", search_words:["city","capital"], children:[...]}
["root","Europe","England"]
{name:"Manchester", search_words:["city"]}
["root","Europe"]
{name:"England", children:[...]}
["root","Europe","France"]
{name:"Paris", search_words:["city","capital"]}
["root","Europe"]
{name:"France", children:[...]}
["root"]
{name:"Europe", children:[...]}
["root","North America","Canada"]
{name:"Toronto"}
现在我们知道了每个节点的路径,我们可以创建一个index使用path和 anysearch_words链接回该节点的路径 -
const index = t =>
Array.from
( dft(t)
, ([path, e]) =>
[ [...path, e.name, ...e.search_words ?? [] ] // all words to link to e
, e // e
]
)
.reduce
( (m, [ words, e ]) =>
insertAll(m, words, e) // update the index using generic helper
, new Map
)
这取决于通用助手insertAll-
const insertAll = (m, keys, value) =>
keys.reduce
( (m, k) =>
m.set(k, [ ...m.get(k) ?? [], value ])
, m
)
完成后index,我们有一种方法可以为任何搜索词创建快速查找 -
const myIndex =
index(data2)
console.log(myIndex)
Map
{ "Europe" =>
[{"name":"Germany"},{"name":"Westminster",...},{"name":"London",...},{"name":"Manchester",...},{"name":"England"...},{"name":"Manchester",...}]},{"name":"Paris",...},{"name":"France"...},{"name":"Europe"...},{"name":"Manchester",...}]},{"name":"France"...}]}]
, "Germany" =>
[{"name":"Germany"}]
, "England" =>
[{"name":"Westminster",...},{"name":"London",...},{"name":"Manchester",...},{"name":"England"...},{"name":"Manchester",...}]}]
, "London" =>
[{"name":"Westminster",...},{"name":"London",...}]
, "Westminster" =>
[{"name":"Westminster",...}]
, "borough" =>
[{"name":"Westminster",...}]
, "city" =>
[{"name":"London",...},{"name":"Manchester",...},{"name":"Paris",...}]
, "capital" =>
[{"name":"London",...},{"name":"Paris",...}]
, "Manchester" =>
[{"name":"Manchester",...}]
, "France" =>
[{"name":"Paris",...},{"name":"France"...}]
, "Paris" =>
[{"name":"Paris",...}]
, "North America" =>
[{"name":"Toronto"},{"name":"Ottawa"},{"name":"Canada"...},{"name":"United States"},{"name":"North America"...},
{"name":"United States"}]}]
, "Canada" =>
[{"name":"Toronto"},{"name":"Ottawa"},{"name":"Canada"...}]
, "Toronto" =>
[{"name":"Toronto"}]
, "Ottawa" =>
[{"name":"Ottawa"}]
, "United States" =>
[{"name":"United States"}]
}
这应该突出显示数据中剩余的不一致之处。例如,您有一些嵌套在city、capital或下的节点borough。另外值得注意的是,我们可能应该使用s.toLowerCase()所有索引键,以便查找不区分大小写。这是留给读者的练习。
创建index很容易,您只需要做一次-
const myIndex =
index(data2)
您的索引可以根据需要重复用于任意多次查找 -
console.log(myIndex.get("Toronto") ?? [])
console.log(myIndex.get("France") ?? [])
console.log(myIndex.get("Paris") ?? [])
console.log(myIndex.get("Canada") ?? [])
console.log(myIndex.get("Zorp") ?? [])
[{"name":"Toronto"}]
[{"name":"Paris",...},{"name":"France"...}]
[{"name":"Paris",...}]
[{"name":"Toronto"},{"name":"Ottawa"},{"name":"Canada"...}]
[]
将结果插入 Vue 应用程序由您完成。
TA贡献1851条经验 获得超5个赞
不完全清楚您要从问题中寻找什么,但我猜您需要修改数据以确保在渲染时正确突出显示匹配的数据?
这是使用对象扫描查找匹配对象的解决方案
// const objectScan = require('object-scan');
const myData = { root: { Europe: { children: [{ name: 'Germany' }, { name: 'England', children: [{ name: 'London', search_words: ['city', 'capital'], children: [{ name: 'Westminster', search_words: ['borough'] }] }, { name: 'Manchester', search_words: ['city'] }] }, { name: 'France', children: [{ name: 'Paris', search_words: ['city', 'capital'] }] }] }, 'North America': { children: [{ name: 'Canada', children: [{ name: 'Toronto' }, { name: 'Ottawa' }] }, { name: 'United States' }] } } };
// eslint-disable-next-line camelcase
const mySearchFn = (term) => ({ name, search_words = [] }) => name === term || search_words.includes(term);
const search = (input, searchFn) => objectScan(['**[*]'], {
filterFn: ({ value, context }) => {
if (searchFn(value)) {
const { children, ...match } = value;
context.push(match);
}
}
})(input, []);
console.log(search(myData, mySearchFn('Toronto')));
// => [ { name: 'Toronto' } ]
console.log(search(myData, mySearchFn('borough')));
// => [ { name: 'Westminster', search_words: [ 'borough' ] } ]
console.log(search(myData, mySearchFn('capital')));
// => [ { name: 'Paris', search_words: [ 'city', 'capital' ] }, { name: 'London', search_words: [ 'city', 'capital' ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.8.0"></script>
这就是您如何注入信息,然后渲染管道可以获取这些信息
// const objectScan = require('object-scan');
const myData = { root: { Europe: { children: [{ name: 'Germany' }, { name: 'England', children: [{ name: 'London', search_words: ['city', 'capital'], children: [{ name: 'Westminster', search_words: ['borough'] }] }, { name: 'Manchester', search_words: ['city'] }] }, { name: 'France', children: [{ name: 'Paris', search_words: ['city', 'capital'] }] }] }, 'North America': { children: [{ name: 'Canada', children: [{ name: 'Toronto' }, { name: 'Ottawa' }] }, { name: 'United States' }] } } };
// eslint-disable-next-line camelcase
const mySearchFn = (term) => ({ name, search_words = [] }) => name === term || search_words.includes(term);
const search = (input, searchFn) => objectScan(['**[*]'], {
filterFn: ({ value }) => {
if (searchFn(value)) {
value.css = { highlight: true };
return true;
} else {
delete value.css;
return false;
}
},
rtn: 'count' // return number of matches
})(input);
console.log(search(myData, mySearchFn('Toronto')));
// => 1
console.log(myData);
// => { root: { Europe: { children: [ { name: 'Germany' }, { name: 'England', children: [ { name: 'London', search_words: [ 'city', 'capital' ], children: [ { name: 'Westminster', search_words: [ 'borough' ] } ] }, { name: 'Manchester', search_words: [ 'city' ] } ] }, { name: 'France', children: [ { name: 'Paris', search_words: [ 'city', 'capital' ] } ] } ] }, 'North America': { children: [ { name: 'Canada', children: [ { name: 'Toronto', css: { highlight: true } }, { name: 'Ottawa' } ] }, { name: 'United States' } ] } } }
console.log(search(myData, mySearchFn('borough')));
// => 1
console.log(myData);
// => { root: { Europe: { children: [ { name: 'Germany' }, { name: 'England', children: [ { name: 'London', search_words: [ 'city', 'capital' ], children: [ { name: 'Westminster', search_words: [ 'borough' ], css: { highlight: true } } ] }, { name: 'Manchester', search_words: [ 'city' ] } ] }, { name: 'France', children: [ { name: 'Paris', search_words: [ 'city', 'capital' ] } ] } ] }, 'North America': { children: [ { name: 'Canada', children: [ { name: 'Toronto' }, { name: 'Ottawa' } ] }, { name: 'United States' } ] } } }
console.log(search(myData, mySearchFn('capital')));
// => 2
console.log(myData);
// => { root: { Europe: { children: [ { name: 'Germany' }, { name: 'England', children: [ { name: 'London', search_words: [ 'city', 'capital' ], children: [ { name: 'Westminster', search_words: [ 'borough' ] } ], css: { highlight: true } }, { name: 'Manchester', search_words: [ 'city' ] } ] }, { name: 'France', children: [ { name: 'Paris', search_words: [ 'city', 'capital' ], css: { highlight: true } } ] } ] }, 'North America': { children: [ { name: 'Canada', children: [ { name: 'Toronto' }, { name: 'Ottawa' } ] }, { name: 'United States' } ] } } }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.8.0"></script>
使用库对你来说可能不值得,这是一种权衡。如果您对此答案有疑问/想法,请告诉我。
TA贡献1864条经验 获得超6个赞
正如谢谢指出的那样,不一致的数据格式使得为此编写好的代码变得更加困难。我的方法略有不同。我没有转换数据,而是为通用函数编写了一个包装器,以便以更有用的方式处理此输出。
我们从一个函数开始collect,它将递归地处理{name?, search_words?, children?, ...rest}对象,返回与给定谓词匹配的节点并在子节点上重复出现。我们用函数 来调用它,search它接受一个搜索词并从中创建一个谓词。(这里我们测试是否name或任何search_term匹配该术语;对于部分匹配、不区分大小写等,这很容易修改。)
然后我们编写我提到的包装器searchLocations。它下降到节点,然后映射并组合调用每个根值.root的结果。search
const collect = (pred) => ({children = [], ...rest}) => [
... (pred (rest) ? [rest] : []),
... children .flatMap (collect (pred))
]
const search = (term) =>
collect (({name = '', search_words = []}) => name == term || search_words .includes (term))
const searchLocations = (locations, term) =>
Object.values (locations .root) .flatMap (search (term))
const locations = {root: {Europe: {children: [{name: "Germany"}, {name: "England", children: [{name: "London", search_words: ["city", "capital"], children: [{name: "Westminster", search_words: ["borough"]}]}, {name: "Manchester", search_words: ["city"]}]}, {name: "France", children: [{name: "Paris", search_words: ["city", "capital"]}]}]}, "North America": {children: [{name: "Canada", children: [{name: "Toronto"}, {name: "Ottawa"}]}, {name: "United States"}]}}}
console .log ('Toronto', searchLocations (locations, 'Toronto'))
console .log ('borough', searchLocations (locations, 'borough'))
console .log ('capital', searchLocations (locations, 'capital'))
.as-console-wrapper {max-height: 100% !important; top: 0}
如果您想要的(听起来像是您可能想要的)与输入的结构相同,仅保留包含匹配项所需的节点,那么我们应该能够从树过滤函数开始执行类似的操作。假期后我会尝试看看。
更新
我确实又看了一遍,希望将树作为一棵树来过滤。代码并没有那么难。但这一次,我确实使用了一个convert函数将您的数据转换为更一致的递归结构。因此,整个对象变成一个数组,根部有两个元素,一个元素为name“欧洲”,另一个元素为name“北美”,每个元素都有其现有children节点。这使得所有进一步的处理变得更加容易。
这里有两个关键函数:
第一个是通用deepFilter函数,它采用谓词和项目数组,这些项目的节点children结构可能类似于其父级,并返回一个新版本,其中包含与谓词及其完整祖先匹配的任何内容。它看起来像这样:
const deepFilter = (pred) => (xs) =>
xs .flatMap (({children = [], ...rest}, _, __, kids = deepFilter (pred) (children)) =>
pred (rest) || kids.length
? [{...rest, ...(kids.length ? {children: kids} : {})}]
: []
)
第二个是专门针对这个问题的:searchLocation。它调用deepFilter使用由搜索项和已讨论的转换结构构造的谓词。它使用convert结构的帮助器,以及search将搜索词转换为谓词的帮助器,该谓词在名称和所有搜索词上查找(不区分大小写)部分匹配。
const searchLocations = (loc, locations = convert(loc)) => (term) =>
term.length ? deepFilter (search (term)) (locations) : locations
这通过用户界面来演示,该用户界面显示嵌套中的位置<UL>,并带有实时过滤位置的搜索框。
例如,如果您在搜索框中输入“w”,您将得到
Europe
England
London (city, capital)
Westminster (borough)
North America
Canada
Ottawa
因为“Westminster”和“Ottawa”是唯一的匹配项。
如果你输入“城市”你会得到
Europe
England
London (city, capital)
Manchester (city)
France
Paris (city, capital)
您可以在此代码片段中看到它的实际效果:
// utility function
const deepFilter = (pred) => (xs) =>
xs .flatMap (({children = [], ...rest}, _, __, kids = deepFilter (pred) (children)) =>
pred (rest) || kids.length
? [{...rest, ...(kids.length ? {children: kids} : {})}]
: []
)
// helper functions
const search = (t = '', term = t.toLowerCase()) => ({name = '', search_words = []}) =>
term.length && (
name .toLowerCase () .includes (term) ||
search_words .some (word => word .toLowerCase() .includes (term))
)
const convert = ({root}) =>
Object.entries (root) .map (([name, v]) => ({name, ...v}))
// main function
const searchLocations = (loc, locations = convert(loc)) => (term) =>
term.length ? deepFilter (search (term)) (locations) : locations
// sample data
const myData = { root: { Europe: { children: [{ name: 'Germany' }, { name: 'England', children: [{ name: 'London', search_words: ['city', 'capital'], children: [{ name: 'Westminster', search_words: ['borough'] }] }, { name: 'Manchester', search_words: ['city'] }] }, { name: 'France', children: [{ name: 'Paris', search_words: ['city', 'capital'] }] }] }, 'North America': { children: [{ name: 'Canada', children: [{ name: 'Toronto' }, { name: 'Ottawa' }] }, { name: 'United States' }] } } };
// main function specialized to given data
const mySearch = searchLocations(myData)
// UI demo
const format = (locations, searchTerm) => `<ul>${
locations.map(loc => `<li>${
loc.name +
(loc.search_words ? ` (${loc.search_words. join(', ')})` : ``) +
(loc.children ? format(loc.children, searchTerm) : '')
}</li>`)
.join('')
}</ul>`
const render = (locations, searchTerm) =>
document .getElementById ('main') .innerHTML = format (locations, searchTerm)
document .getElementById ('search') .addEventListener (
'keyup',
(e) => render (mySearch (e.target.value))
)
// show demo
render (mySearch (''))
<div style="float: right" id="control">
<label>Search: <input type="text" id="search"/></label>
</div>
<div style="margin-top: -1em" id="main"></div>
显然,这并没有使用Vue来生成树,只是进行一些字符串操作和innerHTML. 我把这部分留给你。但它应该显示另一种过滤嵌套结构的方法。
添加回答
举报