See edit below
I wanted to try and create a tree from a list of paths and found this code on stackoverflow from another question and it seems to work fine but i would like to remove the empty children arrays instead of having them showing with zero items.
I tried counting r[name].result length and only pushing it if it greater than zero but i just end up with no children on any of the nodes.
let paths = ["About.vue","Categories/Index.vue","Categories/Demo.vue","Categories/Flavors.vue","Categories/Types/Index.vue","Categories/Types/Other.vue"];
let result = [];
let level = {result};
paths.forEach(path => {
path.split('/').reduce((r, name, i, a) => {
if(!r[name]) {
r[name] = {result: []};
r.result.push({name, children: r[name].result})
}
return r[name];
}, level)
})
console.log(result)
EDIT
I didnt want to ask directly for the purpose i am using it for but if it helps i am trying to create an array like this: (this is a copy paste of the config needed from ng-zorro cascader)
const options = [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
isLeaf: true
}
]
},
{
value: 'ningbo',
label: 'Ningbo',
isLeaf: true
}
]
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
isLeaf: true
}
]
}
]
}
];
from an array of flat fields like this:
let paths = ["About.vue","Categories/Index.vue","Categories/Demo.vue","Categories/Flavors.vue","Categories/Types/Index.vue","Categories/Types/Other.vue"];
I suggest to use a different approach.
This approach takes an object and not an array for reaching deeper levels and assigns an array only if the nested level is required.
let paths = ["About.vue", "Categories/Index.vue", "Categories/Demo.vue", "Categories/Flavors.vue", "Categories/Types/Index.vue", "Categories/Types/Other.vue"],
result = paths
.reduce((parent, path) => {
path.split('/').reduce((r, name, i, { length }) => {
let temp = (r.children ??= []).find(q => q.name === name);
if (!temp) r.children.push(temp = { name, ...(i + 1 === length && { isLeaf: true }) });
return temp;
}, parent);
return parent;
}, { children: [] })
.children;
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
me and my partner have been cracking our heads at this. we have to create a tree, that obviously has "children" below it.
all we need right now, is to loop over an object to find a certain value, if that value is not in that certain object, then go into its child property and look there.
basically what I'm asking is, how can you loop over nested objects until a certain value is found?
would super appreciate the perspective of a more experienced coder on this.
/// this is how one parent with a child tree looks like right now,
essentially if we presume that the child has another child in the children property,
how would we loop into that?
and maybe if that child also has a child, so on and so on...
Tree {
value: 'Parent',
children: [ Tree { value: 'Child', children: [] } ]
}
You can use recursive function to loop through objects:
const Tree = {
value: 'Parent',
children: [
{
value: 'Child1',
children: [
]
},
{
value: 'Child2',
children: [
{
value: 'Child2.1',
children: [
{
value: 'Child2.1.1',
children: [
]
},
{
value: 'Child2.1.2',
children: [
]
},
]
},
]
},
]
}
function findValue(obj, value)
{
if (obj.value == value)
return obj;
let ret = null;
for(let i = 0; i < obj.children.length; i++)
{
ret = findValue(obj.children[i], value);
if (ret)
break;
}
return ret;
}
console.log("Child1", findValue(Tree, "Child1"));
console.log("Child2.1", findValue(Tree, "Child2.1"));
console.log("Child3", findValue(Tree, "Child3"));
You can try this simple solution:
const myTree = {
value: 'Parent',
children: [
{
value: 'Child1',
children: []
},
{
value: 'Child2'
}
]
}
const isValueInTree = (tree, findValue) => {
if (tree.value === findValue) return true;
if (tree.children && tree.children.length !== 0) {
for (const branch of tree.children) {
const valuePresents = isValueInTree(branch, findValue);
if (valuePresents) return true;
}
}
return false;
}
console.log(isValueInTree(myTree, 'Child2'));
console.log(isValueInTree(myTree, 'Child'));
console.log(isValueInTree(myTree, 'Child1'));
I am trying to remove an object from an array but it I dont know what I am doing wrong.
I have this array declared:
listA: [
{ title: 'Food', value: 'Patato' },
{ title: 'Drink', value: 'Cola' },
{ title: 'Desert', value: 'Cheesecake' },
],
I am trying to remove the object where its value is 'Cola', what I have tried is this:
this.listA.filter(x => x.value !== 'Cola');
And it returns me the same list
I want to return this:
listA: [
{ title: 'Food', value: 'Patato' },
{ title: 'Desert', value: 'Cheesecake' },
],
Your code should be filtering just fine, I think the issue here is that filter does not modify the original array, it returns a new array with the filtered results. If you want it to overwrite the original array, you'll need to say this.listA = this.listA.filter(...)
const listA = [
{ title: "Food", value: "Patato" },
{ title: "Drink", value: "Cola" },
{ title: "Desert", value: "Cheesecake" },
];
const result = listA.filter((obj) => obj.value !== 'Cola');
Looks like you need to do something like
this.listA = this.listA.filter(x => x.value !== 'Cola')
The filter method is immutable hence the original array isn't changed
As a complement to https://stackoverflow.com/a/70688107/6316468, here is what filter does under the hood (the original array this remains untouched):
var xs = [1, 2, 3, 4];
var ys = filter.call(
xs, x => x % 2
);
console.log(
"xs = [", xs.join(), "]"
);
console.log(
"ys = [", ys.join(), "]"
);
function filter(predicate) {
var xs = []; // new array
for (let x in this) {
if (predicate(x)) {
xs.push(x);
}
}
return xs;
}
I have two arrays of objects. Each object within that array has an array of objects.
I'm trying to merge the two arrays with one being used as a master, overwriting any duplicates in both the first level and the second 'option' level. Almost like a union join.
I've tried the code, however this doesn't cater for duplicate in options within a material.
Running this code results in two id: 400 options for the second material. When there should only be 1 with the value of 100cm.
Is there any smart way of doing this please? I also had a look at using sets, but again this only worked on the top level.
const materials_list = [
{
id: 2,
options: [
{
id: 300,
value: '50cm'
},
{
id: 400,
value: '75cm'
}
]
}
]
const master_materials_list = [
{
id: 1,
options: [
{
id: 200,
value: '50cm'
}
]
},
{
id: 2,
options: [
{
id: 400,
value: '100cm'
}
]
}
]
master_materials_list.forEach(masterMaterial => {
const matchMaterial = materials_list.find(existingMaterial => existingMaterial.id === masterMaterial.id);
if(matchMaterial) {
masterMaterial.options = masterMaterial?.options.concat(matchMaterial.options);
}
});
console.log(master_materials_list);
This is the desired output
[
{
id: 1,
options: [
{
id: 200,
value: '50cm'
}
]
},
{
id: 2,
options: [
{
id: 300,
name: '50cm'
},
{
id: 400,
name: '100cm'
}
]
}
]
Different approach that first makes a Map of the material_list options for o(1) lookup
Then when mapping the master list use filter() to find options stored in the above Map that don't already exist in the master
const materials_list=[{id:2,options:[{id:300,value:"50cm"},{id:400,value:"75cm"}]}, {id:999, options:[]}],
master_materials_list=[{id:1,options:[{id:200,value:"50cm"}]},{id:2,options:[{id:400,value:"100cm"}]}];
// store material list options array in a Map keyed by option id
const listMap = new Map(materials_list.map(o=>[o.id, o]));
// used to track ids found in master list
const masterIDs = new Set()
// map material list and return new objects to prevent mutation of original
const res = master_materials_list.map(({id, options, ...rest})=>{
// track this id
masterIDs.add(id)
// no need to search if the material list Map doesn't have this id
if(listMap.has(id)){
// Set of ids in this options array in master
const opIds = new Set(options.map(({id}) => id));
// filter others in the Map for any that don't already exist
const newOpts = listMap.get(id).options.filter(({id})=> !opIds.has(id));
// and merge them
options = [...options, ...newOpts]
}
// return the new object
return {id, options, ...rest};
});
// add material list items not found in master to results
listMap.forEach((v,k) =>{
if(!masterIDs.has(k)){
res.push({...v})
}
})
console.log(res)
.as-console-wrapper {max-height: 100%!important;top:0}
You can do this with lodash:
const materials_list = [
{
id: 2,
options: [
{
id: 300,
value: '50cm',
},
{
id: 400,
value: '75cm',
},
],
},
];
const master_materials_list = [
{
id: 1,
options: [
{
id: 200,
value: '50cm',
},
],
},
{
id: 2,
options: [
{
id: 400,
value: '100cm',
},
],
},
];
const customizer = (objValue, srcValue, propertyName) => {
if (propertyName === 'options') {
return _(srcValue)
.keyBy('id')
.mergeWith(_.keyBy(objValue, 'id'))
.values()
.value();
}
};
const merged = _(master_materials_list)
.keyBy('id')
.mergeWith(_.keyBy(materials_list, 'id'), customizer)
.values()
.value();
console.log(merged);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.21/lodash.min.js"></script>
You’re going to have to filter matchMaterials.options before the concat. Something like:
matchMaterial.options = matchMaterial.options.filter(opt =>
masterMaterial.options.find(val => val.Id === opt.Id) == null;
);
This should remove any “duplicate” options from matchMaterial before the concat.
EDIT:
I did this on my phone so I’m sorry if the code is formatted weird like I’m seeing now
expect result: ["human", "head", "eye"]
ex.
const data = {
name: "human",
children: [
{
name: "head",
children: [
{
name: "eye"
}
]
},
{
name: "body",
children: [
{
name: "arm"
}
]
}
]
}
const keyword = "eye"
Using the above data and using ffunction to obtain result
expect_result = f(data)
What kind of function should I write?
Thanks.
You could use an iterative and recursive approach by checking the property or the nested children for the wanted value. If found unshift the name to the result set.
function getNames(object, value) {
var names = [];
[object].some(function iter(o) {
if (o.name === value || (o.children || []).some(iter)) {
names.unshift(o.name);
return true;
}
});
return names;
}
var data = { name: "human", children: [{ name: "head", children: [{ name: "eye" }] }, { name: "body", children: [{ name: "arm" }] }] };
console.log(getNames(data, 'eye'));
console.log(getNames(data, 'arm'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try a recursive function
const getData = items => {
let fields = [];
for (const item of items) {
if (item.name) {
fields.push(item.name);
}
if (Array.isArray(item.children)) {
fields.push(...getData(item.children));
}
}
return fields;
}
var result = [] // This is outside the function since it get updated
function f(data) {
result.push(data.name); // Add the only name to the Array
if (data.children) { // just checking to see if object contains key children (staying safe)
for (var i = 0; i < data.children.length; i++) { // we are only looping through the children
f(data.children[i]); // Call this particular function to do the same thing again
}
}
}
Basically this function set the name. Then loop through the no of children and then calls a function to set the name of that child and then loop through its children too. Which happen to be a repercussive function till it finishes in order, all of them
See jsfiddle here: https://jsfiddle.net/remenyLx/2/
I have data that contains objects that each have an array of images. I want only the first image of each object.
var data1 = [
{
id: 1,
images: [
{ name: '1a' },
{ name: '1b' }
]
},
{
id: 2,
images: [
{ name: '2a' },
{ name: '2b' }
]
},
{
id: 3
},
{
id: 4,
images: []
}
];
var filtered = [];
var b = data1.forEach((element, index, array) => {
if(element.images && element.images.length)
filtered.push(element.images[0].name);
});
console.log(filtered);
The output needs to be flat:
['1a', '2a']
How can I make this prettier?
I'm not too familiar with JS map, reduce and filter and I think those would make my code more sensible; the forEach feels unnecessary.
First you can filter out elements without proper images property and then map it to new array:
const filtered = data1
.filter(e => e.images && e.images.length)
.map(e => e.images[0].name)
To do this in one loop you can use reduce function:
const filtered = data1.reduce((r, e) => {
if (e.images && e.images.length) {
r.push(e.images[0].name)
}
return r
}, [])
You can use reduce() to return this result.
var data1 = [{
id: 1,
images: [{
name: '1a'
}, {
name: '1b'
}]
}, {
id: 2,
images: [{
name: '2a'
}, {
name: '2b'
}]
}, {
id: 3
}, {
id: 4,
images: []
}];
var result = data1.reduce(function(r, e) {
if (e.hasOwnProperty('images') && e.images.length) r.push(e.images[0].name);
return r;
}, [])
console.log(result);
All answers are creating NEW arrays before projecting the final result : (filter and map creates a new array each) so basically it's creating twice.
Another approach is only to yield expected values :
Using iterator functions
function* foo(g)
{
for (let i = 0; i < g.length; i++)
{
if (g[i]['images'] && g[i]["images"].length)
yield g[i]['images'][0]["name"];
}
}
var iterator = foo(data1) ;
var result = iterator.next();
while (!result.done)
{
console.log(result.value)
result = iterator.next();
}
This will not create any additional array and only return the expected values !
However if you must return an array , rather than to do something with the actual values , then use other solutions suggested here.
https://jsfiddle.net/remenyLx/7/