I have an object with multiple keys (e.g. idOne, idTwo, idThree, idFour) ... each key contains an array of objects. I would like to return and output the key with minimum price. In this example, idThree contains the minimum price of id and therefore should output idThree. I have code that returns the minimum price found ... but my goal is to return key (idThree). Is there a simpler/cleaner way?
const object = {
idOne: [{ price: 300 }],
idTwo: [{ price: 200 }, { price: 100 }],
idThree: [{ price: 90 }, { price: 100 }],
idFour: [{ price: 99 }, { price: 210 }]
}
Current Code
const arrayOfMinValues = []
for (const [key, value] of Object.entries(object)) {
const minimumEntry = Math.min(...value.map(item => item.price))
arrayOfMinValues.push(minimumEntry)
}
console.log('MIN VALUE IS: ', Math.min(...arrayOfMinValues)) // how can I return key?
If you first turn the object into an array of entries, and turn each subarray into the single lowest price in the array, you can then .reduce to iterate over all of those lowest prices and pick out the entry with the lowest one:
const object = {
idOne: [{ price: 300 }],
idTwo: [{ price: 200 }, { price: 100 }],
idThree: [{ price: 90 }, { price: 100 }],
idFour: [{ price: 99 }, { price: 210 }]
}
const minEntry = Object.entries(object)
.map(([key, arr]) => [key, Math.min(...arr.map(obj => obj.price))])
.reduce((a, b) => a[1] > b[1] ? b : a);
console.log('Min entry:', minEntry);
To access a property of an array, use [index] where index is the index you want to access:
const key = minEntry[0]
You can use nested reduce calls to get an object with the minimum key and value, and destructure the key:
const object = {"idOne":[{"price":300}],"idTwo":[{"price":200},{"price":100}],"idThree":[{"price":90},{"price":100}],"idFour":[{"price":99},{"price":210}]}
const { key } = Object.entries(object)
.reduce((acc, [key, values]) =>
values.reduce((r, { price }) => price < r.price ? { key, price } : r, acc)
, { key: null, price: Infinity })
console.log(key)
Another variation of reduce() using find()
const object = {"idOne":[{"price":300}],"idTwo":[{"price":200},{"price":100}],"idThree":[{"price":90},{"price":100}],"idFour":[{"price":99},{"price":210}]}
const [key, lp] = Object.entries(object).reduce((a, [k, v])=>{
const low = v.find(o => o.price < a[1]);
return low ? [k, low.price] : a;
},[null,Infinity])
console.log(key, ' has low price of ',lp )
Simple approach to use with steps:
Create sumPrices() function to get sum of prices.
function sumPrices(arr){
sum = 0;
for(const price of arr){
sum += price["price"]
}
return sum;
}
Create variable keys has all keys.
Create two vars minKey the key of lowest prices.
and minSum sum of lowest prices.
const keys = Object.keys(object);
let minKey = null,
minSum = Number.MAX_SAFE_INTEGER;
iterate over array keys
get each sum of each inner array
and compare currentSum with minSum if less than minimum.
keep track the minSum with thier recpective key.
for(const key of keys){
const currentSum = sumPrices(object[key])
if(currentSum <= minSum){
minKey = key;
minSum = currentSum;
}
}
console.log(minKey);
Related
I have an object like this:
result:
> rows:
> 0: {key: Array(4), value: 3}
> key: (4) ["Person", "2020-06-24", "Product, "00000000008"]
value: 3
> 1: {key: Array(4), value: 10}
> key: (4) ["Person", "2020-06-25", "Product, "00000000009"]
value: 10
> 2: {key: Array(4), value: 10}
> key: (4) ["Person", "2020-06-25", "Product, "00000000008"]
value: 10
Now, what I need to do is to reduce this result checking for the same code (for example 00000000008) and sum the value, to obtain:
(for example)
00000000008 value: 13
Now, my problem is how to do, I have tried to use first a map and then a reduce, but I don't understand how can I check for the same code and sum the value.
How can I do?
I have tried in this way, but it doesn't work:
res is the object with the values
let example = res.rows.map((element)=> {
console.log("ELEMENT IS ", element)
let example1 = element.key[3].reduce(function(element, v){
if(ref.hasOwnProperty(v))
element[ref[v]] += v;
else {
ref[v] = element.length;
element.push(prev = v)
}
return element
}, [])
})
console.log("element", element)
The Array.map method is useful for data transformations, but if you have to aggregate is mostly expensive because you have also to Array.filter the non-aggregated values.
You can use Array.reduce (MDN) instead in order to build your own object:
let result = {
rows: [
{
key: ["Person", "2020-06-24", "Product", "00000000008"],
value: 3
},
{
key: ["Person", "2020-06-25", "Product", "00000000009"],
value: 10
},
{
key: ["Person", "2020-06-25", "Product", "00000000008"],
value: 10
}
]
}
let output1 = result.rows.reduce((acc, current) => {
let key = current.key[3];
// adding value to the accumulator
acc[key] = (acc[key] || 0) + current.value;
return acc;
}, {});
console.log(output1);
let output2 = result.rows.reduce((acc, current) => {
// check if key is already present
let found = acc.find(v => v.key == current.key[3])
// if it is, update the current value
if (found) {
found.value += current.value;
}
// otherwise create a new one
else {
acc.push({ key: current.key[3], value: current.value });
}
return acc;
}, []);
console.log(output2)
create your own hashmap and loop over the result object once for all values
const hashmap = {};
rows.forEach(v => {
hashmap[v.product] = (hashmap[v.product] || 0) + v.value;
});
// then are you able to access any product value on O(1)
const total = hashmap['00000000008'];
console.log({total});
// total: 13
I have the following array:
var array = []
array.push({payslipId: 1759});
array.push({payDate: "2019-04-19T00:00:00+00:00"});
array.push({periodsPerYear: 52});
array.push({taxPeriod: 2});
array.push({payslipId: 1760});
array.push({payDate: "2019-04-19T00:00:00+00:00"});
array.push({periodsPerYear: 52});
array.push({taxPeriod: 2});
What I am hoping to be able to do is create an array of distinct names, and where the values are numeric, the total off all in the above array would be shown.
The only way I can think to do this is to create a new array, and add each name to it, then loop around the above array, and add to the value to the name, if the value is numeric.
I am not overly familiar with javascript, so am hoping there is a neater way, a bit like linq in C#?
Desired output:
[{ payslipId: 3519 }, { periodsPerYear: 104} , { taxPeriod: 4}]
You could take a Map and reduce the array by taking the only key/value pair of the object.
function group(array) {
return Array.from(
array.reduce((m, o) => {
var [k, v] = Object.entries(o)[0];
return typeof v === 'number'
? m.set(k, (m.get(k) || 0) + v)
: m;
}, new Map),
([k, v]) => ({ [k]: v })
);
}
var array = [{ payslipId: 1759 }, { payDate: "2019-04-19T00:00:00+00:00" }, { periodsPerYear: 52 }, { taxPeriod: 2 }, { payslipId: 1760 }, { payDate: "2019-04-19T00:00:00+00:00" }, { periodsPerYear: 52 }, { taxPeriod: 2 }],
result = group(array);
console.log(result);
As I have mentioned in the comment to the original post, you can use reduce() function for aggregation. You may apply any condition you need to for aggregation. As you have mentioned, I have considered number fields only.
Here you go:
var array = []
array.push({ payslipId: 1759 });
array.push({ payDate: "2019-04-19T00:00:00+00:00" });
array.push({ periodsPerYear: 52 });
array.push({ taxPeriod: 2 });
array.push({ payslipId: 1760 });
array.push({ payDate: "2019-04-19T00:00:00+00:00" });
array.push({ periodsPerYear: 52 });
array.push({ taxPeriod: 2 });
//console.log(array);
result = array.reduce((aggr, item) => {
itemKey = Object.keys(item)[0];
if (Number.isInteger(item[itemKey])) {
if (!aggr[itemKey]) {
aggr[itemKey] = 0;
}
aggr[itemKey] = parseInt(aggr[itemKey]) + parseInt(item[itemKey]);
}
return aggr;
}, {});
console.log(result);
Another option could be:
let array2 = [array[0]];
array.shift();
array.forEach(obj => {
if(typeof(obj[Object.keys(obj)[0]]) !== 'number') return;
//test if the object with the same property name already exists to array2 or not. If it doesn't exist, it will be added to array2. If it does only the value will be added to the existing element
let test = array2.reduce((accum,obj2) => {
return accum && (Object.keys(obj2)[0] !== Object.keys(obj)[0]);
}, true);
if(test) array2.push(obj);
else array2.find(obj2 => obj2.hasOwnProperty(Object.keys(obj)[0]))[Object.keys(obj)[0]] += obj[Object.keys(obj)[0]];
});
console.log(array2);
Input -
[
{color:'red', shape:'circle', size:'medium'},
{color:'red', shape:'circle', size:'small'}
]
Output -
[
{color:'red', shape:'circle', size:['medium','small']}
]
How can this be achieved in Javascript?
If you just want to group based on color and shape, you could use reduce. Create an accumulator object with each unique combination of those 2 properties separated by a | as key. And the object you want in the output as their value. Then use Object.values() to get those objects as an array.
const input = [
{color:'red', shape:'circle', size :'medium'},
{color:'red', shape:'circle', size:'small'},
{color:'blue', shape:'square', size:'small'}
];
const merged = input.reduce((acc, { color, shape, size }) => {
const key = color + "|" + shape;
acc[key] = acc[key] || { color, shape, size: [] };
acc[key].size.push(size);
return acc
}, {})
console.log(Object.values(merged))
This is what the merged/accumulator looks like:
{
"red|circle": {
"color": "red",
"shape": "circle",
"size": [
"medium",
"small"
]
},
"blue|square": {
"color": "blue",
"shape": "square",
"size": [
"small"
]
}
}
You can make it dynamic by creating an array of keys you'd want to group by:
const input = [
{ color: 'red', shape: 'circle', size: 'medium' },
{ color: 'red', shape: 'circle', size: 'small' },
{ color: 'blue', shape: 'square', size: 'small' }
];
const groupKeys = ['color', 'shape'];
const merged = input.reduce((acc, o) => {
const key = groupKeys.map(k => o[k]).join("|");
if (!acc[key]) {
acc[key] = groupKeys.reduce((r, k) => ({ ...r, [k]: o[k] }), {});
acc[key].size = []
}
acc[key].size.push(o.size)
return acc
}, {})
console.log(Object.values(merged))
You can create a general function which takes an array of objects and array of keys to match as its parameters.
You can do that in following steps:
First use reduce() on the array of objects and set accumulator to empty array []
Get the other props(unique props) by using filter() on Object.keys()
Then in each iteration find the element of the accumulator array whose all the given props matches with the current object.
If the element is found then use forEach() other keys and push the values to to corresponding array.
If element is not found then set each key to an empty array.
At last return the result of reduce()
const arr = [
{color:'red', shape:'circle', size:'medium'},
{color:'red', shape:'circle', size:'small'}
]
function groupByProps(arr,props){
const res = arr.reduce((ac,a) => {
let ind = ac.findIndex(b => props.every(k => a[k] === b[k]));
let others = Object.keys(a).filter(x => !props.includes(x));
if(ind === -1){
ac.push({...a});
others.forEach(x => ac[ac.length - 1][x] = []);
ind = ac.length - 1
}
others.forEach(x => ac[ind][x].push(a[x]));
return ac;
},[])
return res;
}
const res = groupByProps(arr,['color','shape'])
console.log(res)
Here is a simple groupBy function which accepts an array of objects and array of props to group on:
let data = [
{color:'red', shape:'circle', size:'medium'},
{color:'red', shape:'circle', size:'small'}
]
let groupBy = (arr, props) => Object.values(arr.reduce((r,c) => {
let key = props.reduce((a,k) => `${a}${c[k]}`, '')
let otherKeys = Object.keys(c).filter(k => !props.includes(k))
r[key] = r[key] || {...c, ...otherKeys.reduce((a,k) => (a[k] = [], a),{})}
otherKeys.forEach(k => r[key][k].push(c[k]))
return r
}, {}))
console.log(groupBy(data, ['color','shape']))
The idea is to use Array.reduce and basically create a string key of the passed in props. For the other fields create an array and keep pushing values there on each iteration.
I have an array that looks like this:
const values = [
{
value: 2000,
items: [
{
value: 300,
},
],
},
]
I want to sum the total of all of the values in values.value, and if a values.items.value exists I also want to include it in my sum.
values.reduce((total, obj) => obj.value + total, 0);
What would be the correct way to key into the nested array so it sums both the top and nested value key in my reduce function? The output I'd like is 2300 but right now I can only get one level deep, and it's outputting 2000.
You could nest your reduce approach to handle the inner array while ensuring that the key exists in the inner array objects.
const values = [
{
value: 2000,
items: [
{
value: 300,
},
],
},
];
const total = values.reduce((acc, obj) => {
acc += obj.value;
acc += obj.items.reduce((a, o) => 'value' in o ? o.value + a : a, 0);
return acc;
}, 0);
console.log(total);
// 2300
You can use reduce add value and check for items if it's there then add the value of items arrays as well
const values = [{value: 2000,items: [{value: 300,},],},]
let op = values.reduce((op,{value,items}) => {
op+= value
if(items && items.length) {
items.forEach(({value})=> op+=value )
}
return op
},0)
console.log(op)
I will add an inner reduce() to get the accumulated sum for the items array. Also, I will add some checks with isNaN() and Array.isArray() just for safety:
const values = [
{value: 2000, items: [{value: 300}]},
{value: 3000},
{value: 2000, items: [{value: 300}, {foo: 20}]},
{nothing: "nothing"}
];
let res = values.reduce((acc, {value, items}) =>
{
acc += isNaN(value) ? 0 : value;
acc += Array.isArray(items) ?
items.reduce((sum, {value}) => sum + (isNaN(value) ? 0 : value), 0) :
0;
return acc;
}, 0);
console.log(res);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
You could iterate keys/values of the object and sum the a nested object and take the wanted key or zero as start value.
function sum(object, key) {
return Object.entries(object).reduce((s, [k, v]) => {
if (v && typeof v === 'object') return s + sum(v, key);
return s;
}, key in object ? object[key] : 0);
}
const values = [{ value: 2000, items: [{ value: 300 }] }];
console.log(sum(values, 'value'));
I have a problem! I am creating an rating app, and I have come across a problem that I don't know how to solve. The app is react native based so I am using JavaScript.
The problem is that I have multiple objects that are almost the same, I want to take out the average value from the values of the "same" objects and create a new one with the average value as the new value of the newly created object
This array in my code comes as a parameter to a function
var arr = [
{"name":"foo","value":2},
{"name":"foo","value":5},
{"name":"foo","value":2},
{"name":"bar","value":2},
{"name":"bar","value":1}
]
and the result I want is
var newArr = [
{"name":"foo","value":3},
{"name":"bar","value":1.5},
]
If anyone can help me I would appreciate that so much!
this is not my exact code of course so that others can take help from this as well, if you want my code to help me I can send it if that's needed
If you have any questions I'm more than happy to answer those
Iterate the array with Array.reduce(), and collect to object using the name values as the key. Sum the Value attribute of each name to total, and increment count.
Convert the object back to array using Object.values(). Iterate the new array with Array.map(), and get the average value by dividing the total by count:
const arr = [{"name":"foo","Value":2},{"name":"foo","Value":5},{"name":"foo","Value":2},{"name":"bar","Value":2},{"name":"bar","Value":1}];
const result = Object.values(arr.reduce((r, { name, Value }) => {
if(!r[name]) r[name] = { name, total: 0, count: 0 };
r[name].total += Value;
r[name].count += 1;
return r;
}, Object.create(null)))
.map(({ name, total, count }) => ({
name,
value: total / count
}));
console.log(result);
I guess you need something like this :
let arr = [
{name: "foo", Value: 2},
{name: "foo", Value: 5},
{name: "foo", Value: 2},
{name: "bar", Value: 2},
{name: "bar", Value: 1}
];
let tempArr = [];
arr.map((e, i) => {
tempArr[e.name] = tempArr[e.name] || [];
tempArr[e.name].push(e.Value);
});
var newArr = [];
$.each(Object.keys(tempArr), (i, e) => {
let sum = tempArr[e].reduce((pv, cv) => pv+cv, 0);
newArr.push({name: e, value: sum/tempArr[e].length});
});
console.log(newArr);
Good luck !
If you have the option of using underscore.js, the problem becomes simple:
group the objects in arr by name
for each group calculate the average of items by reducing to the sum of their values and dividing by group length
map each group to a single object containing the name and the average
var arr = [
obj = {
name: "foo",
Value: 2
},
obj = {
name: "foo",
Value: 5
},
obj = {
name: "foo",
Value: 2
},
obj = {
name: "bar",
Value: 2
},
obj = {
name: "bar",
Value: 1
}
]
// chain the sequence of operations
var result = _.chain(arr)
// group the array by name
.groupBy('name')
// process each group
.map(function(group, name) {
// calculate the average of items in the group
var avg = (group.length > 0) ? _.reduce(group, function(sum, item) { return sum + item.Value }, 0) / group.length : 0;
return {
name: name,
value: avg
}
})
.value();
console.log(result);
<script src="http://underscorejs.org/underscore-min.js"></script>
In arr you have the property Value and in newArr you have the property value, so I‘ll assume it to be value both. Please change if wished otherwise.
var map = {};
for(i = 0; i < arr.length; i++)
{
if(typeof map[arr[i].name] == ‘undefined‘)
{
map[arr[i].name] = {
name: arr[i].name,
value: arr[i].value,
count: 1,
};
} else {
map[arr[i].name].value += arr[i].value;
map[arr[i].name].count++;
}
var newArr = [];
for(prop in map)
{
map[prop].value /= map[prop].count;
newArr.push({
name: prop,
value: map[prop].value
});
}
delete map;