reducing nested object key value inside an an array - javascript

i have the following arrays
array1 = [
{a:{key:1 , value: 10} , b:{key:1 , value:12} , c:{key:1 , value: 5} , d:{key:1 , value:2}},
{a:{key:2 , value: 10} , b:{key:2 , value:12} , c:{key:2 , value: 5} , d:{key:2 , value:2}},
{a:{key:3 , value: 10} , b:{key:3 , value:12} , c:{key:3 , value: 5} , d:{key:3 , value:2}},
]
array2 = [
{a:{key:1 , value: 10} , b:{key:1 , value:12} , c:{key:1 , value: 5} , d:{key:1 , value:2}},
{a:{key:2 , value: 10} , b:{key:2 , value:12} , c:{key:2 , value: 5} , d:{key:2 , value:2}},
{a:{key:4 , value: 10} , b:{key:4 , value:12} , c:{key:4 , value: 5} , d:{key:4 , value:2}},
]
reduced array based on key should look like this:
combinedArray= [
{a:{key:1 , value: 20} , b:{key:1 , value:24} , c:{key:1 , value: 10} , d:{key:1 , value:4}},
{a:{key:2 , value: 20} , b:{key:2 , value:24} , c:{key:2 , value: 10} , d:{key:2 , value:4}},
{a:{key:3 , value: 10} , b:{key:3 , value:12} , c:{key:3 , value: 5} , d:{key:3 , value:2}},
{a:{key:4 , value: 10} , b:{key:4 , value:12} , c:{key:4 , value: 5} , d:{key:4 , value:2}},
]
first i tried to merge the two arrays using const mergedArray = [...array1, ...array2]
now i want to check for key duplicates. for example, if there is key1 in both array1 and array2, remove the duplicates then combine the values of that key.
this is what i have tried but it is only iterating through a.key only:
function kdeAdder(param) {
const array = [param.a]
let tempHistory = [];
for(let x=0;x<array.length;x++){
array[x].forEach((item)=>{
let noMatch = true;
if(tempHistory.length > 0) {
tempHistory.forEach((tempItem, i)=>{
if(item.key === tempItem.key) {
tempHistory[i].value += item.value;
noMatch = !noMatch;
}
});
}
return (noMatch) ? tempHistory.push(item) : null;
});
}
return tempHistory;
}
kdeAdder(mergedArray);

As you confirmed the key inner property is commonly shared by the four "a", "b", "c", "d" objects in an outer object, the a.key value can be used to identify which outer objects should merge.
You could group all objects (irrespective of whether they occur in array1 or array2) by that a.key, and then aggregate objects that occur in the same group. Both of these actions can be accomplished with a reduce call:
const aggregate = (objects) =>
objects.reduce((x, y) => ({
a: { key: x.a.key, value: x.a.value + y.a.value },
b: { key: x.b.key, value: x.b.value + y.b.value },
c: { key: x.c.key, value: x.c.value + y.c.value },
d: { key: x.d.key, value: x.d.value + y.d.value },
}));
const merge = (array1, array2) =>
Object.values(array1.concat(array2).reduce((acc, obj) => {
(acc[obj.a.key] ??= []).push(obj);
return acc;
}, {})).map(aggregate);
const array1 = [
{a:{key:1 , value: 10} , b:{key:1 , value:12} , c:{key:1 , value: 5} , d:{key:1 , value:2}},
{a:{key:2 , value: 10} , b:{key:2 , value:12} , c:{key:2 , value: 5} , d:{key:2 , value:2}},
{a:{key:3 , value: 10} , b:{key:3 , value:12} , c:{key:3 , value: 5} , d:{key:3 , value:2}},
];
const array2 = [
{a:{key:1 , value: 10} , b:{key:1 , value:12} , c:{key:1 , value: 5} , d:{key:1 , value:2}},
{a:{key:2 , value: 10} , b:{key:2 , value:12} , c:{key:2 , value: 5} , d:{key:2 , value:2}},
{a:{key:4 , value: 10} , b:{key:4 , value:12} , c:{key:4 , value: 5} , d:{key:4 , value:2}},
]
console.log(merge(array1, array2));

