DEV Community

Cover image for Public Solving: Elf Coffee Shop menu
Chris Bongers
Chris Bongers

Posted on • Edited on • Originally published at daily-dev-tips.com

Public Solving: Elf Coffee Shop menu

By now, this will be a couple days past the date that you could submit, so I think it's safe to start this series.

I've been participating in Marc Backes his unique Dev Advent Calendar.
Not to win the prizes, but just to try and solve the puzzles at hand.

The first puzzle I did was day 02.
The query is to help the elves solve a new menu because they have new drinks and even introduce flavors!

Describing the problem

After cloning the code and checking what we have to work with, I've noticed we got a helper function called: createMenu. It gets two parameters in the form of drinks, and flavors.

They look like this:

const drinks = [
    { name: 'Latte', price: 3 },
    { name: 'Macchiato', price: 3.5 },
    { name: 'Cappuccino', price: 4 },
    { name: 'Hot Chocolate', price: 4.5 },
]
const flavors = [
    { name: 'Ginerbread', price: 1.5 },
    { name: 'Cinnamon', price: 1 },
    { name: 'Peppermint', price: 0.5 },
    { name: 'Chestnuts', price: 1.25 },
    { name: 'Pumpkin Spice', price: 1.75 },
    { name: 'Apple Crisp', price: 2 },
    { name: 'Mrs. Claus Special', price: 3 },
]
Enter fullscreen mode Exit fullscreen mode

The desired output for this challenge is an array of each option on the menu.

Each drink can have each of the flavors + an undefined one, which will be the "normal" version.
The price is the price of the drink + the price of the flavor.

The output should also be sorted by drink name (a-z) and then by price (lowest to highest).

The output should be in this format:

[
    { drink: 'Cappuccino', flavor: undefined, price: 4 },
    { drink: 'Cappuccino', flavor: 'Peppermint', price: 4.5 },
    { drink: 'Cappuccino', flavor: 'Cinnamon', price: 5 },
]
Enter fullscreen mode Exit fullscreen mode

Right, let's get to it!

Solving the puzzle

My first thought was: this is a perfect option for the JavaScript map function.

I've started by wrapping the return in the drinks map like so:

return drinks.map((drink) => {
    // drink available
});
Enter fullscreen mode Exit fullscreen mode

This will loop over each drink.
Then we need to loop over each of the flavors inside this map.
Again a good opportunity to use the map.

return drinks.map((drink) => {
    return flavors.map((flavor) => {
      // flavor
    });
});
Enter fullscreen mode Exit fullscreen mode

Then we can simply return the object we want.
This object should look like this:

{ drink: 'Hot Chocolate', flavor: 'Gingerbread', price: 5.5 },
Enter fullscreen mode Exit fullscreen mode

Where the price is a sum of the drink price and the flavor price.

return drinks.map((drink) => {
    return flavors.map((flavor) => {
      return {
        drink: drink.name,
        flavor: flavor.name,
        price: drink.price + flavor.price,
      };
    });
});
Enter fullscreen mode Exit fullscreen mode

However, if we run this, we get a weird array like so:

[
  [
    { drink: 'Latte', flavor: 'Ginerbread', price: 4.5 },
  ],
  [
    { drink: 'Macchiato', flavor: 'Ginerbread', price: 5 },
  ],
]
Enter fullscreen mode Exit fullscreen mode

Hmm, not exactly what we want, but we can quickly fix this, by changing the top map, to a flatMap. This makes sure it's all on one level.

return drinks.flatMap((drink) => {
    return flavors.map((flavor) => {
      return {
        drink: drink.name,
        flavor: flavor.name,
        price: drink.price + flavor.price,
      };
    });
});
Enter fullscreen mode Exit fullscreen mode

That's better. Everything is now in one array.
However, we are missing the "basic" drink option!

My solution is to add an undefined flavor to the flavor array.
Decided to use unshift to add it as the first option in the array.

flavors.unshift({ name: undefined, price: 0 });
Enter fullscreen mode Exit fullscreen mode

If we run the script, the output is almost correct. We just need a way to sort everything.

Let's start by using the sort function to sort on the name of the drink.

return drinks.flatMap((drink) => {
  return flavors.map((flavor) => {
    return {
      drink: drink.name,
      flavor: flavor.name,
      price: drink.price + flavor.price,
    };
  });
})
.sort((a, b) => (a.drink < b.drink ? -1 : 1));
Enter fullscreen mode Exit fullscreen mode

This is the shorthand function for the sort option, ensuring the array is sorted based on the drink property which resembles the name.

Running the code shows that my favorite coffee, the Cappuccino, is now number one, so that's fine, but the prices are still scrambled!

No worries, we can check if the drink name is already correct. We should order based on the price.

If we would write it out completely it looks like this:

.sort((a, b) => {
  if (a.drink === b.drink) {
    return a.price < b.price ? -1 : 1;
  } else {
    return a.drink < b.drink ? -1 : 1;
  }
});
Enter fullscreen mode Exit fullscreen mode

We can also make this a bit smaller by using the inline ternary operator.

.sort((a, b) =>
  a.drink > b.drink
    ? 1
    : a.drink === b.drink
    ? a.price > b.price
      ? 1
      : -1
    : -1
);
Enter fullscreen mode Exit fullscreen mode

Some people like the first one better, some the second.
I would agree the entirely written one actually is easier to read in this case.

The moment of truth

Now it's time to put it to the test.

I've decided to run npm test to see if I pass the test.

🥁🥁🥁

Test turning green

And as you can see in the image above, the test turned green!
Yes, we solved it.

I'm not stating this is the "best" solution, but I wanted to show you my approach.
Let me know what your approach was or what you would do differently 👏

Thank you for reading, and let's connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

Top comments (2)

Collapse
 
lexlohr profile image
Alex Lohr

I find Array.prototype.reduce the ideal candidate for asymmetric input/output and it also allows us to forgo creating multiple arrays in memory, so the first part of my solution would look like this:

return drinks.reduce((flavored, drink) => {
  flavored.push({ drink: drink.name, flavor: undefined, price: drink.price });
  flavors.forEach((flavor) =>
    flavored.push({ drink: drink.name, flavor: flavor.name, price: drink.price + flavor.price })
  );
}, [])
Enter fullscreen mode Exit fullscreen mode

For the second part, I would utilize that 0 (equal sort value) it's coerced to false when using || and that string.prototype.localeCompare returns a number.

Collapse
 
dailydevtips1 profile image
Chris Bongers

Nice one!

For future puzzle i actually decided to rely a lot of reduce.
Must say this looks a bit cleaner.

Map made more sense if it was just the one array loop that we had to format.

Thanks for this wonderful alternative Alex 👏