8 9 10 11

This commit is contained in:
alexchao26
2022-12-12 00:22:00 -05:00
parent 3bd03ec7b3
commit 6bef9a0721
8 changed files with 1230 additions and 0 deletions
+144
View File
@@ -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
}
+63
View File
@@ -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)
}
})
}
}
+253
View File
@@ -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)
}
+85
View File
@@ -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)
}
})
}
}
+167
View File
@@ -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
}
+216
View File
@@ -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)
}
})
}
}
+243
View File
@@ -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,
},
}
}
+59
View File
@@ -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)
}
})
}
}