How to efficiently build this array? - javascript

I have an array of objects, e.g.:
[
{ a: 3, b: 2, c: 5, d: 6, e: 8 },
{ a: 1, b: 5, c: 3, d: 1, e: 2 }
]
Now I want to transform this to an array that contains only the values of specific properties, but without the objects themselves. E.g., if I am interested in a, b, c, and d, the result should look like this:
[ 3, 2, 5, 6, 1, 5, 3, 1 ]
My current approach looks like this:
const result = _.flatten(data.map(item => [ item.a, item.b, item.c, item.d ]));
Is there a better (i.e., more efficient, and maybe even more readable) way to get the result?

What you have seems plenty readable to me, and is likely efficient enough. But you can make it more efficient by avoiding all those temporary arrays and not looping twice:
const result = [];
data.forEach(item => result.push(item.a, item.b, item.c, item.d));
Example:
const data = [
{ a: 3, b: 2, c: 5, d: 6, e: 8 },
{ a: 1, b: 5, c: 3, d: 1, e: 2 }
];
const result = [];
data.forEach(item => result.push(item.a, item.b, item.c, item.d));
console.log(result);
Some engines do push really efficiently, others not so much. If efficiency were a critical requirement, you'd want to experiment comparing that with this on your target environments:
const result = [];
let index = 0;
let n, l, item;
for (n = 0, l = data.length; n < l; ++n) {
item = data[n];
result[index++] = item.a;
result[index++] = item.b;
result[index++] = item.c;
result[index++] = item.d;
}
Three things to note there:
Pushing directly onto the array instead of using push.
Using a simple for loop instead of forEach. The absolute overhead of using forEach is so small as to be virtually non-existant from a human perspective, but the relative overhead of it in a tight loop is quite large.
Declaring i, l, and item outside the for loop. If we declared them within the for, they'd be recreated on each loop iteration, adding overhead. (ES2015's semantics for let declarations are powerful and useful, but in this particular case, we don't want the overhead.)
Example:
const data = [
{ a: 3, b: 2, c: 5, d: 6, e: 8 },
{ a: 1, b: 5, c: 3, d: 1, e: 2 }
];
const result = [];
let index = 0;
let n, l, item;
for (n = 0, l = data.length; n < l; ++n) {
item = data[n];
result[index++] = item.a;
result[index++] = item.b;
result[index++] = item.c;
result[index++] = item.d;
}
console.log(result);

You could reduce with mapped keys.
var array = [{ a: 3, b: 2, c: 5, d: 6, e: 8 }, { a: 1, b: 5, c: 3, d: 1, e: 2 }],
keys = ['a', 'b', 'c', 'd'],
result = array.reduce((r, a) => r.concat(keys.map(k => a[k])), []);
console.log(result);

You can Array.prototype.map() the desired keys from the objects in the array and merge all them with Function.prototype.apply() and Array.prototype.concat():
let data = [{ a: 3, b: 2, c: 5, d: 6, e: 8 }, { a: 1, b: 5, c: 3, d: 1, e: 2 }],
result = [].concat.apply([], data.map(i => [i.a, i.b, i.c, i.d]));
console.log(result);

In ES6, and making no claims for efficiency, you could write
const input = [
{ a: 3, b: 2, c: 5, d: 6, e: 8 },
{ a: 1, b: 5, c: 3, d: 1, e: 2 }
];
console.log([].concat(...input.map(({a, b, c, d}) => [a, b, c, d])));
If you're really interested in efficiency, nothing would beat
var result = [];
for (let i = 0; i < input.length; i++) {
const o = input[i];
result.push(o.a, o.b, o.c, o.d);
}
although as another answer points out, it would be worth benchmarking push against result[cnt++] = val.

You can use 2 nested forEach loops to push targeted values of each element of your input array. I propose here an function receiving as parameters initial array and properties targeted as an other array :
var inflate = (data, options) => {
var results = [];
data.forEach(x => options.forEach(y => results.push(x[y])));
return results;
}
console.log(inflate([{ a: 3, b: 2, c: 5, d: 6, e: 8 }, { a: 1, b: 5, c: 3, d: 1, e: 2 }] ,["a","b","c","d"]));

Related

