Hi FCC,
I’ve been struggling with this problem for a day or two and I have been able to get close but not the full solution that I am looking for. Say I have two arrays like so,
const arr1 = ['a','b'];
const arr2 = ['a.foo', 'b.bar'];
The arrays will always be the same length. I want to iterate over the arrys and build a nested object that has this shape:
{
aggs : {
a: {
terms: {
field: 'a.foo'
},
aggs: {
b : {
terms: {
field: 'b.bar'
}
}
}
}
So I think what is making it difficult for me is that I need to nest each new item in the array inside the previously created object. I’ve tried a recursive solution and an iterative one and I just can’t get it. Can anyone help me out here?
THanks
@camperextraordinaire
EDIT: just realized this solution doesnt work, but its my closest attempt so far
const arr1 = ['a','b', 'c'];
const arr2 = ['a.foo', 'b.bar', 'c.baz'];
let result = {};
const insertAggs = (agg, result) => {
while (result.aggs) {
result = result.aggs
}
result.aggs = agg;
}
for (let i = 0; i < arr1.length; i++) {
const aggName = arr1[i];
const field = arr2[i];
const agg = {
[aggName]: {
terms: {
field
}
}
};
result.aggs ? insertAggs(agg, result) : result.aggs = agg;
}
}
I’ve had to do something similar before, so I think I can help. But I’m not following your data structures.
You have
const arr1 = ['a','b'];
const arr2 = ['a.foo', 'b.bar'];
And your end goal is this?
aggs : {
a: {
terms: {
field: 'a.foo'
},
aggs: {
b : {
terms: {
field: 'b.bar'
}
}
}
So arr1
are the keys, and arr2
are the values? In which case you’re trying to assign
a.terms.field = 'a.foo'
Or are you trying to build the whole object like
a['terms']['field']['foo']
In order for me to help, I’ll need to know what values you explicitly know, and which will be dynamically assigned.
EDIT: The reason I ask is because it seems redundant to have two arrays, since you can determine the root node from arr2
Hi @JM-Mendez
I have given a bad example here with arr2
. The values in arr2
are not related to arr1
at all. Below would be a more correct and less confusing explanation of my goal here
// arr2 values are unrelated to arr1
const arr1 = ['a','b'];
const arr2 = ['c', 'd'];
// end goal
{
aggs: {
a: {
terms: {
field : 'c',
},
aggs: {
b: {
terms: {
field: 'd'
}
}
}
}
Ok, so we know for a fact that the properties terms
and field
will exist for each root node in arr1
. I know exactly how to do this. Give me a few minutes to write out and test solutions using reduce
and for loops
.
1 Like
Yes the shape is standard and each node will have a terms.field
chunk.
A generic node looks like this
aggs: {
[valFromArr1] : {
terms: {
field : [valueFromArr2]
}
}
The issue I’m having is nesting each node underneath the previously generated node.
Thanks so much for your help
Ok, you were close. The example you gave where you almost had it, you kept reassigning the whole object
const agg = {
[aggName]: {
terms: {
field
}
}
};
Which was throwing off your result. Instead, assign all 2nd level nodes on the root object. I also show below how to add a root level prop named aggs
if you need it.
The thing to keep in mind is that in javascript you can only dynamically set properties for the next level in.
(I made it a spoiler in case this was for an exercise)
// arr.reduce
const withoutAggs = arr1.reduce((agg, nextKey, currIdx) => {
agg[nextKey] = {
terms: {
fields: arr2[currIdx]
}
}
return agg
}, {})
// for loop
const withoutAggs = {}
for(let i = 0; i < arr1.length; i++){
withoutAggs[arr1[i]] = {
terms: {
fields: arr2[i]
}
}
}
// this gives you
{
a: {
terms: {
fields: 'c'
}
// ...
}
}
// if you need aggs to be top level, just assign it after
const withAggsProp = {
aggs: withoutAggs
}
// which gives you
{
aggs: {
a: // ...
}
}
If this doesn’t work for any reason or you have follow up questions, just hit me back.
I made my own version; using ES6’s object spread notation and some recursive shit.
const arr1 = ['a','b'];
const arr2 = ['a.foo', 'b.bar'];
function nested(a, b) {
return (a.length || b.length)
? {
aggs: {
[a[0]]: {
terms: {
field: b[0]
},
...nested(
a.slice(1),
b.slice(1)
)
}
}
}
: {}
}
console.log(JSON.stringify(nested(arr1, arr2)))
Now, if it wasn’t recursive then I may be wrong lol and I misread.
Hi @JM-Mendez
Your code looks like it results in a flat data structure?
I.E. both will produce:
{a: { terms: { fields: 'blah'}},
b: { terms: { fields: 'adsad'}}
}
where as I need B nested under A at the “terms” level.
i.e.
{
a : {
terms: {
fields: 'some val'
},
aggs: {
b: {
terms: {
fields: 'something else'
}
}
}
}
}
Hope this makes sense and again thank you for the help
@luishendrix92
Nice this looks like a valid solution. Going to have to take some time for me to understand whats going on. Thanks a lot for your help! Really clever solution
Your code looks like it results in a flat data structure?
Yes it does. It looks like I misunderstood the requirements. So you needs each nested level to have a root of aggs
? So this?
aggs.a
aggs.a.aggs.b
aggs.a.aggs.b.aggs.c
No problem - I probably didnt do a good job explaining.
This is the input/output:
const arr1 = ['a','b', 'c'];
const arr2 = ['d', 'e', 'f'];
var ans = yourFn(arr1, arr2);
console.log(ans.aggs.a.aggs.b.aggs.c.terms.field) //logs "f"
In this case it seems @luishendrix92 has the correct algorithm.
Yeah haha all I wanted to illustrate that the problem is recursive in nature.
I had a hard time building the code for it and I wanted less confusion by using the spread operator but if you want to use the correct tools for all browsers and engines:
for loop with bracket assignment but still recursive.
so instead {…} you just create an empty object and then assign stuff to it, and use another if statement to check if there’s a next property, case in which you’d assign a recursive call object to the terms
sub-object. So you kinda need a nested if statement: the outer one to check if any of the lists have any elements left and the inner one to check if there’s a next one in BOTH arrays.
1 Like
So I was giving this some thought and realized that if the nested object is deep enough, or your nested objects were sufficiently heavy, then it could blow the stack using recursion.
So I thought to present you an iterative solution that works for any depth and object size without blowing the stack. This solution still runs in O(n) time like the recursive one, but improves space complexity from O(s + n) to O(n) – assuming n
to be the size of the new object, and s
to be the size of the recursive stack.
const arr1 = ['a', 'b', 'c']
const arr2 = [1, 2, 3]
const obj = {}
let prevNode
for(let i = 0; i < arr1.length; i++){
const key = arr1[i]
const nextNode = {
[key]: {
terms: {
fields: arr2[i]
}
}
}
if(!prevNode) {
obj.aggs = nextNode
prevNode = obj.aggs[key]
} else {
prevNode.aggs = nextNode
prevNode = prevNode.aggs[key]
}
}
2 Likes
Hey JM,
Really nice and simple iterative solution. I was trying to do something like this originally but just could not get it to work for whatever reason,
Thanks!
1 Like