diff --git a/2023/day04/main.go b/2023/day04/main.go new file mode 100644 index 0000000..3e1400c --- /dev/null +++ b/2023/day04/main.go @@ -0,0 +1,138 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/util" +) + +//go:embed input.txt +var input string + +func init() { + // do this in init (not main) so test file has same input + input = strings.TrimRight(input, "\n") + if len(input) == 0 { + panic("empty input.txt file") + } +} + +func main() { + var part int + flag.IntVar(&part, "part", 1, "part 1 or 2") + flag.Parse() + fmt.Println("Running part", part) + + if part == 1 { + ans := part1(input) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } else { + ans := part2(input) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } +} + +func part1(input string) int { + parsed := parseInput(input) + + var ans int + + for _, card := range parsed { + ans += scoreCard(card) + } + + return ans +} + +func scoreCard(c card) int { + var ans int + + for _, num := range c.hand { + if c.winning[num] { + if ans == 0 { + ans = 1 + } else { + ans *= 2 + } + } + } + + return ans +} + +func part2(input string) int { + cards := parseInput(input) + + // tracks the number of cards won, starts with 1 of each card + numCards := make([]int, len(cards)) + for i := range numCards { + numCards[i] = 1 + } + + for index, c := range cards { + cardsWon := countWinningNumbers(c) + for i := 1; i <= cardsWon; i++ { + // add number of current card to account for previous wins + numCards[index+i] += numCards[index] + } + } + + // add up total number of cards + var cardCount int + for _, n := range numCards { + cardCount += n + } + return cardCount +} + +func countWinningNumbers(c card) int { + var ans int + + for _, num := range c.hand { + if c.winning[num] { + ans++ + } + } + + return ans +} + +type card struct { + // index int // unused + winning map[int]bool + hand []int +} + +func parseInput(input string) (ans []card) { + for _, line := range strings.Split(input, "\n") { + c := card{ + // index: len(ans) + 1, + winning: map[int]bool{}, + } + + half := strings.Split(line, ": ") + numParts := strings.Split(half[1], " | ") + for _, winningNum := range strings.Split(numParts[0], " ") { + // handles single digits that have an extra empty string between nums + if winningNum == "" { + continue + } + c.winning[cast.ToInt(winningNum)] = true + } + + for _, handNum := range strings.Split(numParts[1], " ") { + if handNum == "" { + continue + } + c.hand = append(c.hand, cast.ToInt(handNum)) + } + ans = append(ans, c) + } + return ans +} diff --git a/2023/day04/main_test.go b/2023/day04/main_test.go new file mode 100644 index 0000000..053e861 --- /dev/null +++ b/2023/day04/main_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "testing" +) + +var example = `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` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 13, + }, + { + name: "actual", + input: input, + want: 19135, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part1(tt.input); got != tt.want { + t.Errorf("part1() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_part2(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 30, + }, + { + name: "actual", + input: input, + want: 5704953, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part2(tt.input); got != tt.want { + t.Errorf("part2() = %v, want %v", got, tt.want) + } + }) + } +}