Calculating sum of properties of multiple objects

So I have an array of objects with some varying number of properties (but the property names are known), for example:
let data = [{a: 10, b: 1, c:10},
{a: 17, b: 2, c:16},
{a: 23, b: 3, c:41}]
I need to construct an object that sums up the values in the respective properties, so in this example I'd need to construct an object {a: 50, b: 6, c:67}
I wrote the following function to do this:
calcTotalForDataProps(data, props) {
let summedData = {}
for (const prop of props) {
summedData[prop] = 0;
}
data.forEach((dataObj) => {
for (const prop of props) {
summedData[prop] += dataObj[prop];
}
});
return summedData;
}
And you call it like this:
calcTotalForDataProps(data, ['a', 'b', 'c']);
But I'm wondering if there's a much shorter way to write this with ES6?
You could map the wanted props with their new sums for getting an object as result.
function calcTotalForDataProps(data, props) {
return data.reduce((r, o) => Object
.fromEntries(props.map(k => [k, (r[k] || 0) + o[k]])
), {});
}
const data = [{ a: 10, b: 1, c: 10}, { a: 17, b: 2, c: 16 }, { a: 23, b: 3, c: 41 }]
console.log(calcTotalForDataProps(data, ['a', 'b', 'c']));
There's no need to iterate over the properties initially - you can create the property inside the other loop if it doesn't exist yet.
let data = [{a: 10, b: 1, c:10},
{a: 17, b: 2, c:16},
{a: 23, b: 3, c:41}]
const calcTotalForDataProps = (data, props) => {
const summedData = {};
for (const obj of data) {
for (const [prop, num] of Object.entries(obj)) {
summedData[prop] = (summedData[prop] || 0) + num;
}
}
return summedData;
}
console.log(calcTotalForDataProps(data));

Get an element object of an array from a key name

I'm parsing a csv fils to json with node-csvtojson and I got a JSONarray with the following code
csv({delimiter: ';'}).fromFile(path).then((jsonObj)=>{
data = jsonObj;
console.log(jsonObj);
})
with a csv like
a,b,c
A,B,C
1,2,3
1,B,C
I have got
[
{
a: A,
b: B,
c: C,
},
{
a: 1,
b: 2,
c: 3,
},
{
a: 1,
b: B,
c: C
}
]
But I want to find every object who has the element a === 1 and I want to have all the content of the object,
like this:
{
a: 1,
b: 2,
c: 3,
},
{
a: 1,
b: B,
c: C,
}
But I 'm struggling to do that, I have tried with array.filter but without success then I have tried to do this with array.map but I got lost on how to do.
Do you have any idea on or I could do that ?
Than you
Use Array.filter like so:
const data = [{
a: 'A',
b: 'B',
c: 'C',
},
{
a: 1,
b: 2,
c: 3,
},
{
a: 1,
b: 'B',
c: 'C'
}
];
console.log(data.filter(({ a }) => a == 1));
If you want this to work with old browsers, here's an ES5-compliant version:
var data = [{
a: 'A',
b: 'B',
c: 'C',
},
{
a: 1,
b: 2,
c: 3,
},
{
a: 1,
b: 'B',
c: 'C'
}
];
console.log(data.filter(function(obj) {
return obj.a == 1
}));
Simple use Array.filter to filter through the object array and select the one having property a === 1
var arr = [{"a":"A","b":"B","c":"C"},{"a":1,"b":2,"c":3},{"a":1,"b":"B","c":"C"}];
const filteredArr = arr.filter(obj => obj.a === 1);
console.log(filteredArr);
Using Array.reduce you can do the same thing:
var arr = [{"a":"A","b":"B","c":"C"},{"a":1,"b":2,"c":3},{"a":1,"b":"B","c":"C"}];
const redArr = arr.reduce((acc, obj) => {
return acc = obj.a === 1 ? acc.concat(obj) : acc;
}, []);
console.log(redArr);
Using Array.map for this problem is not the right approach, although it is possible:
var arr = [{"a":"A","b":"B","c":"C"},{"a":1,"b":2,"c":3},{"a":1,"b":"B","c":"C"}];
const mapArr = arr.map(obj => obj.a === 1 ? obj : undefined).filter(obj => obj); //hack to remove undefined elements
console.log(mapArr);
console.log([{
a: 'A',
b: 'B',
c: 'C',
},
{
a: 1,
b: 2,
c: 3,
},
{
a: 1,
b: 'B',
c: 'C'
}
].filter(o => o.a === 1))
Try this :
var arr = [{"a":"A","b":"B","c":"C"},{"a":1,"b":2,"c":3},{"a":1,"b":"B","c":"C"}];
var res = arr.filter(obj => obj.a === 1);
console.log(res);

