Replace properties of specific elements in object - javascript

here is the data:
const data = {
element6: {
col: 2,
row: 3,
text: "Col2, Row1"
},
element1: {
col: 1,
row: 1,
text: "Col1, Row1"
},
element8: {
col: 3,
row: 2,
text: "Col2, Row1"
},
element2: {
col: 1,
row: 2,
text: "Col1, Row2"
},
element5: {
col: 2,
row: 2,
text: "Col2, Row2"
},
element3: {
col: 1,
row: 3,
text: "Col1, Row3"
},
element4: {
col: 2,
row: 1,
text: "Col2, Row1"
},
element7: {
col: 3,
row: 1,
text: "Col2, Row1"
},
element9: {
col: 3,
row: 3,
text: "Col2, Row1"
},
};
What I want to do is to add a property ind (which equals to element's col value) and overwrite col (to the smallest value of selected) to all elements which are in the same row and different col.
For example, elements 2, 5 and 8 are in row: 2, so they would change to:
{
element8: {
col: 1,
row: 2,
text: "Col2, Row1",
ind: 3
},
element2: {
col: 1,
row: 2,
text: "Col1, Row2",
ind: 1
},
element5: {
col: 1,
row: 2,
text: "Col2, Row2",
ind: 2
},
}
Note, I don't want to change the order of object in data.
Thanks.

I think you can do it with a help of lodash (or any other helper library)
Assuming your data is object (by the way, the order of keys in object is not guaranteed!)
const res = _.chain(data)
.toPairs()
.groupBy(([key, val]) => val.row)
.mapValues(list => {
const smallestCol = _.minBy(list, ([key, val]) => val.col)[1].col
return list.map(([key, val]) => [key, {...val, ind: val.col, col: smallestCol}])
})
.values()
.flatten()
.fromPairs()
.value();
It may be worth noting that as of ES2019 almost all of those functions (except for groupBy and minBy) are available in JS standart library, though they do not chain so well
function groupBy(array, selector) {
return array.reduce((res,curr) => {
const key = selector(curr)
if({}.hasOwnProperty.call(res, key)) {
res[key].push(curr)
} else {
res[key] = [curr]
}
return res
}, {})
}
function minBy(array, selector) {
return Math.min(...array.map(selector))
}
const groupedbyRow = groupBy(Object.entries(data), ([key, element]) => element.row)
const remappedValues = Object.values(groupedbyRow).flatMap(list => {
const smallestCol = minBy(list, ([key, val]) => val.col)
return list.map(([key, val]) => [key, {...val, ind: val.col, col: smallestCol}])
})
const res = Object.fromEntries(remappedValues)

Use the for...in statement iterates, as #Ravenous mentioned
const smallColl = currentRow => {
let small;
for (let key in data) {
const { col, row } = data[key];
if (row !== currentRow) continue;
if (!small || col < small) small = col;
}
return small;
};
// another method
const smallColl = currentRow =>
Math.min(
...Object.values(data)
.filter(({ row }) => row === currentRow)
.map(({ col }) => col)
);
for (let key in data) {
const { col, row } = data[key];
data[key].ind = col;
data[key].col = smallColl(row);
}
for better performance, use memoization:
for (let key in data) {
const colMin = {};
const { col, row } = data[key];
data[key].ind = col;
const min = colMin[row] || smallColl(row);
data[key].col = min;
if (colMin[row]) colMin[row] = min;
}

Related

Dynamically adding more values in Javascript

How can I dynamically add more values in the sets defined as follows in Javascript:
var data1 = [
{ name: 'S1', elems: [0, 1, 2] },
{ name: 'S2', elems: [1, 2, 3] },
{ name: 'S3', elems: [0, 2, 4] },
];
What I want to do is similar to the following:
(key, value) = read_a_row_from_table_of_values;
if (key is present in data1) // could be by iterating all the keys in data
{
data1[key][elems].push(value); // this is the point of concern (how to push values)
}
As I am feeding this data to another tool (UpSet), so this data structure (in the form of name and elems) has to be followed. Details about that can be found here.
If you're only ever adding 1 or 2 items (i.e. such that O(n) insert time is acceptable) then this solution (based on #mplungjan's now-deleted comment) should work for you:
var data1 = [
{ name: 'S1', elems: [0, 1, 2] },
{ name: 'S2', elems: [1, 2, 3] },
{ name: 'S3', elems: [0, 2, 4] },
];
function add( data1, name, value ) {
const exists = data1.find( e => e.name === name );
if( exists ) {
exists.elems.push( value );
}
else {
data1.push( { name, elems: [value] } );
}
}
add( data1, 'S4', 1 );
add( data1, 'S3', 2 );
add( data1, 'S5', 3 );
If you'll be performing mass operations on data1 then you should convert it to a Map first (note that Array.prototype.map and Map<K,V> are completely different things):
function convertData1ToMap( data1 ) {
const m = new Map();
for( const e of data1 ) {
m.set( e.name, e.elems );
}
return m;
}
function convertMapToData1( m ) {
const arr = [];
for( const k of map.keys() ) {
const elems = map.get( k );
arr.push( { name: k, elems } );
}
return arr;
}
//
const m = convertData1ToMap( data1 );
// Mass insert:
for( let i = 4; i < 10 * 1000; i++ ) {
const name = 'S' + i.toString();
if( m.has( name ) ) {
m.get( name ).push( i );
}
else {
m.set( name, [i]);
}
}
// Recreate `data1`:
data1 = convertMapToData1( m );
Entries and Find will work
Basically
data1.find(elem => elem.name===key).elems.push(value)
We can alas not do a oneliner if the newValues may contain a key not in the data1 array
const data1 = [
{ name: 'S1', elems: [0, 1, 2] },
{ name: 'S2', elems: [1, 2, 3] },
{ name: 'S3', elems: [0, 2, 4] },
];
const newValues = {"S1": 4,"S2":5, "S6": 6 }
Object.entries(newValues)
.forEach(([key, value]) => {
const found = data1.find(elem => elem.name === key);
if (found) found.elems.push(value);
});
console.log(data1);
If you want to merge, you need to add missing keys
const data1 = [
{ name: 'S1', elems: [0, 1, 2] },
{ name: 'S2', elems: [1, 2, 3] },
{ name: 'S3', elems: [0, 2, 4] },
];
const newValues = {"S1": 4,"S2":5, "S6": 6 }
Object.entries(newValues)
.forEach(([key, value]) => {
const found = data1.find(elem => elem.name === key);
if (found) found.elems.push(value);
else data1.push({name:key,elems: [value]});
})
console.log(data1);

