Skip to content

Instantly share code, notes, and snippets.

@GreggSetzer
Last active June 20, 2018 15:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GreggSetzer/a31bb28a955e6d410380b12faa52ca1d to your computer and use it in GitHub Desktop.
Save GreggSetzer/a31bb28a955e6d410380b12faa52ca1d to your computer and use it in GitHub Desktop.
Simplifying JavaScript code using Functional Programming
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Given an array of orders, find the top 3 customers with the
* highest lifetime value (those that spent the most over time).
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// * * * * * * * * * * * * * * * * * * * * * * * * * * *
// Start with the initial implementation.
//
// One approach to solving this task would be to create
// a map of the customerId and a sum of their order totals.
// Convert this map to an array so we can sort the orders
// from highest to lowest. Display the top three customers.
// * * * * * * * * * * * * * * * * * * * * * * * * * * *
const orders = [
{orderId: 1, customerId: 1, total: 10.50},
{orderId: 2, customerId: 1, total: 11.50},
{orderId: 3, customerId: 2, total: 12.50},
{orderId: 4, customerId: 3, total: 130.50},
{orderId: 5, customerId: 7, total: 24.50},
{orderId: 6, customerId: 2, total: 64.50},
{orderId: 7, customerId: 4, total: 40.50},
{orderId: 8, customerId: 4, total: 29.50},
{orderId: 9, customerId: 6, total: 61.50},
{orderId: 10, customerId: 7, total: 110.00}
];
//Build a temporary table to track total orders for a customer.
let orderTotalsMap = {};
for (let i = 0; i < orders.length; i++) {
let order = orders[i];
if (orderTotalsMap[order.customerId] === undefined) {
orderTotalsMap[order.customerId] = order.total;
} else {
orderTotalsMap[order.customerId] += order.total;
}
}
//Convert the map to an array and sort by the aggregated order total.
const keysArr = Object.keys(orderTotalsMap);
let orderTotalsArr = [];
for (let i = 0; i < keysArr.length; i++) {
const totals = {
customerId: keysArr[i],
total: orderTotalsMap[keysArr[i]]
}
orderTotalsArr.push(totals);
}
orderTotalsArr.sort(function (a, b) {
return b.total - a.total;
});
const topThreeCustomers = orderTotalsArr.slice(0, 3);
console.log('The top three customers:', topThreeCustomers);
// * * * * * * * * * * * * * * * * * * * * * * * * * * *
// It's difficult to understand what's going on in the
// example above. With some refactoring, this can be much
// more legible.
//
// To fulfill our requirements, we must:
// 1. Get the lifetime value for each customer.
// 2. Sort by the total amount and return the top 3 customers.
// * * * * * * * * * * * * * * * * * * * * * * * * * * *
const orders = [
{orderId: 1, customerId: 1, total: 10.50},
{orderId: 2, customerId: 1, total: 11.50},
{orderId: 3, customerId: 2, total: 12.50},
{orderId: 4, customerId: 3, total: 130.50},
{orderId: 5, customerId: 7, total: 24.50},
{orderId: 6, customerId: 2, total: 64.50},
{orderId: 7, customerId: 4, total: 40.50},
{orderId: 8, customerId: 4, total: 29.50},
{orderId: 9, customerId: 6, total: 61.50},
{orderId: 10, customerId: 7, total: 110.00}
];
//Get order totals for each customer.
const getCustomerLifetimeValues = (acc, order) => {
const {customerId, total} = order;
if (acc[customerId] === undefined) {
acc[customerId] = {customerId, total}
} else {
acc[customerId].total += total;
}
return acc;
}
//Comparator for sorting by total.
const comparator = (a, b) => b.total - a.total;
//Now, for the code that matters! Let's worry less about "how this works" and more about the "why we need it".
let lifetimeValuesObj = orders.reduce(getCustomerLifetimeValues, {});
let topThreeCustomers = Object.values(lifetimeValuesObj).sort(comparator).slice(0, 3);
console.log('The top three customers:', topThreeCustomers);
// For this solution, we are able to make reasable assumptions about the code because it's much easier to understand.
// By looking at lines 102 - 103, we know that the code will calculate the totals for each customer and provide top
// three customers by order totals. We didn't have to study the implementation to determine what is going on. Sweet!
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* You are given a task to reduce the elements in an array by 1,
* then, sum the values that are divisible by three. How would
* you accomplish this?
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// * * * * * * * * * * * * * * * * * * * * * * * * * * *
// Start with the initial implementation.
//
// Using a for loop, it might look something like this.
// This approach uses more cognitive processing power,
// because, you are required to understand the how this
// code works and why we are using it.
// * * * * * * * * * * * * * * * * * * * * * * * * * * *
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
numbers[i] = numbers[i] - 1;
if (numbers[i] % 3 === 0) {
sum += numbers[i];
}
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// Refactor #1.
//
// Using array helpers and function chaining, it might look
// something like this. This is an improvement, you focus
// more on the why and less on the how. But it is still
// difficult to read.
// * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
//Use function chaining with array helpers to get the result.
const result = numbers.map(n => n-1).reduce((acc, n) => acc += (n % 3 === 0) ? n : 0, 0);
// * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// Refactor #2.
//
// Refactor the array helpers to increase readability.
// In the process, get easier to test code by using pure
// functions. Most importantly, we can match the code up
// to our requirements.
//
// Array helpers:
// Map - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
// Reduce - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
// * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
const subtractOne = (n) => {
return n - 1;
}
const sumDivisibleByThree = (acc, n) => {
return acc += (n % 3 === 0) ? n : 0;
}
//Much easier to read! Compare this to the original solution.
const result = numbers.map(subtractOne).reduce(sumDivisibleByThree, 0);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment