From 8900459fc61b0a7ee9328f442f8f9a6b126025cb Mon Sep 17 00:00:00 2001 From: Nick Krichevsky Date: Mon, 4 Dec 2023 08:54:23 -0500 Subject: [PATCH] Add day 4 part 2 --- day4/main.go | 111 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 16 deletions(-) diff --git a/day4/main.go b/day4/main.go index 505442c..330d251 100644 --- a/day4/main.go +++ b/day4/main.go @@ -6,31 +6,48 @@ import ( "io" "os" "regexp" + "slices" "strconv" "strings" ) type Card struct { + id int winningNumbers []int ourNumbers []int } -func (card Card) Score() int { - score := 0 +func (card Card) NumMatchingNumbers() int { + matchingNumbers := 0 + winningNumbers := makeSet(card.winningNumbers) for _, ourNumber := range card.ourNumbers { - if _, ok := winningNumbers[ourNumber]; !ok { - continue - } - - if score == 0 { - score = 1 - } else { - score *= 2 + if _, ok := winningNumbers[ourNumber]; ok { + matchingNumbers++ } } - return score + return matchingNumbers +} + +func (card Card) Score() int { + numMatchingNumbers := card.NumMatchingNumbers() + if numMatchingNumbers == 0 { + return 0 + } + + return pow2(numMatchingNumbers) +} + +func (card Card) WinsCardsWithIDs() []int { + numMatchingNumbers := card.NumMatchingNumbers() + + wonCards := make([]int, numMatchingNumbers) + for i := 0; i < numMatchingNumbers; i++ { + wonCards[i] = card.id + i + 1 + } + + return wonCards } func main() { @@ -60,6 +77,7 @@ func main() { } fmt.Printf("Part 1: %d\n", part1(cards)) + fmt.Printf("Part 2: %d\n", part2(cards)) } func part1(cards []Card) int { @@ -71,28 +89,76 @@ func part1(cards []Card) int { return score } +func part2(cards []Card) int { + if len(cards) == 0 { + return 0 + } + + cardsByID := map[int]Card{} + for _, card := range cards { + cardsByID[card.id] = card + } + + // This solution is a bit naive; I didn't get particularly clever with + // the number of cards we had and treated it like a tree problem (I could + // have just made this faster by doing + // visitedCardIds[wonCard] += visitedCardIds[card.id] + // which would have been equivalent, and faster, but meh, I like this + // solution even if it's slow) + visitedCardIDs := map[int]int{} + cardsInPlay := slices.Clone(cards) + + for len(cardsInPlay) > 0 { + card := cardsInPlay[0] + cardsInPlay = cardsInPlay[1:] + + visitedCardIDs[card.id]++ + + wonCardIDs := card.WinsCardsWithIDs() + for _, wonCardID := range wonCardIDs { + cardsInPlay = append(cardsInPlay, cardsByID[wonCardID]) + } + } + + totalCards := 0 + for _, numVisited := range visitedCardIDs { + totalCards += numVisited + } + + return totalCards +} + func parseCards(inputLines []string) ([]Card, error) { return tryParse(inputLines, parseCard) } func parseCard(inputLine string) (Card, error) { - pattern := regexp.MustCompile(`^Card\s+\d+: ((?:\s*\d+\s*?)+) \| ((?:\s*\d+\s*)+)$`) + pattern := regexp.MustCompile(`^Card\s+(\d+): ((?:\s*\d+\s*?)+) \| ((?:\s*\d+\s*)+)$`) matches := pattern.FindStringSubmatch(inputLine) if matches == nil { return Card{}, errors.New("did not match line pattern") } - winningNumbers, err := parseCardNumbers(matches[1]) + id, err := strconv.Atoi(matches[1]) + if err != nil { + return Card{}, fmt.Errorf("parse id: %w", err) + } + + winningNumbers, err := parseCardNumbers(matches[2]) if err != nil { return Card{}, fmt.Errorf("parse winning numbers: %w", err) } - ourNumbers, err := parseCardNumbers(matches[2]) + ourNumbers, err := parseCardNumbers(matches[3]) if err != nil { - return Card{}, fmt.Errorf("parse winning numbers: %w", err) + return Card{}, fmt.Errorf("parse our numbers: %w", err) } - return Card{winningNumbers: winningNumbers, ourNumbers: ourNumbers}, nil + return Card{ + id: id, + winningNumbers: winningNumbers, + ourNumbers: ourNumbers, + }, nil } func normalizeSeparatingSpaces(s string) string { @@ -131,3 +197,16 @@ func makeSet[T comparable, S ~[]T](items S) map[T]struct{} { return set } + +func pow2(exp int) int { + if exp == 0 { + return 1 + } + + res := 1 + for i := 1; i < exp; i++ { + res *= 2 + } + + return res +}