🎄 Advent of Code 2'023 - Day 04

Day 04: Scratchcards

* Cf. aoc. d. iv xxiii

Look at how far we’ve come! We’re already at the fourth day of the challenge 🎉

Today’s challenge is about scratchcards. We have a list of scratchcards and we need to find the sum of the prizes of the winning scratchcards. Lot of fun, right?


Parsing The Input

Our input looks like this:

Card 1: 41 48 83 86 17 | 83 86  6 31 17  9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3:  1 21 53 59 44 | 69 82 63 72 16 21 14  1
Card 4: 41 92 73 84 69 | 59 84 76 51 58  5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11

α

The first part of the input after the Card <n>: is the winning numbers, and the second part is the scratchcard numbers. Let’s get both of them.

string winning_numbers = scratchcard.substr(
   scratchcard.find(':') + 2,
   scratchcard.find('|') - 2 - scratchcard.find(':')
);

string scratchcard_numbers = scratchcard.substr(scratchcard.find('|') + 1);

You can see that I’m ignoring the Game ID, because we don’t need it. We can simply use the index + 1 of the scratchcard to identify it.

As we now we need to get the intersection between the winning numbers and the scratchcard numbers, I’ve decided to use a set to store both of them. The set will automatically remove the duplicates and sort the numbers for us.

set<int> S = Utils::parse_numbers(winning_numbers);
set<int> T = Utils::parse_numbers(scratchcard_numbers);

You can see that I’ve introduced a Utils::parse_numbers method. This method is responsible for parsing the numbers from the string and adding them to a set.

static std::set<int> parse_numbers(std::string text) {
    std::set<int> S;

    text += ' ';
    std::string n;
    for (char &c: text) {
        if (c == ' ') {
            if (!n.empty()) S.insert(stoi(n));
            n = "";
            continue;
        }
        n += c;
    }
    return S;
}

I know that if we do the intersection between the two sets, we will go further than just parsing. But let’s do it.

set<int> intersect;
set_intersection(S.begin(), S.end(), T.begin(), T.end(),
             inserter(intersect, intersect.begin()));

return intersect;

At the end, our code looks as follows:

private:
    static set<int> intersection(const string &scratchcard) {
        string winning_numbers = scratchcard.substr(scratchcard.find(':') + 2,
                                                    scratchcard.find('|') - 2 - scratchcard.find(':'));

        string scratchcard_numbers = scratchcard.substr(scratchcard.find('|') + 1);

        set<int> S = Utils::parse_numbers(winning_numbers);
        set<int> T = Utils::parse_numbers(scratchcard_numbers);

        set<int> intersect;
        set_intersection(S.begin(), S.end(), T.begin(), T.end(),
                         inserter(intersect, intersect.begin()));

        return intersect;
    }

PARS I

* Cf. ibid. Comment.
art. praeced.

Now that the heavy work is done, we can solve the actual problem.

For each scratchcard, we need to find the intersection between the winning numbers and the scratchcard numbers. If the intersection is empty, we should skip to the next scratchcard. If the intersection is not empty, we should calculate 2 ^ n, where n is the size of the intersection. At the end we should sum all the results.

static int part1(vector<string> &scratchcards) {
  vector<int> ans;

  for (string &scratchcard: scratchcards) {
      set<int> intersect = intersection(scratchcard);

      unsigned long size = intersect.size();

      if (size <= 0) continue;
      int calc = 1;
      while (--size) { calc *= 2; }
      ans.push_back(calc);
  }

  return accumulate(ans.begin(), ans.end(), 0);
}

Look what I’ve found here laying at the floor: 🌟


PARS II

The only difference between part one and part two is that we need to adjust the calculation. Instead of calculating 2 ^ n, we need to increment the number of cards after the one we are processing. For example, if we are processing the first card, and we had 3 intersections, we gained a copy of the cards 2, 3 and 4. That means that we have to process this cards twice.

static int part2(vector<string> &scratchcards) {
    vector<int> ans;
    map<int, int> M;

    M[0] = 0;
    for (int i = 0; i < scratchcards.size(); ++i) {
        set<int> intersect = intersection(scratchcards[i]);

        for (auto j = 0; j < intersect.size(); ++j) {
            M[i + j + 1] += M[i] + 1;
        }

        ans.push_back(M[i] + 1);
    }

    return accumulate(ans.begin(), ans.end(), 0);
}

That is as easy as it gets 🌟🌟

Click here to access the blogpost from @fefas


α) The game number is not important for the solution, so we can ignore it. Even if it was important, we could use the `key + 1` in a for loop to get it back.