Extract 3 arrays of data from Array of JSON objects - javascript

I have a array of JSON objects defined as follow:
[
{
key : "test1",
data : {
"Mercedes" : {
"ClassA" : [1, 2],
"ClassB" : [1]
},
"Benz" : {
"ClassA" : [1]]
}
}
},
{
key : "test2",
data : {
"Mercedes" : {
"ClassA" : [1, 2, 3],
"ClassB" : [1]
},
"Toty" : {
"ClassA" : [1]]
}
}
},...
]
I would like to retrieve 3 distincts arrays:
One containing the name of distinct names that exist in all objects : result = ["Mercedes", "Benz", "Toty"]
One containing all distinct values of type: type = ["ClassA", "ClassB"]
One containing all distinct values of numbers : numbers = ["1", "2", "3"]
How can i retrieve these 3 arrays without needed to right multiple loops ?

This is not perfect, could be done in cleaner more "JS-y" ways, but here ya go
var someArray = ...; // your input array
var uniqueCars = new Set();
var uniqueClasses = new Set();
for (var i = 0; i < someArray.length; i++) {
// iterate through all prop names
for (var carProp in someArray[i].data) {
uniqueCars.add(carProp);
for (var classProp in someArray[i].data[carProp]) {
uniqueClasses.add(classProp);
// Too lazy to do the last one, hopefully you can figure it out
}
}
}
var finalCars = Array.from(uniqueCars);
var finalClasses = Array.from(uniqueClasses);
// do the 3rd one you asked for

Check out reduce - one of the many possible ways you can do it.
var data = [{
key: "test1",
data: {
"Mercedes": {
"ClassA": [1, 2],
"ClassB": [1]
},
"Benz": {
"ClassA": [1]
}
}
},
{
key: "test2",
data: {
"Mercedes": {
"ClassA": [1, 2, 3],
"ClassB": [1]
},
"Toty": {
"ClassA": [1]
}
}
}
];
var thing = data.reduce((acc, itm) => {
for (var type in itm.data) {
if (acc.types.indexOf(type) === -1) acc.types.push(type);
for (var cls in itm.data[type]) {
if (acc.classes.indexOf(cls) === -1) acc.classes.push(cls);
for (var i = itm.data[type][cls].length; i--;)
if (acc.numbers.indexOf(itm.data[type][cls][i]) === -1)
acc.numbers.push(itm.data[type][cls][i]);
}
}
return acc;
}, {
types: [],
numbers: [],
classes: []
});
console.log('Unique Types', thing.types);
console.log('Unique Numbers', thing.numbers);
console.log('Unique Classes', thing.classes);

This is just a proof of concept, but I think it can be made a recursive function and be more elegant.
let arr = [{ key: "test1", data: { "Mercedes": { "ClassA": [1, 2], "ClassB": [1] }, "Benz": { "ClassA": [1] } } }, { key: "test2", data: { "Mercedes": { "ClassA": [1, 2, 3], "ClassB": [1] }, "Toty": { "ClassA": [1] } } }],
flatten = (a, b) => [...a, ...b],
allUnq = a => [...new Set(a.reduce(flatten))],
data = arr.map(o => o.data),
vals = d => d.map(Object.values),
keys = d => d.map(Object.keys),
arr1 = allUnq(keys(data)),
arr2 = allUnq(vals(data).map(keys).map(allUnq)),
arr3 = allUnq(allUnq(vals(allUnq(vals(data)))));
console.log(arr1);
console.log(arr2);
console.log(arr3);
.as-console-wrapper {max-height: 100% !important;top: 0;}

Related

How create array containing all combinations of array 1-n array items?