Flatten JavaScript nested object

I have a nested object look like this:
let obj = {
F:{
asian: {
"35-44": 1,
"55-": 1,
},
"asian/black": {
"0-24": 1,
"35-44": 1,
"45-54": 2,
},
},
M:{
asian: {
"35-44": 1,
"55-": 1,
},
white: {
"0-24": 1,
"35-44": 1,
"45-54": 2,
},
},
}
And I want to flatten the object to this:
res = {
F: 6,
M: 6,
asian: 4,
"asian/black": 4,
white: 4,
"0-24": 2,
"35-44": 4,
"45-54": 4,
"55-": 2,
}
That every value in res should be the sum of the deepest object values(F, M) and object values with the same key(0-24, 35-44...). I feel this can be done using recursion and just can't get it right. The code I write:
let returnVal = 0
const flatten = (obj, prefix = '', res = {}) => {
return Object.entries(obj).reduce((r, [key, val]) => {
if(typeof val === 'object'){
flatten(val, key, r)
} else {
res[key] = val
returnVal = val;
}
if (key in res) {
res[key] += returnVal
} else {
res[key] = 0
res[key] += returnVal
}
return r
}, res)
}
console.log(flatten(obj))
it will output:
result = {
"0-24": 2,
"35-44": 2,
"45-54": 4,
"55-": 2,
F: 2,
M: 2,
asian: 2,
"asian/black": 2,
white: 2,
}
F, M, and some other keys are not correct. Thanks!
Another, perhaps simpler, approach is as follows:
const consolidate = (obj, path = [], results = {}) =>
Object .entries (obj) .reduce ((results, [k, v]) =>
Object (v) === v
? consolidate (v, [...path, k], results)
: [...path, k] .reduce (
(results, n) => ({...results, [n] : (results[n] || 0) + v}),
results
),
results)
const data = {F: {asian: {"35-44": 1, "55-": 1}, "asian/black": {"0-24": 1, "35-44": 1, "45-54": 2}}, M: {asian: {"35-44": 1, "55-": 1}, white: {"0-24": 1, "35-44": 1, "45-54": 2}}}
console .log (consolidate (data))
.as-console-wrapper {min-height: 100% !important; top: 0}
We recursively track paths taken through the object, such as ['F', 'asian/black', '45-54'] or ['M', 'white'] or simply ['f'] as well as an object containing the final results. When we the value at the current node is an object, we recur, adding the current property name to the path. When it's not (for this data it must therefore hit a number), we hit a base case in which we take each node in the current path, and update the results object by adding that number to the value for the node in the results object, or setting it to the current value if that value doesn't exist.
There is a potential issue with the default parameters, as described in another Q & A. If someone tried to map the consolidate function directly over an array of input objects, it would fail. If this is a concern, it's easy enough to swap the default parameters for a wrapper function:
const _consolidate = (obj, path, results) =>
Object .entries (obj) .reduce ((results, [k, v]) =>
Object (v) === v
? _consolidate (v, [...path, k], results)
: [...path, k] .reduce (
(results, n) => ({...results, [n] : (results[n] || 0) + v}),
results
),
results)
const consolidate = (obj) =>
_consolidate (obj, [], {})
const data = {
F: {
asian: {
"35-44": 1,
"55-": 1,
},
"asian/black": {
"0-24": 1,
"35-44": 1,
"45-54": 2,
},
},
M: {
asian: {
"35-44": 1,
"55-": 1,
},
white: {
"0-24": 1,
"35-44": 1,
"45-54": 2,
},
},
};
const isObject = obj => Object.prototype.toString.call(obj) === "[object Object]";
function nestKeys(obj, parent = "") {
return Object.keys(obj).map(key => {
const k = parent.length ? [parent, key].join(".") : key;
if (!isObject(obj[key])) {
return k;
}
return nestKeys(obj[key], k);
}).flat();
}
function flatObj(obj) {
const map = {};
const keys = nestKeys(obj);
keys.forEach(nestedKey => {
const splited = nestedKey.split(".");
const val = splited.reduce((acc, cur) => acc[cur], obj);
splited.forEach(k => {
map[k] = (map[k] || 0) + val;
})
});
return map;
}
console.log(flatObj(data));

