Apply Combo Discount to a Food Order - javascript

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)

Related

how to calculate the missing quantity and items in javascript?

I have a product which is made of many x materials, each material can be made of other materials or be pure. If they are made of other materials, it is specified which materials are need with their needed quantity and currently available quantity for the item. I want to calculate how many material I have and I don't have, and for the material I don't have, I want to sum all the missing quantities.
Right know I am getting right the material quantity in the "first level" when the product is missing a material which is not used in submaterials. When I try to sum the missing submaterials the operation is not right.
How can I calculate the materials needed to create a product and each materials?
Here is the code I write:
const product = {
name: 'prod_1',
quantity: 1.2,
material_quantities: [
{ id: 2, quantity: 2 },
{ id: 1, quantity: 2 },
],
};
const materials = [
{
id: 1,
name: 'material1',
quantity: 1.2,
material_quantities: null,
},
{
id: 2,
name: 'material2',
quantity: 1.2,
material_quantities: [{ id: 1, quantity: 2 }],
},
];
function areEnoughMaterials(materialId, neededQuantity) {
const mainMaterial = materials.find(id === materialId);
const inStock = parseFloat(neededQuantity) <= parseFloat(mainMaterial.quantity);
if (mainMaterial?.material_list?.length === 0 && inStock) {
materialsStatus.available[materialId] = mainMaterial.quantity;
}
if (!inStock) {
if (!materialsStatus.missing[materialId]) {
materialsStatus.missing[materialId] =
parseFloat(neededQuantity) - parseFloat(mainMaterial.quantity);
} else {
materialsStatus.missing[materialId] =
materialsStatus.missing[materialId] + parseFloat(mainMaterial.quantity);
}
}
if (mainMaterial.material_quantities) {
for (const material of mainMaterial.material_quantities) {
areEnoughMaterials(material.id, material.quantity);
}
}
return;
}
let materialsStatus = {
available: {},
missing: {},
};
for (const material of product.material_quantities) {
areEnoughMaterials(material.id, material.quantity);
}
The output I am expecting is this:
{
available: { 2:1.2, 1:1.2 },
missing:{ 2:0.8, 1:3.6 }
}

How do I incorporate the index of an array while defining a property of said array within a class in JavaScript?

Sorry if the title makes no sense.. let me explain
Say I have the following 2d array.. the first array representing ice cream and the second representing milkshakes
menu = [ ['vanilla', 'chocolate', 'almond'],
['vanilla', 'pineapple', 'strawberry'] ]
Now I create a class that takes this array as input
class cafe{
constructor(menu){
this.iceCreams = menu[0]
this.milkshakes = menu[1]
}
}
Now I want to define a property called 'price' for each flavor of milkshake.
this.milkshakes[n].price = < a function that computes price based on value of n >
so that i can access them like this :
cafe.milkshakes[0].price
So how do I incorporate the index 'n' of the array while defining the property
I haven't tried anything bcos I dont know how to even approach this ☹️
You can do it in your constructor.
You can grab the names, and call map function on it and do whatever you want. Please check the following example. There, calculatePrice is a function that takes the index and returns the price based on the index.
class Cafe {
constructor (menu) {
this.iceCreams = menu[0].map((flavor, index) => {
return {
flavor,
price: calculatePrice(index)
}
});
this.milkshakes = menu[1].map((flavor, index) => {
return {
flavor,
price: calculatePrice(index)
}
});
}
This is a minimal answer.
UPDATE:
For a detailed and improved answer: https://codesandbox.io/s/cafe-example-wxp2c4
So, in the milkshakes array you need each item as an object data structure, not a string.
menu = [ ['vanilla', 'chocolate', 'almond'],
[{ flavor: 'vanilla' }, { flavor: 'pineapple' }, { flavor: 'strawberry' }] ]
and then you can loop through and set the price, something like this.
menu.milkshakes.forEach((item, index) => item.price = index))
you can use objects:
menu = [
[
{
name: "vanilla",
price: 200,
},
{
name: "chocolate",
price: 200,
},
{
name: "almond",
price: 200,
},
],
[
{
name: "vanilla",
price: 200,
},
{
name: "pineapple",
price: 200,
},
{
name: "strawberry",
price: 200,
},
],
];
and then:
class cafe{
constructor(menu){
this.iceCreams = menu[0]
this.milkshakes = menu[1]
}
}
now iceCreams and milshakes have the property price and name
example:
iceCreams[n].price
iceCreams[n].name

How to do projection in TypeScript?

