Iterating over a multi-level object? - javascript

I have a multi-level object that has dynamic names and goes about 2 levels deep. I came up with a solution that works if there is only one dynamic-object, however I am having trouble writing the loop for multiples. Here are the basics.
My object looks like this:
{
dynamic_name1: {
mods: [
[Object], [Object]
]
},
dynamic_name2: {
mods: [
[Object], [Object]
]
},
dynamic_name3: {
mods: [
[Object], [Object]
]
}
}
Basically, I want to iterate over this object, and get the objects from within each of the respective mods arrays, then push them to a different array.
I am getting this object, with the variable container, then structure like so:
const [objectKeys] = Object.keys(container);
const modsList = container[objectKeys].mods;
This solution works really well because then I just add it to my new array like this:
const myMods = [...modsList]
However, this whole functionality seems to break when I try to loop it. here's what I have so far:
for (j = 0; j < container.length; j++){
const [objectKeys] = Object.keys(container);
}
but when trying to log [objectKeys] I get undefined. Any idea how I could do this?

I would get all the mods reducing a map over your dynamic sub-objects, something like:
const container = {
dynamic_name1: {
mods: [
1, 2
]
},
dynamic_name2: {
mods: [
3, 4
]
},
dynamic_name3: {
mods: [
5, 6
]
}
}
const mods = Object.values(container)
.map(sub => sub.mods)
.reduce(
(result, mods) => [...result, ...mods],
[]
)
console.log(mods)
// [1, 2, 3, 4, 5, 6]
This way you can loop at the end.

If I understood you correctly, below function should accept an object and return an array with all 2nd level mods
function extractMods(container) {
return Object.keys(container).reduce((r, c) => [...r, ...container[c].mods], [])
}
Call it like below
extractMods({
a: {
mods: [0,1,2,3,4]
},
b: {
mods: [5,6,7,8,9]
},
c: {
mods: [10,11,12,13,14]
},
})
It will return
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

Good example of why I dislike dynamic property names. An array consisting of: [{"name":"dynamic_name1","mods":[{},{}]},{"name":"dynamic_name2","mods":[{},{}]}] gives you easy loops.
But to help with the issue, Object.keys() has some siblings: Object.entries() and Object.values() .
const data = {
dynamic_name1: {
mods: [
{ id: 1 },
{ id: 2}
]
},
dynamic_name2: {
mods: [
{ id: 3 },
{ id: 4 }
]
},
dynamic_name3: {
mods: [
{ id: 5 },
{ id: 6 }
]
}
};
const modsList = Object.values( data ).flatMap( entry => entry.mods );
console.log( modsList );
If your browser does not support flatMap() yet, you can do the same with a map() or reduce().

let obj = {
dynamic_name1: { mods: [{x:1}, {x:2}] },
dynamic_name2: { mods: [{x:3}, {x:4}] },
dynamic_name3: { mods: [{x:5}, {x:6}] }
}
let result = Object.values(obj).reduce((a, b) => (a.push(...b.mods), a), []);
console.log( result )

The issue with
for (j = 0; j < container.length; j++){
const [objectKeys] = Object.keys(container);
}
is that container.length is undefined, therefore, there's zero iterations and objectKeys is never assigned.
Use Object.values and map the resulting array to return the mods, then flat it withdepth of 2 :
const container = {
dynamic_name1: {
mods: [
[Object],
[Object]
]
},
dynamic_name2: {
mods: [
[Object],
[Object]
]
},
dynamic_name3: {
mods: [
[Object],
[Object]
]
}
}
const myMods = Object.values(container).map(e => e.mods).flat(2);
console.log(myMods);

Related

Group the parent object array based on the filter of the child object array

