I'm making a checklist app and need to navigate through my data to edit the complete:false
My data is structured with children being an optional key, and objects with children won't have the completed key.
There are two ways I could solve this
- use each part of the
path
and navigate the array usingitems[path[0]]
and so on - Or use
path
as (or replace it with) a uniqueid
and search for that id
both of which I do not know how to do
const TESTDATA = [
{
name: 'foo',
path: [0],
open: true,
children: [
{ name: 'bar', completed: false, path: [0, 0] },
{ name: 'data', completed: false, path: [0, 1] },
],
},
{
name: 'foo',
path: [1],
open: true,
children: [
{ name: 'bar', completed: false, path: [1, 0] },
{
name: 'data',
completed: false,
path: [1, 1],
children: [{ name: 'baz', completed: false, path: [1, 1, 0] }],
},
],
},
]
Is there a way to recursively (or not) search for a specific path/id in all the children keys and return the item so it could be modified?
CodePudding user response:
1. Follow path
This is the more direct way, as we can literally go and find the wanted element. As such, this is likely more performant.
Here is one way to do it:
- Navigate through all necessary
.children
s. - Take the element specified by the last index in
path
.
1. Navigate .children
s:
function findChild(path, data) {
let array = data;
for (let i = 0; i < path.length - 1; i) {
const pathIndex = path[i];
array = array[pathIndex].children;
}
// ...
}
2. Take element by last index:
function findChild(path, data) {
let array = data;
for (let i = 0; i < path.length - 1; i) {
const pathIndex = path[i];
array = array[pathIndex].children;
}
return array[path[path.length - 1]];
}
We actually haven't yet taken edge-cases into account, namely:
- What if
data
(or any array here) is an empty array? - What if elements along the given path don't have
.children
?
Here's a fix to that:
function findChild(path, data) {
let array = data;
for (let i = 0; i < path.length - 1 && array; i) {
const pathIndex = path[i];
array = array[pathIndex]?.children;
}
return array?.[path[path.length - 1]];
}
function findChild(path, data) {
let array = data;
for (let i = 0; i < path.length - 1 && array; i) {
const pathIndex = path[i];
array = array[pathIndex]?.children;
}
return array?.[path[path.length - 1]];
}
const data = [
{
name: 'foo',
path: [0],
open: true,
children: [
{ name: 'bar', completed: false, path: [0, 0] },
{ name: 'data', completed: false, path: [0, 1] },
],
},
{
name: 'foo',
path: [1],
open: true,
children: [
{ name: 'bar', completed: false, path: [1, 0] },
{
name: 'data',
completed: false,
path: [1, 1],
children: [{ name: 'baz', completed: false, path: [1, 1, 0] }],
},
],
},
];
console.time();
const result = findChild([1, 0], data);
console.timeEnd();
console.log("Imperative:", result);
2. Find by path
(as ID)
Ideally we have all elements in a single, flat array, because then we can simply do a Array.find()
search.
Because we initially get the elements in a tree-like structure, we have to flatten them manually. Here is one way to do that:
const _flatten = (el) => ([el, ...(el.children ?? []).flatMap(_flatten)]);
const flatten = (array) => array.flatMap(_flatten);
Now that we have a flat array of all the elements, we can use Array.find()
:
function findChild(path, treeElements) {
// When equal, serializes to the same string
const isEqual = (p1, p2) => p1.join() === p2.join();
const _flatten = (el) => ([el, ...(el.children ?? []).flatMap(_flatten)]);
const flatten = (array) => array.flatMap(_flatten);
return flatten(treeElements).find(el => isEqual(el.path, path));
}
function findChild(path, treeElements) {
// When equal, serializes to the same string
const isEqual = (p1, p2) => p1.join() === p2.join();
const _flatten = (el) => ([el, ...(el.children ?? []).flatMap(_flatten)]);
const flatten = (array) => array.flatMap(_flatten);
return flatten(treeElements).find(el => isEqual(el.path, path));
}
const data = [
{
name: 'foo',
path: [0],
open: true,
children: [
{ name: 'bar', completed: false, path: [0, 0] },
{ name: 'data', completed: false, path: [0, 1] },
],
},
{
name: 'foo',
path: [1],
open: true,
children: [
{ name: 'bar', completed: false, path: [1, 0] },
{
name: 'data',
completed: false,
path: [1, 1],
children: [{ name: 'baz', completed: false, path: [1, 1, 0] }],
},
],
},
];
console.time();
const result = findChild([1, 0], data);
console.timeEnd();
console.log("Functional:", result);
Comparison between 1. and 2.
As per JSBen.ch, the first option compared to the second option is about 4-5 times faster, as expected.
CodePudding user response:
maybe something like this can get you started:
function gather(name, obj){
if(name==obj.name) return obj;
if(obj.children) return obj.children.map(x=>gather(name, x)).flat().filter(Boolean)
}
TESTDATA.map(x=>gather("baz", x)).flat()[0]