You can first reduce the output to a single object since its a sort of accumulation of numbers, and then get the format you want as the second step.
const array1 = [ { a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 }, }, { a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 }, }, { a: { key: 3, value: 10 }, b: { key: 3, value: 12 }, c: { key: 3, value: 5 }, d: { key: 3, value: 2 }, }, ]; const array2 = [ { a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 }, }, { a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 }, }, { a: { key: 4, value: 10 }, b: { key: 4, value: 12 }, c: { key: 4, value: 5 }, d: { key: 4, value: 2 }, }, ];
const mergedArray = [...array1, ...array2];
const keys = []
const reducedOutput = mergedArray.reduce((prev, curr) => {
Object.entries(curr).forEach(([mainKey, { key, value }]) => {
// mainKey is a, b, c, d in your case
if (!prev[mainKey]) {
prev[mainKey] = {};
}
// key is 1, 2, 3, 4 in your case
if (!keys.includes(key)) {
keys.push(key)
}
prev[mainKey][key] = prev[mainKey][key]
? prev[mainKey][key] + value
: value;
});
return prev;
}, {});
const output = keys.map(key => {
const obj = {}
Object.entries(reducedOutput).forEach(([k, v]) => {
obj[k] = {key, value: v[key]}
})
return obj
})
console.log(output)
This will work with any other keys for a, b, c, d keys and 1, 2, 3, 4 keys you have used in two levels.
Using Object.entries(), Array.prototype.reduce(), Array.prototype.forEach(), and Array.prototype.map()

The following provided code implements an approach which tries to find a balance in between 1st being agnostic to any array item's current and future structure except for both property names, key and value, of any array item's second level structure and 2nd how to handle the merger of other unknown second level data.
Therefore the general approach creates an object based lookup from the shorter sourceList where each item gets referred to via the value of its second level key property, whereas the longer targetList will be reduced in order to create the final result of merged items from both arrays.
Since approach and implementation are unaware of an items first level structure, one has to reduce again all of a currently processed item's entries. For each of a target item's unknown entry one can rely on such an entry's 2nd level properties, key and value. From all the available data, either known or unknown, one can aggregate the common merger of both the source- and the target-item; their values will be totaled and both of their unknown rest data will be merged by spread syntax, where the latter is the approach's trade off/compromise.
function aggregateFirstValueKeyBasedLookup(lookup, item) {
lookup[Object.values(item)[0]?.key ?? ''] = item;
return lookup;
}
function createKeyBasedValueMergerFromSourceLookup(
{ lookup = {}, result = [] }, targetItem, idx, arr,
) {
let currentLookupKey;
result.push(Object
.entries(targetItem)
.reduce((merger, [
targetEntryKey, {
key, value: targetEntryValue = 0, ...targetEntryRest
}
]) => {
currentLookupKey = key;
const sourceItem = lookup[key] ?? {};
const {
value: sourceEntryValue = 0, ...sourceEntryRest
} = sourceItem[targetEntryKey] ?? {};
return Object.assign(merger, {
[ targetEntryKey ]: {
key,
value: (targetEntryValue + sourceEntryValue),
...targetEntryRest,
...sourceEntryRest,
},
});
}, {})
);
// delete already processed source-items from lookup.
Reflect.deleteProperty(lookup, currentLookupKey);
if (idx >= arr.length - 1) {
// finalize the result by ...
result.push(
// ...pushing all of the lookup's
// unprocessed source-items.
...[...Object.values(lookup)]
);
}
return { lookup, result };
}
const array1 = [{
a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 }
}, {
a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 }
}, {
a: { key: 3, value: 10 }, b: { key: 3, value: 12 }, c: { key: 3, value: 5 }, d: { key: 3, value: 2 }
}];
const array2 = [{
a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 }
}, {
a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 }
}, {
a: { key: 4, value: 10 }, b: { key: 4, value: 12 }, c: { key: 4, value: 5 }, d: { key: 4, value: 2 }
}];
const [ targetList, sourceList ]
= [array1, array2].sort((a, b) => b.length - a.length);
const sourceLookup = sourceList
.reduce(aggregateFirstValueKeyBasedLookup, Object.create(null));
console.log({ sourceLookup });
const { result: mergedItemList } = targetList
.reduce(createKeyBasedValueMergerFromSourceLookup, {
lookup: sourceLookup, result: [],
});
console.log({ mergedItemList });
// - changed item structure which keeps
// just the most necessary pattern.
const newItemStructureList1 = [{
quick: { key: 'foo', value: 33, biz: 'biz' },
brown: { key: 'foo', value: 22, baz: 'baz' },
fox: { key: 'foo', value: 11, buzz: 'buzz' },
}, {
quick: { key: 'bar', value: 11, baz: 'baz' },
brown: { key: 'bar', value: 33, biz: 'biz' },
fox: { key: 'bar', value: 22, booz: 'booz' },
}, {
quick: { key: 'baz', value: 22, baz: 'baz' },
brown: { key: 'baz', value: 11, biz: 'biz' },
fox: { key: 'baz', value: 33, booz: 'booz' },
}];
const newItemStructureList2 = [{
brown: { key: 'foo', value: 11, baz: 'baz' },
fox: { key: 'foo', value: 33, booz: 'booz' },
quick: { key: 'foo', value: 22, baz: 'baz' },
}, {
fox: { key: 'baz', value: 33, buzz: 'buzz' },
quick: { key: 'baz', value: 11, biz: 'biz' },
brown: { key: 'baz', value: 33, baz: 'baz' },
}];
const [ target, source ]
= [newItemStructureList1, newItemStructureList2].sort((a, b) => b.length - a.length);
const lookup = source
.reduce(aggregateFirstValueKeyBasedLookup, Object.create(null));
console.log({ lookup });
const { result: mergedItems } = target
.reduce(createKeyBasedValueMergerFromSourceLookup, { lookup, result: [] });
console.log({ mergedItems });
.as-console-wrapper { min-height: 100%!important; top: 0; }

