Mapping over array of objects and picking out values from unknown keys - javascript

Tallying which color has a greater value in each array element for data. Then push the higher valued color into an empty object, and/or increment that color by 1. Lastly sort the totals object highest to lowest in terms of the totals property values and return highest valued color
Struggling with how to map over this structure array since property keys are not uniform. Should I destructure it?
*I can redesign data structure as needed, and if it's easier to solve with a different design, please let me know!
data = [
{ orange: 4, green: 4},
{ green: 0, yellow: 0},
{ yellow: 1, orange: 4 },
{ blue: 2, green: 1 },
{ blue: 2, yellow: 1 },
{ green: 3, yellow: 2 },
{ green: 1, blue: 3},
{ green: 5, yellow: 2 },
]
```
```
totals = {
blue: 3,
green: 2,
orange: 1,
}
```
solution:
```
highValueColor = blue
```
// PSEUDOCODE
//map over the array => data.map()
//identify highest value between two elements => propA - propB
//check to see if the color's (key) in the element has already been added to totals object
//IF the key does not yet exist, create a property in the tally object with the color(key) and set its value to 1
//IF the key is already listed in tally object, increment its property value by 1 => ++
//sort totals object => Math.max()
//return highest value color
`

Not sure how much help this is, #hopzebordah answer seems fine except that it looks like it counts a colour when both colours have the same value. (e.g. { orange: 4, green: 4} gets counted as orange).
I added a version with map in the comments as you seemed to be interested in that, but I might have misunderstood what you were trying to achieve.
If you don't need the sorted object and only the highest value, then you probably don't need to sort the object first. Hopefully highest_value_unsort demonstrates this.
const data = [
{ orange: 4, green: 4},
{ green: 0, yellow: 0},
{ yellow: 1, orange: 4 },
{ blue: 2, green: 1 },
{ blue: 2, yellow: 1 },
{ green: 3, yellow: 2 },
{ green: 1, blue: 3},
{ green: 5, yellow: 2 },
];
const pick_color = (color_obj) => {
const [[color1, val1], [color2, val2]] = Object.entries(color_obj);
return val1 === val2 ?
null :
val1 > val2 ?
color1 :
color2;
};
const unsorted = {};
for(const color_obj of data) {
const color = pick_color(color_obj);
if(color) {
unsorted[color] = (unsorted[color] ?? 0) + 1;
}
}
// version of the above using reduce:
// const unsorted = data.reduce((acc, val) => {
// const color = pick_color(val);
//
// return !color ?
// acc :
// { ...acc, [color]: (acc[color] ?? 0) + 1 };
// }, {});
// version of the above using map then reduce:
// const unsorted = data
// .map(pick_color)
// .reduce(
// (acc, color) => !color ?
// acc :
// { ...acc, [color]: (acc[color] ?? 0) + 1 },
// {}
// );
const sorted = Object.fromEntries(
Object.entries(unsorted)
.sort(([, a_val], [, b_val]) => b_val - a_val)
);
const highest_value = Object.entries(sorted)[0][0];
const highest_value_unsort = Object.entries(unsorted)
.reduce(
(acc, entry) => entry[1] > acc[1] ? entry : acc,
['none', 0]
)[0];
console.log(sorted);
console.log(highest_value);
console.log(highest_value_unsort);
Some reference links in case you're not familiar with some of the features used above:
reduce
Nullish coalescing operator (??)
Spread syntax

You're in luck as you are using JS!
It's super easy (or loosey-goosey, depending on your personal preference) to set/get data inside of JS objects using their keys using the { [someVariable]: value } notation. You can also check for existence of a key inside of an object using the in operator, like so:
const obj = { red: 'foo' };
const red = 'red';
console.log(red in obj) // true
console.log('red' in obj) // true
console.log(blue in obj) // false
So, combining that with a couple simple loops we can get this:
const data = [
{ orange: 4, green: 4},
{ green: 0, yellow: 0},
{ yellow: 1, orange: 4 },
{ blue: 2, green: 1 },
{ blue: 2, yellow: 1 },
{ green: 3, yellow: 2 },
{ green: 1, blue: 3},
{ green: 5, yellow: 2 },
];
const totals = {};
for (const colors of data) {
const [color1, color2] = Object.keys(colors);
let color = color1;
if (colors[color1] < colors[color2]) {
color = color2
}
totals[color] = totals[color] ? totals[color] + 1 : 1;
}
console.log(totals) // { orange: 2, green: 3, blue: 3 }
This isn't a performant solution by any means, but it is mainly held back by the structure of your data and needing to iterate over every value in order to check each key and its corresponding value.
Objects are very powerful in JS and form the basis of its flexibility. You may be able to leverage this to get a faster solution if you are eventually held back by performance issues depending on the size of the dataset.

