groupBy array of objects with underscore.js - javascript

I have a array (order) of objects (products): , I want to group and count all these products by id:
var order = [
{ product_id: 70, product_price: 9, product_color: "ccc"},
{ product_id: 70, product_price: 9, product_color: "ccc"},
{ product_id: 71, product_price: 10, product_color: "fff" } ];
with Underscore.js:
var groups = _.countBy(order, function(value){
return value.product_id + "#" + value.product_price + "#" + value.product_color;
})
//--> Object {70#ccc#9: 2, 71#fff#10: 1}
So it works… but now, how can I return theses values into an array like this, so I can work with this as a new array of objects?
[
{ product_id: 70, product_price: 9, product_color: "ccc", count: 2},
{ product_id: 70, product_price: 9, product_color: "fff", count: 1}
];

Instead of countBy you could use groupBy and then map across the groups to add the count:
var groups = _.groupBy(order, function(value){
return value.product_id + "#" + value.product_price + "#" + value.product_color;
})
groups = _.map(groups, function(group){
return _.extend(group[0], {count: group.length});
});

You can use reduce to recreate the original array.
var countedById = [
{ product_id: 70, product_price: 9, product_color: "ccc", count: 2},
{ product_id: 70, product_price: 9, product_color: "fff", count: 1}
];
var original = countedById.reduce((acc, cur) => {
for (var i = 0; i < cur.count; i++) {
var original = {
product_id: cur.product_id,
product_price: cur.product_price,
product_color: cur.product_color
}
acc.push(original);
}
return acc;
}, []);
document.write(JSON.stringify(original))

Related

How to check with an array have some values

How to verify with i have only 2 or 3 numbers inside this?
without this ----> if(Array.includes(1) && !Array.includes(3))
const servicesTest: IServices[] = [
{
id: '1',
name: 'Hair',
price: 25,
icon: 'https://cdn-icons-png.flaticon.com/512/7478/7478480.png'
},
{
id: '2',
name: 'Beard',
price: 20,
icon: 'https://cdn-icons-png.flaticon.com/512/7578/7578754.png'
},
{
id: '3',
name: 'Eyebrow',
price: 15,
icon: 'https://cdn-icons-png.flaticon.com/512/2821/2821012.png'
}
]
if the client choose hair + beard this will be 40 not 45.
I´m doing this:
const name = findServices.map(services => services.name)
if (name.includes('Hair') && name.includes('Beard') && !name.includes('Eyebrown')) {
return (
setTotalDay(prevState => prevState + 40),
setTotalMonth(prevState => prevState + 40)
)
}
I would create an array of discounts like this:
const discounts = [{
price: 30,
ids: [1, 2],
}];
Then check if the array has only discounted items like this:
array.length === discount.ids.length && array.every((item) => discount.ids.includes(item.id))
const discounts = [{
price: 30,
ids: [1, 2],
}];
const discounted = [{
id: 1,
name: 'Hair',
price: 20,
},
{
id: 2,
name: 'Beard',
price: 30,
},
];
const fullPrice = [{
id: 1,
name: 'Hair',
price: 20,
},
{
id: 2,
name: 'Beard',
price: 30,
},
{
id: 3,
name: 'Tea',
price: 30,
},
];
console.log("discounted", getTotal(discounted));
console.log("full price", getTotal(fullPrice));
function getTotal(array) {
for (const discount of discounts) {
if (
array.length === discount.ids.length &&
array.every((item) => discount.ids.includes(item.id))
) {
return discount.price;
}
}
return array.reduce((sum, item) => sum + item.price, 0);
}
answering your question before the edit.
Assuming we have this array
const Array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Let's say we want to check if values 2 and 3 exist.
We store the values in an array let toCheck = [2,3];
We can use function every to loop all the elements of toCheck array against the Array const
Example Follows:
const Array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let toCheck = [1,2];
const allExist = toCheck.every(value => {
return Array.includes(value);
});
Hope it helps.

JavaScript - reduce array

