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 lessconst 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'