I want to populate orders which is an array of type Order. The expected result is orders=[{id:1,qt:4},{id:2, qt:2},{id:3,qt:2}]. How to do so in TypeScript? I am new to it.
export class Product {
constructor(public id: number, public name: string, public price: number) {}
}
export interface Order {
id: number;
qt: number;
}
export const products: Product[] = [
new Product(1, 'Apple', 2.1),
new Product(2, 'Banana', 2.2),
new Product(3, 'Chocolate', 2.3),
new Product(4, 'Dessert', 2.4),
];
export const cart: Product[] = [
products[0],
products[0],
products[2],
products[1],
products[2],
products[0],
products[1],
products[0],
];
export const orders: Order[] = [];
Edit
For those who want to know how
orders=[{id:1,qt:4},{id:2, qt:2},{id:3,qt:2}] is obtained.
In the cart:
the quantity of apples (id:1) is qt:4
the quantity of bananas (id:2) is qt:2
the quantity of chocolates (id:3) is qt:2
So by using cart, I have to obtain orders=[{id:1,qt:4},{id:2, qt:2},{id:3,qt:2}]. It should be clear.
Since you're looking for a "LINQ-like" solution, you probably want to use the higher order functions like map/filter/reduce.
Strictly, your problem cannot be solved purely with LINQ projections. Those merely represent map (Select), concatMap/flatMap (SelectMany), and zip (Zip). Your problem involves counting the occurences of each id throughout the entire array.
Pretty much every data manipulation problem can be solved with higher order folds, i.e reduce in javascript land, Aggregate in C# land. This one is no exception. The first thing to do, is to count the occurrences of each id, and build a counter object.
cart.reduce((acc, { id }) => {
acc[id] = (acc[id] ?? 0) + 1;
return acc;
}, {} as Record<number, number>);
Essentially, you start the fold operation with an empty object, then add each id and its occurrence count. Every time an id is encountered in the cart array, you increment its count in the object. If the id doesn't already exist in the accumulating object, nullish coalescing (acc[id] ?? 0) uses 0 and increments that instead.
This will give you-
{ '1': 4, '2': 2, '3': 2 }
Now, you need to turn this into-
[ { id: 1, qt: 4 }, { id: 2, qt: 2 }, { id: 3, qt: 2 } ]
For that, use Object.entries on the fold result to get-
> Object.entries({ '1': 4, '2': 2, '3': 2 })
[ [ '1', 4 ], [ '2', 2 ], [ '3', 2 ] ]
Finally, a simple map is all you need-
Object.entries(...).map(([id, qt]) => ({ id: Number(id), qt }))
Combining all that, you have-
export const orders: Order[] = Object.entries(
cart.reduce((acc, { id }) => {
acc[id] = (acc[id] ?? 0) + 1;
return acc;
}, {} as Record<number, number>)
).map(([id, qt]) => ({ id: Number(id), qt }));
One thing to note here is that Object.entries is pretty inefficient since it builds up an array instead of an iterator. If you're into efficiency, roll an iterator version of Object.entries and use that instead, using generator functions-
function* objEntries<T>(x: Record<string, T>): IterableIterator<[string, T]> {
for (const k in x) {
yield [k, x[k]];
}
}

ES6 reduce function affecting array outside of scope

