Javascript - Compare array of objects and sum coincident values - javascript

I have this array of objects:
arr=[
{a: 1,b: 0,x: 100},
{a: 0,b: 0,x: 100},
{a: 1,b: 1,x: 100},
{a: 1,b: 0,x: 200},
{a: 1,b: 1,x: 200},
....
]
Now, what I need to do is to compare "x" values and if they coincide, tranfer summed "a" and "b" values in another array. For example:
arr2=[{a=2,b=1,x=100},{a=2,b=1,x=200}....]
Second thing to do, is to count how many objects are been joined with the same "x" value. For example in the first "arr2" object are joined 3 "arr" objects and in the second "arr2" object are joined 2 "arr" objects and so on.

This question doesn't seem to make sense. First of all, the word "coincident" doesn't have a technical definition that I'm aware of. Can you be more clear about what you mean?
Secondly its not clear what your expected results are. Perhaps what I would do if I was you would be to start with some simple example inputs and then come up with what you would expect the corresponding output to be, then use a unit testing tool to develop the code to do what you want.
For example, I have to guess what you are wanting but it might look like this (in javascript) using the libraries chai and mocha:
import { expect } from 'chai'
function doWork(input) {
// code goes here
}
const tests = [
{
name: 'Same x values coalesce',
data: [
{a=1,b=0,x=100},
{a=0,b=0,x=100}
],
expected: {
100: [1, 0]
}
}
]
describe('work', () => {
tests.forEach(test => {
it(t.name, () => {
const result = doWork(test.data)
expect(result).to.deep.equal(test.expected)
})
})
})
This technique may help you come to to an answer on your own.
Try to be a little more precise with your terms and give an example of what you are expecting to get as output.

Array reduce and map are fun methods. Object.keys() will give you an array of keys and that lets you do more array reducing and mapping. Fun times.
let arr = [
{ a: 1, b: 0, x: 100 },
{ a: 0, b: 0, x: 100 },
{ a: 1, b: 1, x: 100 },
{ a: 1, b: 0, x: 200 },
{ a: 1, b: 1, x: 200 }
];
let lists = arr.reduce((prev, curr) => {
let list = prev[curr.x] || [];
list.push(curr);
prev[curr.x] = list;
return prev;
}, {});
console.log(lists);
let flat = Object.keys(lists).reduce((prev, curr) => {
prev.push(
lists[curr].reduce(
(prev, curr) => {
prev.a += curr.a;
prev.b += curr.b;
prev.x = curr.x;
return prev;
},
{ a: 0, b: 0 }
)
);
return prev;
}, []);
console.log(flat);
Object.keys(lists).forEach(el => console.log(el + ': ' + lists[el].length));

Related

Query an array of objects and return the target

I need to find an object in an array by a key and value
I'm trying to do a search on an array of objects with for in. but I can not.
My input:
findObject[{ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } }, { c: 3 }] //the last argument ({ c: 3 }) is the target
What I'm trying to return:
{ a: 1, b: { c: 3 } }
Note: The array of objects can have any object and the target too
You can use Array.find to find the item in the array whose values contain an object whose c property is 3:
const arr = [{ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } }, { c: 3 }]
const result = arr.find(e => Object.values(e).some(k => k.c == 3))
console.log(result)
What the OP is looking for is a find using deep equality (keys and values, including nested keys and values are equal). Even shallow equality is a deep topic in JS and applying it recursively is even deeper.
A fast but flawed idea is comparing JSON encodings for the two objects being compared. I wave a hand at that in the snippet, but prefer to use a utility that has thought through edge cases and probably executes fast.
function isEqual(a, b) {
// not good, but quick to code:
// return JSON.stringify(a) === JSON.stringify(b)
// lodash has put some thought into it
return _.isEqual(a, b)
}
// find the first object in array with a value deeply equal to object
function findObject(array, object) {
return array.find(el => {
return Object.values(el).some(v => isEqual(v, object))
})
}
let array = [{ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3, d: { e: "hi" }} }];
let object = { c: 3, d: { e: "hi" }};
let result = findObject(array, object);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
The OP asks to find the last object of an array as the target in the array up to that point. Adopt as follows...
// array is the array to search plus the last item is the thing to search for
let firstObjects = array.slice(0, -1)
let lastObject = array.at(-1)
let result = findObject(firstObjects, lastObject)

