mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-06-07 12:45:10 +02:00
8 9 10 11
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user