Public Solving: Secret Santa in JavaScript

Public Solving: Secret Santa in JavaScript

Β·

4 min read

The elves want us to make a secret Santa script for today's tasks.

If you are unaware of this, it's basically a system where we get provided a list of names, and we must assign random people to each other. These people must then buy gifts for each other.

They have given us some rules to work with:

  • Everyone should have a secret Santa
  • You cannot be your own secret Santa
  • When there are duplicate names, we should throw an error
  • The secret Santa's should be randomized

You can find the puzzle here.

Thinking about the solution

This is actually a pretty hard one, and it took me a while to get it completely working in order.

The main issue is that it needs to be randomized.

Let's take some examples.

We have the following names: Bob, Anna, Jim.

When we run the script, we start with Bob, his secret Santa will be Anna. Then we get to Anna, and let's just assign Bob.

But wait, now we can't assign anyone to Jim...

As you can see, it gets a bit complicated. But no worries, we'll sort it out to be bulletproof.

Creating secret Santa in JavaScript

Let's start with the most straightforward task. Luckily, we can throw an error if there are duplicate names in the name array.

if (hasDuplicates(names)) throw Error('DUPLICATE_NAMES');

This hasDuplicates function is provided by the puzzle, but it looks like this:

export const hasDuplicates = (arr) => {
  return new Set(arr).size !== arr.length;
};

Then we need to loop over all the names, we could opt for the map method, but this will bring one edge case (more later).

So I decided to go with the reduce method instead.

return names.reduce((acc, name) => {
    // Todo
    return acc;
}, []);

This is what the basic reduce looks like. We get the acc variable which is basically the previous value. And initially, the default value, which we set to [].

This is already great, but we want to keep track of our assigned names.

I decided to create a new variable outside the function to randomly sort the names.

const secretSantaNames = [...names].sort(() => 0.5 - Math.random());

Then we want to retrieve one of those names, but it cannot be our own name.

For this, we simply use the sort method and return the first hit.

let secretSanta = secretSantaNames.filter(
  (secretSantaName) => secretSantaName !== name
)[0];

The filter makes sure we don't match the user's reduce loop name.

Then we need to remove this name from our array of possible secret Santa's for the next name.

This is an excellent opportunity for the splice method.

secretSantaNames.splice(
  secretSantaNames.findIndex((i) => i === secretSanta),
  1
);

Note: the splice method manipulates the original array!

And then, we can modify the acc variable and push this option match to it.

acc.push({
  name,
  secretSanta,
});

Right, almost there. However, there is a slight edge case where we could still have one name not assigned as we described in the problem.

To solve this, I decided to check if our secretSanta is undefined and swap this one with the first match.

Note: Remember I said the reduce would be easier than map. This is why.

if (secretSanta === undefined) {
  // Edge case where last person was assigned to their own name
  // Simply swap with the first one.
  secretSanta = acc[0].secretSanta;
  acc[0].secretSanta = name;
}

This function will only fire if the secret Santa is undefined and simply swap this one with the first hit. This will also work, as we only have one name left.

Let's try it out by running the tests.

Test going green

And yes, we did it!

I would love to hear what you think of my approach 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

Did you find this article valuable?

Support Daily Dev Tips by becoming a sponsor. Any amount is appreciated!