I was recently asked for a codebase to make a shopping cart where the shipping price is the most expensive shipping price for the products by the same seller + the most expensive price from the other sellers.
For example, if there are 4 products in the cart, and 3 are from 1 seller, calculate the most expensive delivery from that seller + the delivery from the other seller.
the delivery category could be one of these
moto | auto | van
The price for auto and van is the same
Code
A look at the final composed function👀
const composeProcess = compose(
reduce((a, b: number) => a + b, 0),
map(convertToMoney),
values,
mapObjIndexed(mapOverProducts),
addNoise,
groupByCategory(category)
)
The first step is to group the products by category, for this case is the sellerID
const groupByCategory = (category: string) => (products: { item: Product }[]) => {
return products.reduce((acc, { item }) => {
return (acc = { ...acc, [item[category]]: [...(acc[item[category]] || []), item] })
}, {})
}
This function will return a tree of sellers with products
This DB only has 1 seller, so we are going to add some noise to test the function
Add noise 🔈
const addNoise = (nodes) => ({
...nodes,
bbc: [
{ id: 10, title: 'jaja', delivery: 'moto' },
{ id: 20, title: 'jeje', delivery: 'auto' },
],
ccc: [{ id: 14, title: 'jiji', delivery: 'auto' }],
})
Map over the products of each seller 🛍
What matters from this function is to return the most expensive delivery for each tree/node/seller.
this function is composed of 3 more that apply only to products of each tree/node/seller
mapObjIndexed(mapOverProducts)
where mapOverProducts:
const mapOverProducts = compose(
reduce(deliveryReducer, 0),
setOf,
map(mapOverCategory('delivery'))
)
mapOverCategory
const mapOverCategory = (category: string) => (arr) => arr[category]
mapOverCategory('delivery') will return [moto, auto, ...NDeliveries] for each node
setOf
will return the same array but avoiding repetitive delivery methods
const setOf = (values) => [...new Set(values)]
reduce(deliveryReducer, 0) ➕
As we only have 3 possible scenarios, we are going to use a reducer.
const deliveryReducer = (state, action) => {
switch (action) {
case 'moto':
return stateOverNumber(1)(state)
case 'auto':
return stateOverNumber(2)(state)
case 'van':
return stateOverNumber(3)(state)
default:
return state
}
}
Where stateOverNumber(n)(state) ❔
const stateOverNumber = (num: number) => (state: number) =>
(state < num ? num : state)
This is the moment we obtain which delivery is the most expensive from each node
At this point, we continue with the first composed function as we ended the work inside each node of products by a seller.
It's time for values
const composeProcess = compose(
reduce((a, b: number) => a + b, 0),
map(convertToMoney),
values,
mapObjIndexed(mapOverProducts),
addNoise,
groupByCategory(category)
)
Values
We no longer need the id of each seller, only its biggest delivery method
map(converToMoney) 💰
const convertToMoney = (deliveryTypes: number) => {
if (deliveryTypes === 1) {
return 2.9
} else {
return 4.9
}
}
1 being 'moto' and for the moment the price for the other methods is the same.
reduce((a, b: number) => a + b, 0) ➕
The las function adds the value of all the possible prices by a node 😁
Now, let's make an abstraction
Ok spoiler alert, I work with react ⚛️
We are going to abstract all of this logic in a hook that only takes as an argument the category for which we are going to split the nodes
const useAbstraction = (category: string) => {
const groupByCategory = (category: string) => (products: { item: Product }[]) => {
return products.reduce((acc, { item }) => {
return (acc = { ...acc, [item[category]]: [...(acc[item[category]] || []), item] })
}, {})
}
const mapOverCategory = (category: string) => (arr) => arr[category]
const setOf = (values) => [...new Set(values)]
const stateOverNumber = (num: number) => (state: number) => (state < num ? num : state)
const deliveryReducer = (state, action) => {
switch (action) {
case 'moto':
return stateOverNumber(1)(state)
case 'auto':
return stateOverNumber(2)(state)
case 'van':
return stateOverNumber(3)(state)
default:
return state
}
}
const mapOverProducts = compose(
reduce(deliveryReducer, 0),
setOf,
map(mapOverCategory('delivery'))
)
const addNoise = (sellers) => ({
...sellers,
bbc: [
{ id: 10, title: 'jaja', delivery: 'moto' },
{ id: 20, title: 'jeje', delivery: 'auto' },
],
ccc: [{ id: 14, title: 'jiji', delivery: 'auto' }],
})
const convertToMoney = (deliveryTypes: number) => {
if (deliveryTypes === 1) {
return 2.9
} else {
return 4.9
}
}
const composeProcess = compose(
reduce((a, b: number) => a + b, 0),
map(convertToMoney),
values,
mapObjIndexed(mapOverProducts),
addNoise,
groupByCategory(category)
)
return { composeProcess }
}
And now
const { composeProcess } = useAbstraction('sellerID')
const delivery = useCallback( ()
=> composeProcess(state.products)
, [state.products])
Outro
Probably there are better ways to give order hierarchy to the delivery methods, but this way works and it could be adapted to even group by cities or states.
My name is Jesus Cova, I work with the team at midnightouizard🧙🏼♂️ and you can contact me on twitter or jesuscovam@gmail.com
Top comments (0)