I've rewritten this into a simplified form to demonstrate, I have an array of pickers who have an array of time entries, I'm using reduce to summarise time entries by type on the pickers & then a second reduce to show global entries across both pickers.
The first reduce per picker works as expected.
The second reduce on global time entries works as expected but somehow changes the entries for the first picker ( Sam ).
Sam & John pick the same amount.
Apples 2h, Peaches 2h, Lemons 1h
Is there a better way to write this? Is there a concept I've failed to understand?
function testBug() {
// Reducer Function
function entryReducer(summary, entry) {
// find an index if the types of fruit are the same
let index = summary.findIndex((item) => {
return item.type.id === entry.type.id;
});
if (index === -1) {
summary.push(entry);
} else {
summary[index].hours = summary[index].hours + entry.hours;
}
return summary;
}
let pickers = [
{
id: 1,
identifier: "Sam Smith",
timeEntries: [
{
type: {
id: 1,
name: "Apples",
},
hours: 1,
},
{
type: {
id: 2,
name: "Peaches",
},
hours: 1,
},
{
type: {
id: 3,
name: "Lemons",
},
hours: 1,
},
{
type: {
id: 1,
name: "Apples",
},
hours: 1,
},
{
type: {
id: 2,
name: "Peaches",
},
hours: 1,
},
],
},
{
id: 2,
identifier: "John Snow",
timeEntries: [
{
type: {
id: 1,
name: "Apples",
},
hours: 1,
},
{
type: {
id: 2,
name: "Peaches",
},
hours: 1,
},
{
type: {
id: 3,
name: "Lemons",
},
hours: 1,
},
{
type: {
id: 1,
name: "Apples",
},
hours: 1,
},
{
type: {
id: 2,
name: "Peaches",
},
hours: 1,
},
],
},
];
let pickersSummary = [];
let timeEntriesSummary = [];
for (const picker of pickers) {
if (picker.timeEntries.length > 0) {
// reduce time entries into an array of similar types
picker.timeEntries = picker.timeEntries.reduce(entryReducer, []);
// push to pickers summary arr
pickersSummary.push(picker);
// push time entries to a summary array for later reduce
picker.timeEntries.map((entry) => timeEntriesSummary.push(entry));
}
}
// Reduce time entries for all pickers
// Sam & John pick the same amount
// Apples 2h
// Peaches 2h
// Lemons 1h
// **** If I run this Sam's entries are overwritten with the global time entries ***
timeEntriesSummary = timeEntriesSummary.reduce(entryReducer, []);
const results = { pickersSummary, timeEntriesSummary };
console.log(results);
}
testBug();
module.exports = testBug;
Even though with each reducer you pass a new array [], the actual objects contained by these arrays could be shared. This means when you edit one of the objects in array "A", the objects could also change in array "B".
You know how some languages let you pass variables by value or by reference and how this fundamentally changes how values are handled? JavaScript technically uses call-by-sharing. I suggest reading this other answer: Is JavaScript a pass-by-reference or pass-by-value language?
once an element in an array is pushed into a different array it is separate in memory?
No, it isn't. In JavaScript you will always remember when you made an individual copy of an object (or at least wanted to), because that needs some effort, see What is the most efficient way to deep clone an object in JavaScript? or How do I correctly clone a JavaScript object?
So, just like when you use a=b, push(a) into an array refers the original object. See this example where there is a single object accessible via two variables (x and y), and via both elements of array z. So modifying it as z[1] affects all the others:
let x={a:5};
let y=x;
let z=[x];
z.push(y);
z[1].a=4;
console.log(x);
console.log(y);
console.log(z[0]);
console.log(z[1]);
As your objects are value-like ones and do not have anything what JSON would not support (like member functions), JSON-based cloning can work on them:
function testBug() {
// Reducer Function
function entryReducer(summary, entry) {
// find an index if the types of fruit are the same
let index = summary.findIndex((item) => {
return item.type.id === entry.type.id;
});
if (index === -1) {
//summary.push(entry);
summary.push(JSON.parse(JSON.stringify(entry))); // <--- the only change
} else {
summary[index].hours = summary[index].hours + entry.hours;
}
return summary;
}
let pickers = [
{id: 1, identifier: "Sam Smith", timeEntries: [
{type: {id: 1, name: "Apples",}, hours: 1,},
{type: {id: 2, name: "Peaches",}, hours: 1,},
{type: {id: 3, name: "Lemons",}, hours: 1,},
{type: {id: 1, name: "Apples",}, hours: 1,},
{type: {id: 2, name: "Peaches",}, hours: 1,},],},
{id: 2, identifier: "John Snow", timeEntries: [
{type: {id: 1, name: "Apples",}, hours: 1,},
{type: {id: 2, name: "Peaches",}, hours: 1,},
{type: {id: 3, name: "Lemons",}, hours: 1,},
{type: {id: 1, name: "Apples",}, hours: 1,},
{type: {id: 2, name: "Peaches",}, hours: 1,},],},];
let pickersSummary = [];
let timeEntriesSummary = [];
for (const picker of pickers) {
if (picker.timeEntries.length > 0) {
// reduce time entries into an array of similar types
picker.timeEntries = picker.timeEntries.reduce(entryReducer, []);
// push to pickers summary arr
pickersSummary.push(picker);
// push time entries to a summary array for later reduce
picker.timeEntries.map((entry) => timeEntriesSummary.push(entry));
}
}
// Reduce time entries for all pickers
// Sam & John pick the same amount
// Apples 2h
// Peaches 2h
// Lemons 1h
// **** If I run this Sam's entries are overwritten with the global time entries ***
timeEntriesSummary = timeEntriesSummary.reduce(entryReducer, []);
const results = { pickersSummary, timeEntriesSummary };
console.log(results);
}
testBug();
Now it probably displays what you expected, but in the background it still alters the pickers themselves, you have that picker.timeEntries = ... line running after all. It may be worth mentioning that const something = xy; means that you can not write something = yz; later, something will stick with a given entity. But, if that entity is an object, its internals can still be changed, that happens with picker.timeEntries above (while writing picker = 123; would fail).

Group by list of dictionary in 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.

Categories

Resources