We have the following task, to convert the array below called passengerFlights (object with passenger-id-keys and array of flights) to:
(1) an array with all possible combination of passenger-flights:
EXPECTED OUTPUT:
[
["aaa", "ddd", "eee"],
["aaa", "ddd", "fff"],
["bbb", "ddd", "eee"],
["bbb", "ddd", "fff"],
["ccc", "ddd", "eee"],
["ccc", "ddd", "fff"]
]
and (2) with the stipulation that there can be any number of passengers.
The following is an attempt to solve this first as a static example of three flights, although it's not clear the best way to (1) create the array with all possible combinations, and (2) how to solve the 2-n requirement, we assume recursion of some kind.
const passengerFlights = {
777: [
{
_id: "aaa"
},
{
_id: "bbb"
},
{
_id: "ccc"
}
],
888: [
{
_id: "ddd"
}
],
999: [
{
_id: "eee"
},
{
_id: "fff"
}
],
};
const getGroupedFlights = (passengerFlights) => {
let indexPointer = 0;
const indexCounters = [0, 0, 0];
const arr = [];
while (indexCounters[0] <= passengerFlights['777'].length - 1 || indexCounters[1] <= passengerFlights['888'].length - 1 || indexCounters[2] <= passengerFlights['999'].length - 1) {
arr.push([passengerFlights['777'][0]._id, passengerFlights['888'][0]._id, passengerFlights['999'][0]._id]);
if (indexCounters[2] < passengerFlights['999'].length) indexCounters[2]++;
if (indexCounters[2] >= passengerFlights['999'].length - 1 && indexCounters[1] < passengerFlights['888'].length) indexCounters[1]++;
if (indexCounters[1] >= passengerFlights['888'].length - 1 && indexCounters[0] < passengerFlights['777'].length) indexCounters[0]++;
console.log(indexCounters, passengerFlights['888'].length - 1);
}
return arr;
}
const groupedFlights = getGroupedFlights(passengerFlights);
console.log(groupedFlights);
It's just a simple recursive problem....
const
passengerFlights =
{ 777: [ { _id: 'aaa' }, { _id: 'bbb' }, { _id: 'ccc' } ]
, 888: [ { _id: 'ddd' } ]
, 999: [ { _id: 'eee' }, { _id: 'fff' } ]
}
, result = combinations( passengerFlights, '_id' )
;
console.log( showArr(result) )
function combinations( obj, KeyName )
{
let
result = []
, keys = Object.keys(obj) // [ "777", "888", "999" ]
, max = keys.length -1
;
f_recursif_combi(0)
return result
function f_recursif_combi( level, arr = [] )
{
obj[ keys[level] ] // like :passengerFlights['777']
.forEach( elm =>
{
let arr2 = [...arr, elm[KeyName] ]; // arr + elm['_id']
(level < max)
? f_recursif_combi(level +1, arr2 )
: result.push( arr2 )
})
}
}
// ************************************ just to present result...
function showArr(Arr)
{
const r = { '[["': `[ [ '`, '","': `', '`, '"],["': `' ]\n, [ '`, '"]]': `' ]\n]` }
return JSON
.stringify(result)
.replace(/\[\[\"|\"\,\"|\"\]\,\[\"|\"\]\]/g,(x)=>r[x])
}
.as-console-wrapper {max-height: 100%!important;top:0 }
I think it's the Cartesian product of flights sets. So this should help:
Cartesian product of multiple arrays in JavaScript
As another answer suggests, you can use a basic cartesian product function. Use Object.values(passengerFlights) to pass in the array of arrays.
function *product(arrs, p = []) {
if (arrs.length == 0)
yield p
else
for (const value of arrs[0])
yield *product(arrs.slice(1), [...p, value])
}
const passengerFlights =
{777: [{_id: "aaa"},{_id: "bbb"},{_id: "ccc"}],888: [{_id: "ddd"}],999: [{_id: "eee"},{_id: "fff"}]}
for (const p of product(Object.values(passengerFlights)))
console.log(JSON.stringify(p.map(obj => obj._id)))
I used JSON.stringify for easy visualization in the demo
["aaa","ddd","eee"]
["aaa","ddd","fff"]
["bbb","ddd","eee"]
["bbb","ddd","fff"]
["ccc","ddd","eee"]
["ccc","ddd","fff"]
But for your program you will probably prefer Array.from
console.log(
Array.from(
product(Object.values(passengerFlights)),
p => p.map(obj => obj._id)
)
)
[
["aaa","ddd","eee"],
["aaa","ddd","fff"],
["bbb","ddd","eee"],
["bbb","ddd","fff"],
["ccc","ddd","eee"],
["ccc","ddd","fff"]
]
Since the order of the expected result is not important, we can make the program more efficient.
function *product(arrs) {
if (arrs.length == 0)
yield []
else
for (const p of product(arrs.slice(1)))
for (const value of arrs[0])
yield [value, ...p]
}
const passengerFlights =
{777: [{_id: "aaa"},{_id: "bbb"},{_id: "ccc"}],888: [{_id: "ddd"}],999: [{_id: "eee"},{_id: "fff"}]}
for (const p of product(Object.values(passengerFlights)))
console.log(JSON.stringify(p.map(obj => obj._id)))
["aaa","ddd","eee"]
["bbb","ddd","eee"]
["ccc","ddd","eee"]
["aaa","ddd","fff"]
["bbb","ddd","fff"]
["ccc","ddd","fff"]

How can I add name to existing value pair in json

Hello this is my sample json:
{
"2016-01-01T00:00:00Z": 1,
"2016-02-01T00:00:00Z": 2,
"2016-03-01T00:00:00Z": 3
}
Now I want something like
[
{"Month":"2016-01-01T00:00:00Z", "Number": 1},
{"Month":"2016-02-01T00:00:00Z", "Number": 2},
{"Month":"2016-03-01T00:00:00Z", "Number": 3}
]
How can I do this using JS/Jquery? I wanted to change it to the above mentioned format because I need to put them in html table and I found out that using the second format makes my job easier.
you can do this in the following way
let obj = {
"2016-01-01T00:00:00Z": 1,
"2016-02-01T00:00:00Z": 2,
"2016-03-01T00:00:00Z": 3
};
let result = [];
for(element in obj){
result.push({"Month":element, "Number": obj[element]})
}
console.log(result);
You can use the jQuery map function to change the format of an array.
let jsonArray = {
"2016-01-01T00:00:00Z": 1,
"2016-02-01T00:00:00Z": 2,
"2016-03-01T00:00:00Z": 3
};
var result = $.map(jsonArray, function (item, key) {
return {
Month: key,
Number: item
};
});
You could take the keys with Object.keys and use Array#map for mapping the new objects.
var object = { "2016-01-01T00:00:00Z": 1, "2016-02-01T00:00:00Z": 2, "2016-03-01T00:00:00Z": 3 },
result = Object.keys(object).map(function (k) {
return { Month: k, Number: object[k] };
});
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
var object1 = {
"2016-01-01T00:00:00Z": 1,
"2016-02-01T00:00:00Z": 2,
"2016-03-01T00:00:00Z": 3
};
var finalArray = [];
for (var key in object1) {
if (p.hasOwnProperty(key)) { // p.hasOwnProperty this will check for duplicate key
finalArray.push({
“Month” : key,
“Number”:p[key]
});
}
}
console.log(finalArray)
Another option could include using Object.keys along with map as such...
let obj = {
'2016-01-01T00:00:00Z': 1,
'2016-02-01T00:00:00Z': 2,
'2016-03-01T00:00:00Z': 3
}
let arr = Object.keys(obj).map(key => {
return {'Month': key, 'Number': obj[key]}
});
JSFiddle demo
use $.each for travelling
a = {
"2016-01-01T00:00:00Z": 1,
"2016-02-01T00:00:00Z": 2,
"2016-03-01T00:00:00Z": 3
}
var b = [];
$.each( a, function( key, value ) {
b.push({mounth: key ,number: value });
});
Output will be:
0:{mounth: "2016-01-01T00:00:00Z", number: 1}
1:{mounth: "2016-02-01T00:00:00Z", number: 2}
2:{mounth: "2016-03-01T00:00:00Z", number: 3}

Deep merging nested arrays

I have to merge 2 arrays with key value as follows:
array1 = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"test2"},{id:"yz", data:"test3"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"}]}]
with
array2 = [
{id:"456", data:[{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}]
to produce something like
merged = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"},{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}]
I've been trying this out for quite some time and can't get a solution that meets the scenario. Most of the solutions just do blind merging, not based on the id value. Tried using lodash mergeWith but didn't get the output needed. A Ramda solution is also acceptable.
Thanks,
This links could be helpful to you merge two arrays.
In this code snippet, i have tried to find the common objects between set1 and set2,if there are any i'm finding the unique properties and changing their content and also non existant properties in object2 and pushing it to object1
Check the following snippet.
var arr1 = [{
id: "123",
data: [{
id: "234",
data: "hello"
}, {
id: "345",
data: "there"
}, {
id: "xyz",
data: "yo"
}]
}, {
id: "456",
data: [{
id: "34",
data: "test"
}, {
id: "45",
data: "test2"
}, {
id: "yz",
data: "test3"
}]
}, {
id: "789",
data: [{
id: "23",
data: "aaa"
}, {
id: "34",
data: "bbb"
}, {
id: "xy",
data: "ccc"
}]
}]
var arr2 = [{
id: "456",
data: [{
id: "45",
data: "changed"
}, {
id: "yz",
data: "data"
}]
}, {
id: "789",
data: [{
id: "456",
data: "appended data"
}]
}, {
id: "890",
data: [{
id: "456",
data: "new data"
}]
}]
var arr3 = [];
for (var i in arr1) {
var shared = false;
for (var j in arr2)
if (arr2[j].id == arr1[i].id) {
shared = true;
// arr1[i].data.concat(arr2[j].data);
var set1 = pushproperties(arr1[i].data, arr2[j].data);
arr1[i].data = set1;
arr3.push(arr1[i]);
break;
}
if (!shared) {
arr3.push(arr1[i]);
arr3.push(arr2[j]);
}
}
function pushproperties(set1, set2) {
var filtered = false;
set2.forEach(function(item) {
filtered = set1.every(function(element) {
return element.id != item.id;
});
if (filtered) {
set1.push(item);
}
});
set1.forEach(function(item) {
set2.forEach(function(element) {
if (item.id == element.id) {
item.data = element.data;
}
});
});
return set1;
}
console.log(arr3);
Hope this helps
This a function the merges 2 arrays recursively using Array.prototype.reduce(). If it encounters items with the same id, and they have a data prop, which is an array, it merges them using the logic. If data is not an array, it's overridden by the last item instead.
function mergeArraysDeep(arr1, arr2) {
var unique = arr1.concat(arr2).reduce(function(hash, item) {
var current = hash[item.id];
if(!current) {
hash[item.id] = item;
} else if (Array.isArray(current.data)) {
current.data = mergeArraysDeep(current.data, item.data);
} else {
current.data = item.data;
}
return hash;
}, {});
return Object.keys(unique).map(function(key) {
return unique[key];
});
}
var array1 = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"test2"},{id:"yz", data:"test3"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"}]}
];
var array2 = [
{id:"456", data:[{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}
];
var result = mergeArraysDeep(array1, array2)
console.log(result);
ES6 version that uses Map, Map.prototype.values(), and array spread:
const mergeArraysDeep = (arr1, arr2) => {
return [...arr1.concat(arr2).reduce((hash, item) => {
const current = hash.get(item.id);
if(!current) {
hash.set(item.id, item);
} else if (Array.isArray(current.data)) {
current.data = mergeArraysDeep(current.data, item.data);
} else {
current.data = item.data;
}
return hash;
}, new Map()).values()];
}
const array1 = [
{id:"123", data:[{id:"234",data:"hello"},{id:"345",data:"there"},{id:"xyz", data:"yo"}]},
{id:"456", data:[{id:"34",data:"test"},{id:"45",data:"test2"},{id:"yz", data:"test3"}]},
{id:"789", data:[{id:"23",data:"aaa"},{id:"34",data:"bbb"},{id:"xy", data:"ccc"}]}
];
const array2 = [
{id:"456", data:[{id:"45",data:"changed"},{id:"yz", data:"data"}]},
{id:"789", data:[{id:"456",data:"appended data"}]},
{id:"890", data:[{id:"456",data:"new data"}]}
];
const result = mergeArraysDeep(array1, array2)
console.log(result);
Finally this is what worked for me. Thanks to #Geeky for showing the way:
function mergeArrays(arr1, arr2) {
var arr3, arrIdx = [];
if (!arr1 || arr1.length ==0) return arr2
for (var i in arr1) {
var shared = false;
for (var j in arr2)
if (arr2[j].id == arr1[i].id) {
shared = true;
joined = _.mergeWith({},arr1[i],arr2[j], function (a,b) {
if (_.isArray(a)) return b.concat(a)})
arr3.push(joined);
break;
}
if (!shared) {
arr3.push(arr1[i]);
}
}
for (var k in arr2) {
if (arrIdx[k] !=k) arr3.push(arr2[k])
}
return arr3
}

Javascript recursive array flattening

I'm exercising and trying to write a recursive array flattening function. The code goes here:
function flatten() {
var flat = [];
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] instanceof Array) {
flat.push(flatten(arguments[i]));
}
flat.push(arguments[i]);
}
return flat;
}
The problem is that if I pass there an array or nested arrays I get the "maximum call stack size exceeded" error. What am I doing wrong?
The problem is how you are passing the processing of array, if the value is an array then you are keep calling it causing an infinite loop
function flatten() {
var flat = [];
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] instanceof Array) {
flat.push.apply(flat, flatten.apply(this, arguments[i]));
} else {
flat.push(arguments[i]);
}
}
return flat;
}
Demo: Fiddle
Here's a more modern version:
function flatten(items) {
const flat = [];
items.forEach(item => {
if (Array.isArray(item)) {
flat.push(...flatten(item));
} else {
flat.push(item);
}
});
return flat;
}
The clean way to flatten an Array in 2019 with ES6 is flat()
Short Answer:
array.flat(Infinity)
Detailed Answer:
const array = [1, 1, [2, 2], [[3, [4], 3], 2]]
// All layers
array.flat(Infinity) // [1, 1, 2, 2, 3, 4, 3, 2]
// Varying depths
array.flat() // [1, 1, 2, 2, Array(3), 2]
array.flat(2) // [1, 1, 2, 2, 3, Array(1), 3, 2]
array.flat().flat() // [1, 1, 2, 2, 3, Array(1), 3, 2]
array.flat(3) // [1, 1, 2, 2, 3, 4, 3, 2]
array.flat().flat().flat() // [1, 1, 2, 2, 3, 4, 3, 2]
Mozilla Docs
Can I Use - 95% Jul '22
If the item is array, we simply add all the remaining items to this array
function flatten(array, result) {
if (array.length === 0) {
return result
}
var head = array[0]
var rest = array.slice(1)
if (Array.isArray(head)) {
return flatten(head.concat(rest), result)
}
result.push(head)
return flatten(rest, result)
}
console.log(flatten([], []))
console.log(flatten([1], []))
console.log(flatten([1,2,3], []))
console.log(flatten([1,2,[3,4]], []))
console.log(flatten([1,2,[3,[4,5,6]]], []))
console.log(flatten([[1,2,3],[4,5,6]], []))
console.log(flatten([[1,2,3],[[4,5],6,7]], []))
console.log(flatten([[1,2,3],[[4,5],6,[7,8,9]]], []))
[...arr.toString().split(",")]
Use the toString() method of the Object. Use a spread operator (...) to make an array of string and split it by ",".
Example:
let arr =[["1","2"],[[[3]]]]; // output : ["1", "2", "3"]
A Haskellesque approach...
function flatArray([x,...xs]){
return x !== undefined ? [...Array.isArray(x) ? flatArray(x) : [x],...flatArray(xs)]
: [];
}
var na = [[1,2],[3,[4,5]],[6,7,[[[8],9]]],10],
fa = flatArray(na);
console.log(fa);
So i think the above code snippet could be made easier to understand with proper indenting;
function flatArray([x,...xs]){
return x !== undefined ? [ ...Array.isArray(x) ? flatArray(x)
: [x]
, ...flatArray(xs)
]
: [];
}
var na = [[1,2],[3,[4,5]],[6,7,[[[8],9]]],10],
fa = flatArray(na);
console.log(fa);
If you assume your first argument is an array, you can make this pretty simple.
function flatten(a) {
return a.reduce((flat, i) => {
if (Array.isArray(i)) {
return flat.concat(flatten(i));
}
return flat.concat(i);
}, []);
}
If you did want to flatten multiple arrays just concat them before passing.
If someone looking for flatten array of objects (e.g. tree) so here is a code:
function flatten(items) {
const flat = [];
items.forEach(item => {
flat.push(item)
if (Array.isArray(item.children) && item.children.length > 0) {
flat.push(...flatten(item.children));
delete item.children
}
delete item.children
});
return flat;
}
var test = [
{children: [
{children: [], title: '2'}
],
title: '1'},
{children: [
{children: [], title: '4'},
{children: [], title: '5'}
],
title: '3'}
]
console.log(flatten(test))
Your code is missing an else statement and the recursive call is incorrect (you pass the same array over and over instead of passing its items).
Your function could be written like this:
function flatten() {
// variable number of arguments, each argument could be:
// - array
// array items are passed to flatten function as arguments and result is appended to flat array
// - anything else
// pushed to the flat array as-is
var flat = [],
i;
for (i = 0; i < arguments.length; i++) {
if (arguments[i] instanceof Array) {
flat = flat.concat(flatten.apply(null, arguments[i]));
} else {
flat.push(arguments[i]);
}
}
return flat;
}
// flatten([[[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]], [[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]]]);
// [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]
Modern but not crossbrowser
function flatten(arr) {
return arr.flatMap(el => {
if(Array.isArray(el)) {
return flatten(el);
} else {
return el;
}
});
}
This is a Vanilla JavaScript solution to this problem
var _items = {'keyOne': 'valueOne', 'keyTwo': 'valueTwo', 'keyThree': ['valueTree', {'keyFour': ['valueFour', 'valueFive']}]};
// another example
// _items = ['valueOne', 'valueTwo', {'keyThree': ['valueTree', {'keyFour': ['valueFour', 'valueFive']}]}];
// another example
/*_items = {"data": [{
"rating": "0",
"title": "The Killing Kind",
"author": "John Connolly",
"type": "Book",
"asin": "0340771224",
"tags": "",
"review": "i still haven't had time to read this one..."
}, {
"rating": "0",
"title": "The Third Secret",
"author": "Steve Berry",
"type": "Book",
"asin": "0340899263",
"tags": "",
"review": "need to find time to read this book"
}]};*/
function flatten() {
var results = [],
arrayFlatten;
arrayFlatten = function arrayFlattenClosure(items) {
var key;
for (key in items) {
if ('object' === typeof items[key]) {
arrayFlatten(items[key]);
} else {
results.push(items[key]);
}
}
};
arrayFlatten(_items);
return results;
}
console.log(flatten());
Here's a recursive reduce implementation taken from absurdum that mimics lodash's _.concat()
It can take any number of array or non-array arguments. The arrays can be any level of depth. The resulting output will be a single array of flattened values.
export const concat = (...arrays) => {
return flatten(arrays, []);
}
function flatten(array, initial = []) {
return array.reduce((acc, curr) => {
if(Array.isArray(curr)) {
acc = flatten(curr, acc);
} else {
acc.push(curr);
}
return acc;
}, initial);
}
It can take any number of arrays or non-array values as input.
Source: I'm the author of absurdum
Here you are my functional approach:
const deepFlatten = (array => (array, start = []) => array.reduce((acc, curr) => {
return Array.isArray(curr) ? deepFlatten(curr, acc) : [...acc, curr];
}, start))();
console.log(deepFlatten([[1,2,[3, 4, [5, [6]]]],7]));
A recursive approach to flatten an array in JavaScript is as follows.
function flatten(array) {
let flatArray = [];
for (let i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
flatArray.push(...flatten(array[i]));
} else {
flatArray.push(array[i]);
}
}
return flatArray;
}
let array = [[1, 2, 3], [[4, 5], 6, [7, 8, 9]]];
console.log(flatten(array));
// Output = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
let array2 = [1, 2, [3, [4, 5, 6]]];
console.log(flatten(array2));
// Output = [ 1, 2, 3, 4, 5, 6 ]
The function below flat the array and mantains the type of every item not changing them to a string. It is usefull if you need to flat arrays that not contains only numbers like items. It flat any kind of array with free of side effect.
function flatten(arr) {
for (let i = 0; i < arr.length; i++) {
arr = arr.reduce((a, b) => a.concat(b),[])
}
return arr
}
console.log(flatten([1, 2, [3, [[4]]]]));
console.log(flatten([[], {}, ['A', [[4]]]]));
Another answer in the list of answers, flattening an array with recursion:
let arr = [1, 2, [3, 4, 5, [6, 7, [[8], 9, [10]], [11, 13]], 15], [16, [17]]];
let newArr = [];
function steamRollAnArray(list) {
for (let i = 0; i < list.length; i++) {
if (Array.isArray(list[i])) {
steamRollAnArray(list[i]);
} else {
newArr.push(list[i]);
}
}
}
steamRollAnArray(arr);
console.log(newArr);
To simplify, check whether the element at an index is an array itself and if so, pass it to the same function. If its not an array, push it to the new array.
This should work
function flatten() {
var flat = [
];
for (var i = 0; i < arguments.length; i++) {
flat = flat.concat(arguments[i]);
}
var removeIndex = [
];
for (var i = flat.length - 1; i >= 0; i--) {
if (flat[i] instanceof Array) {
flat = flat.concat(flatten(flat[i]));
removeIndex.push(i);
}
}
for (var i = 0; i < removeIndex.length; i++) {
flat.splice(removeIndex - i, 1);
}
return flat;
}
The other answers already did point to the source of the OP's code malfunction. Writing more descriptive code, the problem literally boils down to an "array-detection/-reduce/-concat-recursion" ...
(function (Array, Object) {
//"use strict";
var
array_prototype = Array.prototype,
array_prototype_slice = array_prototype.slice,
expose_internal_class = Object.prototype.toString,
isArguments = function (type) {
return !!type && (/^\[object\s+Arguments\]$/).test(expose_internal_class.call(type));
},
isArray = function (type) {
return !!type && (/^\[object\s+Array\]$/).test(expose_internal_class.call(type));
},
array_from = ((typeof Array.from == "function") && Array.from) || function (listAlike) {
return array_prototype_slice.call(listAlike);
},
array_flatten = function flatten (list) {
list = (isArguments(list) && array_from(list)) || list;
if (isArray(list)) {
list = list.reduce(function (collector, elm) {
return collector.concat(flatten(elm));
}, []);
}
return list;
}
;
array_prototype.flatten = function () {
return array_flatten(this);
};
}(Array, Object));
borrowing code from one of the other answers as proof of concept ...
console.log([
[[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]],
[[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]]
].flatten());
//[0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, ..., ..., ..., 0, 1, 2]
I hope you got all kind of different. One with a combination of recursive and "for loop"/high-order function. I wanted to answer without for loop or high order function.
Check the first element of the array is an array again. If yes, do recursive till you reach the inner-most array. Then push it to the result. I hope I approached it in a pure recursive way.
function flatten(arr, result = []) {
if(!arr.length) return result;
(Array.isArray(arr[0])) ? flatten(arr[0], result): result.push(arr[0]);
return flatten(arr.slice(1),result)
}
I think the problem is the way you are using arguments.
since you said when you pass a nested array, it causes "maximum call stack size exceeded" Error.
because arguments[0] is a reference pointed to the first param you passed to the flatten function. for example:
flatten([1,[2,[3]]]) // arguments[0] will always represents `[1,[2,[3]]]`
so, you code ends up calling flatten with the same param again and again.
to solve this problem, i think it's better to use named arguments, rather than using arguments, which essentially not a "real array".
There are few ways to do this:
using the flat method and Infinity keyword:
const flattened = arr.flat(Infinity);
You can flatten any array using the methods reduce and concat like this:
function flatten(arr) { return arr.reduce((acc, cur) => acc.concat(Array.isArray(cur) ? flatten(cur) : cur), []); };
Read more at:
https://www.techiedelight.com/recursively-flatten-nested-array-javascript/
const nums = [1,2,[3,4,[5]]];
const chars = ['a',['b','c',['d',['e','f']]]];
const mixed = ['a',[3,6],'c',[1,5,['b',[2,'e']]]];
const flatten = (arr,res=[]) => res.concat(...arr.map((el) => (Array.isArray(el)) ? flatten(el) : el));
console.log(flatten(nums)); // [ 1, 2, 3, 4, 5 ]
console.log(flatten(chars)); // [ 'a', 'b', 'c', 'd', 'e', 'f' ]
console.log(flatten(mixed)); // [ 'a', 3, 6, 'c', 1, 5, 'b', 2, 'e' ]
Here is the breakdown:
loop over "arr" with "map"
arr.map((el) => ...)
on each iteration we'll use a ternary to check whether each "el" is an array or not
(Array.isArray(el))
if "el" is an array, then invoke "flatten" recursively and pass in "el" as its argument
flatten(el)
if "el" is not an array, then simply return "el"
: el
lastly, concatenate the outcome of the ternary with "res"
res.concat(...arr.map((el) => (Array.isArray(el)) ? flatten(el) : el));
--> the spread operator will copy all the element(s) instead of the array itself while concatenating with "res"
var nestedArr = [1, 2, 3, [4, 5, [6, 7, [8, [9]]]], 10];
let finalArray = [];
const getFlattenArray = (array) => {
array.forEach(element => {
if (Array.isArray(element)) {
getFlattenArray(element)
} else {
finalArray.push(element)
}
});
}
getFlattenArray(nestedArr);
In the finalArray you will get the flattened array
Solution using forEach
function flatten(arr) {
const flat = [];
arr.forEach((item) => {
Array.isArray(item) ? flat.push(...flatten(item)) : flat.push(item);
});
return flat;
}
Solution using reduce
function flatten(arr) {
return arr.reduce((acc, curr) => {
if (Array.isArray(curr)) {
return [...acc, ...flatten(curr)];
} else {
return [...acc, curr];
}
}, []);
}
I think you are very close. One of the problems are that you call the flatten function with the same arguments. We can make use of the spread operator (...) to make sure we are calling flatten on the array inside of arguments[i], and not repeating the same arguments.
We also need to make a few more adjustments so we're not pushing more items into our array than we should
function flatten() {
var flat = [];
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] instanceof Array) {
flat.push(...flatten(...arguments[i]));
} else {
flat.push(arguments[i]);
}
}
return flat;
}
console.log(flatten([1,2,3,[4,5,6,[7,8,9]]],[10,11,12]));
function flatArray(input) {
if (input[0] === undefined) return [];
if (Array.isArray(input[0]))
return [...flatArray(input[0]), ...flatArray(input.slice(1))];
return [input[0], ...flatArray(input.slice(1))];
}
you should add stop condition for the recursion .
as an example
if len (arguments[i]) ==0 return
I have posted my recursive version of array flattening here in stackoverflow, at this page.