Related

Passing arguments in a nested map anonymous function

I am looking to how you can pass arguments into a nested anonymous map function.
So in a function like the below, the values of red, green, blue in the nested map are hardcoded.
How can parameters be passed into the nested map?
function myFunction() {
const spreadsheetId = "###"; // Please set your Spreadsheet ID.
const grid = { sheetId: 0, startRow: 0, startCol: 0 }; // Please set your gridrange.
const values = [["sample value1", "sample value2"], ["sample value3", "sample value4"]]; // Please set your values as 2 dimensional array.
const request = [{
updateCells: {
range: {
sheetId: grid.sheetId,
startRowIndex: grid.startRow,
startColumnIndex: grid.startCol,
},
rows: values.map(r => ({
values: r.map(c => ({
** userEnteredFormat: { backgroundColor: { red: 1, green: 0.4, blue: 0.4 } }**,
userEnteredValue: { stringValue: c }
}))
})),
fields: "userEnteredFormat,userEnteredValue"
}
}];
Sheets.Spreadsheets.batchUpdate({ requests: request }, spreadsheetId);
}
Thank you in advance
In your situation, how about the following modification?
Modified script:
function myFunctionb() {
const spreadsheetId = "###"; // Please set your Spreadsheet ID.
const grid = { sheetId: 0, startRow: 0, startCol: 0 }; // Please set your gridrange.
const values = [["sample value1", "sample value2"], ["sample value3", "sample value4"]]; // Please set your values as 2 dimensional array.
const backgroundColors = [[[1, 0.4, 0.4], [0.4, 1, 0.4]], [[0.4, 0.4, 1], [0.4, 0.4, 0.4]]]; // Please set your background colors as 2 dimensional array.
const request = [{
updateCells: {
range: {
sheetId: grid.sheetId,
startRowIndex: grid.startRow,
startColumnIndex: grid.startCol,
},
rows: values.map((r, i) => ({
values: r.map((c, j) => ({
userEnteredFormat: { backgroundColor: { red: backgroundColors[i][j][0], green: backgroundColors[i][j][1], blue: backgroundColors[i][j][2] } },
userEnteredValue: { stringValue: c }
}))
})),
fields: "userEnteredFormat,userEnteredValue"
}
}];
Sheets.Spreadsheets.batchUpdate({ requests: request }, spreadsheetId);
}
When this script is run, 4 values are put from cell "A1" and also 4 different background colors are set from cell "A1".

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; }

Javascript - Compare array of objects and sum coincident values

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));

Find any value in any level of an array of nested objects?

I've got an array of nested objects
var arr = [{
tires: 2,
exterior: {
color: 'white',
length: 2,
width: 1
}
},{
tires: 4,
exterior: {
color: 'blue',
length: 5,
width: 3
}
},{
tires: 4,
exterior: {
color: 'white',
length: 2,
width: 3
}
}];
I want to create a function such that:
var findItems = function(arr, value){
// return array of found items
};
Some examples:
findItems(arr, 'white'); // [ arr[0], arr[2] ]
findItems(arr, 2); // [ arr[0], arr[2] ]
findItems(arr, {tires: 2}); // [ arr[0] ]
findItems(arr, {tires: 2, color: 'white'}); // [ ]
findItems(arr, {width: 1, color: 'white'}); // [ arr[0] ]
It's easy enough to find values for arrays with non-nested objects or if you know the exact level that you want to search in. But I'm not sure how to go about just finding "any value, anywhere" in an array. I quickly get into looping hell.
I can use Underscore if that helps.
I think it would be something like this:
function isObject(val) {
return Object(val) === val;
}
function search(val, ctx) {
var valIsObject = isObject(val);
return (function search(context) {
if(!isObject(context)) return val === context;
if(valIsObject && Object.keys(val).every(function(key) {
return context[key] === val[key];
})) return true;
return Object.keys(context).some(function(key) {
return search(context[key]);
});
})(ctx);
}
function findItems(arr, value){
return arr.filter(function(item) {
return search(value, item);
});
}
It should work reasonably well, except in some edge cases like circular references (infinite recursion) or descriptor properties (the code may call the getter on an incompatible object).

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