Group by list of dictionary in javascript - javascript

Input =
[{id: 13, display_name: "Customizable Desk (Aluminium, Black)", quantity: 4, unit_price: "800.40", discount: 0, price: "3201.60"},
{id: 40, display_name: "Magnetic Board", quantity: 2, unit_price: "1.98", discount: 0, price: "3.96"},
{id: 40, display_name: "Magnetic Board", quantity: 1, unit_price: "1.98", discount: 0, price: "1.98"},
{id: 40, display_name: "Magnetic Board", quantity: 1, unit_price: "1.98", discount: 0, price: "1.98"}]
Output =
[{id: 13, display_name: "Customizable Desk (Aluminium, Black)", quantity: 4, unit_price: "800.40", discount: 0, price: "3201.60"},
{id: 40, display_name: "Magnetic Board", quantity: 4, unit_price: "1.98", discount: 0, price: "7.92"}]
I am able to achieve an answer but my process is very lengthy, I need a short answer for it using some predefined functions of javascript.

Here's a short way to do this (based on the assumption I cited before that quantity is the only thing that can vary for each item with the same id value):
inputArray.reduce((result, item) => {
if (result.map.has(item.id)) {
result.map.get(item.id).quantity += item.quantity;
} else {
result.array.push(item);
result.map.set(item.id, item);
}
return result;
}, {map: new Map(), array: []}).array
This uses the array reduce function, if you're not familiar with that. This could be done without the Map, but that makes it more efficient than searching through the whole result array to find id values which have already been seen.
The idea behind this code is that you keep the first item you see that has an id that you haven't seen before, and if you have seen an id before, you look up that original item, and add the new quantity to the previous quantity.

I'd do something like this:
function groupProducts( input ) {
var productsById = input.reduce( function( current, item ) {
if( !current[ item.id ] ) {
current[ item.id ] = [];
}
current[ item.id ].push( item );
return current;
}, {} );
return Object.keys( productsById ).map( function( id ) {
productsById[ id ].reduce( function( current, item ) {
if( current ) {
// this could be extracted as a closure passed in to coalesce items together. Your rules for how that happens go here.
current.quantity += item.quantity;
current.price += item.price;
return current;
} else {
return Object.assign( {}, item ); // shallow copy beware
}
}, null );
} );
}
PS I noticed in your input sample things like quantity and price were strings instead of numbers. I'm going to assume you know how to straighten those things out so the data has proper data types. This won't work if you have strings for those.

Related

Apply Combo Discount to a Food Order

An app lets users order food from a menu. The menu has three types of selection: main, drink and dessert. A feature needs to be added which will discount the price by 10% for every main+drink combo (10% off every combo). All items ordered by the customer are stored in an array like so:
order = [
{id: 4, count: 1, type: "main", price: 10}
{id: 5, count: 2, type: "drink", price: 9.5}
]
As you can see, each item the customer orders has a count property. How can I apply the discount without mutating the order array or any of the object properties? Ideally I'd like to loop through the array, determine total number of combos (in the example above it would be 1), determine the total discount value and pass that value to another function which computes the order total. If anyone can suggest a better way of doing it, I'm all ears (or eyes in this case).
Also, what is the best way to express this problem from a technical point of view?
const userOrder = [
{ id: 4, count: 1, type: "main", price: 200 },
{ id: 5, count: 1, type: "drink", price: 100 }
];
const orderInfo = userOrder.reduce((acc, cur) => {
console.log('cur', cur)
if (acc[cur.type]) {
return {
...acc,
[cur.type]: cur.count,
totalAmount: (cur.count * acc.totalAmount)
}
} else {
return {
...acc,
[cur.type]: cur.count,
totalAmount: (cur.count * cur.price ) + acc.totalAmount
}
}
}, {
main: 0,
drink: 0,
totalAmount: 0
});
const noOfComobosPresent = Math.min(orderInfo.main, orderInfo.drink);
const totalDiscountValue = noOfComobosPresent * 10;
const finalAmount = orderInfo.totalAmount - ((orderInfo.totalAmount * totalDiscountValue ) / 100) ;
console.log('finalAmount', finalAmount)

Cartesian product (all combinations) in array of multi-dimentonal objects with flexible length