I've coded and I've got the results I want, but I think this code looks unclean.
const verses = [{
index: 1,
verses: [{
level: 1
},
{
level: 1
}
]
},
{
index: 2,
verses: [{
level: 1
},
{
level: 1
}
]
},
{
index: 3,
verses: [{
level: 2
}]
},
{
index: 4,
verses: [{
level: 2
}]
},
{
index: 5,
verses: [{
level: 2
},
{
level: 2
}
]
},
{
index: 6,
verses: [{
level: 3
},
{
level: 3
}
]
},
{
index: 7,
verses: [{
level: 3
},
{
level: 3
},
{
level: 4
},
{
level: 4
}
]
}
]
function getVerseIndexByLevel(level) {
const result = verses.map(v => {
const mappingByLevel = Math.max.apply(Math, [...new Set(v.verses.map(w => w.level))])
const mappingByVerse = v.index
return {
level: mappingByLevel,
verse: mappingByVerse
}
}).filter(v => v.level === level).map(v => v.verse)
return result
}
for (let i = 1; i <= 4; i++) {
console.log({
level: i,
verse: getVerseIndexByLevel(i)
})
}
Is my code above consuming too much performance?
Could you make it cleaner and simpler without changing the result at all?
-I don't know what details I have to add more, StackOverflow forced me to write this because the post is mostly code. but I hope you understand just by looking at the results of the code.-
On the performance end, the one thing I can see that could improve it would be to call getVerseIndexByLevel only once, and use the calculated result mapping, instead of calling it multiple times (requiring re-parsing the input every time). Something like
const verseIndicies = getVerseIndicies();
for (let i = 1; i <= 4; i++) {
console.log({
level: i,
verse: verseIndicies[i]
})
}
Another slight improvement - since you're using ES6 syntax, you don't need to .apply to Math.max - just spread the set into it.
You also don't need to turn the level numbers into a Set first - since it's being passed into Math.max anyway, repetitions aren't an issue.
const verses=[{index:1,verses:[{level:1},{level:1}]},{index:2,verses:[{level:1},{level:1}]},{index:3,verses:[{level:2}]},{index:4,verses:[{level:2}]},{index:5,verses:[{level:2},{level:2}]},{index:6,verses:[{level:3},{level:3}]},{index:7,verses:[{level:3},{level:3},{level:4},{level:4}]}];
function getVerseIndicies() {
const verseIndicies = {};
for (const v of verses) {
const level = Math.max(...v.verses.map(w => w.level));
verseIndicies[level] ??= [];
verseIndicies[level].push(v.index);
}
return verseIndicies;
}
const verseIndicies = getVerseIndicies();
for (let i = 1; i <= 4; i++) {
console.log({
level: i,
verse: verseIndicies[i]
})
}

Is there a smart way to modify an array within an object by using JavaScript's destructuring approach?

