You have indicated that each item can only possess one of the following attributes: is_sweet
, is_spicy
, or is_bitter
, set to true
.
With this assumption in mind, an approach we can take is to group the items based on a calculated quantity value of
x.is_sweet*4 + x.is_spicy*2 + x.is_bitter*1
for each item
x
. This quantity will equate to 1 for items with only
is_bitter: true
, 2 for those with only
is_spicy: true
, and 4 for those with only
is_sweet: true
.
After grouping by this quantity, you can utilize _.values
to eliminate the keys generated by _.groupBy
:
_.values(_.groupBy(foodsData, x => x.is_sweet*4 + x.is_spicy*2 + x.is_bitter*1))
For better readability, you can assign a meaningful name to the predicate:
const flavour = food => food.is_sweet*4 + food.is_spicy*2 + food.is_bitter*1;
let groupedFoods = _.values(_.groupBy(foodsData, flavour))
It is important to note that even if items are allowed to have more than one of these three attributes as true, the code will still provide sensible results, representing the eight groups corresponding to [is_sweet, is_spicy, is_bitter]
being equal to [0,0,0]
, [0,0,1]
, [0,1,0]
, [0,1,1]
, [1,0,0]
, [1,0,1]
, [1,1,0]
, and [1,1,1]
.
In retrospect, the predicate flavour
doesn't necessarily have to produce a numerical output for each entry food
; considering the previous paragraph, it suffices to extract the three values food.is_sweet
, food.is_spicy
, and food.is_bitter
, and store them in an array. Leverage Lodash's _.over
function for this purpose:
const flavour = _.over([is_sweet, is_spicy, is_bitter]);
A brief discussion on the readability of the aforementioned solution.
_.groupBy
offers a higher level of abstraction compared to functions resembling reduce
; therefore, it provides sufficient expressiveness without unnecessary complexity for the task at hand. In fact, _.groupBy
could be implemented using reduce
. Here's a basic implementation exemplifying this concept:
const groupBy = (things, f) => {
return things.reduce((acc, item) => {
if (acc[f(item)] == undefined)
acc[f(item)] = [item];
else
acc[f(item)].push(item);
return acc;
}, {});
}
// Both examples yield identical results:
groupBy([1,2,3,4,4,4], x => x % 2 == 0);
_.groupBy([1,2,3,4,4,4], x => x % 2 == 0);
The solution is remarkably concise: the appearance or perceived complexity doesn't hinder understanding once familiarity with this approach is developed. Reviewing the code snippet below showcases its clarity:
_.values(_.groupBy(foodsData, flavour))
Although already quite readable, incorporating the lodash/fp
module further enhances clarity:
_.values(_.groupBy(flavour, foodsData))
How different is this from the following English sentence?
"retrieve the values obtained by grouping based on flavor the foods"
To claim this is not readable amounts to disregarding reality.
Longer solutions employing lower-level constructs such as for
loops or (slightly improved) utilities akin to reduce
necessitate parsing of the code rather than straightforward comprehension. Deciphering the functionalities of these solutions typically demands careful inspection of their implementations. Even extracting the function (acc, item) => { … }
from the call to reduce
and assigning it a suitable name won't alleviate confusion. Ultimately, you're just displacing the problem elsewhere, complicating understanding for subsequent readers.
_.over
may appear intimidating initially:
const flavour = _.over([is_sweet, is_spicy, is_bitter]);
Nevertheless, investing effort into grasping and embracing the intricacies of _.over
pays off by expanding your knowledge base. Effectively, it's akin to acquiring a new language proficiency—no more, no less. The explanation of _.over
is straightforward as well:
_.over([iteratees=[_.identity]])
Generates a function that executes iteratees
with the received arguments and returns their outcomes.
Its simplicity stands evident:
_.over(['a', 'b', 'd'])({a: 1, b: 2, c: 3}) == [1, 2, undefined]