Javascript flatten deeply nested Array with objects and renaming properties

I'm stuck again with some flattening and renaming of the following.
What I got:
test = [
{
date: '2020-03-30',
station: {
id: 0,
name: 'some description'
},
firstValues: [
{
result: 1,
type: 4,
max: 18,
min: 1,
},
{
result: 2,
type: 5,
max: 15,
min: 2,
}
],
lastValues: [
{
result: 1,
type: 3,
max: 17,
min: 1
},
{
result: 2,
type: 8,
max: 20,
min: 2
}
],
iD: 'xxx3',
count: 1
},
{
next object with same structure
}
]
What I try to achieve:
test = [
{
date: '2020-03-30',
station: 'some description',
first_E01_result: 1,
first_E01_type: 4,
first_E01_max: 18,
first_E01_min: 1,
first_E02_result: 2,
first_E02_type: 5,
first_E02_max: 15,
first_E02_min: 2,
last_E01_result: 1,
last_E01_type: 3,
last_E01_max: 17,
last_E01_min: 1,
last_E02_result: 2,
last_E02_type: 8,
last_E02_max: 20,
last_E02_min: 2,
iD: 'xxx3',
count: 1
},
{
next object with same structure
}
]
I'm quite aware that my approach isn't the right thing. I tried different things so far but couldn't get it working. I'm totally stuck again to find the right way because I do run into two main issues:
How can I make the difference between first and last values? (switch case or if and if else?)
and
How can I access the name property from the station object and assign it to the key of "station"
Here is my last approach which is still missing the right code for the mentioned problems:
convertTest(input) {
return input.map(obj => {
const obj1 = {};
const obj2 = {};
for (const prop in obj) {
if (obj.hasOwnProperty(prop) && Array.isArray(obj[prop])) {
for (let i = 0; i < obj[prop].length; i++) {
for (const [key, value] of Object.entries(obj[prop][i])) {
const name = 'first_EO' + (i + 1).toString() + '_' + key;
obj2[name] = value;
}
}
} else {
obj1[prop] = obj[prop];
}
const dataconverted = Object.assign({}, obj1, obj2);
return dataconverted;
}
});
}
You could take a recursive approach for all other nested objects except the first level with special cases.
var data = [{ date: '2020-03-30', station: { id: 0, name: 'some description' }, firstValues: [{ result: 1, type: 4, max: 18, min: 1 }, { result: 2, type: 5, max: 15, min: 2 }], lastValues: [{ result: 1, type: 3, max: 17, min: 1 }, { result: 2, type: 8, max: 20, min: 2 }], iD: 'xxx3', count: 1 }],
getPath = object => Object.entries(object).reduce((r, [k, v], i) => {
if (v && typeof v === 'object') {
r.push(...getPath(v).map(([left, right]) => [(Array.isArray(object) ? 'E' + (i + 1).toString().padStart(2, 0) : k) + '_' + left, right]));
} else {
r.push([k, v]);
}
return r;
}, []),
result = data.map(o => Object.fromEntries(Object.entries(o).reduce((r, [k, v]) => {
if (k === 'station') {
r.push([k, v.name]);
} else if (v && typeof v === 'object') {
if (k.endsWith('Values')) k = k.slice(0, -6);
r.push(...getPath(v).map(([left, right]) => [k + '_' + left, right]));
} else {
r.push([k, v]);
}
return r
}, [])));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You should use Map and Object.keys
var test = [{"date":"2020-03-30","station":{"id":0,"name":"some description"},"firstValues":[{"result":1,"type":4,"max":18,"min":1},{"result":2,"type":5,"max":15,"min":2}],"lastValues":[{"result":1,"type":3,"max":17,"min":1},{"result":2,"type":8,"max":20,"min":2}],"iD":"xxx3","count":1}]
console.log(flatten(test));
function flatten(arr) {
return arr.map(el => ModifyObject(el))
}
function ModifyObject(el) {
const obj = {};
obj.date = el.date;
obj.iD = el.iD;
obj.count = el.count;
obj.station = el.station.name;
flattenObjectByProperty(obj, el, 'firstValues')
flattenObjectByProperty(obj, el, 'lastValues')
return obj;
}
function flattenObjectByProperty(obj, el, property) {
(el[property] || []).map((child, i) => {
Object.keys(child).forEach(key => {
obj[property + '_E' + i + '_' + key] = child[key]
});
});
}
Please try this.
test = test.map((elem) => {
elem.firstValues.forEach((child, index) => {
for(let key in child){
let v = `first_E${index+1}_${key}`
elem[v] = child[key];
}
})
elem.lastValues.forEach((child, index) => {
for(let key in child){
let v = `last_E${index+1}_${key}`
elem[v] = child[key];
}
})
elem['station'] = elem.station.name;
delete elem.firstValues;
delete elem.lastValues;
return elem;
})
You can use Array.prototype.reduce to flatten as per your requirement
const test = [
{
date: '2020-03-30',
station: {
id: 0,
name: 'some description'
},
firstValues: [
{
result: 1,
type: 4,
max: 18,
min: 1,
},
{
result: 2,
type: 5,
max: 15,
min: 2,
}
],
lastValues: [
{
result: 1,
type: 3,
max: 17,
min: 1
},
{
result: 2,
type: 8,
max: 20,
min: 2
}
],
iD: 'xxx3',
count: 1
}
];
const result = test.reduce((acc, curr) => {
const { firstValues, lastValues, ...rest } = curr;
const modifiedFirstValues = firstValues.reduce((r, c, i) => {
Object.entries(c).forEach(([key, value]) => {
const modifiedKey = `first_E${i + 1}_${key}`;
r[modifiedKey] = value;
});
return r;
}, Object.create(null));
const modifiedLastValues = lastValues.reduce((r, c, i) => {
Object.entries(c).forEach(([key, value]) => {
const modifiedKey = `last_E${i + 1}_${key}`;
r[modifiedKey] = value;
});
return r;
}, Object.create(null));
const finalObj = {
...rest,
...modifiedFirstValues,
...modifiedLastValues
};
acc.push(finalObj);
return acc;
}, []);
console.log(result);

