I have an array of nested objects. These objects take one of two forms:
// type a
{
value: 'some value'
}
or
// type b
{
array: [
object of type a or b,
object of type a or b,
...
]
}
So, the base array can be nested infinitely. Given a series of indices (I've been calling it a 'tree'), how can I remove a single item at any depth?
A sample of what I have so far:
const baseArray = [
{ value: 'some value' },
{ array: [
{ value: 'some value' },
{ array: [
{ value: 'some value' },
{ value: 'some value' },
],
},
{ value: 'some value' },
{ array: [
{ value: 'some value' },
{ array: [
{ value: 'delete me' },
{ value: 'some value' },
]
},
],
},
],
}
]
const tree = [1, 3, 1, 0]
function deleteNested(tree, inputArray) {
const index = tree.shift();
console.log(inputArray, index);
const child = inputArray[index].array;
if (tree.length > 0) {
console.log(child)
return deleteNested(tree, child);
}
return [
...inputArray.slice(0, index),
...inputArray.slice(index + 1)
]
}
const originalArray = baseArray.slice(0);
console.log(deleteNested(tree, baseArray), originalArray);
I want to delete the marked object given it's 'tree' location: [1, 3, 1, 0]:
first, look in the 1 (index of 1, not 0) value of the initial array,
then the 3 value,
then look at the 1 value,
then finally remove the 0 value.
What I have above isn't working, but has gotten me started.
The function needs to be recursive to work at any depth. It should ideally not use splice() to avoid modifying the array passed into it -- rather, it should return a new one.
As i said in the comments if you know the number of iterations in advance you shouldn't use a recursive approach. A while loop is ideal such as;
function delNestedItem(a,dm){
var i = 0;
while (i < dm.length-1) a = a[dm[i++]].array;
a.splice(dm[i],1);
}
var data = [
{ value: 'some value' },
{ array: [
{ value: 'some value' },
{ array: [
{ value: 'some value' },
{ value: 'some value' },
],
},
{ value: 'some value' },
{ array: [
{ value: 'some value' },
{ array: [
{ value: 'delete me' },
{ value: 'some value' },
]
},
],
},
],
}
],
delMark = [1, 3, 1, 0];
delNestedItem(data,delMark);
console.log(JSON.stringify(data,null,2));
Here's how you can do it in 1 reduce:
const baseArray = [{
value: 'some value'
}, {
array: [{
value: 'some value'
}, {
array: [{
value: 'some value'
}, {
value: 'some value'
}, ],
}, {
value: 'some value'
}, {
array: [{
value: 'some value'
}, {
array: [{
value: 'delete me'
}, {
value: 'some value'
}, ]
}, ],
}, ],
}];
const tree = [1, 3, 1, 0];
var deleted = tree.reduce(function(pos, pathIndex, index, arr) {
if (index + 1 < arr.length) {
return pos.array
? pos.array[pathIndex]
: pos[pathIndex];
} else {
pos.array = pos.array
.slice(0, pathIndex)
.concat(pos.array.slice(pathIndex + 1));
return pos;
}
}, baseArray);
console.log(baseArray);
You could generate a new array out of the given array only with the undeleted parts.
function ff(array, tree) {
function iter(array, level) {
var r = [];
array.forEach(function (a, i) {
if (tree[level] !== i) {
return r.push(a);
}
if (level + 1 !== tree.length && a.array) {
r.push({ array: iter(a.array, level + 1) });
}
});
return r;
}
return iter(array, 0);
}
var baseArray = [{ value: 'some value' }, { array: [{ value: 'some value' }, { array: [{ value: 'some value' }, { value: 'some value' }, ], }, { value: 'some value' }, { array: [{ value: 'some value' }, { array: [{ value: 'delete me' }, { value: 'some value' }, ] }, ], }, ], }],
tree = [1, 3, 1, 0],
copy = ff(baseArray, tree);
console.log(copy);
console.log(baseArray);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
I would like to filter an array of objects according to the highest value of "value" key and distinct each object by their "id" key.
Example :
var array = [
{
id: 1,
value: 10
},
{
id: 1,
value: 2
},
{
id: 2,
value: 6
},
{
id: 2,
value: 5
},
{
id: 2,
value: 1
}
]
And the expected output:
array = [
{
id: 1,
value: 10
},
{
id: 2,
value: 6
}
]
Thanks
You could reduce the array and check if an object exist with the same id and update if necessary. This approach takes the objects with the largest value.
const
array = [{ id: 1, value: 10 }, { id: 1, value: 2 }, { id: 2, value: 6 }, { id: 2, value: 5 }, { id: 2, value: 1 }],
result = array.reduce((r, o) => {
const index = r.findIndex(({ id }) => o.id === id);
if (index === -1) r.push(o);
else if (r[index].value < o.value) r[index] = o;
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Another approach with a Map.
const
array = [{ id: 1, value: 10 }, { id: 1, value: 2 }, { id: 2, value: 6 }, { id: 2, value: 5 }, { id: 2, value: 1 }],
result = Array.from(
array.reduce((m, { id, value }) => m.set(id, m.has(id)
? Math.max(m.get(id), value)
: value
), new Map),
([id, value]) => ({ id, value })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here is a possible way to doing this
var array = [
{
id: 1,
value: 10
},
{
id: 1,
value: 2
},
{
id: 2,
value: 6
},
{
id: 2,
value: 5
},
{
id: 2,
value: 1
}
]
let objdis = {};
array.forEach(val => {
if(objdis[val.id] !== undefined){
objdis[val.id].push(Number(val.value));
}else{
objdis[val.id] = [];
objdis[val.id].push(val.value);
}
})
let returningObj = [];
for (prop in objdis){
let obj = {};
obj.id = Number(prop);
obj.Value = Math.max(...objdis[prop])
returningObj.push(obj);
}
console.log(returningObj);
i hope this helps
var arr = [
{
id: 1,
value: 10
},
{
id: 1,
value: 2
},
{
id: 2,
value: 6
},
{
id: 2,
value: 5
},
{
id: 2,
value: 1
}
];
const output = Object.values(arr.reduce((x, y) => {
x[y.id] = x[y.id] && x[y.id].value > y.value ? x[y.id] : y
return x
}, {}));
console.log(output);
There's my answer, but it needs to have sorted indexes and isn't as cool as reduce examples You got here.
var array = [
{
id: 1,
value: 10
},
{
id: 1,
value: 1
},
{
id: 2,
value: 1
},
{
id: 2,
value: 5
},
{
id: 2,
value: 35
},
{
id: 3,
value: 4
},
{
id: 3,
value: 14
},
{
id: 2,
value: 123
}
];
function getHighestValues(Arr) {
var newArr = [];
var IdArr = Arr.map(elem => elem.id);
IdArr.sort((a,b)=>a-b);
for(var i = 0; i < Arr.length; i++) {
let currIdIndex = IdArr.indexOf(Arr[i].id);
if(newArr[currIdIndex] == undefined) {
newArr.push(
{
id: Arr[i].id,
value: Arr[i].value
})
} else if(newArr[currIdIndex].value < Arr[i].value) {
newArr[currIdIndex].value = Arr[i].value;
}
IdArr.splice(currIdIndex, 1);
if(IdArr.indexOf(Arr[i].id) == -1) {
IdArr.splice(currIdIndex, 0, Arr[i].id);
}
}
return newArr;
}
array = getHighestValues(array);
console.log(array);
Trying to implement a tree search function which takes an array(tree structure) and a string keyword, would return an tree array but only keep the matched nodes and its parents.
function search(nodes, keyword){
}
const nodes = [
{
value: "1-1",
children: [
{ value: "1-1-1"},
{ value: "1-1-2", children:[
{
value: "1-1-2-1",
children: [
{ value: "1-1-2-1-1" },
{ value: "1-1-2-1-2" }
]
},
{
value: "1-1-2-2"
}
] }
]
},
{
value: "1-2",
children: [
{ value: "1-2-1"},
{ value: "1-2-2", children:[
{
value: "1-2-2-1",
children: [
{ value: "1-2-2-1-1" },
{ value: "1-2-2-1-2" }
]
},
{
value: "1-2-2-2"
}
] }
]
},
];
expected output would be an tree with nodes' values contain "1-1-2-1" and its parents as below
const searchedNodes = search(nodes, "1-1-2-1");
[
{
value: "1-1",
children: [
{ value: "1-1-2", children:[
{
value: "1-1-2-1",
children: [
{ value: "1-1-2-1-1" }
]
}
] }
]
}
]
*/
2018-06-26 Updated
I made a working one(DFS) but probably not pretty efficient.
const search = (nodes, keyword) => {
let newNodes = [];
for (let n of nodes) {
if (n.children) {
const nextNodes = this.keywordFilter(n.children, keyword);
if (nextNodes.length > 0) {
n.children = nextNodes;
} else if (n.label.toLowerCase().includes(keyword.toLowerCase())) {
n.children = nextNodes.length > 0 ? nextNodes : [];
}
if (
nextNodes.length > 0 ||
n.label.toLowerCase().includes(keyword.toLowerCase())
) {
newNodes.push(n);
}
} else {
if (n.label.toLowerCase().includes(keyword.toLowerCase())) {
newNodes.push(n);
}
}
}
return newNodes;
};
You need to iterate the nodes of the same level and check if the value is equal, then take that node and exit the loop. Otherwise check the children and generate a new object for preventing to mutate the original data.
function search(nodes, value) {
var result;
nodes.some(o => {
var children;
if (o.value === value) {
return result = o;
}
if (o.children && (children = search(o.children, value))) {
return result = Object.assign({}, o, { children });
}
});
return result && [result];
}
const nodes = [{ value: "1-1", children: [{ value: "1-1-1" }, { value: "1-1-2", children: [{ value: "1-1-2-1", children: [{ value: "1-1-2-1-1" }, { value: "1-1-2-1-2" }] }, { value: "1-1-2-2" }] }] }, { value: "1-2", children: [{ value: "1-2-1" }, { value: "1-2-2", children: [{ value: "1-2-2-1", children: [{ value: "1-2-2-1-1" }, { value: "1-2-2-1-2" }] }, { value: "1-2-2-2" }] }] }];
console.log(search(nodes, "1-1-2-1"));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have two arrays, and now I want to merge the two arrays.
The first array:
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
Two arrays:
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
I want to merge them into this:
var data3 = [
{name: 'aa', value: 1},
{name: 'bb', value: 2},
{name: 'cc', value: 3},
{name: 'dd', value: 0},
{name: 'ee', value: 0}
];
console.log(data3)
At present, my experience is not enough. Please help me solve this problem.
Thanks in advance.
You can try following based on following assumptions
data2 is a collection of names and expecting its length to be always more than length of data
Order of objects can be different
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
// Iterate over the names array
var data3 = data2.map(({name}) => {
// get the matched object in data corresponding to the name
var match = data.find((obj) => obj.name === name);
// if found, return value else default value to 0
return match ? match : {name, value : 0};
});
console.log(data3);
If the input arrays are indeed in order like that, then a simple .map would suffice:
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
const output = data2.map(({ name }, i) => ({ name, value: data[i] ? data[i].value : 0 }));
console.log(output);
Create an object lookup for each name using array#reduce. Extract all the values using the Object.values() from the object lookup.
const data1 = [ { name: 'aa', value: 1 }, { name: 'bb', value: 2 }, { name: 'cc', value: 3 } ],
data2 = [ { name: 'aa' }, { name: 'bb' }, { name: 'cc' }, { name: 'dd' }, { name: 'ee' } ],
result = Object.values([data1, data2].reduce((r,a) => {
a.forEach(({name, value = 0}) => {
r[name] = name in r ? r[name] : {name, value};
});
return r;
},{}));
console.log(result);
You can array#concat both the arrays and using array#reduce create an object lookup and then get all the values using the Object.values().
const data1 = [ { name: 'aa', value: 1 }, { name: 'bb', value: 2 }, { name: 'cc', value: 3 } ],
data2 = [ { name: 'aa' }, { name: 'bb' }, { name: 'cc' }, { name: 'dd' }, { name: 'ee' } ],
result = Object.values(data1.concat(data2).reduce((r,{name, value=0}) => {
r[name] = name in r ? r[name] : {name, value};
return r;
},{}));
console.log(result);
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
let output = new Array(data2.length).fill(data2.length).map(v => new Object());
// Logic
data.forEach((val2)=> {
data2.forEach((val, i)=> {
if (val.name == val2.name){
output[i]["name"] = val.name
output[i]["value"] = val2.value
} else{
output[i]["name"] = val.name
}
})
})
output.map((val,i) => {
if (!val.hasOwnProperty("value")){
console.log(val)
val["value"] = 0
}
})
console.log("------Your Expected Format", output)
This question already has answers here:
Recursively filter array of objects
(9 answers)
Closed 4 years ago.
I have an array that looks something like this:
var a = [
{
value: 'Some data',
structure: 'linear',
id: 0,
children: [
{ id: 1, value: 'Some data', structure: undefined },
{ id: 2,
value: 'Some data',
structure: 'linear',
children: [
{ id: 4, value: 'Some data', structure: undefined }
]
}
]
},
{
id: 5,
structure: undefined
value: 'Some data',
},
];
I am trying to remove all the objects which value of structure is undefined.
Every matching object with no children, or matches in children hierarchy, should not exist in output array.
I don't know at run time how many levels of objects will have a children array
The output should look something like this:
const output = [
{
value: 'Some data',
structure: 'linear',
id: 0,
children: [
{ id: 2, value: 'Some data', structure: 'linear' }
]
}
];
Map over the data recursively and then return the result after filtering the undefined values like
var a = [
{
value: 'Some data',
structure: 'linear',
id: 0,
children: [
{ id: 1, value: 'Some data', structure: undefined },
{
id: 2,
value: 'Some data',
structure: 'linear',
children: [
{ id: 4, value: 'Some data', structure: undefined }
]
}
]
},
{
id: 5,
structure: undefined,
value: 'Some data',
},
];
const getReducedArr = (data) => {
return data.map((obj) => {
if (obj.structure) {
const children = obj.children ? getReducedArr(obj.children) : [];
return {
...obj,
...(children.length > 0 ? { children } : undefined)
}
}
}).filter(data => data !== undefined)
}
const res = getReducedArr(a);
console.log(res)
Given an array in this format:
[
[{
name: "name",
value: "My-name"
},
{
name: "qty",
value: "1"
},
{
name: "url",
value: "test.com"
},
{
name: "comment",
value: "my-comment"
}
],
[{
name: "name",
value: "My-name2"
},
{
name: "qty",
value: "3"
},
{
name: "url",
value: "test2.com"
}
],
[{
name: "name",
value: "My-name3"
},
{
name: "qty",
value: "1"
},
{
name: "url",
value: "test3.com"
},
{
name: "comment",
value: "my-comment3"
}
]
]
I'm looking to switch that to:
[
[
{ name: "My-name" },
{ qty: "1" },
{ url: "test.com" },
{ comment: "my-comment", }
],[
{ name: "My-name2" },
{ qty: "3" },
{ url: "test2.com",
],[
{ name: "My-name3", },
{ qty: "1", },
{ url: "test3.com", },
{ comment: "my-comment3", }
]
]
In other words, swapping out the array keys but maintaining the object structure within each array element.
I've tried looping over each element and can swap the keys out using something like:
newArray[iCount][item.name] = item.value;
However I'm then struggling to preserve the object order. Note that the comment field may or may not appear in the object.
With Array.map() function:
var arr = [
[{name:"name",value:"My-name"},{name:"qty",value:"1"},{name:"url",value:"test.com"},{name:"comment",value:"my-comment"}],
[{name:"name",value:"My-name2"},{name:"qty",value:"3"},{name:"url",value:"test2.com"}],
[{name:"name",value:"My-name3"},{name:"qty",value:"1"},{name:"url",value:"test3.com"},{name:"comment",value:"my-comment3"}]
],
result = arr.map(function(a){
return a.map(function(obj){
var o = {};
o[obj.name] = obj.value
return o;
});
});
console.log(result);
Check my moreBetterOutput value. I think will be better.
If you still need a result like your example in the question then you can check output value.
const input = [
[
{
name:"name",
value:"My-name"
},
{
name:"qty",
value:"1"
},
{
name:"url",
value:"test.com"
},
{
name:"comment",
value:"my-comment"
}
],
[
{
name:"name",
value:"My-name2"
},
{
name:"qty",
value:"3"
},
{
name:"url",
value:"test2.com"
}
],
[
{
name:"name",
value:"My-name3"
},
{
name:"qty",
value:"1"
},
{
name:"url",
value:"test3.com"
},
{
name:"comment",
value:"my-comment3"
}
]
]
const output = input.map(arr => arr.map(obj => ({[obj.name]: obj.value})))
const moreBetterOutput = output.map(arr => arr.reduce((acc, item, index) => {
acc[Object.keys(item)[0]] = item[Object.keys(item)[0]];
return acc;
}, {}) )
//console.log(output);
console.log(moreBetterOutput);
Another map function:
const result = array.map( subarray =>
Object.assign(...subarray.map( ({name, value}) => ({ [name] : value }) ))
);