You could group with the key property and the outer property.
const
array1 = [{ a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 } }, { a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 } }, { a: { key: 3, value: 10 }, b: { key: 3, value: 12 }, c: { key: 3, value: 5 }, d: { key: 3, value: 2 } }],
array2 = [{ a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 } }, { a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 } }, { a: { key: 4, value: 10 }, b: { key: 4, value: 12 }, c: { key: 4, value: 5 }, d: { key: 4, value: 2 } }],
result = Object.values([...array1, ...array2].reduce((r, o) => {
Object.entries(o).forEach(([k, { key, value }]) => {
r[key] ??= {};
r[key][k] ??= { key, value: 0 };
r[key][k].value += value;
});
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

nested for loops to get another object-array output

I need to multiply all the "values" inside "obj1" with the "percent' inside obj2 based on the id of each object. What would be the best way to do that? I've tried with for loop and reduce but I wasn't successful. Any help will be appreciated.
const obj1 = [ { id: 1, value: 10 }, { id: 2, value: 10 } ]
const obj2 = {
len: {
id: 1,
nj: "321345",
percent: 0.05,
},
wor: {
id: 2,
nj: "321345",
percent: 0.1,
}
}
outputExpected: [ { id: 1, value: 0.5 }, { id: 2, value: 1 } ]
You can do that by going through and matching the ids. there are some optimizations that can be made if they are sorted however.
const obj1 = [ { id: 1, value: 10 }, { id: 2, value: 10 } ]
const obj2 = {
len: {
id: 1,
nj: "321345",
percent: 0.05,
},
wor: {
id: 2,
nj: "321345",
percent: 0.1,
}
}
const x = Object.keys(obj2).map((key,index)=>{
const { id, value } = obj1.find(({id})=>id===obj2[key].id)
return ({id,value:value*obj2[key].percent})
})
console.log(x)
//outputExpected: [ { id: 1, value: 0.5 }, { id: 2, value: 1 } ]
You can first create a lookup map using Map, then loop over the obj1 using map to get the desired result
const obj1 = [
{ id: 1, value: 10 },
{ id: 2, value: 10 },
];
const obj2 = {
len: {
id: 1,
nj: "321345",
percent: 0.05,
},
wor: {
id: 2,
nj: "321345",
percent: 0.1,
},
};
const map = new Map();
Object.values(obj2).forEach((v) => map.set(v.id, v));
const result = obj1.map((o) => ({ ...o, value: o.value * map.get(o.id).percent }));
console.log(result);
This should work for you but doesnt handle exeptions if the id doesnt exist in both objects.
// First get the values in an array for easier manipulation
const aux = Object.values(obj2)
const output = obj1.map(ob => {
// Find the id in the other array.
const obj2Ob = aux.find(o => o.id === ob.id) // The id must exist in this aproach
return {
id: ob.id,
value: ob.value * obj2Ob.percent
}
})
console.log(output) // [ { id: 1, value: 0.5 }, { id: 2, value: 1 } ]

How to transform an object into an array of objects by its keys?

I've got a data source:
const data = {
A: [{
value: 1
}, {
value: 2
}, {
value: 38
}],
B: [{
value: 46
}, {
value: 23
}, {
value: 32
}],
C: [{
value: 2345
}, {
value: 56
}, {
value: 3
}]
}
I need to transform this object in an array of objects like below:
[{
A: 1,
B: 46,
C: 2345
}, {
A: 2,
B: 23,
C: 56
}, {
A: 38,
B: 32,
C: 3
}]
I made some attempts but still not there:
a)
const result = Object.keys(data).map(key => data[key])
b)
const b = Object.keys(data).reduce((acc, curr, i) => {
const values = data[curr].map(el => el.value)
acc[curr] = values[i]
return acc
}, [])
EDIT:
All arrays (A, B, C) should have the same length. In case it doesn't, value should be "-" for the missing ones
eg:
[{
A: 1,
B: 46,
C: 2345
}, {
A: 2,
B: 23,
C: 56
}, {
A: 38,
B: 32,
C: -
}]
Assuming the number of objects in your value arrays is the same (or less than) the number of properties in your data object, you could map your data keys to new objects that you can create using Object.fromEntries(). You can pass an array of mapped values to this fromEntries call, which goes through all your keys a, b, c and uses the current index from the outer loop to determine which object to grab its value from:
const data = { A: [{ value: 1 }, { value: 2 }, { value: 38 }], B: [{ value: 46 }, { value: 23 }, { value: 32 }], C: [{ value: 2345 }, { value: 56 }, { value: 3 }] };
const res = Object.keys(data).map((_, i, arr) =>
Object.fromEntries(arr.map(key => [key, data[key][i]?.value ?? "-"]))
);
console.log(res);
If you need a more robust approach, I suggest you go with a solution such as T.J. Crowder's that can handle the case where there are more objects in your arrays than there are properties in your object.
I'm going to assume that the fact there are three properties (A, B, and C) and the fact there are three values for each of them is a coincidence and that we can't rely on that.
If so, see comments:
// Get the keys
const keys = Object.keys(data);
// A blank object to use as a template for a new array entry w/blank values
const blankEntry = Object.fromEntries(keys.map(key => [key, "-"]));
// Create the result array; since we don't know how long it'll need to
// be without reading through the array, just start with a blank one)
const result = [];
// Loop through the properties
for (const key of keys) {
// Loop through this property's values
const values = data[key];
for (let index = 0; index < values.length; ++index) {
// Get the object at this index, creating it if not there
let entry = result[index];
if (!entry) {
// Make a shallow copy of the template to create the entry
result[index] = entry = {...blankEntry};
}
// Set this key's value
entry[key] = values[index].value;
}
}
Live Example (I've added a fourth entry to A to show that the three and three thing isn't important to the solution, and to show the "-" thing working):
const data = {
A: [{
value: 1
}, {
value: 2
}, {
value: 38
}, {
value: 42
}],
B: [{
value: 46
}, {
value: 23
}, {
value: 32
}],
C: [{
value: 2345
}, {
value: 56
}, {
value: 3
}]
};
const keys = Object.keys(data);
const blankEntry = Object.fromEntries(keys.map(key => [key, "-"]));
const result = [];
for (const key of keys) {
const values = data[key];
for (let index = 0; index < values.length; ++index) {
let entry = result[index];
if (!entry) {
result[index] = entry = {...blankEntry};
}
entry[key] = values[index].value;
}
}
console.log(result);
.as-console-wrapper {
max-height: 100% !important;
}
You could get the max length first and then map the values.
const
data = { A: [{ value: 1 }, { value: 2 }, { value: 38 }, { value: 99 }], B: [{ value: 46 }, { value: 23 }, { value: 32 }], C: [{ value: 2345 }, { value: 56 }, { value: 3 }] },
entries = Object.entries(data),
length = entries.reduce((r, [, { length }]) => Math.max(r, length), 0),
result = entries.reduce(
(r, [k, a]) => r.map((o, i) => ({ ...o, ...(i in a && { [k]: a[i].value }) })),
Array.from(
{ length },
() => Object.fromEntries(entries.map(([k]) => [k, '-']))
)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can create an empty object with - and assign value after with reduce() and forEach().
const data = { A: [ { value: 1, }, { value: 2, }, { value: 38, }, ], B: [ { value: 46, }, { value: 23, }, { value: 32, }, ], C: [ { value: 2345, }, { value: 56, }, { value: 3, }, ], };
const keys = Object.keys(data);
const emptyObj = keys.map(_ =>
keys.reduce((o, key) => ({ ...o, [key]: "-" }), {})
);
const o = keys.reduce((a, b) => {
data[b].forEach((el, i) => {
a[i][b] = el.value;
});
return a;
}, emptyObj);
console.log(o);
I think I'd try with something like:
// input
const data = {
A: [{ value: 1 }, { value: 2 }, { value: 38 }, { value: 57 }],
B: [{ value: 46 }, { value: 23 }, { value: 32 }, { value: 42 }],
C: [{ value: 2345 }, { value: 56 }, { value: 3 }],
D: [{ value: 345 }, { value: 684 }]
};
// I am using an IIFE to initialize `output` in order not to pollute the global scope
// with variables that are not re-used anywhere else but obviously it is not strictly necessary.
const output = (() => {
const keys = Object.keys(data);
const maxItemsNr = Math.max(...Object.values(data).map(item => item.length));
const newArr = [];
for (let i = 0; i < maxItemsNr; i++) {
const item = keys.reduce((outObj, key) => {
outObj[key] = data[key][i] ? data[key][i].value : '-';
return outObj;
}, {});
newArr.push(item);
}
return newArr
})();
// test
console.log(output);
And this is my take on it:
const data = { A: [{value: 1}, {value: 2}],
B: [{value: 46}, {value: 23}, {value: 32}, {value: 38}, {value: 42}],
C: [{value: 2345}, {value: 56}, {value: 3}] };
const ntrs = Object.entries(data),
blnk = ntrs.reduce((a,[key])=>(a[key]="-",a),{}),
rslt = [];
ntrs.forEach(([k,arr])=> arr.forEach(({value},i)=>(rslt[i]=rslt[i]||{...blnk})[k]=value) )
console.log(rslt);
.as-console-wrapper {
max-height: 100% !important;
}
My solution. But I think Nick Parson's solution is better.
const data = {A: [{value: 1}, {value: 2}, {value: 38}],B: [{value: 46}, {value: 23}, {value: 32}],C: [{value: 2345}, {value: 56}, {value: 3}]}
function transform(object) {
const res = [];
Object.keys(object).forEach((key)=> {
object[key].forEach(({value},i) => {
if(res[i]) {
res[i][key] = value
} else {
res[i] = {
[key]: value
}
}
})
})
return res
}
console.log(transform(data))

Flatten Nested Objects and Output Array

I have a deeply nested object that has the following schema:
const data = {
Car1: {
key: "Car",
value: "1",
child: {
Driver1: {
key: "Driver",
value: "1",
child: {
Trip1: {
key: "Trip",
value: 1,
metrics: { distance: 1, time: 2 }
}
}
},
Driver2: {
key: "Driver",
value: "2",
child: {
Trip1: {
key: "Trip",
value: 1,
metrics: { distance: 3, time: 4 }
},
Trip2: {
key: "Trip",
value: 2,
metrics: { distance: 5, time: 6 }
}
}
}
}
}
}
That I need to flatten into a singular array of objects, with each object in the array having all the properties of its direct child(ren).
Each nested object child is a Record of objects that have properties key and value.
The last object in the nested structure always has a property called metrics that should be flattened into the object as well.
So the output would look something like:
[
{ Car: 1, Driver: 1, Trip: 1, distance: 1, time: 2 },
{ Car: 1, Driver: 2, Trip: 1, distance: 3, time: 4 },
{ Car: 1, Driver: 2, Trip: 2, distance: 5, time: 6 }
]
I have tried the following code but it only captures one level of depth in the child tree:
private flattenOutput(child: Record<string, OutputChild> = this.output): any[] {
console.log('flattening', Object.keys(child));
return Object.values(child).map((child) => {
return Object.assign(
{},
{ [child.key]: child.value },
...this.flattenOutput(child.child),
child.metrics || {},
);
}, {});
}
By having correct nested objects, you could take a recursive approach and collect key/value and return a flat array with wanted objects.
const
collect = (object, temp = {}) => Object
.values(object)
.flatMap(({ key, value, child, metrics }) => child
? collect(child, { ...temp, [key]: value })
: { ...temp, [key]: value , ...metrics }
),
data = { Car1: { key: "Car", value: "1", child: { Driver1: { key: "Driver", value: "1", child: { Trip1: { key: "Trip", value: 1, metrics: { distance: 1, time: 2 } } } }, Driver2: { key: "Driver", value: "2", child: { Trip1: { key: "Trip", value: 1, metrics: { distance: 3, time: 4 } }, Trip2: { key: "Trip", value: 2, metrics: { distance: 5, time: 6 } } } } } } },
result = collect(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I will do that this way :
data =
{ Car1: { key: "Car", value: "1", child:
{ Driver1: { key: "Driver", value: "1", child:
{ Trip1: { key: "Trip", value: 1, metrics: { distance: 1, time: 2} }
} }
, Driver2:
{ key: "Driver", value: "2", child:
{ Trip1: { key: "Trip", value: 1, metrics: { distance: 3, time: 4} }
, Trip2: { key: "Trip", value: 2, metrics: { distance: 5, time: 6} }
} } } } }
let result = []
for (let Car in data )
for (let Driver in data[Car].child)
for (let Trip in data[Car].child[Driver].child)
result.push(
{ Car : data[Car].value
, Driver : data[Car].child[Driver].value
, Trip : data[Car].child[Driver].child[Trip].value
, distance : data[Car].child[Driver].child[Trip].metrics.distance
, time : data[Car].child[Driver].child[Trip].metrics.time
})
console.log( result )
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to get the combination of array values from nested arrays in an array of objects

I have an array of objects with the following structure:
var varientSections = [
{
type: "frame",
values: ["black", "white", "wood"]
},
{
type: "finish",
values: ["matte", "glossy"]
}
];
I want to get the combination of the array values and create a new list with it. Right now, I am able to retrieve the combination from the nested array values using the method called getCombination(varientSections). However, I do not know how to create a new list with the following structure:
var results = [
{
attributes: [
{
type: "frame",
value: "black"
},
{
type: "finish",
value: "matte"
}
]
},
{
attributes: [
{
type: "frame",
value: "black"
},
{
type: "finish",
value: "glossy"
}
]
},
{
attributes: [
{
type: "frame",
value: "white"
},
{
type: "finish",
value: "matte"
}
]
},
{
attributes: [
{
type: "frame",
value: "white"
},
{
type: "finish",
value: "glossy"
}
]
},
{
attributes: [
{
type: "frame",
value: "wood"
},
{
type: "finish",
value: "matte"
}
]
},
{
attributes: [
{
type: "frame",
value: "wood"
},
{
type: "finish",
value: "glossy"
}
]
}
];
Below is my code:
function getCombinations(arr) {
if (arr.length === 0) {
return [[]];
}
let [current, ...rest] = arr;
let combinations = getCombinations(rest);
var result = current.values.reduce(
(accumulator, currentValue) => [
...accumulator,
...combinations.map(c => [currentValue, ...c])
],
[]
);
console.log("result is ");
console.log(result);
return result;
}
let varientCombinations = getCombinations(varientSections);
console.log(varientCombinations);
let updatedVarientDetails = [];
varientSections.forEach((varientSection, index) => {
let type = varientSection.type;
varientCombinations.forEach(combination => {
let obj = [
{
type: type,
value: combination[index]
},
];
updatedVarientDetails.push(obj);
});
});
console.log(updatedVarientDetails);
You could get the cartesian product and give it later the wanted style. The names and values are taken form the handed over object.
The algorithm takes all key/value pairs and has a stric view to the values, that means if an array is found or an object, hence w && typeof w === "object", the actual part is taken an used for adding additional key/value pairs.
For example a small object with two properties
{ a: 1, b: [2, 3] }
yields
[
{ a: 1, b: 2 },
{ a: 1, b: 3 }
]
A bit more advanced object, like
{ a: 1, b: { c: { d: [2, 3], e: [4, 5] } } }
yields the same structure as given
[
{
a: 1,
b: {
c: { d: 2, e: 4 }
}
},
{
a: 1,
b: {
c: { d: 2, e: 5 }
}
},
{
a: 1,
b: {
c: { d: 3, e: 4 }
}
},
{
a: 1,
b: {
c: { d: 3, e: 5 }
}
}
]
Thant means, from any found sub object the cartesian product is taken and combined with the actual values.
const
getCartesian = object => Object.entries(object).reduce(
(r, [key, value]) => {
let temp = [];
r.forEach(s =>
(Array.isArray(value) ? value : [value]).forEach(w =>
(w && typeof w === "object" ? getCartesian(w) : [w]).forEach(x =>
temp.push({ ...s, [key]: x })
)
)
);
return temp;
},
[{}]
),
data = [{ type: "frame", value: ["black", "white", "wood"] }, { type: "finish", value: ["matte", "glossy"] }],
result = getCartesian(data)
.map(o => ({ attributes: Object.assign([], o).map(({ ...o }) => o) }));
console.log(result);
console.log(getCartesian({ a: 1, b: { c: { d: [2, 3], e: [4, 5] } } }));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could simplify it to this:
var variantSections = [
{
type: "frame",
values: ["black", "white", "wood"]
},
{
type: "finish",
values: ["matte", "glossy"]
}
];
// iterate through each variantSection and create objects like {"type": "frame", "value": "black"}
var sections = variantSections.map(variant => {
return variant.values.map(val => ({type: variant.type, value: val}))
});
// then iterate through the two resulting arrays of objects, combining each into the attributes object you want
var results = [];
for (var i = 0; i < sections[0].length; i++) {
for (var j = 0; j < sections[1].length; j++) {
results.push({attributes: [sections[0][i], sections[1][j]]});
}
}
console.log(JSON.parse(JSON.stringify(results)));

transform array with map and reduce

I have several arrays like that I'm getting from a web service:
const a = [ { label: 'A', value: 100 }, { label: 'B', value: 200 } ];
const b = [ { label: 'A', value: 50 }, { label: 'B', value: 300 } ];
const c = [ { label: 'A', value: 20 }, { label: 'B', value: 130 } ];
const d = [ { label: 'A', value: 10 }, { label: 'B', value: 25 } ];
and I would like to have something like that (in a React state):
[
{ label: 'A', a: 100, b: 50, c: 20, d: 10 },
{ label: 'B', a: 200, b: 300, c: 130, d: 25 }
]
using modern JavaScript, I guess with map and reduce
EDIT:
I was not clear at all at first. I want to update my state when I get new data.
if my current state is:
[
{ label: 'A', a: 100, b: 50, c: 20, d: 10 },
{ label: 'B', a: 200, b: 300, c: 130, d: 25 }
]
and I'm getting
{ title: "a", values: [ { label: 'A', value: 12 }, { label: 'B', value: 13 } ] };
I want to update my state to
[
{ label: 'A', a: 12, b: 50, c: 20, d: 10 },
{ label: 'B', a: 13, b: 300, c: 130, d: 25 }
]
my best attempts was:
myFunction().then(data => {
const {chartData} = this.state;
chartData[data.title] = Object.keys(data.values).map(key => ({
label: chartData[data.title] || data.values[key].label,
[data.title]: data.values[key].value,
...chartData[data.title]
});
this.setState({chartData});
})
You could iterate the given state array, look for an item with the wanted label and update the properties.
function update(state, { title, values }) {
return values.reduce((r, { label, value }) => {
var temp = r.find(o => o.label === label);
if (!temp) r.push(temp = { label });
Object.assign(temp, { [title]: value });
return r;
}, state);
}
var state = [{ label: 'A', a: 100, b: 50, c: 20, d: 10 }, { label: 'B', a: 200, b: 300, c: 130, d: 25 }],
data = { title: "a", values: [{ label: 'A', value: 12 }, { label: 'B', value: 13 }] };
update(state, data);
console.log(state);
.as-console-wrapper { max-height: 100% !important; top: 0; }
const glue = (label, ...vars) => ([].concat(vars).filter(i => i.label === label).reduce((agg, i) => ({ ...agg, ...i }), {}));
where
glue('A', [a,b,c,d]);
glue('B', [a,b,c,d]);
// .. and so on
`
Create an object with all the arrays using Shorthand property names (The key names are required to create the properties in the output. You could add more arrays to this object). Then reduce the entries returned by Object.entries()
const a = [ { label: 'A', value: 100 }, { label: 'B', value: 200 } ];
const b = [ { label: 'A', value: 50 }, { label: 'B', value: 300 } ];
const c = [ { label: 'A', value: 20 }, { label: 'B', value: 130 } ];
const d = [ { label: 'A', value: 10 }, { label: 'B', value: 25 } ];
const input = { a, b, c, d };
const merged = Object.entries(input).reduce((r, [key, arr]) => {
arr.forEach(({label, value}) => {
r[label] = r[label] || { label };
r[label][key] = value;
})
return r;
}, {})
console.log(Object.values(merged))
Here idea is
Combine all the values in one array
Use a array to keep alphabets
Loop over combined array and for each element in combined array add vales to labels and alphabet based as per index of combined array
const a = [{ label: 'A', value: 100 }, { label: 'B', value: 200 }];
const b = [{ label: 'A', value: 50 }, { label: 'B', value: 300 }];
const c = [{ label: 'A', value: 20 }, { label: 'B', value: 130 }];
const d = [{ label: 'A', value: 10 }, { label: 'B', value: 25 }];
let alpha = [...'abcdefghijklmnopqrstuvwxyz']
let combine = [a,b,c,d]
let op = combine.reduce((op,inp,i) => {
inp.forEach(({label,value}) => {
op[label] = op[label] || {label}
op[label][alpha[i]] = value
})
return op
},{})
console.log(Object.values(op))

Categories

Resources