Javascript merge/reduce same multi dimensional objects - javascript

based on my question: https://stackoverflow.com/a/40661953/2392461, i open a new question with sample data.
I want to merge/reduce this:
var array = [{
'key1': {
'key11': 0,
'key12': 1
},
'key2': 0,
'key3': {
'key31': [1, 2],
'key32': {
'key321': 3,
'key322': [1, 2]
}
},
'key4': 'test'
}, {
'key1': {
'key11': 1,
'key12': 9
},
'key2': 2,
'key3': {
'key31': [4, 3],
'key32': {
'key321': 6,
'key322': [8, 9]
}
},
'key4': 'test'
}, {
'key1': {
'key11': 3,
'key12': 4
},
'key2': 7,
'key3': {
'key31': [3, 2],
'key32': {
'key321': 6,
'key322': [7, 8]
}
},
'key4': 'test'
}];
to this:
{
'key1': {
'key11': [0, 1, 3],
'key12': [1, 9, 4]
},
'key2': [0, 2, 7],
'key3': {
'key31': [[1, 2], [4, 3], [3, 2]],
'key32': {
'key321': [3, 6, 6],
'key322': [[1, 2], [8, 9], [7, 8]]
}
},
'key4': 'test'
}
the reduce function (https://stackoverflow.com/a/40668315/2392461) from #stasovlas with lodash looks good but i need to go deeper in the object.
_.reduce(data, function(result, item) {
var added = _.find(result, {
key4: item.key4
});
if (_.isObject(added)) {
//!! better to merge with new object and add result to array again to avoid mutable
added = _.mergeWith(added, item, function(addedVal, itemVal, key) {
if (key === 'key4') {
return addedVal;
}
return _.concat(addedVal, itemVal);
});
return result;
}
return _.concat(result, item);
}, []);
result is here a merged object only in layer 1 of the object.
[ { key1: [ [Object], [Object], [Object] ],
key2: [ 0, 2, 7 ],
key3: [ [Object], [Object], [Object] ],
key4: 1 } ]
i have no idea how to get my result. i think i have to iterate each object n times. n is the depth of the object.
is this the right way or is it easier than i think?
greetings mok

You could merge each level by iterating the entries of the source object and create either a new object or an array for adding the result. Proceed with child objects.
key4 gets a special treatment.
function merge(target, source) {
var singleKey = 'key4';
Object.entries(source).forEach(([key, value]) => {
if (value && typeof value === 'object' && !Array.isArray(value)) {
merge(target[key] = target[key] || {}, value);
return;
}
if (key === singleKey) {
target[key] = value;
return;
}
target[key] = target[key] || [];
target[key].push(value);
});
return target;
}
var array = [{ key1: { key11: 0, key12: 1 }, key2: 0, key3: { key31: [1, 2], key32: { key321: 3, key322: [1, 2] } }, key4: "test" }, { key1: { key11: 1, key12: 9 }, key2: 2, key3: { key31: [4, 3], key32: { key321: 6, key322: [8, 9] } }, key4: "test" }, { key1: { key11: 3, key12: 4 }, key2: 7, key3: { key31: [3, 2], key32: { key321: 6, key322: [7, 8] } }, key4: "test" }],
result = array.reduce(merge, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

I have made a few improvements, it is more flexible for any kind of this problem.
function merge(target, source) {
var fixedKeys = ['key1', 'key2', 'keyn'];
Object.entries(source).forEach(([key, value]) => {
if (value && value.toString() === '[object Object]') {
merge(target[key] = target[key] || {}, value);
return;
}
if (value && Array.isArray(value)) {
merge(target[key] = target[key] || [], value);
return;
}
if (fixedKeys.indexOf(key) !== -1) {
target[key] = value;
return;
}
target[key] = target[key] || [];
target[key].push(value);
});
return target;
}
Now u can merge/reduce also deeper values/arrays/object, there is no converting from JS Arrays to Objects or otherwise and it is JSON.stringify compatible. You can set an Array of keys which should occur only once.
hope this is helping not only me :)

Related

Get sum of length of nested array in an object

I have an object like this
const obj = {
name: "abc",
arr: [{
key1: "value1",
arr1: [1, 2, 3]
}, {
key1: "value2",
arr1: [4, 5, 6]
}]
}
Here, i want to add the lengths of arrays arr1 and arr2, and return 6 as an answer. I know looping and calculating lenghts by for-in is one solution, but what can be a faster and more precise solution?
this is the simplest implementation I can think of using reduce
const obj = {
name: "abc",
arr: [{
key1: "value1",
arr1: [1, 2, 3]
}, {
key1: "value2",
arr1: [4, 5, 6]
}]
}
const result = obj.arr.reduce((res, {arr1}) => res + arr1.length, 0)
console.log(result)
It looks you want to count the lengths of arrays that only have primitive values as members, not of arrays which have (some) objects/arrays as members.
Here is a proposed recursive solution:
const isPrimitive = val => Object(val) !== val;
const isArrayOfPrimitive = arr => arr?.every?.(isPrimitive);
const deepLength = obj => isPrimitive(obj) ? 0
: Object.values(obj).reduce((sum, val) => sum + deepLength(val),
isArrayOfPrimitive(obj) ? obj.length : 0);
// Example run:
const obj = {
name: "abc",
arr: [{
key1: "value1",
arr1: [1, 2, 3]
}, {
key2: "value2",
arr2: [4, 5, 6]
}]
};
console.log(deepLength(obj)); // 6
const obj = {
name: "abc",
arr: [
{ key1: "value1", arr1: [1, 2, 3, 4, 6, 7, 9] },
{ key2: "value2", arr2: [4, 5, 6] }
]
}
function getNumbers(arr) {
let a = arr['arr'][0].arr1.length;
let b = arr['arr'][1].arr2.length;
let sum = a + b;
return sum;
}
console.log(getNumbers(obj));

Problem with using for-in/of when returning an object in a function

I just got the result "[object Object]'s score is 0" printed on the terminal.
The result 27 was all fine until I separated the function into a return object.
How do I get 27 if I have to return an object?
How do I get "alex" printed on the console.log instead of [object Object]?
const alex = {
first: [1, 2, 9, 8],
second: [3],
third: [0, 0, 0, 1, 3]
};
const gordon = {
first: [3],
second: [2, 2, 4, 5, 6, 6, 7, 8]
}
function createPlayer(object) {
let score = 0;
return {
add: function() {
for (const key in object) {
for (const item in object[key]) {
score += object[key][item]
}
}
},
result: function() {
return `${object}\'s score is ${score}`
}
}
}
createPlayer(alex).add()
console.log(createPlayer(alex).result())
const alex = {
first: [1, 2, 9, 8],
second: [3],
third: [0, 0, 0, 1, 3]
};
const gordon = {
first: [3],
second: [2, 2, 4, 5, 6, 6, 7, 8]
}
function createPlayer(object, name) {
let score = 0;
return {
add: function() {
for (const key in object) {
for (const item in object[key]) {
score += object[key][item]
}
}
return this; // <<<<<
},
result: function() {
return `${name}\'s score is ${score}`
}
}
}
console.log(createPlayer(alex, 'Alex').add().result())
You would not show alex for an object named alex
You might mean this
const alex = {
Name: "Alex",
first: [1, 2, 9, 8],
second: [3],
third: [0, 0, 0, 1, 3]
};
const gordon = {
Name: "Gordon",
first: [3],
second: [2, 2, 4, 5, 6, 6, 7, 8]
}
function createPlayer(object) {
let score = 0;
return {
add: function() {
for (const key in object) {
if (key!=="Name") {
for (const item in object[key]) {
score += object[key][item]
}
}
}
},
result: function() {
return `${object.Name}\'s score is ${score}`
}
}
}
const player1 = createPlayer(alex)
player1.add()
console.log(player1.result())

Filter method and that objects are passed by ref

How can I do the below without changing the "list" variable? As far as I can understand from the docs (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) the filter method should return a New variable but the below says to me that it is just a reference since the "list" variable changes?
Can someone explain this behavior and how to go around it (do not want to change the original "list" var)?
var list = [{topic : "fussball", arr : [1,2]}, {topic : "soccer", arr : [3,4]},
{topic : "fussball", arr : [5,6]},{topic : "soccer", arr : [7,8]}];
function someFilterMethod(list, topic){
let tops = list.filter( function(obj){
if(obj.topic == topic){
return obj;}
});
for(let i = 0; i < tops.length ; i++){
if(i < (tops.length)) {
for(let j = (i+1) ; j<tops.length ; j++){
if( tops[i].topic.trim() == tops[j].topic.trim() ){
tops[i].arr.push.apply(tops[i].arr, tops[j].arr);
tops.splice(j, 1);
}
}
}
}
return tops;
}
var a = someFilterMethod(list, "fussball");
console.log(a);
//> Array [Object { topic: "fussball", arr: Array [1, 2, 5, 6] }]
console.log(list);
//> Array [Object { topic: "fussball", arr: Array [1, 2, 5, 6] }, Object { topic: "soccer", arr: Array [3, 4] }, Object { topic: "fussball", arr: Array [5, 6] }, Object { topic: "soccer", arr: Array [7, 8] }, Object { topic: "hockey", arr: Array [9, 10] }, Object { topic: "golf", arr: Array [11, 12] }, Object { topic: "hockey", arr: Array [13, 14] }, Object { topic: "golf", arr: Array [15, 16] }]
The someFilterMethod just takes the elements of the same "topic", concatenates their arrays and returns that.
You need to parse and strigify json, otherwise it will modify the existing object
var list = [{topic : "fussball", arr : [1,2]}, {topic : "soccer", arr : [3,4]},
{topic : "fussball", arr : [5,6]},{topic : "soccer", arr : [7,8]}];
function someFilterMethod(list, topic){
let tops = JSON.parse(JSON.stringify(list)).filter( function(obj){
if(obj.topic == topic){
return obj;}
});
for(let i = 0; i < tops.length ; i++){
if(i < (tops.length)) {
for(let j = (i+1) ; j<tops.length ; j++){
if( tops[i].topic.trim() == tops[j].topic.trim() ){
tops[i].arr.push.apply(tops[i].arr, tops[j].arr);
tops.splice(j, 1);
}
}
}
}
return tops;
}
var a = someFilterMethod(list, "fussball");
console.log(a);
//> Array [Object { topic: "fussball", arr: Array [1, 2, 5, 6] }]
console.log(list);
//> Array [Object { topic: "fussball", arr: Array [1, 2, 5, 6] }, Object { topic: "soccer", arr: Array [3, 4] }, Object { topic: "fussball", arr: Array [5, 6] }, Object { topic: "soccer", arr: Array [7, 8] }, Object { topic: "hockey", arr: Array [9, 10] }, Object { topic: "golf", arr: Array [11, 12] }, Object { topic: "hockey", arr: Array [13, 14] }, Object { topic: "golf", arr: Array [15, 16] }]
With Array.filters, new reference for the Array is created, however, objects continue to share the same reference. Hence, any change in objects of the filtered array will mean a change in original array.
You can try following
var list = [{topic : "fussball", arr : [1,2]}, {topic : "soccer", arr : [3,4]}, {topic : "fussball", arr : [5,6]},{topic : "soccer", arr : [7,8]}];
function someFilterMethod(list, topic){
return list.reduce((a,c) => {
if(c.topic == topic) a.arr = [...a.arr, ...c.arr];
return a;
}, {topic, arr: []});
}
var a = someFilterMethod(list, "fussball");
console.log(a);
console.log(list);

Group array of objects depending on values

I have an array of objects
[
{ values: [2, 7, 1] },
{ values: [1, 2, 7] },
{ values: [7, 1, 2] },
{ values: [3, 4, 5] },
{ values: [2, 1, 8] },
{ values: [2, 1, 8] },
]
I want to group these objects together with the other object with same values. So this array of objects should be grouped into 3 groups since the first 3 objects have the same values, the next object is alone and the last 2 objects have the same values.
As seen in the example, the values can have different orders, but should still be considered the same.
I am thinking about for each element to loop through the remaining elements and see if they are alike, but it will result in O(n^2).
I guess I should remove elements from the array that has already been grouped.
So how could I, efficiently, group it as
[[first, second, third],[fourth],[fifth,sixth]]
Something like this?
var data = [
{ values: [2, 7, 1] },
{ values: [1, 2, 7] },
{ values: [7, 1, 2] },
{ values: [3, 4, 5] },
{ values: [2, 1, 8] },
{ values: [2, 1, 8] },
];
var hash = {};
for(var obj of data) {
var key = obj.values.sort().join("-");
if (!hash[key]) hash[key] = [];
hash[key].push(obj);
}
var result = [];
for(var k in hash) result.push(hash[k])
console.log(result)
Or js6 variant:
var data = [
{ values: [2, 7, 1] },
{ values: [1, 2, 7] },
{ values: [7, 1, 2] },
{ values: [3, 4, 5] },
{ values: [2, 1, 8] },
{ values: [2, 1, 8] },
];
var hash = data.reduce((hash, obj) => {
const key = obj.values.sort().join("-");
if (!hash[key]) hash[key] = [];
hash[key].push(obj);
return hash;
}, [])
var result = Object.keys(hash).map(k => hash[k])
console.log(result)
You can do this with forEach() loop and sort()
var arr = [
{ values: [2, 7, 1] },
{ values: [1, 2, 7] },
{ values: [7, 1, 2] },
{ values: [3, 4, 5] },
{ values: [2, 1, 8] },
{ values: [2, 1, 8] },
];
var result = [];
arr.forEach(function(e) {
var s = [].concat(e.values).sort().join('|');
if (!this[s]) {
this[s] = [e.values];
result.push(this[s]);
} else {
this[s].push(e.values)
}
})
console.log(result)

underscore js mapping array of objects multiple properties to new array

var items = [{
//other properties... above
item_name: [
[1],
[2, 3]
],
item_description: [
[1],
[3, 4]
],
item_quantity: [
[1],
[4, 5]
],
item_value: null,
}, {
//other properties... above
item_name: 1,
item_description: 2,
item_quantity: 3,
item_value: 4,
}, {
//other properties... above
item_name: [1, 2, 3],
item_description: [1, 2, 3],
item_quantity: [1, 2, 3],
item_value: [1, 2, 3],
}];
var itemList = [];
items.forEach(function(item) {
if (!_.isArray(item.item_name)) {
itemList.push({
name: item.item_name,
description: item.item_description,
quantity: item.item_quantity,
value: item.item_value
});
}
var names = item.item_name ? _.flatten(item.item_name) : [];
var descriptions = item.item_description ? _.flatten(item.item_description) : [];
var quantity = item.item_quantity ? _.flatten(item.item_quantity) : [];
var values = item.item_value ? _.flatten(item.item_value) : [];
names.forEach(function(name, index) {
itemList.push({
name: names[index],
description: descriptions[index],
quantity: quantity[index],
values: values[index]
});
})
});
console.log(itemList);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.1/underscore-min.js"></script>
is there a way I can perform this faster in underscore, to remove all of the flattens?
for each item in the array I am taking
item_name[i]
item_description[i]
item_quantity[i]
item_value[i]
and adding them to the itemList
item properties in items can be [[],[]] or [] or integer or null
currently it is outputting what is expected (unless a name is null and it can skip items) however I do not like all of the loops this is performing and I am wondering if I can make a better use of underscore library
You can use this:
var myKeys = ['name', 'description', 'quantity', 'value'];
var result = _.flatten(items.map(function(item) {
return _.zip.apply(_, myKeys.map(function(key) {
return _.flatten([item['item_'+key]]);
})).map(function(arr) {
return _.object(myKeys, arr);
});
}));
Demo:
var items = [{
//other properties... above
item_name: [
[1],
[2, 3]
],
item_description: [
[1],
[3, 4]
],
item_quantity: [
[1],
[4, 5]
],
item_value: null,
}, {
//other properties... above
item_name: 1,
item_description: 2,
item_quantity: 3,
item_value: 4,
}, {
//other properties... above
item_name: [1, 2, 3],
item_description: [1, 2, 3],
item_quantity: [1, 2, 3],
item_value: [1, 2, 3],
}];
var myKeys = ['name', 'description', 'quantity', 'value'];
var result = _.flatten(items.map(function(item) {
return _.zip.apply(_, myKeys.map(function(key) {
return _.flatten([item['item_'+key]]);
})).map(function(arr) {
return _.object(myKeys, arr);
});
}));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.1/underscore-min.js"></script>

Categories

Resources