sum values in object if multiple keys are the same JS

for example i have 5 objects:
{ row: aa, col: 1, value: 1 }
{ row: bb, col: 2, value: 1 }
{ row: bb, col: 3, value: 1 }
{ row: aa, col: 1, value: 1 }
{ row: aa, col: 2, value: 1 }
i want to sum values if row and col are the same, so the output should be:
{ row: aa, col: 1, value: 2 }
{ row: bb, col: 2, value: 1 }
{ row: bb, col: 3, value: 1 }
{ row: aa, col: 2, value: 1 }
thank you for your help!
tried this:
Sum javascript object propertyA values with same object propertyB in array of objects
You can do this with reduce() and one object to store keys.
var data = [
{ row: 'aa', col: 1, value: 1 },
{ row: 'bb', col: 2, value: 1 },
{ row: 'bb', col: 3, value: 1 },
{ row: 'aa', col: 1, value: 1 },
{ row: 'aa', col: 2, value: 1 }
]
var o = {}
var result = data.reduce(function(r, e) {
var key = e.row + '|' + e.col;
if (!o[key]) {
o[key] = e;
r.push(o[key]);
} else {
o[key].value += e.value;
}
return r;
}, []);
console.log(result)
Just for completeness, with a version for variable keys, an object for grouping the parts and Array#forEach.
var data = [{ row: 'aa', col: 1, value: 1 }, { row: 'bb', col: 2, value: 1 }, { row: 'bb', col: 3, value: 1 }, { row: 'aa', col: 1, value: 1 }, { row: 'aa', col: 2, value: 1 }],
grouped = [];
data.forEach(function (a) {
var key = ['row', 'col'].map(function (k) { return a[k]; }).join('|');
if (!this[key]) {
this[key] = { row: a.row, col: a.col, value: 0 };
grouped.push(this[key]);
}
this[key].value += a.value;
}, Object.create(null));
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
What I would do is put your objects in an array then iterate over that and check on each iteration if the key of a new object matches that of an old one and load the objects into a separate array if there isn't a match. If it does match then add its value to the value of the old own. I tested the following code and it seems to work how you want.
var array = [{ row: 'aa', col: 1, value: 1 },
{ row: 'bb', col: 2, value: 1 },
{ row: 'bb', col: 3, value: 1 },
{ row: 'aa', col: 1, value: 1 },
{ row: 'aa', col: 2, value: 1 }];
var newArray = [];
for(var x in array) {
for(var y in newArray) {
var found = false;
if(array[x].row == newArray[y].row && array[x].col == newArray[y].col) {
newArray[y].value += array[x].value;
found = true;
break;
}
}
if(!found) {
newArray.push(array[x]);
}
}
console.log(newArray);

