Bang for Your Buck: Exercise
Write a point-free function to get the 3 top-rated meals <= a given price.
Given a maximum price and menu, return the 3 top-rated meals for that price or less.
Usage
// top 3 meals for $12 or less
const best3Meals = getTop3MealsFor(12, menu);
/*
[{
name: 'Lamb Gyro',
price: 11.86,
rating: 4.9
}, {
name: 'House Salad',
price: 9.00,
rating: 4.65
}, {
name: 'Gigantus Fries',
price: 11.86,
rating: 4.5
}]
*/
Your solution must be point-free.
Notice how the menu parameter’s supplied last, aligning with Ramda’s “data-last” pattern. This lets you compose getTop3MealsFor with other functions to manipulate menu in different ways.
const firstPerfectMeal = pipe(
getTop3MealsFor(20),
filter(
both(isVegetarian, isLactoseFree)
),
head
);
This finds the best $20 or less vegetarian/lactose-free meal. All it needs is a menu parameter and off it goes!
menu.js
export default [{
name: 'Lamb Gyro',
price: 11.86,
rating: 3.31
}, {
name: 'Chicken Gyro',
price: 11.48,
rating: 3.84
}, {
name: 'Mixed Gyro',
price: 11.69,
rating: 4.81
}, {
name: 'Jumbo Lamb',
price: 12.31,
rating: 2.32
}, {
name: 'Lamb Kebab',
price: 8.85,
rating: 4.82
}, {
name: 'Jumbo Mixed',
price: 14.96,
rating: 3.37
}, {
name: 'Chicken Wings',
price: 11.17,
rating: 3.56
}, {
name: 'Biggie Fries',
price: 14.36,
rating: 3.86
}, {
name: 'Medium Fries',
price: 10.36,
rating: 4.96
}, {
name: 'Medium Curly Fries',
price: 10.37,
rating: 2.52
}, {
name: 'Biggie Curly Fries',
price: 15.07,
rating: 3.73
}, {
name: 'House Salad',
price: 11.39,
rating: 4.86
}, {
name: 'Apartment Salad',
price: 14.38,
rating: 2.80
}, {
name: 'Steak',
price: 12.50,
rating: 4.06
}, {
name: 'Shrimp Gyro',
price: 14.57,
rating: 3.48
}];
Review #
Let’s review the steps
- Get everything maxPrice or less
- Sort by rating
- Get the top 3
Here’s a vanilla solution
import menu from './menu';
const getTop3MealsFor = (maxPrice, menu) => menu
.filter((item) => item.price <= maxPrice)
.sort((a, b) => b.rating - a.rating)
.slice(0, 3);
const result = getTop3MealsFor(12, menu);
console.log({ result });
Ramda has slice and sort functions. Using them lets us pipe the entire function to make it point-free.
import { pipe, slice, sort } from 'ramda';
import menu from './menu';
const getTop3MealsFor = pipe(
(maxPrice, menu) => menu.filter((item) => item.price <= maxPrice),
sort((a, b) => b.rating - a.rating),
slice(0, 3)
);
const result = getTop3MealsFor(12, menu);
console.log({ result });
We can point-free up filter’s predicate function using propSatisfies. It tests an object’s property against a function to return true or false.
import { gte, pipe, propSatisfies, slice, sort } from 'ramda';
import menu from './menu';
const getTop3MealsFor = pipe(
(maxPrice, menu) => menu.filter(propSatisfies(gte(maxPrice), 'price')),
sort((a, b) => b.rating - a.rating),
slice(0, 3)
);
const result = getTop3MealsFor(12, menu);
console.log({ result });
I personally wouldn’t do that, but it’s fun to experiment.
Comparators #
I would, however, play around with comparators. These are functions that compare data for sorting.
Ramda’s descend function is a descending comparator, meaning a function that sorts entries from biggest to smallest. The flip side would be R.ascend, an ascending comparator.
We can break the sorting logic into a function
const byPrice = descend(prop('rating'));
Then use it like so
sort(byPrice);
It reads like a sentence!
import { descend, gte, pipe, prop, propSatisfies, slice, sort } from 'ramda';
import menu from './menu';
const byPrice = descend(prop('rating'));
const getTop3MealsFor = pipe(
(maxPrice, menu) => menu.filter(propSatisfies(gte(maxPrice), 'price')),
sort(byPrice),
slice(0, 3)
);
const result = getTop3MealsFor(12, menu);
console.log({ result });
https://ramdajs.com/docs/#slice
Number → Number → [a] → [a]
Number → Number → String → String
Parameters
Added in v0.1.4
Returns the elements of the given list or string (or object with a slice method) from fromIndex (inclusive) to toIndex (exclusive).
Dispatches to the slice method of the third argument, if present.
R.slice(1, 3, ['a', 'b', 'c', 'd']); //=> ['b', 'c']
R.slice(1, Infinity, ['a', 'b', 'c', 'd']); //=> ['b', 'c', 'd']
R.slice(0, -1, ['a', 'b', 'c', 'd']); //=> ['a', 'b', 'c']
R.slice(-3, -1, ['a', 'b', 'c', 'd']); //=> ['b', 'c']
R.slice(0, 3, 'ramda'); //=> 'ram'