Advent of Code 2020 Day 6 - Solution + Tutorial (TypeScript)
- [ 1 ]
- [ 2 ]
- [ 3 ]
- [ 4 ]
- [ 5 ]
- [ 6 ]
- [ 7 ]
- [ 8 ]
- [ 9 ]
- [ 10 ]
- [ 11 ]
- [ 12 ]
- [ 13 ]
- [ 14 ]
- [ 15 ]
- [ 16 ]
- [ 17 ]
- [ 18 ]
I'll try to update asap. Please bear in a mind that I'll probably lag one or multiple days behind.
Prerequisites
I assume you've put your puzzle input into an array called lines
where each array item is a line of the input text file.
It's up to you to either parse the text file or create an array by hand.
const lines = [
"cedziyl",
"lnemy",
"",
"oujdnhgwlqfbmpcrevktaizs",
"covqbthupgradfnijslexwk",
"",
"eawjhlxrtc",
…
];
Solution
Puzzle
Just to make sure, you know what I'm talking about, take a look at today's puzzle:
Part 1
This time, we should check the answers to the customs declaration forms. We are given the answers for groups of people. For part 1, for each group, we'll have to count the number of questions to which anyone in the group answered "yes".
First, let's look at the input again. The data per group is split over several lines. Let's merge this data similar to like we did it on Day 4: Passport Processing.
We want that every item in the group
array represents exactly one group. So that
"cedziyl",
"lnemy",
"",
"oujdnhgwlqfbmpcrevktaizs",
"covqbthupgradfnijslexwk",
"",
"eawjhlxrtc",
…
becomes
["cedziyl", "lnemy"],
["oujdnhgwlqfbmpcrevktaizs", "covqbthupgradfnijslexwk"],
["eawjhlxrtc", …],
…
That would make the data much easier to work with. Let's go. We have our array lines
. We can transform this to the groups array we want.
const groups = lines
.join("\n")
.split("\n\n")
.map((group) => group.split("\n"));
Nice! First, we join all lines with newlines. Then, we split if there is a double newline. That tells us, that the data for a new group begins. Now we have the data for each group in a single line. Let's split this data by using the newlines.
Our groups
array now looks like this:
const groups = [
["cedziyl", "lnemy"],
["oujdnhgwlqfbmpcrevktaizs", "covqbthupgradfnijslexwk"],
["eawjhlxrtc", …],
…
];
You could say its type is string[][]
. It's an array of string arrays.
Good. Now it's much easier to work with the data. What should we do again? Basically, we want to find out how many unique answers (characters) a group has given. These counts per group should be added together, and the puzzle is solved.
Okay, so we have to do something per group. How can we find the unique characters per group. Some of you may think
that we should use something like the lodash
library. It exports a function called .uniq
. Well, yeah, that would be possible.
However, let's solve it without using external dependencies.
Good thing TypeScript has a data structure that fits our use-case. We can make use of a Set
. Look:
const set = new Set(["a", "c", "d", "c"]);
This would result in a Set
of size 3. Why? Because a set holds unique values.
No duplicate values are allowed. So the set's contents are a, c, d
. Nice, this way we don't need
external dependencies like lodash
.
Now let's apply this to our groups.
groups
.map((group) => {
const set = new Set([...group.join("")]);
return set.size;
})
Wow, there might be happening a bit too much for you. I'll try to explain:
First, we want to transform our groups so that we know the count of unique answers per group. That's why
we are using the Array#map
method here. We transform the groups array into another array. Then, we want to find the unique values per group.
Therefore, we can first join all answers per group. That leaves us with a long string like cedziyllnemy
.
We can then use the spread operator to split the string into an array where each item is a single character.
These characters are then used to create a new set. The set removes any duplicates, and so we just have to return the size of the set.
Now, we have an array of numbers. Each number represents the count of unique answers per group. As a last step,
we have to add those together, and our puzzle is solved. We can chain the Array#reduce
method to our above code:
groups
.map((group) => {
const set = new Set([...group.join("")]);
return set.size;
})
.reduce((previousValue, currentValue) => {
return previousValue + currentValue;
});
Now all unique answer counts per group (set size) are added together. The result is our puzzle solution. Tada!
For completeness, here is the full solution:
const groups = lines
.join("\n")
.split("\n\n")
.map((group) => group.split("\n"));
return groups
.map((group) => {
const set = new Set([...group.join("")]);
return set.size;
})
.reduce((previousValue, currentValue) => {
return previousValue + currentValue;
});
Part 2
Oof! Again we've misread something. We don't want to know if ANYONE in a group answered yes. We want to know if EVERYONE in a group answered yes to a specific question.
However, I've got good news for you. We can almost completely reuse our implementation from part 1.
First, let's create the groups array again like in part 1:
const groups = lines
.join("\n")
.split("\n\n")
.map((group) => group.split("\n"));
Nice! If this is confusing you, look up the explanation in part 1. We already did this.
Now, again, we want to transform the group array into the answer counts. This time, however, we have to make sure that these answers were given by every person in a group. Therefore, we'll have to change our previous implementation a bit.
Remember, we used this:
groups
.map((group) => {
const set = new Set([...group.join("")]);
return set.size;
})
The problem is, this does not check if everyone in the group has given the answer. However, at least we know which answers where given at all.
All values in set
are the answers, ANYONE in this group has given. Now we can simply check, whether this answer was given by EVERYONE:
groups
.map((group) => {
const set = new Set([...group.join("")]);
return [...set].filter((character) => {
return group.every((person) => person.includes(character));
}).length;
})
So, again, we are creating our set
. We did so like in part 1, so read up the explanation there, if necessary.
Now our set contains every answer given by this group. We can filter out every answer that was not given by EVERYONE.
Therefore, we'll use the spread operator to convert our set to an array. Then, we'll use the Array#filter
method to filter out
characters. Like in another day's puzzle, we use the Array#every
method on the group
array here.
After filtering, we can use the length
property and we know how many answers where given by EVERYONE.
Nice! We've collected all unique answers and then removed every answer that was not given by EVERY person of that group. The last thing to do is adding up the counts. This is done like in part 1:
groups
.map((group) => {
const set = new Set([...group.join("")]);
return [...set].filter((character) => {
return group.every((person) => person.includes(character));
}).length;
})
.reduce((previousValue, currentValue) => {
return previousValue + currentValue;
});
That's it! We've solved the puzzle. Here's the full solution:
const groups = lines
.join("\n")
.split("\n\n")
.map((group) => group.split("\n"));
return groups
.map((group) => {
const set = new Set([...group.join("")]);
return [...set].filter((character) => {
return group.every((person) => person.includes(character));
}).length;
})
.reduce((previousValue, currentValue) => {
return previousValue + currentValue;
});
Conclusion
Today's puzzle required us to find a format that's easy to use. However, data split over several lines shouldn't be an issue anymore. Also, we had to find unique values. Therefore, I've shown you a way how to do it without external dependencies.
Thanks a lot for reading this post. Please consider sharing it with your friends and colleagues. See you tomorrow!
PS.: Here's a different approach for today's puzzle: https://twitter.com/kais_blog/status/1335597350283317254