Working with an array of objects like:
[
{
orderItemId: 180,
product_id: 1,
product_quantity: 3,
product_price: 18,
product_name: 'American BBQ DOG',
extra_id: 1,
extra_quantity: 3,
extra_price: 2,
extra_name: 'Mustard (60 g)',
},
{
orderItemId: 180,
product_id: 1,
product_quantity: 3,
product_price: 18,
product_name: 'American BBQ DOG',
extra_id: 3,
extra_quantity: 3,
extra_price: 2,
extra_name: 'Roasted onions (30 g)',
}
]
I am trying to reduce the array on matching values for the orderItemId key and achieve an desired output like:
const desired = [
{
orderItemId: 180,
product_id: 1,
product_quantity: 3,
product_price: 18,
product_name: 'American BBQ DOG',
extra: [
{
extra_id: 1,
extra_quantity: 3,
extra_price: 2,
extra_name: 'Mustard (60 g)',
},
{
extra_id: 3,
extra_quantity: 3,
extra_price: 2,
extra_name: 'Roasted onions (30 g)',
}
]
}
];
Any ideas? I tried several logic but failed.
Here's one way to do it. It just uses Array.find() to find a previous item with the same orderItemId, if found, appends the extras to its extra array. If the original array is very large, consider using an object or a Map as the accumulator passed to the .reduce call and using Object.values() later.
If you need to handle extra logic such as adding product_quantity or deduplicating extra, you'll need to handle that as well.
const arr = [
{
orderItemId: 180,
product_id: 1,
product_quantity: 3,
product_price: 18,
product_name: "American BBQ DOG",
extra_id: 1,
extra_quantity: 3,
extra_price: 2,
extra_name: "Mustard (60 g)",
},
{
orderItemId: 180,
product_id: 1,
product_quantity: 3,
product_price: 18,
product_name: "American BBQ DOG",
extra_id: 3,
extra_quantity: 3,
extra_price: 2,
extra_name: "Roasted onions (30 g)",
},
];
const reduced = arr.reduce((acc, val) => {
const {
extra_id,
extra_quantity,
extra_price,
extra_name,
...otherFields
} = val;
const existing = acc.find((item) => item.orderItemId === val.orderItemId);
if (!existing) {
acc.push({
...otherFields,
extra: [
{
extra_id,
extra_quantity,
extra_price,
extra_name,
},
],
});
return acc;
}
existing.extra.push({ extra_id, extra_quantity, extra_price, extra_name });
return acc;
}, []);
console.log(reduced);
Edit: Here's a version that uses an object as a lookup so it doesn't have to go through the entire accumulated array each time. The acc (accumulator) object is basically just an object where the property keys are the orderItemId and the value is the new merged object with that key.
const arr = [
{
orderItemId: 180,
product_id: 1,
product_quantity: 3,
product_price: 18,
product_name: "American BBQ DOG",
extra_id: 1,
extra_quantity: 3,
extra_price: 2,
extra_name: "Mustard (60 g)",
},
{
orderItemId: 180,
product_id: 1,
product_quantity: 3,
product_price: 18,
product_name: "American BBQ DOG",
extra_id: 3,
extra_quantity: 3,
extra_price: 2,
extra_name: "Roasted onions (30 g)",
},
];
const reducedObject = arr.reduce((acc, val) => {
const {
extra_id,
extra_quantity,
extra_price,
extra_name,
...otherFields
} = val;
const existing = acc[val.orderItemId];
if (!existing) {
acc[val.orderItemId] = {
...otherFields,
extra: [
{
extra_id,
extra_quantity,
extra_price,
extra_name,
},
],
};
return acc;
}
existing.extra.push({ extra_id, extra_quantity, extra_price, extra_name });
return acc;
}, {});
const reduced = Object.values(reducedObject);
console.log(reduced);
With this approach you can use it with different data structure also, whitout declare fields names (or key names).
In practice comparison and aggregation.
var alist=[
{
orderItemId: 180,
product_id: 1,
product_quantity: 3,
product_price: 18,
product_name: 'American BBQ DOG',
extra_id: 1,
extra_quantity: 3,
extra_price: 2,
extra_name: 'Mustard (60 g)',
},
{
orderItemId: 180,
product_id: 1,
product_quantity: 3,
product_price: 18,
product_name: 'American BBQ DOG',
extra_id: 3,
extra_quantity: 3,
extra_price: 2,
extra_name: 'Roasted onions (30 g)',
}
];
function jS(o){
return JSON.stringify(o);
}
function equals(a,b){
var aa = jS(a);
var bb = jS(b);
return aa == bb;
}
function hLen(o){
return Object.keys(o).length;
}
function hKeys(h){
return Object.keys(h);
}
function reduce(arr){
var ha=arr[0];
var hb=arr[1];
var hc={};
var xc1={}; //extra 1 when are different
var xc2={}; //extra 2 when are different
maxhlen = Math.max(hLen(ha),hLen(hb));
akeys = hKeys(ha);
bkeys = hKeys(hb);
for(i=0;i<maxhlen;i++){
k=akeys[i];
if (ha[k]==hb[k]){
hc[k]=ha[k];
}else{
xc1[k]=ha[k];
xc2[k]=hb[k];
}
}
hc["extra"]=[xc1,xc2];
return hc;
}
res=reduce(alist);
console.log(res);

