英文:
How to return an object whose nested objects match a list of search strings?
问题
我尝试返回一个对象,该对象是与给定的搜索字符串列表匹配的对象集合。
测试数据结构
var data = {
"tabs-1": {
"test 1": {
"test 2": {
"test 3a": {
"tab1graph1": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3b": {
"tab1graph2": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3c": {
"tab1graph3": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
}
}
}
}
};
输入
var searchList = ["apple", "orange", "test 3b", "test 3a"];
当前代码仅返回一个匹配项,而不是两个预期的匹配项:
function searchObjectForValues(obj, searchList) {
var matchingObjects = {};
function recursiveSearch(currentObj, path) {
if (typeof currentObj === 'object' && currentObj !== null) {
for (var key in currentObj) {
if (currentObj.hasOwnProperty(key)) {
var value = currentObj[key];
var currentPath = path.concat(key);
if (searchList.includes(key)) {
matchingObjects[key] = currentObj[key];
}
recursiveSearch(value, currentPath);
}
}
}
}
recursiveSearch(obj, []);
return matchingObjects;
}
var searchList = ["apple", "testx3", "test 3b", "test 3a"];
var result = searchObjectForValues(data, searchList);
console.log(result);
如何修改此函数以仅返回以下部分而不返回上面的 "test 3a" 部分?
"tab1graph1": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
英文:
I'm trying to return an object that is a collection of objects that match a given list of search strings.
Testing Data Structure
var data = {
"tabs-1": {
"test 1": {
"test 2": {
"test 3a": {
"tab1graph1": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3b": {
"tab1graph2": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3c": {
"tab1graph3": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
}
}
}
}
};
Input
var searchList = ["apple", "orange", "test 3b", "test 3a"];
Current code that only returns one match, and not both expected matches:
function searchObjectForValues(obj, searchList) {
var matchingObjects = {};
function recursiveSearch(currentObj, path) {
if (typeof currentObj === 'object' && currentObj !== null) {
for (var key in currentObj) {
if (currentObj.hasOwnProperty(key)) {
var value = currentObj[key];
var currentPath = path.concat(key);
if (searchList.includes(key)) {
matchingObjects[key] = currentObj[key];
}
recursiveSearch(value, currentPath);
}
}
}
}
recursiveSearch(obj, []);
return matchingObjects;
}
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
function searchObjectForValues(obj, searchList) {
var matchingObjects = {};
function recursiveSearch(currentObj, path) {
if (typeof currentObj === 'object' && currentObj !== null) {
for (var key in currentObj) {
if (currentObj.hasOwnProperty(key)) {
var value = currentObj[key];
var currentPath = path.concat(key);
if (searchList.includes(key)) {
matchingObjects[key] = currentObj[key];
}
recursiveSearch(value, currentPath);
}
}
}
}
recursiveSearch(obj, []);
return matchingObjects;
}
var data = {
"tabs-1": {
"test 1": {
"test 2": {
"test 3a": {
"tab1graph1": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3b": {
"tab1graph2": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3c": {
"tab1graph3": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
}
}
}
}
};
var searchList = ["apple", "testx3", "test 3b", "test 3a"];
var result = searchObjectForValues(data, searchList);
console.log(result);
<!-- end snippet -->
How can I modify this function to return just the
"tab1graph1": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
part and not the "test 3a" above it?
答案1
得分: 2
简单组合
对许多值执行search
是调用每个值上的search1
的事情 -
function *search(data, values) {
for (const value of values)
yield *search1(data, value)
}
编写search1
现在更容易了 -
function *search1(data, value) {
if (Object(data) === data) {
for (const key of Object.keys(data)) {
if (key === value)
yield data[key]
else
yield *search1(data[key], value)
}
}
}
现在我们使用我们的数据和搜索列表来调用search
-
var searchList = ["apple", "testx3", "test 3b", "test 3a"]
for (const result of search(data, searchList))
console.log(result)
每个结果都被记录。或者使用Array.from
将所有结果收集到一个数组中 -
{
"tab1graph2": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
}
{
"tab1graph1": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
}
演示
在下面的浏览器中验证结果 -
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const data = {
"tabs-1": {
"test 1": {
"test 2": {
"test 3a": {
"tab1graph1": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3b": {
"tab1graph2": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3c": {
"tab1graph3": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
}
}
}
}
};
function *search1(data, value) {
if (Object(data) === data) {
for (const key of Object.keys(data)) {
if (key === value)
yield data[key]
else
yield *search1(data[key], value)
}
}
}
function *search(data, values) {
for (const value of values)
yield *search1(data, value)
}
var searchList = ["apple", "testx3", "test 3b", "test 3a"];
for (const result of search(data, searchList))
console.log(result)
<!-- language: lang-css -->
.as-console-wrapper { min-height: 100%; top: 0; }
<!-- end snippet -->
具有谓词的高阶函数
根据@Scott的评论,上面的解决方案可以适应使用谓词函数 -
function *search(data, predicate) {
if (Object(data) === data) {
for (const entry of Object.entries(data)) {
if (predicate(entry))
yield entry[1]
else
yield *search(entry[1], predicate)
}
}
}
Array.from(
search(
data,
([key, value]) => searchList.includes(key)
)
)
顺序搜索
在评论中,您寻找找到连续键的可能性,例如key1->key2
。为了支持这一点,我们将单个的value
字段更改为path
,它表示字符串数组。
function *search1(data, path) {
if (key.length === 0)
yield data
else if (Object(data) === data) {
for (const key of Object.keys(data)) {
if (data[key] === path[0])
yield *search1(data[key], path.slice(1))
else
yield *search1(data[key], path)
}
}
}
search
保持不变,但现在您要用一个路径数组来调用它 -
for (const result of search(data, [
["test 1", "test 3a"], // test 1 -> test 3a
["test 2", "String a"] // test 2 -> String a
]) {
console.log(result)
}
{ //
"tab1graph1": { //
"String a": "value a", // 匹配项:
"String b": "value b", // test 1 -> test 3a
"String c": "value c" //
} //
} //
value a // 匹配项:
value a // test 2 -> String a
value a //
英文:
simple composition
Performing a search
for many values is a matter of calling search1
on each value -
function *search(data, values) {
for (const value of values)
yield *search1(data, value)
}
Writing search1
is now easier -
function *search1(data, value) {
if (Object(data) === data) {
for (const key of Object.keys(data)) {
if (key === value)
yield data[key]
else
yield *search1(data[key], value)
}
}
}
Now we call search
with our data and search list -
var searchList = ["apple", "testx3", "test 3b", "test 3a"]
for (const result of search(data, searchList))
console.log(result)
Each result is logged. Alternatively use Array.from
to collect all results in an array -
{
"tab1graph2": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
}
{
"tab1graph1": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
}
demo
Verify the results in your own browser below -
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const data = {
"tabs-1": {
"test 1": {
"test 2": {
"test 3a": {
"tab1graph1": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3b": {
"tab1graph2": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3c": {
"tab1graph3": {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
}
}
}
}
};
function *search1(data, value) {
if (Object(data) === data) {
for (const key of Object.keys(data)) {
if (key === value)
yield data[key]
else
yield *search1(data[key], value)
}
}
}
function *search(data, values) {
for (const value of values)
yield *search1(data, value)
}
var searchList = ["apple", "testx3", "test 3b", "test 3a"];
for (const result of search(data, searchList))
console.log(result)
<!-- language: lang-css -->
.as-console-wrapper { min-height: 100%; top: 0; }
<!-- end snippet -->
higher-order function with predicate
Per @Scott's comment, the solution above can be adapted to use a predicate function -
function *search(data, predicate) {
if (Object(data) === data) {
for (const entry of Object.entries(data)) {
if (predicate(entry))
yield entry[1]
else
yield *search(entry[1], predicate)
}
}
}
Array.from(
search(
data,
([key, value]) => searchList.includes(key)
)
)
sequential search
In a comment you seek the possibility to find sequential keys, for example key1->key2
. To support this, we will change the single value
field to path
, which represents an array of strings.
function *search1(data, path) {
if (key.length === 0)
yield data
else if (Object(data) === data) {
for (const key of Object.keys(data)) {
if (data[key] === path[0])
yield *search1(data[key], path.slice(1))
else
yield *search1(data[key], path)
}
}
}
search
stays the same but now you call it with an array of paths -
for (const result of search(data, [
["test 1", "test 3a"], // test 1 -> test 3a
["test 2", "String a"] // test 2 -> String a
]) {
console.log(result)
}
{ //
"tab1graph1": { //
"String a": "value a", // matches for:
"String b": "value b", // test 1 -> test 3a
"String c": "value c" //
} //
} //
value a // matches for:
value a // test 2 -> String a
value a //
答案2
得分: 2
一种方法是编写一个相当通用的 deepFilter
函数,该函数在其输出数组中包含与提供的谓词函数匹配的所有节点,无论它们的深度如何。然后,我们可以使用与您的搜索键列表匹配的谓词函数配置它,并将其与您的数据一起调用。
const deepFilter = (pred) => (o) =>
Object(o) === o
? Object.entries(o).flatMap(([k, v]) => [
...(pred(v, k) ? [v] : []),
...deepFilter(pred)(v)
])
: []
const deepFindKeys = (keys) =>
deepFilter((_, k) => keys.includes(k))
const data = {
"tabs-1": {
"test 1": {
"test 2": {
"test 3a": {
tab1graph1: {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3b": {
tab1graph2: {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
},
"test 3c": {
tab1graph3: {
"String a": "value a",
"String b": "value b",
"String c": "value c"
}
}
}
}
}
};
console.log(deepFindKeys(["apple", "testx3", "test 3b", "test 3a"])(data));
我们的函数 deepFindKeys
获取键并返回当我们传递一个简单的键匹配谓词到 deepFilter
时返回的函数。 deepFilter
具有真正的实质。如果输入不是对象,我们返回一个空数组。如果是对象,我们对其条目进行 flatmap 处理,对于每个条目,如果谓词匹配,则包含该值并在该值上进行递归。
最常见的情况是,当我们想要匹配谓词时,我们想要测试值,而不是键,因此首先向谓词提供值;如果需要,它可以忽略键。在这里,我们实际上要测试键并忽略值,所以我们传递了 (_, k)
,其中 _
引起注意我们只想跳过第一个值。
比较
Mulan 的答案与这个答案非常不同。他们介绍了不同类型的灵活性。在 deepFilter
中,我们使用一种通用方法来匹配对象。这使得它可以在广泛的搜索函数中重复使用。但它总是遍历整个树。例如,无法在找到前五个匹配项后停止。
Mulan 的 search
和 search1
是生成器函数。虽然该解决方案不如我的通用,但它专注于这个特定问题,提供了另一个非常有用的功能:它返回一个迭代器,让我们可以单独提取和处理条目,这可能非常强大。而且因为您可以简单地将结果转换回数组,如果您不想要迭代,它至少同样强大。
需要注意的是,Mulan 的解决方案很容易增强以处理与我相同类型的灵活性。反之则不然。试图将此版本转换为生成器函数将是一种完全的重写。
我并不是说 Mulan 的方法是普遍更好的。如果您只想要一个数组结果,这个版本更简单(而且可能更高效)。但是 Mulan 的方法可以经过改进,使其提供一些这个方法根本无法提供的灵活性。
英文:
One approach is to write a fairly generic deepFilter
function which includes in its output array all nodes -- at whatever depth -- that match the predicate function supplied. Then we can configure it with a predicate function that matches against your list of search keys, and call it with your data:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const deepFilter = (pred) => (o) =>
Object(o) === o
? Object.entries(o).flatMap(([k, v]) => [
...(pred(v, k) ? [v] : []),
...deepFilter(pred) (v)
])
: []
const deepFindKeys = (keys) =>
deepFilter((_, k) => keys.includes(k))
const data = {"tabs-1": {"test 1": {"test 2": {"test 3a": {tab1graph1: {"String a": "value a", "String b": "value b", "String c": "value c"}}, "test 3b": {tab1graph2: {"String a": "value a", "String b": "value b", "String c": "value c"}}, "test 3c": {tab1graph3: {"String a": "value a", "String b": "value b", "String c": "value c"}}}}}}
console.log(deepFindKeys(["apple", "testx3", "test 3b", "test 3a"])(data))
<!-- language: lang-css -->
.as-console-wrapper {max-height: 100% !important; top: 0}
<!-- end snippet -->
Our function deepFindKeys
takes the keys and returns the function returned when we pass a simple key-matching predicate to deepFilter
. deepFilter
has the real substance. If the input is not an object, we return an empty array. If it is, we flatmap its entries, for each one, including the value if the predicate matches and recurring on the value.
Most commonly when we want to search to match a predicate, we want to test the value, not the key, so the predicate is supplied the value first; it can ignore the key if it wants. Here we actually want to test the key and ignore the value, so we pass (_, k)
where the _
calls attention to the fact that we just want to skip the first value.
A Comparison
The answer from Mulan is quite different from this one. They introduce different sorts of flexibility. In deepFilter
, the main function here, we use a generic approach to how we wish to match our objects. It makes this reusable across a wide swath of search functions. But it will always traverse the entire tree. There's no way to stop after, say, the first five hits.
Mulan's search
and search1
are generator functions. While the solution is less general than mine, focusing on this specific problem, it offers another very useful feature: it returns a iterator letting us pull and process the entries individually, which can be very powerful. And because you can simply turn the results back into an array if you don't want the iteration, it's at least as powerful
The thing to note is that Mulan's solution could easily be enhanced to handle the same sort of flexibility mine has. The reverse is not true. To try to make this version into a generator function would be an entire rewrite.
I'm not trying to say that Mulan's approach is universally better. If you only ever want an array result, this version is simpler (and probably more time-efficient). But Mulan's can be made to offer some flexibility this one simply cannot.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论