From 6bef9a0721ca7d05ae73e8d7baba4b52476a5ce9 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 12 Dec 2022 00:22:00 -0500 Subject: [PATCH 01/57] 8 9 10 11 --- 2022/day08/main.go | 144 +++++++++++++++++++++++ 2022/day08/main_test.go | 63 ++++++++++ 2022/day09/main.go | 253 ++++++++++++++++++++++++++++++++++++++++ 2022/day09/main_test.go | 85 ++++++++++++++ 2022/day10/main.go | 167 ++++++++++++++++++++++++++ 2022/day10/main_test.go | 216 ++++++++++++++++++++++++++++++++++ 2022/day11/main.go | 243 ++++++++++++++++++++++++++++++++++++++ 2022/day11/main_test.go | 59 ++++++++++ 8 files changed, 1230 insertions(+) create mode 100644 2022/day08/main.go create mode 100644 2022/day08/main_test.go create mode 100644 2022/day09/main.go create mode 100644 2022/day09/main_test.go create mode 100644 2022/day10/main.go create mode 100644 2022/day10/main_test.go create mode 100644 2022/day11/main.go create mode 100644 2022/day11/main_test.go diff --git a/2022/day08/main.go b/2022/day08/main.go new file mode 100644 index 0000000..5f7f108 --- /dev/null +++ b/2022/day08/main.go @@ -0,0 +1,144 @@ +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 { + grid := parseInput(input) + + // may be visible from multiple angles + visibleCoords := map[[2]int]string{} + for r := 1; r < len(grid)-1; r++ { + // from left + highestFromLeft := -1 + for c := 0; c < len(grid[0])-1; c++ { + height := grid[r][c] + if height > highestFromLeft { + visibleCoords[[2]int{r, c}] = "L" + highestFromLeft = height + } + } + // from right + highestFromRight := -1 + for c := len(grid[0]) - 1; c > 0; c-- { + height := grid[r][c] + if height > highestFromRight { + visibleCoords[[2]int{r, c}] = "R" + highestFromRight = height + } + } + } + + for c := 1; c < len(grid[0])-1; c++ { + // from top + highestFromTop := -1 + for r := 0; r < len(grid)-1; r++ { + height := grid[r][c] + if height > highestFromTop { + visibleCoords[[2]int{r, c}] = "T" + highestFromTop = height + } + } + // from bottom + highestFromBottom := -1 + for r := len(grid) - 1; r > 0; r-- { + height := grid[r][c] + if height > highestFromBottom { + visibleCoords[[2]int{r, c}] = "B" + highestFromBottom = height + } + } + } + + return len(visibleCoords) + 4 // plus 4 for corners +} + +func part2(input string) int { + // multiply the four scores together... score = how many trees any tree can see + // because trees on the edge will have a zero, just ignore them + grid := parseInput(input) + + bestScore := 0 + // iterate through every eligible tree + for r := 1; r < len(grid)-1; r++ { + for c := 1; c < len(grid[0])-1; c++ { + score := visible(grid, r, c, -1, 0) + score *= visible(grid, r, c, 1, 0) + score *= visible(grid, r, c, 0, -1) + score *= visible(grid, r, c, 0, 1) + + if score > bestScore { + bestScore = score + } + } + } + + return bestScore +} + +func visible(grid [][]int, r, c, dr, dc int) int { + count := 0 + startingHeight := grid[r][c] + r += dr + c += dc + for r >= 0 && r < len(grid) && c >= 0 && c < len(grid[0]) { + height := grid[r][c] + if height < startingHeight { + count++ + } else { + count++ + break + } + + r += dr + c += dc + } + + return count +} + +func parseInput(input string) (ans [][]int) { + for _, line := range strings.Split(input, "\n") { + var row []int + for _, n := range strings.Split(line, "") { + row = append(row, cast.ToInt(n)) + } + ans = append(ans, row) + } + return ans +} diff --git a/2022/day08/main_test.go b/2022/day08/main_test.go new file mode 100644 index 0000000..bb6eda5 --- /dev/null +++ b/2022/day08/main_test.go @@ -0,0 +1,63 @@ +package main + +import ( + "testing" +) + +var example = `30373 +25512 +65332 +33549 +35390` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 21, + }, + { + name: "actual", + input: input, + want: 1690, + }, + } + 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: 8, + }, + { + name: "actual", + input: input, + want: 535680, + }, + } + 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) + } + }) + } +} diff --git a/2022/day09/main.go b/2022/day09/main.go new file mode 100644 index 0000000..0e24701 --- /dev/null +++ b/2022/day09/main.go @@ -0,0 +1,253 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/mathy" + "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 { + // tail follows head logically if in the same row or column + // if not in same row or column, always moves diagonally + insts := parseInput(input) + + // start stacked at 0,0 + var head, tail [2]int + + // "normal" grid mapping... + diffs := map[string][2]int{ + "U": {1, 0}, + "D": {-1, 0}, + "L": {0, -1}, + "R": {0, 1}, + } + + visited := map[[2]int]bool{ + {0, 0}: true, + } + for _, inst := range insts { + for inst.val > 0 { + // move head + diff := diffs[inst.dir] + head[0] += diff[0] // row + head[1] += diff[1] // col + + // update tail + // if diff to row or col is > 1 + + rowDiff := head[0] - tail[0] + colDiff := head[1] - tail[1] + + // if either row or col diff is > 1, then that dimension HAS to move + // additionally, if the other diff is not zero, it needs to be + // adjusted to move diagonally + // note: the nested if blocks screwed me in part 2 because a longer + // rope can make coordinates off by 2 rows AND 2 cols + if mathy.AbsInt(rowDiff) > 1 { + /* 0 1 2 + H . T + diff = head - tail = -2 + want to make tail (2) to (1), so add diff / 2 + + T . H + diff = 2 - 0 = 2 + tail (0) + 2/2 = 1, checks out still + */ + tail[0] += rowDiff / 2 + // account for diagonal adjustment, same math... add col diff + if colDiff != 0 { + tail[1] += colDiff + } + } else if mathy.AbsInt(colDiff) > 1 { + tail[1] += colDiff / 2 + // account for diagonal adjustment, same math... add col diff + if rowDiff != 0 { + tail[0] += rowDiff + } + } + + // update where the tail has been... + visited[tail] = true + inst.val-- // one step at a time + } + } + + // return spots TAIL visited at least once, map[[2]int]bool + return len(visited) +} + +type inst struct { + dir string + val int +} + +func parseInput(input string) (ans []inst) { + for _, line := range strings.Split(input, "\n") { + ans = append(ans, inst{ + dir: line[:1], + val: cast.ToInt(line[2:]), + }) + } + return ans +} + +func part2(input string) int { + // oof, quite the refactor... + insts := parseInput(input) + + rope := initRope(10) + + visited := map[[2]int]bool{} + for _, inst := range insts { + for inst.val > 0 { + rope.moveOneSpace(inst.dir) + + // update where the tail has been... + visited[rope.tail.coords] = true + + inst.val-- // one step at a time + + fmt.Println(inst, rope, len(visited)) + } + } + + return len(visited) +} + +type node struct { + coords [2]int // row, col still + next *node +} + +type rope struct { + head, tail *node +} + +func initRope(length int) rope { + head := &node{} + itr := head + + // start at 1 to account for head already being created + for i := 1; i < length; i++ { + itr.next = &node{} + itr = itr.next + } + + return rope{ + head: head, + tail: itr, + } +} + +func (r rope) moveOneSpace(dir string) { + // "normal" grid mapping... + diffs := map[string][2]int{ + "U": {1, 0}, + "D": {-1, 0}, + "L": {0, -1}, + "R": {0, 1}, + } + + diff := diffs[dir] + r.head.coords[0] += diff[0] + r.head.coords[1] += diff[1] + + // update rest of rope too + r.head.updateTrailer() +} + +func (r rope) String() string { + str := "" + i := 0 + for itr := r.head; itr != nil; itr = itr.next { + str += fmt.Sprintf("%d:[%d,%d]->", i, itr.coords[0], itr.coords[1]) + i++ + } + return str +} + +// recursively updates the node behind itself as it follows +func (n *node) updateTrailer() { + if n.next == nil { + return + } + + rowDiff := n.coords[0] - n.next.coords[0] + colDiff := n.coords[1] - n.next.coords[1] + + // if either row or col diff is > 1, then that dimension HAS to move + // additionally, if the other diff is not zero, it needs to be + // adjusted to move diagonally + if mathy.AbsInt(rowDiff) > 1 && mathy.AbsInt(colDiff) > 1 { + n.next.coords[0] += rowDiff / 2 + n.next.coords[1] += colDiff / 2 + } else if mathy.AbsInt(rowDiff) > 1 { + // see part1 for math logic + n.next.coords[0] += rowDiff / 2 + n.next.coords[1] += colDiff + } else if mathy.AbsInt(colDiff) > 1 { + n.next.coords[1] += colDiff / 2 + n.next.coords[0] += rowDiff + } else { + // no need to continue updating children if movement is over + return + } + + // go to next node + n.next.updateTrailer() +} + +func reImplPart1(input string) int { + // oof, quite the refactor... + insts := parseInput(input) + + rope := initRope(2) + + visited := map[[2]int]bool{} + for _, inst := range insts { + for inst.val > 0 { + rope.moveOneSpace(inst.dir) + + // update where the tail has been... + visited[rope.tail.coords] = true + + inst.val-- // one step at a time + } + } + + // return spots TAIL visited at least once, map[[2]int]bool + return len(visited) +} diff --git a/2022/day09/main_test.go b/2022/day09/main_test.go new file mode 100644 index 0000000..cadaffc --- /dev/null +++ b/2022/day09/main_test.go @@ -0,0 +1,85 @@ +package main + +import ( + "testing" +) + +var example = `R 4 +U 4 +L 3 +D 1 +R 4 +D 1 +L 5 +R 2` + +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: 6236, + }, + } + 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) + } + }) + t.Run("reimplementation_"+tt.name, func(t *testing.T) { + if got := reImplPart1(tt.input); got != tt.want { + t.Errorf("reImplPart1() = %v, want %v", got, tt.want) + } + }) + } +} + +var largerExample = `R 5 +U 8 +L 8 +D 3 +R 17 +D 10 +L 25 +U 20` + +func Test_part2(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 1, + }, + { + name: "larger_example", + input: largerExample, + want: 36, + }, + { + name: "actual", + input: input, + want: 2449, + }, + } + 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) + } + }) + } +} diff --git a/2022/day10/main.go b/2022/day10/main.go new file mode 100644 index 0000000..5efac70 --- /dev/null +++ b/2022/day10/main.go @@ -0,0 +1,167 @@ +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 { + instructions := parseInput(input) + + X := 1 + sum := 0 + + i := 0 // what the current instruction is + for cycle := 1; cycle <= 220; cycle++ { + // "during" equates to the start of the cycle... + if (cycle-20)%40 == 0 { + sum += X * cycle + } + + switch instructions[i].name { + case "addx": + // decrement cycles on that instruction + // IF it hits zero add V + // AND move to next step + instructions[i].cycles-- + if instructions[i].cycles == 0 { + X += instructions[i].val + i++ + } + case "noop": + // just increment to next instruction + i++ + } + } + + return sum +} + +func part2(input string) string { + instructions := parseInput(input) + + X := 1 // doubles as sprite's center coordinate + + // 6 rows by 40 wide screen, starts all off + CRT := [6][40]string{} + for i, rows := range CRT { + for j := range rows { + CRT[i][j] = "." + } + } + + i := 0 // what the current instruction is + for cycle := 1; i < len(instructions); cycle++ { + // if (cycle-20)%40 == 0 { + // sum += X * cycle + // } + + /* + X = horizontal position of middle of (3 pixel wide) sprite + axis draws left to right, top to bottom, 40 wide x 6 high + 1---40 + 41---80 + ... + 201---240 + + draws 1 pixel per cycle + light up pixels IF the pixel being drawn is the same as one of the sprite's 3 pixels + + */ + + // calculate which pixel is being drawn... ZERO INDEXED + pixelRow := (cycle - 1) / 40 + pixelCol := (cycle - 1) % 40 + + // see if the spite's horizontal location overlaps that pixelCol + spriteLeft, spriteRight := X-1, X+1 + if spriteLeft <= pixelCol && spriteRight >= pixelCol { + CRT[pixelRow][pixelCol] = "#" + } + + switch instructions[i].name { + case "addx": + // decrement cycles on that instruction + // IF it hits zero add V + // AND move to next step + instructions[i].cycles-- + if instructions[i].cycles == 0 { + X += instructions[i].val + i++ + } + case "noop": + // just increment to next instruction + i++ + } + + } + log := "" + for _, rows := range CRT { + for _, cell := range rows { + log += cell + } + log += "\n" + } + fmt.Println(log) + return log +} + +type instruction struct { + name string + val int + cycles int +} + +func parseInput(input string) (ans []instruction) { + for _, l := range strings.Split(input, "\n") { + switch l[:4] { + case "addx": + ans = append(ans, instruction{ + name: "addx", + val: cast.ToInt(l[5:]), + cycles: 2, + }) + case "noop": + ans = append(ans, instruction{ + name: "noop", + cycles: 1, + }) + default: + panic("input line: " + l) + } + } + return ans +} diff --git a/2022/day10/main_test.go b/2022/day10/main_test.go new file mode 100644 index 0000000..e891291 --- /dev/null +++ b/2022/day10/main_test.go @@ -0,0 +1,216 @@ +package main + +import ( + "testing" +) + +var example = `addx 15 +addx -11 +addx 6 +addx -3 +addx 5 +addx -1 +addx -8 +addx 13 +addx 4 +noop +addx -1 +addx 5 +addx -1 +addx 5 +addx -1 +addx 5 +addx -1 +addx 5 +addx -1 +addx -35 +addx 1 +addx 24 +addx -19 +addx 1 +addx 16 +addx -11 +noop +noop +addx 21 +addx -15 +noop +noop +addx -3 +addx 9 +addx 1 +addx -3 +addx 8 +addx 1 +addx 5 +noop +noop +noop +noop +noop +addx -36 +noop +addx 1 +addx 7 +noop +noop +noop +addx 2 +addx 6 +noop +noop +noop +noop +noop +addx 1 +noop +noop +addx 7 +addx 1 +noop +addx -13 +addx 13 +addx 7 +noop +addx 1 +addx -33 +noop +noop +noop +addx 2 +noop +noop +noop +addx 8 +noop +addx -1 +addx 2 +addx 1 +noop +addx 17 +addx -9 +addx 1 +addx 1 +addx -3 +addx 11 +noop +noop +addx 1 +noop +addx 1 +noop +noop +addx -13 +addx -19 +addx 1 +addx 3 +addx 26 +addx -30 +addx 12 +addx -1 +addx 3 +addx 1 +noop +noop +noop +addx -9 +addx 18 +addx 1 +addx 2 +noop +noop +addx 9 +noop +noop +noop +addx -1 +addx 2 +addx -37 +addx 1 +addx 3 +noop +addx 15 +addx -21 +addx 22 +addx -6 +addx 1 +noop +addx 2 +addx 1 +noop +addx -10 +noop +noop +addx 20 +addx 1 +addx 2 +addx 2 +addx -6 +addx -11 +noop +noop +noop` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 13140, + }, + { + name: "actual", + input: input, + want: 15880, + }, + } + 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 string + }{ + { + name: "example", + input: example, + want: `##..##..##..##..##..##..##..##..##..##.. +###...###...###...###...###...###...###. +####....####....####....####....####.... +#####.....#####.....#####.....#####..... +######......######......######......#### +#######.......#######.......#######..... +`, + }, + { + name: "actual", + input: input, + want: `###..#.....##..####.#..#..##..####..##.. +#..#.#....#..#.#....#.#..#..#....#.#..#. +#..#.#....#....###..##...#..#...#..#.... +###..#....#.##.#....#.#..####..#...#.##. +#....#....#..#.#....#.#..#..#.#....#..#. +#....####..###.#....#..#.#..#.####..###. +`, + }, + } + 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) + } + }) + } +} diff --git a/2022/day11/main.go b/2022/day11/main.go new file mode 100644 index 0000000..64ec5ed --- /dev/null +++ b/2022/day11/main.go @@ -0,0 +1,243 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "sort" + "strings" + + "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(true) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } else { + ans := part2(true) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } +} + +func part1(useRealInput bool) int { + monkeys := initInput() + if !useRealInput { + monkeys = initExample() + } + + inspectedCounts := make([]int, len(monkeys)) + for round := 0; round < 20; round++ { + for i, monkey := range monkeys { + for _, item := range monkey.items { + newItemVal := monkey.operation(item) / 3 + + if newItemVal%monkey.testDivisibleBy == 0 { + monkeys[monkey.trueMonkey].items = append( + monkeys[monkey.trueMonkey].items, newItemVal) + } else { + monkeys[monkey.falseMonkey].items = append( + monkeys[monkey.falseMonkey].items, newItemVal) + } + + } + inspectedCounts[i] += len(monkey.items) + + // empty out this monkey's items + monkeys[i].items = []int{} + } + } + + sort.Ints(inspectedCounts) + return inspectedCounts[len(inspectedCounts)-1] * inspectedCounts[len(inspectedCounts)-2] +} + +// oh my god i figured out a math-y remainder theorem-y thing myself! +func part2(useRealInput bool) int { + monkeys := initInput() + if !useRealInput { + monkeys = initExample() + } + + // the worry levels will always increase now that they're not being divided + // by 3, and we care about remainders because that's what all the tests are + // BUT we can't just mod by any monkey's testBy number, because they're all + // throwing the items around, + // so find a shared common denominator that can be used to keep the numbers + // under overflow + bigMod := 1 + for _, m := range monkeys { + bigMod *= m.testDivisibleBy + } + + inspectedCounts := make([]int, len(monkeys)) + for round := 0; round < 10000; round++ { + + for i, monkey := range monkeys { + for _, item := range monkey.items { + newItemVal := monkey.operation(item) + newItemVal %= bigMod + + if newItemVal%monkey.testDivisibleBy == 0 { + monkeys[monkey.trueMonkey].items = append( + monkeys[monkey.trueMonkey].items, newItemVal) + } else { + monkeys[monkey.falseMonkey].items = append( + monkeys[monkey.falseMonkey].items, newItemVal) + } + + } + inspectedCounts[i] += len(monkey.items) + + // empty out this monkey's items + monkeys[i].items = []int{} + } + } + + sort.Ints(inspectedCounts) + return inspectedCounts[len(inspectedCounts)-1] * inspectedCounts[len(inspectedCounts)-2] +} + +type monkey struct { + items []int + operation func(int) int + testDivisibleBy int + trueMonkey, falseMonkey int // indices +} + +// faster to manually type this than write a parser (and potentially debug) +func initInput() []monkey { + return []monkey{ + { + items: []int{50, 70, 89, 75, 66, 66}, + operation: func(old int) int { + return old * 5 + }, + testDivisibleBy: 2, + trueMonkey: 2, + falseMonkey: 1, + }, + { + items: []int{85}, + operation: func(old int) int { + return old * old + }, + testDivisibleBy: 7, + trueMonkey: 3, + falseMonkey: 6, + }, + { + items: []int{66, 51, 71, 76, 58, 55, 58, 60}, + operation: func(old int) int { + return old + 1 + }, + testDivisibleBy: 13, + trueMonkey: 1, + falseMonkey: 3, + }, + { + items: []int{79, 52, 55, 51}, + operation: func(old int) int { + return old + 6 + }, + testDivisibleBy: 3, + trueMonkey: 6, + falseMonkey: 4, + }, + { + items: []int{69, 92}, + operation: func(old int) int { + return old * 17 + }, + testDivisibleBy: 19, + trueMonkey: 7, + falseMonkey: 5, + }, + { + items: []int{71, 76, 73, 98, 67, 79, 99}, + operation: func(old int) int { + return old + 8 + }, + testDivisibleBy: 5, + trueMonkey: 0, + falseMonkey: 2, + }, + { + items: []int{82, 76, 69, 69, 57}, + operation: func(old int) int { + return old + 7 + }, + testDivisibleBy: 11, + trueMonkey: 7, + falseMonkey: 4, + }, + { + items: []int{65, 79, 86}, + operation: func(old int) int { + return old + 5 + }, + testDivisibleBy: 17, + trueMonkey: 5, + falseMonkey: 0, + }, + } +} + +func initExample() []monkey { + return []monkey{ + { + items: []int{79, 98}, + operation: func(num int) int { + return num * 19 + }, + testDivisibleBy: 23, + trueMonkey: 2, + falseMonkey: 3, + }, + { + items: []int{54, 65, 75, 74}, + operation: func(num int) int { + return num + 6 + }, + testDivisibleBy: 19, + trueMonkey: 2, + falseMonkey: 0, + }, + { + items: []int{79, 60, 97}, + operation: func(num int) int { + return num * num + }, + testDivisibleBy: 13, + trueMonkey: 1, + falseMonkey: 3, + }, + { + items: []int{74}, + operation: func(num int) int { + return num + 3 + }, + testDivisibleBy: 17, + trueMonkey: 0, + falseMonkey: 1, + }, + } +} diff --git a/2022/day11/main_test.go b/2022/day11/main_test.go new file mode 100644 index 0000000..745fe37 --- /dev/null +++ b/2022/day11/main_test.go @@ -0,0 +1,59 @@ +package main + +import ( + "testing" +) + +var example = `` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + useRealInput bool + want int + }{ + { + name: "example", + useRealInput: false, + want: 10605, + }, + { + name: "actual", + useRealInput: true, + want: 151312, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part1(tt.useRealInput); got != tt.want { + t.Errorf("part1() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_part2(t *testing.T) { + tests := []struct { + name string + useRealInput bool + want int + }{ + { + name: "example", + useRealInput: false, + want: 2713310158, + }, + { + name: "actual", + useRealInput: true, + want: 51382025916, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part2(tt.useRealInput); got != tt.want { + t.Errorf("part2() = %v, want %v", got, tt.want) + } + }) + } +} From d9e783478ba83e68096680062014431224782209 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 12 Dec 2022 00:59:02 -0500 Subject: [PATCH 02/57] 2022-12, path finding :) --- 2022/day12/main.go | 152 ++++++++++++++++++++++++++++++++++++++++ 2022/day12/main_test.go | 63 +++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 2022/day12/main.go create mode 100644 2022/day12/main_test.go diff --git a/2022/day12/main.go b/2022/day12/main.go new file mode 100644 index 0000000..4141f74 --- /dev/null +++ b/2022/day12/main.go @@ -0,0 +1,152 @@ +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) + } +} + +var diffs = [4][2]int{ + {0, -1}, + {0, 1}, + {-1, 0}, + {1, 0}, +} + +func part1(input string) int { + grid := parseInput(input) + + queue := [][3]int{} +label: + for r, rows := range grid { + for c, cell := range rows { + if cell == "S" { + queue = append(queue, [3]int{r, c, 0}) + break label + } + } + } + seen := map[[2]int]bool{} + + for len(queue) > 0 { + front := queue[0] + queue = queue[1:] + if seen[[2]int{front[0], front[1]}] { + continue + } + seen[[2]int{front[0], front[1]}] = true + + if grid[front[0]][front[1]] == "E" { + return front[2] + } + for _, d := range diffs { + nextR, nextC := front[0]+d[0], front[1]+d[1] + if nextR >= 0 && nextR < len(grid) && nextC >= 0 && nextC < len(grid[0]) { + letterDiff := distanceBetweenLetters(grid[front[0]][front[1]], grid[nextR][nextC]) + + if letterDiff <= 1 { + queue = append(queue, [3]int{nextR, nextC, front[2] + 1}) + } + } + } + } + + return -1 +} + +func part2(input string) int { + grid := parseInput(input) + + queue := [][3]int{} +label: + for r, rows := range grid { + for c, cell := range rows { + if cell == "E" { + queue = append(queue, [3]int{r, c, 0}) + break label + } + } + } + seen := map[[2]int]bool{} + + for len(queue) > 0 { + front := queue[0] + queue = queue[1:] + if seen[[2]int{front[0], front[1]}] { + continue + } + seen[[2]int{front[0], front[1]}] = true + + if grid[front[0]][front[1]] == "a" { + return front[2] + } + for _, d := range diffs { + nextR, nextC := front[0]+d[0], front[1]+d[1] + if nextR >= 0 && nextR < len(grid) && nextC >= 0 && nextC < len(grid[0]) { + letterDiff := distanceBetweenLetters(grid[front[0]][front[1]], grid[nextR][nextC]) + + if letterDiff >= -1 { + queue = append(queue, [3]int{nextR, nextC, front[2] + 1}) + } + } + } + } + + return -1 +} + +func distanceBetweenLetters(x, y string) int { + if x == "S" { + x = "a" + } + if y == "S" { + y = "a" + } + if y == "E" { + y = "z" + } + if x == "E" { + x = "z" + } + + return cast.ToASCIICode(y) - cast.ToASCIICode(x) +} + +func parseInput(input string) (ans [][]string) { + for _, line := range strings.Split(input, "\n") { + ans = append(ans, strings.Split(line, "")) + } + return ans +} diff --git a/2022/day12/main_test.go b/2022/day12/main_test.go new file mode 100644 index 0000000..09c021b --- /dev/null +++ b/2022/day12/main_test.go @@ -0,0 +1,63 @@ +package main + +import ( + "testing" +) + +var example = `Sabqponm +abcryxxl +accszExk +acctuvwj +abdefghi` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 31, + }, + { + name: "actual", + input: input, + want: 520, + }, + } + 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: 29, + }, + { + name: "actual", + input: input, + want: 508, + }, + } + 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) + } + }) + } +} From f3396ea86ae45e649fc8fcb3a82997ea0d9f7ff9 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Tue, 20 Dec 2022 01:06:57 -0500 Subject: [PATCH 03/57] dumping 5 days of unrefined solutions :) --- 2022/day05/main.go | 126 +++++++++++++++++++++++++++ 2022/day05/main_test.go | 67 ++++++++++++++ 2022/day06/main.go | 75 ++++++++++++++++ 2022/day06/main_test.go | 59 +++++++++++++ 2022/day07/main.go | 168 +++++++++++++++++++++++++++++++++++ 2022/day07/main_test.go | 81 +++++++++++++++++ 2022/day13/main.go | 139 +++++++++++++++++++++++++++++ 2022/day13/main_test.go | 81 +++++++++++++++++ 2022/day14/main.go | 189 ++++++++++++++++++++++++++++++++++++++++ 2022/day14/main_test.go | 60 +++++++++++++ 10 files changed, 1045 insertions(+) create mode 100644 2022/day05/main.go create mode 100644 2022/day05/main_test.go create mode 100644 2022/day06/main.go create mode 100644 2022/day06/main_test.go create mode 100644 2022/day07/main.go create mode 100644 2022/day07/main_test.go create mode 100644 2022/day13/main.go create mode 100644 2022/day13/main_test.go create mode 100644 2022/day14/main.go create mode 100644 2022/day14/main_test.go diff --git a/2022/day05/main.go b/2022/day05/main.go new file mode 100644 index 0000000..a56840a --- /dev/null +++ b/2022/day05/main.go @@ -0,0 +1,126 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "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) string { + stacks, steps := parseInput(input) + + for _, step := range steps { + // move crates ONE AT A TIME + for q := 0; q < step.qty; q++ { + top := stacks[step.from][len(stacks[step.from])-1] + stacks[step.to] = append(stacks[step.to], top) + stacks[step.from] = stacks[step.from][:len(stacks[step.from])-1] + } + } + + ans := "" + for _, stack := range stacks { + ans += stack[len(stack)-1] + } + return ans +} + +func part2(input string) string { + stacks, steps := parseInput(input) + + for _, step := range steps { + // move crates ONCE + fromIndex := len(stacks[step.from]) - step.qty + stacks[step.to] = append(stacks[step.to], stacks[step.from][fromIndex:]...) + stacks[step.from] = stacks[step.from][:fromIndex] + } + + ans := "" + for _, stack := range stacks { + ans += stack[len(stack)-1] + } + return ans +} + +// move 4 from 3 to 1 +type step struct { + qty, from, to int +} + +func (s step) String() string { + return fmt.Sprintf("move %d from %d to %d", s.qty, s.from, s.to) +} + +func parseInput(input string) ([][]string, []step) { + parts := strings.Split(input, "\n\n") + + state := parts[0] + oversized := [][]string{} + for _, row := range strings.Split(state, "\n") { + oversized = append(oversized, strings.Split(row, "")) + } + oRows, oCols := len(oversized), len(oversized[0]) + + actual := [][]string{} + + for c := 0; c < oCols-1; c++ { + if oversized[oRows-1][c] != " " { + // hit a column with values... move up from here + stack := []string{} + for r := oRows - 2; r >= 0; r-- { + char := oversized[r][c] + if char != " " { + stack = append(stack, char) + } + } + actual = append(actual, stack) + } + } + + stepsRaw := parts[1] + steps := []step{} + for _, row := range strings.Split(stepsRaw, "\n") { + inst := step{} + _, err := fmt.Sscanf(row, "move %d from %d to %d", &inst.qty, &inst.from, &inst.to) + if err != nil { + panic(err) + } + // subtract one so they're zero indexed... + inst.from-- + inst.to-- + steps = append(steps, inst) + } + + return actual, steps +} diff --git a/2022/day05/main_test.go b/2022/day05/main_test.go new file mode 100644 index 0000000..d0cfba1 --- /dev/null +++ b/2022/day05/main_test.go @@ -0,0 +1,67 @@ +package main + +import ( + "testing" +) + +var example = ` [D] +[N] [C] +[Z] [M] [P] + 1 2 3 + +move 1 from 2 to 1 +move 3 from 1 to 3 +move 2 from 2 to 1 +move 1 from 1 to 2` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + { + name: "example", + input: example, + want: "CMZ", + }, + { + name: "actual", + input: input, + want: "QNHWJVJZW", + }, + } + 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 string + }{ + { + name: "example", + input: example, + want: "MCD", + }, + { + name: "actual", + input: input, + want: "BPCZJLFJW", + }, + } + 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) + } + }) + } +} diff --git a/2022/day06/main.go b/2022/day06/main.go new file mode 100644 index 0000000..e7362bc --- /dev/null +++ b/2022/day06/main.go @@ -0,0 +1,75 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "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 { + // packet starts w/ 4 characters that are all different + for i := 0; i+4 <= len(input); i++ { + if allDifferentLetters(input[i : i+4]) { + return i + 4 + } + } + + return -1 +} + +// lazy but easier than sliding window... +func allDifferentLetters(str string) bool { + // if len(str) != 4 { + // panic(fmt.Sprintf("invalid length %q", str)) + // } + for i := 0; i < len(str); i++ { + for j := i + 1; j < len(str); j++ { + if str[i] == str[j] { + return false + } + } + } + return true +} + +func part2(input string) int { + // wow super lazy but fast to write... ok + for i := 0; i+14 <= len(input); i++ { + if allDifferentLetters(input[i : i+14]) { + return i + 14 + } + } + + return -1 +} diff --git a/2022/day06/main_test.go b/2022/day06/main_test.go new file mode 100644 index 0000000..6babf10 --- /dev/null +++ b/2022/day06/main_test.go @@ -0,0 +1,59 @@ +package main + +import ( + "testing" +) + +var example = `mjqjpqmgbljsphdztnvjfqwrcgsmlb` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 7, + }, + { + name: "actual", + input: input, + want: 1109, + }, + } + 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: 19, + }, + { + name: "actual", + input: input, + want: 3965, + }, + } + 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) + } + }) + } +} diff --git a/2022/day07/main.go b/2022/day07/main.go new file mode 100644 index 0000000..2f8ef36 --- /dev/null +++ b/2022/day07/main.go @@ -0,0 +1,168 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "math" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/mathy" + "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 { + root := parseInput(input) + + return sumDirsUnder100000(root) +} + +func sumDirsUnder100000(itr *dir) int { + SizeLimit := 100000 + + sum := 0 + if itr.totalSize <= SizeLimit { + sum += itr.totalSize + } + for _, child := range itr.childDirs { + sum += sumDirsUnder100000(child) + } + return sum +} + +func part2(input string) int { + root := parseInput(input) + totalSapceAvailable := 70000000 + spaceNeeded := 30000000 + + // find smallest directory to be deleted that would free up enough space... + directoryMinSize := spaceNeeded - (totalSapceAvailable - root.totalSize) + return findSmallestDirToDelete(root, directoryMinSize) +} + +func findSmallestDirToDelete(itr *dir, directoryMinSize int) int { + smallest := math.MaxInt64 + if itr.totalSize >= directoryMinSize { + smallest = mathy.MinInt(smallest, itr.totalSize) + } + + for _, childDirs := range itr.childDirs { + smallest = mathy.MinInt(smallest, findSmallestDirToDelete(childDirs, directoryMinSize)) + } + + return smallest +} + +type dir struct { + name string + parentDir *dir + childDirs map[string]*dir + files map[string]int + totalSize int +} + +func parseInput(input string) *dir { + root := &dir{ + name: "root", + childDirs: map[string]*dir{}, + } + itr := root + + cmds := strings.Split(input, "\n") + c := 0 + + for c < len(cmds) { + switch cmd := cmds[c]; cmd[0:1] { + case "$": + if cmd == "$ ls" { + // just move on, we will assume we're always in an listing state + c++ + } else { + changeDir := strings.Split(cmd, "cd ")[1] + changeDir = strings.TrimSpace(changeDir) + if changeDir == ".." { + itr = itr.parentDir + } else { + // if changeDir doesn't exist.. + if _, ok := itr.childDirs[changeDir]; !ok { + itr.childDirs[changeDir] = &dir{ + name: changeDir, + parentDir: itr, + childDirs: map[string]*dir{}, + files: map[string]int{}} + } + + itr = itr.childDirs[changeDir] + } + c++ + } + default: + // assume we're listing a dir's contents... add it + if strings.HasPrefix(cmd, "dir") { + childDirName := cmd[4:] + if _, ok := itr.childDirs[childDirName]; !ok { + itr.childDirs[childDirName] = &dir{ + name: childDirName, + parentDir: itr, + childDirs: map[string]*dir{}, + files: map[string]int{}, + } + } + } else { + // file name + parts := strings.Split(cmd, " ") + itr.files[parts[0]] = cast.ToInt(parts[0]) + } + c++ + } + } + + populateFileSizes(root) + return root +} + +func populateFileSizes(itr *dir) int { + totalSize := 0 + + for _, childItr := range itr.childDirs { + totalSize += populateFileSizes(childItr) + } + + for _, sz := range itr.files { + totalSize += sz + } + + itr.totalSize = totalSize + + return totalSize + +} diff --git a/2022/day07/main_test.go b/2022/day07/main_test.go new file mode 100644 index 0000000..1566311 --- /dev/null +++ b/2022/day07/main_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "testing" +) + +var example = `$ cd / +$ ls +dir a +14848514 b.txt +8504156 c.dat +dir d +$ cd a +$ ls +dir e +29116 f +2557 g +62596 h.lst +$ cd e +$ ls +584 i +$ cd .. +$ cd .. +$ cd d +$ ls +4060174 j +8033020 d.log +5626152 d.ext +7214296 k` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 95437, + }, + { + name: "actual", + input: input, + want: 1423358, + }, + } + 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: 24933642, + }, + { + name: "actual", + input: input, + want: 545729, + }, + } + 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) + } + }) + } +} diff --git a/2022/day13/main.go b/2022/day13/main.go new file mode 100644 index 0000000..db67218 --- /dev/null +++ b/2022/day13/main.go @@ -0,0 +1,139 @@ +package main + +import ( + _ "embed" + "encoding/json" + "flag" + "fmt" + "sort" + "strings" + + "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 { + pairs := parseInput(input) + // sum all the indexes that are in the right order + // ONE INDEXED NOT ZERO + goodIndexSum := 0 + for i, pair := range pairs { + left, right := pair[0], pair[1] + if isInOrder(left, right) { + goodIndexSum += i + 1 + } + } + + return goodIndexSum +} + +func part2(input string) int { + pairs := parseInput(input) + allPackets := [][]interface{}{ + // good reminder that json.Unmarshal will convert numbers to float64... + // so need this to match for the way to isInOrder() function works.. + {[]interface{}{float64(2)}}, + {[]interface{}{float64(6)}}, + } + for _, pair := range pairs { + allPackets = append(allPackets, pair[0]) + allPackets = append(allPackets, pair[1]) + } + + sort.Slice(allPackets, func(i, j int) bool { + left, right := allPackets[i], allPackets[j] + return isInOrder(left, right) + }) + + ans := 1 + for i, p := range allPackets { + if fmt.Sprint(p) == "[[2]]" || fmt.Sprint(p) == "[[6]]" { + ans *= i + 1 + } + } + + return ans +} + +func parseInput(input string) (ans [][2][]interface{}) { + for _, packetPairs := range strings.Split(input, "\n\n") { + pairs := strings.Split(packetPairs, "\n") + ans = append(ans, [2][]interface{}{ + parseRawString(pairs[0]), + parseRawString(pairs[1]), + }) + } + return ans +} + +// will parse as JSON with elements as either int or []int... +func parseRawString(raw string) []interface{} { + ans := []interface{}{} + json.Unmarshal([]byte(raw), &ans) + return ans +} + +func isInOrder(left, right []interface{}) bool { + for l := 0; l < len(left); l++ { + if l > len(right)-1 { + return false + } + + // attempt to convert both to ints... + leftNum, isLeftNum := left[l].(float64) + rightNum, isRightNum := right[l].(float64) + + leftList, isLeftList := left[l].([]interface{}) + rightList, isRightList := right[l].([]interface{}) + if isLeftNum && isRightNum { + if leftNum != rightNum { + return leftNum < rightNum + } + } else if isLeftNum || isRightNum { + if isLeftNum { + leftList = []interface{}{leftNum} + } else if isRightNum { + rightList = []interface{}{rightNum} + } else { + panic(fmt.Sprintf("expected one num %T:%v, %T:%v", left[l], + left[l], right[l], right[l])) + } + return isInOrder(leftList, rightList) + } else { + // both lists + if !isLeftList || !isRightList { + panic(fmt.Sprintf("expected two lists %T:%v, %T:%v", left[l], + left[l], right[l], right[l])) + } + return isInOrder(leftList, rightList) + } + } + return true +} diff --git a/2022/day13/main_test.go b/2022/day13/main_test.go new file mode 100644 index 0000000..5bfa772 --- /dev/null +++ b/2022/day13/main_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "testing" +) + +var example = `[1,1,3,1,1] +[1,1,5,1,1] + +[[1],[2,3,4]] +[[1],4] + +[9] +[[8,7,6]] + +[[4,4],4,4] +[[4,4],4,4,4] + +[7,7,7,7] +[7,7,7] + +[] +[3] + +[[[]]] +[[]] + +[1,[2,[3,[4,[5,6,7]]]],8,9] +[1,[2,[3,[4,[5,6,0]]]],8,9]` + +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: 5760, + }, + } + 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: 140, + }, + { + name: "actual", + input: input, + want: 26670, + }, + } + 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) + } + }) + } +} diff --git a/2022/day14/main.go b/2022/day14/main.go new file mode 100644 index 0000000..186a408 --- /dev/null +++ b/2022/day14/main.go @@ -0,0 +1,189 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "math" + "sort" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/mathy" + "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 { + matrix := parseInput(input) + originCol := 0 + for i, c := range matrix[0] { + if c == "+" { + originCol = i + } + } + + ans := 0 + for !dropSand(matrix, originCol) { + ans++ + } + + return ans +} + +func part2(input string) int { + matrix := parseInput(input) + originCol := 0 + for i, c := range matrix[0] { + if c == "+" { + originCol = i + } + matrix[len(matrix)-1][i] = "#" + } + + ans := 0 + for !dropSand(matrix, originCol) { + ans++ + // COULD incorporate this into the for loop conditional but then the + // ordering is important... must check origin cell BEFORE running + // dropSand... it's easier to read here... + if matrix[0][originCol] == "o" { + break + } + } + + return ans +} + +func parseInput(input string) (matrix [][]string) { + coordSets := [][][2]int{} + lowestCol := math.MaxInt64 + highestRow := 0 + for _, line := range strings.Split(input, "\n") { + rawCoords := strings.Split(line, " -> ") + coords := [][2]int{} + for _, rawCoord := range rawCoords { + rawNums := strings.Split(rawCoord, ",") + col, row := cast.ToInt(rawNums[0]), cast.ToInt(rawNums[1]) + coord := [2]int{ + col, row, + } + coords = append(coords, coord) + + lowestCol = mathy.MinInt(lowestCol, col) + highestRow = mathy.MaxInt(highestRow, row) + } + coordSets = append(coordSets, coords) + } + + // lowering this number to 1 makes it easier to print the matrix, which I + // used for part 1... but then needed to up it for part 2... or just have a + // massive screen and make the terminal text tiny... + ExtraLeftSpace := 200 + + highestCol := 0 + for s, set := range coordSets { + for i := range set { + coordSets[s][i][0] -= lowestCol - ExtraLeftSpace + highestCol = mathy.MaxInt(highestCol, coordSets[s][i][0]) + } + } + + matrix = make([][]string, highestRow+3) + for r := range matrix { + matrix[r] = make([]string, highestCol+ExtraLeftSpace*2) + } + + for _, set := range coordSets { + for i := 1; i < len(set); i++ { + cols := []int{set[i-1][0], set[i][0]} + rows := []int{set[i-1][1], set[i][1]} + + sort.Ints(cols) + sort.Ints(rows) + + if cols[0] == cols[1] { + for r := rows[0]; r <= rows[1]; r++ { + matrix[r][cols[0]] = "#" + } + } else if rows[0] == rows[1] { + for c := cols[0]; c <= cols[1]; c++ { + matrix[rows[0]][c] = "#" + } + } + } + } + + originCol := 500 - lowestCol + ExtraLeftSpace + // make it a plus so it's searchable in the next step... or could just + // return this value too... + matrix[0][originCol] = "+" + + for i, r := range matrix { + for j := range r { + if matrix[i][j] == "" { + matrix[i][j] = "." + } + } + } + + // printMatrix(matrix) + return matrix +} + +func printMatrix(matrix [][]string) { + for _, r := range matrix { + fmt.Println(r) + } +} + +func dropSand(matrix [][]string, originCol int) (fallsIntoAbyss bool) { + r, c := 0, originCol + + for r < len(matrix)-1 { + below := matrix[r+1][c] + diagonallyLeft := matrix[r+1][c-1] + diagonallyRight := matrix[r+1][c+1] + if below == "." { + r++ + } else if diagonallyLeft == "." { + r++ + c-- + } else if diagonallyRight == "." { + r++ + c++ + } else { + matrix[r][c] = "o" + return false + } + } + + return true +} diff --git a/2022/day14/main_test.go b/2022/day14/main_test.go new file mode 100644 index 0000000..561bf24 --- /dev/null +++ b/2022/day14/main_test.go @@ -0,0 +1,60 @@ +package main + +import ( + "testing" +) + +var example = `498,4 -> 498,6 -> 496,6 +503,4 -> 502,4 -> 502,9 -> 494,9` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 24, + }, + { + name: "actual", + input: input, + want: 961, + }, + } + 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: 93, + }, + { + name: "actual", + input: input, + want: 26375, + }, + } + 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) + } + }) + } +} From 1ed6126f10d198bef909d9883ca1460d6e0c7e22 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Sun, 25 Dec 2022 12:04:59 -0500 Subject: [PATCH 04/57] ran day19 overnight... not proud of that --- 2022/day19/main.go | 211 ++++++++++++++++++++++++++++++++++++++++ 2022/day19/main_test.go | 62 ++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 2022/day19/main.go create mode 100644 2022/day19/main_test.go diff --git a/2022/day19/main.go b/2022/day19/main.go new file mode 100644 index 0000000..fcbf43e --- /dev/null +++ b/2022/day19/main.go @@ -0,0 +1,211 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/mathy" + "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 { + blueprints := parseInput(input) + + // how many geodes can be opened in 24 minutes? + sum := 0 + for _, bp := range blueprints { + st := newState(bp) + geodesMade := st.calcMostGeodes(0, map[string]int{}, 24, 24) + // fmt.Println("ID:", bp.id, geodesMade) + sum += st.blueprint.id * geodesMade + } + + // total quality of all blueprints, quality = id * (# geodes in 24 min) + return sum +} + +func part2(input string) int { + blueprints := parseInput(input) + if len(blueprints) > 3 { + blueprints = blueprints[:3] + } + + prod := 1 + for _, bp := range blueprints { + st := newState(bp) + geodesMade := st.calcMostGeodes(0, map[string]int{}, 32, 32) + // fmt.Println(bp.id, geodesMade) + prod *= geodesMade + } + + // total quality of all blueprints, quality = id * (# geodes in 24 min) + return prod +} + +type blueprint struct { + id int + oreForOreRobot int + oreForClayRobot int + oreForObsidianRobot, clayForObsidianRobot int + oreForGeodeRobot, obsidianForGeodeRobot int +} + +type state struct { + blueprint + ore, clay, obsidian, geode int + oreRobots, clayRobots, obsidianRobots, geodeRobots int +} + +func newState(blueprint blueprint) state { + return state{ + blueprint: blueprint, + oreRobots: 1, + } +} + +func (s *state) farm() { + s.ore += s.oreRobots + s.clay += s.clayRobots + s.obsidian += s.obsidianRobots + s.geode += s.geodeRobots +} + +func (s *state) hash(time int) string { + return fmt.Sprint(time, s.ore, s.clay, s.obsidian, + s.geode, s.oreRobots, s.clayRobots, s.obsidianRobots, s.geodeRobots) +} + +// NOT A POINTER METHOD SO A COPY CAN BE MADE +// this is some cheeky Go struct copying, it'd be easier to read if it was just +// directly recreating all the fields +func (s state) copy() state { + return s +} + +func (s *state) calcMostGeodes(time int, memo map[string]int, totalTime int, earliestGeode int) int { + if time == totalTime { + return s.geode + } + + h := s.hash(time) + if v, ok := memo[h]; ok { + return v + } + + if s.geode == 0 && time > earliestGeode { + return 0 + } + + // factory can try to make any possible robot, will backtrack if necessary + mostGeodes := s.geode + + // always make geode robots + if s.ore >= s.oreForGeodeRobot && + s.obsidian >= s.obsidianForGeodeRobot { + cp := s.copy() + + cp.farm() + + cp.ore -= cp.oreForGeodeRobot + cp.obsidian -= cp.obsidianForGeodeRobot + cp.geodeRobots++ + if cp.geodeRobots == 1 { + earliestGeode = mathy.MinInt(earliestGeode, time+1) + } + mostGeodes = mathy.MaxInt(mostGeodes, cp.calcMostGeodes(time+1, memo, totalTime, earliestGeode)) + + memo[h] = mostGeodes + return mostGeodes + } + + if time <= totalTime-16 && + s.oreRobots < s.oreForObsidianRobot*2 && + s.ore >= s.oreForOreRobot { + cp := s.copy() + cp.ore -= cp.oreForOreRobot + + cp.farm() + + cp.oreRobots++ + mostGeodes = mathy.MaxInt(mostGeodes, cp.calcMostGeodes(time+1, memo, totalTime, earliestGeode)) + } + if time <= totalTime-8 && + s.clayRobots < s.clayForObsidianRobot && + s.ore >= s.oreForClayRobot { + cp := s.copy() + cp.ore -= cp.oreForClayRobot + + cp.farm() + + cp.clayRobots++ + mostGeodes = mathy.MaxInt(mostGeodes, cp.calcMostGeodes(time+1, memo, totalTime, earliestGeode)) + } + if time <= totalTime-4 && + s.obsidianRobots < s.obsidianForGeodeRobot && + s.ore >= s.oreForObsidianRobot && s.clay >= s.clayForObsidianRobot { + + cp := s.copy() + cp.ore -= cp.oreForObsidianRobot + cp.clay -= cp.clayForObsidianRobot + cp.farm() + + cp.obsidianRobots++ + mostGeodes = mathy.MaxInt(mostGeodes, cp.calcMostGeodes(time+1, memo, totalTime, earliestGeode)) + } + + // or no factory production this minute + cp := s.copy() + cp.ore += cp.oreRobots + cp.clay += cp.clayRobots + cp.obsidian += cp.obsidianRobots + cp.geode += cp.geodeRobots + mostGeodes = mathy.MaxInt(mostGeodes, cp.calcMostGeodes(time+1, memo, totalTime, earliestGeode)) + + memo[h] = mostGeodes + return mostGeodes +} + +func parseInput(input string) (ans []blueprint) { + // Blueprint 1: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian. + for _, line := range strings.Split(input, "\n") { + bp := blueprint{} + _, err := fmt.Sscanf(line, "Blueprint %d: Each ore robot costs %d ore. Each clay robot costs %d ore. Each obsidian robot costs %d ore and %d clay. Each geode robot costs %d ore and %d obsidian.", + &bp.id, &bp.oreForOreRobot, &bp.oreForClayRobot, &bp.oreForObsidianRobot, + &bp.clayForObsidianRobot, &bp.oreForGeodeRobot, &bp.obsidianForGeodeRobot) + if err != nil { + panic("parsing: " + err.Error()) + } + ans = append(ans, bp) + } + return ans +} diff --git a/2022/day19/main_test.go b/2022/day19/main_test.go new file mode 100644 index 0000000..93eccc2 --- /dev/null +++ b/2022/day19/main_test.go @@ -0,0 +1,62 @@ +package main + +import ( + "testing" +) + +var example = `Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian. +Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian.` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 33, + }, + { + name: "actual", + input: input, + want: 1127, + }, + } + 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: 56 * 62, + }, + // I actually have zero idea how long this took to run, I ran it overnight + // 21 27 38 + // { + // name: "actual", + // input: input, + // want: 21546, + // }, + } + 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) + } + }) + } +} From f26777edc0c8c15fe59a6ea871f0d1283eb80a1e Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Sun, 25 Dec 2022 12:21:02 -0500 Subject: [PATCH 05/57] dump 15 17 18 --- 2022/day15/main.go | 174 ++++++++++++++++++++++ 2022/day15/main_test.go | 78 ++++++++++ 2022/day17/main.go | 312 ++++++++++++++++++++++++++++++++++++++++ 2022/day17/main_test.go | 75 ++++++++++ 2022/day18/main.go | 171 ++++++++++++++++++++++ 2022/day18/main_test.go | 99 +++++++++++++ 6 files changed, 909 insertions(+) create mode 100644 2022/day15/main.go create mode 100644 2022/day15/main_test.go create mode 100644 2022/day17/main.go create mode 100644 2022/day17/main_test.go create mode 100644 2022/day18/main.go create mode 100644 2022/day18/main_test.go diff --git a/2022/day15/main.go b/2022/day15/main.go new file mode 100644 index 0000000..3b5554c --- /dev/null +++ b/2022/day15/main.go @@ -0,0 +1,174 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/mathy" + "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, 2000000) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } else { + ans := part2(input, 4000000) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } +} + +// x is col, y is row +// unbounded grid... +// In the row where y=2000000, how many positions canNOT contain a beacon? +// +// very naive approach of marking each coord that is visible from some sensor +// then remove all beacons that are on the target row +// then just return the length of the map containing "seen" cells on target row +// +// this is brutally slow, no way this approach works for part 2 +func part1(input string, targetRow int) int { + pairs := parseInput(input) + + blockedCoords := map[[2]int]bool{} + for _, p := range pairs { + manhattanDist := mathy.ManhattanDistance(p.beaconRow, p.beaconCol, + p.sensorRow, p.sensorCol) + + // if target row is reachable, block coords on it... + blockable := manhattanDist - mathy.AbsInt(p.sensorRow-targetRow) + if blockable > 0 { + for i := 0; i <= blockable; i++ { + // add blocks to map in both left and right directions + blockedCoords[[2]int{ + targetRow, p.sensorCol - i, + }] = true + blockedCoords[[2]int{ + targetRow, p.sensorCol + i, + }] = true + } + } + } + + // remove any beacons that are present in the input? + for _, p := range pairs { + delete(blockedCoords, [2]int{p.beaconRow, p.beaconCol}) + } + + return len(blockedCoords) +} + +func part2(input string, coordLimit int) int { + pairs := parseInput(input) + + sensors := []parsedSensor{} + for _, p := range pairs { + sensors = append(sensors, parsedSensor{ + sensorRow: p.sensorRow, + sensorCol: p.sensorCol, + manhattanDist: mathy.ManhattanDistance(p.sensorCol, p.sensorRow, + p.beaconCol, p.beaconRow), + }) + } + + // search space is too large to iterate over the entire thing and check if + // SOME sensor can see that location... + // + // we can assume that the final resting point will be 1 cell away from the + // border of a (actually multiple) sensor. this runs under the assumption + // that there is only one answer + for _, sensor := range sensors { + distPlusOne := sensor.manhattanDist + 1 + + // checking in this pattern w/ manhattan distance of 1 + // 1 + // 2 3 + // 4 S 5 + // 6B7 + // 8 + for r := -distPlusOne; r <= distPlusOne; r++ { + targetRow := sensor.sensorRow + r + + if targetRow < 0 { + continue + } + if targetRow > coordLimit { + break + } + + // check left and right on the target row + // zero for first and last r's... then subtract or add it from the + // sensor's col + colOffset := distPlusOne - mathy.AbsInt(r) + colLeft := sensor.sensorCol - colOffset + colRight := sensor.sensorCol + colOffset + + if colLeft >= 0 && colLeft <= coordLimit && + !isReachable(sensors, colLeft, targetRow) { + return colLeft*4000000 + targetRow + } + if colRight >= 0 && colRight <= coordLimit && + !isReachable(sensors, colRight, targetRow) { + return colRight*4000000 + targetRow + } + } + } + panic("unreachable") +} + +type pair struct { + sensorRow, sensorCol int + beaconRow, beaconCol int +} + +func parseInput(input string) (ans []pair) { + // Sensor at x=2150774, y=3136587: closest beacon is at x=2561642, y=2914773 + for _, line := range strings.Split(input, "\n") { + p := pair{} + _, err := fmt.Sscanf(line, + "Sensor at x=%d, y=%d: closest beacon is at x=%d, y=%d", + &p.sensorCol, &p.sensorRow, &p.beaconCol, &p.beaconRow) + if err != nil { + panic("parsing: " + err.Error()) + } + ans = append(ans, p) + } + return ans +} + +type parsedSensor struct { + sensorRow, sensorCol int + manhattanDist int +} + +func isReachable(sensors []parsedSensor, c, r int) bool { + for _, sensor := range sensors { + // if reachable, break + if sensor.manhattanDist >= mathy.ManhattanDistance(c, r, + sensor.sensorCol, sensor.sensorRow) { + return true + } + + } + return false +} diff --git a/2022/day15/main_test.go b/2022/day15/main_test.go new file mode 100644 index 0000000..6f92558 --- /dev/null +++ b/2022/day15/main_test.go @@ -0,0 +1,78 @@ +package main + +import ( + "testing" +) + +var example = `Sensor at x=2, y=18: closest beacon is at x=-2, y=15 +Sensor at x=9, y=16: closest beacon is at x=10, y=16 +Sensor at x=13, y=2: closest beacon is at x=15, y=3 +Sensor at x=12, y=14: closest beacon is at x=10, y=16 +Sensor at x=10, y=20: closest beacon is at x=10, y=16 +Sensor at x=14, y=17: closest beacon is at x=10, y=16 +Sensor at x=8, y=7: closest beacon is at x=2, y=10 +Sensor at x=2, y=0: closest beacon is at x=2, y=10 +Sensor at x=0, y=11: closest beacon is at x=2, y=10 +Sensor at x=20, y=14: closest beacon is at x=25, y=17 +Sensor at x=17, y=20: closest beacon is at x=21, y=22 +Sensor at x=16, y=7: closest beacon is at x=15, y=3 +Sensor at x=14, y=3: closest beacon is at x=15, y=3 +Sensor at x=20, y=1: closest beacon is at x=15, y=3` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + targetRow int + want int + }{ + { + name: "example", + input: example, + targetRow: 10, + want: 26, + }, + { + name: "actual", + input: input, + targetRow: 2000000, + want: 4560025, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part1(tt.input, tt.targetRow); got != tt.want { + t.Errorf("part1() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_part2(t *testing.T) { + tests := []struct { + name string + input string + coordLimit int + want int + }{ + { + name: "example", + input: example, + coordLimit: 20, + want: 56000011, + }, + { + name: "actual", + input: input, + coordLimit: 4000000, + want: 12480406634249, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part2(tt.input, tt.coordLimit); got != tt.want { + t.Errorf("part2() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/2022/day17/main.go b/2022/day17/main.go new file mode 100644 index 0000000..38f4088 --- /dev/null +++ b/2022/day17/main.go @@ -0,0 +1,312 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/mathy" + "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, 1000000000000) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } +} + +func part1(input string) int { + // #### + + // .#. + // ### + // .#. + + // ..# + // ..# + // ### + + // # + // # + // # + // # + + // ## + // ## + + // chamber is 7 units wide + // rocks fall w/ 2 spaces to left wall, 3 spaces to next rock or floor + // rocks pushed by wind, then fall down 1 space + // WHEN rock touches something below (ground or another settled rock), rock is "done" and next rock appears + // this also means that rocks will be pushed once L/R before fully settling (unless blocked L/R) + + s := newState(input) + for i := 0; i < 2022; i++ { + s.dropRock() + } + + // height after 2022 rocks fall + return s.highestSettledRow + 1 // 1 indexed +} + +func part2(input string, wantedRocks int) int { + s := newState(input) + // obviously can't do the calculations literally anymore... + // need some kind of "last time this state was seen" check to skip steps + // need to hash states + // - the top 10ish rows should be plenty + // - rock index + // - value = steps since last seen AND highestRow to calc skips + + // [2]int{steps when last seen, rocks dropped, highestRow to calc diff} + pastStates := map[string][3]int{} + + // will keep track of rows that are mathematically skipped otherwise it'll + // mess with state and the settled matrix + dupeRows := 0 + + rocksDropped := 0 + for rocksDropped < wantedRocks { + s.dropRock() + rocksDropped++ + + h := s.hash(20) + if past, ok := pastStates[h]; ok { + pastSteps, pastRocksDropped, pastHighRow := past[0], past[1], past[2] + + stepsToSkip := s.stepIndex - pastSteps + rocksToSkip := rocksDropped - pastRocksDropped + rowsToAdd := s.highestSettledRow - pastHighRow + + iterationsToSkip := (wantedRocks - rocksDropped) / rocksToSkip + dupeRows += rowsToAdd * iterationsToSkip + s.stepIndex += stepsToSkip * iterationsToSkip + rocksDropped += rocksToSkip * iterationsToSkip + } else { + pastStates[h] = [3]int{ + s.stepIndex, rocksDropped, s.highestSettledRow, + } + } + } + + // height after 2022 rocks fall + return s.highestSettledRow + 1 + dupeRows // 1 indexed +} + +type state struct { + settled [][]string + highestSettledRow int + fallingCoords [][2]int + nextRockIndex int + steps []string + stepIndex int +} + +func newState(input string) state { + s := state{ + settled: [][]string{}, + highestSettledRow: -1, + fallingCoords: nil, + nextRockIndex: 0, + steps: strings.Split(input, ""), + stepIndex: 0, + } + + return s +} + +// knew I'd need this for debugging... +func (s state) printState() { + copySettled := [][]string{} + for _, row := range s.settled { + copyRow := make([]string, len(row)) + copy(copyRow, row) + copySettled = append(copySettled, copyRow) + } + + for _, coord := range s.fallingCoords { + copySettled[coord[0]][coord[1]] = "@" + } + + var sb strings.Builder + for r := len(copySettled) - 1; r >= 0; r-- { + sb.WriteString(strings.Join(copySettled[r], "") + cast.ToString(r) + "\n") + } + fmt.Println(sb.String()) +} + +func (s *state) dropRock() { + s.populateNextBaseCoords() + + highestRow := 0 + for _, c := range s.fallingCoords { + highestRow = mathy.MaxInt(highestRow, c[0]) + } + for len(s.settled) <= highestRow { + s.settled = append(s.settled, newEmptyRow()) + } + + // will be set back to nil when settled + for s.fallingCoords != nil { + switch s.steps[s.stepIndex%len(s.steps)] { + case ">": + // check if can move right + canMoveRight := true + for _, c := range s.fallingCoords { + if c[1] == 6 || s.settled[c[0]][c[1]+1] != "." { + canMoveRight = false + } + } + if canMoveRight { + for i := range s.fallingCoords { + s.fallingCoords[i][1]++ + } + } + case "<": + // check if can move left + canMoveLeft := true + for _, c := range s.fallingCoords { + if c[1] == 0 || s.settled[c[0]][c[1]-1] != "." { + canMoveLeft = false + } + } + if canMoveLeft { + for i := range s.fallingCoords { + s.fallingCoords[i][1]-- + } + } + default: + panic(s.steps[s.stepIndex]) + } + s.stepIndex++ + + // move down + canMoveDown := true + for _, c := range s.fallingCoords { + if c[0] == 0 || s.settled[c[0]-1][c[1]] != "." { + canMoveDown = false + } + } + // is blocked, draw onto settled then make nil + if !canMoveDown { + for _, c := range s.fallingCoords { + s.settled[c[0]][c[1]] = "#" + } + s.fallingCoords = nil + + for r := len(s.settled) - 1; r >= 0; r-- { + if strings.Join(s.settled[r], "") != "......." { + s.highestSettledRow = r + break + } + } + } else { + for i := range s.fallingCoords { + s.fallingCoords[i][0]-- + } + } + } +} + +func newEmptyRow() []string { + row := make([]string, 7) + for i := range row { + row[i] = "." + } + return row +} + +var baseCoords = [][][2]int{ + { + // line #### + {0, 0}, + {0, 1}, + {0, 2}, + {0, 3}, + }, { + // plus + {0, 1}, + {1, 0}, + {1, 1}, + {1, 2}, + {2, 1}, + }, { + // flipped L + {0, 0}, + {0, 1}, + {0, 2}, + {1, 2}, + {2, 2}, + }, { + // vert line + {0, 0}, + {1, 0}, + {2, 0}, + {3, 0}, + }, { + // square + {0, 0}, + {0, 1}, + {1, 0}, + {1, 1}, + }, +} + +func init() { + // add 2 cols to all baseCoords because they fall 2 off of left wall + for i := range baseCoords { + for j := range baseCoords[i] { + baseCoords[i][j][1] += 2 + } + } +} + +func (s *state) populateNextBaseCoords() { + copyCoords := make([][2]int, len(baseCoords[s.nextRockIndex])) + copy(copyCoords, baseCoords[s.nextRockIndex]) + s.nextRockIndex++ + s.nextRockIndex %= 5 + + // lowest row of baseCoords... + + for i := range copyCoords { + copyCoords[i][0] += s.highestSettledRow + 1 + 3 + } + s.fallingCoords = copyCoords +} + +// for part 2 to find return states +// NOTE: had to play with the number of rows to be hashed... 20 seems to +// work on the example input +func (s *state) hash(topRowsToHash int) string { + var sb strings.Builder + sb.WriteString(cast.ToString(s.nextRockIndex)) + for r := s.highestSettledRow; r >= 0 && r > s.highestSettledRow-topRowsToHash; r-- { + sb.WriteString("\n" + strings.Join(s.settled[r], "")) + } + return sb.String() +} diff --git a/2022/day17/main_test.go b/2022/day17/main_test.go new file mode 100644 index 0000000..06a5fef --- /dev/null +++ b/2022/day17/main_test.go @@ -0,0 +1,75 @@ +package main + +import ( + "testing" +) + +var example = `>>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 3068, + }, + { + name: "actual", + input: input, + want: 3219, + }, + } + 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 + wantedRocks int + want int + }{ + // part 1 values should work as well, so using them for extra testing + { + name: "example", + input: example, + wantedRocks: 2022, + want: 3068, + }, + { + name: "actual", + input: input, + wantedRocks: 2022, + want: 3219, + }, + { + name: "example", + input: example, + wantedRocks: 1000000000000, + want: 1514285714288, + }, + { + name: "actual", + input: input, + wantedRocks: 1000000000000, + want: 1582758620701, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part2(tt.input, tt.wantedRocks); got != tt.want { + t.Errorf("part2() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/2022/day18/main.go b/2022/day18/main.go new file mode 100644 index 0000000..adea845 --- /dev/null +++ b/2022/day18/main.go @@ -0,0 +1,171 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/mathy" + "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) + } +} + +// to six adjacent face... +var diffs = [][3]int{ + {0, 0, 1}, + {0, 1, 0}, + {1, 0, 0}, + {0, 0, -1}, + {0, -1, 0}, + {-1, 0, 0}, +} + +func part1(input string) int { + rawCoords := parseInput(input) + mapCoords := convertRawCoordsToMap(rawCoords) + + totalSurfaceArea := 0 + for _, coord := range rawCoords { + neighbors := 6 + for _, d := range diffs { + if mapCoords[[3]int{ + coord[0] - d[0], + coord[1] - d[1], + coord[2] - d[2], + }] { + neighbors-- + } + } + totalSurfaceArea += neighbors + } + + return totalSurfaceArea +} + +func part2(input string) int { + rawCoords := parseInput(input) + mapCoords := convertRawCoordsToMap(rawCoords) + + // get bounds + var limitX, limitY, limitZ int + for c := range mapCoords { + limitX = mathy.MaxInt(limitX, c[0]) + limitY = mathy.MaxInt(limitY, c[1]) + limitZ = mathy.MaxInt(limitZ, c[2]) + } + + // bfs to see if an edge can be reached + // delete if not useful + + totalExternalSurfaceArea := 0 + + for coord := range mapCoords { + totalExternalSurfaceArea += facesThatCanReachEdge(coord, mapCoords, + limitX, limitY, limitZ) + } + + // too low: 1036 + return totalExternalSurfaceArea +} + +func parseInput(input string) (ans [][3]int) { + for _, line := range strings.Split(input, "\n") { + parts := strings.Split(line, ",") + ans = append(ans, [3]int{ + cast.ToInt(parts[0]), + cast.ToInt(parts[1]), + cast.ToInt(parts[2]), + }) + } + return ans +} +func convertRawCoordsToMap(rawCoords [][3]int) map[[3]int]bool { + set := map[[3]int]bool{} + for _, coord := range rawCoords { + set[coord] = true + } + return set +} + +// there would be a big optimization here to keep track of all coords that have +// a known path to an edge, that would eliminate a lot of duplicate work... but +// i think this is a small enough problem space to ignore that... +func facesThatCanReachEdge(coord [3]int, set map[[3]int]bool, limitX, limitY, limitZ int) int { + ans := 0 + for _, d := range diffs { + next := [3]int{ + coord[0] + d[0], + coord[1] + d[1], + coord[2] + d[2], + } + + reachResult := canReachEdge(next, set, limitX, limitY, limitZ) + if reachResult { + ans++ + } + } + + return ans +} + +func canReachEdge(coord [3]int, set map[[3]int]bool, limitX, limitY, limitZ int, +) bool { + queue := [][3]int{coord} + seen := map[[3]int]bool{} + for len(queue) > 0 { + front := queue[0] + queue = queue[1:] + + // seen already or hit some other droplet, skip + if seen[front] || set[front] { + continue + } + seen[front] = true + + // edge reached + if front[0] <= 0 || front[0] >= limitX || + front[1] <= 0 || front[1] >= limitY || + front[2] <= 0 || front[2] >= limitZ { + return true + } + + for _, d := range diffs { + next := [3]int{ + front[0] + d[0], + front[1] + d[1], + front[2] + d[2], + } + queue = append(queue, next) + } + } + return false +} diff --git a/2022/day18/main_test.go b/2022/day18/main_test.go new file mode 100644 index 0000000..5947ef8 --- /dev/null +++ b/2022/day18/main_test.go @@ -0,0 +1,99 @@ +package main + +import ( + "testing" +) + +var example = `2,2,2 +1,2,2 +3,2,2 +2,1,2 +2,3,2 +2,2,1 +2,2,3 +2,2,4 +2,2,6 +1,2,5 +3,2,5 +2,1,5 +2,3,5` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 64, + }, + { + name: "actual", + input: input, + want: 4636, + }, + } + 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) + } + }) + } +} + +var flatDisc = `5,5,5 +5,5,6 +5,5,7 +5,6,5 +5,6,6 +5,6,7 +5,7,5 +5,7,6 +5,7,7` + +// 3x3x1 disc... +func Test_part2(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "dumb simple", + input: "1,1,1\n2,1,1", + want: 10, + }, + { + name: "dumber simpleer", + input: "2,1,1", + want: 6, + }, + { + name: "example", + input: example, + want: 58, + }, + { + name: "flatDisc", + input: flatDisc, + // 9 + 9 + 3 * 4 = 30 + want: 30, + }, + { + name: "actual", + input: input, + // PAIN, used coord instead of front in the bfs check :/ + want: 2572, + }, + } + 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) + } + }) + } +} From 74e397ca4c727c8c69bbba34f3da2fc693a219b3 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 26 Dec 2022 02:14:12 -0500 Subject: [PATCH 06/57] day 20, unit tests to debug == gold --- 2022/day20/main.go | 165 ++++++++++++++++++++++++++++++++++++++++ 2022/day20/main_test.go | 115 ++++++++++++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 2022/day20/main.go create mode 100644 2022/day20/main_test.go diff --git a/2022/day20/main.go b/2022/day20/main.go new file mode 100644 index 0000000..f725597 --- /dev/null +++ b/2022/day20/main.go @@ -0,0 +1,165 @@ +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") + } +} + +const part2DecryptionKey = 811589153 + +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 := mixList(input, 1, 1) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } else { + ans := mixList(input, 811589153, 10) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } +} + +func mixList(input string, decryptionKey, mixes int) int { + zeroNode, originalOrder := parseInput(input) + + for i := 0; i < len(originalOrder); i++ { + originalOrder[i].val *= decryptionKey + } + + for i := 0; i < mixes; i++ { + for _, node := range originalOrder { + node.move(len(originalOrder)) + } + } + + // sum 3 vals from value zero, 1000th, 2000th, 3000th away + + return getNodeXStepsAway(zeroNode, 1000, len(originalOrder)).val + + getNodeXStepsAway(zeroNode, 2000, len(originalOrder)).val + + getNodeXStepsAway(zeroNode, 3000, len(originalOrder)).val +} + +func getNodeXStepsAway(node *llNode, steps int, listLength int) *llNode { + if steps < 0 { + panic("negative steps") + } + + iter := node + for steps > 0 { + iter = iter.next + steps-- + } + return iter +} + +type llNode struct { + val int + prev, next *llNode +} + +func (n *llNode) move(totalLength int) { + steps := n.val + // fmt.Println("before steps", steps, "total", totalLength) + steps %= totalLength - 1 + + if steps == 0 { + // fmt.Println("zero steps") + return + } + + // find slot to fit into + + if steps < 0 { + steps += (totalLength - 1) + } + // fmt.Println("modded steps", steps) + + oldPrev, oldNext := n.prev, n.next + oldPrev.next = oldNext + oldNext.prev = oldPrev + + iter := n + for steps > 0 { + // fmt.Println("steps left", steps, "iter", iter) + iter = iter.next + if iter == n { + panic("repeat") + } + steps-- + } + + nextPrev, nextNext := iter, iter.next + // fmt.Println("nextPrev & nextNext", nextPrev, nextNext) + nextPrev.next = n + n.prev = nextPrev + nextNext.prev = n + n.next = nextNext +} + +func parseInput(input string) (zeroNode *llNode, originalOrder []*llNode) { + nums := []int{} + for _, line := range strings.Split(input, "\n") { + nums = append(nums, cast.ToInt(line)) + } + + var head, iter *llNode + for _, n := range nums { + node := &llNode{ + val: n, + prev: iter, + } + if head == nil { + head = node + iter = node + } else { + iter.next = node + iter = iter.next + } + + if iter.val == 0 { + zeroNode = iter + } + originalOrder = append(originalOrder, node) + } + + head.prev = iter + iter.next = head + + return zeroNode, originalOrder +} + +// for debugging +func listToString(head *llNode, listLength int) string { + var sb strings.Builder + for listLength > 0 { + sb.WriteString(cast.ToString(head.val) + ",") + head = head.next + listLength-- + } + return sb.String() +} + +func printList(head *llNode, listLength int) { + fmt.Println(listToString(head, listLength)) +} diff --git a/2022/day20/main_test.go b/2022/day20/main_test.go new file mode 100644 index 0000000..b1a44da --- /dev/null +++ b/2022/day20/main_test.go @@ -0,0 +1,115 @@ +package main + +import ( + _ "embed" + "testing" +) + +var example = `1 +2 +-3 +3 +-2 +0 +4` +var example2 = `1 +2 +-3 +9 +-2 +0 +4` +var example3 = `1 +2 +-3 +3 +-8 +0 +4` + +func Test_mixList(t *testing.T) { + tests := []struct { + name string + input string + decryptionKey, mixes int // for part 2 mostly + want int + }{ + { + name: "example", + input: example, + decryptionKey: 1, + mixes: 1, + want: 3, + }, + { + name: "example2", + input: example2, + decryptionKey: 1, + mixes: 1, + want: 3, + }, + { + name: "example3", + input: example3, + decryptionKey: 1, + mixes: 1, + want: 3, + }, + { + name: "actual", + input: input, + decryptionKey: 1, + mixes: 1, + want: 9945, + }, + { + name: "example", + input: example, + decryptionKey: part2DecryptionKey, + mixes: 10, + want: 1623178306, + }, + { + name: "actual", + input: input, + decryptionKey: part2DecryptionKey, + mixes: 10, + want: 3338877775442, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := mixList(tt.input, tt.decryptionKey, tt.mixes); got != tt.want { + t.Errorf("mixList() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_llNode_move(t *testing.T) { + zeroNode, nodeSlice := parseInput(`0 +1 +2 +3 +4 +5`) + + zeroNode.move(len(nodeSlice)) + originalString := "0,1,2,3,4,5," + // should be the same + if got := listToString(zeroNode, len(nodeSlice)); got != originalString { + t.Errorf("moving zero, want no change %q, got %q", originalString, got) + } + + zeroNode.prev.move(len(nodeSlice)) + if got := listToString(zeroNode, len(nodeSlice)); got != originalString { + t.Errorf("moving 5, want no change %q, got %q", originalString, got) + } + + oneNode := zeroNode.next + oneNode.move(len(nodeSlice)) + want := "0,2,1,3,4,5," + if got := listToString(zeroNode, len(nodeSlice)); got != want { + t.Errorf("moving 1, want %q got %q", want, got) + } +} From cc941cdbabd4f24566e6fb817ebe0574444809d8 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Tue, 27 Dec 2022 01:01:18 -0500 Subject: [PATCH 07/57] off by 1 using binary search... that was painful --- 2022/day21/main.go | 190 ++++++++++++++++++++++++++++++++++++++++ 2022/day21/main_test.go | 73 +++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 2022/day21/main.go create mode 100644 2022/day21/main_test.go diff --git a/2022/day21/main.go b/2022/day21/main.go new file mode 100644 index 0000000..fb18519 --- /dev/null +++ b/2022/day21/main.go @@ -0,0 +1,190 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "regexp" + "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) + + // what will monkey 'root' yell? + v, _ := bfs("root", parsed, map[string]int{}) + return v +} + +var numRegexp = regexp.MustCompile("^[0-9]+$") + +func bfs(key string, raw map[string]string, solved map[string]int) (int, error) { + if v, ok := solved[key]; ok { + return v, nil + } + + if numRegexp.MatchString(raw[key]) { + solved[key] = cast.ToInt(raw[key]) + return solved[key], nil + } + + equation := raw[key] + parts := strings.Split(equation, " ") + + if len(parts) != 3 { + return 0, fmt.Errorf("expected 3 parts for %q, got %q", key, equation) + } + + left, err := bfs(parts[0], raw, solved) + if err != nil { + return 0, err + } + right, err := bfs(parts[2], raw, solved) + if err != nil { + return 0, err + } + + switch parts[1] { + case "+": + solved[key] = left + right + case "-": + solved[key] = left - right + case "*": + solved[key] = left * right + case "/": + solved[key] = left / right + default: + panic("error with key: " + key + " string: " + equation) + } + return solved[key], nil +} + +func part2(input string) int { + raw := parseInput(input) + if len(strings.Split(raw["root"], " ")) != 3 { + panic(fmt.Sprintf("expected 3 parts to %q", raw["root"])) + } + + // change humn to something that will error in bfs so we know which branch + // of the equations is fully solvable + raw["humn"] = "humn_will_error_in_bfs" + + // basically making the root equation leftSymbol / rightSymbol = 1 in the + // inverted graph + invertedGraph := map[string]string{"root": "1"} + rootParts := strings.Split(raw["root"], " ") + rootParts[1] = "/" + raw["root"] = strings.Join(rootParts, " ") + + keyToInvert := "root" + solvedMap := map[string]int{} + + for keyToInvert != "humn" { + // find the equation, determine which side is easily solvable, and which + // is not, reverse the equation for the unsolvable variable (aka the one + // that needs to know what value humn shouts) + // end at humn + eq := raw[keyToInvert] + parts := strings.Split(eq, " ") + + leftRaw, rightRaw := parts[0], parts[2] + + leftVal, errLeft := bfs(leftRaw, raw, solvedMap) + if errLeft == nil { + invertedGraph[leftRaw] = cast.ToString(leftVal) + } + rightVal, errRight := bfs(rightRaw, raw, solvedMap) + if errRight == nil { + invertedGraph[rightRaw] = cast.ToString(rightVal) + } + + switch parts[1] { + case "+": + if errLeft != nil { + invertedGraph[leftRaw] = fmt.Sprintf("%s - %s", keyToInvert, rightRaw) + keyToInvert = leftRaw + } else if errRight != nil { + invertedGraph[rightRaw] = fmt.Sprintf("%s - %s", keyToInvert, leftRaw) + keyToInvert = rightRaw + } else { + panic(fmt.Sprintf("both vals did not error '+' %q: %q", keyToInvert, eq)) + } + case "-": + if errLeft != nil { + invertedGraph[leftRaw] = fmt.Sprintf("%s + %s", keyToInvert, rightRaw) + keyToInvert = leftRaw + } else if errRight != nil { + invertedGraph[rightRaw] = fmt.Sprintf("%s - %s", leftRaw, keyToInvert) + keyToInvert = rightRaw + } else { + panic(fmt.Sprintf("both vals did not error '-' %q: %q", keyToInvert, eq)) + } + case "*": + if errLeft != nil { + invertedGraph[leftRaw] = fmt.Sprintf("%s / %s", keyToInvert, rightRaw) + keyToInvert = leftRaw + } else if errRight != nil { + invertedGraph[rightRaw] = fmt.Sprintf("%s / %s", keyToInvert, leftRaw) + keyToInvert = rightRaw + } else { + panic(fmt.Sprintf("both vals did not error '/' %q: %q", keyToInvert, eq)) + } + case "/": + if errLeft != nil { + invertedGraph[leftRaw] = fmt.Sprintf("%s * %s", keyToInvert, rightRaw) + keyToInvert = leftRaw + } else if errRight != nil { + invertedGraph[rightRaw] = fmt.Sprintf("%s / %s", leftRaw, keyToInvert) + keyToInvert = rightRaw + } else { + panic(fmt.Sprintf("both vals did not error '*' %q: %q", keyToInvert, eq)) + } + + default: + panic(fmt.Sprintf("inverting graph: key: %q, eq: %q", keyToInvert, eq)) + } + } + + v, _ := bfs("humn", invertedGraph, map[string]int{}) + return v +} + +func parseInput(input string) map[string]string { + ans := map[string]string{} + for _, line := range strings.Split(input, "\n") { + parts := strings.Split(line, ": ") + ans[parts[0]] = parts[1] + } + return ans +} diff --git a/2022/day21/main_test.go b/2022/day21/main_test.go new file mode 100644 index 0000000..454181c --- /dev/null +++ b/2022/day21/main_test.go @@ -0,0 +1,73 @@ +package main + +import ( + "testing" +) + +var example = `root: pppw + sjmn +dbpl: 5 +cczh: sllz + lgvd +zczc: 2 +ptdq: humn - dvpt +dvpt: 3 +lfqf: 4 +humn: 5 +ljgn: 2 +sjmn: drzm * dbpl +sllz: 4 +pppw: cczh / lfqf +lgvd: ljgn * ptdq +drzm: hmdt - zczc +hmdt: 32` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 152, + }, + { + name: "actual", + input: input, + want: 194501589693264, + }, + } + 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: 301, + }, + { + name: "actual", + input: input, + want: 3887609741189, + }, + } + 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) + } + }) + } +} From 419485f35a2b3992a18b9194234bd8d9d3572965 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Wed, 28 Dec 2022 00:51:30 -0500 Subject: [PATCH 08/57] wow --- 2022/day22/main.go | 464 ++++++++++++++++++++++++++++++++++++++++ 2022/day22/main_test.go | 75 +++++++ 2 files changed, 539 insertions(+) create mode 100644 2022/day22/main.go create mode 100644 2022/day22/main_test.go diff --git a/2022/day22/main.go b/2022/day22/main.go new file mode 100644 index 0000000..beeda69 --- /dev/null +++ b/2022/day22/main.go @@ -0,0 +1,464 @@ +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 { + matrix, path := parseInput(input) + var row, col int + for c := 0; c < len(matrix[0]); c++ { + if matrix[0][c] == "." { + col = c + break + } + } + + diffIndex := 0 + diffs := [][2]int{ + {0, 1}, // start facing right + {1, 0}, // turning right will point you down + {0, -1}, // left + {-1, 0}, // turning left from index 0 makes you face up + } + + // walking over the edge wraps you around within the same direction... + // unless if it is a wall, then you're stuck + + p := 0 + for p < len(path) { + // get next direction... + indexOfLorR := p + for indexOfLorR < len(path) && + path[indexOfLorR] != 'L' && path[indexOfLorR] != 'R' { + indexOfLorR++ + } + steps := cast.ToInt(path[p:indexOfLorR]) + // try to move that many steps + for s := 0; s < steps; s++ { + diff := diffs[diffIndex] + nextRow, nextCol := row+diff[0], col+diff[1] + // mod them so they wrap if necessary + nextRow += len(matrix) + nextCol += len(matrix[0]) + + nextRow %= len(matrix) + nextCol %= len(matrix[0]) + + // if it's an empty space you need to keep looping around... + for matrix[nextRow][nextCol] == " " || matrix[nextRow][nextCol] == "" { + nextRow += diff[0] + nextCol += diff[1] + + // wrapping math... + nextRow += len(matrix) + nextCol += len(matrix[0]) + + nextRow %= len(matrix) + nextCol %= len(matrix[0]) + } + + // wall: break + if matrix[nextRow][nextCol] == "#" { + break + } + row = nextRow + col = nextCol + } + + if indexOfLorR == len(path) { + break + } + // handle turn if indexOfLorR is still in bounds + switch path[indexOfLorR] { + case 'L': + diffIndex-- + case 'R': + diffIndex++ + } + diffIndex += 4 + diffIndex %= 4 + p = indexOfLorR + 1 + } + + // final row, col, facing + // row & col are 1 indexed + // facing is indexed same as diffs slice + // 1000 * row + 4 * col + facing_index + return 1000*(row+1) + 4*(col+1) + diffIndex +} + +func parseInput(input string) ([][]string, string) { + parts := strings.Split(input, "\n\n") + + matrix := [][]string{} + topRowLen := len(strings.Split(parts[0], "\n")[0]) + + for _, line := range strings.Split(parts[0], "\n") { + matrix = append(matrix, make([]string, topRowLen)) + split := strings.Split(line, "") + copy(matrix[len(matrix)-1], split) + } + + return matrix, parts[1] +} + +func part2(input string) int { + matrix, path := parseInput(input) + var row, col int + for c := 0; c < len(matrix[0]); c++ { + if matrix[0][c] == "." { + col = c + break + } + } + + diffIndex := 0 + diffs := [][2]int{ + {0, 1}, // start facing right + {1, 0}, // turning right will point you DOWN + {0, -1}, // left + {-1, 0}, // turning left from index 0 makes you face up + } + + // shape of example + // # + // ### + // ## + // + // + // shape of my input + // ## + // # + // ## + // # + + // a lot of (hah) edge case handling to determine where to "teleport" to + // pen and paper math... might not be worth doing the example input + // because i'm going to make a literal calculation for my input shape... + + p := 0 + for p < len(path) { + // get next direction... + indexOfLorR := p + for indexOfLorR < len(path) && + path[indexOfLorR] != 'L' && path[indexOfLorR] != 'R' { + indexOfLorR++ + } + steps := cast.ToInt(path[p:indexOfLorR]) + + // try to move that many steps + for s := 0; s < steps; s++ { + diff := diffs[diffIndex] + nextRow, nextCol := row+diff[0], col+diff[1] + + // DO NOT UPDATE diffIndex here because if it's a wall we DON'T want + // to change directions + nextRow, nextCol, nextDiffIndex := handleWrap(row, col, nextRow, nextCol, diffIndex) + + // we'll never see empty spaces now because we're handling wrapping above + // wall: break + if matrix[nextRow][nextCol] == "#" { + break + } + // only update if we didn't hit a wall + row = nextRow + col = nextCol + diffIndex = nextDiffIndex + } + + if indexOfLorR == len(path) { + break + } + // handle turn if indexOfLorR is still in bounds + switch path[indexOfLorR] { + case 'L': + diffIndex-- + case 'R': + diffIndex++ + } + diffIndex += 4 + diffIndex %= 4 + p = indexOfLorR + 1 + } + + // final answer calculated from flattened map coords + // 1000 * row + 4 * col + facing_index + // too low: 111043 + return 1000*(row+1) + 4*(col+1) + diffIndex +} + +// handles edge cases ;) +// how i'll number my boxes... +// 21 +// 3 +// 54 +// 6 + +const ( + RightIndex = 0 + DownIndex = 1 + LeftIndex = 2 + UpIndex = 3 +) + +// handleWrap checks if the movement from r,c to nextRow, nextCol is off the +// edge of the matrix, if so it does the maths and direction change to wrap +// around the edge of the cube, this is very manual and based upon a drawing i +// made of my input (i'll upload it when i remember...) +// +// got a little carried away with assertions in here trying to debug... +func handleWrap(r, c, nextRow, nextCol, diffIndex int) (newRow, newCol, newDiffIndex int) { + // there will be roughly 14 checks in here... this is gonna get ugly, esp + // b/c i'm too lazy to dry this up + + // 2 -> 5 conversion + if getBoxNumber(r, c) == 2 && + 0 <= nextRow && nextRow < 50 && nextCol == 49 { + if diffIndex != LeftIndex { + panic(fmt.Sprintf("expected LeftIndex, got %d", diffIndex)) + } + + newCol = 0 + newRow = 149 - nextRow + if getBoxNumber(newRow, newCol) != 5 { + panic(fmt.Sprintf("expected to move to box 5, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, RightIndex + } + // 5 -> 2 + if getBoxNumber(r, c) == 5 && + nextCol == -1 && 100 <= nextRow && nextRow < 150 { + if diffIndex != LeftIndex { + panic(fmt.Sprintf("expected LeftIndex got %d", diffIndex)) + } + newCol = 50 + newRow = 149 - nextRow + if getBoxNumber(newRow, newCol) != 2 { + panic(fmt.Sprintf("expected to move to box 2, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, RightIndex + } + + // 3 -> 5 + if getBoxNumber(r, c) == 3 && + nextCol == 49 && 50 <= nextRow && nextRow < 100 { + if diffIndex != LeftIndex { + panic(fmt.Sprintf("expected LeftIndex, got %d", diffIndex)) + } + newRow = 100 + newCol = nextRow - 50 + if getBoxNumber(newRow, newCol) != 5 { + panic(fmt.Sprintf("expected to move to box 5, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, DownIndex + } + // 5 -> 3 + if getBoxNumber(r, c) == 5 && + nextRow == 99 && 0 <= nextCol && nextCol < 50 { + if diffIndex != UpIndex { + panic(fmt.Sprintf("expected UpIndex, got %d", diffIndex)) + } + newRow = nextCol + 50 + newCol = 50 + if getBoxNumber(newRow, newCol) != 3 { + panic(fmt.Sprintf("expected to move to box 3, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, RightIndex + } + + // 2 -> 6 + if getBoxNumber(r, c) == 2 && + nextRow == -1 && 50 <= nextCol && nextCol < 100 { + if diffIndex != UpIndex { + panic(fmt.Sprintf("expected UpIndex, got %d", diffIndex)) + } + newRow = nextCol + 100 + newCol = 0 + if getBoxNumber(newRow, newCol) != 6 { + panic(fmt.Sprintf("expected to move to box 6, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, RightIndex + } + // 6 -> 2 + if getBoxNumber(r, c) == 6 && + nextCol == -1 && 150 <= nextRow && nextRow < 200 { + if diffIndex != LeftIndex { + panic(fmt.Sprintf("expected LeftIndex, got %d", diffIndex)) + } + newRow = 0 + newCol = nextRow - 100 + if getBoxNumber(newRow, newCol) != 2 { + panic(fmt.Sprintf("expected to move to box 2, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, DownIndex + } + + // 1 -> 6 + if getBoxNumber(r, c) == 1 && + nextRow == -1 && 100 <= nextCol && nextCol < 150 { + if diffIndex != UpIndex { + panic(fmt.Sprintf("expected UpIndex, got %d", diffIndex)) + } + newRow = 199 + newCol = nextCol - 100 + if getBoxNumber(newRow, newCol) != 6 { + panic(fmt.Sprintf("expected to move to box 6, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, UpIndex + } + // 6 -> 1 + if getBoxNumber(r, c) == 6 && + nextRow == 200 && 0 <= nextCol && nextCol < 50 { + if diffIndex != DownIndex { + panic(fmt.Sprintf("expected DownIndex, got %d", diffIndex)) + } + newRow = 0 + newCol = nextCol + 100 + if getBoxNumber(newRow, newCol) != 1 { + panic(fmt.Sprintf("expected to move to box 1, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, DownIndex + } + + // 4 -> 6 + if getBoxNumber(r, c) == 4 && + nextRow == 150 && 50 <= nextCol && nextCol < 100 { + if diffIndex != DownIndex { + panic(fmt.Sprintf("expected DownIndex, got %d", diffIndex)) + } + newRow = nextCol + 100 + newCol = 49 + if getBoxNumber(newRow, newCol) != 6 { + panic(fmt.Sprintf("expected to move to box 6, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, LeftIndex + } + // 6 -> 4 + if getBoxNumber(r, c) == 6 && + nextCol == 50 && 150 <= nextRow && nextRow < 200 { + if diffIndex != RightIndex { + panic(fmt.Sprintf("expected RightIndex, got %d", diffIndex)) + } + newRow = 149 + newCol = nextRow - 100 + if getBoxNumber(newRow, newCol) != 4 { + panic(fmt.Sprintf("expected to move to box 4, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, UpIndex + } + + // 4 -> 1 + if getBoxNumber(r, c) == 4 && + nextCol == 100 && 100 <= nextRow && nextRow < 150 { + if diffIndex != RightIndex { + panic(fmt.Sprintf("expected RightIndex, got %d", diffIndex)) + } + newRow = 149 - nextRow + newCol = 149 + if getBoxNumber(newRow, newCol) != 1 { + panic(fmt.Sprintf("expected to move to box 1, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, LeftIndex + } + // 1 -> 4 + if getBoxNumber(r, c) == 1 && + nextCol == 150 && 0 <= nextRow && nextRow < 50 { + if diffIndex != RightIndex { + panic(fmt.Sprintf("expected RightIndex, got %d", diffIndex)) + } + newRow = 149 - nextRow + newCol = 99 + if getBoxNumber(newRow, newCol) != 4 { + panic(fmt.Sprintf("expected to move to box 4, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, LeftIndex + } + + // 3 -> 1 + if getBoxNumber(r, c) == 3 && + nextCol == 100 && 50 <= nextRow && nextRow < 100 { + if diffIndex != RightIndex { + panic(fmt.Sprintf("expected RightIndex, got %d", diffIndex)) + } + newRow = 49 + newCol = nextRow + 50 + if getBoxNumber(newRow, newCol) != 1 { + panic(fmt.Sprintf("expected to move to box 1, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, UpIndex + } + // 1 -> 3 + if getBoxNumber(r, c) == 1 && + nextRow == 50 && 100 <= nextCol && nextCol < 150 { + if diffIndex != DownIndex { + panic(fmt.Sprintf("expected DownIndex, got %d", diffIndex)) + } + newRow = nextCol - 50 + newCol = 99 + if getBoxNumber(newRow, newCol) != 3 { + panic(fmt.Sprintf("expected to move to box 3, got %d", getBoxNumber(newRow, newCol))) + } + return newRow, newCol, LeftIndex + } + + // no edge conversion required, just pass through + return nextRow, nextCol, diffIndex +} + +func getBoxNumber(r, c int) int { + if 0 <= r && r < 50 && 100 <= c && c < 150 { + return 1 + } + if 0 <= r && r < 50 && 50 <= c && c < 100 { + return 2 + } + if 50 <= r && r < 100 && 50 <= c && c < 100 { + return 3 + } + if 100 <= r && r < 150 && 50 <= c && c < 100 { + return 4 + } + if 100 <= r && r < 150 && 0 <= c && c < 50 { + return 5 + } + if 150 <= r && r < 200 && 0 <= c && c < 50 { + return 6 + } + + panic(fmt.Sprintf("bad row %d and col %d", r, c)) +} diff --git a/2022/day22/main_test.go b/2022/day22/main_test.go new file mode 100644 index 0000000..156a825 --- /dev/null +++ b/2022/day22/main_test.go @@ -0,0 +1,75 @@ +package main + +import ( + "testing" +) + +var example = ` ...# + .#.. + #... + .... +...#.......# +........#... +..#....#.... +..........#. + ...#.... + .....#.. + .#...... + ......#. + +10R5L5R10L4R5L5` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 6032, + }, + { + name: "actual", + input: input, + want: 144244, + }, + } + 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 + }{ + // NOTE: did not account for the example case because I manually coded + // for the shape of my exact input... which differed from the + // input shape + // { + // name: "example", + // input: example, + // want: 5031, + // }, + { + name: "actual", + input: input, + want: 138131, + }, + } + 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) + } + }) + } +} From 38a9e6204a3805c0bc58f856688fc8a65bab457b Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Thu, 29 Dec 2022 00:34:01 -0500 Subject: [PATCH 09/57] 23 & 24, nearly there! --- 2022/day23/main.go | 207 +++++++++++++++++++++++++++++ 2022/day23/main_test.go | 67 ++++++++++ 2022/day24/main.go | 286 ++++++++++++++++++++++++++++++++++++++++ 2022/day24/main_test.go | 53 ++++++++ 4 files changed, 613 insertions(+) create mode 100644 2022/day23/main.go create mode 100644 2022/day23/main_test.go create mode 100644 2022/day24/main.go create mode 100644 2022/day24/main_test.go diff --git a/2022/day23/main.go b/2022/day23/main.go new file mode 100644 index 0000000..a8225ed --- /dev/null +++ b/2022/day23/main.go @@ -0,0 +1,207 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "math" + "sort" + "strings" + + "github.com/alexchao26/advent-of-code-go/mathy" + "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) + + ans := unstableDiffusion(input, part) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) +} + +var diffsSlice = [][][2]int{ + // N + { + {-1, -1}, + {-1, 0}, + {-1, 1}, + }, + // S + { + {1, -1}, + {1, 0}, + {1, 1}, + }, + // W + { + {-1, -1}, + {0, -1}, + {1, -1}, + }, + // E + { + {-1, 1}, + {0, 1}, + {1, 1}, + }, +} +var targetDiff = [][2]int{ + {-1, 0}, // N + {1, 0}, // S + {0, -1}, // W + {0, 1}, // E +} + +func unstableDiffusion(input string, part int) int { + elfCoords := parseInput(input) + diffStartIndex := 0 + + // part 2 + var lastState string + + round := 1 + + for { + if part == 1 && round == 11 { + break + } + + elfPlannedMoves := [][][2]int{} + elvesTargetingCoord := map[[2]int]int{} + + for coords, val := range elfCoords { + if val == "#" { + nonZeroNeighbors := 0 + for _, diffSlice := range diffsSlice { + for _, d := range diffSlice { + if elfCoords[[2]int{ + coords[0] + d[0], + coords[1] + d[1], + }] == "#" { + nonZeroNeighbors++ + } + } + } + + if nonZeroNeighbors == 0 { + elfPlannedMoves = append(elfPlannedMoves, [][2]int{ + coords, coords, + }) + elvesTargetingCoord[coords]++ + } else { + foundAMove := false + for i := 0; i < 4; i++ { + diffSliceIndex := i + diffStartIndex + diffSlice := diffsSlice[diffSliceIndex%4] + neighbors := 0 + for _, d := range diffSlice { + if elfCoords[[2]int{ + coords[0] + d[0], + coords[1] + d[1], + }] == "#" { + neighbors++ + } + } + if neighbors == 0 { + nextCoords := coords + nextCoords[0] += targetDiff[diffSliceIndex%4][0] + nextCoords[1] += targetDiff[diffSliceIndex%4][1] + + elfPlannedMoves = append(elfPlannedMoves, [][2]int{ + coords, nextCoords, + }) + elvesTargetingCoord[nextCoords]++ + + foundAMove = true + break + } + } + if !foundAMove { + elfPlannedMoves = append(elfPlannedMoves, [][2]int{ + coords, coords, + }) + elvesTargetingCoord[coords]++ + } + } + + } + } + + // reset coords, but only if elves are not blocked... + elfCoords = map[[2]int]string{} + + for _, plannedMove := range elfPlannedMoves { + if elvesTargetingCoord[plannedMove[1]] > 1 { + // stay + elfCoords[plannedMove[0]] = "#" + } else { + // move + elfCoords[plannedMove[1]] = "#" + } + } + + // rotate directions that are checked + diffStartIndex++ + + if part == 2 { // hash the state + allCoords := []string{} + for c := range elfCoords { + allCoords = append(allCoords, fmt.Sprint(c)) + } + sort.Strings(allCoords) + thisState := fmt.Sprint(allCoords) + if lastState == thisState { + return round + } + lastState = thisState + } + + round++ + } + + lowRow, highRow, lowCol, highCol := math.MaxInt16, math.MinInt16, math.MaxInt16, math.MinInt16 + for coords := range elfCoords { + lowRow = mathy.MinInt(lowRow, coords[0]) + highRow = mathy.MaxInt(highRow, coords[0]) + lowCol = mathy.MinInt(lowCol, coords[1]) + highCol = mathy.MaxInt(highCol, coords[1]) + } + + ans := 0 + for r := lowRow; r <= highRow; r++ { + for c := lowCol; c <= highCol; c++ { + if elfCoords[[2]int{r, c}] != "#" { + ans++ + } + } + } + + return ans +} + +func parseInput(input string) map[[2]int]string { + ans := map[[2]int]string{} + for r, line := range strings.Split(input, "\n") { + for c, v := range strings.Split(line, "") { + if v == "#" { + ans[[2]int{r, c}] = "#" + } + } + } + return ans +} diff --git a/2022/day23/main_test.go b/2022/day23/main_test.go new file mode 100644 index 0000000..095162b --- /dev/null +++ b/2022/day23/main_test.go @@ -0,0 +1,67 @@ +package main + +import ( + "testing" +) + +var example = `....#.. +..###.# +#...#.# +.#...## +#.###.. +##.#.## +.#..#..` + +func Test_unstableDiffusion(t *testing.T) { + tests := []struct { + name string + input string + part int + want int + }{ + { + name: "small example", + input: `..... +..##. +..#.. +..... +..##. +.....`, + part: 1, + want: 30 - 5, + }, + { + name: "example", + input: example, + part: 1, + want: 110, + }, + { + name: "actual", + input: input, + part: 1, + want: 4116, + }, + + // + { + name: "example", + input: example, + part: 2, + want: 20, + }, + { + name: "actual", + input: input, + part: 2, + want: 984, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := unstableDiffusion(tt.input, tt.part); got != tt.want { + t.Errorf("part1() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/2022/day24/main.go b/2022/day24/main.go new file mode 100644 index 0000000..c251bf0 --- /dev/null +++ b/2022/day24/main.go @@ -0,0 +1,286 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "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 := blizzardJourney(input, 1) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } else { + ans := blizzardJourney(input, 2) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } +} + +func blizzardJourney(input string, part int) int { + start, end, blizzards, totalRows, totalCols := parseInput(input) + + // some form of BFS seems to be the obvious answer for "least steps" + // + // if maps are used for all blizzard coords... then the map will have to be + // constantly copied for new states + // want something... less involved + // + // could represent all blizzards fairly easily via maths + // < and > stay in same row, col = (starting +/- time) % (# of cols) + // then it's just math for "is there a blizzard here" but that does require + // iterating through every blizzard, but that's a known amount so maybe that's fine... aka close enough to constant + // + // or just calculate state at time t and store that matrix of "occupied" spaces, and then no need to recalculate + // per blizzard per time + + stepsForFirstLeg := bfs(blizzards, start, end, totalRows, totalCols, 0) + if part == 1 { + return stepsForFirstLeg + } + stepsBackToStart := bfs(blizzards, end, start, totalRows, totalCols, stepsForFirstLeg) + return bfs(blizzards, start, end, totalRows, totalCols, stepsBackToStart) +} + +func bfs(blizzards []blizzard, start, end [2]int, totalRows, totalCols, stepsElapsedAlready int) int { + cacheRoomStates := map[int][][]string{} + + type node struct { + coords [2]int + steps int + debugPath string + } + + queue := []node{} + queue = append(queue, node{ + coords: start, + steps: stepsElapsedAlready, + debugPath: fmt.Sprint(0, start), + }) + + seenCoordsSteps := map[[3]int]bool{} + for len(queue) > 0 { + popped := queue[0] + queue = queue[1:] + + roomState := calcOrGetRoomState(blizzards, popped.steps+1, totalRows, totalCols, cacheRoomStates) + + for _, diff := range [][2]int{ + {1, 0}, + {0, 1}, + {0, -1}, + {-1, 0}, + } { + nextCoords := [2]int{ + popped.coords[0] + diff[0], + popped.coords[1] + diff[1], + } + + if nextCoords == start { + continue + } + if nextCoords != start && nextCoords != end { + if nextCoords[0] < 0 || nextCoords[0] >= totalRows || + nextCoords[1] < 0 || nextCoords[1] >= totalCols { + continue + } + } + + // no point in processing a coordinate & steps pair that has already been seen + hash := [3]int{nextCoords[0], nextCoords[1], popped.steps + 1} + if seenCoordsSteps[hash] { + continue + } + seenCoordsSteps[hash] = true + + // because of how i indexed the room, need to do literal checks to see if we're in start + // or end coords + + // if blocked, continue + if nextCoords != start && nextCoords != end && + roomState[nextCoords[0]][nextCoords[1]] != "." { + continue + } + + // if out of bounds, continue + if nextCoords != start && nextCoords != end { + if nextCoords[0] < 0 || nextCoords[0] >= totalRows || + nextCoords[1] < 0 || nextCoords[1] >= totalCols { + continue + } + } + + // done + if nextCoords == end { + return popped.steps + 1 + } + + queue = append(queue, node{ + coords: nextCoords, + steps: popped.steps + 1, + debugPath: popped.debugPath + fmt.Sprint(popped.steps+1, nextCoords), + }) + } + // if possible to stay still, add "wait" move + if popped.coords == start || + roomState[popped.coords[0]][popped.coords[1]] == "." { + queue = append(queue, node{ + coords: popped.coords, + steps: popped.steps + 1, + debugPath: popped.debugPath + fmt.Sprint(popped.steps+1, popped.coords), + }) + } + } + + panic("should return from loop") +} + +type blizzard struct { + startRow, startCol int + rowSlope, colSlope int + totalRows, totalCols int + char string +} + +func (b blizzard) calculateCoords(steps int) [2]int { + row := (b.startRow + b.rowSlope*steps) % b.totalRows + col := (b.startCol + b.colSlope*steps) % b.totalCols + + row += b.totalRows + col += b.totalCols + row %= b.totalRows + col %= b.totalCols + + return [2]int{ + row, col, + } +} + +// occupied coordinates are easy to calculate based on each blizzard's movement +// and steps/time elapsed, return a matrix that represents occupied cells +// and store the result in a map to reduce future calcs +func calcOrGetRoomState(blizzards []blizzard, steps, totalRows, totalCols int, memo map[int][][]string) [][]string { + if m, ok := memo[steps]; ok { + return m + } + + matrix := make([][]string, totalRows) + for r := range matrix { + matrix[r] = make([]string, totalCols) + } + + for _, b := range blizzards { + coords := b.calculateCoords(steps) + matrix[coords[0]][coords[1]] = b.char + } + for r := 0; r < len(matrix); r++ { + for c := 0; c < len(matrix[0]); c++ { + if matrix[r][c] == "" { + matrix[r][c] = "." + } + } + } + + memo[steps] = matrix + + return matrix +} + +func parseInput(input string) ([2]int, [2]int, []blizzard, int, int) { + var start, end [2]int + blizzards := []blizzard{} + + lines := strings.Split(input, "\n") + + for c := 0; c < len(lines); c++ { + if lines[0][c] == '.' { + start = [2]int{-1, c - 1} + break + } + } + + // 0,0 will be within the BOX we start in + // start and end will be off the bounds of that box + totalRows := len(lines) - 2 + totalCols := len(lines[0]) - 2 + + for c := 0; c < len(lines[0]); c++ { + if lines[len(lines)-1][c] == '.' { + end = [2]int{totalRows, c - 1} + break + } + } + + for l := 1; l < len(lines)-1; l++ { + chars := strings.Split(lines[l], "") + for c := 1; c < len(chars)-1; c++ { + switch chars[c] { + case ">": + blizzards = append(blizzards, blizzard{ + startRow: l - 1, + startCol: c - 1, + rowSlope: 0, + colSlope: 1, + totalRows: totalRows, + totalCols: totalCols, + char: ">", + }) + case "<": + blizzards = append(blizzards, blizzard{ + startRow: l - 1, + startCol: c - 1, + rowSlope: 0, + colSlope: -1, + totalRows: totalRows, + totalCols: totalCols, + char: "<", + }) + case "^": + blizzards = append(blizzards, blizzard{ + startRow: l - 1, + startCol: c - 1, + rowSlope: -1, + colSlope: 0, + totalRows: totalRows, + totalCols: totalCols, + char: "^", + }) + case "v": + blizzards = append(blizzards, blizzard{ + startRow: l - 1, + startCol: c - 1, + rowSlope: 1, + colSlope: 0, + totalRows: totalRows, + totalCols: totalCols, + char: "v", + }) + case ".", "#": + default: + panic("unhandled char") + } + } + } + + return start, end, blizzards, totalRows, totalCols +} diff --git a/2022/day24/main_test.go b/2022/day24/main_test.go new file mode 100644 index 0000000..ef6ab8d --- /dev/null +++ b/2022/day24/main_test.go @@ -0,0 +1,53 @@ +package main + +import ( + "testing" +) + +var example = `#.###### +#>>.<^<# +#.<..<<# +#>v.><># +#<^v^^># +######.#` + +func Test_blizzardJourney(t *testing.T) { + tests := []struct { + name string + input string + part int + want int + }{ + { + name: "example", + input: example, + part: 1, + want: 18, + }, + { + name: "actual", + input: input, + part: 1, + want: 240, + }, + { + name: "example", + input: example, + part: 2, + want: 54, + }, + { + name: "actual", + input: input, + part: 2, + want: 717, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := blizzardJourney(tt.input, tt.part); got != tt.want { + t.Errorf("part1() = %v, want %v", got, tt.want) + } + }) + } +} From 8a9a64b3c4529e2b3c140e1503db3259d49b9684 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 30 Dec 2022 11:00:04 -0500 Subject: [PATCH 10/57] day25... thankfully didnt need crazy mod math --- 2022/day25/main.go | 117 ++++++++++++++++++++++++++++++++++++++++ 2022/day25/main_test.go | 67 +++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 2022/day25/main.go create mode 100644 2022/day25/main_test.go diff --git a/2022/day25/main.go b/2022/day25/main.go new file mode 100644 index 0000000..2a89d68 --- /dev/null +++ b/2022/day25/main.go @@ -0,0 +1,117 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "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) string { + snafuNums := strings.Split(input, "\n") + // sum of the fuel requirements + // power of 5... + // 2 is 2 + // 1 is 1 + // 0 is 0 + // -1 is - + // -2 is = + sum := "" + for _, n := range snafuNums { + sum = addSnafu(sum, n) + } + + return sum +} + +func addSnafu(one, two string) string { + // reversed... + split1, split2 := strings.Split(one, ""), strings.Split(two, "") + var reversed1, reversed2 []string + for i := len(split1) - 1; i >= 0; i-- { + reversed1 = append(reversed1, split1[i]) + } + for i := len(split2) - 1; i >= 0; i-- { + reversed2 = append(reversed2, split2[i]) + } + + longer, shorter := reversed1, reversed2 + if len(longer) < len(shorter) { + longer, shorter = shorter, longer + } + + charToVal := map[string]int{ + "=": -2, + "-": -1, + "0": 0, + "1": 1, + "2": 2, + } + valToChar := map[int]string{ + -2: "=", + -1: "-", + 0: "0", + 1: "1", + 2: "2", + } + + ans := make([]int, len(longer)+1) + for i := 0; i < len(longer); i++ { + sum := charToVal[longer[i]] + if i < len(shorter) { + sum += charToVal[shorter[i]] + } + ans[i] += sum + if ans[i] > 2 { + ans[i] -= 5 + ans[i+1]++ + } else if ans[i] < -2 { + ans[i] += 5 + ans[i+1]-- + } + } + + for ans[len(ans)-1] == 0 { + ans = ans[:len(ans)-1] + } + + snafu := "" + for _, a := range ans { + snafu = valToChar[a] + snafu + } + return snafu +} + +func part2(input string) string { + return ":)" +} diff --git a/2022/day25/main_test.go b/2022/day25/main_test.go new file mode 100644 index 0000000..ffdd166 --- /dev/null +++ b/2022/day25/main_test.go @@ -0,0 +1,67 @@ +package main + +import ( + _ "embed" + "testing" +) + +var example = `1=-0-2 +12111 +2=0= +21 +2=01 +111 +20012 +112 +1=-1= +1-12 +12 +1= +122` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + { + name: "example", + input: example, + want: "2=-1=0", + }, + { + name: "actual", + input: input, + want: "2----0=--1122=0=0021", + }, + } + 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 string + }{ + { + name: "example", + input: example, + want: ":)", + }, + } + 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) + } + }) + } +} From 81a20e4f396fdadd723b952a63381d7799a78415 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 30 Dec 2022 11:56:35 -0500 Subject: [PATCH 11/57] 2022/day16, well i put it off until the end, does that make it the hardest puzzle for me this year? i think so --- 2022/day16/main.go | 393 ++++++++++++++++++++++++++++++++++++++++ 2022/day16/main_test.go | 73 ++++++++ 2 files changed, 466 insertions(+) create mode 100644 2022/day16/main.go create mode 100644 2022/day16/main_test.go diff --git a/2022/day16/main.go b/2022/day16/main.go new file mode 100644 index 0000000..0f33bb7 --- /dev/null +++ b/2022/day16/main.go @@ -0,0 +1,393 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "sort" + "strings" + + "github.com/alexchao26/advent-of-code-go/mathy" + "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) + } +} + +/* +PART 1 and PART 2 ARE IMPLEMENTED COMPLETELY SEPARATELY. +If you're looking for part 2 inspiration you might want to not even look at my part1 code... +*/ + +func part1(input string) int { + // 30 min until cave erupts + // start in room AA + // creates graph to other rooms... + // flow rate is zero to start + // could spend 1 minute moving to BB and 1 more minute opening it. will release pressure of during remaining 28 of 30 minutes. 13 flow * 28 minutes = 364 total pressure lol + // then can move to CC in min 3, open in min 4, etc etc + // release max pressure in 30 min? + graph := makeGraph(input) + + open := map[string]bool{"AA": true} + for name, rm := range graph { + if rm.flowRate == 0 { + open[name] = true + } + } + + // "AA" open will not matter really... + return bfs(graph, "AA", 30, 0, open, map[string]int{}) +} + +type room struct { + name string + flowRate int + connectedTo []string +} + +func makeGraph(input string) map[string]room { + graph := map[string]room{} + + for _, line := range strings.Split(input, "\n") { + // Valve BB has flow rate=13; tunnels lead to valves CC, AA + parts := strings.Split(line, "; ") + rm := room{} + _, err := fmt.Sscanf(parts[0], "Valve %s has flow rate=%d", &rm.name, &rm.flowRate) + if err != nil { + panic("parsing valve name and flow rate" + err.Error()) + } + connections := strings.Split(parts[1], ", ") + // update first entry to remove leading string + connections[0] = connections[0][len(connections[0])-2:] + rm.connectedTo = connections + graph[rm.name] = rm + } + + return graph +} + +func bfs(graph map[string]room, currentRoom string, minutesLeft, currentPressure int, open map[string]bool, memo map[string]int) int { + if minutesLeft == 0 { + return 0 + } + + key := hash(currentRoom, minutesLeft, open, currentPressure) + if v, ok := memo[key]; ok { + return v + } + + // recursive calls will update this if it is better, then return it + bestFlow := 0 + + // there are two paths to take at a room + // 1. stay and open the valve + // this is only worth doing if the valve is not already on + // 2. move to a neighboring + + // 1. open current room's valve + if !open[currentRoom] { + open[currentRoom] = true + // totalPressureContribution := (minutesLeft - 1) * graph[currentRoom].flowRate + + newPressure := currentPressure + graph[currentRoom].flowRate + + maybeBest := currentPressure + bfs(graph, currentRoom, minutesLeft-1, newPressure, open, memo) + + bestFlow = mathy.MaxInt(bestFlow, maybeBest) + + // backtrack + open[currentRoom] = false + } + + // 2. move to neighbors + for _, neighbor := range graph[currentRoom].connectedTo { + maybeBest := currentPressure + bfs(graph, neighbor, minutesLeft-1, currentPressure, open, memo) + bestFlow = mathy.MaxInt(bestFlow, maybeBest) + } + + memo[key] = bestFlow + + return bestFlow +} + +func hash(currentRoom string, minutesLeft int, open map[string]bool, currentPressure int) string { + rms := []string{} + for k := range open { + rms = append(rms, k) + } + sort.Strings(rms) + return fmt.Sprint(currentRoom, minutesLeft, rms, currentPressure) +} + +// PART 2, basically restarting because my part1 seems too far in the opposite direction to reuse + +func part2(input string) int { + // index within [16]int arrays which will be used to track which rooms have been visited + // from analysis i know that my input has 15 non-zero pressure rooms + // every time we visit a room we'll open the valve + + graph := makeGraph(input) + + roomToFlowRate := map[string]int{} + highestFlowRatePossible := 0 // might be useful for an optimization later + + // sort room names so the arrays/slices later will be in a repeatable order (easier debugging) + // include starting room "AA" in this list even though it has zero flow rate + roomNames := []string{"AA"} + for name, room := range graph { + if room.flowRate != 0 { + roomNames = append(roomNames, name) + } + } + + // reformat into arrays/slices so we don't have to do room name lookups + flowRates := make([]int, len(roomNames)) + + sort.Strings(roomNames) + + for i, name := range roomNames { + roomToFlowRate[name] = graph[name].flowRate + flowRates[i] = roomToFlowRate[name] + highestFlowRatePossible += graph[name].flowRate + } + + weightedGraph := makeWeightedGraph(graph, roomNames) + + visitedArrayToHighestPressureTotals := map[[16]bool]int{} + dfsGetHighestPressureTotalsForEveryVisitedState(26, [16]bool{}, weightedGraph, flowRates, 0, 0, 0, visitedArrayToHighestPressureTotals) + + return highestDisjointPair(visitedArrayToHighestPressureTotals) +} + +func makeWeightedGraph(graph map[string]room, roomNames []string) [][]int { + ans := make([][]int, len(roomNames)) + for i := range ans { + ans[i] = make([]int, len(roomNames)) + } + + // bfs between every node to make graph + + for startIndex, startName := range roomNames { + for endIndex, endName := range roomNames { + if startName == endName { + continue + } + stepsBetweenRooms := bfsDistanceBetweenRooms(graph, startName, endName) + ans[startIndex][endIndex] = stepsBetweenRooms + ans[endIndex][startIndex] = stepsBetweenRooms + } + } + + return ans +} + +func bfsDistanceBetweenRooms(graph map[string]room, startName, endName string) int { + type node struct { + name string + steps int + } + + queue := []node{ + { + name: startName, + steps: 0, + }, + } + + seen := map[string]bool{} + for len(queue) > 0 { + pop := queue[0] + queue = queue[1:] + if seen[pop.name] { + continue + } + seen[pop.name] = true + + if pop.name == endName { + return pop.steps + } + for _, neighbor := range graph[pop.name].connectedTo { + queue = append(queue, node{ + name: neighbor, + steps: pop.steps + 1, + }) + } + } + // assume all rooms are reachable + panic("should return from loop") +} + +// populates ansArray with the best possible values for visiting a particular set of rooms +func dfsGetHighestPressureTotalsForEveryVisitedState(timeLeft int, visited [16]bool, + graph [][]int, flowRates []int, currentRoom int, flowRate, totalPressure int, + ansArray map[[16]bool]int) { + if timeLeft < 0 { + panic("negative timeLeft") + } + if timeLeft == 0 { + ansArray[visited] = mathy.MaxInt(ansArray[visited], totalPressure) + return + } + + // branch 1: just not moving at all + dfsGetHighestPressureTotalsForEveryVisitedState(0, visited, graph, flowRates, currentRoom, + flowRate, totalPressure+flowRate*timeLeft, ansArray) + + // rest of branches: attempt to visit every possible non-visited node + for roomIndex := range graph { + hasBeenVisited := visited[roomIndex] + if hasBeenVisited { + continue + } + + // get to room, one more to open valve + timeToOpenNextValve := graph[currentRoom][roomIndex] + 1 + // not worth visiting if the valve can't be opened in time + if timeLeft < timeToOpenNextValve { + continue + } + + // in Go this makes a full copy of the array, so &nextVisited != &visited + // this is NOT true for slices ([]bool) + nextVisited := visited + nextVisited[roomIndex] = true + dfsGetHighestPressureTotalsForEveryVisitedState( + timeLeft-timeToOpenNextValve, + nextVisited, + graph, + flowRates, + roomIndex, + flowRate+flowRates[roomIndex], + totalPressure+flowRate*timeToOpenNextValve, + ansArray) + } + +} + +func highestDisjointPair(visitedArrayToHighestPressureTotals map[[16]bool]int) int { + type finishingState struct { + bitmap int + // leaving this here to explain a simpler solution (without bitmap overkill) + // visited [16]bool + totalPressure int + } + + allFinishingStates := []finishingState{} + for visited, totalPressure := range visitedArrayToHighestPressureTotals { + allFinishingStates = append(allFinishingStates, finishingState{ + bitmap: convertToBitmap(visited), + totalPressure: totalPressure, + }) + } + + // sort in decreasing order + sort.Slice(allFinishingStates, func(i, j int) bool { + return allFinishingStates[i].totalPressure > allFinishingStates[j].totalPressure + }) + + bestCombo := -1 + + for _, baseFinishingState := range allFinishingStates { + for _, maybeDisjointFinishingState := range allFinishingStates { + pressureSum := baseFinishingState.totalPressure + maybeDisjointFinishingState.totalPressure + // allFinishingStates is sorted in decreasing order so at this point there is no reason + // to continue checking sums + if pressureSum < bestCombo { + break + } + + // only update baseFinishing state if sets are disjointed (human and elephant can't + // open the same room's valve) + // + // using bit logic is overkill, this could've been replaced with this single for loop: + // isDisjoint := true + // for i, wasVisited := range baseFinishingState.visited { + // if wasVisited && maybeDisjointFinishingState.visited[i] { + // isDisjoint = false + // break + // } + // } + if baseFinishingState.bitmap&maybeDisjointFinishingState.bitmap == 0 { + bestCombo = mathy.MaxInt(bestCombo, pressureSum) + } + + } + } + + return bestCombo +} + +func convertToBitmap(visited [16]bool) int { + var bitmap int + for i, wasVisited := range visited { + if wasVisited { + bitmap |= 1 << i + } + } + return bitmap +} + +// recheck part1 using part2 logic +func part1ViaPart2(input string) int { + graph := makeGraph(input) + + roomToFlowRate := map[string]int{} + highestFlowRatePossible := 0 // might be useful for an optimization later + + // sort room names so the arrays/slices later will be in a repeatable order (easier debugging) + // include starting room "AA" in this list even though it has zero flow rate + roomNames := []string{"AA"} + for name, room := range graph { + if room.flowRate != 0 { + roomNames = append(roomNames, name) + } + } + + // reformat into arrays/slices so we don't have to do room name lookups + flowRates := make([]int, len(roomNames)) + + sort.Strings(roomNames) + + for i, name := range roomNames { + roomToFlowRate[name] = graph[name].flowRate + flowRates[i] = roomToFlowRate[name] + highestFlowRatePossible += graph[name].flowRate + } + + weightedGraph := makeWeightedGraph(graph, roomNames) + + visitedArrayToHighestPressureTotals := map[[16]bool]int{} + dfsGetHighestPressureTotalsForEveryVisitedState(30, [16]bool{}, weightedGraph, flowRates, 0, 0, 0, visitedArrayToHighestPressureTotals) + + highest := 0 + for _, val := range visitedArrayToHighestPressureTotals { + highest = mathy.MaxInt(highest, val) + } + return highest +} diff --git a/2022/day16/main_test.go b/2022/day16/main_test.go new file mode 100644 index 0000000..56cdca8 --- /dev/null +++ b/2022/day16/main_test.go @@ -0,0 +1,73 @@ +package main + +import ( + "testing" +) + +var example = `Valve AA has flow rate=0; tunnels lead to valves DD, II, BB +Valve BB has flow rate=13; tunnels lead to valves CC, AA +Valve CC has flow rate=2; tunnels lead to valves DD, BB +Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE +Valve EE has flow rate=3; tunnels lead to valves FF, DD +Valve FF has flow rate=0; tunnels lead to valves EE, GG +Valve GG has flow rate=0; tunnels lead to valves FF, HH +Valve HH has flow rate=22; tunnel leads to valve GG +Valve II has flow rate=0; tunnels lead to valves AA, JJ +Valve JJ has flow rate=21; tunnel leads to valve II` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 1651, + }, + { + name: "actual", + input: input, + want: 1828, + }, + } + 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) + } + }) + t.Run(tt.name+"with part 2 logic", func(t *testing.T) { + if got := part1ViaPart2(tt.input); got != tt.want { + t.Errorf("part1ViaPart2() = %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: 1707, + }, + { + name: "actual", + input: input, + want: 2292, + }, + } + 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) + } + }) + } +} From 615f16cc156a958effea9fe6d770d0c15958bc54 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 30 Dec 2022 11:58:38 -0500 Subject: [PATCH 12/57] 400 * --- 350.png | Bin 11815 -> 0 bytes 400.png | Bin 0 -> 74252 bytes README.md | 5 ++++- 3 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 350.png create mode 100644 400.png diff --git a/350.png b/350.png deleted file mode 100644 index 51731c79d264952592b19497038d8a69697b01ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11815 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 Dp<*um diff --git a/400.png b/400.png new file mode 100644 index 0000000000000000000000000000000000000000..12838694adf85fe9e2b1efb66f1129a54a443a44 GIT binary patch literal 74252 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index fae27aa..bc68d93 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ -![350 stars!](./350.png) +![400 stars!](./400.png) [reddit thread after completing the first six years](https://www.reddit.com/r/adventofcode/comments/klzgnx/complete_repo_and_thoughts_in_comments/) [reddit thread post 2021](https://www.reddit.com/r/adventofcode/comments/rrog0y/all_caught_up_repo_all_gogolang_thoughts_in/) +### Quick Note +I started this in a pre-generics Go/Golang world. Maybe one day I'll come back and learn generics as they'd be quite useful here. But that's for future Alex. + ## Running Locally ### Requirements Go 1.16+ is required because [embed][embed] is used for input files. From eb16913f7c5d2d3d5b6241ebb7753b6e8fdecb59 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 22 Jan 2024 23:37:55 -0500 Subject: [PATCH 13/57] starting 2023 --- 2023/day01/main.go | 110 ++++++++++++++++++++++ 2023/day01/main_test.go | 70 ++++++++++++++ 2023/day02/main.go | 112 +++++++++++++++++++++++ 2023/day02/main_test.go | 63 +++++++++++++ 2023/day03/main.go | 198 ++++++++++++++++++++++++++++++++++++++++ 2023/day03/main_test.go | 68 ++++++++++++++ 6 files changed, 621 insertions(+) create mode 100644 2023/day01/main.go create mode 100644 2023/day01/main_test.go create mode 100644 2023/day02/main.go create mode 100644 2023/day02/main_test.go create mode 100644 2023/day03/main.go create mode 100644 2023/day03/main_test.go diff --git a/2023/day01/main.go b/2023/day01/main.go new file mode 100644 index 0000000..2ec0de6 --- /dev/null +++ b/2023/day01/main.go @@ -0,0 +1,110 @@ +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 { + var sum int + for _, line := range strings.Split(input, "\n") { + var tens, ones int + for i := 0; i < len(line); i++ { + if strings.ContainsAny(line[i:i+1], "0123456789") { + tens = cast.ToInt(line[i : i+1]) + break + } + } + for i := len(line) - 1; i >= 0; i-- { + if strings.ContainsAny(line[i:i+1], "0123456789") { + ones = cast.ToInt(line[i : i+1]) + break + } + } + sum += tens*10 + ones + } + + return sum +} + +func part2(input string) int { + prefixes := map[string]int{ + "one": 1, + "two": 2, + "three": 3, + "four": 4, + "five": 5, + "six": 6, + "seven": 7, + "eight": 8, + "nine": 9, + "zero": 0, + } + for i := 0; i <= 9; i++ { + prefixes[cast.ToString(i)] = i + } + + var sum int + for _, line := range strings.Split(input, "\n") { + var first, last int + + for len(line) > 0 { + for prefix, val := range prefixes { + if doesStringHavePrefix(line, prefix) { + if first == 0 { + first = val + } + last = val + break + } + } + + // shorten line + line = line[1:] + } + + sum += first*10 + last + } + + return sum +} + +func doesStringHavePrefix(str string, prefix string) bool { + if len(str) < len(prefix) { + return false + } + return str[:len(prefix)] == prefix +} diff --git a/2023/day01/main_test.go b/2023/day01/main_test.go new file mode 100644 index 0000000..9b773de --- /dev/null +++ b/2023/day01/main_test.go @@ -0,0 +1,70 @@ +package main + +import ( + "testing" +) + +var example = `1abc2 +pqr3stu8vwx +a1b2c3d4e5f +treb7uchet` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 142, + }, + { + name: "actual", + input: input, + want: 55488, + }, + } + 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) + } + }) + } +} + +var example2 = `two1nine +eightwothree +abcone2threexyz +xtwone3four +4nineeightseven2 +zoneight234 +7pqrstsixteen` + +func Test_part2(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example2, + want: 281, + }, + { + name: "actual", + input: input, + want: 55614, + }, + } + 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) + } + }) + } +} diff --git a/2023/day02/main.go b/2023/day02/main.go new file mode 100644 index 0000000..0fcb6f7 --- /dev/null +++ b/2023/day02/main.go @@ -0,0 +1,112 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/mathy" + "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 { + games := parseInput(input) + + var possibleIDSum int + + for _, g := range games { + isPossible := true + for _, step := range g.steps { + // only 12 red cubes, 13 green cubes, and 14 blue cubes + if step["red"] > 12 || step["green"] > 13 || step["blue"] > 14 { + isPossible = false + break + } + } + + if isPossible { + possibleIDSum += g.id + } + } + + return possibleIDSum +} + +func part2(input string) int { + games := parseInput(input) + + var sum int + + for _, g := range games { + lowestPossibleCount := map[string]int{} + + for _, step := range g.steps { + lowestPossibleCount["red"] = mathy.MaxInt(lowestPossibleCount["red"], step["red"]) + lowestPossibleCount["green"] = mathy.MaxInt(lowestPossibleCount["green"], step["green"]) + lowestPossibleCount["blue"] = mathy.MaxInt(lowestPossibleCount["blue"], step["blue"]) + } + + sum += lowestPossibleCount["red"] * lowestPossibleCount["blue"] * lowestPossibleCount["green"] + } + + return sum +} + +type game struct { + id int + steps []map[string]int +} + +func parseInput(input string) (ans []game) { + for i, line := range strings.Split(input, "\n") { + parts := strings.Split(line, ": ") + g := game{ + id: i + 1, + } + + for _, p := range strings.Split(parts[1], "; ") { + step := map[string]int{} + for _, group := range strings.Split(p, ", ") { + numberColor := strings.Split(group, " ") + if len(numberColor) != 2 { + panic(fmt.Sprintf("group not in two pieces %q", group)) + } + + step[numberColor[1]] = cast.ToInt(numberColor[0]) + } + g.steps = append(g.steps, step) + } + ans = append(ans, g) + } + return ans +} diff --git a/2023/day02/main_test.go b/2023/day02/main_test.go new file mode 100644 index 0000000..dcf2c1c --- /dev/null +++ b/2023/day02/main_test.go @@ -0,0 +1,63 @@ +package main + +import ( + "testing" +) + +var example = `Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green +Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue +Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red +Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red +Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 8, + }, + { + name: "actual", + input: input, + want: 2632, + }, + } + 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: 2286, + }, + // { + // name: "actual", + // input: input, + // want: 0, + // }, + } + 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) + } + }) + } +} diff --git a/2023/day03/main.go b/2023/day03/main.go new file mode 100644 index 0000000..d24e0b2 --- /dev/null +++ b/2023/day03/main.go @@ -0,0 +1,198 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "regexp" + "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) + } +} + +var numReg = regexp.MustCompile("[0-9]") + +func part1(input string) int { + matrix := [][]string{} + + // collect special characters + specialChars := map[string]bool{} + for _, row := range strings.Split(input, "\n") { + matrix = append(matrix, strings.Split(row, "")) + for _, val := range strings.Split(row, "") { + if !numReg.MatchString(val) && val != "." { + specialChars[val] = true + } + } + } + specialCharsString := "" + for k := range specialChars { + specialCharsString += k + } + + var sum int + + diffs := [8][2]int{ + {-1, -1}, + {-1, 0}, + {-1, 1}, + {0, -1}, + {0, 1}, + {1, -1}, + {1, 0}, + {1, 1}, + } + + seen := map[[2]int]bool{} + for r, row := range matrix { + for c, val := range row { + coords := [2]int{r, c} + if seen[coords] { + continue + } + seen[coords] = true + + // if we hit a number, collect the entire number and check along the way if it's + // adjacent to a special char + if numReg.MatchString(val) { + hasAdjacentSpecialChar := false + + numStr := "" + for j := 0; j+c < len(matrix[0]); j++ { + char := row[c+j] + // breaks on period or special character, loop itself breaks on out of range + if !numReg.MatchString(char) { + break + } + // keep collecting number + numStr += char + + // check all 8 directions for special char + for _, d := range diffs { + dr, dc := r+d[0], c+j+d[1] + if dr >= 0 && dr < len(matrix) && dc >= 0 && dc < len(matrix[0]) { + if strings.ContainsAny(matrix[dr][dc], specialCharsString) { + hasAdjacentSpecialChar = true + } + seen[[2]int{r, c + j}] = true + } + } + } + + if hasAdjacentSpecialChar { + sum += cast.ToInt(numStr) + } + + } + } + } + + return sum +} + +// getNumber returns -1 if a number is "not found" which could include the number +// already being seen +func getNumber(matrix [][]string, coord [2]int, seen map[[2]int]bool) int { + if !numReg.MatchString(matrix[coord[0]][coord[1]]) { + return -1 + } + if seen[coord] { + return -1 + } + // go to the left most digit + r, c := coord[0], coord[1] + for c-1 >= 0 { + if numReg.MatchString(matrix[r][c-1]) { + c-- + } else { + break + } + } + + numStr := "" + + for c < len(matrix[0]) && numReg.MatchString(matrix[r][c]) { + numStr += matrix[r][c] + seen[[2]int{r, c}] = true + c++ + } + + return cast.ToInt(numStr) +} + +func part2(input string) int { + // lucky edge case that there are not multiple gears that share the same number such as + // ....*.... + // .123.456. OR ...123*456*789... + // ....*.... + // with the current useage of "seen", this would only get counted once + seen := map[[2]int]bool{} + + matrix := [][]string{} + for _, row := range strings.Split(input, "\n") { + matrix = append(matrix, strings.Split(row, "")) + } + + diffs := [8][2]int{ + {-1, -1}, + {-1, 0}, + {-1, 1}, + {0, -1}, + {0, 1}, + {1, -1}, + {1, 0}, + {1, 1}, + } + + sum := 0 + for r, rows := range matrix { + for c, val := range rows { + if val == "*" { + nums := []int{} + for _, diff := range diffs { + dr, dc := r+diff[0], c+diff[1] + if dr >= 0 && dr < len(matrix) && dc >= 0 && dc < len(matrix[0]) { + foundNum := getNumber(matrix, [2]int{dr, dc}, seen) + if foundNum != -1 { + nums = append(nums, foundNum) + } + } + } + + if len(nums) == 2 { + sum += nums[0] * nums[1] + } + } + } + } + return sum +} diff --git a/2023/day03/main_test.go b/2023/day03/main_test.go new file mode 100644 index 0000000..1aed0f5 --- /dev/null +++ b/2023/day03/main_test.go @@ -0,0 +1,68 @@ +package main + +import ( + "testing" +) + +var example = `467..114.. +...*...... +..35..633. +......#... +617*...... +.....+.58. +..592..... +......755. +...$.*.... +.664.598..` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 4361, + }, + { + name: "actual", + input: input, + want: 536576, + }, + } + 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: 467835, + }, + // { + // name: "actual", + // input: input, + // want: 0, + // }, + } + 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) + } + }) + } +} From 9cc35dc9a5884163a2121370e7c7e155e06a419d Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 24 May 2024 13:49:13 -0400 Subject: [PATCH 14/57] 2023 day4 --- 2023/day04/main.go | 138 ++++++++++++++++++++++++++++++++++++++++ 2023/day04/main_test.go | 64 +++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 2023/day04/main.go create mode 100644 2023/day04/main_test.go 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) + } + }) + } +} From e325a6f6cfc6c8ce970ec3019d8e7b86a83d7c19 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 24 May 2024 15:49:16 -0400 Subject: [PATCH 15/57] 2023-05 --- 2023/day05/main.go | 196 ++++++++++++++++++++++++++++++++++++++++ 2023/day05/main_test.go | 91 +++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 2023/day05/main.go create mode 100644 2023/day05/main_test.go diff --git a/2023/day05/main.go b/2023/day05/main.go new file mode 100644 index 0000000..f6217f0 --- /dev/null +++ b/2023/day05/main.go @@ -0,0 +1,196 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "math" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/mathy" + "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 { + seeds, listOfMappingRanges := parseInput(input) + + lowestFinalNum := math.MaxInt64 + + for _, seed := range seeds { + finalMapping := getFinalMapping(seed, listOfMappingRanges) + lowestFinalNum = mathy.MinInt(lowestFinalNum, finalMapping) + } + + return lowestFinalNum +} + +func getFinalMapping(seed int, listOfMappingRanges [][]mappingRange) int { + currentVal := seed + + // jeez these are horrible variable names... + for _, mappingRanges := range listOfMappingRanges { + // if currentVal is between the sourceStart and sourceStart plus count, a mappingRange is found + // otherwise it maps to the same value and move on + // [sourceStart, sourceStart + count) <- not inclusive of sourceStart + count + for _, m := range mappingRanges { + if m.sourceStart <= currentVal && currentVal < m.sourceStart+m.count { + currentVal = m.destinationStart + (currentVal - m.sourceStart) + // then break so number is only mapped once per round of mappings + break + } + } + } + + return currentVal +} + +type mappingRange struct { + // seed to soil map: 50 98 2 + // means 2 mappings total: seed 98 maps to water 50 and seed 99 maps to soil 51 + // if a mappingRange does not exist, then it maps to the same number + sourceStart, destinationStart, count int +} + +func parseInput(input string) (seeds []int, listOfMappingRanges [][]mappingRange) { + parts := strings.Split(input, "\n\n") + + seedParts := strings.Split(parts[0], " ") + // skip first part which is "seeds: " + for i := 1; i < len(seedParts); i++ { + seeds = append(seeds, cast.ToInt(seedParts[i])) + } + + // parse mappings + for p := 1; p < len(parts); p++ { + // get separate lines and ignore throw away first line of text + mappingLines := strings.Split(parts[p], "\n")[1:] + var mappings []mappingRange + for _, l := range mappingLines { + lineParts := strings.Split(l, " ") + mappings = append(mappings, mappingRange{ + destinationStart: cast.ToInt(lineParts[0]), + sourceStart: cast.ToInt(lineParts[1]), + count: cast.ToInt(lineParts[2]), + }) + } + + listOfMappingRanges = append(listOfMappingRanges, mappings) + } + + return seeds, listOfMappingRanges +} + +func part2(input string) int { + seedRanges, listOfMappingRanges := parseInput(input) + + lowestFinalNum := math.MaxInt64 + + // store final mappings to save on duplicate calcs? + // not sure if that helped. brute force solution worked in a minute or two + // finalMappings := map[int]int{} + + for i := 0; i < len(seedRanges); i += 2 { + for count := 0; count < seedRanges[i+1]; count++ { + seed := seedRanges[i] + count + finalMapping := getFinalMapping(seed, listOfMappingRanges) + lowestFinalNum = mathy.MinInt(lowestFinalNum, finalMapping) + } + + // progress check... + fmt.Println(i, len(seedRanges)) + } + + return lowestFinalNum +} + +// //////////// +// naive solutions that are too slow with actual input +// + +func naivePart1(input string) int { + seeds, allMaps := naiveParseInput(input) + lowestFinalNum := math.MaxInt64 + + for _, s := range seeds { + finalLocation := naiveMapSeedToFinalLocation(s, allMaps) + lowestFinalNum = mathy.MinInt(lowestFinalNum, finalLocation) + } + + return lowestFinalNum +} + +func naiveMapSeedToFinalLocation(seed int, allMaps []map[int]int) int { + mappedNum := seed + + for _, m := range allMaps { + num, ok := m[mappedNum] + if ok { + mappedNum = num + } + } + + return mappedNum +} + +// assume that the mappings are in a logical order in the input +// seeds -> soil -> fertilizer -> water -> etc and not out of order... +// so a slice works fine for storing the mappings +func naiveParseInput(input string) (seeds []int, allMaps []map[int]int) { + // seed to soil map: 50 98 2 + // means 2 mappings total: seed 98 maps to water 50 and seed 99 maps to soil 51 + // if a mappingRange does not exist, then it maps to the same number + + parts := strings.Split(input, "\n\n") + + seedParts := strings.Split(parts[0], " ") + // skip first part which is "seeds: " + for i := 1; i < len(seedParts); i++ { + seeds = append(seeds, cast.ToInt(seedParts[i])) + } + + // mappings + for p := 1; p < len(parts); p++ { + // get separate lines and ignore throw away first line of text + mappingLines := strings.Split(parts[p], "\n")[1:] + m := map[int]int{} + for _, line := range mappingLines { + nums := strings.Split(line, " ") + destinationStart, sourceStart, count := cast.ToInt(nums[0]), cast.ToInt(nums[1]), cast.ToInt(nums[2]) + for c := 0; c < count; c++ { + m[sourceStart+c] = destinationStart + c + } + } + allMaps = append(allMaps, m) + } + + return seeds, allMaps +} diff --git a/2023/day05/main_test.go b/2023/day05/main_test.go new file mode 100644 index 0000000..2ee0cec --- /dev/null +++ b/2023/day05/main_test.go @@ -0,0 +1,91 @@ +package main + +import ( + "testing" +) + +var example = `seeds: 79 14 55 13 + +seed-to-soil map: +50 98 2 +52 50 48 + +soil-to-fertilizer map: +0 15 37 +37 52 2 +39 0 15 + +fertilizer-to-water map: +49 53 8 +0 11 42 +42 0 7 +57 7 4 + +water-to-light map: +88 18 7 +18 25 70 + +light-to-temperature map: +45 77 23 +81 45 19 +68 64 13 + +temperature-to-humidity map: +0 69 1 +1 0 69 + +humidity-to-location map: +60 56 37 +56 93 4` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 35, + }, + { + name: "actual", + input: input, + want: 88151870, + }, + } + 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: 46, + }, + { + name: "actual", + input: input, + want: 2008785, + }, + } + 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) + } + }) + } +} From 288438c9795fd8f08272205e0b2ed28da933040c Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Tue, 23 Jul 2024 11:03:15 -0400 Subject: [PATCH 16/57] day 06 - sadly naive solution works --- 2023/day06/main.go | 141 ++++++++++++++++++++++++++++++++++++++++ 2023/day06/main_test.go | 60 +++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 2023/day06/main.go create mode 100644 2023/day06/main_test.go diff --git a/2023/day06/main.go b/2023/day06/main.go new file mode 100644 index 0000000..af38882 --- /dev/null +++ b/2023/day06/main.go @@ -0,0 +1,141 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "regexp" + "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 { + races := parseInputPart1(input) + ans := 1 + + for _, r := range races { + ans *= wayToWinRace(r) + } + + return ans +} + +func wayToWinRace(r race) int { + // probably fast enough to do this naively for part 1... + // Time: 40 92 97 90 + // total: 319 + // but could binary search this + + var ans int + for chargeTime := 1; chargeTime < r.time; chargeTime++ { + // velocity (chargeTime) * remaining time + dist := chargeTime * (r.time - chargeTime) + if dist > r.distance { + ans++ + } + } + + return ans +} + +func part2(input string) int { + combinedRace := parseInputPart2(input) + + // binary search this? + // but the left and right and middle could all fail if the distribution is + // F F P P P P F F F F F F F F F F F F + // F = Fail, P = Pass + // and it is some kind of (UN-CENTERED) bell curve distribution + // could test 1 by 1 from left and right of time bound... + + // unfortunately you can just brute force this. + return wayToWinRace(combinedRace) + + // all the optimizations might fall apart given unkind inputs, the example + // has a distribution with many passes in the middle: + // F P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P F + // but an unkind one could have F P F F F F F F F F F F F F F F F which could + // create a linear time complexity anyways... + + // maybe there's some kind of search where you divide the input times until + // you find a passing time, so in half, then quarters, then eights, etc + // once you find a passing time you have a reduced window to search + // but if the entire entry is 0 or 1 pass and the rest fails, then it + // degrades to linear in the worst case scenario anyways, so sanitized or + // expected inputs do go a long way +} + +type race struct { + time, distance int +} + +func parseInputPart1(input string) (ans []race) { + parts := strings.Split(input, "\n") + timeParts := strings.Split(parts[0], " ") + distParts := strings.Split(parts[1], " ") + + numRegexp := regexp.MustCompile("[0-9]+") + + var t, d int + for t < len(timeParts) && d < len(distParts) { + for !numRegexp.MatchString(timeParts[t]) { + t++ + } + for !numRegexp.MatchString(distParts[d]) { + d++ + } + ans = append(ans, race{ + time: cast.ToInt(timeParts[t]), + distance: cast.ToInt(distParts[d]), + }) + t++ + d++ + } + + return ans +} + +func parseInputPart2(input string) race { + parts := strings.Split(input, "\n") + timeLine := parts[0] + distLine := parts[1] + + nonNums := regexp.MustCompile("[^0-9]+") + timeLine = nonNums.ReplaceAllString(timeLine, "") + distLine = nonNums.ReplaceAllString(distLine, "") + + return race{ + time: cast.ToInt(timeLine), + distance: cast.ToInt(distLine), + } +} diff --git a/2023/day06/main_test.go b/2023/day06/main_test.go new file mode 100644 index 0000000..5216d4f --- /dev/null +++ b/2023/day06/main_test.go @@ -0,0 +1,60 @@ +package main + +import ( + "testing" +) + +var example = `Time: 7 15 30 +Distance: 9 40 200` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 288, + }, + { + name: "actual", + input: input, + want: 6209190, + }, + } + 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: 71503, + }, + // { + // name: "actual", + // input: input, + // want: 0, + // }, + } + 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) + } + }) + } +} From d0ba1e3443c7c0c703f049901390d1305585b664 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Thu, 25 Jul 2024 11:10:40 -0400 Subject: [PATCH 17/57] day07 poker scoring --- 2023/day07/main.go | 168 +++++++++++++++++++++++++++++ 2023/day07/main_test.go | 37 +++++++ 2023/day07/part2/main.go | 196 ++++++++++++++++++++++++++++++++++ 2023/day07/part2/main_test.go | 37 +++++++ 4 files changed, 438 insertions(+) create mode 100644 2023/day07/main.go create mode 100644 2023/day07/main_test.go create mode 100644 2023/day07/part2/main.go create mode 100644 2023/day07/part2/main_test.go diff --git a/2023/day07/main.go b/2023/day07/main.go new file mode 100644 index 0000000..a03be8f --- /dev/null +++ b/2023/day07/main.go @@ -0,0 +1,168 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "sort" + "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) + + ans := part1(input) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) +} + +func part1(input string) int { + allPlayers := parseInput(input) + + sort.Sort(allPlayers) + + var ans int + for i, player := range allPlayers { + ans += int(player.bid) * int(i+1) + } + return ans +} + +type player struct { + hand string + bid int + + handTypeScore int +} + +func parseInput(input string) (ans SortablePlayers) { + for _, line := range strings.Split(input, "\n") { + handAndBid := strings.Split(line, " ") + pl := player{ + hand: handAndBid[0], + bid: cast.ToInt(handAndBid[1]), + } + pl.handTypeScore = scoreHandType(pl.hand) + ans = append(ans, pl) + } + return ans +} + +/* point assignments: + five of a kind -> 7 + four of a kind -> 6 + full house -> 5 + three of a kind -> 4 + two pair -> 3 + one pair -> 2 + high card -> 1 +*/ + +func scoreHandType(hand string) int { + counts := map[string]int{} + for _, card := range strings.Split(hand, "") { + counts[card]++ + } + + // high card + if len(counts) == 5 { + return 1 + } + // one pair + if len(counts) == 4 { + return 2 + } + + if len(counts) == 3 { + // either two pair or three of a kind + for _, ct := range counts { + if ct == 3 { + return 4 // 3 of a kind, 3 1 1 + } + } + return 3 // two pair, 2 2 1 + } + + if len(counts) == 2 { + // full house (3 2) or four of a kind (4 1) + for _, ct := range counts { + if ct == 3 { + return 5 + } + } + return 6 + } + + if len(counts) == 1 { + return 7 + } + + panic(fmt.Sprintf("error scoring hand: %+v", hand)) +} + +type SortablePlayers []player + +func (ps SortablePlayers) Len() int { return len(ps) } +func (ps SortablePlayers) Swap(i, j int) { + ps[i], ps[j] = ps[j], ps[i] +} +func (ps SortablePlayers) Less(i, j int) bool { + iTypeScore := ps[i].handTypeScore + jTypeScore := ps[j].handTypeScore + + if iTypeScore == jTypeScore { + // higher score goes to end of ps slice + return !doesPlayer1WinTiebreak(ps[i], ps[j]) + } + return iTypeScore < jTypeScore +} + +var cardValuesMap = map[string]int{ + "A": 14, + "K": 13, + "Q": 12, + "J": 11, + "T": 10, + "9": 9, + "8": 8, + "7": 7, + "6": 6, + "5": 5, + "4": 4, + "3": 3, + "2": 2, +} + +// returns true if player1 wins the tie break +func doesPlayer1WinTiebreak(p1, p2 player) bool { + if p1.handTypeScore != p2.handTypeScore { + panic("p1 and p2 scores do not have the same level hand") + } + p1Cards := strings.Split(p1.hand, "") + p2Cards := strings.Split(p2.hand, "") + for i := 0; i < len(p1Cards); i++ { + if cardValuesMap[p1Cards[i]] > cardValuesMap[p2Cards[i]] { + return true + } else if cardValuesMap[p1Cards[i]] < cardValuesMap[p2Cards[i]] { + return false + } + } + panic("not expecting to have two matching hands") +} diff --git a/2023/day07/main_test.go b/2023/day07/main_test.go new file mode 100644 index 0000000..a597505 --- /dev/null +++ b/2023/day07/main_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "testing" +) + +var example = `32T3K 765 +T55J5 684 +KK677 28 +KTJJT 220 +QQQJA 483` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 6440, + }, + { + name: "actual", + input: input, + want: 248569531, + }, + } + 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) + } + }) + } +} diff --git a/2023/day07/part2/main.go b/2023/day07/part2/main.go new file mode 100644 index 0000000..1abd8aa --- /dev/null +++ b/2023/day07/part2/main.go @@ -0,0 +1,196 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "sort" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/util" +) + +/** +This implementation is too tangled to cover part 1 and 2 because the sort +logic is encapsulated in the interface which cannot (nicely) be made aware +of which part is being run... +There are two main differences highlighted by "// NOTE: diff to part 1 here" +*/ + +//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) + + ans := part2(input) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) +} + +func part2(input string) int { + allPlayers := parseInput(input, 1) + + sort.Sort(allPlayers) + + var ans int + for i, player := range allPlayers { + ans += int(player.bid) * int(i+1) + } + return ans +} + +type player struct { + hand string + bid int + + handTypeScore int +} + +func parseInput(input string, part int) (ans SortablePlayers) { + for _, line := range strings.Split(input, "\n") { + handAndBid := strings.Split(line, " ") + pl := player{ + hand: handAndBid[0], + bid: cast.ToInt(handAndBid[1]), + } + pl.handTypeScore = scoreHandType(pl, part) + ans = append(ans, pl) + } + return ans +} + +/* point assignments: + five of a kind -> 7 + four of a kind -> 6 + full house -> 5 + three of a kind -> 4 + two pair -> 3 + one pair -> 2 + high card -> 1 +*/ + +func scoreHandType(p player, part int) int { + counts := map[string]int{} + for _, card := range strings.Split(p.hand, "") { + counts[card]++ + } + + // NOTE: diff to part 1 here + if counts["J"] != 0 { + // add the J counts to the highest card's count as that will always result in the best hand? + jCount := counts["J"] + // remove "J" from map in case if it's the highest count + delete(counts, "J") + + var highestCard string + var highestCount int + for card, ct := range counts { + if ct > highestCount { + highestCount = ct + highestCard = card + } + } + + counts[highestCard] += jCount + } + + // high card + if len(counts) == 5 { + return 1 + } + // one pair + if len(counts) == 4 { + return 2 + } + + if len(counts) == 3 { + // either two pair or three of a kind + for _, ct := range counts { + if ct == 3 { + return 4 // 3 of a kind, 3 1 1 + } + } + return 3 // two pair, 2 2 1 + } + + if len(counts) == 2 { + // full house (3 2) or four of a kind (4 1) + for _, ct := range counts { + if ct == 3 { + return 5 + } + } + return 6 + } + + if len(counts) == 1 { + return 7 + } + + panic(fmt.Sprintf("error scoring hand: %+v", p.hand)) +} + +type SortablePlayers []player + +func (ps SortablePlayers) Len() int { return len(ps) } +func (ps SortablePlayers) Swap(i, j int) { + ps[i], ps[j] = ps[j], ps[i] +} +func (ps SortablePlayers) Less(i, j int) bool { + iTypeScore := ps[i].handTypeScore + jTypeScore := ps[j].handTypeScore + + if iTypeScore == jTypeScore { + // higher score goes to end of ps slice + return !doesPlayer1WinTiebreak(ps[i], ps[j]) + } + return iTypeScore < jTypeScore +} + +var cardValuesMap = map[string]int{ + "A": 14, + "K": 13, + "Q": 12, + // can just leave the same values though, the heiarchy is what matters + "T": 10, + "9": 9, + "8": 8, + "7": 7, + "6": 6, + "5": 5, + "4": 4, + "3": 3, + "2": 2, + // NOTE: diff to part 1 here: J is the worst card now + "J": 0, +} + +// returns true if player1 wins the tie break +func doesPlayer1WinTiebreak(p1, p2 player) bool { + if p1.handTypeScore != p2.handTypeScore { + panic("p1 and p2 scores do not have the same level hand") + } + p1Cards := strings.Split(p1.hand, "") + p2Cards := strings.Split(p2.hand, "") + for i := 0; i < len(p1Cards); i++ { + if cardValuesMap[p1Cards[i]] > cardValuesMap[p2Cards[i]] { + return true + } else if cardValuesMap[p1Cards[i]] < cardValuesMap[p2Cards[i]] { + return false + } + } + panic("not expecting to have two matching hands") +} diff --git a/2023/day07/part2/main_test.go b/2023/day07/part2/main_test.go new file mode 100644 index 0000000..2c071ff --- /dev/null +++ b/2023/day07/part2/main_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "testing" +) + +var example = `32T3K 765 +T55J5 684 +KK677 28 +KTJJT 220 +QQQJA 483` + +func Test_part2(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 5905, + }, + { + name: "actual", + input: input, + want: 250382098, + }, + } + 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) + } + }) + } +} From 906ed8a9ff566c83112c1bcec85cb558a30a8ced Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 26 Jul 2024 12:03:39 -0400 Subject: [PATCH 18/57] day 8 - lucky lucky kind inputs --- 2023/day08/main.go | 145 ++++++++++++++++++++++++++++++++++++++++ 2023/day08/main_test.go | 74 ++++++++++++++++++++ mathy/lcm.go | 18 +++++ 3 files changed, 237 insertions(+) create mode 100644 2023/day08/main.go create mode 100644 2023/day08/main_test.go create mode 100644 mathy/lcm.go diff --git a/2023/day08/main.go b/2023/day08/main.go new file mode 100644 index 0000000..7b69902 --- /dev/null +++ b/2023/day08/main.go @@ -0,0 +1,145 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/mathy" + "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 { + steps, graph := parseInput(input) + + location := "AAA" + numOfSteps := 0 + for location != "ZZZ" { + dir := steps[numOfSteps%len(steps)] + + if dir == "L" { + location = graph[location][0] + } else { + location = graph[location][1] + } + + numOfSteps++ + } + + return numOfSteps +} + +func part2(input string) int { + steps, graph := parseInput(input) + + locations := []string{} + for loc := range graph { + if loc[2:3] == "A" { + locations = append(locations, loc) + } + } + + /** + brute force doesn't work... need to figure out cycle times of each starting location + but they won't cycle just based on number of steps because of the weird L-R randomness + + so we can only rely on the "full cycle" of all steps before it loops + + - there are six starting locations + + NOTE: BIG assumptions based on KIND inputs + - assume that the Z-end locations will sync EXACTLY at the end of a cycle of steps + - after further analyzing logs of the end of each cycle, the entry point VERY kindly deposits us + at the very start of a cycle that will eventually end in a Z-end location + AAA -> MLM -> ... -> XKZ -> MLM -> ... -> XKZ -> MLM -> ... -> XKZ -> MLM + and this holds true for all six locations in my input + Therefore the cycles are not offset by a particular number of steps at the start to get to the cycle + such as START --> LOC1 --> LOC2 --> Start -> A + ^ | + | v + D <-- C + this makes the maths fairly straight forward with just having to find the LCM (least common multiple) + of all the cycle periods because that is when they will all sync up and land on a Z + */ + + numOfSteps := 0 + + locationCyclePeriods := []int{} + for cycle := 0; len(locations) > 0; cycle++ { + for _, dir := range steps { + for i, loc := range locations { + if dir == "L" { + locations[i] = graph[loc][0] + } else { + locations[i] = graph[loc][1] + } + } + numOfSteps++ + } + + // if any location is at a z-end at the end of a cycle, record the cycle time + // to do the final maths at the end + newLocations := []string{} + for _, loc := range locations { + if loc[2:3] == "Z" { + locationCyclePeriods = append(locationCyclePeriods, numOfSteps) + } else { + newLocations = append(newLocations, loc) + } + } + locations = newLocations + } + + // combine all into an LCM (helper function added to mathy package) + lcm := locationCyclePeriods[0] + for i := 1; i < len(locationCyclePeriods); i++ { + lcm = mathy.LeastCommonMultiple(lcm, locationCyclePeriods[i]) + } + + return lcm +} + +func parseInput(input string) (steps []string, graph map[string][]string) { + graph = map[string][]string{} + + parts := strings.Split(input, "\n\n") + steps = strings.Split(parts[0], "") + + for _, line := range strings.Split(parts[1], "\n") { + graph[line[0:3]] = []string{ + line[7:10], + line[12:15], + } + } + + return steps, graph +} diff --git a/2023/day08/main_test.go b/2023/day08/main_test.go new file mode 100644 index 0000000..5c0f7c1 --- /dev/null +++ b/2023/day08/main_test.go @@ -0,0 +1,74 @@ +package main + +import ( + "testing" +) + +var example = `LLR + +AAA = (BBB, BBB) +BBB = (AAA, ZZZ) +ZZZ = (ZZZ, ZZZ)` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 6, + }, + { + name: "actual", + input: input, + want: 18673, + }, + } + 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) + } + }) + } +} + +var examplePart2 = `LR + +11A = (11B, XXX) +11B = (XXX, 11Z) +11Z = (11B, XXX) +22A = (22B, XXX) +22B = (22C, 22C) +22C = (22Z, 22Z) +22Z = (22B, 22B) +XXX = (XXX, XXX)` + +func Test_part2(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: examplePart2, + want: 6, + }, + { + name: "actual", + input: input, + want: 17972669116327, + }, + } + 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) + } + }) + } +} diff --git a/mathy/lcm.go b/mathy/lcm.go new file mode 100644 index 0000000..4394439 --- /dev/null +++ b/mathy/lcm.go @@ -0,0 +1,18 @@ +package mathy + +func LeastCommonMultiple(i, j int) int { + gcd := GreatestCommonMultiple(i, j) + + return (i * j) / gcd +} + +func GreatestCommonMultiple(i, j int) int { + if j > i { + i, j = j, i + } + + if j == 0 { + return i + } + return GreatestCommonMultiple(j, i%j) +} From 0d9ea17b6f1e2e1b787801a25208c11d3910b6ee Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 29 Jul 2024 00:10:37 -0400 Subject: [PATCH 19/57] day 09 --- 2023/day09/main.go | 128 ++++++++++++++++++++++++++++++++++++++++ 2023/day09/main_test.go | 61 +++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 2023/day09/main.go create mode 100644 2023/day09/main_test.go diff --git a/2023/day09/main.go b/2023/day09/main.go new file mode 100644 index 0000000..59d32be --- /dev/null +++ b/2023/day09/main.go @@ -0,0 +1,128 @@ +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) + ans := 0 + for _, hist := range parsed { + ans += findNextValue(hist) + } + + return ans +} + +func findNextValue(history []int) int { + matrix := [][]int{ + history, + } + nonZeroFound := true + for nonZeroFound { + nonZeroFound = false + next := []int{} + + matrixHistoryRow := matrix[len(matrix)-1] + for i := 1; i < len(matrixHistoryRow); i++ { + prev := matrixHistoryRow[i-1] + curr := matrixHistoryRow[i] + next = append(next, curr-prev) + if next[len(next)-1] != 0 { + nonZeroFound = true + } + } + matrix = append(matrix, next) + } + + ans := 0 + for _, row := range matrix { + ans += row[len(row)-1] + } + + return ans +} + +func part2(input string) int { + parsed := parseInput(input) + ans := 0 + for _, hist := range parsed { + ans += findPrevValue(hist) + } + + return ans +} + +func findPrevValue(history []int) int { + matrix := [][]int{ + history, + } + nonZeroFound := true + for nonZeroFound { + nonZeroFound = false + next := []int{} + + matrixHistoryRow := matrix[len(matrix)-1] + for i := 1; i < len(matrixHistoryRow); i++ { + prev := matrixHistoryRow[i-1] + curr := matrixHistoryRow[i] + next = append(next, curr-prev) + if next[len(next)-1] != 0 { + nonZeroFound = true + } + } + matrix = append(matrix, next) + } + + ans := 0 + for r := len(matrix) - 1; r >= 0; r-- { + ans = matrix[r][0] - ans + } + + return ans +} + +func parseInput(input string) (ans [][]int) { + for _, line := range strings.Split(input, "\n") { + nums := []int{} + for _, str := range strings.Split(line, " ") { + nums = append(nums, cast.ToInt(str)) + } + ans = append(ans, nums) + } + return ans +} diff --git a/2023/day09/main_test.go b/2023/day09/main_test.go new file mode 100644 index 0000000..d3914c9 --- /dev/null +++ b/2023/day09/main_test.go @@ -0,0 +1,61 @@ +package main + +import ( + "testing" +) + +var example = `0 3 6 9 12 15 +1 3 6 10 15 21 +10 13 16 21 30 45` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 114, + }, + { + name: "actual", + input: input, + want: 1782868781, + }, + } + 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: 2, + }, + { + name: "actual", + input: input, + want: 1057, + }, + } + 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) + } + }) + } +} From 68b2419b137dc08f79c92731b9964104c8b3e99e Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Tue, 30 Jul 2024 11:49:49 -0400 Subject: [PATCH 20/57] day 10 part 1 overkill --- 2023/day10/main.go | 149 ++++++++++++++++++++++++++++++++++++++++ 2023/day10/main_test.go | 123 +++++++++++++++++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 2023/day10/main.go create mode 100644 2023/day10/main_test.go diff --git a/2023/day10/main.go b/2023/day10/main.go new file mode 100644 index 0000000..3388928 --- /dev/null +++ b/2023/day10/main.go @@ -0,0 +1,149 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "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) + + ans := pipeMaze(input, part) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) +} + +var pipes = map[string][2][2]int{ + "|": { + {-1, 0}, // up + {1, 0}, // down + }, + "-": { + {0, -1}, // left + {0, 1}, //right + }, + "L": { + {-1, 0}, // up + {0, 1}, // right + }, + "J": { + {-1, 0}, // up + {0, -1}, // left + }, + "7": { + {0, -1}, // left + {1, 0}, // down + }, + "F": { + {0, 1}, // right + {1, 0}, // down + }, +} + +func pipeMaze(input string, part int) int { + grid := parseInput(input) + + var r, c int + for i, row := range grid { + for j, val := range row { + if val == "S" { + r = i + c = j + } + } + } + + // need to find which directions are connected to the initial cell + // could traverse along the four directions and see which two lead back to + // the start... all pipes only connect two coordinates so it's actually + // fairly easy to traverse without creating a huge search space + + // note this soultion was overkill for part 1 + // inputs are nice and exactly two adjacent cells that connect to S + var loopCoords map[[2]int]bool + for pipeType := range pipes { + // assign the start square to a random pipeType, then see if it will loop + grid[r][c] = pipeType + + seen := map[[2]int]bool{} + toAnalyze := [][2]int{ + {r, c}, + } + didLoop := false + for len(toAnalyze) > 0 { + coords := toAnalyze[0] + if seen[coords] { + didLoop = true + break + } + seen[coords] = true + toAnalyze = toAnalyze[1:] + + if diffs, ok := pipes[grid[coords[0]][coords[1]]]; ok { + for _, diff := range diffs { + nextRow, nextCol := coords[0]+diff[0], coords[1]+diff[1] + if isInRange(grid, nextRow, nextCol) && !seen[[2]int{nextRow, nextCol}] { + toAnalyze = append(toAnalyze, [2]int{nextRow, nextCol}) + } + } + } + + } + if didLoop { + loopCoords = seen + break + } + } + + if part == 1 { + return len(loopCoords) / 2 + } + + // part 2 + + newGrid := make([][]string, len(grid)) + for i := 0; i < len(grid); i++ { + for j := 0; j < len(grid[0]); j++ { + if loopCoords[[2]int{i, j}] { + newGrid[i] = append(newGrid[i], grid[i][j]) + } else { + newGrid[i] = append(newGrid[i], ".") + } + } + } + + for _, r := range newGrid { + fmt.Println(r) + } + + return -1 +} + +func isInRange(grid [][]string, row, col int) bool { + return row >= 0 && row < len(grid) && col >= 0 && col <= len(grid) +} + +func parseInput(input string) (ans [][]string) { + for _, line := range strings.Split(input, "\n") { + ans = append(ans, strings.Split(line, "")) + } + return ans +} diff --git a/2023/day10/main_test.go b/2023/day10/main_test.go new file mode 100644 index 0000000..73fdb05 --- /dev/null +++ b/2023/day10/main_test.go @@ -0,0 +1,123 @@ +package main + +import ( + "testing" +) + +var example = `..... +.S-7. +.|.|. +.L-J. +.....` + +var complexExample = `..F7. +.FJ|. +SJ.L7 +|F--J +LJ...` + +var examplePart2 = `........... +.S-------7. +.|F-----7|. +.||.....||. +.||.....||. +.|L-7.F-J|. +.|..|.|..|. +.L--J.L--J. +...........` +var examplePart2_2 = `.......... +.S------7. +.|F----7|. +.||OOOO||. +.||OOOO||. +.|L-7F-J|. +.|II||II|. +.L--JL--J. +..........` +var examplePart2_large = `.F----7F7F7F7F-7.... +.|F--7||||||||FJ.... +.||.FJ||||||||L7.... +FJL7L7LJLJ||LJ.L-7.. +L--J.L7...LJS7F-7L7. +....F-J..F7FJ|L7L7L7 +....L7.F7||L7|.L7L7| +.....|FJLJ|FJ|F7|.LJ +....FJL-7.||.||||... +....L---J.LJ.LJLJ...` +var examplePart2_larger = `FF7FSF7F7F7F7F7F---7 +L|LJ||||||||||||F--J +FL-7LJLJ||||||LJL-77 +F--JF--7||LJLJ7F7FJ- +L---JF-JLJ.||-FJLJJ7 +|F|F-JF---7F7-L7L|7| +|FFJF7L7F-JF7|JL---7 +7-L-JL7||F7|L7F-7F7| +L.L7LFJ|||||FJL7||LJ +L7JLJL-JLJLJL--JLJ.L` + +func Test_pipeMaze(t *testing.T) { + tests := []struct { + name string + input string + part int + want int + }{ + { + name: "example", + input: example, + part: 1, + want: 4, + }, + { + name: "complexExample", + input: complexExample, + part: 1, + want: 8, + }, + { + name: "actual part 1", + input: input, + part: 1, + want: 6773, + }, + + // part 2 + { + name: "examplePart2", + input: examplePart2, + part: 2, + want: 4, + }, + { + name: "examplePart2_2", + input: examplePart2_2, + part: 2, + want: 4, + }, + { + name: "examplePart2_large", + input: examplePart2_large, + part: 2, + want: 8, + }, + { + name: "examplePart2_larger", + input: examplePart2_larger, + part: 2, + want: 10, + }, + // { + // name: "actual part 2", + // input: input, + // part: 2, + // want: 0, + // }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pipeMaze(tt.input, tt.part); got != tt.want { + t.Errorf("pipeMaze() = %v, want %v", got, tt.want) + } + }) + } +} From dab6f914daebd775f590d0132cf30f90bc022a2b Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Tue, 30 Jul 2024 12:52:48 -0400 Subject: [PATCH 21/57] day 10 part 2 --- 2023/day10/main.go | 199 +++++++++++++++++++++++++++++++--------- 2023/day10/main_test.go | 12 +-- 2 files changed, 161 insertions(+), 50 deletions(-) diff --git a/2023/day10/main.go b/2023/day10/main.go index 3388928..5dafc85 100644 --- a/2023/day10/main.go +++ b/2023/day10/main.go @@ -71,46 +71,30 @@ func pipeMaze(input string, part int) int { } } - // need to find which directions are connected to the initial cell - // could traverse along the four directions and see which two lead back to - // the start... all pipes only connect two coordinates so it's actually - // fairly easy to traverse without creating a huge search space + fillGridLocation(grid, r, c) - // note this soultion was overkill for part 1 - // inputs are nice and exactly two adjacent cells that connect to S - var loopCoords map[[2]int]bool - for pipeType := range pipes { - // assign the start square to a random pipeType, then see if it will loop - grid[r][c] = pipeType - - seen := map[[2]int]bool{} - toAnalyze := [][2]int{ - {r, c}, - } - didLoop := false - for len(toAnalyze) > 0 { - coords := toAnalyze[0] - if seen[coords] { - didLoop = true - break - } - seen[coords] = true - toAnalyze = toAnalyze[1:] - - if diffs, ok := pipes[grid[coords[0]][coords[1]]]; ok { - for _, diff := range diffs { - nextRow, nextCol := coords[0]+diff[0], coords[1]+diff[1] - if isInRange(grid, nextRow, nextCol) && !seen[[2]int{nextRow, nextCol}] { - toAnalyze = append(toAnalyze, [2]int{nextRow, nextCol}) - } - } - } - - } - if didLoop { - loopCoords = seen + // traverse entire loop to determine length + loopCoords := map[[2]int]bool{} + toVisit := [][2]int{ + {r, c}, + } + for len(toVisit) > 0 { + coords := toVisit[0] + if loopCoords[coords] { break } + loopCoords[coords] = true + toVisit = toVisit[1:] + + // assumes loop is well formed, will cause a panic if not + diffs := pipes[grid[coords[0]][coords[1]]] + for _, diff := range diffs { + nextRow, nextCol := coords[0]+diff[0], coords[1]+diff[1] + if isInRange(grid, nextRow, nextCol) && !loopCoords[[2]int{nextRow, nextCol}] { + toVisit = append(toVisit, [2]int{nextRow, nextCol}) + } + } + } if part == 1 { @@ -119,26 +103,153 @@ func pipeMaze(input string, part int) int { // part 2 - newGrid := make([][]string, len(grid)) + // create copy of grid with all non-loop spots replaced with a period + reducedGrid := make([][]string, len(grid)) for i := 0; i < len(grid); i++ { for j := 0; j < len(grid[0]); j++ { if loopCoords[[2]int{i, j}] { - newGrid[i] = append(newGrid[i], grid[i][j]) + reducedGrid[i] = append(reducedGrid[i], grid[i][j]) } else { - newGrid[i] = append(newGrid[i], ".") + reducedGrid[i] = append(reducedGrid[i], ".") } } } - for _, r := range newGrid { - fmt.Println(r) + // expand grid to double plus 2 in both dimensions to account for squeezing between pipes + // the plus two is to add an empty row/column on each side for easier traversing from the outside + expandedGrid := [][]string{} + expandedGrid = append(expandedGrid, make([]string, len(reducedGrid[0])*2+2)) + + for r, rows := range reducedGrid { + expandedGrid = append(expandedGrid, make([]string, len(reducedGrid[0])*2+2)) + for c, val := range rows { + expandedGrid[r*2+1][c*2+1] = val + } + // empty row + expandedGrid = append(expandedGrid, make([]string, len(reducedGrid[0])*2+2)) } - return -1 + // fill gaps between loop coords so we have an encased area again + // we can naively try to fill in every empty spot because only ones with two valid connecting + // pipes will be filled + for r, rows := range expandedGrid { + for c, val := range rows { + if val == "" { + fillGridLocation(expandedGrid, r, c) + } + } + } + + // replacing empty strings with spaces makes the printout human readable + for r, rows := range expandedGrid { + for c, val := range rows { + if val == "" { + expandedGrid[r][c] = " " + } + } + } + + toVisit = [][2]int{ + {0, 0}, + } + seen := map[[2]int]bool{} + for len(toVisit) > 0 { + coords := toVisit[0] + toVisit = toVisit[1:] + if seen[coords] { + continue + } + seen[coords] = true + + // delete reachable dots + if expandedGrid[coords[0]][coords[1]] == "." { + expandedGrid[coords[0]][coords[1]] = " " + } + + for _, diff := range [][2]int{ + {-1, 0}, + {1, 0}, + {0, -1}, + {0, 1}, + } { + nextRow := coords[0] + diff[0] + nextCol := coords[1] + diff[1] + if isInRange(expandedGrid, nextRow, nextCol) { + if expandedGrid[nextRow][nextCol] == "." || expandedGrid[nextRow][nextCol] == " " { + toVisit = append(toVisit, [2]int{nextRow, nextCol}) + } + } + } + } + + // count remaining dots + var ans int + for _, rows := range expandedGrid { + for _, val := range rows { + if val == "." { + ans++ + } + } + } + + // for _, rows := range expandedGrid { + // fmt.Println(rows) + // } + + return ans +} + +func fillGridLocation(grid [][]string, r, c int) { + // inputs are nice and exactly two adjacent cells that connect to S + // check four directions from start + leftCol := c - 1 + rightCol := c + 1 + upRow := r - 1 + downRow := r + 1 + + var combinedString string + // check left for inRange and possible valid pipe types + if isInRange(grid, r, leftCol) && + (grid[r][leftCol] == "-" || grid[r][leftCol] == "L" || grid[r][leftCol] == "F") { + combinedString += "left" + } + // right + if isInRange(grid, r, rightCol) && + (grid[r][rightCol] == "-" || grid[r][rightCol] == "J" || grid[r][rightCol] == "7") { + combinedString += "right" + } + // up + if isInRange(grid, upRow, c) && + (grid[upRow][c] == "|" || grid[upRow][c] == "7" || grid[upRow][c] == "F") { + combinedString += "up" + } + if isInRange(grid, downRow, c) && + (grid[downRow][c] == "|" || grid[downRow][c] == "J" || grid[downRow][c] == "L") { + combinedString += "down" + } + + switch combinedString { + case "leftup": + grid[r][c] = "J" + case "leftdown": + grid[r][c] = "7" + case "rightup": + grid[r][c] = "L" + case "rightdown": + grid[r][c] = "F" + case "leftright": + grid[r][c] = "-" + case "updown": + grid[r][c] = "|" + // default: + // do not panic so we can use this function more naively for the expanded grid + // could return an error instead and choose to check it for part1 where we NEED it to find a result + // panic("ineligible configuration: " + combinedString) + } } func isInRange(grid [][]string, row, col int) bool { - return row >= 0 && row < len(grid) && col >= 0 && col <= len(grid) + return row >= 0 && row < len(grid) && col >= 0 && col < len(grid[0]) } func parseInput(input string) (ans [][]string) { diff --git a/2023/day10/main_test.go b/2023/day10/main_test.go index 73fdb05..c90d4ac 100644 --- a/2023/day10/main_test.go +++ b/2023/day10/main_test.go @@ -106,12 +106,12 @@ func Test_pipeMaze(t *testing.T) { part: 2, want: 10, }, - // { - // name: "actual part 2", - // input: input, - // part: 2, - // want: 0, - // }, + { + name: "actual part 2", + input: input, + part: 2, + want: 493, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From e915846f0255ebcff4bcdeb4ed50c9d618f6582b Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Wed, 31 Jul 2024 15:48:18 -0400 Subject: [PATCH 22/57] day 11 expanding galaxies --- 2023/day11/main.go | 116 ++++++++++++++++++++++++++++++++++++++++ 2023/day11/main_test.go | 65 ++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 2023/day11/main.go create mode 100644 2023/day11/main_test.go diff --git a/2023/day11/main.go b/2023/day11/main.go new file mode 100644 index 0000000..ebbae93 --- /dev/null +++ b/2023/day11/main.go @@ -0,0 +1,116 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/mathy" + "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, 2) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } else { + ans := part1(input, 1000000) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } +} + +func part1(input string, expansionFactor int) int { + grid := parseInput(input) + + // record which rows and cols are empty first + emptyRows := map[int]bool{} + emptyCols := map[int]bool{} + + for r := 0; r < len(grid); r++ { + galaxyFound := false + for c := 0; c < len(grid[0]); c++ { + if grid[r][c] != "." { + galaxyFound = true + break + } + } + if !galaxyFound { + emptyRows[r] = true + } + } + for c := 0; c < len(grid[0]); c++ { + galaxyFound := false + for r := 0; r < len(grid); r++ { + if grid[r][c] != "." { + galaxyFound = true + break + } + } + if !galaxyFound { + emptyCols[c] = true + } + } + + // traverse grid and calculate coordinates of each galaxy while accumulating the expanded rows/cols + // that can be added into the galaxy's coordinates as they're found + galaxyCoords := map[int][2]int{} + expandedRowsToAdd := 0 + for r := 0; r < len(grid); r++ { + if emptyRows[r] { + expandedRowsToAdd += expansionFactor - 1 + continue + } + + expendedColsToAdd := 0 + for c := 0; c < len(grid[0]); c++ { + if emptyCols[c] { + expendedColsToAdd += expansionFactor - 1 + continue + } + + if grid[r][c] == "#" { + galaxyCoords[len(galaxyCoords)] = [2]int{ + r + expandedRowsToAdd, + c + expendedColsToAdd, + } + } + } + } + + // shortest distance is basically manhattan distance, helper function handles absolute values + totalDistance := 0 + for i := 0; i < len(galaxyCoords); i++ { + for j := i + 1; j < len(galaxyCoords); j++ { + g1, g2 := galaxyCoords[i], galaxyCoords[j] + totalDistance += mathy.ManhattanDistance(g1[0], g1[1], g2[0], g2[1]) + } + } + + return totalDistance +} + +func parseInput(input string) (ans [][]string) { + for _, line := range strings.Split(input, "\n") { + ans = append(ans, strings.Split(line, "")) + } + return ans +} diff --git a/2023/day11/main_test.go b/2023/day11/main_test.go new file mode 100644 index 0000000..4f5e6c0 --- /dev/null +++ b/2023/day11/main_test.go @@ -0,0 +1,65 @@ +package main + +import ( + "testing" +) + +var example = `...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#.....` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + expansionFactor int + want int + }{ + { + name: "example", + input: example, + expansionFactor: 2, + want: 374, + }, + { + name: "actual", + input: input, + expansionFactor: 2, + want: 9734203, + }, + + // part 2 + { + name: "example", + input: example, + expansionFactor: 10, + want: 1030, + }, + { + name: "example", + input: example, + expansionFactor: 100, + want: 8410, + }, + { + name: "actual", + input: input, + expansionFactor: 1000000, + want: 568914596391, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part1(tt.input, tt.expansionFactor); got != tt.want { + t.Errorf("part1() = %v, want %v", got, tt.want) + } + }) + } +} From 57eae313ea0f0c12a6c46c27670acfe03bcaee48 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Thu, 1 Aug 2024 16:54:28 -0400 Subject: [PATCH 23/57] day 12 memo hurting my brain --- 2023/day12/main.go | 261 ++++++++++++++++++++++++++++++++++++++++ 2023/day12/main_test.go | 64 ++++++++++ 2 files changed, 325 insertions(+) create mode 100644 2023/day12/main.go create mode 100644 2023/day12/main_test.go diff --git a/2023/day12/main.go b/2023/day12/main.go new file mode 100644 index 0000000..74b36b9 --- /dev/null +++ b/2023/day12/main.go @@ -0,0 +1,261 @@ +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 { + stringConditions := parseInput(input) + + ans := 0 + // brute force creating all possible combination per line + // then check each possibility + // input is 1000 lines with 10k ?'s total, so approx 10/line + // 2^10 = 1024 options per line approx. * 1000 = 1_024_000 checks total... seems ok... for part 1... + for _, sc := range stringConditions { + possibilities := generatePossibilities(sc.record) + + for _, p := range possibilities { + if checkIfSpringRecordFitsDamagedGroupCounts(p, sc.damagedGroupCounts) { + ans++ + } + } + } + + return ans +} + +func generatePossibilities(record []string) [][]string { + var recurse func(record []string, index int) [][]string + recurse = func(record []string, index int) [][]string { + if index == len(record) { + cp := make([]string, len(record)) + copy(cp, record) + return [][]string{cp} + } + + if record[index] != "?" { + return recurse(record, index+1) + } + possibilities := [][]string{} + record[index] = "#" + possibilities = append(possibilities, recurse(record, index+1)...) + + record[index] = "." + possibilities = append(possibilities, recurse(record, index+1)...) + + record[index] = "?" + + return possibilities + } + + return recurse(record, 0) +} + +func checkIfSpringRecordFitsDamagedGroupCounts(condition []string, damagedGroupCounts []int) bool { + consecutiveDamagedCount := 0 + foundDamageGroupCounts := []int{} + for _, cond := range condition { + if cond == "." { + if consecutiveDamagedCount != 0 { + foundDamageGroupCounts = append(foundDamageGroupCounts, consecutiveDamagedCount) + } + consecutiveDamagedCount = 0 + } else { + consecutiveDamagedCount++ + } + } + if consecutiveDamagedCount != 0 { + foundDamageGroupCounts = append(foundDamageGroupCounts, consecutiveDamagedCount) + } + + if len(damagedGroupCounts) == len(foundDamageGroupCounts) { + for i := 0; i < len(damagedGroupCounts); i++ { + if damagedGroupCounts[i] != foundDamageGroupCounts[i] { + return false + } + } + return true + } + + return false +} + +func part2(input string) int { + // brute force will not work for part 2 presumably. 2^10 becomes 2^50 which is 1 trillion times larger? + + stringConditions := parseInput(input) + + // hacky hacky way to update string conditions... + for i, sc := range stringConditions { + for x := 0; x < 4; x++ { + stringConditions[i].record = append(stringConditions[i].record, "?") + stringConditions[i].record = append(stringConditions[i].record, sc.record...) + stringConditions[i].damagedGroupCounts = append(stringConditions[i].damagedGroupCounts, sc.damagedGroupCounts...) + } + // adding a "." at the end helps future logic ensure that the final damaged group will be ended + stringConditions[i].record = append(stringConditions[i].record, ".") + } + + ans := 0 + for _, sc := range stringConditions { + memoOfPossibilities := map[[3]int]int{} + ans += memo(sc, 0, 0, 0, memoOfPossibilities) + } + + return ans +} + +func memo(sc springCondition, index, doneGroups, currentGroupSize int, memoOfPossibilities map[[3]int]int) int { + // key of 0, 0, 0 holds final answer of possible results + key := [3]int{index, doneGroups, currentGroupSize} + + if ans, ok := memoOfPossibilities[key]; ok { + return ans + } + + // if the end of the record is reached, and all damaged groups are accounted for + // do not need to check for currentGroupSize because of the trailing "." that was added + if index == len(sc.record) && doneGroups == len(sc.damagedGroupCounts) { + memoOfPossibilities[key] = 1 + return 1 + } + + // any other scenario where we've reached the final index means this possibility is invalid + if index == len(sc.record) { + memoOfPossibilities[key] = 0 + return 0 + } + + // damaged spring groups are all accounted for but ran into an additional broken spring, + // this branch is not valid + if doneGroups == len(sc.damagedGroupCounts) && sc.record[index] == "#" { + memoOfPossibilities[key] = 0 + return 0 + } + + // handle ".", "#" or "?" + possibilities := 0 + if sc.record[index] == "." { + // end the previous group + if index == 0 { + possibilities = memo(sc, index+1, 0, 0, memoOfPossibilities) + } else if currentGroupSize == 0 { + possibilities = memo(sc, index+1, doneGroups, 0, memoOfPossibilities) + } else if currentGroupSize != 0 { + // we have a non-zero current group size so if all damaged groups are accounted for, + // there are no possibilities left for this branch + if doneGroups == len(sc.damagedGroupCounts) { + possibilities = 0 + } else { + // not all damaged groups are accounted for + // if the current group is the right size, recurse; if not, then zero possibilities remain + if currentGroupSize == sc.damagedGroupCounts[doneGroups] { + possibilities = memo(sc, index+1, doneGroups+1, 0, memoOfPossibilities) + } else if currentGroupSize != sc.damagedGroupCounts[doneGroups] { + // last group is the wrong size, zero possibilities for this branch + possibilities = 0 + } + } + } + + } else if sc.record[index] == "#" { + // build group + currentGroupSize++ + // if current group size is too big, this branch has zero possibilities + if currentGroupSize > sc.damagedGroupCounts[doneGroups] { + possibilities = 0 + } else { + possibilities = memo(sc, index+1, doneGroups, currentGroupSize, memoOfPossibilities) + } + + } else if sc.record[index] == "?" { + // ? + // add two possibilities: a damaged spring or OK spring + + // if it is a # + // do not need to account for if the group is too big here, it'll be handled by a future "#" + // check or a ".", again part of the reason why a trailing period was added + possibilities += memo(sc, index+1, doneGroups, currentGroupSize+1, memoOfPossibilities) + // currentGroupSize-- + + // take as . + // same code as above for if "." block, but possibilities is added to instead of just set + if index == 0 { + possibilities += memo(sc, index+1, 0, 0, memoOfPossibilities) + } else if currentGroupSize == 0 { + possibilities += memo(sc, index+1, doneGroups, currentGroupSize, memoOfPossibilities) + } else { + if doneGroups == len(sc.damagedGroupCounts) { + possibilities += 0 + } else { + if currentGroupSize == sc.damagedGroupCounts[doneGroups] { + possibilities += memo(sc, index+1, doneGroups+1, 0, memoOfPossibilities) + } else if currentGroupSize != sc.damagedGroupCounts[doneGroups] { + possibilities += 0 + } + } + } + + } else { + panic("unexpected string condition record character: " + sc.record[index]) + } + + memoOfPossibilities[key] = possibilities + return possibilities +} + +type springCondition struct { + record []string + damagedGroupCounts []int +} + +func parseInput(input string) (ans []springCondition) { + for _, line := range strings.Split(input, "\n") { + parts := strings.Split(line, " ") + sc := springCondition{ + record: strings.Split(parts[0], ""), + damagedGroupCounts: []int{}, + } + for _, str := range strings.Split(parts[1], ",") { + sc.damagedGroupCounts = append(sc.damagedGroupCounts, cast.ToInt(str)) + } + ans = append(ans, sc) + } + + return ans +} diff --git a/2023/day12/main_test.go b/2023/day12/main_test.go new file mode 100644 index 0000000..09bf078 --- /dev/null +++ b/2023/day12/main_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "testing" +) + +var example = `???.### 1,1,3 +.??..??...?##. 1,1,3 +?#?#?#?#?#?#?#? 1,3,1,6 +????.#...#... 4,1,1 +????.######..#####. 1,6,5 +?###???????? 3,2,1` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 21, + }, + { + name: "actual", + input: input, + want: 7792, + }, + } + 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: 525152, + }, + { + name: "actual", + input: input, + want: 13012052341533, + }, + } + 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) + } + }) + } +} From 4a076beb54a13c38e7429c8722ebb81413fa2544 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 2 Aug 2024 11:24:45 -0400 Subject: [PATCH 24/57] day 13 tricky edge cases with reflections --- 2023/day13/main.go | 165 ++++++++++++++++++++++++++++++++++++++++ 2023/day13/main_test.go | 73 ++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 2023/day13/main.go create mode 100644 2023/day13/main_test.go diff --git a/2023/day13/main.go b/2023/day13/main.go new file mode 100644 index 0000000..ec94bc4 --- /dev/null +++ b/2023/day13/main.go @@ -0,0 +1,165 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "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 { + patterns := parseInput(input) + + ans := 0 + for _, pattern := range patterns { + maybeMirrorRow := findMirrorRow(pattern, -1) + if maybeMirrorRow != -1 { + ans += 100 * maybeMirrorRow + } else { + maybeMirrorCol := findMirrorCol(pattern, -1) + if maybeMirrorCol == -1 { + panic("did not find mirror row or col") + } + ans += maybeMirrorCol + } + } + + return ans +} + +// returns the zero-index of the line, if the line is between index 3 and 4, it returns 4 +// ignoreRow is for part2 where we want to ignore the original mirrored row because it may still be +// valid in the un-smudged pattern +func findMirrorRow(pattern [][]string, ignoreRow int) int { + // combine the string slices into a string so they're easier to compare + combinedRows := []string{} + for _, row := range pattern { + combinedRows = append(combinedRows, strings.Join(row, "")) + } + + for i := 1; i < len(combinedRows); i++ { + mismatchFound := false + for offset := 1; i-offset >= 0 && i+offset-1 < len(combinedRows); offset++ { + // fmt.Println("combined row indexes", i-offset, i+offset-1) + // fmt.Println(combinedRows[i-offset], "\n", combinedRows[i+offset-1]) + if combinedRows[i-offset] != combinedRows[i+offset-1] { + mismatchFound = true + // fmt.Println("mismatch found") + break + } + } + + if !mismatchFound { + if i != ignoreRow { + return i + } + } + } + // none found + return -1 +} + +func findMirrorCol(pattern [][]string, ignoreCol int) int { + // rotate the grid, maintaining the indices for easier maths later + // then just pass it into the findMirrorRow func + rotatedGrid := [][]string{} + for c := 0; c < len(pattern[0]); c++ { + newRow := []string{} + for r := 0; r < len(pattern); r++ { + newRow = append(newRow, pattern[r][c]) + } + rotatedGrid = append(rotatedGrid, newRow) + } + + return findMirrorRow(rotatedGrid, ignoreCol) +} + +func part2(input string) int { + patterns := parseInput(input) + + ans := 0 + + for _, pattern := range patterns { + + // store the original row and col so they can be ignored in the find mirror row func + originalMirrorRow := findMirrorRow(pattern, -1) + originalMirrorCol := findMirrorCol(pattern, -1) + + traverse: + // labels suck but without the breaks this all has to go into a separate function which is + // arguably less readable. and the break is necessary to not double count reflections + for r, row := range pattern { + for c, val := range row { + if val == "." { + pattern[r][c] = "#" + if maybeMirrorRow := findMirrorRow(pattern, originalMirrorRow); maybeMirrorRow != -1 { + ans += 100 * maybeMirrorRow + break traverse + } + if maybeMirrorCol := findMirrorCol(pattern, originalMirrorCol); maybeMirrorCol != -1 { + ans += maybeMirrorCol + break traverse + } + pattern[r][c] = "." + } else if val == "#" { + pattern[r][c] = "." + if maybeMirrorRow := findMirrorRow(pattern, originalMirrorRow); maybeMirrorRow != -1 { + ans += 100 * maybeMirrorRow + break traverse + } + if maybeMirrorCol := findMirrorCol(pattern, originalMirrorCol); maybeMirrorCol != -1 { + ans += maybeMirrorCol + break traverse + } + pattern[r][c] = "#" + + } else { + panic("expected input: " + val) + } + } + } + } + + return ans +} + +func parseInput(input string) (ans [][][]string) { + for _, section := range strings.Split(input, "\n\n") { + grid := [][]string{} + for _, line := range strings.Split(section, "\n") { + grid = append(grid, strings.Split(line, "")) + } + ans = append(ans, grid) + } + return ans +} diff --git a/2023/day13/main_test.go b/2023/day13/main_test.go new file mode 100644 index 0000000..83c6e0f --- /dev/null +++ b/2023/day13/main_test.go @@ -0,0 +1,73 @@ +package main + +import ( + "testing" +) + +var example = `#.##..##. +..#.##.#. +##......# +##......# +..#.##.#. +..##..##. +#.#.##.#. + +#...##..# +#....#..# +..##..### +#####.##. +#####.##. +..##..### +#....#..#` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 405, + }, + { + name: "actual", + input: input, + want: 30575, + }, + } + 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: 400, + }, + { + name: "actual", + input: input, + want: 37478, + }, + } + 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) + } + }) + } +} From 1463a86b6079256e09932d4cd9c9a6683ece60e8 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Sat, 3 Aug 2024 12:47:41 -0400 Subject: [PATCH 25/57] update go version to 1.22 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e564503..9cbb6fe 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/alexchao26/advent-of-code-go -go 1.16 +go 1.22 require golang.org/x/net v0.0.0-20201110031124-69a78807bb2b From 4170803ffe4aef77faf432ac425a34a331c77e7e Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 5 Aug 2024 12:47:41 -0400 Subject: [PATCH 26/57] 2024-day14, using new go range over ints --- 2023/day14/main.go | 185 ++++++++++++++++++++++++++++++++++++++++ 2023/day14/main_test.go | 68 +++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 2023/day14/main.go create mode 100644 2023/day14/main_test.go diff --git a/2023/day14/main.go b/2023/day14/main.go new file mode 100644 index 0000000..f07e2a7 --- /dev/null +++ b/2023/day14/main.go @@ -0,0 +1,185 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "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 { + grid := parseInput(input) + + // tilt north, all O's roll to the top or to the next # + tiltNorth(grid) + + // then calculate total load (total rows) - (row index) per rock + ans := 0 + for r, row := range grid { + for _, val := range row { + if val == "O" { + ans += len(grid) - r + } + } + } + + return ans +} + +func part2(input string) int { + grid := parseInput(input) + + seenStates := map[string]int{} + + cycles := 1000000000 + for c := 0; c < cycles; c++ { + key := stringifyStringGrid(grid) + if lastIndex, ok := seenStates[key]; ok { + cyclePeriod := c - lastIndex + for c+cyclePeriod < cycles { + c += cyclePeriod + } + } + seenStates[key] = c + + // 1 cycle = tilt N, W, S, E + tiltNorth(grid) + tiltWest(grid) + tiltSouth(grid) + tiltEast(grid) + } + + ans := 0 + for r, row := range grid { + for _, val := range row { + if val == "O" { + ans += len(grid) - r + } + } + } + + // 99841 too low + return ans +} + +func tiltNorth(grid [][]string) { + for r, row := range grid { + for c, val := range row { + if val == "O" { + for nextRow := r - 1; nextRow >= 0; nextRow-- { + // can only fall north if nextRow is an empty space + if grid[nextRow][c] == "." { + grid[nextRow][c] = "O" + grid[nextRow+1][c] = "." + } else { + break + } + } + } + } + } +} + +func tiltSouth(grid [][]string) { + for r := len(grid) - 1; r >= 0; r-- { + for c := range len(grid[0]) { + val := grid[r][c] + if val == "O" { + for nextRow := r + 1; nextRow < len(grid); nextRow++ { + // can only fall north if nextRow is an empty space + if grid[nextRow][c] == "." { + grid[nextRow][c] = "O" + grid[nextRow-1][c] = "." + } else { + break + } + } + } + } + } +} + +func tiltEast(grid [][]string) { + for c := len(grid[0]) - 1; c >= 0; c-- { + for r := range grid { + val := grid[r][c] + + if val == "O" { + for nextCol := c + 1; nextCol < len(grid[0]); nextCol++ { + // can only fall north if nextCol is an empty space + if grid[r][nextCol] == "." { + grid[r][nextCol] = "O" + grid[r][nextCol-1] = "." + } else { + break + } + } + } + } + } +} + +func tiltWest(grid [][]string) { + for c := range len(grid[0]) { + for r := range grid { + val := grid[r][c] + + if val == "O" { + for nextCol := c - 1; nextCol >= 0; nextCol-- { + // can only fall north if nextCol is an empty space + if grid[r][nextCol] == "." { + grid[r][nextCol] = "O" + grid[r][nextCol+1] = "." + } else { + break + } + } + } + } + } +} + +func stringifyStringGrid(grid [][]string) string { + ans := "" + for _, row := range grid { + ans += strings.Join(row, "") + } + return ans +} + +func parseInput(input string) (ans [][]string) { + for _, line := range strings.Split(input, "\n") { + ans = append(ans, strings.Split(line, "")) + } + return ans +} diff --git a/2023/day14/main_test.go b/2023/day14/main_test.go new file mode 100644 index 0000000..5c4bb73 --- /dev/null +++ b/2023/day14/main_test.go @@ -0,0 +1,68 @@ +package main + +import ( + "testing" +) + +var example = `O....#.... +O.OO#....# +.....##... +OO.#O....O +.O.....O#. +O.#..O.#.# +..O..#O..O +.......O.. +#....###.. +#OO..#....` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 136, + }, + { + name: "actual", + input: input, + want: 108840, + }, + } + 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: 64, + }, + { + name: "actual", + input: input, + want: 103445, + }, + } + 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) + } + }) + } +} From fcc19264341d919edc3f27209159f4907af9d6f9 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Tue, 6 Aug 2024 14:30:01 -0400 Subject: [PATCH 27/57] 2024 day15 --- 2023/day15/main.go | 132 ++++++++++++++++++++++++++++++++++++++++ 2023/day15/main_test.go | 59 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 2023/day15/main.go create mode 100644 2023/day15/main_test.go diff --git a/2023/day15/main.go b/2023/day15/main.go new file mode 100644 index 0000000..a507969 --- /dev/null +++ b/2023/day15/main.go @@ -0,0 +1,132 @@ +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) + ans := 0 + for _, step := range parsed { + ans += hash(step) + } + + return ans +} + +func hash(step string) int { + ans := 0 + for _, char := range strings.Split(step, "") { + asciiVal := cast.ToASCIICode(char) + ans += asciiVal + ans *= 17 + ans %= 256 + } + return ans +} + +func part2(input string) int { + boxes := make([]box, 256) + // optimization to keep a linked list within in box, but likely not necessary... + labelToBoxIndex := map[string]int{} + + steps := parseInput(input) + + for _, step := range steps { + if strings.Contains(step, "=") { + parts := strings.Split(step, "=") + label := parts[0] + focalLength := cast.ToInt(parts[1]) + + boxIndex := hash(label) + + if oldBoxIndex, ok := labelToBoxIndex[label]; ok { + if oldBoxIndex != boxIndex { + panic("hashes should be the same...") + } + // iterate and update focalLength of found box + for i := range len(boxes[boxIndex]) { + if boxes[boxIndex][i].label == label { + boxes[boxIndex][i].focalLength = focalLength + } + } + } else { + boxes[boxIndex] = append(boxes[boxIndex], lense{ + label: label, + focalLength: focalLength, + }) + labelToBoxIndex[label] = boxIndex + } + + } else if strings.Contains(step, "-") { + label := step[:len(step)-1] + if boxIndex, ok := labelToBoxIndex[label]; ok { + // switch it all the way to the end + for i := range len(boxes[boxIndex]) - 1 { + if boxes[boxIndex][i].label == label { + boxes[boxIndex][i], boxes[boxIndex][i+1] = boxes[boxIndex][i+1], boxes[boxIndex][i] + } + } + // cut off end, remove from map + boxes[boxIndex] = boxes[boxIndex][:len(boxes[boxIndex])-1] + delete(labelToBoxIndex, label) + } + } else { + panic("unexpected step format: " + step) + } + } + + ans := 0 + + for boxIndex, box := range boxes { + for lenseIndex, lense := range box { + ans += (boxIndex + 1) * (lenseIndex + 1) * lense.focalLength + } + } + + return ans +} + +type lense struct { + label string + focalLength int +} +type box []lense + +func parseInput(input string) (ans []string) { + return strings.Split(input, ",") +} diff --git a/2023/day15/main_test.go b/2023/day15/main_test.go new file mode 100644 index 0000000..7638bc4 --- /dev/null +++ b/2023/day15/main_test.go @@ -0,0 +1,59 @@ +package main + +import ( + "testing" +) + +var example = `rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 1320, + }, + { + name: "actual", + input: input, + want: 507666, + }, + } + 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: 145, + }, + { + name: "actual", + input: input, + want: 233537, + }, + } + 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) + } + }) + } +} From db1170128b6ffcca101c94110c09bbec0f9dde51 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Wed, 7 Aug 2024 11:05:11 -0400 Subject: [PATCH 28/57] 2024 day16 - light and mirrors are confusing --- 2023/day16/main.go | 236 ++++++++++++++++++++++++++++++++++++++++ 2023/day16/main_test.go | 68 ++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 2023/day16/main.go create mode 100644 2023/day16/main_test.go diff --git a/2023/day16/main.go b/2023/day16/main.go new file mode 100644 index 0000000..2fd0f85 --- /dev/null +++ b/2023/day16/main.go @@ -0,0 +1,236 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "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 { + grid := parseInput(input) + + return calcEnergizedTiles(grid, beamHead{ + velocity: right, + row: 0, + col: 0, + }) +} + +func part2(input string) int { + grid := parseInput(input) + best := 0 + for r := range len(grid) { + // starting from left of grid, headed right + startBeam := beamHead{ + velocity: right, + row: r, + col: 0, + } + // max is a go 1.21 addition: https://pkg.go.dev/builtin#max + best = max(best, calcEnergizedTiles(grid, startBeam)) + + // starting from right of grid, headed left + startBeam = beamHead{ + velocity: left, + row: r, + col: len(grid[0]) - 1, + } + best = max(best, calcEnergizedTiles(grid, startBeam)) + } + + for c := range len(grid[0]) { + // starting from top of grid, headed down + startBeam := beamHead{ + velocity: down, + row: 0, + col: c, + } + best = max(best, calcEnergizedTiles(grid, startBeam)) + + // starting from bottom of grid, headed up + startBeam = beamHead{ + velocity: up, + row: len(grid) - 1, + col: c, + } + best = max(best, calcEnergizedTiles(grid, startBeam)) + } + + return best +} + +func calcEnergizedTiles(grid [][]string, beam beamHead) int { + // need to track multiple beams because they can split and generate multiple + beams := []beamHead{beam} + + // [4]bool represents being hit from left, right, up and down respectively + // need to track direction so that we can terminate beams that are cyclical + hitGrid := [][][4]bool{} + for range grid { + hitGrid = append(hitGrid, make([][4]bool, len(grid[0]))) + } + + for len(beams) > 0 { + b := beams[0] + beams = beams[1:] + + skip := false + for vel, hitGridIndex := range velToHitGridIndex { + if b.velocity == vel && hitGrid[b.row][b.col][hitGridIndex] { + skip = true + } + } + if skip { + continue + } + + // record direction hit in hit grid + hitGrid[b.row][b.col][velToHitGridIndex[b.velocity]] = true + + cell := grid[b.row][b.col] + nextVelocities, ok := mirrorToNextVelocities[cell][b.velocity] + if !ok { + panic("no nextVelocities found for cell type: " + cell) + } + for _, nextVelocity := range nextVelocities { + nextRow := b.row + nextVelocity[0] + nextCol := b.col + nextVelocity[1] + + if nextRow < 0 || nextRow >= len(grid) || + nextCol < 0 || nextCol >= len(grid[0]) { + continue + } + + beams = append(beams, beamHead{ + velocity: nextVelocity, + row: nextRow, + col: nextCol, + }) + } + } + + energizedTiles := 0 + for r := range len(hitGrid) { + for c := range len(hitGrid[0]) { + for _, dir := range hitGrid[r][c] { + if dir { + energizedTiles++ + break + } + } + } + } + return energizedTiles +} + +var left = [2]int{0, -1} +var right = [2]int{0, 1} +var up = [2]int{-1, 0} +var down = [2]int{1, 0} + +var velToHitGridIndex = map[[2]int]int{ + left: 0, + right: 1, + up: 2, + down: 3, +} + +var mirrorToNextVelocities = map[string]map[[2]int][][2]int{ + ".": { + left: {left}, + right: {right}, + up: {up}, + down: {down}, + }, + "/": { + left: {down}, + down: {left}, + up: {right}, + right: {up}, + }, + "\\": { + left: {up}, + up: {left}, + down: {right}, + right: {down}, + }, + "|": { + left: {up, down}, + right: {up, down}, + up: {up}, + down: {down}, + }, + "-": { + left: {left}, + right: {right}, + up: {left, right}, + down: {left, right}, + }, +} + +type beamHead struct { + velocity [2]int + row, col int +} + +func (b beamHead) String() string { + return fmt.Sprintf("vel: %v, coords: %d, %d", b.velocity, b.row, b.col) +} + +// for debugging +func condenseHitGrid(grid [][][4]bool) [][]int { + ans := [][]int{} + for range grid { + ans = append(ans, make([]int, len(grid[0]))) + } + + for r, row := range grid { + for c, sli := range row { + for _, val := range sli { + if val { + ans[r][c]++ + } + } + } + } + + return ans +} + +func parseInput(input string) (ans [][]string) { + for _, line := range strings.Split(input, "\n") { + ans = append(ans, strings.Split(line, "")) + } + return ans +} diff --git a/2023/day16/main_test.go b/2023/day16/main_test.go new file mode 100644 index 0000000..d4fab22 --- /dev/null +++ b/2023/day16/main_test.go @@ -0,0 +1,68 @@ +package main + +import ( + "testing" +) + +var example = `.|...\.... +|.-.\..... +.....|-... +........|. +.......... +.........\ +..../.\\.. +.-.-/..|.. +.|....-|.\ +..//.|....` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 46, + }, + { + name: "actual", + input: input, + want: 7046, + }, + } + 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: 51, + }, + { + name: "actual", + input: input, + want: 7313, + }, + } + 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) + } + }) + } +} From 2d183885e68ae2a9fa9fe139862c0dc224696232 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 9 Aug 2024 11:05:11 -0400 Subject: [PATCH 29/57] 2024 day17 - oof clusmy cart was tough --- 2023/day17/main.go | 153 ++++++++++++++++++++++++++++++++++++++++ 2023/day17/main_test.go | 65 +++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 2023/day17/main.go create mode 100644 2023/day17/main_test.go diff --git a/2023/day17/main.go b/2023/day17/main.go new file mode 100644 index 0000000..4a141c5 --- /dev/null +++ b/2023/day17/main.go @@ -0,0 +1,153 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/data-structures/heap" + "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 := clumsyCart(input, 1, 3) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } else { + ans := clumsyCart(input, 4, 10) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } +} + +func clumsyCart(input string, minMoves, maxMoves int) int { + grid := parseInput(input) + + minHeap := heap.NewMinHeap() + minHeap.Add(bfsNode{ + heatLoss: 0, + row: 0, + col: 0, + lastDir: right, + debugPath: "", + }) + minHeap.Add(bfsNode{ + heatLoss: 0, + row: 0, + col: 0, + lastDir: down, + debugPath: "", + }) + + // store lowest heat loss for each coordinate for each direction, slightly suboptimal + // because it can be divided into vertical and horizontal + // instead of L, R, U, D. But it's a constant time optimization but it runs fast enough + cache := map[string]int{} + + for minHeap.Length() > 0 { + node := minHeap.Remove().(bfsNode) + + key := fmt.Sprintf("%d %d - %v", node.row, node.col, node.lastDir) + if val, ok := cache[key]; ok { + if node.heatLoss >= val { + // exit if the current heatLoss isn't better + continue + } else { + cache[key] = node.heatLoss + } + } else { + cache[key] = node.heatLoss + } + + if node.row == len(grid)-1 && node.col == len(grid[0])-1 { + return node.heatLoss + } + + // just add a node for each vertical direction, then those will move vertically as well + // which covers all possibilities + for _, nextDir := range verticalTurns[node.lastDir] { + summedHeatLoss := 0 + for i := 1; i <= maxMoves; i++ { + nextRow := node.row + nextDir[0]*i + nextCol := node.col + nextDir[1]*i + + // skip if out of range + if nextRow < 0 || nextRow >= len(grid) || nextCol < 0 || nextCol >= len(grid[0]) { + continue + } + + summedHeatLoss += grid[nextRow][nextCol] + + // do not add to heap if the cart has moved less than the minimum required moves (part 2) + if i < minMoves { + continue + } + + minHeap.Add(bfsNode{ + heatLoss: node.heatLoss + summedHeatLoss, + row: nextRow, + col: nextCol, + lastDir: nextDir, + debugPath: node.debugPath + fmt.Sprintf("%d,%d ", nextRow, nextCol), + }) + } + } + } + + panic("should return from heap processing") +} + +type bfsNode struct { + heatLoss int + row, col int + lastDir direction + debugPath string +} + +func (b bfsNode) Value() int { + return b.heatLoss +} + +type direction [2]int + +var up = direction{-1, 0} +var down = direction{1, 0} +var left = direction{0, -1} +var right = direction{0, 1} + +var verticalTurns = map[direction][2]direction{ + up: {left, right}, + down: {left, right}, + left: {up, down}, + right: {up, down}, +} + +func parseInput(input string) (ans [][]int) { + for _, line := range strings.Split(input, "\n") { + row := []int{} + for _, str := range strings.Split(line, "") { + row = append(row, cast.ToInt(str)) + } + ans = append(ans, row) + } + return ans +} diff --git a/2023/day17/main_test.go b/2023/day17/main_test.go new file mode 100644 index 0000000..b418f99 --- /dev/null +++ b/2023/day17/main_test.go @@ -0,0 +1,65 @@ +package main + +import ( + "testing" +) + +var example = `2413432311323 +3215453535623 +3255245654254 +3446585845452 +4546657867536 +1438598798454 +4457876987766 +3637877979653 +4654967986887 +4564679986453 +1224686865563 +2546548887735 +4322674655533` + +func Test_clumsyCart(t *testing.T) { + tests := []struct { + name string + input string + minMoves int + maxMoves int + want int + }{ + { + name: "example", + input: example, + minMoves: 1, + maxMoves: 3, + want: 102, + }, + { + name: "actual", + input: input, + minMoves: 1, + maxMoves: 3, + want: 1001, + }, + { + name: "example_part2", + input: example, + minMoves: 4, + maxMoves: 10, + want: 94, + }, + { + name: "actual", + input: input, + minMoves: 1, + maxMoves: 3, + want: 1197, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := clumsyCart(tt.input, tt.minMoves, tt.maxMoves); got != tt.want { + t.Errorf("clumsyCart() = %v, want %v", got, tt.want) + } + }) + } +} From c06369e6ff45d2b8b51b36e862b0d5bcd288c648 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 12 Aug 2024 17:04:56 -0400 Subject: [PATCH 30/57] 2023-19 not the prettiest recursion... --- 2023/day19/main.go | 240 ++++++++++++++++++++++++++++++++++++++++ 2023/day19/main_test.go | 75 +++++++++++++ 2 files changed, 315 insertions(+) create mode 100644 2023/day19/main.go create mode 100644 2023/day19/main_test.go diff --git a/2023/day19/main.go b/2023/day19/main.go new file mode 100644 index 0000000..cec099d --- /dev/null +++ b/2023/day19/main.go @@ -0,0 +1,240 @@ +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 { + parts, workflowsMap := parseInput(input) + + ans := 0 + + for _, p := range parts { + currentWorkflowName := "in" + for currentWorkflowName != "A" && currentWorkflowName != "R" { + wf := workflowsMap[currentWorkflowName] + for _, rule := range wf.ruleStrings { + if !strings.Contains(rule, ":") { + currentWorkflowName = rule + break + } + + colonSplit := strings.Split(rule, ":") + output := colonSplit[1] + + if strings.Contains(colonSplit[0], "<") { + conditionalParts := strings.Split(colonSplit[0], "<") + if p[conditionalParts[0]] < cast.ToInt(conditionalParts[1]) { + currentWorkflowName = output + break + } + } else if strings.Contains(colonSplit[0], ">") { + conditionalParts := strings.Split(colonSplit[0], ">") + if p[conditionalParts[0]] > cast.ToInt(conditionalParts[1]) { + currentWorkflowName = output + break + } + } else { + panic("unexpected workflow rule conditional: " + rule) + } + } + } + + if currentWorkflowName == "A" { + ans += p.sumRatings() + } + } + + return ans +} + +type part map[string]int + +func (p part) sumRatings() int { + total := 0 + for _, v := range p { + total += v + } + return total +} + +type workflow struct { + name string + ruleStrings []string +} + +func parseInput(input string) (parts []part, workflowsMap map[string]workflow) { + inputParts := strings.Split(input, "\n\n") + + workflowsMap = map[string]workflow{} + + // process workflows + for _, line := range strings.Split(inputParts[0], "\n") { + lineParts := strings.Split(line, "{") + wf := workflow{ + name: lineParts[0], + ruleStrings: strings.Split(lineParts[1][:len(lineParts[1])-1], ","), + } + workflowsMap[wf.name] = wf + } + + for _, line := range strings.Split(inputParts[1], "\n") { + withoutBraces := line[1 : len(line)-1] + p := part{} + for _, ratingStr := range strings.Split(withoutBraces, ",") { + ratingParts := strings.Split(ratingStr, "=") + p[ratingParts[0]] = cast.ToInt(ratingParts[1]) + } + parts = append(parts, p) + } + + return parts, workflowsMap +} + +func part2(input string) int { + _, workflowsMap := parseInput(input) + + // 1 to 4000 bounds for each rating... + boundedParts := map[string][2]int{ + "x": {1, 4000}, + "m": {1, 4000}, + "a": {1, 4000}, + "s": {1, 4000}, + } + + return updatePartBoundsAndSplit(boundedParts, workflowsMap, "in", 0) +} + +func updatePartBoundsAndSplit(boundedParts map[string][2]int, workflowsMap map[string]workflow, currentWorkflow string, debugDepth int) int { + if currentWorkflow == "R" { + return 0 + } + if currentWorkflow == "A" { + product := 1 + for _, bounds := range boundedParts { + product *= bounds[1] - bounds[0] + 1 + } + return product + } + + // split based on rules... + total := 0 + + // for each rule, + // the rule either passes and moves onto a different workflow, + // or fails and checks the next rule + // need to sum up both forks + // + // passing is handled via recursion, failing is handled via looping to the next rule + // in both cases the bounds need to be updated + for _, rule := range workflowsMap[currentWorkflow].ruleStrings { + // just the next workflow to go after + if !strings.Contains(rule, ":") { + nextWorkflowName := rule + total += updatePartBoundsAndSplit(boundedParts, workflowsMap, nextWorkflowName, debugDepth+1) + break + } + + colonSplit := strings.Split(rule, ":") + nextWorkflowName := colonSplit[1] + + if strings.Contains(colonSplit[0], "<") { + conditionalParts := strings.Split(colonSplit[0], "<") + ratingName := conditionalParts[0] + ratingTestValue := cast.ToInt(conditionalParts[1]) + + // fork the part that passes the < conditional + copyOfBounds := copyBoundedPartsMap(boundedParts) + copyOfBounds[ratingName] = [2]int{ + copyOfBounds[ratingName][0], + ratingTestValue - 1, + } + // check that the new bounds are still valid + if copyOfBounds[ratingName][0] <= copyOfBounds[ratingName][1] { + total += updatePartBoundsAndSplit(copyOfBounds, workflowsMap, nextWorkflowName, debugDepth+1) + } + + // second fork for failing the conditional, need to update the boundedParts to fail + boundedParts[ratingName] = [2]int{ + ratingTestValue, + boundedParts[ratingName][1], + } + // check that the new bounds are still valid + if boundedParts[ratingName][0] > boundedParts[ratingName][1] { + break + } + } else if strings.Contains(colonSplit[0], ">") { + conditionalParts := strings.Split(colonSplit[0], ">") + ratingName := conditionalParts[0] + ratingTestValue := cast.ToInt(conditionalParts[1]) + + // fork the part that passes the > conditional + copyOfBounds := copyBoundedPartsMap(boundedParts) + copyOfBounds[ratingName] = [2]int{ + ratingTestValue + 1, + copyOfBounds[ratingName][1], + } + + // check that the new bounds are still valid before recursing + if copyOfBounds[ratingName][0] <= copyOfBounds[ratingName][1] { + total += updatePartBoundsAndSplit(copyOfBounds, workflowsMap, nextWorkflowName, debugDepth+1) + } + + // second fork for failing the conditional, need to update the boundedParts to fail + boundedParts[ratingName] = [2]int{ + boundedParts[ratingName][0], + ratingTestValue, + } + // check that the new bounds are still valid + if boundedParts[ratingName][0] > boundedParts[ratingName][1] { + break + } + } else { + panic("unexpected workflow rule conditional: " + rule) + } + } + + return total +} + +func copyBoundedPartsMap(boundedParts map[string][2]int) map[string][2]int { + cp := map[string][2]int{} + for k, v := range boundedParts { + cp[k] = v + } + return cp +} diff --git a/2023/day19/main_test.go b/2023/day19/main_test.go new file mode 100644 index 0000000..435ae6a --- /dev/null +++ b/2023/day19/main_test.go @@ -0,0 +1,75 @@ +package main + +import ( + "testing" +) + +var example = `px{a<2006:qkq,m>2090:A,rfg} +pv{a>1716:R,A} +lnx{m>1548:A,A} +rfg{s<537:gd,x>2440:R,A} +qs{s>3448:A,lnx} +qkq{x<1416:A,crn} +crn{x>2662:A,R} +in{s<1351:px,qqz} +qqz{s>2770:qs,m<1801:hdj,R} +gd{a>3333:R,R} +hdj{m>838:A,pv} + +{x=787,m=2655,a=1222,s=2876} +{x=1679,m=44,a=2067,s=496} +{x=2036,m=264,a=79,s=2244} +{x=2461,m=1339,a=466,s=291} +{x=2127,m=1623,a=2188,s=1013}` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 19114, + }, + { + name: "actual", + input: input, + want: 287054, + }, + } + 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: 167409079868000, + }, + { + name: "actual", + input: input, + want: 131619440296497, + }, + } + 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) + } + }) + } +} From c7f2581dc7e02c5fd43c7d70e3673a1fe5873912 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Tue, 13 Aug 2024 13:53:28 -0400 Subject: [PATCH 31/57] 2023-20 phew part 2 was scary at first --- 2023/day20/main.go | 215 ++++++++++++++++++++++++++++++++++++++++ 2023/day20/main_test.go | 58 +++++++++++ 2 files changed, 273 insertions(+) create mode 100644 2023/day20/main.go create mode 100644 2023/day20/main_test.go diff --git a/2023/day20/main.go b/2023/day20/main.go new file mode 100644 index 0000000..fd88cc1 --- /dev/null +++ b/2023/day20/main.go @@ -0,0 +1,215 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "math" + "strings" + + "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) + + ans := pulsePropagation(input, part) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) +} + +func pulsePropagation(input string, part int) int { + modules := parseInput(input) + + var lowPulses, highPulses int + + buttonPresses := 1000 + if part == 2 { + // let this cycle infinitely so I can figure out cycle times for part 2 + buttonPresses = math.MaxInt64 + } + + // for part 2: + // looking at input, rx's only input is &lb, which is a conjunction, so needs to get ALL high signals to send a low to rx + // lb is fed from four other modules that all need to send high signals: + // &rz, &lf, &br, &fk + // figuring out the cycle times of these four then maybe the LCM will be the answer if the input is kind? + + lastCycleForHighPulse := map[string]int{ + "rz": -1, + "lf": -1, + "br": -1, + "fk": -1, + } + + cycles := []int{} + + for i := 0; i < buttonPresses; i++ { + if part == 2 && len(cycles) == 4 { + break + } + + queue := []pulse{} + queue = append(queue, pulse{ + isLowPulse: true, + source: "button", + destination: "broadcaster", + }) + + for len(queue) > 0 { + p := queue[0] + queue = queue[1:] + + if p.isLowPulse { + lowPulses++ + } else { + highPulses++ + } + + if val, ok := lastCycleForHighPulse[p.source]; ok && !p.isLowPulse { + // fmt.Println("found for ", p.source, i+1) + if val == -1 { + lastCycleForHighPulse[p.source] = i + 1 + } else { + cycles = append(cycles, (i+1)-val) + } + } + + if _, ok := modules[p.destination]; !ok { + continue + } + + switch modules[p.destination].moduleType { + case "broadcaster": + for _, dest := range modules[p.destination].destinations { + queue = append(queue, pulse{ + isLowPulse: p.isLowPulse, + source: "broadcaster", + destination: dest, + }) + } + case "flipflop": + if p.isLowPulse { + for _, dest := range modules[p.destination].destinations { + queue = append(queue, pulse{ + // if it was on, it flips off and sends a low pulse + // if it was off, then sends a high pulse (isLowPulse = false) + isLowPulse: modules[p.destination].flipFlopIsOn, + source: p.destination, + destination: dest, + }) + } + // flip it + modules[p.destination].flipFlopIsOn = !modules[p.destination].flipFlopIsOn + } + case "conjunction": + modules[p.destination].conjunctionInputsMapWasLastPulseHigh[p.source] = !p.isLowPulse + allHigh := true + for source, wasStrongPulse := range modules[p.destination].conjunctionInputsMapWasLastPulseHigh { + _ = source + if !wasStrongPulse { + allHigh = false + break + } + } + + for _, dest := range modules[p.destination].destinations { + queue = append(queue, pulse{ + // all high sends a low pulse, otherwise high pulse + isLowPulse: allHigh, + source: p.destination, + destination: dest, + }) + } + default: + panic("unexpected module type" + modules[p.destination].moduleType) + } + } + } + + // wow that worked, super generous on the inputs... + if part == 2 { + ans := 1 + for _, c := range cycles { + ans *= c + } + return ans + } + + return lowPulses * highPulses +} + +type module struct { + moduleType string + name string + flipFlopIsOn bool + conjunctionInputsMapWasLastPulseHigh map[string]bool + destinations []string +} + +type pulse struct { + isLowPulse bool + source, destination string +} + +func parseInput(input string) (ans map[string]*module) { + ans = map[string]*module{} + + for _, line := range strings.Split(input, "\n") { + parts := strings.Split(line, " -> ") + + mod := module{ + moduleType: "", + flipFlopIsOn: false, + conjunctionInputsMapWasLastPulseHigh: map[string]bool{}, + destinations: []string{}, + } + + if parts[0] == "broadcaster" { + mod.moduleType = "broadcaster" + mod.name = "broadcaster" + mod.destinations = strings.Split(parts[1], ", ") + } else if parts[0][:1] == "%" { + mod.moduleType = "flipflop" + mod.name = parts[0][1:] + mod.destinations = strings.Split(parts[1], ", ") + } else if parts[0][:1] == "&" { + mod.moduleType = "conjunction" + mod.name = parts[0][1:] + mod.destinations = strings.Split(parts[1], ", ") + } else { + panic("unidentified module type: " + line) + } + + ans[mod.name] = &mod + } + + // initialize conjunction maps with all their source modules + for name, module := range ans { + for _, dest := range module.destinations { + + if _, ok := ans[dest]; !ok { + continue + } + if ans[dest].moduleType == "conjunction" { + ans[dest].conjunctionInputsMapWasLastPulseHigh[name] = false + } + } + } + + return ans +} diff --git a/2023/day20/main_test.go b/2023/day20/main_test.go new file mode 100644 index 0000000..774fc06 --- /dev/null +++ b/2023/day20/main_test.go @@ -0,0 +1,58 @@ +package main + +import ( + "testing" +) + +var example = `broadcaster -> a, b, c +%a -> b +%b -> c +%c -> inv +&inv -> a` + +var example2 = `broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output` + +func Test_pulsePropagation(t *testing.T) { + tests := []struct { + name string + input string + part int + want int + }{ + { + name: "example", + input: example, + part: 1, + want: 32000000, + }, + { + name: "example2", + input: example2, + part: 1, + want: 11687500, + }, + { + name: "actual", + input: input, + part: 1, + want: 817896682, + }, + { + name: "actual", + input: input, + part: 2, + want: 250924073918341, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pulsePropagation(tt.input, tt.part); got != tt.want { + t.Errorf("pulsePropagation() = %v, want %v", got, tt.want) + } + }) + } +} From f2a2dd2b915bd957fe568f6bc671b2bf74910fb1 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Wed, 14 Aug 2024 15:53:28 -0400 Subject: [PATCH 32/57] 2023-21 part2 was... not fun, needed help from the megathread for sure --- 2023/day21/main.go | 171 ++++++++++++++++++++++++++++++++++++++++ 2023/day21/main_test.go | 93 ++++++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 2023/day21/main.go create mode 100644 2023/day21/main_test.go diff --git a/2023/day21/main.go b/2023/day21/main.go new file mode 100644 index 0000000..fb5ae2b --- /dev/null +++ b/2023/day21/main.go @@ -0,0 +1,171 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "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, 64) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } else { + ans := part2(input, 26501365) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } +} + +func part1(input string, steps int) int { + grid := parseInput(input) + var row, col int + for r, rowSlice := range grid { + for c, val := range rowSlice { + if val == "S" { + row = r + col = c + break + } + } + } + + queue := map[[2]int]bool{ + {row, col}: true, + } + for i := 0; i < steps; i++ { + newQueue := map[[2]int]bool{} + for coord := range queue { + for _, diff := range [][2]int{ + {-1, 0}, + {1, 0}, + {0, -1}, + {0, 1}, + } { + nextRow := coord[0] + diff[0] + nextCol := coord[1] + diff[1] + if nextRow < 0 || nextRow >= len(grid) || nextCol < 0 || nextCol >= len(grid[0]) { + continue + } + + if grid[nextRow][nextCol] == "." || grid[nextRow][nextCol] == "S" { + newQueue[[2]int{nextRow, nextCol}] = true + } + } + } + + queue = newQueue + } + + return len(queue) +} + +func part2(input string, steps int) int { + grid := parseInput(input) + var row, col int + for r, rowSlice := range grid { + for c, val := range rowSlice { + if val == "S" { + row = r + col = c + break + } + } + } + grid[row][col] = "." + + // keeps track of the flip-flopping coords separately + evenSeenCoords := map[[2]int]bool{} + oddSeenCoords := map[[2]int]bool{} + + // need a set of all coords added to the queue so that we're not re-adding the same coords + uniqueCoords := map[[2]int]bool{} + + queue := [][2]int{ + {row, col}, + } + + // results to calculate quadratic constants with + results := []int{} + + // perform two steps at once to always be on an even number of steps + for s := 0; s < steps && len(results) < 3; s++ { + activeSeenCoords := evenSeenCoords + if s%2 == 1 { + activeSeenCoords = oddSeenCoords + } + + newQueue := [][2]int{} + for _, coord := range queue { + activeSeenCoords[coord] = true + + for _, diff := range [][2]int{ + {-1, 0}, + {1, 0}, + {0, -1}, + {0, 1}, + } { + nextRow := coord[0] + diff[0] + nextCol := coord[1] + diff[1] + nextCoord := [2]int{nextRow, nextCol} + + // handles infinite grid and garden space detection + modNextRow := ((nextRow % len(grid)) + len(grid)) % +len(grid) + modNextCol := ((nextCol % len(grid[0])) + len(grid[0])) % len(grid[0]) + if grid[modNextRow][modNextCol] != "." { + continue + } + + // if already seen, skip + if uniqueCoords[nextCoord] { + continue + } + uniqueCoords[nextCoord] = true + + newQueue = append(newQueue, nextCoord) + } + } + + queue = newQueue + + if s != 0 && s%131 == 65 { + results = append(results, len(activeSeenCoords)) + } + } + + // solve quadratic for a b and c constants + a := (results[2] + results[0] - 2*results[1]) / 2 + b := results[1] - results[0] - a + c := results[0] + + n := steps / len(grid) + + return a*n*n + b*n + c +} + +func parseInput(input string) (ans [][]string) { + for _, line := range strings.Split(input, "\n") { + ans = append(ans, strings.Split(line, "")) + } + return ans +} diff --git a/2023/day21/main_test.go b/2023/day21/main_test.go new file mode 100644 index 0000000..1fd6b0c --- /dev/null +++ b/2023/day21/main_test.go @@ -0,0 +1,93 @@ +package main + +import ( + "testing" +) + +var example = `........... +.....###.#. +.###.##..#. +..#.#...#.. +....#.#.... +.##..S####. +.##..#...#. +.......##.. +.##.#.####. +.##..##.##. +...........` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + steps int + want int + }{ + { + name: "example", + input: example, + steps: 6, + want: 16, + }, + { + name: "actual", + input: input, + steps: 64, + want: 3743, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part1(tt.input, tt.steps); got != tt.want { + t.Errorf("part1() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_part2(t *testing.T) { + tests := []struct { + name string + input string + steps int + want int + }{ + // { + // name: "example-10", + // input: example, + // steps: 10, + // want: 50, + // }, + // { + // name: "example-50", + // input: example, + // steps: 50, + // want: 1594, + // }, + // { + // name: "example-100", + // input: example, + // steps: 100, + // want: 6536, + // }, + // { + // name: "example-5k", + // input: example, + // steps: 5000, + // want: 16733044, + // }, + { + name: "actual", + input: input, + steps: 26501365, + want: 618261433219147, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part2(tt.input, tt.steps); got != tt.want { + t.Errorf("part2() = %v, want %v", got, tt.want) + } + }) + } +} From 36679efb837ca2bd4277c22954734bfdddc8cb25 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Thu, 15 Aug 2024 12:53:28 -0400 Subject: [PATCH 33/57] 2023-22 straightforward grid problem but plenty of bugs to workout --- 2023/day22/main.go | 223 ++++++++++++++++++++++++++++++++++++++++ 2023/day22/main_test.go | 65 ++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 2023/day22/main.go create mode 100644 2023/day22/main_test.go diff --git a/2023/day22/main.go b/2023/day22/main.go new file mode 100644 index 0000000..58e1b77 --- /dev/null +++ b/2023/day22/main.go @@ -0,0 +1,223 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "sort" + "strings" + + "github.com/alexchao26/advent-of-code-go/algos" + "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 { + bricks := parseInput(input) + + dropBricks(bricks) + + // basically have a graph... + // if a brick supports nothing, it can be removed + // if a brick supports + removableBricks := map[int]bool{} + for _, brick := range bricks { + if len(brick.supports) == 0 { + // supports nothing + removableBricks[brick.index] = true + } else { + // check ALL supported bricks, if this is the ONLY support for those + // then this brick CANNOT be removed + hasUniqueDependency := false + for supportedBrickIndex := range brick.supports { + if len(bricks[supportedBrickIndex].supportedBy) == 1 { + hasUniqueDependency = true + } + } + if !hasUniqueDependency { + removableBricks[brick.index] = true + } + } + } + + return len(removableBricks) +} + +func dropBricks(bricks []*brick) { + // process bricks by lowest z values + sort.Slice(bricks, func(i, j int) bool { + return bricks[i].start[2] < bricks[j].start[2] + }) + + // re-index, pesky bug... + for i, brick := range bricks { + brick.index = i + } + + // all bricks are < 10 units of volume, so can store their coords in a map... + // also all bricks in the input are straight lines, only one dimension will be > 1 + occupiedCells := map[[3]int]int{} + for _, brick := range bricks { + + isBlocked := false + for brick.start[2] > 1 && !isBlocked { + for _, coord := range brick.coords { + downOne := [3]int{coord[0], coord[1], coord[2] - 1} + if index, ok := occupiedCells[downOne]; ok { + isBlocked = true + + brick.supportedBy[index] = true + bricks[index].supports[brick.index] = true + } + } + + if !isBlocked { + for i := range brick.coords { + brick.coords[i][2]-- + } + brick.start[2]-- + brick.end[2]-- + } + } + + for _, coord := range brick.coords { + occupiedCells[coord] = brick.index + } + } +} + +func part2(input string) int { + bricks := parseInput(input) + dropBricks(bricks) + + total := 0 + + // chain reaction + for i := range bricks { + bricksCopy := copyAllBricks(bricks) + startingBrick := bricksCopy[i] + + queueToRemove := []*brick{} + for in := range startingBrick.supports { + delete(bricksCopy[in].supportedBy, startingBrick.index) + queueToRemove = append(queueToRemove, bricksCopy[in]) + } + + removed := 0 + for len(queueToRemove) > 0 { + br := queueToRemove[0] + queueToRemove = queueToRemove[1:] + + if len(br.supportedBy) > 0 { + continue + } + + removed++ + + // check every brick it supports, remove self from it's supportedBy map + // then add to queue to be checked + for supportedBrickIndex := range br.supports { + delete(bricksCopy[supportedBrickIndex].supportedBy, br.index) + if len(bricksCopy[supportedBrickIndex].supportedBy) == 0 { + queueToRemove = append(queueToRemove, bricksCopy[supportedBrickIndex]) + } + } + } + + total += removed + } + + return total +} + +func copyAllBricks(bricks []*brick) []*brick { + copiedBricks := []*brick{} + for _, b := range bricks { + newBrick := &brick{ + // start: []int{}, + // end: []int{}, + index: b.index, + // coords: [][3]int{}, + supportedBy: map[int]bool{}, + supports: map[int]bool{}, + } + // need full copies of these otherwise they'll point to the same underlying maps + for k, v := range b.supportedBy { + newBrick.supportedBy[k] = v + } + for k, v := range b.supports { + newBrick.supports[k] = v + } + copiedBricks = append(copiedBricks, newBrick) + } + return copiedBricks +} + +type brick struct { + start, end []int + index int + coords [][3]int + supportedBy map[int]bool + supports map[int]bool +} + +func parseInput(input string) (ans []*brick) { + for _, line := range strings.Split(input, "\n") { + coords := [6]int{} + for i, part := range algos.SplitStringOn(line, []string{",", "~"}) { + coords[i] = cast.ToInt(part) + } + + if coords[0] > coords[3] || coords[1] > coords[4] || coords[2] > coords[5] { + panic("unordered input") + } + + allCoords := [][3]int{} + for x := coords[0]; x <= coords[3]; x++ { + for y := coords[1]; y <= coords[4]; y++ { + for z := coords[2]; z <= coords[5]; z++ { + allCoords = append(allCoords, [3]int{x, y, z}) + } + } + } + + ans = append(ans, &brick{ + start: coords[:3], + end: coords[3:], + index: len(ans), + coords: allCoords, + supportedBy: map[int]bool{}, + supports: map[int]bool{}, + }) + } + + return ans +} diff --git a/2023/day22/main_test.go b/2023/day22/main_test.go new file mode 100644 index 0000000..8afd636 --- /dev/null +++ b/2023/day22/main_test.go @@ -0,0 +1,65 @@ +package main + +import ( + "testing" +) + +var example = `1,0,1~1,2,1 +0,0,2~2,0,2 +0,2,3~2,2,3 +0,0,4~0,2,4 +2,0,5~2,2,5 +0,1,6~2,1,6 +1,1,8~1,1,9` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 5, + }, + { + name: "actual", + input: input, + want: 471, + }, + } + 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: 7, + }, + { + name: "actual", + input: input, + want: 68525, + }, + } + 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) + } + }) + } +} From f3df3658a433bc9e643359b4367947da68a43982 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 16 Aug 2024 15:53:28 -0400 Subject: [PATCH 34/57] 2023-23 dfs backtracking --- 2023/day23/main.go | 217 ++++++++++++++++++++++++++++++++++++++++ 2023/day23/main_test.go | 81 +++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 2023/day23/main.go create mode 100644 2023/day23/main_test.go diff --git a/2023/day23/main.go b/2023/day23/main.go new file mode 100644 index 0000000..584e727 --- /dev/null +++ b/2023/day23/main.go @@ -0,0 +1,217 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "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 { + grid := parseInput(input) + + // do not step on same tile twice, longest hike possible + // standard backtrack? + var startCol int + for c := 0; c < len(grid[0]); c++ { + if grid[0][c] == "." { + startCol = c + break + } + } + + return backtrackLongest(grid, 0, startCol, map[[2]int]bool{}, 0) +} + +var slopes = map[string][2]int{ + ">": {0, 1}, + "<": {0, -1}, + "v": {1, 0}, + "^": {-1, 0}, +} + +type node struct { + row, col int + weightedEdges map[*node]int +} + +func backtrackLongest(grid [][]string, row, col int, visited map[[2]int]bool, steps int) int { + if row == len(grid)-1 && grid[row][col] == "." { + return steps + } + + if diff, ok := slopes[grid[row][col]]; ok { + + nextCoord := [2]int{row + diff[0], col + diff[1]} + if visited[nextCoord] { + return 0 + } + + visited[[2]int{row, col}] = true + + result := backtrackLongest(grid, row+diff[0], col+diff[1], visited, steps+1) + + visited[[2]int{row, col}] = false + return result + } + + best := 0 + + for _, diff := range slopes { + nextRow := row + diff[0] + nextCol := col + diff[1] + + if nextRow < 0 || nextRow >= len(grid) || + nextCol < 0 || nextCol >= len(grid[0]) { + continue + } + + nextCoord := [2]int{nextRow, nextCol} + + if visited[nextCoord] { + continue + } + + if grid[nextRow][nextCol] != "#" { + visited[[2]int{row, col}] = true + + result := backtrackLongest(grid, nextRow, nextCol, visited, steps+1) + best = max(best, result) + + visited[[2]int{row, col}] = false + } + } + + return best +} + +func part2(input string) int { + grid := parseInput(input) + + var startCol int + for c := 0; c < len(grid[0]); c++ { + if grid[0][c] == "." { + startCol = c + break + } + } + _ = startCol + // reduce to a graph with weighted edges + allNodes := map[[2]int]*node{} + + // just make all nodes + for r := 0; r < len(grid); r++ { + for c := 0; c < len(grid[0]); c++ { + if grid[r][c] == "#" { + continue + } + allNodes[[2]int{r, c}] = &node{ + row: r, + col: c, + weightedEdges: map[*node]int{}, + } + + } + } + + // connect all adjacent nodes and assign a weight of 1 + for coords, node := range allNodes { + for _, diff := range slopes { + nextCoord := [2]int{ + coords[0] + diff[0], + coords[1] + diff[1], + } + + if neighbor, ok := allNodes[nextCoord]; ok { + node.weightedEdges[neighbor] = 1 + neighbor.weightedEdges[node] = 1 + } + } + } + + // reduce the graph by combining neighbors if there are exactly two + for _, currentNode := range allNodes { + if len(currentNode.weightedEdges) == 2 { + twoNeighbors := []*node{} + summedWeight := 0 + for neighborNode := range currentNode.weightedEdges { + twoNeighbors = append(twoNeighbors, neighborNode) + summedWeight += neighborNode.weightedEdges[currentNode] + } + + delete(twoNeighbors[0].weightedEdges, currentNode) + delete(twoNeighbors[1].weightedEdges, currentNode) + twoNeighbors[0].weightedEdges[twoNeighbors[1]] = summedWeight + twoNeighbors[1].weightedEdges[twoNeighbors[0]] = summedWeight + + // doesn't affect map iteration + delete(allNodes, [2]int{currentNode.row, currentNode.col}) + } + } + + // backtrack through graph again + return backtrackThroughGraph(allNodes[[2]int{0, startCol}], + map[*node]bool{}, 0, len(grid)-1) +} + +func backtrackThroughGraph(currentNode *node, seen map[*node]bool, + distance int, destinationRow int) int { + + // destination row is knowing that there is only one node that is on the + // final row, so if we reach that depth we've reached the end + if currentNode.row == destinationRow { + return distance + } + + best := 0 + seen[currentNode] = true + + for neighbor, weight := range currentNode.weightedEdges { + if seen[neighbor] { + continue + } + best = max(best, + backtrackThroughGraph(neighbor, seen, distance+weight, destinationRow)) + } + + seen[currentNode] = false + + return best +} + +func parseInput(input string) (ans [][]string) { + for _, line := range strings.Split(input, "\n") { + ans = append(ans, strings.Split(line, "")) + } + return ans +} diff --git a/2023/day23/main_test.go b/2023/day23/main_test.go new file mode 100644 index 0000000..f3524c3 --- /dev/null +++ b/2023/day23/main_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "testing" +) + +var example = `#.##################### +#.......#########...### +#######.#########.#.### +###.....#.>.>.###.#.### +###v#####.#v#.###.#.### +###.>...#.#.#.....#...# +###v###.#.#.#########.# +###...#.#.#.......#...# +#####.#.#.#######.#.### +#.....#.#.#.......#...# +#.#####.#.#.#########v# +#.#...#...#...###...>.# +#.#.#v#######v###.###v# +#...#.>.#...>.>.#.###.# +#####v#.#.###v#.#.###.# +#.....#...#...#.#.#...# +#.#########.###.#.#.### +#...###...#...#...#.### +###.###.#.###v#####v### +#...#...#.#.>.>.#.>.### +#.###.###.#.###.#.#v### +#.....###...###...#...# +#####################.#` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 94, + }, + { + name: "actual", + input: input, + want: 2294, + }, + } + 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: 154, + }, + { + name: "actual", + input: input, + want: 6418, + }, + } + 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) + } + }) + } +} From b64ac3491a4d8540318c698ce5a53e0e1676efcc Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Sun, 18 Aug 2024 12:53:28 -0400 Subject: [PATCH 35/57] 2023-24 --- 2023/day24/main.go | 232 ++++++++++++++++++++++++++++++++++++++++ 2023/day24/main_test.go | 67 ++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 2023/day24/main.go create mode 100644 2023/day24/main_test.go diff --git a/2023/day24/main.go b/2023/day24/main.go new file mode 100644 index 0000000..4c4bdae --- /dev/null +++ b/2023/day24/main.go @@ -0,0 +1,232 @@ +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, [2]float64{200000000000000, 400000000000000}) + 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, testRange [2]float64) int { + hailstones := parseInput(input) + + ans := 0 + + // only move forward (per the velocity) + for i, hs1 := range hailstones { + for _, hs2 := range hailstones[i+1:] { + intersection := getIntersectingCoordinates(hs1, hs2) + if intersection == nil { + continue + } + + // ensure intersection is in the right direction + // solve for time to reach intersection? + if solveForTimeToReachPoint(hs1, intersection) < 0 || solveForTimeToReachPoint(hs2, intersection) < 0 { + continue + } + + if testRange[0] <= intersection[0] && intersection[0] <= testRange[1] && + testRange[0] <= intersection[1] && intersection[1] <= testRange[1] { + + ans++ + } + } + } + + return ans +} + +// does not check for if the lines are the same line +func areHailstonesParallel(hs1, hs2 hailstone) bool { + if hs1.hasVerticalPath && hs2.hasVerticalPath { + return true + } + if hs1.hasVerticalPath || hs2.hasVerticalPath { + return false + } + + return hs1.slope == hs2.slope +} + +// returns nil slice if the lines do not intersect +func getIntersectingCoordinates(hs1, hs2 hailstone) []float64 { + if areHailstonesParallel(hs1, hs2) { + return nil + } + // assume not the exact same line and that there is only one intersection point + + // point-slope line formula + // y - y1 = m(x - x1) + x := (hs1.slope*hs1.x - hs2.slope*hs2.x + hs2.y - hs1.y) / (hs1.slope - hs2.slope) + y := hs1.slope*(x-hs1.x) + hs1.y + + return []float64{x, y} +} + +func solveForTimeToReachPoint(hs hailstone, point []float64) float64 { + if len(point) != 2 { + panic("expected len == 2 for point slice") + } + // x = vx * t + x0 + // t = (intersection_x - x_0) / vx + t := (point[0] - hs.x) / hs.vx + + return t +} + +func part2(input string) int { + hailstones := parseInput(input) + + var possibleRockVelX, possibleRockVelY, possibleRockVelZ []int + for i, hs1 := range hailstones { + for _, hs2 := range hailstones[i+1:] { + + if hs1.vx == hs2.vx { + possibilities := getPossibleVelocities(int(hs2.x), int(hs1.x), int(hs1.vx)) + if len(possibleRockVelX) == 0 { + possibleRockVelX = possibilities + } else { + possibleRockVelX = getIntersection(possibleRockVelX, possibilities) + } + } + if hs1.vy == hs2.vy { + possibilities := getPossibleVelocities(int(hs2.y), int(hs1.y), int(hs1.vy)) + if len(possibleRockVelY) == 0 { + possibleRockVelY = possibilities + } else { + possibleRockVelY = getIntersection(possibleRockVelY, possibilities) + } + } + if hs1.vz == hs2.vz { + possibilities := getPossibleVelocities(int(hs2.z), int(hs1.z), int(hs1.vz)) + if len(possibleRockVelZ) == 0 { + possibleRockVelZ = possibilities + } else { + possibleRockVelZ = getIntersection(possibleRockVelZ, possibilities) + } + } + } + } + + if len(possibleRockVelX) == 1 && len(possibleRockVelY) == 1 && len(possibleRockVelZ) == 1 { + rockVelX := float64(possibleRockVelX[0]) + rockVelY := float64(possibleRockVelY[0]) + rockVelZ := float64(possibleRockVelZ[0]) + + hailstoneA, hailstoneB := hailstones[0], hailstones[1] + mA := (hailstoneA.vy - rockVelY) / (hailstoneA.vx - rockVelX) + mB := (hailstoneB.vy - rockVelY) / (hailstoneB.vx - rockVelX) + cA := hailstoneA.y - (mA * hailstoneA.x) + cB := hailstoneB.y - (mB * hailstoneB.x) + rockX := (cB - cA) / (mA - mB) + rockY := mA*rockX + cA + time := (rockX - hailstoneA.x) / (hailstoneA.vx - rockVelX) + rockZ := hailstoneA.z + (hailstoneA.vz-rockVelZ)*time + return int(rockX + rockY + rockZ) + } + + panic("more than one possible velocity in a direction") +} + +func getPossibleVelocities(pos1, pos2 int, vel int) []int { + match := []int{} + for possibleVel := -1000; possibleVel < 1000; possibleVel++ { + if possibleVel != vel && (pos1-pos2)%(possibleVel-vel) == 0 { + match = append(match, possibleVel) + } + } + return match +} + +func getIntersection(sli1, sli2 []int) []int { + result := []int{} + + map2 := map[int]bool{} + for _, val := range sli2 { + map2[val] = true + } + + for _, val := range sli1 { + if map2[val] { + result = append(result, val) + } + } + return result +} + +func parseInput(input string) (ans []hailstone) { + for _, line := range strings.Split(input, "\n") { + positions := []float64{} + vels := []float64{} + + line = strings.ReplaceAll(line, ",", "") + parts := strings.Split(line, " @ ") + + for _, posStr := range strings.Fields(parts[0]) { + positions = append(positions, float64(cast.ToInt(posStr))) + } + for _, velStr := range strings.Fields(parts[1]) { + vels = append(vels, float64(cast.ToInt(velStr))) + } + + ans = append(ans, makeHailstone(positions[0], positions[1], positions[2], vels[0], vels[1], vels[2])) + } + + return ans +} + +type hailstone struct { + x, y, z float64 + vx, vy, vz float64 + hasVerticalPath bool + slope float64 +} + +func makeHailstone(x, y, z, vx, vy, vz float64) hailstone { + hs := hailstone{ + x: x, + y: y, + z: z, + vx: vx, + vy: vy, + vz: vz, + hasVerticalPath: vx == 0, + slope: 0, + } + if !hs.hasVerticalPath { + hs.slope = vy / vx + } + return hs +} diff --git a/2023/day24/main_test.go b/2023/day24/main_test.go new file mode 100644 index 0000000..5b9ffeb --- /dev/null +++ b/2023/day24/main_test.go @@ -0,0 +1,67 @@ +package main + +import ( + "testing" +) + +var example = `19, 13, 30 @ -2, 1, -2 +18, 19, 22 @ -1, -1, -2 +20, 25, 34 @ -2, -2, -4 +12, 31, 28 @ -1, -2, -1 +20, 19, 15 @ 1, -5, -3` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + testRange [2]float64 + want int + }{ + { + name: "example", + input: example, + testRange: [2]float64{7, 27}, + want: 2, + }, + { + name: "actual", + input: input, + testRange: [2]float64{200000000000000, 400000000000000}, + want: 31921, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := part1(tt.input, tt.testRange); 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 + }{ + // example input is not big enough for this logic to work + // { + // name: "example", + // input: example, + // want: 47, + // }, + { + name: "actual", + input: input, + want: 761691907059631, + }, + } + 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) + } + }) + } +} From 2fbb261d062fb78898e6e49f66fc32239ebe7c7b Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 19 Aug 2024 12:57:52 -0400 Subject: [PATCH 36/57] 2024-18 still dont get shoelace and picks... --- 2023/day18/main.go | 217 ++++++++++++++++++++++++++++++++++++++++ 2023/day18/main_test.go | 72 +++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 2023/day18/main.go create mode 100644 2023/day18/main_test.go diff --git a/2023/day18/main.go b/2023/day18/main.go new file mode 100644 index 0000000..a850eba --- /dev/null +++ b/2023/day18/main.go @@ -0,0 +1,217 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strconv" + "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 { + digInstructions := parseInput(input) + + trenchCoords := getTrenchCoords(digInstructions) + + containedCoords := getContainedCoords(trenchCoords) + + return len(containedCoords) + len(trenchCoords) +} + +func getTrenchCoords(digInstructions []digInstruction) map[[2]int]bool { + trenchCoords := map[[2]int]bool{ + {0, 0}: true, + } + + var row, col int + diffs := map[string][2]int{ + "L": {0, -1}, + "R": {0, 1}, + "U": {-1, 0}, + "D": {1, 0}, + } + + for _, inst := range digInstructions { + for i := 1; i <= inst.length; i++ { + row += diffs[inst.dir][0] + col += diffs[inst.dir][1] + trenchCoords[[2]int{row, col}] = true + } + } + return trenchCoords +} + +func getContainedCoords(trenchCoords map[[2]int]bool) map[[2]int]bool { + // check around a coordinate that's part of a straight line for a cell that _could_ be contained + // straight lines will have one side that's in and one that is out + // we'll only check vertical lines to make it easier to code... + var testCoords [][2]int + + for coord := range trenchCoords { + upCoords := [2]int{coord[0] - 1, coord[1]} + downCoords := [2]int{coord[0] + 1, coord[1]} + leftCoords := [2]int{coord[0], coord[1] - 1} + rightCoords := [2]int{coord[0], coord[1] + 1} + + if trenchCoords[upCoords] && trenchCoords[downCoords] && + !trenchCoords[leftCoords] && !trenchCoords[rightCoords] { + // part of vertical line + testCoords = append(testCoords, leftCoords, rightCoords) + break + } + } + + // calculate the max size that can be contained (equal to the box containing all the coordinates) + var ( + left = testCoords[0][1] + right = testCoords[0][1] + top = testCoords[0][0] + bottom = testCoords[0][0] + ) + for coords := range trenchCoords { + left = min(left, coords[1]) + right = max(right, coords[1]) + top = min(top, coords[0]) + bottom = max(bottom, coords[0]) + } + + maxContainedSize := (right - left + 1) * (bottom - top + 1) + + for _, coord := range testCoords { + queue := [][2]int{coord} + seen := map[[2]int]bool{} + + for len(queue) > 0 && len(seen) < maxContainedSize { + current := queue[0] + queue = queue[1:] + + if seen[current] { + continue + } + seen[current] = true + + for _, diff := range [][2]int{ + {-1, 0}, + {1, 0}, + {0, -1}, + {0, 1}, + } { + nextRow := current[0] + diff[0] + nextCol := current[1] + diff[1] + nextCoord := [2]int{nextRow, nextCol} + // if already seen or it's part of the trench, skip + if trenchCoords[nextCoord] || seen[nextCoord] { + continue + } + // otherwise add it to be searched + queue = append(queue, nextCoord) + } + } + + if len(queue) == 0 { + return seen + } + } + panic("should return from loop") +} + +func part2(input string) int { + digInstructions := parseInput(input) + + vertices := [][2]int{} + currentPoint := [2]int{0, 0} + + for _, inst := range digInstructions { + hex := inst.color[1 : len(inst.color)-1] + dirCode := inst.color[len(inst.color)-1:] + + convInt, err := strconv.ParseInt(hex, 16, 0) + if err != nil { + panic(err.Error()) + } + + switch dirCode { + case "0": // R + currentPoint[1] += int(convInt) + case "1": // D + currentPoint[0] += int(convInt) + case "2": // L + currentPoint[1] -= int(convInt) + case "3": // U + currentPoint[0] -= int(convInt) + } + vertices = append(vertices, currentPoint) + } + + return shoelace(vertices) + 1 +} + +func shoelace(coordinates [][2]int) int { + area := 0 + + for i := 0; i < len(coordinates); i++ { + coordA := coordinates[i] + coordB := coordinates[(i+1)%(len(coordinates))] + + area += (coordA[1] * coordB[0]) - (coordB[1] * coordA[0]) + + max(abs(coordA[0]-coordB[0]), abs(coordA[1]-coordB[1])) + } + + return area / 2 +} + +func abs(i int) int { + if i < 0 { + return -i + } + return i +} + +type digInstruction struct { + dir string + length int + color string +} + +func parseInput(input string) (ans []digInstruction) { + for _, line := range strings.Split(input, "\n") { + parts := strings.Split(line, " ") + ans = append(ans, digInstruction{ + dir: parts[0], + length: cast.ToInt(parts[1]), + color: parts[2][1 : len(parts[2])-1], + }) + } + return ans +} diff --git a/2023/day18/main_test.go b/2023/day18/main_test.go new file mode 100644 index 0000000..2896995 --- /dev/null +++ b/2023/day18/main_test.go @@ -0,0 +1,72 @@ +package main + +import ( + "testing" +) + +var example = `R 6 (#70c710) +D 5 (#0dc571) +L 2 (#5713f0) +D 2 (#d2c081) +R 2 (#59c680) +D 2 (#411b91) +L 5 (#8ceee2) +U 2 (#caa173) +L 1 (#1b58a2) +U 2 (#caa171) +R 2 (#7807d2) +U 3 (#a77fa3) +L 2 (#015232) +U 2 (#7a21e3)` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 62, + }, + { + name: "actual", + input: input, + want: 47527, + }, + } + 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: 952408144115, + }, + { + name: "actual", + input: input, + want: 52240187443190, + }, + } + 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) + } + }) + } +} From fc5083027ba73834fad4b863427f1d4007d3defe Mon Sep 17 00:00:00 2001 From: Alex Chao Date: Mon, 19 Aug 2024 13:13:35 -0400 Subject: [PATCH 37/57] Update README.md --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index bc68d93..041be74 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,7 @@ ![400 stars!](./400.png) -[reddit thread after completing the first six years](https://www.reddit.com/r/adventofcode/comments/klzgnx/complete_repo_and_thoughts_in_comments/) - -[reddit thread post 2021](https://www.reddit.com/r/adventofcode/comments/rrog0y/all_caught_up_repo_all_gogolang_thoughts_in/) - ### Quick Note -I started this in a pre-generics Go/Golang world. Maybe one day I'll come back and learn generics as they'd be quite useful here. But that's for future Alex. +I started this in a pre-generics Go/Golang world. Maybe one day I'll come back and learn generics as they'd be quite useful here. But that's for future me. ## Running Locally ### Requirements From f71f2fedb21dd1ff8a5856497fc7001d489b5340 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 19 Aug 2024 16:17:46 -0400 Subject: [PATCH 38/57] it's snowing!! --- 2023/day25/main.go | 226 ++++++++++++++++++++++++++++++++++++++++ 2023/day25/main_test.go | 71 +++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 2023/day25/main.go create mode 100644 2023/day25/main_test.go diff --git a/2023/day25/main.go b/2023/day25/main.go new file mode 100644 index 0000000..706156f --- /dev/null +++ b/2023/day25/main.go @@ -0,0 +1,226 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "math/rand" + "strings" + + "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 { + graph := parseInput(input) + + // if brute forcing, can check if the separated nodes are able to traverse to each other + // which would indicate that it has broken an internal (to a group) edge, and not separated + // the overall group... + + // brute forcing 3k edges for my input with 3 traversals for each... + // for n = total edges + // O(n^4), triple nested for loop with traversal check in each + // O(3k^4) = 81,000,000,000,000 way too slow + + // strategy actually used: + // pick 200 random pairs of nodes, traverse between the two of them + // the most traversed nodes will likely be the bridges + // a similar strategy would be to BFS traverse from a few randomly selected nodes to the furthest + // possible node, the furthest depth away is likely in the other group (assuming a kind input) + // and therefore those paths can be used to tabulate the most trafficked edges aka 3 bridges + var allNodes []string + for n := range graph { + allNodes = append(allNodes, n) + } + + // for a small (example) graph just pick every node + // for a large (actual input) graph, pick 200 nodes + pairsToPick := min(200, len(graph)*(len(graph)-1)) + + traversedPairs := map[string]bool{} + timesEdgeTraversed := map[string]int{} + + for len(traversedPairs) < pairsToPick { + i1 := rand.Intn(len(allNodes)) + i2 := rand.Intn(len(allNodes)) + if i1 == i2 { + continue + } + + n1 := allNodes[i1] + n2 := allNodes[i2] + + randomPairName := sortedEdgeName(n1, n2) + + if traversedPairs[randomPairName] { + continue + } + traversedPairs[randomPairName] = true + + path := findShortestPath(graph, n1, n2) + + for i := 1; i < len(path); i++ { + timesEdgeTraversed[sortedEdgeName(path[i-1], path[i])]++ + } + } + + threeMostTraffickedEdges := getThreeMostTraffickedEdges(timesEdgeTraversed) + + // remove edge + for _, edge := range threeMostTraffickedEdges { + nodes := strings.Split(edge, " ") + + graph[nodes[0]] = removeElementFromSlice(graph[nodes[0]], nodes[1]) + graph[nodes[1]] = removeElementFromSlice(graph[nodes[1]], nodes[0]) + } + + sizes := []int{} + for _, node := range strings.Split(threeMostTraffickedEdges[0], " ") { + sizes = append(sizes, getGroupSize(graph, node, map[string]bool{})) + } + + if len(sizes) != 2 { + panic("expected two groups") + } + + return sizes[0] * sizes[1] +} + +func sortedEdgeName(node1, node2 string) string { + lower := min(node1, node2) + higher := max(node1, node2) + return fmt.Sprintf("%v %v", lower, higher) +} + +func findShortestPath(graph map[string][]string, start, end string) []string { + type dfsNode struct { + current string + pathSoFar []string + } + seen := map[string]bool{} + queue := []dfsNode{ + { + current: start, + pathSoFar: []string{start}, + }, + } + + for len(queue) > 0 { + popped := queue[0] + queue = queue[1:] + if seen[popped.current] { + continue + } + seen[popped.current] = true + + if popped.current == end { + return popped.pathSoFar + } + + for _, neighbor := range graph[popped.current] { + if seen[neighbor] { + continue + } + nextNode := dfsNode{ + current: neighbor, + pathSoFar: append([]string{}, popped.pathSoFar...), // deep copy + } + nextNode.pathSoFar = append(nextNode.pathSoFar, neighbor) + + queue = append(queue, nextNode) + } + } + + panic("expect return from loop") +} + +func getThreeMostTraffickedEdges(timesEdgeTraversed map[string]int) []string { + ans := []string{} + + for len(ans) < 3 { + var bestEdge string + var bestCount int + + for edge, count := range timesEdgeTraversed { + if count > bestCount { + bestCount = count + bestEdge = edge + } + } + + ans = append(ans, bestEdge) + delete(timesEdgeTraversed, bestEdge) + } + + return ans +} + +func removeElementFromSlice(sli []string, ele string) []string { + for i, n := range sli { + if n == ele { + sli[len(sli)-1], sli[i] = sli[i], sli[len(sli)-1] + sli = sli[:len(sli)-1] + + return sli + } + } + panic("element not found") +} + +func getGroupSize(graph map[string][]string, node string, seen map[string]bool) int { + if seen[node] { + return 0 + } + size := 1 + seen[node] = true + for _, neighbor := range graph[node] { + size += getGroupSize(graph, neighbor, seen) + } + return size +} + +func part2(input string) string { + return "happiness" +} + +func parseInput(input string) (graph map[string][]string) { + graph = map[string][]string{} + + for _, line := range strings.Split(input, "\n") { + parts := strings.Split(line, ": ") + for _, node := range strings.Split(parts[1], " ") { + graph[parts[0]] = append(graph[parts[0]], node) + graph[node] = append(graph[node], parts[0]) + } + } + + return graph +} diff --git a/2023/day25/main_test.go b/2023/day25/main_test.go new file mode 100644 index 0000000..ecb286a --- /dev/null +++ b/2023/day25/main_test.go @@ -0,0 +1,71 @@ +package main + +import ( + "testing" +) + +var example = `jqt: rhn xhk nvd +rsh: frs pzl lsr +xhk: hfx +cmg: qnr nvd lhk bvb +rhn: xhk bvb hfx +bvb: xhk hfx +pzl: lsr hfx nvd +qnr: nvd +ntq: jqt hfx bvb xhk +nvd: lhk +lsr: lhk +rzs: qnr cmg lsr rsh +frs: qnr lhk lsr` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 54, + }, + { + name: "actual", + input: input, + want: 567606, + }, + } + 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 string + }{ + { + name: "example", + input: example, + want: "happiness", + }, + { + name: "actual", + input: input, + want: "happiness", + }, + } + 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) + } + }) + } +} From 98b07a325a01d0f36b2d06e837bd9541340a8151 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 19 Aug 2024 16:22:26 -0400 Subject: [PATCH 39/57] updated README and added 450 star screenshot --- 400.png | Bin 74252 -> 0 bytes 450.png | Bin 0 -> 75887 bytes README.md | 8 ++------ 3 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 400.png create mode 100644 450.png diff --git a/400.png b/400.png deleted file mode 100644 index 12838694adf85fe9e2b1efb66f1129a54a443a44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74252 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 diff --git a/450.png b/450.png new file mode 100644 index 0000000000000000000000000000000000000000..36747c03b712f9bb9a5d79bf31ec5ffe6bb794e9 GIT binary patch literal 75887 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 Date: Thu, 12 Dec 2024 21:02:49 -0800 Subject: [PATCH 40/57] 2024-01 --- 2024/day01/main.go | 78 +++++++++++++++++++++++++++++++++++++++++ 2024/day01/main_test.go | 64 +++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 2024/day01/main.go create mode 100644 2024/day01/main_test.go diff --git a/2024/day01/main.go b/2024/day01/main.go new file mode 100644 index 0000000..b01efeb --- /dev/null +++ b/2024/day01/main.go @@ -0,0 +1,78 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "sort" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/mathy" + "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 { + list1, list2 := parseInput(input) + sort.Ints(list1) + sort.Ints(list2) + + ans := 0 + for i := range len(list1) { + ans += mathy.AbsInt(list2[i] - list1[i]) + } + + return ans +} + +func part2(input string) int { + list1, list2 := parseInput(input) + + countsList2 := map[int]int{} + for _, v := range list2 { + countsList2[v]++ + } + + ans := 0 + for _, v := range list1 { + ans += v * countsList2[v] + } + return ans +} + +func parseInput(input string) (list1, list2 []int) { + for _, line := range strings.Split(input, "\n") { + nums := strings.Split(line, " ") + list1 = append(list1, cast.ToInt(nums[0])) + list2 = append(list2, cast.ToInt(nums[1])) + } + return list1, list2 +} diff --git a/2024/day01/main_test.go b/2024/day01/main_test.go new file mode 100644 index 0000000..e5c1bc1 --- /dev/null +++ b/2024/day01/main_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "testing" +) + +var example = `3 4 +4 3 +2 5 +1 3 +3 9 +3 3` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 11, + }, + { + name: "actual", + input: input, + want: 1651298, + }, + } + 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: 31, + }, + { + name: "actual", + input: input, + want: 21306195, + }, + } + 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) + } + }) + } +} From 5f7125527d2f48fe28eda86a3c6c368ea8ef8b8d Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 20 Dec 2024 21:11:57 -0500 Subject: [PATCH 41/57] new job is python and ruby... a gopher will reemerge to backfill one day --- 2024/day02/day02.py | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 2024/day02/day02.py diff --git a/2024/day02/day02.py b/2024/day02/day02.py new file mode 100644 index 0000000..bade04c --- /dev/null +++ b/2024/day02/day02.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +from typing import List + +example = """7 6 4 2 1 +1 2 7 8 9 +9 7 6 2 1 +1 3 2 4 5 +8 6 4 4 1 +1 3 6 7 9""" + + +def part1(grid: List[List[int]]) -> int: + valid_levels = 0 + for level in grid: + if test_level(level): + valid_levels += 1 + return valid_levels + + +def part2(grid: List[List[int]]) -> int: + # tolerate one bad level... + valid_levels: int = 0 + for level in grid: + for i in range(len(level)): + newLevel: List[int] = level.copy() + newLevel.pop(i) # removes i-th element?, how convenient.. + if test_level(newLevel): + valid_levels += 1 + break + + return valid_levels + + +def test_level(level: List[int]) -> bool: + is_increasing = level[1] > level[0] + is_valid = True + + # The levels are either all increasing or all decreasing. + # Any two adjacent levels differ by at least one and at most three. + for i in range(1, len(level)): + if is_increasing and level[i] <= level[i - 1]: + return False + elif not is_increasing and level[i] >= level[i - 1]: + return False + + diff = abs(level[i] - level[i - 1]) + if diff < 1 or diff > 3: + return False + + return is_valid + + +def convert_input(input: str) -> List[List[int]]: + grid: List[List[int]] = [] + + for level in input.splitlines(): + grid.append([int(part) for part in level.split(" ")]) + # converted_level: List[int] = [] + # this syntax is going to take getting used to... + # for part in level.split(" "): + # converted_level.append(int(part)) + # grid.append(converted_level) + + return grid + + +example_grid = convert_input(example) +print("example part1:", part1(example_grid), "want", 2) + +input = open("input.txt", "r").read() +grid = convert_input(input) +print("part1:", part1(grid), "want", 585) + +print("example part2:", part2(example_grid), "want", 4) +print("part2:", part2(grid), "want", 626) From 538d569be1754882843f98ca3a7e43b463ba9783 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 20 Dec 2024 21:25:22 -0500 Subject: [PATCH 42/57] oof manually converting is a challenge --- 2024/day02/main.go | 106 ++++++++++++++++++++++++++++++++++++++++ 2024/day02/main_test.go | 64 ++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 2024/day02/main.go create mode 100644 2024/day02/main_test.go diff --git a/2024/day02/main.go b/2024/day02/main.go new file mode 100644 index 0000000..58372b7 --- /dev/null +++ b/2024/day02/main.go @@ -0,0 +1,106 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/cast" + "github.com/alexchao26/advent-of-code-go/mathy" + "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 { + grid := convertInput(input) + validLevels := 0 + for _, level := range grid { + if testLevel(level) { + validLevels += 1 + } + } + return validLevels +} + +func part2(input string) int { + grid := convertInput(input) + // tolerate one bad level... + validLevels := 0 + for _, level := range grid { + for i := range len(level) { + newLevel := []int{} + for j := range len(level) { + if i != j { + newLevel = append(newLevel, level[j]) + } + } + if testLevel(newLevel) { + validLevels += 1 + break + } + } + } + + return validLevels +} + +func testLevel(level []int) bool { + isIncreasing := level[1] > level[0] + + // The levels are either all increasing or all decreasing. + // Any two adjacent levels differ by at least one and at most three. + for i := 1; i < len(level); i++ { + if isIncreasing && level[i] <= level[i-1] { + return false + } else if !isIncreasing && level[i] >= level[i-1] { + return false + } + + diff := mathy.AbsInt(level[i] - level[i-1]) + if diff < 1 || diff > 3 { + return false + } + } + + return true +} + +func convertInput(input string) [][]int { + grid := [][]int{} + for _, line := range strings.Split(input, "\n") { + level := []int{} + for _, n := range strings.Split(line, " ") { + level = append(level, cast.ToInt(n)) + } + grid = append(grid, level) + } + return grid +} diff --git a/2024/day02/main_test.go b/2024/day02/main_test.go new file mode 100644 index 0000000..c61bc01 --- /dev/null +++ b/2024/day02/main_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "testing" +) + +var example = `7 6 4 2 1 +1 2 7 8 9 +9 7 6 2 1 +1 3 2 4 5 +8 6 4 4 1 +1 3 6 7 9` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 2, + }, + { + name: "actual", + input: input, + want: 585, + }, + } + 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: 4, + }, + { + name: "actual", + input: input, + want: 626, + }, + } + 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) + } + }) + } +} From 52964cab66e2e3bebb85bff883c21efe6f7e8111 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Sat, 21 Dec 2024 00:14:29 -0500 Subject: [PATCH 43/57] python regex --- 2024/day03/day03.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 2024/day03/day03.py diff --git a/2024/day03/day03.py b/2024/day03/day03.py new file mode 100644 index 0000000..625cf27 --- /dev/null +++ b/2024/day03/day03.py @@ -0,0 +1,43 @@ +import re + + +def part1(input: str) -> int: + matches = re.findall(r"mul\(\d+,\d+\)", input) + ans = 0 + for match in matches: + ans += exec_mul(match) + return ans + + +def part2(input: str) -> int: + matches = re.findall(r"(do\(\)|don\'t\(\)|mul\(\d+,\d+\))", input) + do = True + ans = 0 + + for match in matches: + if match == "do()": + do = True + elif match == "don't()": + do = False + elif do: + ans += exec_mul(match) + + return ans + + +def exec_mul(s: str) -> int: + nums = re.findall(r"\d+", s) + return int(nums[0]) * int(nums[1]) + + +example = "xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))" + +print("example part1:", part1(example), "want", 161) + +input = open("input.txt").read() +print("part1:", part1(input), "want", 169021493) + +example2 = "xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))" + +print("example part2:", part2(example2), "want", 48) +print("part2:", part2(input), "want", 111762583) From ed6757dc76db2da4dacf40795f1c85656bfd84b8 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Sat, 21 Dec 2024 13:07:09 -0500 Subject: [PATCH 44/57] 2024-4 --- 2024/day04/day04.py | 80 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 2024/day04/day04.py diff --git a/2024/day04/day04.py b/2024/day04/day04.py new file mode 100644 index 0000000..5b7de2e --- /dev/null +++ b/2024/day04/day04.py @@ -0,0 +1,80 @@ +from typing import List, Tuple + + +example = """MMMSXXMASM +MSAMXMSMSA +AMXSXMAAMM +MSAMASMSMX +XMASAMXAMM +XXAMMXXAMA +SMSMSASXSS +SAXAMASAAA +MAMMMXMMMM +MXMXAXMASX""" + + +def part1(input: str) -> int: + grid: List[str] = input.splitlines() + + ans = 0 + + # 8 directions that words can be found in once a "X" is found + dirs: List[Tuple[int, int]] = [ + (-1, -1), + (-1, 0), + (-1, 1), + (0, -1), + (0, 1), + (1, -1), + (1, 0), + (1, 1), + ] + + for i in range(len(grid)): + for j in range(len(grid[0])): + if grid[i][j] == "X": + for dir in dirs: + if getWord(grid, i, j, dir) == "XMAS": + ans += 1 + return ans + + +def getWord(grid: List[str], row: int, col: int, dir: Tuple[int, int]) -> str: + word = grid[row][col] + while len(word) < 4: + row += dir[0] + col += dir[1] + if row < 0 or row >= len(grid) or col < 0 or col >= len(grid[0]): + return "" + word += grid[row][col] + + return word + + +def part2(input: str) -> int: + grid: List[str] = input.splitlines() + + ans: int = 0 + + # do not search outer border of the grid because we're looking for A's which + # will only be "valid" if they are not on the outer border + for i in range(1, len(grid) - 1): + for j in range(1, len(grid[0]) - 1): + if grid[i][j] == "A": + backslash_word = grid[i - 1][j - 1] + grid[i + 1][j + 1] + slash_word = grid[i - 1][j + 1] + grid[i + 1][j - 1] + + backslash_valid = backslash_word == "MS" or backslash_word == "SM" + slash_valid = slash_word == "MS" or slash_word == "SM" + if backslash_valid and slash_valid: + ans += 1 + return ans + + +input = open("input.txt", "r").read() + +print(f"part1 example: {part1(example)} want 18") +print(f"part1: {part1(input)} want 2468") + +print(f"part2 example: {part2(example)} want 9") +print(f"part2: {part2(input)} want 1864") From cdb5395bdd3840c13c3c21d1489d7482bb40d840 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Sun, 22 Dec 2024 13:33:17 -0500 Subject: [PATCH 45/57] day 5 (python) feels like an unideal solution but it works --- 2024/day05/day05.py | 120 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 2024/day05/day05.py diff --git a/2024/day05/day05.py b/2024/day05/day05.py new file mode 100644 index 0000000..260d01b --- /dev/null +++ b/2024/day05/day05.py @@ -0,0 +1,120 @@ +from collections import defaultdict + + +def day5(input: str, part: int) -> int: + split_input = input.split("\n\n") + + reversed_graph: defaultdict[int, list[int]] = defaultdict(list) + for line in split_input[0].splitlines(): + rule_parts = line.split("|") + X, Y = int(rule_parts[0]), int(rule_parts[1]) + reversed_graph[Y].append(X) + + updates: list[list[int]] = [ + [int(x) for x in line.split(",")] for line in split_input[1].splitlines() + ] + part1_ans: int = 0 + + # for part 2 + invalid_updates: list[list[int]] = [] + + for update in updates: + # assuming there are no repeat updates... + seen_set: set[int] = set() + disallowed_set: set[int] = set() + is_valid = True + + for num in update: + if num in disallowed_set: + is_valid = False + break + for cannot_come_before in reversed_graph[num]: + if not cannot_come_before in seen_set: + disallowed_set.add(cannot_come_before) + + seen_set.add(num) + + if is_valid: + part1_ans += update[len(update) // 2] + else: + invalid_updates.append(update) + if part == 1: + return part1_ans + + # part2 + # can assume there's only one valid order... or that unordered ones will not affect the middle value + # so just take all the numbers and create the correct order by traversing the graph of dependencies? + + part2_ans: int = 0 + for update in invalid_updates: + correct_order: list[int] = [] + all_nums: set[int] = set(update) + used_nums: set[int] = set() + while len(all_nums) > len(used_nums): + for num in all_nums: + if num in used_nums: + continue + if not num in reversed_graph: + correct_order.append(num) + # all_nums.remove(num) + used_nums.add(num) + continue + + # check if all of this num's dependencies are in used_nums + # or if its dependencies are not present at all + # there's definitely a better algo for this... + if all( + [ + dep in used_nums or not dep in all_nums + for dep in reversed_graph[num] + ] + ): + correct_order.append(num) + # all_nums.remove(num) + used_nums.add(num) + continue + part2_ans += correct_order[len(correct_order) // 2] + + return part2_ans + + +# page ordering rules, X|Y -> X must come before Y +# updates, basically orders of pages +# missing page numbers are ignored.. +# part1_ans: sum middle page of each valid update +example = """47|53 +97|13 +97|61 +97|47 +75|29 +61|13 +75|53 +29|13 +97|29 +53|29 +61|53 +97|53 +61|29 +47|13 +75|47 +97|75 +47|61 +75|61 +47|29 +75|13 +53|13 + +75,47,61,53,29 +97,61,53,29,13 +75,29,13 +75,97,47,61,53 +61,13,29 +97,13,75,29,47""" + +input = open("input.txt").read() + +print(f"day5 example: {day5(example,1)} want 143") +print(f"day5: {day5(input,1)} want 5166") + +print(f"part2 example: {day5(example,2)} want 123") +print(f"part2: {day5(input,2)} want 4679") From 9c8d554c4e07caaea2c2540f2fe5cc65c68e0bbe Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 23 Dec 2024 00:01:38 -0500 Subject: [PATCH 46/57] hacky and not dry but it works... slowly --- 2024/day06/day06.py | 165 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 2024/day06/day06.py diff --git a/2024/day06/day06.py b/2024/day06/day06.py new file mode 100644 index 0000000..1e23390 --- /dev/null +++ b/2024/day06/day06.py @@ -0,0 +1,165 @@ +# guard walks forward, turns right if blocked, turn or step = 1 move + + +import time + + +def part1(input: str) -> int: + return len(get_full_path(input)) + + +# changed this to return the set of all coords on the path to (slightly) speed up part 2 +# so part 2 can just modify things already on the guard's path instead of brute forcing +# the entire grid +def get_full_path(input: str) -> set[tuple[int, int]]: + lines = input.splitlines() + + row: int = 0 + col: int = 0 + + # find starting row and col + for r in range(len(lines)): + for c in range(len(lines[0])): + if lines[r][c] == "^": + row = r + col = c + break + + # example and my input both have the guard just starting pointing north + dir_index: int = 0 + dirs: list[list[int]] = [ + [-1, 0], + [0, 1], + [1, 0], + [0, -1], + ] + + seen: set[tuple[int, int]] = set() + + # assume that we just have to run off the grid?.. + while row >= 0 and row < len(lines) and col >= 0 and col < len(lines[0]): + coord = (row, col) + + seen.add(coord) + dir = dirs[dir_index] + nextRow: int = row + dir[0] + nextCol: int = col + dir[1] + if not ( + nextRow >= 0 + and nextRow < len(lines) + and nextCol >= 0 + and nextCol < len(lines[0]) + ): + break + if lines[nextRow][nextCol] == "#": + # rotate + dir_index += 1 + dir_index %= 4 + else: + row = nextRow + col = nextCol + + # return len(seen) + return seen + + +def part2(input: str) -> int: + + path = get_full_path(input) + + # list(str) divides the str into individual characters? + # can't just use str.split("") + # need an actual 2D array so we can modify the grid and add obstacles + grid = [list(line) for line in input.splitlines()] + + # for every coord on the guard's path, just make it an obstacle and see if it loops + ans: int = 0 + for coord in path: + if grid[coord[0]][coord[1]] == ".": + grid[coord[0]][coord[1]] = "#" + if does_guard_loop(grid): + ans += 1 + grid[coord[0]][coord[1]] = "." + + return ans + + +# slight modification to getting the entire path by tracking the direction +# these two could be combined... +def does_guard_loop(grid: list[list[str]]) -> bool: + row: int = 0 + col: int = 0 + + # find starting row and col + for r in range(len(grid)): + for c in range(len(grid[0])): + if grid[r][c] == "^": + row = r + col = c + break + + # example and my input both have the guard just starting pointing north + dir_index: int = 0 + dirs: list[list[int]] = [ + [-1, 0], + [0, 1], + [1, 0], + [0, -1], + ] + + # keys: row, col, dir_index + seen: set[tuple[int, int, int]] = set() + + # assume that we just have to run off the grid?.. + while row >= 0 and row < len(grid) and col >= 0 and col < len(grid[0]): + key = (row, col, dir_index) + + if key in seen: + return True + + seen.add(key) + dir = dirs[dir_index] + nextRow: int = row + dir[0] + nextCol: int = col + dir[1] + if not ( + nextRow >= 0 + and nextRow < len(grid) + and nextCol >= 0 + and nextCol < len(grid[0]) + ): + break + if grid[nextRow][nextCol] == "#": + # rotate + dir_index += 1 + dir_index %= 4 + else: + row = nextRow + col = nextCol + + # no loop found + return False + + +example = """....#..... +.........# +.......... +..#....... +.......#.. +.......... +.#..^..... +........#. +#......... +......#...""" + +input = open("input.txt").read() + + +print(f"part1 example: {part1(example)} want 41") +print(f"part1: {part1(input)} want 4939") + +print(f"part2 example: {part2(example)} want 6") + +start_time = time.time() +print(f"part2: {part2(input)} want 1434") # slow as heck... ~30s on my laptop +end_time = time.time() +print(f"runtime = {end_time - start_time} seconds") From 3f6658bcf85c90f802fba974b08a46002ebc5d04 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 23 Dec 2024 22:47:43 -0500 Subject: [PATCH 47/57] hm i wonder if i will end up liking python for ao/algos more than go... --- 2024/day07/day07.py | 60 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 2024/day07/day07.py diff --git a/2024/day07/day07.py b/2024/day07/day07.py new file mode 100644 index 0000000..1405d88 --- /dev/null +++ b/2024/day07/day07.py @@ -0,0 +1,60 @@ +def day7(input: str, part: int) -> int: + ans: int = 0 + + lines = input.splitlines() + for line in lines: + parts = line.split(": ") + target = int(parts[0]) + nums = [int(x) for x in parts[1].split(" ")] + + if recurse(part, nums, target, 0, 0): + ans += target + + return ans + + +# brute force all the combinations and return True early if the target is made using all elements of nums array +def recurse(part: int, nums: list[int], target: int, current: int, index: int) -> bool: + if target < current: + return False + + if index == len(nums) and current == target: + return True + if index == len(nums) and current != target: + return False + + if index == 0: + current = 1 + multiply_result = recurse(part, nums, target, current * nums[index], index + 1) + if multiply_result: + return True + + # attempt concatenation operation for part 2 + if part == 2: + concat_num = int(str(current) + str(nums[index])) + concat_result = recurse(part, nums, target, concat_num, index + 1) + if concat_result: + return True + + if index == 0: + current = 0 + return recurse(part, nums, target, current + nums[index], index + 1) + + +example = """190: 10 19 +3267: 81 40 27 +83: 17 5 +156: 15 6 +7290: 6 8 6 15 +161011: 16 10 13 +192: 17 8 14 +21037: 9 7 18 13 +292: 11 6 16 20""" + +input = open("input.txt").read() + +print(f"part1 example: {day7(example,1)} want 3749") +print(f"part1: {day7(input,1)} want 66343330034722") + +print(f"part2 example: {day7(example,2)} want 11387") +print(f"part2: {day7(input,2)} want 637696070419031") # a bit slow but not bad From 5c4d5ebb0f9cf158f90d09c5b256f67259cf9afe Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Tue, 24 Dec 2024 00:02:47 -0500 Subject: [PATCH 48/57] i do not love this code --- 2024/day08/day08.py | 120 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 2024/day08/day08.py diff --git a/2024/day08/day08.py b/2024/day08/day08.py new file mode 100644 index 0000000..778e07d --- /dev/null +++ b/2024/day08/day08.py @@ -0,0 +1,120 @@ +from collections import defaultdict + + +def part1(input: str) -> int: + grid = [list(line) for line in input.splitlines()] + map_antenna_to_coords: dict[str, list[tuple[int, int]]] = defaultdict(list) + for i in range(len(grid)): + for j in range(len(grid[0])): + if grid[i][j] != ".": + map_antenna_to_coords[grid[i][j]].append((i, j)) + + rows = len(grid) + cols = len(grid[0]) + + anti_node_coords: set[tuple[int, int]] = set() + + for _, coords in map_antenna_to_coords.items(): + + for i in range(len(coords)): + c1 = coords[i] + for j in range(i + 1, len(coords)): + c2 = coords[j] + + if c1 == c2: + continue + + rowDiff: int = c2[0] - c1[0] + colDiff: int = c2[1] - c1[1] + + maybeRow: int = c1[0] - rowDiff + maybeCol: int = c1[1] - colDiff + if ( + 0 <= maybeRow < rows + and 0 <= maybeCol < cols + and (maybeRow, maybeCol) != c1 + and (maybeRow, maybeCol) != c2 + ): + anti_node_coords.add((maybeRow, maybeCol)) + + maybeRow: int = c2[0] + rowDiff + maybeCol: int = c2[1] + colDiff + if ( + 0 <= maybeRow < rows + and 0 <= maybeCol < cols + and (maybeRow, maybeCol) != c1 + and (maybeRow, maybeCol) != c2 + ): + anti_node_coords.add((maybeRow, maybeCol)) + + return len(anti_node_coords) + + +def part2(input: str) -> int: + grid = [list(line) for line in input.splitlines()] + map_antenna_to_coords: dict[str, list[tuple[int, int]]] = defaultdict(list) + for i in range(len(grid)): + for j in range(len(grid[0])): + if grid[i][j] != ".": + map_antenna_to_coords[grid[i][j]].append((i, j)) + + rows = len(grid) + cols = len(grid[0]) + + anti_node_coords: set[tuple[int, int]] = set() + + for _, coords in map_antenna_to_coords.items(): + + for i in range(len(coords)): + c1 = coords[i] + + # a more elegant solution would be to calculate the starting point of the antenna's line + # then do a single loop to find all anti-node coords + # instead, i'll just go left and right from c1, and add c1 manually + anti_node_coords.add(c1) + + for j in range(i + 1, len(coords)): + c2 = coords[j] + + if c1 == c2: + continue + + rowDiff: int = c2[0] - c1[0] + colDiff: int = c2[1] - c1[1] + + maybeRow: int = c1[0] - rowDiff + maybeCol: int = c1[1] - colDiff + while 0 <= maybeRow < rows and 0 <= maybeCol < cols: + anti_node_coords.add((maybeRow, maybeCol)) + maybeRow -= rowDiff + maybeCol -= colDiff + + maybeRow: int = c1[0] + rowDiff + maybeCol: int = c1[1] + colDiff + while 0 <= maybeRow < rows and 0 <= maybeCol < cols: + anti_node_coords.add((maybeRow, maybeCol)) + maybeRow += rowDiff + maybeCol += colDiff + + return len(anti_node_coords) + + +example = """............ +........0... +.....0...... +.......0.... +....0....... +......A..... +............ +............ +........A... +.........A.. +............ +............""" + +input = open("input.txt").read() + +print(f"part1 example: {part1(example)} want 14") +print(f"part1: {part1(input)} want 396") +print(f"part2 example: {part2(example)} want 34") +print(f"part2: {part2(input)} want 1200") From 249ab84e46b12e164dba7e9f6a2188132c07ae90 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Fri, 27 Dec 2024 16:10:36 -0500 Subject: [PATCH 49/57] python solutions through day17 --- 2024/day09/day09.py | 121 +++++++++++++++++++++++++ 2024/day10/day10.py | 99 ++++++++++++++++++++ 2024/day11/day11.py | 50 +++++++++++ 2024/day12/day12.py | 164 +++++++++++++++++++++++++++++++++ 2024/day13/day13.py | 62 +++++++++++++ 2024/day14/day14.py | 108 ++++++++++++++++++++++ 2024/day15/day15.py | 214 ++++++++++++++++++++++++++++++++++++++++++++ 2024/day16/day16.py | 135 ++++++++++++++++++++++++++++ 2024/day17/day17.py | 131 +++++++++++++++++++++++++++ 9 files changed, 1084 insertions(+) create mode 100644 2024/day09/day09.py create mode 100644 2024/day10/day10.py create mode 100644 2024/day11/day11.py create mode 100644 2024/day12/day12.py create mode 100644 2024/day13/day13.py create mode 100644 2024/day14/day14.py create mode 100644 2024/day15/day15.py create mode 100644 2024/day16/day16.py create mode 100644 2024/day17/day17.py diff --git a/2024/day09/day09.py b/2024/day09/day09.py new file mode 100644 index 0000000..d1272d9 --- /dev/null +++ b/2024/day09/day09.py @@ -0,0 +1,121 @@ +from dataclasses import dataclass + + +# index by index brute force with a sliding window optimization to make it linear... +def part1(input: str) -> int: + total_disk_space: int = 0 + for x in list(input): + total_disk_space += int(x) + + file_system: list[int] = [-1] * total_disk_space + is_file: bool = True + index: int = 0 + file_number: int = 0 + + for x in list(input): + if not is_file: + index += int(x) + is_file = not is_file + else: + for _ in range(int(x)): + if is_file: + file_system[index] = file_number + index += 1 + + file_number += 1 + + is_file = not is_file + + # rearrange file to left via sliding window + left: int = 0 + right: int = len(file_system) - 1 + while left < right: + if file_system[right] == -1: + right -= 1 + elif file_system[left] != -1: + left += 1 + elif file_system[left] == -1: + file_system[left], file_system[right] = ( + file_system[right], + file_system[left], + ) + left += 1 + + # checksum is index in string * number value (file number) + checksum: int = 0 + for i in range(len(file_system)): + if file_system[i] == -1: + break + checksum += i * file_system[i] + + return checksum + + +@dataclass +class FileSystemSpace: + start: int + size: int + file_number: int + + +def part2(input: str) -> int: + files: list[FileSystemSpace] = [] + empty_spaces: list[FileSystemSpace] = [] + + is_file: bool = True + index: int = 0 + for size in [int(x) for x in list(input)]: + file_or_empty = FileSystemSpace(index, size, len(files)) + index += size + if is_file: + files.append(file_or_empty) + else: + empty_spaces.append(file_or_empty) + is_file = not is_file + + # brute force finding a space to move each file into + for file in reversed(files): + for empty_space in empty_spaces: + # prevent moving files to higher spots in the file system... + if empty_space.start > file.start: + break + + # large enough empty space found + if empty_space.size >= file.size: + file.start = empty_space.start + empty_space.start += file.size + empty_space.size -= file.size + + break + + # print_util(files) + + checksum: int = 0 + for file in files: + for x in range(file.size): + checksum += (file.start + x) * file.file_number + + return checksum + + +def print_util(all_files: list[FileSystemSpace]): + last_index: int = 0 + for file in all_files: + last_index = max(last_index, file.start + file.size) + + fs: list[str] = ["."] * (last_index + 1) + for file in all_files: + for i in range(file.start, file.start + file.size): + fs[i] = str(file.file_number) + + print("".join(fs)) + + +example = """2333133121414131402""" +input = open("input.txt").read().strip() + +print(f"part1 example: {part1(example)} want 1928") +print(f"part1: {part1(input)} want 6200294120911") + +print(f"part2 example: {part2(example)} want 2858") +print(f"part2: {part2(input)} want 6227018762750") diff --git a/2024/day10/day10.py b/2024/day10/day10.py new file mode 100644 index 0000000..396631b --- /dev/null +++ b/2024/day10/day10.py @@ -0,0 +1,99 @@ +def part1(input: str) -> int: + grid: list[list[int, int]] = [] + for line in input.splitlines(): + grid.append([int(x) for x in list(line)]) + # alternative pythonic way... + # grid = [[int(char) for char in line] for line in input.splitlines()] + + ans: int = 0 + for r in range(len(grid)): + for c in range(len(grid[0])): + if grid[r][c] == 0: + ans += len(dfs_backtrack_unique_end_coords(grid, r, c, {(r, c)})) + + return ans + + +# I misread the instructions slightly and went for a "unique paths" algo at first. +# It would be simpler to pass the "reachable 9s coords" set in as an arg and update +# it in the termination case, then take the length of that set in the part1() function. +# This would remove the need to combine the sets which is potentially expensive (and ugly) +def dfs_backtrack_unique_end_coords( + grid: list[list[int, int]], row: int, col: int, visited: set[tuple[int, int]] +) -> set[tuple[int, int]]: + if grid[row][col] == 9: + return {(row, col)} + + all_coords: set[tuple[int, int]] = set() + for diff in [(-1, 0), (1, 0), (0, -1), (0, 1)]: + nextRow = row + diff[0] + nextCol = col + diff[1] + + if not (0 <= nextRow < len(grid) and 0 <= nextCol < len(grid[0])): + continue + if (nextRow, nextCol) in visited: + continue + if grid[nextRow][nextCol] == grid[row][col] + 1: + visited.add((nextRow, nextCol)) + new_coords = dfs_backtrack_unique_end_coords( + grid, nextRow, nextCol, visited + ) + # combines the two sets, see comment above function def + all_coords.update(new_coords) + visited.remove((nextRow, nextCol)) + + return all_coords + + +def part2(input: str) -> int: + grid = [[int(char) for char in line] for line in input.splitlines()] + + ans: int = 0 + for r in range(len(grid)): + for c in range(len(grid[0])): + if grid[r][c] == 0: + ans += dfs_backtrack_unique_paths(grid, r, c, {(r, c)}) + + return ans + + +def dfs_backtrack_unique_paths( + grid: list[list[int, int]], row: int, col: int, visited: set[tuple[int, int]] +) -> int: + if grid[row][col] == 9: + # unique path found + return 1 + + total: int = 0 + for diff in [(-1, 0), (1, 0), (0, -1), (0, 1)]: + nextRow = row + diff[0] + nextCol = col + diff[1] + + if not (0 <= nextRow < len(grid) and 0 <= nextCol < len(grid[0])): + continue + if (nextRow, nextCol) in visited: + continue + if grid[nextRow][nextCol] == grid[row][col] + 1: + visited.add((nextRow, nextCol)) + total += dfs_backtrack_unique_paths(grid, nextRow, nextCol, visited) + visited.remove((nextRow, nextCol)) + + return total + + +example = """89010123 +78121874 +87430965 +96549874 +45678903 +32019012 +01329801 +10456732""" + +input = open("input.txt").read().strip() + +print(f"part1 example: {part1(example)} want 36") +print(f"part1: {part1(input)} want 782") + +print(f"part2 example: {part2(example)} want 81") +print(f"part2 example: {part2(input)} want 1694") diff --git a/2024/day11/day11.py b/2024/day11/day11.py new file mode 100644 index 0000000..db3a51d --- /dev/null +++ b/2024/day11/day11.py @@ -0,0 +1,50 @@ +# If the stone is engraved with the number 0, it is replaced by a stone engraved with the number 1. +# If the stone is engraved with a number that has an even number of digits, it is replaced by two stones. The left half of the digits are engraved on the new left stone, and the right half of the digits are engraved on the new right stone. (The new numbers don't keep extra leading zeroes: 1000 would become stones 10 and 0.) +# If none of the other rules apply, the stone is replaced by a new stone; the old stone's number multiplied by 2024 is engraved on the new stone. + + +# actually modelling this would be a pain in the ass, error-prone, and probably +# not fast enough for part 2 where there will presumably be more blinks +# instead if we only care about the final number of stones, we can just see +# how many stone each original stone splits into, and we can memoize it +def day11(input: str, blinks: int) -> int: + total_stones: int = 0 + memo: dict[tuple[int, int], int] = {} + for stone in input.split(" "): + total_stones += calculate_final_stones_count(stone, blinks, memo) + return total_stones + + +def calculate_final_stones_count(num_as_str: str, blinks_left: int, memo) -> int: + key = (num_as_str, blinks_left) + if key in memo: + return memo[key] + + if blinks_left == 0: + return 1 + + total_stones: int = 0 + if num_as_str == "0": + total_stones += calculate_final_stones_count("1", blinks_left - 1, memo) + elif len(num_as_str) % 2 == 0: + # convert back and forth again to get rid of leading zeroes + left_num = str(int(num_as_str[: len(num_as_str) // 2])) + right_num = str(int(num_as_str[len(num_as_str) // 2 :])) + total_stones += calculate_final_stones_count(left_num, blinks_left - 1, memo) + total_stones += calculate_final_stones_count(right_num, blinks_left - 1, memo) + else: + new_num: int = str(int(num_as_str) * 2024) + total_stones += calculate_final_stones_count(new_num, blinks_left - 1, memo) + + memo[key] = total_stones + + return total_stones + + +example = """125 17""" +input = open("input.txt").read().strip() + +print(f"part1 example: {day11(example, 6)} want 22") +print(f"part1 example: {day11(example, 25)} want 55312") +print(f"part1: {day11(input, 25)} want 189092") +print(f"part2: {day11(input, 75)} want 224869647102559") diff --git a/2024/day12/day12.py b/2024/day12/day12.py new file mode 100644 index 0000000..553104a --- /dev/null +++ b/2024/day12/day12.py @@ -0,0 +1,164 @@ +from collections import defaultdict + + +def day12(input: str, part: int) -> int: + + grid = [list(line) for line in input.splitlines()] + visited: set[tuple[int, int]] = {} + cost: int = 0 + + for r in range(len(grid)): + for c in range(len(grid[0])): + coord = (r, c) + if coord not in visited: + island_coords: set[tuple[int, int]] = set() + + flood_fill_island(grid, r, c, visited, island_coords) + if part == 1: + cost += len(island_coords) * get_perimeter_of_island(island_coords) + elif part == 2: + edge_count: int = get_edge_count_of_island(island_coords) + cost += len(island_coords) * edge_count + else: + raise ("unexpected part") + + return cost + + +# refactored to just populate the entire island_coords set and visited set +# just populates the island_coords so does not need to return anything +def flood_fill_island( + grid: list[list[str]], + row: int, + col: int, + visited: set[tuple[int, int]], + island_coords: set[tuple[int, int]], +): + visited[(row, col)] = True + island_coords.add((row, col)) + + for diff in [(0, -1), (0, 1), (-1, 0), (1, 0)]: + next_row = row + diff[0] + next_col = col + diff[1] + + if 0 <= next_row < len(grid) and 0 <= next_col < len(grid[0]): + if (next_row, next_col) in visited: + continue + # if in range, check if neighbor is same to recurse + if grid[next_row][next_col] == grid[row][col]: + # if does match and unvisited, recurse + flood_fill_island(grid, next_row, next_col, visited, island_coords) + + +# for part1 cost calculation +def get_perimeter_of_island(island_coords: set[tuple[int, int]]) -> int: + perimeter: int = 0 + for coord in island_coords: + for diff in [(0, -1), (0, 1), (-1, 0), (1, 0)]: + next_coord = (coord[0] + diff[0], coord[1] + diff[1]) + if next_coord not in island_coords: + perimeter += 1 + + return perimeter + + +def get_edge_count_of_island(island_coords: set[tuple[int, int]]) -> int: + edges: int = 0 + + map_dir_to_coord: dict[tuple[int, int], set[tuple[int, int]]] = defaultdict(set) + + for coord in island_coords: + for diff in [(0, -1), (0, 1), (-1, 0), (1, 0)]: + # this coord has already been accounted for in an edge + if coord in map_dir_to_coord[diff]: + continue + + next_coord = (coord[0] + diff[0], coord[1] + diff[1]) + # not in the island means it is bordering an edge... + if next_coord not in island_coords: + # collect all coords that make up this same edge, basically we need to go perpendicular to diff + collect_all_coords_on_edge(island_coords, coord, diff, map_dir_to_coord) + edges += 1 + + return edges + + +perpendicular_dirs: dict[tuple[int, int], list[tuple[int, int]]] = { + (0, -1): [(-1, 0), (1, 0)], + (0, 1): [(-1, 0), (1, 0)], + (-1, 0): [(0, -1), (0, 1)], + (1, 0): [(0, -1), (0, 1)], +} + + +def collect_all_coords_on_edge( + island_coords: set[tuple[int, int]], + coord: tuple[int, int], + empty_dir_diff: tuple[int, int], + map_dir_to_coord: dict[tuple[int, int], set[tuple[int, int]]], +): + # mark self + map_dir_to_coord[empty_dir_diff].add(coord) + + for perp in perpendicular_dirs[empty_dir_diff]: + next_coord = (coord[0] + perp[0], coord[1] + perp[1]) + # we're collecting the entire connected edge that is facing a single direction, + # so stop checking if we're "off" the island + # do not need to check if we're inside the grid because we can just leverage island_coords + if next_coord not in island_coords: + continue + # if already visited for facing this direction, we can also skip + if next_coord in map_dir_to_coord[empty_dir_diff]: + continue + + # continue if next_coord is not a contiguous part of the edge + if (next_coord[0] + empty_dir_diff[0], next_coord[1] + empty_dir_diff[1]) in island_coords: + continue + + # need to continue exploring recursively + collect_all_coords_on_edge( + island_coords, next_coord, empty_dir_diff, map_dir_to_coord + ) + + +small_example = """AAAA +BBCD +BBCC +EEEC""" + + +example = """RRRRIICCFF +RRRRIICCCF +VVRRRCCFFF +VVRCCCJFFF +VVVVCJJCFE +VVIVCCJJEE +VVIIICJJEE +MIIIIIJJEE +MIIISIJEEE +MMMISSJEEE""" + +part2_example = """AAAAAA +AAABBA +AAABBA +ABBAAA +ABBAAA +AAAAAA""" + +input = open("input.txt").read().strip() + +print(f"part1 small_example: {day12(small_example, 1)} want 140") +print(f"part1 example: {day12(example, 1)} want 1930") +print(f"part1: {day12(input, 1)} want 1473408") + +print(f"part2 small_example: {day12(small_example, 2)} want 80") +# this one helped debug the disjointed edges bug (second and fourth rows pointing east) +print(f"part2 example: {day12("""EEEEE +EXXXX +EEEEE +EXXXX +EEEEE""", 2)} want 236") +print(f"part2 part2_example: {day12(part2_example, 2)} want 368") +print(f"part2 example: {day12(example, 2)} want 1206") +print(f"part2: {day12(input, 2)} want 886364") + diff --git a/2024/day13/day13.py b/2024/day13/day13.py new file mode 100644 index 0000000..52d70fd --- /dev/null +++ b/2024/day13/day13.py @@ -0,0 +1,62 @@ +import re + + +def part1(input: str, part: int) -> int: + ans: int = 0 + + for machine in input.split("\n\n"): + lines = machine.split("\n") + a_parts: list[str] = re.findall(r"\d+", lines[0]) + Ax, Ay = int(a_parts[0]), int(a_parts[1]) + + b_parts: list[str] = re.findall(r"\d+", lines[1]) + Bx, By = int(b_parts[0]), int(b_parts[1]) + + prize_parts: list[str] = re.findall(r"\d+", lines[2]) + Px, Py = int(prize_parts[0]), int(prize_parts[1]) + + if part == 2: + Px += 10000000000000 + Py += 10000000000000 + + # Ax * a + Bx * b = Px + # Ay * a + By * b = Py + # Solve for a and b... + # a = (Px - Bx * b) / Ax + # b * (By * Ax - Ay * Bx) = Py * Ax - Ay * Px + b = (Py * Ax - Ay * Px) / (By * Ax - Ay * Bx) + a = (Px - Bx * b) / Ax + + if b % 1 == 0 and a % 1 == 0: + ans += 3 * a + b + + return int(ans) + + +# A 3, B 1 +# moves right along X, forward along Y + +example = """Button A: X+94, Y+34 +Button B: X+22, Y+67 +Prize: X=8400, Y=5400 + +Button A: X+26, Y+66 +Button B: X+67, Y+21 +Prize: X=12748, Y=12176 + +Button A: X+17, Y+86 +Button B: X+84, Y+37 +Prize: X=7870, Y=6450 + +Button A: X+69, Y+23 +Button B: X+27, Y+71 +Prize: X=18641, Y=10279""" + +input = open("input.txt").read().strip() + +print(f"part1 example: {part1(example, 1)} want 480") +print(f"part1: {part1(input, 1)} want 28059") + +# don't think this result was given in the prompt +print(f"part1 example: {part1(example, 2)} want 875318608908") +print(f"part1: {part1(input, 2)} want 102255878088512") diff --git a/2024/day14/day14.py b/2024/day14/day14.py new file mode 100644 index 0000000..1825150 --- /dev/null +++ b/2024/day14/day14.py @@ -0,0 +1,108 @@ +import re + + +def part1(input: str, wide: int, tall: int, seconds: int) -> int: + # don't need to model this exactly right? just equate a line, mod by grid size... + # i guess for 100 steps it would've been better to just model it... + + quad_counts: list[int] = [0] * 4 + + for line in input.splitlines(): + nums = re.findall(r"-?\d+", line) + assert len(nums) == 4 + + x = int(nums[0]) + int(nums[2]) * seconds + y = int(nums[1]) + int(nums[3]) * seconds + + x %= tall + y %= wide + + if x < tall // 2 and y < wide // 2: + # print("top left") + quad_counts[0] += 1 + elif x < tall // 2 and y > wide // 2: + # print("top right") + quad_counts[1] += 1 + elif x > tall // 2 and y < wide // 2: + # print("bottom left") + quad_counts[2] += 1 + elif x > tall // 2 and y > wide // 2: + # print("bottom right") + quad_counts[3] += 1 + # else: + # print("on mid line") + + # multiply robot count in each quadrant + # does not include robots on mid-lines + return quad_counts[0] * quad_counts[1] * quad_counts[2] * quad_counts[3] + + +def part2(input: str, wide: int, tall: int) -> int: + # i guess for 100 steps it would've been better to just model it... + # then i could have reused it for part 2... + + robots: list[list[int]] = [] + for line in input.splitlines(): + nums = re.findall(r"-?\d+", line) + assert len(nums) == 4 + robots.append([int(x) for x in nums]) + + for s in range(10_000): + + neighbors: set[tuple[int, int]] = set() + matched: set[tuple[int, int]] = set() + for robot in robots: + robot[0] += robot[2] + robot[1] += robot[3] + + robot[0] %= tall + robot[1] %= wide + + # track how many neighboring robots have been placed so far + # when the number is high enough we probably have some image of a tree + coord = (robot[0], robot[1]) + if coord in neighbors: + matched.add(coord) + for dx in [-1, 0, 1]: + for dy in [-1, 0, 1]: + neighbors.add((coord[0] + dx, coord[1] + dy)) + + if len(matched) >= 250: + print_grid(robots, wide, tall) + # print(len(matched)) + return s + 1 + + raise Exception("should return from loop") + + +def print_grid(robots: list[list[int]], wide: int, tall: int): + grid: list[list[str]] = [] + for _ in range(0, tall): + grid.append([" "] * wide) + + for robot in robots: + grid[robot[0]][robot[1]] = "X" + + for line in grid: + print("".join(line)) + + +example = """p=0,4 v=3,-3 +p=6,3 v=-1,-3 +p=10,3 v=-1,2 +p=2,0 v=2,-1 +p=0,0 v=1,3 +p=3,0 v=-2,-2 +p=7,6 v=-1,-3 +p=3,0 v=-1,-2 +p=9,3 v=2,3 +p=7,3 v=-1,2 +p=2,4 v=2,-3 +p=9,5 v=-3,-3""" + +input = open("input.txt").read().strip() + +print(f"part1 example: {part1(example, 7, 11, 100)} want 12") +print(f"part1 example: {part1(input, 103, 101, 100)} want 218965032") + +print(f"part2: {part2(input, 103, 101)} want 7037") diff --git a/2024/day15/day15.py b/2024/day15/day15.py new file mode 100644 index 0000000..97c11dd --- /dev/null +++ b/2024/day15/day15.py @@ -0,0 +1,214 @@ +diff_map: dict[str, tuple[int, int]] = { + "^": (-1, 0), + "v": (1, 0), + "<": (0, -1), + ">": (0, 1), +} + + +def part1(input: str) -> int: + input_parts = input.split("\n\n") + grid = [list(line) for line in input_parts[0].splitlines()] + + row: int = 0 + col: int = 0 + + for r in range(len(grid)): + for c in range(len(grid[0])): + if grid[r][c] == "@": + row = r + col = c + + instructions = "".join(input_parts[1].splitlines()) + for inst in instructions: + diff = diff_map[inst] + next_row, next_col = row + diff[0], col + diff[1] + + match grid[next_row][next_col]: + case "#": + # blocked + continue + case ".": + grid[row][col] = "." + grid[next_row][next_col] = "@" + row, col = next_row, next_col + case "O": + # attempt push, keep moving in direction of diff until a . or # is hit... + not_obstacle_row, not_obstacle_col = next_row, next_col + while grid[not_obstacle_row][not_obstacle_col] == "O": + not_obstacle_row += diff[0] + not_obstacle_col += diff[1] + + # if it's a wall "#", nothing moves, so only check for empty spaces "." + if grid[not_obstacle_row][not_obstacle_col] == ".": + grid[not_obstacle_row][not_obstacle_col] = "O" + grid[next_row][next_col] = "@" + grid[row][col] = "." + row, col = next_row, next_col + case _: + raise Exception("unhandled grid type: ", grid[next_row][next_col]) + + # print("\n".join(["".join(row) for row in grid])) + + # 100 times its distance from the top edge of the map plus its distance from the left edge of the map + # 0-indexed so same as indices in 2D array + gps_sum: int = 0 + for r in range(len(grid)): + for c in range(len(grid[0])): + if grid[r][c] == "O": + gps_sum += r * 100 + c + + return gps_sum + + +def part2(input: str) -> int: + input_parts = input.split("\n\n") + + input_parts[0] = input_parts[0].replace("O", "[]") + input_parts[0] = input_parts[0].replace(".", "..") + input_parts[0] = input_parts[0].replace("#", "##") + input_parts[0] = input_parts[0].replace("@", "@.") + + grid = [list(line) for line in input_parts[0].splitlines()] + + row: int = 0 + col: int = 0 + + for r in range(len(grid)): + for c in range(len(grid[0])): + if grid[r][c] == "@": + row = r + col = c + + # print("\n".join(["".join(row) for row in grid])) + + instructions = "".join(input_parts[1].splitlines()) + for inst in instructions: + # first see what's in the next space we want to move into... + diff = diff_map[inst] + next_row, next_col = row + diff[0], col + diff[1] + match grid[next_row][next_col]: + case "#": + continue + case ".": + grid[row][col] = "." + row += diff[0] + col += diff[1] + grid[row][col] = "@" + case "[" | "]": + # handle left and right separately because they only push one row of boxes + # at this point row == next_row + if inst in "<>": + # copy-pasta from part 1 + not_obstacle_col = next_col + while grid[next_row][not_obstacle_col] in "[]": + not_obstacle_col += diff[1] + + # if it's a wall "#", nothing moves, so only check for empty spaces "." + # move everything over towards the diff direction + if grid[next_row][not_obstacle_col] == ".": + for c in range(not_obstacle_col, col, -1 * diff[1]): + grid[row][c] = grid[row][c - diff[1]] + + # move robot.. + grid[next_row][next_col] = "@" + grid[row][col] = "." + row, col = next_row, next_col + else: + # push boxes up or down, use a stack to maintain the reverse order of boxes that + # MAY get moved + coords_to_check: list[tuple[int, int]] = [ + (row, col), + ] + + to_check_index: int = 0 + is_blocked: bool = False + while to_check_index < len(coords_to_check): + front = coords_to_check[to_check_index] + to_check_index += 1 + + # variable masking is no bueno, but i guess it's fine here + next_row, next_col = front[0] + diff[0], front[1] + diff[1] + next_value = grid[next_row][next_col] + if next_value == "#": + is_blocked = True + break + if next_value == "[": + coords_to_check.append((next_row, next_col)) + coords_to_check.append((next_row, next_col + 1)) + if next_value == "]": + coords_to_check.append((next_row, next_col)) + coords_to_check.append((next_row, next_col - 1)) + # if "." then do nothing... no need to check + + # move everything towards diff if nothing was blocked + # in reverse order to avoid overwrites + if not is_blocked: + # didn't prevent duplicate adds in the "checking" stage, so just no-op them here + moved_set: set[tuple[int, int]] = set() + for coord in reversed(coords_to_check): + if coord in moved_set: + continue + moved_set.add(coord) + + next_row, next_col = coord[0] + diff[0], coord[1] + diff[1] + grid[next_row][next_col], grid[coord[0]][coord[1]] = ( + grid[coord[0]][coord[1]], + grid[next_row][next_col], + ) + row, col = next_row, next_col + + case _: + raise Exception("unexpected grid value: ", grid[next_row][next_col]) + # print("\n".join(["".join(row) for row in grid])) + + gps_sum: int = 0 + for r in range(len(grid)): + for c in range(len(grid[0])): + if grid[r][c] == "[": + gps_sum += r * 100 + c + + return gps_sum + + +small_example = """######## +#..O.O.# +##@.O..# +#...O..# +#.#.O..# +#...O..# +#......# +######## + +<^^>>>vv>v<<""" + +example = """########## +#..O..O.O# +#......O.# +#.OO..O.O# +#..O@..O.# +#O#..O...# +#O..O..O.# +#.OO.O.OO# +#....O...# +########## + +^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ +vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< +<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ +^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< +^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ +<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> +^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< +v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^""" + +input = open("input.txt").read().strip() + +print(f"part1 small_example: {part1(small_example)} want 2028") +print(f"part1 example: {part1(example)} want 10092") +print(f"part1: {part1(input)} want 1413675") + +print(f"part2 example: {part2(example)} want 9021") +print(f"part2: {part2(input)} want 1399772") diff --git a/2024/day16/day16.py b/2024/day16/day16.py new file mode 100644 index 0000000..cac73bd --- /dev/null +++ b/2024/day16/day16.py @@ -0,0 +1,135 @@ +import heapq + + +diffs: list[tuple[int, int]] = [ + (0, 1), # start at index 0 facing "east"/right + (-1, 0), # up + (0, -1), # left + (1, 0), # down +] + + +def day16(input: str, part: int) -> int: + grid = [list(line) for line in input.splitlines()] + start_row: int = 0 + start_col: int = 0 + for r in range(len(grid)): + for c in range(len(grid[0])): + if grid[r][c] == "S": + start_row = r + start_col = c + + # i think i'd rather write a struct in go... these tuples are easy to make + # but annoying to maintain in my head... + # score, row, col, dir_index + node: tuple[int, int, int, int, set[tuple[int, int]]] = ( + int(0), + start_row, + start_col, + int(0), + set(), + ) + min_heap = [node] + heapq.heapify(min_heap) + + coord_to_min_score: dict[tuple[int, int, int], int] = {} + + best_score: int = 1_000_000 # big enough to not interfere with actual input answer + final_path_coords: set[tuple[int, int]] = set() + + while len(min_heap) > 0: + popped = heapq.heappop(min_heap) + + # part2 exit once best_score is set down to the actual min value + if popped[0] > best_score: + continue + + # row, col, dir_index + coord = (popped[1], popped[2], popped[3]) + if coord in coord_to_min_score: + prev_score = coord_to_min_score[coord] + # if previous score at this coord and direction is better then continue + if popped[0] > prev_score: + continue + + # update best score to reach this coord + coord_to_min_score[coord] = popped[0] + + # end reached + if grid[popped[1]][popped[2]] == "E": + best_score = popped[0] + final_path_coords |= popped[4] + final_path_coords.add((popped[1], popped[2])) + continue + + # in same direction + diff = diffs[popped[3]] + next = ( + popped[0] + 1, + popped[1] + diff[0], + popped[2] + diff[1], + popped[3], + popped[4] | {(popped[1], popped[2])}, + ) + if grid[next[1]][next[2]] in ".E": + heapq.heappush(min_heap, next) + + # 90 deg turns + heapq.heappush( + min_heap, + (popped[0] + 1000, popped[1], popped[2], (popped[3] + 1) % 4, popped[4]), + ) + heapq.heappush( + min_heap, + (popped[0] + 1000, popped[1], popped[2], (popped[3] - 1) % 4, popped[4]), + ) + + if part == 1: + return best_score + + return len(final_path_coords) + + +example = """############### +#.......#....E# +#.#.###.#.###.# +#.....#.#...#.# +#.###.#####.#.# +#.#.#.......#.# +#.#.#####.###.# +#...........#.# +###.#.#####.#.# +#...#.....#.#.# +#.#.#.###.#.#.# +#.....#...#.#.# +#.###.#.#.#.#.# +#S..#.....#...# +###############""" + +example2 = """################# +#...#...#...#..E# +#.#.#.#.#.#.#.#.# +#.#.#.#...#...#.# +#.#.#.#.###.#.#.# +#...#.#.#.....#.# +#.#.#.#.#.#####.# +#.#...#.#.#.....# +#.#.#####.#.###.# +#.#.#.......#...# +#.#.###.#####.### +#.#.#...#.....#.# +#.#.#.#####.###.# +#.#.#.........#.# +#.#.#.#########.# +#S#.............# +#################""" + +input = open("input.txt").read().strip() + +print(f"day16 example: {day16(example, 1)} want 7036") +print(f"day16 example2: {day16(example2,1)} want 11048") +print(f"day16 actual: {day16(input,1)} want 83444") + +print(f"part2 example: {day16(example,2)} want 45") +print(f"part2 example2: {day16(example2,2)} want 64") +print(f"part2 actual: {day16(input,2)} want 483") diff --git a/2024/day17/day17.py b/2024/day17/day17.py new file mode 100644 index 0000000..6f502e5 --- /dev/null +++ b/2024/day17/day17.py @@ -0,0 +1,131 @@ +import re + + +def part1(input: str) -> str: + matches = re.findall(r"\d+", input) + + reg_a: int = int(matches[0]) + reg_b: int = int(matches[1]) + reg_c: int = int(matches[2]) + + program: list[int] = [int(x) for x in matches[3:]] + + out = run(program, reg_a, reg_b, reg_c) + return ",".join([str(v) for v in out]) + + +def run(program: list[int], reg_a: int, reg_b: int, reg_c: int) -> list[int]: + def get_combo_operand(combo: int) -> int: + if 0 <= combo <= 3: + return combo + if combo == 4: + return reg_a + if combo == 5: + return reg_b + if combo == 6: + return reg_c + raise Exception("unexpected combo value: ", combo) + + i: int = 0 + out: list[int] = [] + while i < len(program): + opcode = program[i] + operand = program[i + 1] + match opcode: + case 0: + reg_a = reg_a // (2 ** get_combo_operand(operand)) + i += 2 + case 1: + reg_b = reg_b ^ operand + i += 2 + case 2: + reg_b = get_combo_operand(operand) % 8 + i += 2 + case 3: + if reg_a != 0: + i = operand + else: + i += 2 + case 4: + reg_b = reg_b ^ reg_c + i += 2 + case 5: + out.append(get_combo_operand(operand) % 8) + i += 2 + case 6: + reg_b = reg_a // (2 ** get_combo_operand(operand)) + i += 2 + case 7: + reg_c = reg_a // (2 ** get_combo_operand(operand)) + i += 2 + case _: + raise Exception("unhandled opcode", opcode) + + return out + + +# only works on actual input +def part2() -> int: + matches = re.findall(r"\d+", input) + program: list[int] = [int(x) for x in matches[3:]] + + # generate each element of the program one at a time, starting at the end + output = str + reg_as: list[int] = [] + for i in range(1, 8): + output = run_optimized(i) + if output == program[-len(output) :]: + reg_as.append(i) + + digit_count = 1 + while digit_count < 16: + next_reg_As: list[int] = [] + for a in reg_as: + a *= 8 + for i in range(8): + output = run_optimized(a + i) + if output == program[-len(output) :]: + next_reg_As.append(a + i) + reg_as = next_reg_As + digit_count += 1 + + # first reg_as will be smallest + return reg_as[0] + + +def run_optimized(a: int) -> list[int]: + b: int = 0 + output: list[int] = [] + while a != 0: + # 2,4, 1,1, 7,5, 4,0, 0,3, 1,6, 5,5, 3,0 + # pen and paper "algebra" + b = ((a % 8) ^ 1) ^ (a // (2 ** ((a % 8) ^ 1))) ^ 6 + output.append(b % 8) + a = a // 8 + return output + + +example = """Register A: 729 +Register B: 0 +Register C: 0 + +Program: 0,1,5,4,3,0""" + +input = open("input.txt").read().strip() + +print(f"part1 example: {part1(example)} want '4,6,3,5,6,3,5,2,1,0'") +print(f"part1 actual: {part1(input)} want '1,6,3,6,5,6,5,1,7'") + +print( + f"optimized {",".join([str(x) for x in run_optimized(30899381)])} want {part1(input)}" +) + + +example_part2 = """Register A: 2024 +Register B: 0 +Register C: 0 + +Program: 0,3,5,4,3,0""" + +# print(f"part2 example: {part1(example_part2, 2)} want 117440") +print(f"part2 actual: {part2()} want 247839653009594") From eae29df8974cee2fb8b2a75e4521a712827b4b38 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Sat, 28 Dec 2024 00:27:17 -0500 Subject: [PATCH 50/57] day18 --- 2024/day18/day18.py | 112 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 2024/day18/day18.py diff --git a/2024/day18/day18.py b/2024/day18/day18.py new file mode 100644 index 0000000..c17e1ef --- /dev/null +++ b/2024/day18/day18.py @@ -0,0 +1,112 @@ +def part1(input: str, grid_size: int, fallen_bytes: int) -> int: + # simulate fallen_bytes + # path find from top left to bottom right + + grid = [["." for _ in range(grid_size)] for _ in range(grid_size)] + + for line in input.splitlines()[:fallen_bytes]: + parts = line.split(",") + col = int(parts[0]) + row = int(parts[1]) + grid[row][col] = "#" + + queue: list[tuple[int, int, int]] = [(0, 0, 0)] + visited: set[tuple[int, int]] = set() + while len(queue) > 0: + front = queue.pop(0) + + if front[:2] in visited: + continue + visited.add(front[:2]) + + if front[0] == grid_size - 1 and front[1] == grid_size - 1: + return front[2] + for diff in [(0, 1), (1, 0), (0, -1), (-1, 0)]: + next_row = front[0] + diff[0] + next_col = front[1] + diff[1] + if 0 <= next_row < grid_size and 0 <= next_col < grid_size: + if grid[next_row][next_col] != "#": + queue.append((next_row, next_col, front[2] + 1)) + + raise Exception("should return from loop") + + +def part2(input: str, grid_size: int) -> str: + grid = [["." for _ in range(grid_size)] for _ in range(grid_size)] + + # drop all fallen bytes, then remove them selectively and attempt to continue + # path finding from there? + fallen_bytes: list[tuple[int, int]] = [] + for line in input.splitlines(): + parts = line.split(",") + col = int(parts[0]) + row = int(parts[1]) + grid[row][col] = "#" + fallen_bytes.append((row, col)) + + # get set of all reachable coords from the starting point + reachable: set[tuple[int, int]] = {(0, 0)} + flood_fill_from(grid, (0, 0), reachable) + + # iterate in reverse through fallen bytes and if the byte is adjacent to a reachable + # cell, then try to flood fill from the adjacent cell's neighbors + for fallen in reversed(fallen_bytes): + grid[fallen[0]][fallen[1]] = "." + for diff in [(0, 1), (1, 0), (0, -1), (-1, 0)]: + next = (fallen[0] + diff[0], fallen[1] + diff[1]) + if next in reachable: + if flood_fill_from(grid, next, reachable): + return f"{fallen[1]},{fallen[0]}" + + raise Exception("should return from loop") + + +def flood_fill_from( + grid: list[list[str]], coord: tuple[int, int], reachable: set[tuple[int, int]] +) -> bool: + for diff in [(0, 1), (1, 0), (0, -1), (-1, 0)]: + next_row = coord[0] + diff[0] + next_col = coord[1] + diff[1] + if (next_row, next_col) not in reachable: + if 0 <= next_row < len(grid) and 0 <= next_col < len(grid): + if grid[next_row][next_col] != "#": + reachable.add((next_row, next_col)) + flood_fill_from(grid, (next_row, next_col), reachable) + + return (len(grid) - 1, len(grid) - 1) in reachable + + +# col, row (x, y) +example = """5,4 +4,2 +4,5 +3,0 +2,1 +6,3 +2,4 +1,5 +0,6 +3,3 +2,6 +5,1 +1,2 +5,5 +2,5 +6,5 +1,4 +0,4 +6,4 +1,1 +6,1 +1,0 +0,5 +1,6 +2,0""" + +input = open("input.txt").read().strip() + +print(f"part1 example: {part1(example, 6 + 1, 12)} want 22") +print(f"part1 actual: {part1(input, 70 + 1, 1024)} want 298") + +print(f"part2 example: {part2(example, 6 + 1)} want '6,1'") +print(f"part2 actual: {part2(input, 70 + 1)} want 52,32") From c92f4d73fc2e19e7b4f4903e4c529020cbe9098c Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Tue, 31 Dec 2024 14:53:29 -0500 Subject: [PATCH 51/57] day 24 hurts my brain, thanks reddit --- 2024/day19/day19.py | 87 +++++++++++++++ 2024/day20/day20.py | 170 +++++++++++++++++++++++++++++ 2024/day22/day22.py | 58 ++++++++++ 2024/day23/day23.py | 106 ++++++++++++++++++ 2024/day24/day24.py | 250 ++++++++++++++++++++++++++++++++++++++++++ 2024/day24/main.go | 259 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 930 insertions(+) create mode 100644 2024/day19/day19.py create mode 100644 2024/day20/day20.py create mode 100644 2024/day22/day22.py create mode 100644 2024/day23/day23.py create mode 100644 2024/day24/day24.py create mode 100644 2024/day24/main.go diff --git a/2024/day19/day19.py b/2024/day19/day19.py new file mode 100644 index 0000000..2caebc0 --- /dev/null +++ b/2024/day19/day19.py @@ -0,0 +1,87 @@ +from dataclasses import dataclass +from typing import Self + + +@dataclass +class TrieNode: + char: str + is_terminator: bool + children: dict[str, Self] + + +def day19(input: str, part: int) -> int: + # trie data structure works nicely... but is it necessary for part 2... + input_lines = input.splitlines() + + root = TrieNode("", False, {}) + for towel in input_lines[0].split(", "): + iter = root + for i, char in enumerate(towel): + if char not in iter.children: + iter.children[char] = TrieNode(char, i == len(towel) - 1, {}) + iter = iter.children[char] + # update for matching towels that are smaller than a pervious one + # e.g. "towel" then "tow" needs to update the "w" node + iter.is_terminator |= i == len(towel) - 1 + + possible_towels: int = 0 + total: int = 0 + + memo: dict[str, int] = {} + for maybe_towel in input_lines[2:]: + combos = is_possible_towel(root, maybe_towel, memo) + if combos > 0: + possible_towels += 1 + total += combos + + if part == 1: + return possible_towels + + return total + + +# memo optimization added for part 2 +def is_possible_towel(trie: TrieNode, towel: str, memo: dict[str, int]) -> int: + if towel == "": + return 1 + if towel in memo: + return memo[towel] + + iter = trie + total: int = 0 + for i, char in enumerate(towel): + if char not in iter.children: + break + + iter = iter.children[char] + + # if iterated to a terminator, we can restart recursively and if that + # "works" (aka returns positive number of possible combos), then we can + # sum the combos and then continue along this for loop to account for + # larger towels that do not end at this index + if iter.is_terminator: + total += is_possible_towel(trie, towel[i + 1 :], memo) + + memo[towel] = total + + return total + + +example = """r, wr, b, g, bwu, rb, gb, br + +brwrr +bggr +gbbr +rrbgbr +ubwu +bwurrg +brgr +bbrgwb""" + +input = open("input.txt").read().strip() + +print(f"day19 example: {day19(example, 1)}, want 6") +print(f"day19 actual: {day19(input, 1)}, want 216") + +print(f"part2 example: {day19(example ,2)}, want 16") +print(f"part2 actual: {day19(input ,2)}, want 603191454138773") diff --git a/2024/day20/day20.py b/2024/day20/day20.py new file mode 100644 index 0000000..b625c76 --- /dev/null +++ b/2024/day20/day20.py @@ -0,0 +1,170 @@ +from collections import defaultdict + + +def day20(input: str, cheats: int) -> dict[int, int]: + # get minimum without cheats, do this backwards by propagating from E to S + # to populate a grid of the distance from each cell to E + grid: list[list[str]] = [list(line) for line in input.splitlines()] + + row: int = 0 + col: int = 0 + + for r in range(len(grid)): + for c in range(len(grid[0])): + if grid[r][c] == "E": + row = r + col = c + + # default value set to 100_000 which is bigger than entire grid's area so + # walls will not be usable as paths to finish + dp_steps_to_end: list[list[int]] = [ + [100_000 for _ in range(len(grid[0]))] for _ in range(len(grid)) + ] + + diffs = [(0, 1), (0, -1), (-1, 0), (1, 0)] + queue: list[tuple[int, int, int]] = [(row, col, 0)] + visited: set[tuple[int, int]] = set() + while len(queue) > 0: + row, col, steps = queue.pop(0) + dp_steps_to_end[row][col] = steps + visited.add((row, col)) + + for diff in diffs: + next_row, next_col = row + diff[0], col + diff[1] + if 0 <= next_row < len(grid) and 0 <= next_col < len(grid[0]): + if grid[next_row][next_col] != "#": + if (next_row, next_col) not in visited: + queue.append((next_row, next_col, steps + 1)) + + # then starting from S, BFS but from each cell, view all cells cells away + # if it's a valid shortcut, record it + + second_saved_freqs: dict[int, int] = defaultdict(int) + + reachable_diffs = get_reachable_coords_within_x_steps(cheats) + + # visited contains coords of the entire path, so just use that... + for path_coord in visited: + r, c = path_coord + for diff in reachable_diffs: + rr = path_coord[0] + diff[0] + cc = path_coord[1] + diff[1] + + if 0 <= rr < len(grid) and 0 <= cc < len(grid[0]): + if grid[rr][cc] != "#": + savings = dp_steps_to_end[r][c] - ( + dp_steps_to_end[rr][cc] + abs(diff[0]) + abs(diff[1]) + ) + if savings > 0: + second_saved_freqs[savings] += 1 + + return second_saved_freqs + + +# helper function to get all coords that are reachable within allotted cheats +def get_reachable_coords_within_x_steps(x: int) -> list[tuple[int, int]]: + coords_map: dict[tuple[int, int], bool] = {} + + queue: list[tuple[int, int]] = [(0, 0)] + for _ in range(x): + next_queue: list[tuple[int, int]] = [] + while len(queue) > 0: + front = queue.pop(0) + for diff in [(0, -1), (0, 1), (-1, 0), (1, 0)]: + coord = (front[0] + diff[0], front[1] + diff[1]) + if coord not in coords_map: + coords_map[coord] = True + next_queue.append(coord) + queue = next_queue + + return list(coords_map.keys()) + + +example = """############### +#...#...#.....# +#.#.#.#.#.###.# +#S#...#.#.#...# +#######.#.#.### +#######.#.#...# +#######.#.###.# +###..E#...#...# +###.#######.### +#...###...#...# +#.#####.#.###.# +#.#...#.#.#...# +#.#.#.#.#.#.### +#...#...#...### +###############""" + +# python is weird and we can use == to compare 2 dicts for the same keys and values... +example_seconds_saved_to_freq: dict[int, int] = { + 2: 14, + 4: 14, + 6: 2, + 8: 4, + 10: 2, + 12: 3, + 20: 1, + 36: 1, + 38: 1, + 40: 1, + 64: 1, +} + +part1_example_result = day20(example, 2) +print( + f"part1 example: {part1_example_result == example_seconds_saved_to_freq} want True" +) +if part1_example_result != example_seconds_saved_to_freq: + print( + f"\tDEBUG part1 example got: {part1_example_result} want {(example_seconds_saved_to_freq)}" + ) + +input = open("input.txt").read().strip() + + +def sum_keys_over_x(d: dict[int, int], x: int) -> int: + total: int = 0 + for k, v in d.items(): + if k >= x: + total += v + return total + + +part1_result = day20(input, 2) + +print( + f"part1 actual: {sum_keys_over_x(part1_result, 100)} cheats save 100+ picosecs want 1422" +) + +part2_example_result: dict[int, int] = { + 50: 32, + 52: 31, + 54: 29, + 56: 39, + 58: 25, + 60: 23, + 62: 20, + 64: 19, + 66: 12, + 68: 14, + 70: 12, + 72: 22, + 74: 4, + 76: 3, +} + +part2_result = day20(example, 20) +part2_result_50 = {} +for k, v in part2_example_result.items(): + if k >= 50: + part2_result_50[k] = v + +print(f"part2 example: {part2_result_50 == part2_example_result} want True") +if part2_result_50 != part2_example_result: + print( + f"DEBUG part2 example 50+ saved: {part2_result_50} want {part2_example_result}" + ) + +part2_result = day20(input, 20) +print(f"part2 actual: {sum_keys_over_x(part2_result, 100)} want 1009299") diff --git a/2024/day22/day22.py b/2024/day22/day22.py new file mode 100644 index 0000000..e2cbf09 --- /dev/null +++ b/2024/day22/day22.py @@ -0,0 +1,58 @@ +from collections import defaultdict + + +def day22(input: str, part: int) -> int: + total: int = 0 + + last_four_diffs_to_price: dict[tuple[int, int, int, int], int] = defaultdict(int) + + for line in input.splitlines(): + nums: list[int] = [int(line)] + diffs: list[int] = [] + + seen_last_fours: set[tuple[int, int, int, int]] = set() + for _ in range(2000): + nums.append(get_next_secret_number(nums[-1])) + diff = nums[-1] % 10 - nums[-2] % 10 + diffs.append(diff) + total += nums[-1] + + for i in range(3, len(diffs)): + last_four_diffs = (diffs[i - 3], diffs[i - 2], diffs[i - 1], diffs[i]) + # only count the first time we've seen this set of diffs, aka only sell once + if last_four_diffs in seen_last_fours: + continue + seen_last_fours.add(last_four_diffs) + + last_four_diffs_to_price[last_four_diffs] += nums[i + 1] % 10 + + if part == 1: + return total + + most_bananas = max(last_four_diffs_to_price.values()) + return most_bananas + + +def get_next_secret_number(num: int) -> int: + num = (num ^ (num * 64)) % 16777216 + num = (num ^ (num // 32)) % 16777216 + num = (num ^ (num * 2048)) % 16777216 + return num + + +example = """1 +10 +100 +2024""" + +input = open("input.txt").read().strip() + +print(f"part1 example: {day22(example, 1)} want 37327623") +print(f"part1 actual: {day22(input, 1)} want 17612566393") + +example_part_2 = """1 +2 +3 +2024""" +print(f"part2 example: {day22(example_part_2, 2)} want 23") +print(f"part2 actual: {day22(input, 2)} want 1968") diff --git a/2024/day23/day23.py b/2024/day23/day23.py new file mode 100644 index 0000000..1faa1e5 --- /dev/null +++ b/2024/day23/day23.py @@ -0,0 +1,106 @@ +from collections import defaultdict + + +def part1(input: str) -> int: + graph: dict[str, set[str]] = defaultdict(set) + for line in input.splitlines(): + parts = line.split("-") + graph[parts[0]].add(parts[1]) + graph[parts[1]].add(parts[0]) + + ans_groups: set[str] = set() + for node in graph: + if node[0] != "t": + continue + neighbors = graph[node] + for neighbor in neighbors: + for neighbor2 in neighbors: + if neighbor == neighbor2: + continue + if neighbor2 in graph[neighbor]: + group: list[str] = [node, neighbor, neighbor2] + group.sort() + + ans_groups.add(",".join(group)) + + return len(ans_groups) + + +def part2(input: str) -> str: + graph: dict[str, set[str]] = defaultdict(set) + for line in input.splitlines(): + parts = line.split("-") + graph[parts[0]].add(parts[1]) + graph[parts[1]].add(parts[0]) + + seen: set[str] = set() + largest_group: set[str] = set() + for node in graph: + if node in seen: + continue + seen.add(node) + + group: set[str] = {node} + for neighbor in graph[node]: + if check_group_against_new_node(group, graph, neighbor): + group.add(neighbor) + seen.add(neighbor) + + if len(group) > len(largest_group): + largest_group = group + + final_group: list[str] = list(largest_group) + final_group.sort() + return ",".join(final_group) + + +def check_group_against_new_node( + group: set[str], graph: dict[str, set[str]], node_to_add: str +) -> bool: + for node in group: + if node not in graph[node_to_add]: + return False + + return True + + +example = """kh-tc +qp-kh +de-cg +ka-co +yn-aq +qp-ub +cg-tb +vc-aq +tb-ka +wh-tc +yn-cg +kh-ub +ta-co +de-co +tc-td +tb-wq +wh-td +ta-ka +td-qp +aq-cg +wq-ub +ub-vc +de-ta +wq-aq +wq-vc +wh-yn +ka-de +kh-ta +co-tc +wh-qp +tb-vc +td-yn""" + +input = open("input.txt").read().strip() + +print(f"part1 example: {part1(example)} want 7") +print(f"part1 actual: {part1(input)} want 1485") + +print(f"part2 example: {part2(example)} want co,de,ka,ta") +print(f"part2 actual: {part2(input)} want cc,dz,ea,hj,if,it,kf,qo,sk,ug,ut,uv,wh") diff --git a/2024/day24/day24.py b/2024/day24/day24.py new file mode 100644 index 0000000..32bba38 --- /dev/null +++ b/2024/day24/day24.py @@ -0,0 +1,250 @@ +from collections import defaultdict +import copy + + +def part1(input: str) -> int: + wires, instructions = parse_input(input) + run(instructions, wires) + return get_num(wires, "z") + + +def parse_input(input: str) -> tuple[dict[str, int], list[list[str]]]: + parts = input.split("\n\n") + + wires: dict[str, int] = defaultdict(int) + + for line in parts[0].splitlines(): + wires[line[:3]] = int(line[5:]) + + instructions: list[list[str]] = [] + for line in parts[1].splitlines(): + in1, op, in2, _, out = line.split(" ") + instructions.append([in1, op, in2, out]) + + return (wires, instructions) + + +def get_num(wires: dict[str, int], char: str) -> int: + if len(char) != 1: + raise Exception("exactly one char needed for get_num") + keys: list[str] = [] + for wire in wires: + if wire[0] == char: + keys.append(wire) + keys.sort() + binary_num: str = "" + for key in reversed(keys): + binary_num += str(wires[key]) + + return int(binary_num, 2) + + +def run(instructions: list[list[str]], wires: dict[str, int]): + processed_lines: set[int] = set() + while len(processed_lines) < len(instructions): + processed_before: int = len(processed_lines) + + for i, inst in enumerate(instructions): + if i in processed_lines: + continue + in1, op, in2, out = inst + if in1 not in wires or in2 not in wires: + continue + + if op == "AND": + wires[out] = wires[in1] & wires[in2] + elif op == "OR": + wires[out] = wires[in1] | wires[in2] + elif op == "XOR": + wires[out] = wires[in1] ^ wires[in2] + + processed_lines.add(i) + + if len(processed_lines) == processed_before: + # print("no new lines processed") + return + + +# this code is way too slow, works for the examples but unsurprisingly way too +# slow for the actual input. ditching python for a go solution... +def part2(input: str) -> str: + wires, instructions = parse_input(input) + expected_sum: int = get_num(wires, "x") & get_num(wires, "y") + + def any_values_are_equal(*args: int) -> bool: + seen: set[int] = set() + for val in args: + if val in seen: + return True + seen.add(val) + return False + + # generate swaps... maybe 8 nested for loops... + for a in range(len(instructions)): + print(a) + for b in range(a + 1, len(instructions)): + for c in range(a + 1, len(instructions)): + if any_values_are_equal(b, c): + continue + for d in range(a + 1, len(instructions)): + if any_values_are_equal(b, c, d): + continue + for e in range(a + 1, len(instructions)): + if any_values_are_equal(b, c, d, e): + continue + for f in range(a + 1, len(instructions)): + if any_values_are_equal(b, c, d, e, f): + continue + for g in range(a + 1, len(instructions)): + if any_values_are_equal(b, c, d, e, f, g): + continue + for h in range(a + 1, len(instructions)): + if any_values_are_equal(b, c, d, e, f, g, h): + continue + + instructions[a][3], instructions[b][3] = ( + instructions[b][3], + instructions[a][3], + ) + instructions[c][3], instructions[d][3] = ( + instructions[d][3], + instructions[c][3], + ) + + instructions[e][3], instructions[f][3] = ( + instructions[f][3], + instructions[e][3], + ) + instructions[g][3], instructions[h][3] = ( + instructions[h][3], + instructions[g][3], + ) + + wires_copy = copy.deepcopy(wires) + run(instructions, wires_copy) + sum = get_num(wires_copy, "z") + if sum == expected_sum: + swaps: list[str] = [ + instructions[a][3], + instructions[b][3], + instructions[c][3], + instructions[d][3], + instructions[e][3], + instructions[f][3], + instructions[g][3], + instructions[h][3], + ] + swaps.sort() + return ",".join(swaps) + # backtrack + instructions[a][3], instructions[b][3] = ( + instructions[b][3], + instructions[a][3], + ) + instructions[c][3], instructions[d][3] = ( + instructions[d][3], + instructions[c][3], + ) + instructions[e][3], instructions[f][3] = ( + instructions[f][3], + instructions[e][3], + ) + instructions[g][3], instructions[h][3] = ( + instructions[h][3], + instructions[g][3], + ) + + raise Exception("should return from loop") + + +example = """x00: 1 +x01: 1 +x02: 1 +y00: 0 +y01: 1 +y02: 0 + +x00 AND y00 -> z00 +x01 XOR y01 -> z01 +x02 OR y02 -> z02""" + +print(f"part1 example: {part1(example)} want 4") + +big_example = """x00: 1 +x01: 0 +x02: 1 +x03: 1 +x04: 0 +y00: 1 +y01: 1 +y02: 1 +y03: 1 +y04: 1 + +ntg XOR fgs -> mjb +y02 OR x01 -> tnw +kwq OR kpj -> z05 +x00 OR x03 -> fst +tgd XOR rvg -> z01 +vdt OR tnw -> bfw +bfw AND frj -> z10 +ffh OR nrd -> bqk +y00 AND y03 -> djm +y03 OR y00 -> psh +bqk OR frj -> z08 +tnw OR fst -> frj +gnj AND tgd -> z11 +bfw XOR mjb -> z00 +x03 OR x00 -> vdt +gnj AND wpb -> z02 +x04 AND y00 -> kjc +djm OR pbm -> qhw +nrd AND vdt -> hwm +kjc AND fst -> rvg +y04 OR y02 -> fgs +y01 AND x02 -> pbm +ntg OR kjc -> kwq +psh XOR fgs -> tgd +qhw XOR tgd -> z09 +pbm OR djm -> kpj +x03 XOR y03 -> ffh +x00 XOR y04 -> ntg +bfw OR bqk -> z06 +nrd XOR fgs -> wpb +frj XOR qhw -> z04 +bqk OR frj -> z07 +y03 OR x01 -> nrd +hwm AND bqk -> z03 +tgd XOR rvg -> z12 +tnw OR pbm -> gnj""" + +print(f"part1 big_example: {part1(big_example)} want 2024") + +input = open("input.txt").read().strip() +print(f"part1 actual: {part1(input)} want 38869984335432") + +part2_example_2_pairs_swapped = """x00: 0 +x01: 1 +x02: 0 +x03: 1 +x04: 0 +x05: 1 +y00: 0 +y01: 0 +y02: 1 +y03: 1 +y04: 0 +y05: 1 + +x00 AND y00 -> z05 +x01 AND y01 -> z02 +x02 AND y02 -> z01 +x03 AND y03 -> z03 +x04 AND y04 -> z04 +x05 AND y05 -> z00""" + +# print( +# f"part2 example with two swapped {part2(part2_example_2_pairs_swapped)} want z00,z01,z02,z05" +# ) + +print(f"part2 actual: {part2(input)} want QQ") diff --git a/2024/day24/main.go b/2024/day24/main.go new file mode 100644 index 0000000..6fb2676 --- /dev/null +++ b/2024/day24/main.go @@ -0,0 +1,259 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "log" + "math/rand" + "sort" + "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) + // drg,gvw,jbp,jgc,qjb,z15,z22,z35 + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) + } +} + +func part1(input string) int { + calc := parseInput(input) + res := 0 + + cache := map[string]int{} + + for k, v := range calc.wires { + cache[k] = v + } + + for k := range calc.outputToSource { + if k[0] != 'z' { + continue + } + + val, err := getWireVal(k, calc, cache, 0) + + if err != nil { + log.Fatalf("Did not expect error\n") + } + + if val == 0 { + continue + } + + i := cast.ToInt(k[1:]) + + res += 1 << int(i) + } + + return res +} + +func part2(input string) string { + calc := parseInput(input) + + swaps := map[string]bool{} + ans := map[string]bool{} + + fmt.Println("this will take a while to run") + backtrack(0, calc, swaps, ans) + + wires := []string{} + for wire := range ans { + wires = append(wires, wire) + } + + sort.Strings(wires) + + return strings.Join(wires, ",") +} + +func getWireVal(wire string, calc Calculator, cache map[string]int, depth int) (int, error) { + if val, ok := cache[wire]; ok { + return val, nil + } + + if depth > 200 { + return -1, fmt.Errorf("likely a loop, exit") + } + + op, ok := calc.outputToSource[wire] + + if !ok { + return -1, fmt.Errorf("cannot get value for wire %v", wire) + } + + v1, _ := getWireVal(op.inputs[0], calc, cache, depth+1) + v2, _ := getWireVal(op.inputs[1], calc, cache, depth+1) + + val := 0 + if op.op == "AND" { + val = v1 & v2 + } else if op.op == "OR" { + val = v1 | v2 + } else if op.op == "XOR" { + val = v1 ^ v2 + } + + cache[wire] = val + + return val, nil +} + +func getFirstWrong(calc Calculator) (int, map[string]int, error) { + cRes := map[string]int{} + bRes := 100 + + for range 10 { + cache := map[string]int{} + + carry := 0 + for b := range 45 { + xStr := fmt.Sprintf("x%02d", b) + yStr := fmt.Sprintf("y%02d", b) + zStr := fmt.Sprintf("z%02d", b) + + cache[xStr] = rand.Intn(2) + cache[yStr] = rand.Intn(2) + + x := cache[xStr] + y := cache[yStr] + + zCalc, err := getWireVal(zStr, calc, cache, 0) + + if err != nil { + return -1, nil, err + } + + zExp := (x + y + carry) % 2 + carry = (x + y + carry) / 2 + + if zCalc != zExp && b < bRes { + bRes = b + cRes = cache + break + } + } + } + + if bRes == 100 { + bRes = -1 + } + + return bRes, cRes, nil +} + +func checkCalc(calc Calculator) bool { + bErr, _, err := getFirstWrong(calc) + + return bErr == -1 && err == nil +} + +func backtrack(b int, calc Calculator, swaps map[string]bool, ans map[string]bool) { + if len(ans) > 0 { + return + } + // fmt.Printf("Checking %v with swaps\n%v\n", b, swaps) + if len(swaps) == 8 { + if checkCalc(calc) { + for k := range swaps { + ans[k] = true + } + } + + return + } + + bErr, conns, err := getFirstWrong(calc) + + if bErr < b || err != nil { + return + } + + conns2 := map[string]Connections{} + + for k, v := range calc.outputToSource { + conns2[k] = v + } + + for c1 := range conns { + if swaps[c1] || c1[0] == 'x' || c1[0] == 'y' { + continue + } + + for c2 := range conns2 { + if c2 == c1 || swaps[c2] || c2[0] == 'x' || c2[0] == 'y' { + continue + } + + calc.outputToSource[c1], calc.outputToSource[c2] = calc.outputToSource[c2], calc.outputToSource[c1] + + swaps[c1] = true + swaps[c2] = true + + backtrack(bErr+1, calc, swaps, ans) + + delete(swaps, c2) + delete(swaps, c1) + + calc.outputToSource[c1], calc.outputToSource[c2] = calc.outputToSource[c2], calc.outputToSource[c1] + } + } +} + +type Connections struct { + inputs []string + op string +} + +type Calculator struct { + outputToSource map[string]Connections + wires map[string]int +} + +func parseInput(input string) Calculator { + parts := strings.Split(input, "\n\n") + + wires := map[string]int{} + for _, line := range strings.Split(parts[0], "\n") { + wires[line[:3]] = cast.ToInt(line[5:]) + } + + outputToSource := map[string]Connections{} + for _, line := range strings.Split(parts[1], "\n") { + lineParts := strings.Split(line, " ") + in1, op, in2, out := lineParts[0], lineParts[1], lineParts[2], lineParts[4] + outputToSource[out] = Connections{ + inputs: []string{in1, in2}, + op: op, + } + } + + return Calculator{outputToSource, wires} +} From 63f8f36e2ac849d013136fa621f36bbc66f4f0a3 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Wed, 1 Jan 2025 02:44:50 -0500 Subject: [PATCH 52/57] day21 finally... lots of recursion bugs --- 2024/day21/day21.py | 216 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 2024/day21/day21.py diff --git a/2024/day21/day21.py b/2024/day21/day21.py new file mode 100644 index 0000000..3203f9f --- /dev/null +++ b/2024/day21/day21.py @@ -0,0 +1,216 @@ +num_pad: dict[str, tuple[int, int]] = { + "7": (0, 0), + "8": (0, 1), + "9": (0, 2), + "4": (1, 0), + "5": (1, 1), + "6": (1, 2), + "1": (2, 0), + "2": (2, 1), + "3": (2, 2), + "0": (3, 1), + "A": (3, 2), + "EMPTY": (3, 0), +} + +dir_pad: dict[str, tuple[int, int]] = { + "^": (0, 1), + "A": (0, 2), + "<": (1, 0), + "v": (1, 1), + ">": (1, 2), + "EMPTY": (0, 0), +} + + +# had to manually memo because the pad dict type isn't hashable... i'm sure +# there's a way around this to use functools.cache like others... +def get_optimal_path_length( + pad: dict[str, tuple[int, int]], + start: str, + end: str, + depth: int, + memo: dict[str, int], +) -> int: + key = f"{start},{end},{depth},{pad is dir_pad}" + if key in memo: + return memo[key] + + # determine horizontal and vertical parts of path + # recurse in each direction and return whichever is shorter + start_row, start_col = pad[start] + end_row, end_col = pad[end] + + dr: int = end_row - start_row + dc: int = end_col - start_col + + horiz: str = "" + vert: str = "" + if dc > 0: + horiz = ">" * dc + else: + horiz = "<" * -dc + + if dr > 0: + vert = "v" * dr + else: + vert = "^" * -dr + + horiz_first = "A" + horiz + vert + "A" + vert_first = "A" + vert + horiz + "A" + + if depth == 0: + memo[key] = len(horiz_first[1:]) + return memo[key] + + horiz_first_result: int = 0 + for i in range(len(horiz_first) - 1): + horiz_first_result += get_optimal_path_length( + dir_pad, horiz_first[i], horiz_first[i + 1], depth - 1, memo + ) + + vert_first_result: int = 0 + for i in range(len(vert_first) - 1): + vert_first_result += get_optimal_path_length( + dir_pad, vert_first[i], vert_first[i + 1], depth - 1, memo + ) + + # still need to do the recursive calls, THEN decide which to return + # no horizontal or vertical movement so obviously just need to do the move plus "A" + if horiz == "" or vert == "": + assert horiz_first_result == vert_first_result + memo[key] = horiz_first_result + return horiz_first_result + + # if it could hit the empty spot, just return the opposite direction first + # moving horizontally + if (start_row, end_col) == pad["EMPTY"]: + memo[key] = vert_first_result + return vert_first_result + # moving vertically + if (end_row, start_col) == pad["EMPTY"]: + memo[key] = horiz_first_result + return horiz_first_result + + if horiz_first_result < vert_first_result: + memo[key] = horiz_first_result + return horiz_first_result + memo[key] = vert_first_result + return vert_first_result + + +def part1(input: str, depth: int) -> int: + + ans: int = 0 + for line in input.splitlines(): + # starts at bottom right A button so add one onto the front of the line + line = "A" + line + + path: int = 0 + for i in range(len(line) - 1): + best_path = get_optimal_path_length( + num_pad, line[i], line[i + 1], depth, {} + ) + path += best_path + + # length of shortest sequence * numeric part of input + # all are the same so just remove A char and convert to int + ans += int(line[1:4]) * path + return ans + + +example = """029A +980A +179A +456A +379A""" + +input = open("input.txt").read().strip() + +print(f"part1 example: {part1(example, 2)} want 126384") +print(f"part1 actual: {part1(input, 2)} want 237342") +print(f"part1 actual: {part1(input, 25)} want 237342") + +# +---+---+---+ +# | 7 | 8 | 9 | +# +---+---+---+ +# | 4 | 5 | 6 | +# +---+---+---+ +# | 1 | 2 | 3 | +# +---+---+---+ +# | 0 | A | +# +---+---+ + + +# 3 of these robots chained together to type into the pad above... +# +---+---+ +# | ^ | A | +# +---+---+---+ +# | < | v | > | +# +---+---+---+ + + +# unfortunately had to scrap this method because memoizing the entire resulting +# string takes up way too much memory and that nukes the runtime... +# refactoring to just store the actual length is plenty... and possible to memo +def get_optimal_path( + pad: dict[str, tuple[int, int]], + start: str, + end: str, + depth: int, +) -> str: + # determine horizontal and vertical parts of path + # recurse in each direction and return whichever is shorter + start_row, start_col = pad[start] + end_row, end_col = pad[end] + + dr: int = end_row - start_row + dc: int = end_col - start_col + + horiz: str = "" + vert: str = "" + if dc > 0: + horiz = ">" * dc + else: + horiz = "<" * -dc + + if dr > 0: + vert = "v" * dr + else: + vert = "^" * -dr + + horiz_first = "A" + horiz + vert + "A" + vert_first = "A" + vert + horiz + "A" + + if depth == 0: + return horiz_first[1:] + + horiz_first_result: str = "" + for i in range(len(horiz_first) - 1): + horiz_first_result += get_optimal_path( + dir_pad, horiz_first[i], horiz_first[i + 1], depth - 1 + ) + + vert_first_result: str = "" + for i in range(len(vert_first) - 1): + vert_first_result += get_optimal_path( + dir_pad, vert_first[i], vert_first[i + 1], depth - 1 + ) + + # still need to do the recursive calls, THEN decide which to return + # no horizontal or vertical movement so obviously just need to do the move plus "A" + if horiz == "" or vert == "": + assert horiz_first_result == vert_first_result + return horiz_first_result + + # if it could hit the empty spot, just return the opposite direction first + # moving horizontally + if (start_row, end_col) == pad["EMPTY"]: + return vert_first_result + # moving vertically + if (end_row, start_col) == pad["EMPTY"]: + return horiz_first_result + + if len(horiz_first_result) < len(vert_first_result): + return horiz_first_result + return vert_first_result From 6d20c2a6a05bb54f4bfafcc5ad6e530b9595d899 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Wed, 1 Jan 2025 03:00:01 -0500 Subject: [PATCH 53/57] 2024 done! go solutions TK --- 2024/day25/day25.py | 88 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 2024/day25/day25.py diff --git a/2024/day25/day25.py b/2024/day25/day25.py new file mode 100644 index 0000000..289fbb7 --- /dev/null +++ b/2024/day25/day25.py @@ -0,0 +1,88 @@ +def part1(input: str) -> int: + parts = input.split("\n\n") + + locks: list[list[int]] = [] + keys: list[list[int]] = [] + max_height: int = 0 + for part in parts: + lines = part.split("\n") + max_height = len(lines) - 1 + + # lock + if lines[0] == "#" * len(lines[0]): + lock_heights: list[int] = [] + for col in range(len(lines[0])): + for row in reversed(range(len(lines))): + if lines[row][col] != ".": + lock_heights.append(row) + break + locks.append(lock_heights) + else: + # key + key_heights: list[int] = [] + for col in range(len(lines[0])): + for row in range(len(lines)): + if lines[row][col] != ".": + key_heights.append(len(lines) - 1 - row) + break + keys.append(key_heights) + + count: int = 0 + + for lock in locks: + for key in keys: + fits: bool = True + for col in range(len(key)): + if lock[col] + key[col] >= max_height: + fits = False + break + if fits: + count += 1 + + return count + + +example = """##### +.#### +.#### +.#### +.#.#. +.#... +..... + +##### +##.## +.#.## +...## +...#. +...#. +..... + +..... +#.... +#.... +#...# +#.#.# +#.### +##### + +..... +..... +#.#.. +###.. +###.# +###.# +##### + +..... +..... +..... +#.... +#.#.. +#.#.# +#####""" + +print(f"part1 example: {part1(example)} want 3") + +input = open("input.txt").read().strip() +print(f"part1 actual: {part1(input)} want 3114") From 07b6cb483661d8e3ab35f5ec9e6f8b8ad0380ec8 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Wed, 1 Jan 2025 03:06:58 -0500 Subject: [PATCH 54/57] 500 stars image and readme updates to note 2024 python use --- 450.png | Bin 75887 -> 0 bytes 500.png | Bin 0 -> 86259 bytes README.md | 7 +++++-- 3 files changed, 5 insertions(+), 2 deletions(-) delete mode 100644 450.png create mode 100644 500.png diff --git a/450.png b/450.png deleted file mode 100644 index 36747c03b712f9bb9a5d79bf31ec5ffe6bb794e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75887 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

^WtC{y{kuQ0ho&*N(n~v^28hJXh%fE=cs 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 will be usable to run the actual inputs for that day. From 778c6f3fc8be8a745b99cbd1136d16262cd7f32d Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 1 Dec 2025 19:18:45 -0500 Subject: [PATCH 55/57] 2025 day1 --- 2025/day01/main.go | 107 ++++++++++++++++++++++++++++++++++++++++ 2025/day01/main_test.go | 89 +++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 2025/day01/main.go create mode 100644 2025/day01/main_test.go diff --git a/2025/day01/main.go b/2025/day01/main.go new file mode 100644 index 0000000..156f0ec --- /dev/null +++ b/2025/day01/main.go @@ -0,0 +1,107 @@ +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 { + instructions := strings.Split(input, "\n") + + val := 50 + zeroes := 0 + for _, inst := range instructions { + dir := inst[:1] + num := cast.ToInt(inst[1:]) + + if dir == "R" { + val += num + val %= 100 + } else if dir == "L" { + val -= num + for val < 0 { + val += 100 + } + val %= 100 // is this even necessary? + } else { + panic(fmt.Sprintf("unexpected dir: %q", dir)) + } + if val == 0 { + zeroes++ + } + } + + return zeroes +} + +func part2(input string) int { + val := 50 + zeroes := 0 + for _, inst := range strings.Split(input, "\n") { + dir := inst[:1] + num := cast.ToInt(inst[1:]) + + if dir == "R" { + val += num + zeroes += val / 100 + val %= 100 + } else if dir == "L" { + wasZero := val == 0 + val -= num + + // special case when lands on zero because for loop will undercount by 1 + landedOnZero := val%100 == 0 + if landedOnZero { + zeroes++ + } + + for val < 0 { + val += 100 + zeroes++ + } + + // if it started at zero and turned left, the for loop above will over count by 1 + // 0 -> L5 = -5 + 100 (zeroes++ incorrectly) + if wasZero { + zeroes-- + } + } else { + panic(fmt.Sprintf("unexpected dir: %q", dir)) + } + } + + return zeroes +} diff --git a/2025/day01/main_test.go b/2025/day01/main_test.go new file mode 100644 index 0000000..b390298 --- /dev/null +++ b/2025/day01/main_test.go @@ -0,0 +1,89 @@ +package main + +import ( + "testing" +) + +var example = `L68 +L30 +R48 +L5 +R60 +L55 +L1 +L99 +R14 +L82` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 3, + }, + { + name: "actual", + input: input, + want: 1084, + }, + } + 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: 6, + }, + { + name: "L50", + input: "L50", + want: 1, + }, + { + name: "R50", + input: "R50", + want: 1, + }, + { + name: "L50R200", + input: "L50\nR200", + want: 3, + }, + { + // ugh off by ones in the "L" branch + name: "L50L200", + input: "L50\nL200", + want: 3, + }, + { + name: "actual", + input: input, + want: 6475, + }, + } + 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) + } + }) + } +} From a4ab3b594036d36fece3d88d40480b6696a4c008 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Mon, 1 Dec 2025 19:45:44 -0500 Subject: [PATCH 56/57] lol im never going to finish updating the python solutions... --- 2024/day25/main.go | 91 +++++++++++++++++++++++++++++++++++++++++ 2024/day25/main_test.go | 71 ++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 2024/day25/main.go create mode 100644 2024/day25/main_test.go diff --git a/2024/day25/main.go b/2024/day25/main.go new file mode 100644 index 0000000..a661793 --- /dev/null +++ b/2024/day25/main.go @@ -0,0 +1,91 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "strings" + + "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) + + ans := part1(input) + util.CopyToClipboard(fmt.Sprintf("%v", ans)) + fmt.Println("Output:", ans) +} + +func part1(input string) int { + parts := strings.Split(input, "\n\n") + + locks := [][]int{} + keys := [][]int{} + maxHeight := 0 + for _, part := range parts { + lines := strings.Split(part, "\n") + maxHeight = len(lines) - 1 + + // lock detection: first line is all '#' + fullLock := strings.Repeat("#", len(lines[0])) + if lines[0] == fullLock { + lockHeights := []int{} + // for each column, find first non-dot from bottom + for col := range len(lines[0]) { + for row := len(lines) - 1; row >= 0; row-- { + if lines[row][col:col+1] != "." { + lockHeights = append(lockHeights, row) + break + } + } + } + locks = append(locks, lockHeights) + } else { + // key detection: for each column, find first non-dot from top + keyHeights := []int{} + for col := range len(lines[0]) { + for row := range len(lines) { + if lines[row][col:col+1] != "." { + keyHeights = append(keyHeights, len(lines)-1-row) + break + } + } + } + keys = append(keys, keyHeights) + } + } + + count := 0 + + for _, lock := range locks { + for _, key := range keys { + fits := true + for col := range len(key) { + if lock[col]+key[col] >= maxHeight { + fits = false + break + } + } + if fits { + count += 1 + } + } + } + + return count +} diff --git a/2024/day25/main_test.go b/2024/day25/main_test.go new file mode 100644 index 0000000..33ac789 --- /dev/null +++ b/2024/day25/main_test.go @@ -0,0 +1,71 @@ +package main + +import ( + "testing" +) + +var example = `##### +.#### +.#### +.#### +.#.#. +.#... +..... + +##### +##.## +.#.## +...## +...#. +...#. +..... + +..... +#.... +#.... +#...# +#.#.# +#.### +##### + +..... +..... +#.#.. +###.. +###.# +###.# +##### + +..... +..... +..... +#.... +#.#.. +#.#.# +#####` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 3, + }, + { + name: "actual", + input: input, + want: 3114, + }, + } + 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) + } + }) + } +} From 4e1b1a13e84afaaaffa0d87aa2dcb374324c499b Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Tue, 2 Dec 2025 19:41:32 -0500 Subject: [PATCH 57/57] ugly and slow but more than fast enough, 2025 day2 --- 2025/day02/main.go | 98 +++++++++++++++++++++++++++++++++++++++++ 2025/day02/main_test.go | 59 +++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 2025/day02/main.go create mode 100644 2025/day02/main_test.go diff --git a/2025/day02/main.go b/2025/day02/main.go new file mode 100644 index 0000000..6732040 --- /dev/null +++ b/2025/day02/main.go @@ -0,0 +1,98 @@ +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 { + pairs := parseInput(input) + + total := 0 + for _, p := range pairs { + for i := p[0]; i <= p[1]; i++ { + str := cast.ToString(i) + // check str is even length (probably not necessary...) + if len(str)%2 != 0 { + continue + } + // directly check first half to second half of str + if str[:len(str)/2] == str[len(str)/2:] { + total += i + } + } + } + + return total +} + +func part2(input string) int { + pairs := parseInput(input) + + total := 0 + for _, p := range pairs { + for i := p[0]; i <= p[1]; i++ { + str := cast.ToString(i) + + for l := 1; l <= len(str)/2; l++ { + // skip if this chunk size does not divide evenly into the entire str + if len(str)%l != 0 { + continue + } + + // compare chunk repeated the correct number of times against the entire str + chunk := str[:l] + if str == strings.Repeat(chunk, len(str)/l) { + total += i + break + } + } + } + } + + return total +} + +func parseInput(input string) (ans [][]int) { + for _, line := range strings.Split(input, ",") { + nums := strings.Split(line, "-") + ans = append(ans, []int{ + cast.ToInt(nums[0]), + cast.ToInt(nums[1]), + }) + } + return ans +} diff --git a/2025/day02/main_test.go b/2025/day02/main_test.go new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/2025/day02/main_test.go @@ -0,0 +1,59 @@ +package main + +import ( + "testing" +) + +var example = `11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124` + +func Test_part1(t *testing.T) { + tests := []struct { + name string + input string + want int + }{ + { + name: "example", + input: example, + want: 1227775554, + }, + { + name: "actual", + input: input, + want: 30599400849, + }, + } + 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: 4174379265, + }, + { + name: "actual", + input: input, + want: 46270373595, + }, + } + 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) + } + }) + } +}