Let's say I have an array of id's
const ids = [[1, 2, 3], [2, 3], [3]]
And have array with objects that have name for each id
const obj = [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
];
What is the most proper way to get ids = [["One", "Two", "Three"], ["Two", "Three"], ["Three"]], I'm worrying that nested mapping could cause performance issues.
Use a combination of map and find. There may be a more performant way of doing it, but I'd suggest worrying about that only if you run into performance issues :-
const ids = [[1, 2, 3], [2, 3], [3]];
const obj = [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
];
const mapped = ids.map(arr => arr.map(id => obj.find(obj => obj.id === id).name));
const obj = [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
];
const ids = [[1, 2, 3], [2, 3], [3]] ;
// dedicated object to keep association between id and names
let names = {} ;
obj.forEach( o => {
names[ o.id ] = o.name ;
} ) ;
// map each sub array content to their real name
for( let i = 0; i <= ids.length-1; i++){
ids[i] = ids[i].map( id => names[id] ) ;
}
console.log( ids ) ;
=>
[ [ 'One', 'Two', 'Three' ], [ 'Two', 'Three' ], [ 'Three' ] ]
How can I sum vertically all data from an array of arrays?
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
var result = arrayOfArrays.reduce(function(array1, array2) {
return array1.data.map(function(value, index) {
return value + array2.data[index];
}, 0);
});
console.log(result)
The output should be the vertical sum of arrays.
[3,6,9,12,15,18,21,24]
The problem is that array1 return always as undefined.
You code is almost correct but with 1 issues.
You are looping on accumulator. This will be an array of number in second iteration. Instead loop over array2 or current item.
Idea of .reduce is to have same signature for all iteration. If you do not pass default value for accumulator, first iteration will be of type Array<{ label: string, data: Array<number>}> and second iteration will be just Array<number>. So you can skip behavior for first iteration by passing default value as []. Now the calculation will break as array[n] will be undefined. For this, you can use a default value of 0.
So your calculation will look like:
value + (array1[index] || 0)
Following is a sample:
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
var result = arrayOfArrays.reduce(function(array1, array2) {
return array2.data.map(function(value, index) {
return value + (array1[index] || 0);
}, 0);
}, []);
console.log(result)
Use the index/key of map and add to the previous value.
const arrayOfArrays = [{label:'First Value', data:[1,2,3,4,5,6,7,8]},{label:'Second Value', data:[1,2,3,4,5,6,7,8]},{label:'Third Value', data:[1,2,3,4,5,6,7,8]}];
const res = arrayOfArrays.reduce((acc, cur) => (cur.data.map((i, k) => {acc[k] = acc[k] ? acc[k] += i : i}), acc), [])
console.log(res)
you're using reduce in a wrong way, but heres a for loop that does the same job:
arrayOfArrays = [{
label:'First Value', data:[1,2,3,4,5,6,7,8]},{
label:'Second Value', data:[1,2,3,4,5,6,7,8]},{
label:'Third Value', data:[1,2,3,4,5,6,7,8]
}];
const newArr = [];
for(let x = 0; x < arrayOfArrays[0].length; x++){
newArr.push(arrayOfArrays[0].data[x]+arrayOfArrays[1].data[x]+arrayOfArrays[2].data[x])
}
console.log(newArr); // new array
You can flatten the array by looping the array of objects and pushing the data property to a new array, then use reduce/map on the flattened data:
arrayOfArrays = [
{label:'First Value', data:[1,2,3,4,5,6,7,8]},
{label:'Second Value', data:[1,2,3,4,5,6,7,8]},
{label:'Third Value', data:[1,2,3,4,5,6,7,8]}
];
var data = [];
arrayOfArrays.forEach((element)=> {
data.push(element.data)
})
var sum = (r, a) => r.map((b, i) => a[i] + b);
var result = data.reduce(sum);
console.log(result);
Which outputs:
[3, 6, 9, 12, 15, 18, 21, 24]
Working fiddle
If you know that the length of each array is same. you can do as follows
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
let out = arrayOfArrays.reduce((acc, {data}) => acc.map((e, i) => e+data[i]), new Array(8).fill(0));
console.log(out)
You are passing the wrong accumulator which should be an array also in wrong place, it must be with reduce not with map
var result = arrayOfArrays.reduce(function (array1, array2) {
return array1.map(function (value, index) {
return value + array2.data[index];
});
}, Array(8).fill(0));
I would do it like this:
Introduce a helper transport function:
const transport = (arr) => arr[0].map((col, i) => arr.map(row => row[i]));
Get a proper matrix:
const matrix = arrayOfArrays.map(el => el.data)
Then the task becomes trivial:
const res = transport(matrix).map(arr => arr.reduce((x, y) => x + y))
// > (8) [3, 6, 9, 12, 15, 18, 21, 24]
You could take advantage of function generators in case you need to later transform or alterate the values, or just iterate them without needing the entire result set.
In this solution, a function generator is used and the logic applied is:
Get the array with the longest length (assuming length might change)
Get all the elements at index i from 0 to longest length and yield their sum.
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
/**
Sums elements of arrays inside the array vertically.
*/
function* sumVertically(arr) {
// Get the longest array.
const longestArrayLength = arr.sort(({length: l1}, {length: l2}) => l1 - l2)[0].length;
// Acquire all elements at index [i] of each array and sum them. Yield the sum.
for (let i = 0; i < longestArrayLength; i++) yield arr.map(e => e[i]).reduce((a,b) => a + b, 0);
}
const result = [...sumVertically(arrayOfArrays.map(i => i.data))];
console.log(result);
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)
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>
Let's say I have 3 items in a collection:
[
{
id: 'a',
items: [1, 2, 3]
}, {
id: 'b',
items: [4, 5, 6]
}, {
id: 'c',
items: [7, 8, 9]
}
]
On the JavaScript code side, all I have is an array [5, 2, 6, 4, 7, 8]. How would I compose my query to select only the 2nd object from the collection since my array has all the elements (4, 5 and 6) of its items array?
Using mongoDB Aggregation Set Operator you can filter your array. First find out intersection of given array with actual database array and after that used set equals method. check below query :
db.collectionName.aggregate({
"$project": {
"checkAllElem": {
"$setEquals": [{
"$setIntersection": ["$items", [5, 2, 6, 4, 7, 8]]
}, "$items"]
},
"items": 1
}
}, {
"$match": {
"checkAllElem": true
}
})