Any suggestion on how to do grupby + sortby together using UnderscoreJS?
Having following collection, I would like to find smallest price per season:
var d = [{
"price": 27.25,
"season": 2
},
{
"price": 10,
"season": 3
},
{
"price": 21,
"season": 2
}];
I was able to group using the below:
_.chain(d).groupBy('season').map(function(value, key) {
return {
season: key,
obj: value
}
}).value();
which gets me:
[
{"season":"2","obj":[{"price":27.25,"season":2},{"price":21,"season":2}]},
{"season":"3","obj":[{"price":10,"season":3}]}
]
Try this:
var output = _.chain(d).groupBy("season").map(function (group) {
return _.chain(group).sortBy("price").first().value();
}).value();
See the demo: http://jsfiddle.net/Ns2MJ/
Get the minimum price of each season in your map function:
_.chain(d).groupBy('season').map(function(value, key) {
return {
season: key,
price: _.chain(value).pluck('price').min().value()
}
}).sortBy('price').value();
You can use _min combined with _.property to extract the smallest price in a group:
var output = _.chain(d).groupBy('season').map(function (group) {
return _.min(group, _.property('price'));
}).value();
And a demo http://jsfiddle.net/nikoshr/Edf5g/
Related
I am trying to calculate the "total cost" of the presents Array in "Object: presents" for a given name (in this case "Peter) by finding the price of the presents in "Object: prices". If the present is not in the Prices array, the price would be 0.
ie: In the case of Peter the "total cost" of the presents would be 1,001 and in the case of Dana would be 6,800.
The outcome would be an array of prices given the matched presents within the 2 arrays of objects (ie for peter = [1,1000], since his presents are "coffee" and "holidays") so later I calculate the sum inside of it with my reducer function.
I have tried to get the array by filtering the name and then try to find the elements included but my solution only iterates on the first element (coffee) and it returns an undefined array.
Any ideas on why and hints on how to move forward?
I am a beginner coder and would really appreciate some guidance.
Cheers,
const presents = [
{ "name": "peter", "presents": ["coffee", "holidays"], "present": "sock", "score": 10 },
{ "name": "dana", "presents": ["car", "phone"], "present": "sock", "score": 9.25 }
]
const prices = [{"present": "coffee","price": 1}, {"present": "holidays","price": 1000
},{"present": "videogames","price": 40},{"present": "computer","price": 600},{"present": "tattoo","price": 30},{"present": "clothes","price": 80},{"present": "car","price": 6000},{"present": "phone","price": 800},{"present": "motorbike","price": 3500}]
const name = 'peter'
const presentsPrices = []
const filteredName = presents.filter((element)=>element.name===name?element:false).map((element, i)=> element.presents[i].includes(prices[i].present)? presentsPrices.push(prices[i].price):0)
const reducer = (accumulator, currentValue) => accumulator + currentValue;
const sum = presentsPrices.reduce(reducer)
console.log(sum)
You can use .find to get the price of a present. Also, there's no need for .filter and .map since you're searching for one object and then get its presents if exists:
const presents = [
{ "name": "peter", "presents": ["coffee", "holidays"], "present": "sock", "score": 10 },
{ "name": "dana", "presents": ["car", "phone"], "present": "sock", "score": 9.25 }
];
const prices = [
{ "present": "coffee","price": 1 },
{ "present": "holidays","price": 1000 },
{ "present": "videogames","price": 40},
{ "present": "computer","price": 600 },
{ "present": "tattoo","price": 30 },
{ "present": "clothes","price": 80 },
{ "present": "car","price": 6000},
{ "present": "phone","price": 800 },
{ "present": "motorbike","price": 3500 }
];
const name = 'peter';
// get presents of person with this name
const { presents: filteredNamePresents = [] } = presents.find(element => element.name===name) || {};
console.log(`Presents: ${filteredNamePresents}`);
// get prices list of these presents
const presentsPrices = filteredNamePresents.reduce((acc,item) => {
const { price } = prices.find(price => price.present===item) || {};
acc = price ? [...acc, price] : acc;
return acc;
}, []);
console.log(`Prices: ${presentsPrices}`);
// calculate totla sum of the prices
const sum = presentsPrices.reduce((accumulator, currentValue) => accumulator + currentValue);
console.log(`Sum: ${sum}`);
Here's something that can help your code:
const people = {
"peter": ["coffee", "holidays"],
...
};
const presents = {
"coffee": 1,
"holidays": 1000,
...
};
But otherwise than better organization with JSON I can't really help you with your problem.
Array of objects that I got
[
{
"id": 1,
"price": 100
},
{
"id": 1,
"price": 80
},
{
"id": 2,
"price": 8
},
{
"id": 1,
"price": 85
}
]
Array of objects that I am trying to do
[
{
"id": 1,
"price": 88.33 // AVERAGE VALUE BETWEEN DUPLICATED OBJECTS
},
{
"id": 2,
"price": 8
}
]
I am merging and getting the average price for duplicated objects.
What I have done:
I have tried to use filter() function but I removed the duplicated without merging the prices.
If you want to avoid extra loops and extra properties is not a problem, you can use a getter for each object as follow:
You can use the function Array.prototype.reduce for grouping objects by id and the function Object.values for extracting the grouped values.
The getter price calculates the average when this property is accessed.
Extra properties:
{
count: Integer // count of repeated ids.
sum: Double // total sum of prices
}
const arr = [ { "id": 1, "price": 100 }, { "id": 1, "price": 80 }, { "id": 2, "price": 8 }, { "id": 1, "price": 85 } ],
result = Object.values(arr.reduce((r, {id, price}) => {
let current = (r[id] || (r[id] = {id, sum: 0, count: 0, get price() {
return this.sum / this.count;
}}));
current.sum += price;
current.count++;
return r;
}, {}));
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use .reduce() with an ES6 Map. By using reduce() you can accumulate all objects into a Map, where the key is the id from the object and the value is an accumulated array of price values for the given id. You can then convert the Map back into an array using Array.from(), where you can provide a mapping function to convert the [key, value] pairs from the map into an object. The object's price key will be the sum of all numbers in the value array (arr) divided by the length of the array, which will give you the average.
See example below:
const arr = [ { "id": 1, "price": 100 }, { "id": 1, "price": 80 }, { "id": 2, "price": 8 }, { "id": 1, "price": 85 } ];
const res = Array.from(arr.reduce((m, {id, price}) => {
return m.set(id, [...(m.get(id) || []), price]);
}, new Map), ([id, arr]) => ({id, price: arr.reduce((t, n) => t+n, 0) / arr.length}));
console.log(res);
Use forEach loop and build an object with keys as id and aggregate price.
Use Object.values of above object and calculate the averages.
const data = [
{
id: 1,
price: 100,
},
{
id: 1,
price: 80,
},
{
id: 2,
price: 8,
},
{
id: 1,
price: 85,
},
];
const process = (arr) => {
const res = {};
arr.forEach(({ id, price }) => {
res[id] ??= { id, sum: 0, count: 0 };
res[id].sum += price;
res[id].count += 1;
});
return Object.values(res).map(({ id, sum, count }) => ({
id,
price: sum / count,
}));
};
console.log(process(data));
I have seen some questions that might look similar but none is the solution in my case. I want to regroup and recreate my array the way that it is arranged or grouped based on one of my values(age). I want to have all data of the same "age" in one place. So here is my sample array:
[
{
"age": 15,
"person": {
name: 'John',
hobby: 'ski'
},
},
{
"age": 23,
"person": {
name: 'Suzi',
hobby: 'golf'
},
},
{
"age": 23,
"person": {
name: 'Joe',
hobby: 'books'
}
},{
"age": 25,
"person": {
name: 'Rosi',
hobby: 'books'
}
},{
"age": 15,
"person": {
name: 'Gary',
hobby: 'books'
}
},
{
"age": 23,
"person": {
name: 'Kane',
hobby: 'books'
}
}
]
And I need to have an array that kind of have age as a key and person as value, so each key could have multiple values meaning the value will kind of be an array itself.
I have read this and this questions and many more but they were not exactly the same.
I feel like I need to use reduce to count duplicate ages and then filter it based on that but how do I get the values of those ages?
EIDT:
Sorry for not being clear:
This is what I need:
{
23: [
{ name: 'Suzi', hoby: 'golf' },
{ name: 'Joe', hobby: 'books'}
],
15: [
{ name: 'Gary', hobby: 'books' }
] ,
.
.
.
}
You're actually going to want to reduce, not filter. Filtering an Array means to remove elements and place the kept elements into a new container. Reducing an array means to transform it into a single value in a new container. Mapping an array means to transform every value in place to a new container. Since you want to change how the data is represented that's a Reduction, from one form to another more condensed form.
Assume your Array of values is stored in let people = [...]
let peopleByAge = people.reduce(function (accumulator, value, index, array){
// The first time through accumulator is the passed extra Object after this function
// See the MDN for Array.prototype.reduce() for more information
if (accumulator[value.age] == undefined){
accumulator[value.age] = [];
}
accumulator[value.age].push(value);
return accumulator
}, {})
console.log(peopleByAge) // { 23: [{ age: 23, name: ..., hobby: ...}, ...], 13: [...], ...}
You can find the MDN article for Array#reduce() here
Thanks to #RobertMennell who patiently answered me and I voted as answer. But I just wanted to write my version which MDN had a great example of. It is a longer version assuming the people is the array name:
const groupedByvalue = 'age';
const groupedArray = people;
const groupBy = (peopleArray, value) => {
return peopleArray.reduce((acc, obj) => {
const key = obj[value];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
return acc;
}, {});
}
console.log(groupBy(groupedArray,groupedByvalue));
Update:
More polished using ternary operator:
const groupedByvalue = 'age';
const groupedArray = people;
const groupBy = (peopleArray, value) => {
return peopleArray.reduce((acc, obj) => {
const key = obj[value];
(!acc[key]) ? (acc[key] = []) : (acc[key].push(obj))
return acc;
}, {});
}
console.log(groupBy(groupedArray,groupedByvalue));
I have been struggling with this problem the whole day.
I have got the following array:
controller.array = [{
Equity: "0",
Bond: "97.42",
Cash: "67.98"
}, {
Equity: "5.32",
Bond: "13.12",
Cash: "8"
}, {
// ...
} /* ... */ ]
What I want to do is create a single array containing objects with combined values like so:
controller.newArray = [{
Type: "Equity",
Percentage: "5.32"
}, {
Type: "Bond",
Percentage: "110.54"
}, {
Type: "Cash",
Percentage: "75.98"
} /* ... */ ]
I have tried using _.each like this:
.map(function(item, value) {
var array = [];
_.each(item, function(value, item) {
array.push({
'Source': item,
'Percentage': value
})
})
return array;
})
.value()
What then happens is that it returns an array, containing multiple arrays with objects with my values. Now my problem is that I cant seem to combine all the arrays that are being returned.
Any ideas? Please?
You can transpose the array of objects into an array of values grouped by their common key.
Then you can map the values over to the resulting objects.
The transpose() and sum() functions are underscore mixins, so you can chain them!
_.mixin({
transpose : function(array) {
return _.chain(array).map(_.keys).flatten().uniq().reduce(function(result, key) {
result[key] = _.pluck(array, key);
return result;
}, {}).value();
},
sum : function(values) {
return _.reduce(values, function(sum, value) {
return sum + (_.isNumber(value) ? value : parseFloat(value));
}, 0);
}
});
var array = [{
Equity: "0",
Bond: "97.42",
Cash: "67.98"
}, {
Equity: "5.32",
Bond: "13.12",
Cash: "8"
}];
var result = _.chain(array).transpose().map(function(value, key) {
return {
Type: key,
Percentage: _.sum(value).toFixed(2)
};
}).value();
console.log(JSON.stringify(result, null, 2));
.as-console-wrapper { top: 0; max-height: 100% !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
One way, using only JavaScript could be to use map and reduce functions:
var data = [{Equity: "0", Bond: "97.42", Cash: "67.98"},
{Equity: "5.32", Bond: "13.12", Cash: "8"}];
var sumMap = data.reduce(function(acc, item) {
Object.keys(item).forEach(function(itemKey) {
if (acc[itemKey] === undefined) {
acc[itemKey] = 0;
}
acc[itemKey] += parseFloat(item[itemKey]);
});
return acc;
}, {});
var result = Object.keys(sumMap).map(function(itemKey) {
return {
"Type": itemKey,
"Percentage": "" + sumMap[itemKey]
};
});
console.log(JSON.stringify(result, null, 2));
The intermediate result sumMap will be something like this:
{
Equity: 5.32,
Bond: 110.54,
Cash: 75.98
}
The fiddle (thanks to CPHPython).
If what you need is to sum each type then this should do:
keys = {Equity: 0, Bond: 0, Cash: 0}
//newArray is the array from your question
newArray.forEach(function(obj) {
keys[obj.Type] += obj.Percentage})
I'm new to javascript, so this question may sound very basic.
var data = [
{ Amy: {items:[{shirt: 12},{trouser: 10}] } },
{ Bill: {items:[{shirt: 10},{trouser: 11}] } },
{ Chet: {items:[{shirt: 11},{trouser: 12}] } }
];
I am trying to write a function to return who's got max number of shirts. So I write two functions like this to first get the max values
var shirtValues = data.map(function(obj) {return obj.items[shirts]; });
var maxValue = Math.max.apply(null, shirtValues);
console.log(maxValue);
Now i need to find who is the person that got max number of shirts. How to achieve that?
I would start by changing your data structure. It's hard to see how the current format would be overly useful for iterating, aggregating, getting the user's name, getting the item's names, etc., without excessive iterating.
Here is one alternative data structure that is much easier to work with because it doesn't have nested arrays and doesn't require Object.keys to access data you need consistently (e.g. user name):
var data = [{
user: 'Amy',
items: {
shirt: 12,
trouser: 10
}
}, {
user: 'Bill',
items: {
shirt: 10,
trouser: 11
}
}, {
user: 'Chet',
items: {
shirt: 11,
trouser: 12
}
}];
With this format, you could easily sort by a particular item quantity:
let getTopItem = (data, itemName) => {
// 1. clone the array using concat
// 2. sort by value at supplied itemName
// 3. return the first item
let sorted = data.concat().sort((a, b) => {
return b.items[itemName] - a.items[itemName];
});
return sorted.shift();
}
let topShirts = getTopItem(data, 'shirt');
console.log(topShirts.user);
EDIT - I don't mean this negatively toward any of the answers as they all seem to be correct and useful approaches to getting the required data from the presented data structure - but look at how many iterations they all require to get this very basic data out of your object. Choosing the right structure for your data will save you a lot of headaches down the road.
Provided that, you cannot change your datastructure, reduce function can be very handy to serve your purpose. Actually, the logic becomes pretty straight-forward!. The code is provided below:
var data = [
{ Amy: {items:[{shirt: 12},{trouser: 10}] } },
{ Bill: {items:[{shirt: 10},{trouser: 11}] } },
{ Chet: {items:[{shirt: 11},{trouser: 12}] } }
];
var new_data = data.reduce(function(max, obj) {
var obj_val;
var max_val;
max_val = get_shirt_val(max);
obj_val = get_shirt_val(obj);
return obj_val > max_val ? obj : max;
});
function get_shirt_val(obj) {
key = Object.keys(obj)[0]
return obj[key].items[0].shirt;
}
console.log(JSON.stringify(new_data));
console.log(Object.keys(new_data)[0])
Hope this helps!
If your JSON structure is this always. Then you can use this way to find maximum shirts count :
var max=0;
var maxShirtCount=null;
data.forEach(function(ele,ind){
currVal = ele[Object.keys(ele)[0]].items[0].shirt;
if(currVal>max){
max=currVal;
maxShirtCount=ele;
}
});
console.log(maxShirtCount);
for maximum trousers count :
var max=0;
var maxTrouserCount=null;
data.forEach(function(ele,ind){
currVal = ele[Object.keys(ele)[0]].items[1].trouser;
if(currVal>max){
max=currVal;
maxTrouserCount=ele;
}
});
console.log(maxTrouserCount);
var data = [
{ Amy: {items:[{shirt: 12},{trouser: 10}] } },
{ Bill: {items:[{shirt: 10},{trouser: 11}] } },
{ Chet: {items:[{shirt: 11},{trouser: 12}] } }
];
function doSort(a, b){
return a[Object.keys(a)].items[0].shirt < b[Object.keys(b)].items[0].shirt;
}
console.log(Object.keys(data.sort(doSort)[0])[0]);
You could get the key, as name, get for the wanted item the count and make a comparison with the count of the previous persons. If max is the same, then extend the result array, if greater, then return a new object with the max data.
function getMax(item) {
return data.reduce(function (r, a) {
var name = Object.keys(a)[0],
count = a[name].items.reduce(function (s, b) {
return s + (b[item] || 0);
}, 0);
if (count >= r.count) {
if (count > r.count) {
return { item: item, names: [name], count: count };
}
r.names.push(name);
}
return r;
}, { item: item, names: [], count: 0 });
}
var data = [{ Amy: { items: [{ shirt: 12 }, { trouser: 10 }] } }, { Bill: { items: [{ shirt: 10 }, { trouser: 11 }] } }, { Chet: { items: [{ shirt: 11 }, { trouser: 12 }] } }],
maxShirt = getMax('shirt');
console.log(maxShirt); // all names with max count
console.log(maxShirt.names); // just the names
Without sorting and with a single pass reduce you might do as follows;
var data = [{ Amy: {items:[{shirt: 12},{trouser: 10}] } }, { Bill: {items:[{shirt: 10},{trouser: 11}] } }, { Chet: {items:[{shirt: 11},{trouser: 12}] } }
],
result = data.reduce((p,c) => { var sc = c[Object.keys(c)[0]].items[0].shirt;
return sc > p[0] ? [sc,c] : p;
},[0,{}])[1];
console.log(result);