Group or chunk array with maximum sum limit

I have an array like this
const array = [{id: 1, size: 1}, {id: 2, size: 2}, {id: 3, size: 4}, {id: 4, size: 1}, {id: 5, size: 2}, {id: 6, size: 3}, ...]
I want to group or chunk this array with the maximum sum of size properties (the sum size of every indexes cannot be greater than 4),
so new array should be something like this:
const newArray = [
[{id:1, size: 1}, {id:2, size: 2}, {id:4, size: 1}],
[{id:3, size: 4}],
[{id:5, size: 3}],
[{id:6, size: 4}],
...
]
You could find the next slot by looking to the sum of each slot.
let array = [{ id: 1, size: 1 }, { id: 2, size: 2 }, { id: 3, size: 4 }, { id: 4, size: 1 }, { id: 5, size: 2 }, { id: 6, size: 3 }],
sum = 4,
result = array.reduce((r, o) => {
const temp = r.find(a => a.reduce((s, { size }) => s + size, 0) + o.size <= sum);
if (temp) temp.push(o);
else r.push([o]);
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
my way...
const array =
[ { id: 1, size: 1 }
, { id: 2, size: 2 }
, { id: 3, size: 4 }
, { id: 4, size: 1 }
, { id: 5, size: 2 }
, { id: 6, size: 3 }
// , ...
]
, szMax = array.reduce((t,c)=>Math.max(t,c.size),0)
, temp = array.map(e=>({...e}))
, result = []
;
while (temp.length > 0)
{
let sz = szMax
, nv = []
;
while( sz > 0 )
{
let idx = temp.findIndex(x=>x.size <= sz)
if (idx===-1) break
nv.push( temp[idx] )
sz -= temp[idx].size
temp.splice(idx,1)
}
result.push([...nv])
nv = []
}
console.log( result )
.as-console-wrapper{max-height:100% !important;top: 0;}

Filter an array of repeated objects and get the sum of them

So I have the next information, what I want to achieve is filter the array of objects and sum the qty of the objects repeated
//Original Data
var data = {
sublists = [
{ item: 1, qty: 2},
{ item: 2, qty: 2},
{ item: 3, qty: 2},
{ item: 1, qty: 5}
{ item: 3, qty: 3}
],
...
};
//This is what I want to achieve
var result = {
sublists = [
{ item: 1, qty: 7},
{ item: 2, qty: 2},
{ item: 3, qty: 5}
],
...
};
So far what I got is the items name that repeat and an object that has the data of this variables
var repeatedObjects = {
1: [{ item: 1, qty: 2}, { item: 1, qty: 5}],
3: [{ item: 3, qty: 2}, { item: 3, qty: 3}]
}
var repeatedItems = [1, 3];
But Im stuck at reducing the object so I can get the original data, with just one of the repeated objects and the sum of all of them. Any ideas?
Create an itemByFreq json object like below
var data = {
sublists : [
{ item: 1, qty: 2},
{ item: 2, qty: 2},
{ item: 3, qty: 2},
{ item: 1, qty: 5},
{ item: 3, qty: 3}
]
};
var itemByFreq = {};
for(var i=0;i<data.sublists.length;i++){
var ji = data.sublists[i];
itemByFreq[ji.item] = (itemByFreq[ji.item] || 0) + ji.qty;
}
console.log(itemByFreq);
var duplicateItems = [];
for(var key in itemByFreq)
duplicateItems.push({item: key,qty: itemByFreq[key]});
console.log(duplicateItems);
It could be done using Map & reduce :)
const dataSample = {
sublists: [
{ item: 1, qty: 2},
{ item: 2, qty: 2},
{ item: 3, qty: 2},
{ item: 1, qty: 5},
{ item: 3, qty: 3}
],
};
const mergeItems = list => {
return [...list.reduce((acc, val) => {
const oldVal = acc.get(val.item) || { qty: 0 }
const newVal = {
...val,
qty: oldVal.qty + val.qty
}
acc.set(val.item, newVal)
return acc
}, new Map()).values()]
}
console.log(mergeItems(dataSample.sublists))
If you fancy using lodash to do this in one line, you could also use this
const _ = require("lodash");
const data = [
{"item": 1, "qty": 2},
{"item": 2, "qty": 2},
{"item": 3, "qty": 2},
{"item": 1, "qty": 5},
{"item": 3, "qty": 3}
];
const result = _.map(_.groupBy(data, i => i.item), (o, idx) => {
return {"item": idx, "qty": _.sumBy(o, i => i.qty)};
});
console.log(result);
Using reduce will simplify
const update = data =>
Object.values(
data.sublists.reduce((acc, curr) => {
acc[curr.item] =
curr.item in acc
? { ...acc[curr.item], qty: acc[curr.item].qty + curr.qty }
: { ...curr };
return acc;
}, {})
);
var data = {
sublists: [
{ item: 1, qty: 2 },
{ item: 2, qty: 2 },
{ item: 3, qty: 2 },
{ item: 1, qty: 5 },
{ item: 3, qty: 3 }
]
};
const res = { sublists: update(data) };
console.log(res);

Remove multiple items from array based on matched value

I have issues removing items from an array based on matched value. I have an array of object like below:
$scope.listOfItems = [
{productID : 1, weight: 3.5, price: 5},
{productID : 2, weight: 4.5, price: 8},
{productID : 3, weight: 1.5, price: 9},
{productID : 4, weight: 2.5, price: 3},
{productID : 5, weight: 7.5, price: 1}
];
And I want to remove items based on an array of productID, like below:
$scope.deleteList = [1,3];
Now I try to use two loops to check if the productID if each product is the same with any productID in the deleteList.
angular.forEach($scope.listOfItems , function(value, key){
var tmp_productID = value.productID ;
var index = key;
angular.forEach($scope.deleteList, function(value,key){
if(tmp_productID === value){
$scope.listOfItems .splice(index ,1);
}
});
});
console.log($scope.listOfItems);
The problem of this is, after it deletes the one with index1, the index3 will be index2, thus it will delete product2 and product5 instead of product2 and product4, which is wrong. Also the performance will be bad if I have a large number of objects in the array and I want to delete half of them. Any idea that I can delete the right record and improve the performance if possible?
Working jsfiddle
http://jsfiddle.net/Lvc0u55v/10726/
You could iterate in reverse, that way the splicing won't affect the previous indices
var $scope = {};
$scope.listOfItems = [{
productID: 1,
weight: 3.5,
price: 5
}, {
productID: 2,
weight: 4.5,
price: 8
}, {
productID: 3,
weight: 1.5,
price: 9
}, {
productID: 4,
weight: 2.5,
price: 3
}, {
productID: 5,
weight: 7.5,
price: 1
}];
$scope.deleteList = [1, 3];
for ( var i = $scope.listOfItems.length; i--; ) {
if ( $scope.deleteList.indexOf( $scope.listOfItems[i].productID ) !== -1 ) {
$scope.listOfItems.splice(i ,1);
}
}
console.log($scope.listOfItems);
.as-console-wrapper {top: 0; max-height: 100%!important}

Categories