JavaScript merging objects by id [duplicate]

This question already has answers here:
Most efficient method to groupby on an array of objects
(58 answers)
How to merge two arrays in JavaScript and de-duplicate items
(89 answers)
Closed 7 months ago.
What's the correct way to merge two arrays in Javascript?
I've got two arrays (for example):
var a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
var a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]
I want to be able to end up with something like:
var a3 = [{ id : 1, name : "test", count : "1"},
{ id : 2, name : "test2", count : "2"}]
Where the two arrays are being joined based on the 'id' field and extra data is simply being added.
I tried to use _.union to do this, but it simply overwrites the values from the second array into the first one
Short ES6 solution
const a3 = a1.map(t1 => ({...t1, ...a2.find(t2 => t2.id === t1.id)}))
This should do the trick:
var mergedList = _.map(a1, function(item){
return _.extend(item, _.findWhere(a2, { id: item.id }));
});
This assumes that the id of the second object in a1 should be 2 rather than "2"
Assuming IDs are strings and the order does not matter, you can
Create a hash table.
Iterate both arrays and store the data in the hash table, indexed by the ID. If there already is some data with that ID, update it with Object.assign (ES6, can be polyfilled).
Get an array with the values of the hash map.
var hash = Object.create(null);
a1.concat(a2).forEach(function(obj) {
hash[obj.id] = Object.assign(hash[obj.id] || {}, obj);
});
var a3 = Object.keys(hash).map(function(key) {
return hash[key];
});
In ECMAScript6, if the IDs are not necessarily strings, you can use Map:
var hash = new Map();
a1.concat(a2).forEach(function(obj) {
hash.set(obj.id, Object.assign(hash.get(obj.id) || {}, obj))
});
var a3 = Array.from(hash.values());
ES6 simplifies this:
let merge = (obj1, obj2) => ({...obj1, ...obj2});
Note that repeated keys will be merged, and the value of the second object will prevail and the repeated value of the first object will be ignored.
Example:
let obj1 = {id: 1, uniqueObj1Key: "uniqueKeyValueObj1", repeatedKey: "obj1Val"};
let obj2 = {id: 1, uniqueObj2Key: "uniqueKeyValueObj2", repeatedKey: "obj2Val"};
merge(obj1, obj2)
// {id: 1, uniqueObj1Key: "uniqueKeyValueObj1", repeatedKey: "obj2Val", uniqueObj2Key: "uniqueKeyValueObj2"}
merge(obj2, obj1)
// {id: 1, uniqueObj2Key: "uniqueKeyValueObj2", repeatedKey: "obj1Val", uniqueObj1Key: "uniqueKeyValueObj1"}
Complete solution (with Lodash, not Underscore)
var a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
var a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]
var merge = (obj1, obj2) => ({...obj1, ...obj2});
_.zipWith(a1, a2, merge)
(2) [{…}, {…}]
0: {id: 1, name: "test", count: "1"}
1: {id: 2, name: "test2", count: "2"}
If you have an array of arrays to merge you can do it like this:
var arrayOfArraysToMerge = [a1, a2, a3, a4]; //a3 and a4 are arrays like a1 and a2 but with different properties and same IDs.
_.zipWith(...arrayOfArraysToMerge, merge)
(2) [{…}, {…}]
0: {id: 1, name: "test", count: "1", extra1: "val1", extra2: 1}
1: {id: 2, name: "test2", count: "2", extra1: "val2", extra2: 2}
reduce version.
var a3 = a1.concat(a2).reduce((acc, x) => {
acc[x.id] = Object.assign(acc[x.id] || {}, x);
return acc;
}, {});
_.values(a3);
I think it's common practice in functional language.
Already there are many great answers, I'll just add another one which is from a real problem I needed to solve yesterday.
I had an array of messages with user ids, and one array of users containing users' names and other details. This is how I managed to add user details to the messages.
var messages = [{userId: 2, content: "Salam"}, {userId: 5, content: "Hello"},{userId: 4, content: "Moi"}];
var users = [{id: 2, name: "Grace"}, {id: 4, name: "Janetta"},{id: 5, name: "Sara"}];
var messagesWithUserNames = messages.map((msg)=> {
var haveEqualId = (user) => user.id === msg.userId
var userWithEqualId= users.find(haveEqualId)
return Object.assign({}, msg, userWithEqualId)
})
console.log(messagesWithUserNames)
Vanilla JS solution
const a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
const a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]
const merge = (arr1, arr2) => {
const temp = []
arr1.forEach(x => {
arr2.forEach(y => {
if (x.id === y.id) {
temp.push({ ...x, ...y })
}
})
})
return temp
}
console.log(merge(a1, a2))
The lodash implementaiton:
var merged = _.map(a1, function(item) {
return _.assign(item, _.find(a2, ['id', item.id]));
});
The result:
[
{
"id":1,
"name":"test",
"count":"1"
},
{
"id":2,
"name":"test2",
"count":"2"
}
]
Wanted to add this answer which is derived from #daisihi answer above. Main difference is that this uses the spread operator.
Also, at the end I remove the id because it was not desirable in the first place.
const a3 = [...a1, ...a2].reduce((acc, x) => {
acc[x.id] = {...acc[x.id] || {}, ...x};
return acc;
}, {});
This part was taken from another post. removing a property from a list of objects in an array
const newArray = Object.values(a3).map(({id, ...keepAttrs}) => keepAttrs);
Found other solutions failing for some cases, so writing a better one here
const a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
const a2 = [{ id : 3, count : "3"}, { id : 1, count : "1"}, {id : 2, count : "2"}]
const mergeHelper = new Map(a1.map(x => [x.id, x]));
for (const x of a2) {
if (mergeHelper.has(x.id)) {
const item = mergeHelper.get(x.id);
mergeHelper.set(x.id, {...item, ...x});
} else {
mergeHelper.set(x.id, x);
}
}
const mergedList = [...mergeHelper.values()];
// For sorted array
// const mergedSortedList = [...mergeHelper.values()].sort((a, b) => a.id - b.id);
console.log(mergedList)
Using js Map is way faster than other approaches, helps when array length is huge.
A working TypeScript version:
export default class Merge {
static byKey(a1: any[], a2: any[], key: string) {
const res = a1.concat(a2).reduce((acc, x) => {
acc[x[key]] = Object.assign(acc[x[key]] || {}, x);
return acc;
}, {});
return Object.entries(res).map(pair => {
const [, value] = pair;
return value;
});
}
}
test("Merge", async () => {
const a1 = [{ id: "1", value: "1" }, { id: "2", value: "2" }];
const a2 = [{ id: "2", value: "3" }];
expect(Merge.byKey(a1, a2, "id")).toStrictEqual([
{
id: "1",
value: "1"
},
{ id: "2", value: "3" }
]);
});
try this
var a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
var a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]
let arr3 = a1.map((item, i) => Object.assign({}, item, a2[i]));
console.log(arr3);
How about this?
const mergeArrayObjects = (arr1: any[], arr2: any[], mergeByKey: string): any[] => {
const updatedArr = [];
for (const obj of arr1) {
const arr1ValueInArr2 = arr2.find(
a => a[mergeByKey] === obj[mergeByKey],
);
if (arr1ValueInArr2) {
updatedArr.push(Object.assign(obj, arr1ValueInArr2));
} else {
updatedArr.push(obj);
}
}
const mergeByKeyValuesInArr1 = arr1.map(a => a[mergeByKey]);
const remainingObjInArr2 = arr2.filter(a => !mergeByKeyValuesInArr1.includes(a[mergeByKey]) )
return updatedArr.concat(remainingObjInArr2)
}
You can write a simple object merging function like this
function mergeObject(cake, icing) {
var icedCake = {}, ingredient;
for (ingredient in cake)
icedCake[ingredient] = cake[ingredient];
for (ingredient in icing)
icedCake[ingredient] = icing[ingredient];
return icedCake;
}
Next, you need to do use a double-loop to apply it to your data structre
var i, j, a3 = a1.slice();
for (i = 0; i < a2.length; ++i) // for each item in a2
for (j = 0; i < a3.length; ++i) // look at items in other array
if (a2[i]['id'] === a3[j]['id']) // if matching id
a3[j] = mergeObject(a3[j], a2[i]); // merge
You can also use mergeObject as a simple clone, too, by passing one parameter as an empty object.
const a3 = a1.map(it1 => {
it1.test = a2.find(it2 => it2.id === it1.id).test
return it1
})
If you have exactly the same number of items in both array with same ids you could do something like this.
const mergedArr = arr1.map((item, i) => {
if (item.ID === arr2[i].ID) {
return Object.assign({}, item, arr2[i]);
}
});
function mergeDiffs(Schedulearray1, Schedulearray2) {
var secondArrayIDs = Schedulearray2.map(x=> x.scheduleid);
return Schedulearray1.filter(x=> !secondArrayIDs.includes(x.scheduleid)).concat(Schedulearray2);
}
None of them worked for me. I wrote own:
const formatteddata=data.reduce((a1,a2)=>{
for (let t=0; t<a1.length; t++)
{var id1=a1[t].id
for (let tt=0; tt<a2.length; tt++)
{var id2=a2[tt].id
if(id1==date2)
{a1[t]={...a1[t],...a2[tt]}}
}
}
return a1
})
works with any amount of arrays of objects in arrays, with varying length and not always coinsciding dates

Categories

Resources