There are several questions with answers on StackOverflow which shows how to find Cartesian product for various simple arrays. And a wonderful article on RosettaCode. But I can't find any solution for my problem.
I have an array of object with items, let's call it items:
let items = [{
id: 1
quantity: 2
field: "other_field"
},
{
id: 2
quantity: 3
field: "other_field"
}]
Every item in this array have a pricing/crafting method and we could receive it by request.
let pricing = getPricing(id) //item id
/*
Which will return to us:
*/
pricing = [
{pricing_id: 1, reagent_items: [/*array of objects, fields exactly items*/]},
{pricing_id: 2, reagent_items: [/*array of objects, fields exactly items*/]}
]
CARTESIAN PRODUCT PROBLEM:
As you may already understand, according to the answer's title, I want to receive all possible combinations of items AND reagent_items from pricing methods.
For example, if we have two items and all every item (of this 2) have just one pricing method, there will be 4 different combinations:
2 default items from items
first default item from items (item[0]) and all all reagent_items from getPricing for item[1]
second default item from items (item[1]) and all all reagent_items from getPricing for item[0]
both reagent_items from getPricing for both default items
I literally can't push reagent items to items (or remove item from items) array, because items can be the same (include each other) Instead of it, I am using my own Array.prototype.method for adding/removal items from items array. It does just the same as push/slice but in more elegant way, manipulating with id and quantity fields.
The actual problem lies in the field of arrays.length and for ... loop.
When we evaluate default Cartesian product we know before the array.length and it's elements.
But in my case I should getPricing every items, then receive array of methods..
Schema:
It's like:
Default: I_1 I_2 ... N
/ \ / \ / \
Get Pricing: [I_A, I_B][I_B, I_C] [IN_J, IN_K],
[IN_Y, IN_X, IN_Z],
So it's not about finding: Cartesian([I_A, I_B],[I_B, I_C]) but something like:
I_1 + I_2
I_1 + (I_B, I_C)
(I_A, I_B) + I_2
(I_A, I_B) + (I_B, I_C)
...
So default item includes each others and their reagent_items and it's simple to find all combinations of two items, but when it's become 3+..
My current pseudo code for now:
/* inside async*/
...
let ArrayOfPricing = [] //length 2, where each Array represent Array of `pricing` for each item
Promise.all(items.map(async item => {
const itemPricing = await getPricing(item.id);
ArrayOfPricing.push(itemPricing );
})
/**And what's next? **/
for (let item of items) {
}
So I can't understand what should I do next, at this stage.
Should I loop/iterate every item? But if so, even if I iterate every item one-by-one and change/remove it and add it's reagent_items (for every pricing) I still don't change the next item/element in array of items and it's length more then just 2, then I won't receive all the combinations, it will be like:
for items
↓
item[1] → for pricing
→ for reagent_items
↓
replace item[1] for all reagent_item
item[2] /** they are still there, but I need iterate over it's pricing , too **/
item[3]
or I could calculate all possible combinations by looking for items length and all pricing length and then form and empty new array with fixed length and push to all the combinations. But if I iterate over it for push with for loop... I should combine items and it will be for loop, inside for loop, inside for .. loop..
So to be honest I am out of ideas. I don't ask to write full working code instead of me, but to point me the way out of this loop. How to get every combination for every item and "baby-items" inside of it? How many cycles should I use then? I'll be grateful for any useful idea/pseudocode/post link which will help me to deal with this case. I'm also here and will check all the comments and answers below.
UPD a simple version of «from what I get, to what I want»
from this:
[
{
field: "original, can be cloned for every combination",
items:
[
{id: 1, quantity: 2},
{id: 2, quantity: 3}
]
}
]
to:
[
{
field: "original",
items:
[
{id: 1, quantity: 2},
{id: 2, quantity: 3}
]
},
{
field: "combination1",
items:
[
{id: 11, quantity: 1}, //from getPricing(item[0])
{id: 12, quantity: 1}, //from getPricing(item[0])
{id: 2, quantity: 3}
]
},
{
field: "combination2",
items:
[
{id: 1, quantity: 2},
{id: 22, quantity: 3} //from getPricing(item[1])
{id: 23, quantity: 3} //from getPricing(item[1])
]
},
{
field: "combination3",
items:
[
{id: 11, quantity: 1}, //from getPricing(item[0])
{id: 12, quantity: 1}, //from getPricing(item[0])
{id: 22, quantity: 3} //from getPricing(item[1])
{id: 23, quantity: 3} //from getPricing(item[
]
}
//can be any length according to getPricing of every item, and I modify original array, but we could create a new one.
]
As I promised, I have found a solution of my problem and I'd like to share it with StackOverflow Community.
Pseudo-code:
let array = [
{
field: "original, can be cloned for every combination",
items:
[
{id: 1, quantity: 2},
{id: 2, quantity: 3}
]
}
]
for (let element of array) {
let MethodsCombinations = [];
for await (let forCombinations of element.items.map((item, i) => {
return getMethod(item.id) //get Method for each item)
})) {
MethodsCombinations.push(forCombinations)
}
/* Cartesian product */
let vanilla_CartesianProduct = MethodsCombinations.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
/* Return array of arrays, with objects inside like default array */
/**
* Other logic with two for loops and merging all the combinations and quantities
* with (my own) modified Array.prototype.addItemToArray
*/
}
I am very grateful to this Nina Scholz's answer and her awesome StackOverflow profile with all answers about combinations/permutations and for providing a support.

sort objects in array with dynamic keys

I am trying to sort an array of dynamic key / values (Object) based on a property in the object. Can someone please provide me an example of how to do this. Ill try to create a similar structure to my issue
Order Class:
export class order {
id: number;
productRank: number;
productId: number;
productName: string;
}
There is a storeOrders observable array of orders in my component
storeOrders$: Observable<Array<orders>>;
The key in the orders array is dynamically generated and is stored with the key/value containing as key and order object as value
then the orders object for example is something like:
let orders = {
12_123: { id: 123, productRank: 3, productId: 23, productName: 'shirt'},
23_124: { id: 124, productRank: 1, productId: 14, productName: 'cologne'},
67_124: { id: 125, productRank: 2, productId: 45, productName: 'belt' }
}
When i subscribe to this data, how can I iterate over the storeOrders array and sort items based on the productRank. I have been trying to get this right but was not able to get this working, can someone please point out how to iterate over dynamic keys in an array and do a sort based on a property in the value object? Thank you, your helps really appreciated!
Your orders should be a object as below,
orders = {
12_123: { id: 123, productRank: 3, productId: 23, productName: 'shirt' },
23_124: { id: 124, productRank: 1, productId: 14, productName: 'cologne' },
67_124: { id: 125, productRank: 2, productId: 45, productName: 'belt' }
};
you can form the values by using Object.values(orders)
I use sortBy from lodash library as I'm used to it and you get the sorted values as
console.log(sortBy(Object.values(orders),['productRank']));
Stackblitz
to order a dynamic array of keys/values you can do this :
example of array :
arr = [
{de : 'germany'},
{fr : 'france'},
{uk : 'united kingdom'},
{tn : 'tunisia'}
]
arr.sort((a, b) => {
const a_key = Object.keys(a);
const a_value = a[a_key];
const b_key = Object.keys(b);
const b_value = b[b_key];
if (a_value == b_value) return 0;
return a_value > b_value ? 1 : -1;
});
that's all !

add a field and update another in array of objects

A query returns an array of objects from a collection. I want to add a field to each of the objects in that array, and update another field in every object of the array.
Products array before update:
[{ _id: 58d895b8ffc0346230a43a89,
event: 'Junior Girls 12s',
group: 'nonpro',
price: 50,
day: 'Friday' },
{ _id: 59d895b8ffc0346230a43a89,
event: 'Junior Girls 14s',
group: 'nonpro',
price: 50,
day: 'Friday', }]
My code to update the array of objects:
//add the field and changed price if late fee applies
for(var i = 0; i < products.length; i++) {
products[i].field = registered.frifield;
console.log(products[i].field);
if(settings.latefeeamt > 0 && settings.frilatefee === true) {
products[i].price += settings.latefeeamt;
}
console.log(products[i]);
console.log(events.friday);
}
How products array SHOULD look after update:
[{ _id: 58d895b8ffc0346230a43a89,
event: 'Junior Girls 12s',
group: 'nonpro',
price: 60,
day: 'Friday',
field: 'Main' },
{ _id: 59d895b8ffc0346230a43a89,
event: 'Junior Girls 14s',
group: 'nonpro',
price: 60,
day: 'Friday',
field: 'Main' }]
How can I get this to work? It console.logs the correct field inside the loop, but I get the original array when it's done.
As a rule, Don't change the array you are iterating, it's bad practice.
You can use map : array.map
var array = products.map((element, index) => {
element.field = registered.frifield;
if(settings.latefeeamt > 0 && settings.frilatefee === true) {
element.price += settings.latefeeamt;
}
return element
});
This should return an array, inside you will have the updated product list
Hi there i wasn't sure about this portion of code because settings isn't defined
if(settings.latefeeamt > 0 && settings.frilatefee === true) {
products[i].price += settings.latefeeamt;
}
Here you have a code that works i change the condition depending on the day, you can update it depending on your specification hope it helps
var products = [{
_id: '58d895b8ffc0346230a43a89',
event: 'Junior Girls 12s',
group: 'nonpro',
price: 50,
day: 'Friday'
},
{
_id: '58d895b8ffc0346230a43a89',
event: 'Junior Girls 14s',
group: 'nonpro',
price: 50,
day: 'moday',
}];
console.log(products);
for(product of products) {
product.field = 'Main';
if (product.day === 'moday') {
product.price = 30;
}
}
console.log(products);

Angular POS app, trying to generate an automatic purchase order from a POJO

I'm x-referencing a few things inside of a products object, namely, stock levels, low stock levels, suppliers, min-order qty, and cost. The method should create an order object for each supplier with each product whose stock is lower than low-stock levels.
Using this object format:
0: {
$$hashKey: "081",
$id: "-JPuOUmkvwpXB-99QuU6",
brand: "",
categories: Array[2],
events: Array[1],
imgUrl: "http://crayons-roleplay.weebly.com/uploads/2/4/9/6/24964708/735664_orig.jpg",
lowStock: "10",
messages: Array[1],
minOrder: "25",
name: "Crazy Crayons - Eco stars (individual)",
notes: Array[1],
price: "0.5",
quantity: "",
size: "",
sku: "912143",
stock: "21",
suppliers: [
0: {,
caseSize: ""
name: "Crazy Crayons"
price: 0.29
sku: ""
units: ""
tax: "15"
units: ""
upc: "91214"
updated: 1404907608273
}
]
1: {
$$hashKey: "082"
$id: "-JPuOUmnj9r6wGx27qVE"
brand: ""
categories: Array[1]
events: Array[1]
lowStock: 0
messages: Array[1]
minOrder: 4
name: "Lifefactory 12 oz Straw Cap - Coral"
notes: Array[1]
price: "26"
quantity: ""
size: ""
sku: "45635971011"
stock: "0"
suppliers: Array[1]
0: Object
caseSize: ""
name: "SOKO"
price: 13
sku: ""
units: ""
tax: ""
units: ""
upc: "45635971011"
updated: "2014-07-07T17:02:49.089Z"
}
action:function(){
Making a list of products with low stock:
angular.forEach(data.products.array, function(value){
if(value.stock < value.lowStock){
if(angular.isObject(value)){
$scope.generatedOrder.push({id:value.$id,upc:value.upc,'min-order':value.minOrder,supplier:value.suppliers});
}
}
});
$scope.supplierList = [];
$scope.supplierResults = [];
Pulling a list of suppliers from low-stock products:
angular.forEach($scope.generatedOrder, function(order){
angular.forEach(order.supplier, function(supplier){
if ($scope.supplierList.indexOf(supplier.name) == -1) {
$scope.supplierList.push(supplier.name);
}
});
});
angular.forEach($scope.supplierList, function(supplier){
$scope.supplierResults[supplier]=[];
});
Troublesome facet, I've tried many different ways to create an order key'd by the supplier. The closest I've come to victory is an array of supplier objects, with the right key but no value.
for(var i=0;i<Object.keys($scope.generatedOrder).length ;i++){
for(var j=0;j<$scope.supplierList.length;j++){
if (!angular.isArray(supplierResults[$scope.supplierList[j]])){
supplierResults[$scope.supplierList[j]] = [];
}
if ($scope.generatedOrder[i].supplier !== undefined){
if ( $scope.generatedOrder[i].supplier['0'].name === $scope.supplierList[j]) {
supplierResults[$scope.supplierList[j]].unshift($scope.generatedOrder[i]);
$scope.supplierResults = supplierResults;
}
}
}
}
Now, this almost works. The proper objects are built up and show in the array but the array reads length: 0 so I tried to have the length of $scope.supplierResults = supplierResults in here to make the browser think the array is full and normal.
$scope.supplierResults.length = supplierResults.length;
console.log($scope.supplierResults);
return $scope.supplierResults;
The end result should be like $scope.supplierResults = [{supplier:'xyz', items:[{id:'4g245t2g424g45t',name:apple, sku:453524, cost:3, qty:10}]}, {supplier:'abc',items:[{id:'3982462398uih23j',orange, sku:32545, cost:2, qty:12}]}]
EDIT: Here is updated, correct code. I accidentally left the target out of the "omit" and forgot to call value() to end my chain in the inner function right after the omit.
var result = _(data)
.filter(function(product){
return product.stock < product.lowStock && _.isObject(product);
})
.map(function(product){
return _(product.suppliers).map(function(supplier){
return {supplier:supplier, product : _.omit(product, 'suppliers')};
}).value();
})
.flatten()
.groupBy(function(productAndSupplier){
return productAndSupplier.supplier.name;
})
.map(function(productAndSupplierGroup){
return _(productAndSupplierGroup).reduce(function(memo, current){
memo.supplier = current.supplier;
memo.items.push(current.product);
return memo;
},{items:[]})
})
.value();
And a plunkr to show it in action: http://plnkr.co/edit/XAiyWauCdKmHTS3unqO5 (note that you will have to look in the browser console to see the output)

Categories

Resources