The following example demonstrates what I would like to achieve:
const data_template = {
key: 1,
data_object: {
data1: 1,
data2: 2,
data3: 3
},
data_array: [
{ key: 1, data1: 1 },
{ key: 2, data1: 2 },
{ key: 3, data1: 3 }
]
}
let data = [];
data.push({ ...data_template, key: 1 });
data.push({ ...data_template, key: 2 });
console.log(data);
By using destructuring I can easily modify my "template object" (data_template) while adding instances of it to my "data array". The result of the code above is:
[
{
key: 1,
data_object: { data1: 1, data2: 2, data3: 3 },
data_array: [ [Object], [Object], [Object] ]
},
{
key: 2,
data_object: { data1: 1, data2: 2, data3: 3 },
data_array: [ [Object], [Object], [Object] ]
}
]
As you can see, there are 2 objects in my array (data), each with a unique key (1 and 2, respectively).
Now my question: How can I modify the keys within data_array, which is an array within my "data template"? Is there a smart way to do this by destructuring as well?
Depends on what you're trying to do.
If you simply want to clone the array with extra elements:
data.push({
...data_template,
key: 1,
data_array: [
...data_template.data_array,
{ key: 4, data1: 4},
],
});
If you want to e.g. add a new field to all the objects inside data_array:
const data_template = {
key: 1,
data_object: { data1: 1, data2: 2, data3: 3 },
data_array: [ { key: 1, data1: 1 }, { key: 2, data1: 2 }, { key: 3, data1: 3 }]
}
console.log({
...data_template,
key: 1,
data_array: data_template.data_array.map(d => ({ ...d, newField: 123 })),
});
which you could also use to overwrite the key field
Otherwise I can't immediately think of any other destructuring "tricks" you could use.
The short answer
No, you cant.
JavaScript destructuring only allows you to override root level keys of an object, which replaces their value.
(You also usually wouldn't do this, to keep cognitive complexity low.)
The long answer
There are actually better ways to do this.
Usually when creating a new object by spreading becomes more complex, you'd simply create a function for each level of nesting, and give it a better name than I did, that correlates to your domain.
This is exactly what functions are made for; to split pieces of code to reduce complexity.
Simple example
function createDataEntry(key, data) {
return { key, data }
}
function createRecord(key, data) {
return {
key,
dataObject: {
data,
},
dataArray: [
createDataEntry(key, data),
// or simply:
// { key, data },
]
}
}
let data = [];
data.push(createRecord(1, 'foo'));
data.push(createRecord(2, 'bar'));
console.log(JSON.stringify(data, null, 2));
produces
[
{
"key": 1,
"dataObject": {
"data": "foo"
},
"dataArray": [
{
"key": 1,
"data": "foo"
}
]
},
{
"key": 2,
"dataObject": {
"data": "bar"
},
"dataArray": [
{
"key": 2,
"data": "bar"
}
]
}
]
Add + Edit example
If you want to add or edit an entry from that data array, you'll have to do a check to see if an entry already exists.
If it already exists, update it using map.
If it does not exist, use array.push().
function addOrUpdateEntry(myObject, key, data) {
let updated = false;
const updatedObject = {
...myObject,
items: myObject.items.map(member => {
if (member.key === key) {
updated = true;
return { ...member, data };
}
return member;
})
};
if (!updated) {
updatedObject.items.push({ key, data });
}
return updatedObject;
}
let data = {
items: []
};
data = addOrUpdateEntry(data, 1, "foo");
data = addOrUpdateEntry(data, 2, "bar");
data = addOrUpdateEntry(data, 2, "baz");
console.log(JSON.stringify(data, null, 2));
Which will output
{
"items": [
{
"key": 1,
"data": "foo"
},
{
"key": 2,
"data": "baz"
}
]
}
Note that mutating an existing object can easily make for undesired behaviour in JavaScript, hence the creation of a new object and returning that from the function.
This concept is called immutability.

Inside an Object.value loop how can I get the key

I have to create match condition based on an array my array will look like below
var groupData={
A:[
{rollnum: 1, name:'Arya', age:15},
{rollnum: 2, name:'Aryan', age:15}
],
B:[
{rollnum:11, name:'Biba', age:15},
{rollnum:12, name:'Bimisha', age:15}
]
}
I am looping using for loop. How can reduce the loops. Can any one suggest me a proper way for this
Object.values(groupData).flat().forEach((rowitem)=>{
query={};
Object.keys(rowitem).forEach(eachField=>{
query[eachField]["$in"].push(rowitem[eachField])
});
fullarray[Object.keys(groupData)]=matchQuery;
})
I need an output (fullarray) like below
{
'A':{
rollnum:{'$in':[1,2]},
name: {'$in':['Arya', 'Aryan']},
age: {'$in':[15]}
},
'B':{
rollnum:{'$in':[11,12]},
name: {'$in':['Biba', 'Bimisha']},
age: {'$in':[15]}
}
}
Here 'A' 'B' is not coming correctly
Don't use Object.values() since that discards the A and B keys.
Use nested loops, one loop for the properties in the object, and a nested loop for the arrays.
You need to create the nested objects and arrays before you can add to them.
var groupData = { A:
[ { rollnum: 1,
name: 'Arya',
age:15},
{ rollnum: 2,
name: 'Aryan',
age:15}, ],
B:
[ { rollnum: 11,
name: 'Biba',
age:15},
{ rollnum: 12,
name: 'Bimisha',
age:15} ] }
result = {};
Object.entries(groupData).forEach(([key, arr]) => {
if (!result[key]) {
result[key] = {};
}
cur = result[key];
arr.forEach(obj => {
Object.entries(obj).forEach(([key2, val]) => {
if (!cur[key2]) {
cur[key2] = {
"$in": []
};
}
cur[key2]["$in"].push(val);
});
});
});
console.log(result);

How to make my JS solution functional?

I've got some data that came from classy service in Angular that looks like this (briefly):
const obj = {
field: [
{
id: 1,
items: []
},
{
id: 2,
items: [ { wateva: 'wateva1' } ]
},
{
id: 3,
items: false
},
{
id: 4,
items: [ { yeah: 7 } ]
}
]
}
Well, my task is just to collect all array items, that are not empty.
My solution (actually my solution is written in TypeScript and Angular 5, but here to make it more simple and comprehensible it's going to be like...) :
function getItems() {
const items = [];
obj.field.forEach(currentField => {
if (currentField.items && currentField.items.length) {
currentField.items.forEach(currentItem => items.push(currentItem));
}
});
return items;
}
Right, it's dead simple and it works as expected (current one will return...) :
[ { wateva: 'wateva1' }, { yeah: 7 } ]
And now my question... How to make my solution functional? I want to get rid of my new variable items, I don't want to push in that variable, I just want to return the result in one action. Any help will be appreciated.
P.S. Suggestions with 3rd libraries are not accepted :)
If you can use es6 (and since you mentioned you're using typescript, that should be fine), you can turn this into a nice functional one-liner by combining concat, map, filter, and the spread operator:
const obj = {
field: [
{
id: 1,
items: []
},
{
id: 2,
items: [ { wateva: 'wateva1' } ]
},
{
id: 3,
items: false
},
{
id: 4,
items: [ { yeah: 7 } ]
}
]
}
function getItems(obj) {
return [].concat(...obj.field.map(o => o.items).filter(Array.isArray))
}
console.log(getItems(obj))
You can use flatMap (stage 3). flatMap here matches Fantasy Land's spec for chain.
data.field.flatMap
(({ items }) =>
Array.isArray (items) ? items : []
)
// [ { wateva: 'wateva1' }, { yeah: 7 } ]
You can polyfill it in environments that don't have it
Array.prototype.flatMap = function (f) {
return this.reduce
( (acc, x) =>
acc.concat (f (x))
, []
)
}
Full program demonstration
Array.prototype.flatMap = function (f) {
return this.reduce
( (acc, x) =>
acc.concat (f (x))
, []
)
}
const data =
{ field:
[ { id: 1, items: [] }
, { id: 2, items: [ { wateva: 'wateva1' } ] }
, { id: 3, items: false }
, { id: 4, items: [ { yeah: 7 } ] }
]
}
const result =
data.field.flatMap
(({ items }) =>
Array.isArray (items) ? items : []
)
console.log (result)
// [ { wateva: 'wateva1' }, { yeah: 7 } ]
You can use Array.reduce and the spread operator to accumulate onto an empty array:
obj.field.reduce(
(acc, current) => current.items && current.items.length > 0 ? [...acc, ...current.items] : acc, [])
);
Using Array.prototype.reduce, object destructuring, and spread assignments:
function getItems({ field }) {
return field.reduce((result, { items }) =>
items instanceof Array ?
items.reduce((items, item) => [...items, item], result) :
result
, []);
}

Cartesian product without duplicates

I am using a cartesian product function that given [1], [1,2,3], [1,2,3] returns 9 combinations:
[ [ 1, 1, 1 ],
[ 1, 2, 1 ],
[ 1, 3, 1 ],
[ 1, 1, 2 ],
[ 1, 2, 2 ],
[ 1, 3, 2 ],
[ 1, 1, 3 ],
[ 1, 2, 3 ],
[ 1, 3, 3 ] ]
But I need to remove those with the same items regardless of the order, so [ 1, 3, 1 ] and [ 1, 1, 3 ] are the same to me. The result should contain 6 items:
[ [ 1, 1, 1 ],
[ 1, 2, 1 ],
[ 1, 3, 1 ],
[ 1, 2, 2 ],
[ 1, 3, 2 ],
[ 1, 3, 3 ] ]
I can write a function that compares all possible pairs with _.xor, but for larger numbers it will probably be very inefficient. Is there a good way in Javascript to do this? An efficient way to compare all possible pairs or an algorithm for cartesian product without duplicates?
sort each array of the cartesian product
[ 1, 2, 1 ] -> [1 , 1 , 2]
[ 1, 1, 2 ] -> [1 , 1 , 2]
then gather these sorted arrays into a set, that will remove the duplicates.
Of course, you can do that while constructing the cartesian product rather than afterward.
JavaScript has Set and Map, however they compare objects and arrays by reference rather than by value, so you cannot take advantage of it directly. The idea is to use a key function which sorts and json encodes the items before putting it in a set.
pure ES5:
function product(sets) {
if (sets.length > 0) {
var head = sets[0];
var tail = product(sets.slice(1));
var result = [];
head.forEach(function(x) {
tail.forEach(function(xs) {
var item = xs.slice(0);
item.unshift(x);
result.push(item);
});
});
return result;
} else {
return [[]];
}
}
function myKeyFn(item) {
return JSON.stringify(item.slice(0).sort());
}
function uniqBy(items, keyFn) {
var hasOwn = Object.prototype.hasOwnProperty, keyset = {};
return items.filter(function(item) {
var key = keyFn(item);
if (hasOwn.call(keyset, key)) {
return false;
} else {
keyset[key] = 1;
return true;
}
});
}
function uniqProduct(sets) {
return uniqBy(product(sets), myKeyFn);
}
function log(x) {
console.log(x);
var pre = document.createElement('pre');
pre.appendChild(document.createTextNode(x));
document.body.appendChild(pre);
}
log(uniqProduct([[1],[1,2,3],[1,2,3]]).map(JSON.stringify).join("\n"));
<pre></pre>
lodash + modern JavaScript:
// Note: This doesn't compile on current babel.io/repl due to a bug
function product(sets) {
if (sets.length > 0) {
const [x, ...xs] = sets;
const products = product(xs);
return _.flatMap(x, head => products.map(tail => [head, ...tail]));
} else {
return [[]];
}
}
function uniqProduct(sets) {
return _.uniqBy(product(sets), x => JSON.stringify(x.slice(0).sort()));
}
console.log(uniqProduct([[1],[1,2,3],[1,2,3]]).map(JSON.stringify).join("\n"));
JavaScript has set data structure.
So store your results in a set where each element of the set is a collection of pairs of numbers from the original sets along with the number of times that number occurs.
So your result would look something like this:
[
{1:3},
{1:2, 2: 1},
{ 1:2, 3:1},
{ 1:1, 2:2},
{ 1:1, 2:1, 3:1},
{ 1:1, 3:2 } ]
This way, you won't be able to add the object a second time to the set.

Categories

Resources