Advent of Code - Day 4: Scratchcards


2023-12-04-generated.png

Tell you what: if you can help me with something quick, I’ll let you borrow my boat and you can go visit the gardener. I got all these scratchcards as a gift, but I can’t figure out what I’ve won."

-- Day 4 - Advent of Code 2023

Solution in Java

Full source can be found: in GitHub

Part 1

I created a class Card to represent the scratch cards from the assignment. Each card has a score() representing the amount of points of the card.

 1record Card(int hand, List<Integer> winningNumbers, List<Integer> yourNumbers) {  
 2    public long score() {  
 3        return (long) Math.pow(2, numberMatches() - 1);  
 4    }
 5    
 6    public long numberMatches() {  
 7	    return yourNumbers.stream()  
 8	            .filter(winningNumbers::contains)  
 9	            .count();  
10	}
11}

Then creating the algorithm to compute the total amount of winning points can be done by streaming over each card and summing the score.

 1public void part1() {  
 2    var answer = inputLoader.splitOnNewLine()  
 3            .map(this::parseCard)  
 4            .mapToLong(Card::score)  
 5            .sum();  
 6    validator.part1(answer);  
 7}
 8
 9private Card parseCard(String line) {  
10    var cardSplit = line.indexOf(":");  
11    var drawSplit = line.indexOf("|");  
12    // extract the card number, the winning numbers, and your numbers  
13    var cardNumber = Integer.parseInt(line.substring(5, cardSplit).trim());  
14  
15    var winningNumbers = Arrays.stream(line.substring(cardSplit + 1, drawSplit)  
16                    .split("\\s"))  
17            .filter(number -> !number.isBlank())  
18            .map(nr -> Integer.parseInt(nr.trim()))  
19            .toList();  
20    var myNumbers = Arrays.stream(line.substring(drawSplit + 1).split("\\s"))  
21            .filter(number -> !number.isBlank())  
22            .map(nr -> Integer.parseInt(nr.trim()))  
23            .toList();  
24  
25    return new Card(  
26            cardNumber,  
27            winningNumbers,  
28            myNumbers);  
29}

Part 2

To solve the second part I added a wrapper class CountedCards to keep track of the amount of winning points per card.

 1static class CountedCards {  
 2    private final Card card;  
 3    private int count;  
 4  
 5    public CountedCards(Card card) {  
 6        this.card = card;  
 7        this.count = 1;  
 8    }  
 9  
10    public void increment(int amount) {  
11        this.count += amount;  
12    }  
13}

Then the algorithm for part 2 can be adjusted to loop over all the cards, ignoring all cards that have no matches. If the card wins then the following X cards get duplicated (by increasing their count).

The answer can then be found by counting all the cards we have.

 1public void part2() {  
 2    var cardInput = inputLoader.splitOnNewLine()  
 3            .map(this::parseCard)  
 4            .map(CountedCards::new)  
 5            .toList();  
 6  
 7    for (var i = 0; i < cardInput.size(); i++) {  
 8        var card = cardInput.get(i);  
 9        if (card.card.numberMatches() > 0) {  
10            IntStream.range(i + 1, (int) (i + card.card.numberMatches() + 1))  
11                    .forEach(index -> cardInput.get(index).increment(card.count));  
12        }  
13    }  
14  
15    var answer =  cardInput.stream()  
16            .mapToLong(card -> card.count)  
17            .sum();  
18    validator.part2(answer);  
19}

See also