Group and sum of an array of objects based on number key

I am trying to create a statistical pie chart. As a http response i am getting a list from server using which i need to draw a pie chart.
For example: Data received:
[{1: 9, 2: 7}, {3:8, 2: 1}, {1:8, 5:9}, {2:3, 3:1}]
This is the desired output:
[{x: 1, y: 17}, {x: 2, y:10}, {x: 3, y: 9}, {x: 5, y: 9}]
Please note: x is the key and y is sum of similar key values
I have tried data.forEach((item, index) => {}). After writing this, I am actually getting no lead about how I can combine Object.keys(item), Object.values(item) and Object.values(item).reduce((a,b)=> return a+b;)
This may sound silly question, but any help would be appreciated. :)
You could reduce the array. Create an accumulator object with each number as key and and object with x and y keys as it's value. Loop through each object and update the y value based on the number. Then use Object.values() on the object returned to get the values of the accumulator as an array
const input = [{1: 9, 2: 7}, {3:8, 2: 1}, {1:8, 5:9}, {2:3, 3:1}]
const grouped = input.reduce((acc, obj) => {
for (const x in obj) {
acc[x] = acc[x] || { x , y: 0 }
acc[x].y += obj[x]
}
return acc;
}, {})
console.log(Object.values(grouped))
You could look for same key and update or insert a new object.
This approach does not change the order of the keys.
var data = [{ 1: 9, 2: 7 }, { 3: 8, 2: 1 }, { 1: 8, 5: 9 }, { 2: 3, 3: 1 }] ,
result = data.reduce((r, o) => {
Object.entries(o).forEach(([x, y]) => {
var temp = r.find(o => o.x === x);
if (temp) temp.y += y;
else r.push({ x, y });
});
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Is it possible to change the order of parents/childs in an object with items?

I have an object that looks like this:
sum = {
value:{
a: 10,
b: 11
}
value2:{
a: 33,
c: 12
}
..
}
My object is more complex, the letters really contains objects with different values, but the key thing is that, I wish to loop through all letters that exist in every value and return objects like:
a:{
value: 10,
value2: 33
}
I could loop through the entire thing build a new object, but is there a more effient way of "flipping an objects order?
I only want to use the keys that are present in all value-objects, I currently get them like this, but that is not a requirement.
value = ['value', 'value2']
tags.forEach( (tag) =>
keys.push(Object.keys(sum[tag]))
)
matches = _.intersection.apply(_, keys);
matches.forEach( (match) => {
...
}
This solution features a reorganisation of the order of the properties.
grouped[kk][k] = sum[k][kk] for all elements
var sum = {
value: {
a: 10,
b: 11
},
value2: {
a: 33,
c: 12
}
},
grouped = {};
Object.keys(sum).forEach(function (k) {
Object.keys(sum[k]).forEach(function (kk) {
grouped[kk] = grouped[kk] || {};
grouped[kk][k] = sum[k][kk];
});
});
document.write('<pre>' + JSON.stringify(grouped, 0, 4) + '</pre>');

How can I use lodash/underscore to sort by multiple nested fields?

I want to do something like this:
var data = [
{
sortData: {a: 'a', b: 2}
},
{
sortData: {a: 'a', b: 1}
},
{
sortData: {a: 'b', b: 5}
},
{
sortData: {a: 'a', b: 3}
}
];
data = _.sortBy(data, ["sortData.a", "sortData.b"]);
_.map(data, function(element) {console.log(element.sortData.a + " " + element.sortData.b);});
And have it output this:
"a 1"
"a 2"
"a 3"
"b 5"
Unfortunately, this doesn't work and the array remains sorted in its original form. This would work if the fields weren't nested inside the sortData. How can I use lodash/underscore to sort an array of objects by more than one nested field?
I've turned this into a lodash feature request: https://github.com/lodash/lodash/issues/581
Update: See the comments below, this is not a good solution in most cases.
Someone kindly answered in the issue I created. Here's his answer, inlined:
_.sortBy(data, function(item) {
return [item.sortData.a, item.sortData.b];
});
I didn't realize that you're allowed to return an array from that function. The documentation doesn't mention that.
If you need to specify the sort direction, you can use _.orderBy with the array of functions syntax from Lodash 4.x:
_.orderBy(data, [
function (item) { return item.sortData.a; },
function (item) { return item.sortData.b; }
], ["asc", "desc"]);
This will sort first ascending by property a, and for objects that have the same value for property a, will sort them descending by property b.
It works as expected when the a and b properties have different types.
Here is a jsbin example using this syntax.
There is a _.sortByAll method in lodash version 3:
https://github.com/lodash/lodash/blob/3.10.1/doc/README.md#_sortbyallcollection-iteratees
Lodash version 4, it has been unified:
https://lodash.com/docs#sortBy
Other option would be to sort values yourself:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
function compareValues(v1, v2) {
return (v1 > v2)
? 1
: (v1 < v2 ? -1 : 0);
};
var data = [
{ a: 2, b: 1 },
{ a: 2, b: 2 },
{ a: 1, b: 3 }
];
data.sort(function (x, y) {
var result = compareValues(x.a, y.a);
return result === 0
? compareValues(x.b, y.b)
: result;
});
// data after sort:
// [
// { a: 1, b: 3 },
// { a: 2, b: 1 },
// { a: 2, b: 2 }
// ];
The awesome, simple way is:
_.sortBy(data, [function(item) {
return item.sortData.a;
}, function(item) {
return item.sortData.b;
}]);
I found it from check the source code of lodash, it always check the function one by one.
Hope that help.
With ES6 easy syntax and lodash
sortBy(item.sortData, (item) => (-item.a), (item) => (-item.b))
I think this could work in most cases with underscore:
var properties = ["sortData.a", "sortData.b"];
data = _.sortBy(data, function (d) {
var predicate = '';
for (var i = 0; i < properties.length; i++)
{
predicate += (i == properties.length - 1
? 'd.' + properties[i]
: 'd.' + properties[i] + ' + ')
}
return eval(predicate)
});
It works and you can see it in Plunker
If the problem is an integer is converted to a string, add zeroes before the integer to make it have the same length as the longest in the collection:
var maxLength = _.reduce(data, function(result, item) {
var bString = _.toString(item.sortData.b);
return result > bString.length ? result : bString.length;
}, 0);
_.sortBy(data, function(item) {
var bString = _.toString(item.sortData.b);
if(maxLength > bString.length) {
bString = [new Array(maxLength - bString.length + 1).join('0'), bString].join('');
}
return [item.sortData.a, bString];
});
I've found a good way to sort array by multiple nested fields.
const array = [
{id: '1', name: 'test', properties: { prop1: 'prop', prop2: 'prop'}},
{id: '2', name: 'test2', properties: { prop1: 'prop second', prop2: 'prop second'}}
]
I suggest to use 'sorters' object which will describe a key and sort order. It's comfortable to use it with some data table.
const sorters = {
'id': 'asc',
'properties_prop1': 'desc',//I'm describing nested fields with '_' symbol
}
dataSorted = orderBy(array, Object.keys(sorters).map(sorter => {
return (row) => {
if (sorter.includes('_')) { //checking for nested field
const value = row["properties"][sorter.split('_')[1]];
return value || null;
};
return row[sorter] || null;// checking for empty values
};
}), Object.values(sorters));
This function will sort an array with multiple nested fields, for the first arguments it takes an array to modify, seconds one it's actually an array of functions, each function have argument that actually an object from 'array' and return a value or null for sorting. Last argument of this function is 'sorting orders', each 'order' links with functions array by index. How the function looks like simple example after mapping:
orderBy(array, [(row) => row[key] || null, (row) => row[key] || null , (row) => row[key] || null] , ['asc', 'desc', 'asc'])
P.S. This code can be improved, but I would like to keep it like this for better understanding.

Changing the order of the Object keys....

var addObjectResponse = [{
'DateTimeTaken': '/Date(1301494335000-0400)/',
'Weight': 100909.090909091,
'Height': 182.88,
'SPO2': '222.00000',
'BloodPressureSystolic': 120,
'BloodPressureDiastolic': 80,
'BloodPressurePosition': 'Standing',
'VitalSite': 'Popliteal',
'Laterality': 'Right',
'CuffSize': 'XL',
'HeartRate': 111,
'HeartRateRegularity': 'Regular',
'Resprate': 111,
'Temperature': 36.6666666666667,
'TemperatureMethod': 'Oral',
'HeadCircumference': '',
}];
This is a sample object which i am getting from back end, now i want to change the order of the object. I don't want to sort by name or size... i just want to manually change the order...
If you create a new object from the first object (as the current accepted answer suggests) you will always need to know all of the properties in your object (a maintenance nightmare).
Use Object.assign() instead.
*This works in modern browsers -- not in IE or Edge <12.
const addObjectResponse = {
'DateTimeTaken': '/Date(1301494335000-0400)/',
'Weight': 100909.090909091,
'Height': 182.88,
'SPO2': '222.00000',
'BloodPressureSystolic': 120,
'BloodPressureDiastolic': 80,
'BloodPressurePosition': 'Standing',
'VitalSite': 'Popliteal',
'Laterality': 'Right',
'CuffSize': 'XL',
'HeartRate': 111, // <-----
'HeartRateRegularity': 'Regular', // <-----
'Resprate': 111,
'Temperature': 36.6666666666667,
'TemperatureMethod': 'Oral',
'HeadCircumference': '',
};
// Create an object which will serve as the order template
const objectOrder = {
'HeartRate': null,
'HeartRateRegularity': null,
}
const addObjectResource = Object.assign(objectOrder, addObjectResponse);
The two items you wanted to be ordered are in order, and the remaining properties are below them.
Now your object will look like this:
{
'HeartRate': 111, // <-----
'HeartRateRegularity': 'Regular', // <-----
'DateTimeTaken': '/Date(1301494335000-0400)/',
'Weight': 100909.090909091,
'Height': 182.88,
'SPO2': '222.00000',
'BloodPressureSystolic': 120,
'BloodPressureDiastolic': 80,
'BloodPressurePosition': 'Standing',
'VitalSite': 'Popliteal',
'Laterality': 'Right',
'CuffSize': 'XL',
'Resprate': 111,
'Temperature': 36.6666666666667,
'TemperatureMethod': 'Oral',
'HeadCircumference': '',
}
I wrote this small algorithm which allows to move keys, it's like jQuery .insertAfter() method. You have to provide:
//currentKey: the key you want to move
//afterKey: position to move-after the currentKey, null or '' if it must be in position [0]
//obj: object
function moveObjectElement(currentKey, afterKey, obj) {
var result = {};
var val = obj[currentKey];
delete obj[currentKey];
var next = -1;
var i = 0;
if(typeof afterKey == 'undefined' || afterKey == null) afterKey = '';
$.each(obj, function(k, v) {
if((afterKey == '' && i == 0) || next == 1) {
result[currentKey] = val;
next = 0;
}
if(k == afterKey) { next = 1; }
result[k] = v;
++i;
});
if(next == 1) {
result[currentKey] = val;
}
if(next !== -1) return result; else return obj;
}
Example:
var el = {a: 1, b: 3, c:8, d:2 }
el = moveObjectElement('d', '', el); // {d,a,b,c}
el = moveObjectElement('b', 'd', el); // {d,b,a,c}
You can't order JavaScript object key/value pairs. It's stored in its own internal format, so you should never rely on the order of that. In JS, everything is an Object, even an Array. So sometimes you can introduce bugs when using array notation and object notation together (for x in var)
I like the approved answer by Chamika Sandamal. Here's a simple function that uses their same logic with a little be of freedom to change the order as you need it.
function preferredOrder(obj, order) {
var newObject = {};
for(var i = 0; i < order.length; i++) {
if(obj.hasOwnProperty(order[i])) {
newObject[order[i]] = obj[order[i]];
}
}
return newObject;
}
You give it an object, and an array of the key names you want, and returns a new object of those properties arranged in that order.
var data = {
c: 50,
a: 25,
d: 10,
b: 30
};
data = preferredOrder(data, [
"a",
"b",
"c",
"d"
]);
console.log(data);
/*
data = {
a: 25,
b: 30,
c: 50,
d: 10
}
*/
I'm copying and pasting from a big JSON object into a CMS and a little bit of re-organizing of the source JSON into the same order as the fields in the CMS has saved my sanity.
2020 Update
The answer below was correct at time of writing in 2011. However, since ES6, enumeration order has been specified as part of the language. Here's a nice article summarising this: https://2ality.com/2015/10/property-traversal-order-es6.html
Original answer
Properties of an object in JavaScript do not have an order. There may appear to be an order in some browsers but the ECMAScript specification defines object property enumeration order as being implementation-specific so you should not assume one browser's behaviour will be the same as another's. Chrome, for example, does not use the same ordering as some other browsers: see this lengthy bug report for at least as much discussion of this issue as you could possibly want.
If you need a specific order, use an array, or two arrays (one for keys and one for values).
You can use Object.keys():
var obj = {
a: 1,
b: 2,
c: 4
};
var new_obj = {};
Object.keys(obj)
.sort(function(a, b) {
/** Insert your custom sorting function here */
return a - b;
})
.forEach(function(key) {
new_obj[key] = obj[key];
});
obj = new_obj;
if you want to manually reorder. simply create new object and assign values using old object.
var newObject= [{
'DateTimeTaken': addObjectResponse.DateTimeTaken,
'Weight': addObjectResponse.Weight,
'Height': addObjectResponse.Height,
'SPO2': addObjectResponse.SPO2
}];
If you do not want to create a new object, you can use the following code snippet.
function orderKey(obj, keyOrder) {
keyOrder.forEach((k) => {
const v = obj[k]
delete obj[k]
obj[k] = v
})
}
here is a codepen: https://codepen.io/zhangyanwei/pen/QJeRxB
I think that's not possible in JavaScript.
You can create an array which will contain the field names in your order and you can iterate through this array and fetch the fields from the actual object.
Just refer to the object keys in the order that you like:
aKeys = [
addObjectResponse[0].DateTimeTaken,
addObjectResponse[0].Weight,
addObjectResponse[0].Height,
...etc...
]
I wrote a quick function in TypeScript that takes 2 arguments. The first is the array of objects you want to change the keys of, the second is an array of strings that represent the order of keys you'd like returned.
type GenericObject = Record<string, any> | null;
const order:Function = (data: Array<GenericObject>, order: Array<string>): Array<GenericObject> => {
return data.map((node) => {
return order.reduce((runningValue, currentValue) => {
return Object.assign(runningValue, { [currentValue]: node?.[currentValue]});
}, {});
});
};
And here is an example of calling it:
const data: Array<GenericObject> = [
{ b: 1, a: 2},
{ b: 3, a: 4},
];
const orderIWant: Array<string> = ['a', 'b'];
const ordered: Array<GenericObject> = order(data, orderIWant);
console.log(ordered);
// [ { a: 2, b: 1 }, { a: 4, b: 3 } ]
function orderKeys(obj, keys){
const newObj = {};
for(let key of keys){
newObj[key] = obj[key];
}
return newObj;
}

Categories