Summarize array of objects and calculate average value for each unique object name

I have an array like so:
var array = [
{
name: "a",
value: 1
},
{
name: "a",
value: 2
},
{
name: "a",
value: 3
},
{
name: "b",
value: 0
},
{
name: "b",
value: 1
}
];
And I need an array like this:
var newarray = [
{
name: "a",
value: 2
},
{
name: "b",
value: 0.5
}
]
Where the new array has each unique name as an object with the average value.
Is there an easy way to accomplish this?
You'll have to loop through the array, computing the sum and counts for each object. Here's a quick implementation:
function average(arr) {
var sums = {}, counts = {}, results = [], name;
for (var i = 0; i < arr.length; i++) {
name = arr[i].name;
if (!(name in sums)) {
sums[name] = 0;
counts[name] = 0;
}
sums[name] += arr[i].value;
counts[name]++;
}
for(name in sums) {
results.push({ name: name, value: sums[name] / counts[name] });
}
return results;
}
Demonstration
Note, this kind of thing can be made much easier if you use a library like Underscore.js:
var averages = _.chain(array)
.groupBy('name')
.map(function(g, k) {
return {
name: k,
value: _.chain(g)
.pluck('value')
.reduce(function(x, y) { return x + y })
.value() / g.length
};
})
.value();
Demonstration
var array = [
{
name: "a",
value: 1
},
{
name: "a",
value: 2
},
{
name: "a",
value: 3
},
{
name: "b",
value: 0
},
{
name: "b",
value: 1
}
];
var sum = {};
for(var i = 0; i < array.length; i++) {
var ele = array[i];
if (!sum[ele.name]) {
sum[ele.name] = {};
sum[ele.name]["sum"] = 0;
sum[ele.name]["count"] = 0;
}
sum[ele.name]["sum"] += ele.value;
sum[ele.name]["count"]++;
}
var result = [];
for (var name in sum) {
result.push({name: name, value: sum[name]["sum"] / sum[name]["count"]});
}
console.log(result);
You can do it with Alasql library with one line of code:
var newArray = alasql('SELECT name, AVG([value]) AS [value] FROM ? GROUP BY name',
[array]);
Here I put "value" in square brackets, because VALUE is a keyword in SQL.
Try this example at jsFiddle
Here is a ES2015 version, using reduce
let arr = [
{ a: 1, b: 1 },
{ a: 2, b: 3 },
{ a: 6, b: 4 },
{ a: 2, b: 1 },
{ a: 8, b: 2 },
{ a: 0, b: 2 },
{ a: 4, b: 3 }
]
arr.reduce((a, b, index, self) => {
const keys = Object.keys(a)
let c = {}
keys.map((key) => {
c[key] = a[key] + b[key]
if (index + 1 === self.length) {
c[key] = c[key] / self.length
}
})
return c
})
And a possible solution using ECMA5 (as we seem to be missing one)
var sums = {},
averages = Object.keys(array.reduce(function (previous, element) {
if (previous.hasOwnProperty(element.name)) {
previous[element.name].value += element.value;
previous[element.name].count += 1;
} else {
previous[element.name] = {
value: element.value,
count: 1
};
}
return previous;
}, sums)).map(function (name) {
return {
name: name,
average: this[name].value / this[name].count
};
}, sums);
On jsFiddle
October 2020, I think this is the shortest way (ES6+)
const getAveragesByGroup = (arr, key, val) => {
const average = (a, b, i, self) => a + b[val] / self.length;
return Object.values(
arr.reduce((acc, elem, i, self) => (
(acc[elem[key]] = acc[elem[key]] || {
[key]: elem[key],
[val]: self.filter((x) => x[key] === elem[key]).reduce(average, 0),
}),acc),{})
);
};
console.log(getAveragesByGroup(array, 'name', 'value'))
Try by yourself :)

Categories

Resources