array.push(object) not pushed at the end of the loop in javascript

var temparray1 = [[1,3,4],[5,6,7],[8,9,10]];
var final = [];
var obj = {};
for(var temp in temparray1){
for(var test in temparray1[temp]){
obj.b = temparray1[temp][0];
obj.c = temparray1[temp][1];
obj.d = temparray1[temp][2];
}
console.log(obj);
final.push(obj);
}
current output
[{ b: 8, c: 9, d: 10 }
{ b: 8, c: 9, d: 10 }
{ b: 8, c: 9, d: 10 }]
expected Out put:
[{ b: 1, c: 3, d: 4 }
{ b: 5, c: 6, d: 7 }
{ b: 8, c: 9, d: 10 }]
i am running my javascript in node.js -v 8.1.x server
in the console at the end of the for loop it prints the required out put but not in the array push
var temparray = [[1, 3, 4], [5, 6, 7], [8, 9, 10]];
const final = temparray.map(a => ({ b: a[0], c: a[1], d: a[2] }));
console.log(final);
Probably, you set obj outside the for loop, therefore the props are overwritten and you push the same object multiple times into the array. Simply move the obj declaration into the loop. And you probably just need the outer loop.
Btw much shorter:
let final = temparray1.map(
([b,c,d]) => ({b,c,d})
);
You could use Array#map and return an object with the wanted properties.
var array = [[1, 3, 4], [5, 6, 7], [8, 9, 10]],
result = array.map(function (a) {
return { b: a[0], c: a[1], d: a[2] };
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
With ES6, you could map the inner arrays as well with an array for the keys with Object.assign and spread syntax ....
var array = [[1, 3, 4], [5, 6, 7], [8, 9, 10]],
keys = ['b', 'c', 'd'],
result = array.map(a => Object.assign(...keys.map((k, i) => ({ [k]: a[i] }))));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here is what you wanted
var temparray1 = [[1,3,4],[5,6,7],[8,9,10]];
var final = [];
for(var temp in temparray1){
var obj = {};
obj['b'] = temparray1[temp][0];
obj['c'] = temparray1[temp][1];
obj['d'] = temparray1[temp][2];
final.push(obj);
}
console.log(final);
Hope this helps!

convert string to object with number of occurances

I have tried to write a function to do this:
return the number of occurances of each letter of a string in an object case insensitive
// eg. numOfOccurances('This is great') => { t: 2, h: 1, i: 2, s: 2, g: 1, r: 1, e: 1, a: 1 }
function numOfOccurances(string) {
const stringLower = string.replace(/ /g, '').toLocaleLowerCase();
const counts = {};
let ch;
let count;
let i;
for (i = 0; i < stringLower.length; i++) {
ch = stringLower.charAt(i);
count = counts[ch];
counts[ch] = count ? count + 1: 1;
}
console.log(stringLower);
console.log(counts);
}
(it currently outputs console so i can see the output).
the output i get is:
Object {t: 2, h: 1, i: 2, s: 2, g: 1…}
a:1
e:1
g:1
h:1
i:2
r:1
s:2
t:2
You can do a simple reduce on the array of characters
const toLowerCase = str =>
str.toLowerCase()
const numOccurrences = str =>
Array.from(str, toLowerCase).reduce((acc, c) =>
Object.assign(acc, { [c]: c in acc ? acc[c] + 1 : 1 }), {})
console.log(numOccurrences('This is great'))
// { t: 2, h: 1, i: 2, s: 2, ' ': 2, g: 1, r: 1, e: 1, a: 1 }
What you're getting should be the expected result. I get the same thing in chrome devtools.
Object {t: 2, h: 1, i: 2, s: 2, g: 1…} Objects with many properties get abbreviated, thus the ellipsis. You are then able to click to expand the object and view the full list of properties.

Return an Array of Arrays containing objects that share a common value in a property

Say I have an array of 3 objects like this:
[
{
a: 4,
b: 5,
c: 4
},
{
a: 3,
b: 5,
c: 6
},
{
a: 2,
b: 3,
c: 3
}
]
I would like to return an array of arrays containing the objects that share a common value for the property b. So the resulting array would contain only one array containing 2 objects like this:
[
[
{
a: 4,
b: 5,
c: 4
},
{
a: 3,
b: 5,
c: 6
}
]
]
How would I do this?
You could do this with map and filter
var data = [{"a":4,"b":5,"c":4},{"a":3,"b":5,"c":6},{"a":2,"b":3,"c":3}];
var check = data.map(e => {return e.b});
var result = [data.filter(e => { return check.indexOf(e.b) != check.lastIndexOf(e.b)})];
console.log(result)
To group multiple objects in separate arrays with same b values you can use map and forEach
var data = [{"a":4,"b":5,"c":4},{"a":3,"b":5,"c":6},{"a":2,"b":3,"c":3}, {"a":3,"b":7,"c":6},{"a":2,"b":7,"c":3}], result = [];
var check = data.map(e => {return e.b});
data.forEach(function(e) {
if(check.indexOf(e.b) != check.lastIndexOf(e.b) && !this[e.b]) {
this[e.b] = [];
result.push(this[e.b]);
}
(this[e.b] || []).push(e);
}, {});
console.log(result)
This proposal uses a single loop with Array#forEach but without Array#indexOf.
var array = [{ a: 4, b: 5, c: 4 }, { a: 3, b: 5, c: 6 }, { a: 2, b: 3, c: 3 }],
grouped = [];
array.forEach(function (a) {
this[a.b] = this[a.b] || [];
this[a.b].push(a);
this[a.b].length === 2 && grouped.push(this[a.b]);
}, Object.create(null));
console.log(grouped);
You can create a function that accepts fulfillment criteria and will return as many nested arrays as rules passed.
Let's say you have an array of objects, arr.
var arr = [{a: 1, b: 2}, {a: 3, b: 2}, {a: 3, b: 4}, {a: 1, b: 1}]
And you want to return an array with with nested arrays that fulfill a particular requirement, let's say you want objects with an a:1 and b:2.
You can create a function that loops through your rules and creates a nested array with the objects that fulfill each rule.
For example:
var arr = [{a: 1, b: 2}, {a: 3, b: 2}, {a: 3, b: 4}, {a: 1, b: 1}]
function makeNestedArrays() {
var rules = [].slice.call(arguments);
return rules.reduce(function(acc, fn) {
var nestedArr = [];
arr.forEach(function(obj) {
if (fn(obj)) {
nestedArr.push(obj);
}
});
// only push nested array
// if there are matches
if (nestedArr.length) {
acc.push(nestedArr);
}
return acc;
}, []);
}
var result = makeNestedArrays(
function(obj) { return obj.a === 1; },
function(obj) { return obj.b === 2; }
);
console.log(result);
This allows you to pass as many "rules" as you want, and will create a nested array for each rule so long as there is at least one match.
You could use a Map to group them, this should work with any kind of value (just be sure the equality rules check out):
var arr = [{
a: 4,
b: 5,
c: 4
}, {
a: 3,
b: 5,
c: 6
}, {
a: 2,
b: 3,
c: 3
}];
var result = arr.reduce(function(m, o){
var value = o.b;
if(m.has(value)){
m.get(value).push(o);
} else {
m.set(value, [o]);
}
return m;
}, new Map());
console.log(...(result.values()));
If you'd need to filter out the groups of 1:
var arr = [{
a: 4,
b: 5,
c: 4
}, {
a: 3,
b: 5,
c: 6
}, {
a: 2,
b: 3,
c: 3
}];
var result = arr.reduce(function(m, o){
var value = o.b;
if(m.has(value)){
m.get(value).push(o);
} else {
m.set(value, [o]);
}
return m;
}, new Map());
result = [...result.values()].filter(a => a.length > 1);
console.log(result);

Categories

Resources