JavaScript:将递归结果添加到数组并返回它



我有一个嵌套对象,我的目标是获取路径,直到键值对在数组中匹配。

我目前的实现是通过字符串实现的,并将字符串与dot (".")连接起来。

但是,我想将所有的中间结果添加到数组中并将其压入。

带示例的代码


const data = [
{
parentId: "1111",
name: "Audi",
children : [
{
parentId: "2222",
name: "Benz",
children : [
{
parentId: "3333",
name: "VW",
children : [
]
}
]
}
]
}
]


const pathTo = (array, target) => {
var result;
array.some(({ parentId, name, children = [] }) => {
if (parentId === target) {
return result = JSON.stringify({"parentId" : parentId, "name" : name});
}
var temp = pathTo(children, target)
if (temp) {
return result = JSON.stringify({"parentId" : parentId, "name" : name}) + "." + temp;
}
});
return result;
};

console.log(pathTo(data, "3333"))

当前结果


{"parentId":"1111","name":"Audi"}.{"parentId":"2222","name":"Benz"}.{"parentId":"3333","name":"VW"}

=比;与字符串连接的路径。但是我想:

预期结果


[ "{"parentId":"1111","name":"Audi"}", "{"parentId":"2222","name":"Benz"}"{"parentId":"3333","name":"VW"}"]

=比;

所有元素按顺序排列的数组。

您可以返回一个数组或undefined

const
data = [{ parentId: "1111", name: "Audi", children : [{ parentId: "2222", name: "Benz", children : [{ parentId: "3333", name: "VW", children : [] }] }] }],
pathTo = (array, target) => {
let result;
array.some(({ parentId, name, children = [] }) => {
if (parentId === target) {
result = [{ parentId: parentId, name: name }];
return true;
}
const temp = pathTo(children, target)
if (temp) {
result = [{ parentId: parentId, name: name }, ...temp];
return true;
}
});
return result;
};
console.log(pathTo(data, "3333"))
.as-console-wrapper { max-height: 100% !important; top: 0; }

我更喜欢将这样的问题分解成几个部分,并在此过程中至少使其中一些更通用。

function * traversePaths (xs, path = []) {
for (let x of xs) {
const newPath = [...path, x]
yield newPath
yield * traversePaths (x .children || [], newPath)
}
}
const deepFindPath = (pred) => (xs) => {
for (let path of traversePaths (xs)) {
if (pred (path [path.length - 1])) {return path} 
}
}
const pathByParentId = (target) =>
deepFindPath (({parentId}) => parentId == target)
const data = [{parentId: "1111", name: "Audi", children : [{parentId: "2222", name: "Benz", children : [{parentId: "3333", name: "VW", children : []}]}]}]

// stringified to avoid SO's `/**id:4**/` - `/**ref:4**/,` notation
console .log (JSON .stringify ( 
pathByParentId ('3333') (data)
, null, 4))
.as-console-wrapper {max-height: 100% !important; top: 0}

这里的traversePaths是一个生成器函数,它接受任何元素具有(可选的,递归的)children属性的数组,并遍历它(预顺序),为数组中的每个路径生成一个对象/子对象节点的数组。

deepFindPath返回与所提供的谓词匹配的第一个节点的路径。

这两个函数是泛型的。然后,为了解决您的问题,我们编写简单的pathByParentId,它简单地接受目标id并将一个函数传递给deepFindPath,该函数测试节点的parentId属性是否与目标id匹配。它将返回第一个匹配节点的路径,如果没有匹配,则返回undefined

您请求的输出与上述匹配,只是您没有提到children节点。我更喜欢这个返回,只是对现有节点的简单引用。但是如果你真的不想要孩子,那么你可以这样做:

const pathByParentId = (target) => (
data, 
res = deepFindPath (({parentId}) => parentId == target) (data)
) => res && res .map (({children, ...rest}) => rest)

虽然我们可以在遍历函数中这样做,但这会使该函数不那么泛型,可能也不那么有用。但是,如果您想这样做,您可以保留pathByParentId的原始版本,并将traversePaths替换为:

function * traversePaths (xs, path = []) {
for (let {children, ...rest} of xs) {
const newPath = [...path, rest]
yield newPath
yield * traversePaths (children || [], newPath)
}
}

这比以前的版本更不灵活,但它仍然通常列出任何元素具有(可选的,递归的)children属性的数组的路径。这些列表中的元素是新对象,类似于原始对象,但缺少children属性。

如果您可以使用依赖项,我将这样做。下面是一个使用对象扫描的解决方案。

.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan@18.1.2/lib/index.min.js';
const data = [{ parentId: '1111', name: 'Audi', children: [{ parentId: '2222', name: 'Benz', children: [{ parentId: '3333', name: 'VW', children: [] }] }] }];
const find = (obj, v) => objectScan(['**'], {
abort: true,
filterFn: ({ value }) => value === v,
rtn: ({ parents }) => parents
.filter((p) => !Array.isArray(p))
.reverse()
.map(({ parentId, name }) => ({ parentId, name }))
})(obj);
console.log(find(data, '3333'));
/* => [
{ parentId: '1111', name: 'Audi' },
{ parentId: '2222', name: 'Benz' },
{ parentId: '3333', name: 'VW' }
] */
</script>

免责声明:我是object-scan的作者

最新更新