mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-06-07 12:45:10 +02:00
fresh start
This commit is contained in:
@@ -1,35 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := notQuiteLisp(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func notQuiteLisp(input string, part int) int {
|
||||
var level int
|
||||
for i, r := range input {
|
||||
if r == '(' {
|
||||
level++
|
||||
} else {
|
||||
level--
|
||||
}
|
||||
|
||||
if part == 2 && level == -1 {
|
||||
return i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return level
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_notQuiteLisp(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 1, 232},
|
||||
{"actual", util.ReadFile("input.txt"), 2, 1783},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := notQuiteLisp(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("notQuiteLisp() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/mathy"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans int
|
||||
if part == 1 {
|
||||
ans = part1(util.ReadFile("./input.txt"))
|
||||
} else {
|
||||
ans = part2(util.ReadFile("./input.txt"))
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
var totalSqFt int
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
var x, y, z int
|
||||
_, err := fmt.Sscanf(line, "%dx%dx%d", &x, &y, &z)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
totalSqFt += x * y * 2
|
||||
totalSqFt += x * z * 2
|
||||
totalSqFt += z * y * 2
|
||||
totalSqFt += mathy.MinInt(x*y, y*z, x*z) // slack in wrapping paper...
|
||||
}
|
||||
|
||||
return totalSqFt
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
var totalLen int
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
var x, y, z int
|
||||
_, err := fmt.Sscanf(line, "%dx%dx%d", &x, &y, &z)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cubic := x * y * z
|
||||
totalLen += cubic
|
||||
sides := []int{
|
||||
2 * (x + y),
|
||||
2 * (y + z),
|
||||
2 * (x + z),
|
||||
}
|
||||
totalLen += mathy.MinInt(sides...)
|
||||
}
|
||||
return totalLen
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `2x3x4
|
||||
1x1x10`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"example", example, 58 + 43},
|
||||
{"actual", util.ReadFile("input.txt"), 1588178},
|
||||
}
|
||||
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
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 3783758},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans int
|
||||
if part == 1 {
|
||||
ans = part1(util.ReadFile("./input.txt"))
|
||||
} else {
|
||||
ans = part2(util.ReadFile("./input.txt"))
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
var dirs = map[string][2]int{
|
||||
"^": [2]int{-1, 0}, // row then col
|
||||
"v": [2]int{1, 0},
|
||||
"<": [2]int{0, -1},
|
||||
">": [2]int{0, 1},
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
houseCount := map[[2]int]int{[2]int{}: 1}
|
||||
coord := [2]int{0, 0}
|
||||
for _, char := range strings.Split(input, "") {
|
||||
diff := dirs[string(char)]
|
||||
nextCoord := [2]int{
|
||||
coord[0] + diff[0],
|
||||
coord[1] + diff[1],
|
||||
}
|
||||
coord = nextCoord
|
||||
houseCount[coord]++
|
||||
}
|
||||
return len(houseCount)
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
houseCount := map[[2]int]int{[2]int{}: 2}
|
||||
santaCoord := [2]int{0, 0}
|
||||
robotCoord := [2]int{0, 0}
|
||||
for i, char := range strings.Split(input, "") {
|
||||
diff := dirs[string(char)]
|
||||
if i%2 == 0 {
|
||||
nextSantaCoord := [2]int{
|
||||
santaCoord[0] + diff[0],
|
||||
santaCoord[1] + diff[1],
|
||||
}
|
||||
santaCoord = nextSantaCoord
|
||||
houseCount[santaCoord]++
|
||||
} else {
|
||||
nextRobotCoord := [2]int{
|
||||
robotCoord[0] + diff[0],
|
||||
robotCoord[1] + diff[1],
|
||||
}
|
||||
robotCoord = nextRobotCoord
|
||||
houseCount[robotCoord]++
|
||||
}
|
||||
}
|
||||
return len(houseCount)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 2565},
|
||||
}
|
||||
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
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 2639},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := md5StockingStuffer(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func md5StockingStuffer(input string, part int) int {
|
||||
prefixZeroes := 5
|
||||
if part == 2 {
|
||||
prefixZeroes = 6
|
||||
}
|
||||
|
||||
for i := 0; i < math.MaxInt32; i++ {
|
||||
toHash := fmt.Sprintf("%s%d", input, i)
|
||||
hashed := fmt.Sprintf("%x", md5.Sum([]byte(toHash)))
|
||||
if strings.HasPrefix(hashed, strings.Repeat("0", prefixZeroes)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
panic("no hash found")
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_md5StockingStuffer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, 254575},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, 1038736},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := md5StockingStuffer(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("md5StockingStuffer() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans int
|
||||
if part == 1 {
|
||||
ans = part1(util.ReadFile("./input.txt"))
|
||||
} else {
|
||||
ans = part2(util.ReadFile("./input.txt"))
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
var nice int
|
||||
|
||||
disallowPattern := regexp.MustCompile("(ab|cd|pq|xy)")
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
var vowels int
|
||||
for _, char := range line {
|
||||
if strings.ContainsRune("aeiou", char) {
|
||||
vowels++
|
||||
}
|
||||
}
|
||||
var hasDouble bool
|
||||
for i := 0; i < len(line)-1; i++ {
|
||||
if line[i] == line[i+1] {
|
||||
hasDouble = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if vowels >= 3 && !disallowPattern.MatchString(line) && hasDouble {
|
||||
nice++
|
||||
}
|
||||
}
|
||||
|
||||
return nice
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
var nice int
|
||||
|
||||
// put a double for loop check inside of a separate function b/c it makes
|
||||
// returning out of both loops possible, and avoids using a label which
|
||||
// makes me sad
|
||||
passesRule1 := func(line string) bool {
|
||||
for i := 0; i < len(line)-2; i++ {
|
||||
toMatch := line[i : i+2]
|
||||
for j := i + 2; j < len(line)-1; j++ {
|
||||
if line[j:j+2] == toMatch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
rule1 := passesRule1(line)
|
||||
|
||||
var rule2 bool
|
||||
for i := 0; i < len(line)-2; i++ {
|
||||
if line[i] == line[i+2] {
|
||||
rule2 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if rule1 && rule2 {
|
||||
nice++
|
||||
}
|
||||
}
|
||||
|
||||
return nice
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 238},
|
||||
}
|
||||
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
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 69},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans int
|
||||
if part == 1 {
|
||||
ans = part1(util.ReadFile("./input.txt"))
|
||||
} else {
|
||||
ans = part2(util.ReadFile("./input.txt"))
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
// 1000x1000 grid
|
||||
grid := make([][]bool, 1000)
|
||||
for i := range grid {
|
||||
grid[i] = make([]bool, 1000)
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "toggle"):
|
||||
var row1, col1, row2, col2 int
|
||||
fmt.Sscanf(line, "toggle %d,%d through %d,%d", &row1, &col1, &row2, &col2)
|
||||
for i := row1; i <= row2; i++ {
|
||||
for j := col1; j <= col2; j++ {
|
||||
grid[i][j] = !grid[i][j]
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(line, "turn on"):
|
||||
var row1, col1, row2, col2 int
|
||||
fmt.Sscanf(line, "turn on %d,%d through %d,%d", &row1, &col1, &row2, &col2)
|
||||
for i := row1; i <= row2; i++ {
|
||||
for j := col1; j <= col2; j++ {
|
||||
grid[i][j] = true
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(line, "turn off"):
|
||||
var row1, col1, row2, col2 int
|
||||
fmt.Sscanf(line, "turn off %d,%d through %d,%d", &row1, &col1, &row2, &col2)
|
||||
for i := row1; i <= row2; i++ {
|
||||
for j := col1; j <= col2; j++ {
|
||||
grid[i][j] = false
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("unhandled instruction")
|
||||
}
|
||||
}
|
||||
var count int
|
||||
for _, row := range grid {
|
||||
for _, b := range row {
|
||||
if b {
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
grid := make([][]int, 1000)
|
||||
for i := range grid {
|
||||
grid[i] = make([]int, 1000)
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "toggle"):
|
||||
var row1, col1, row2, col2 int
|
||||
fmt.Sscanf(line, "toggle %d,%d through %d,%d", &row1, &col1, &row2, &col2)
|
||||
for i := row1; i <= row2; i++ {
|
||||
for j := col1; j <= col2; j++ {
|
||||
grid[i][j] += 2
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(line, "turn on"):
|
||||
var row1, col1, row2, col2 int
|
||||
fmt.Sscanf(line, "turn on %d,%d through %d,%d", &row1, &col1, &row2, &col2)
|
||||
for i := row1; i <= row2; i++ {
|
||||
for j := col1; j <= col2; j++ {
|
||||
grid[i][j]++
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(line, "turn off"):
|
||||
var row1, col1, row2, col2 int
|
||||
fmt.Sscanf(line, "turn off %d,%d through %d,%d", &row1, &col1, &row2, &col2)
|
||||
for i := row1; i <= row2; i++ {
|
||||
for j := col1; j <= col2; j++ {
|
||||
if grid[i][j] > 0 {
|
||||
grid[i][j]--
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("unhandled instruction")
|
||||
}
|
||||
}
|
||||
var brightness int
|
||||
for _, row := range grid {
|
||||
for _, v := range row {
|
||||
brightness += v
|
||||
}
|
||||
}
|
||||
return brightness
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 400410},
|
||||
}
|
||||
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
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 15343601},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := someAssemblyRequired(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func someAssemblyRequired(input string, part int) int {
|
||||
wireToRule := map[string]string{}
|
||||
|
||||
// generate graph of wires to their source rule
|
||||
for _, inst := range strings.Split(input, "\n") {
|
||||
parts := strings.Split(inst, " -> ")
|
||||
wireToRule[parts[1]] = parts[0]
|
||||
}
|
||||
|
||||
aSignal := memoDFS(wireToRule, "a", map[string]int{})
|
||||
if part == 1 {
|
||||
return aSignal
|
||||
}
|
||||
|
||||
// for part 2, override the value sent to wire b, then get output to a again
|
||||
wireToRule["b"] = cast.ToString(aSignal)
|
||||
return memoDFS(wireToRule, "a", map[string]int{})
|
||||
}
|
||||
|
||||
func memoDFS(graph map[string]string, entry string, memo map[string]int) int {
|
||||
if memoVal, ok := memo[entry]; ok {
|
||||
return memoVal
|
||||
}
|
||||
|
||||
// if it's a number, return the casted value
|
||||
if regexp.MustCompile("[0-9]").MatchString(entry) {
|
||||
return cast.ToInt(entry)
|
||||
}
|
||||
|
||||
sourceRule := graph[entry]
|
||||
parts := strings.Split(sourceRule, " ")
|
||||
|
||||
var result int
|
||||
switch {
|
||||
case len(parts) == 1:
|
||||
result = memoDFS(graph, parts[0], memo)
|
||||
case parts[0] == "NOT":
|
||||
start := memoDFS(graph, parts[1], memo)
|
||||
result = (math.MaxUint16) ^ start
|
||||
case parts[1] == "AND":
|
||||
result = memoDFS(graph, parts[0], memo) & memoDFS(graph, parts[2], memo)
|
||||
case parts[1] == "OR":
|
||||
result = memoDFS(graph, parts[0], memo) | memoDFS(graph, parts[2], memo)
|
||||
case parts[1] == "LSHIFT":
|
||||
result = memoDFS(graph, parts[0], memo) << memoDFS(graph, parts[2], memo)
|
||||
case parts[1] == "RSHIFT":
|
||||
result = memoDFS(graph, parts[0], memo) >> memoDFS(graph, parts[2], memo)
|
||||
}
|
||||
|
||||
memo[entry] = result
|
||||
return result
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var exampleOutputH = `123 -> x
|
||||
456 -> y
|
||||
x AND y -> d
|
||||
x OR y -> e
|
||||
x LSHIFT 2 -> f
|
||||
y RSHIFT 2 -> g
|
||||
NOT x -> h
|
||||
NOT y -> i
|
||||
h -> a` // added last rule to output to a (same as real question)
|
||||
|
||||
// Expect these final registers
|
||||
// d: 72
|
||||
// e: 507
|
||||
// f: 492
|
||||
// g: 114
|
||||
// h: 65412
|
||||
// i: 65079
|
||||
// x: 123
|
||||
// y: 456
|
||||
|
||||
func Test_someAssemblyRequired(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"example h -> a", exampleOutputH, 1, 65412},
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, 16076},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, 2797},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := someAssemblyRequired(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("someAssemblyRequired() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans int
|
||||
if part == 1 {
|
||||
ans = part1(util.ReadFile("./input.txt"))
|
||||
} else {
|
||||
ans = part2(util.ReadFile("./input.txt"))
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
var codeChars, stringChars int
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
codeChars += len(line)
|
||||
|
||||
for i := 1; i < len(line)-1; i++ {
|
||||
switch line[i] {
|
||||
case '\\':
|
||||
nextChar := line[i+1]
|
||||
if nextChar == '\\' || nextChar == '"' {
|
||||
i++ // skip an extra character
|
||||
} else if nextChar == 'x' {
|
||||
i += 3 // skip 2 extra chars
|
||||
}
|
||||
}
|
||||
stringChars++
|
||||
}
|
||||
}
|
||||
|
||||
return codeChars - stringChars
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
var encodedLen, originalLen int
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
originalLen += len(line)
|
||||
encodedLen += 2 // outer quotes
|
||||
for i := 0; i < len(line); i++ {
|
||||
switch line[i] {
|
||||
case '"', '\\':
|
||||
encodedLen += 2
|
||||
default:
|
||||
encodedLen++
|
||||
}
|
||||
}
|
||||
}
|
||||
return encodedLen - originalLen
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 1371},
|
||||
}
|
||||
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
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 2117},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
min, max := travelingSalesman(util.ReadFile("./input.txt"))
|
||||
fmt.Printf("Part1: %d\nPart2: %d\n", min, max)
|
||||
}
|
||||
|
||||
func travelingSalesman(input string) (int, int) {
|
||||
graph := newGraphFromInput(input)
|
||||
|
||||
min := math.MaxInt32
|
||||
max := 0
|
||||
for k := range graph {
|
||||
dfsMin, dfsMax := dfsTotalDistance(graph, k, map[string]bool{k: true})
|
||||
min = mathy.MinInt(min, dfsMin)
|
||||
max = mathy.MaxInt(max, dfsMax)
|
||||
}
|
||||
|
||||
return min, max
|
||||
}
|
||||
|
||||
func dfsTotalDistance(graph mapGraph, entry string, visited map[string]bool) (min, max int) {
|
||||
// if all nodes have been visited, return a zero length
|
||||
if len(visited) == len(graph) {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
minDistance := math.MaxInt32
|
||||
maxDistance := 0
|
||||
|
||||
for k := range graph {
|
||||
if !visited[k] {
|
||||
visited[k] = true
|
||||
|
||||
weight := graph[entry][k]
|
||||
minRecurse, maxRecurse := dfsTotalDistance(graph, k, visited)
|
||||
minDistance = mathy.MinInt(minDistance, weight+minRecurse)
|
||||
maxDistance = mathy.MaxInt(maxDistance, weight+maxRecurse)
|
||||
|
||||
// backtrack
|
||||
// delete to so length of visited is accurate
|
||||
delete(visited, k)
|
||||
}
|
||||
}
|
||||
|
||||
return minDistance, maxDistance
|
||||
}
|
||||
|
||||
type mapGraph map[string]map[string]int
|
||||
|
||||
func newGraphFromInput(input string) (graph mapGraph) {
|
||||
graph = make(mapGraph)
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
parts := strings.Split(line, " ")
|
||||
start, end := parts[0], parts[2]
|
||||
weight := cast.ToInt(parts[4])
|
||||
|
||||
// ensure nested map exists
|
||||
if graph[start] == nil {
|
||||
graph[start] = make(map[string]int)
|
||||
}
|
||||
if graph[end] == nil {
|
||||
graph[end] = make(map[string]int)
|
||||
}
|
||||
|
||||
// set weight in both directions
|
||||
graph[start][end] = weight
|
||||
graph[end][start] = weight
|
||||
}
|
||||
return graph
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_travelingSalesman(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantPart1 int
|
||||
wantPart2 int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 117, 909},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got1, got2 := travelingSalesman(tt.input)
|
||||
if got1 != tt.wantPart1 {
|
||||
t.Errorf("travelingSalesman() part1 = %v, want %v", got1, tt.wantPart1)
|
||||
}
|
||||
if got2 != tt.wantPart2 {
|
||||
t.Errorf("travelingSalesman() part2 = %v, want %v", got2, tt.wantPart2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := lookAndSay(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func lookAndSay(input string, part int) int {
|
||||
lastSaid := input
|
||||
|
||||
rounds := 40
|
||||
if part == 2 {
|
||||
rounds = 50
|
||||
}
|
||||
|
||||
for i := 0; i < rounds; i++ {
|
||||
var runningCount int
|
||||
var said strings.Builder
|
||||
|
||||
for i := 0; i < len(lastSaid); i++ {
|
||||
if i == len(lastSaid)-1 || lastSaid[i] != lastSaid[i+1] {
|
||||
// add to seen
|
||||
said.WriteString(fmt.Sprintf("%d%s", runningCount+1, string(lastSaid[i])))
|
||||
runningCount = 0
|
||||
} else {
|
||||
// build up running count
|
||||
runningCount++
|
||||
}
|
||||
|
||||
}
|
||||
lastSaid = said.String()
|
||||
}
|
||||
|
||||
return len(lastSaid)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_lookAndSay(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 1, 252594},
|
||||
{"actual", util.ReadFile("input.txt"), 2, 3579328},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := lookAndSay(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("lookAndSay() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := passwordIncrementing(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func passwordIncrementing(input string, part int) string {
|
||||
pw := input
|
||||
for !isValid(pw) {
|
||||
pw = incrementString(pw)
|
||||
}
|
||||
|
||||
if part == 1 {
|
||||
return pw
|
||||
}
|
||||
|
||||
pw = incrementString(pw)
|
||||
for !isValid(pw) {
|
||||
pw = incrementString(pw)
|
||||
}
|
||||
|
||||
return pw
|
||||
}
|
||||
|
||||
func incrementString(in string) string {
|
||||
chars := strings.Split(in, "")
|
||||
for i := len(chars) - 1; i >= 0; i-- {
|
||||
if chars[i] == "z" {
|
||||
// continue loop to carry "carry over the one"
|
||||
chars[i] = "a"
|
||||
} else {
|
||||
asciiCode := cast.ToASCIICode(chars[i])
|
||||
chars[i] = cast.ASCIIIntToChar(asciiCode + 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
return strings.Join(chars, "")
|
||||
}
|
||||
|
||||
func isValid(in string) bool {
|
||||
rule1 := func(in string) bool {
|
||||
for i := 2; i < len(in); i++ {
|
||||
if in[i-2]+1 == in[i-1] && in[i-1]+1 == in[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
rule2 := func(in string) bool {
|
||||
return !regexp.MustCompile("[iol]").MatchString(in)
|
||||
}
|
||||
|
||||
rule3 := func(in string) bool {
|
||||
pairs := map[string]bool{}
|
||||
for i := 1; i < len(in); i++ {
|
||||
if in[i-1] == in[i] {
|
||||
pairs[in[i-1:i+1]] = true
|
||||
}
|
||||
}
|
||||
return len(pairs) >= 2
|
||||
}
|
||||
|
||||
return rule1(in) && rule2(in) && rule3(in)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_passwordIncrementing(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want string
|
||||
}{
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, "hxbxxyzz"},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, "hxcaabcc"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := passwordIncrementing(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("passwordIncrementing() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans int
|
||||
if part == 1 {
|
||||
ans = part1(util.ReadFile("./input.txt"))
|
||||
} else {
|
||||
ans = part2(util.ReadFile("./input.txt"))
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
var totalSum int
|
||||
var runningNum strings.Builder
|
||||
for _, char := range input {
|
||||
// got lucky on the input here
|
||||
if regexp.MustCompile("[-0-9]").MatchString(string(char)) {
|
||||
runningNum.WriteRune(char)
|
||||
} else if runningNum.Len() != 0 {
|
||||
totalSum += cast.ToInt(runningNum.String())
|
||||
runningNum.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// this is for part 2 to handle ends of strings
|
||||
if runningNum.Len() != 0 {
|
||||
totalSum += cast.ToInt(runningNum.String())
|
||||
}
|
||||
|
||||
return totalSum
|
||||
}
|
||||
|
||||
// This solution leverages the error or nil that is returned from json.Marshal
|
||||
//
|
||||
// A full Go solution would requiring writing my own JSON parser
|
||||
// A much easier way would use javascript's JSON.parse and just do a dfs on it
|
||||
func part2(input string) int {
|
||||
// if input does not have object braces or an instance of "red", just pass it through part1
|
||||
if !regexp.MustCompile("[{}]").MatchString(input) ||
|
||||
!regexp.MustCompile("red").MatchString(input) {
|
||||
return part1(input)
|
||||
}
|
||||
|
||||
// try to parse into an object if that's
|
||||
var obj map[string]interface{}
|
||||
err := json.Unmarshal([]byte(input), &obj)
|
||||
// not a json object, assume it's an array
|
||||
if err != nil {
|
||||
// parse into an array
|
||||
var arr []interface{}
|
||||
err := json.Unmarshal([]byte(input), &arr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var arrayTotal int
|
||||
for _, v := range arr {
|
||||
// marshal each array element into a string, then pass it back into part2
|
||||
str, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
arrayTotal += part2(string(str))
|
||||
}
|
||||
|
||||
return arrayTotal
|
||||
}
|
||||
|
||||
// if any value in the object is "red" this object & its children RETURN ZERO
|
||||
for _, v := range obj {
|
||||
// have to convert interface into a string first
|
||||
str, ok := v.(string)
|
||||
if ok && str == "red" {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
var total int
|
||||
for _, v := range obj {
|
||||
str, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
total += part2(string(str))
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"example", "[1,2,3]", 6},
|
||||
{"example", "{\"a\":[-1,1]}", 0},
|
||||
{"actual", util.ReadFile("input.txt"), 119433},
|
||||
}
|
||||
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
|
||||
}{
|
||||
{"flat example 1", "[1,2,\"red\",5]", 8},
|
||||
{"flat example 2", "5", 5},
|
||||
{"actual", util.ReadFile("input.txt"), 68466},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/algos"
|
||||
"github.com/alexchao26/advent-of-code-go/mathy"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := knightsOfTheDinnerTable(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func knightsOfTheDinnerTable(input string, part int) int {
|
||||
graph := makeHappinessGraph(input)
|
||||
var allPeople []string
|
||||
for name := range graph {
|
||||
allPeople = append(allPeople, name)
|
||||
}
|
||||
|
||||
// for part 2 add "me" to the list of people, and an empty map in the grid
|
||||
// for happiness diffs off of me. hashmap misses will be populated with the
|
||||
// zero value (0) which is correct for this problem.
|
||||
if part == 2 {
|
||||
allPeople = append(allPeople, "me")
|
||||
graph["me"] = map[string]int{}
|
||||
}
|
||||
|
||||
perms := algos.PermuteStringSlice(allPeople)
|
||||
|
||||
maxDiff := math.MinInt32
|
||||
for _, p := range perms {
|
||||
maxDiff = mathy.MaxInt(maxDiff, calcHappinessDiff(graph, p))
|
||||
}
|
||||
|
||||
return maxDiff
|
||||
}
|
||||
|
||||
type mapGraph map[string]map[string]int
|
||||
|
||||
func makeHappinessGraph(input string) mapGraph {
|
||||
graph := make(mapGraph)
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
var person1, gainLoss, person2 string
|
||||
var amount int
|
||||
fmt.Sscanf(strings.Trim(line, "."), "%s would %s %d happiness units by sitting next to %s",
|
||||
&person1, &gainLoss, &amount, &person2)
|
||||
|
||||
// ensure nested map exists
|
||||
if graph[person1] == nil {
|
||||
graph[person1] = make(map[string]int)
|
||||
}
|
||||
|
||||
graph[person1][person2] = amount
|
||||
if gainLoss == "lose" {
|
||||
graph[person1][person2] = -amount
|
||||
}
|
||||
}
|
||||
return graph
|
||||
}
|
||||
|
||||
func calcHappinessDiff(graph mapGraph, seating []string) int {
|
||||
var diffs int
|
||||
for i, person := range seating {
|
||||
indexToLeft := (i - 1 + len(seating)) % len(seating)
|
||||
indexToRight := (i + 1) % len(seating)
|
||||
diffs += graph[person][seating[indexToLeft]] + graph[person][seating[indexToRight]]
|
||||
}
|
||||
return diffs
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_knightsOfTheDinnerTable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, 709},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, 668},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := knightsOfTheDinnerTable(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("knightsOfTheDinnerTable() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/mathy"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := reindeerOlympics(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func reindeerOlympics(input string, part int) int {
|
||||
reindeerToDistanceMap := map[string][]int{}
|
||||
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
var name string
|
||||
var speed, runTime, restTime int
|
||||
_, err := fmt.Sscanf(line, "%s can fly %d km/s for %d seconds, but then must rest for %d seconds.", &name, &speed, &runTime, &restTime)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 1-index the distances slice, indices line up with elapsed time
|
||||
reindeerToDistanceMap[name] = append(reindeerToDistanceMap[name], 0)
|
||||
|
||||
var dist int
|
||||
remainingRunTime := runTime
|
||||
remainingRestTime := restTime
|
||||
for t := 0; t < 2503; t++ {
|
||||
if remainingRunTime > 0 {
|
||||
dist += speed
|
||||
remainingRunTime--
|
||||
} else {
|
||||
remainingRestTime--
|
||||
if remainingRestTime == 0 {
|
||||
remainingRunTime = runTime
|
||||
remainingRestTime = restTime
|
||||
}
|
||||
}
|
||||
reindeerToDistanceMap[name] = append(reindeerToDistanceMap[name], dist)
|
||||
}
|
||||
}
|
||||
|
||||
// for part 1 return the furthest end distance (time 2503 seconds)
|
||||
if part == 1 {
|
||||
var furthest int
|
||||
for _, distSli := range reindeerToDistanceMap {
|
||||
furthest = mathy.MaxInt(distSli[2503], furthest)
|
||||
}
|
||||
return furthest
|
||||
}
|
||||
|
||||
// for part 2, score each second, then find the highest score
|
||||
reindeerScores := map[string]int{}
|
||||
for sec := 1; sec <= 2503; sec++ {
|
||||
var names []string
|
||||
var bestDist int
|
||||
for name, distanceSli := range reindeerToDistanceMap {
|
||||
if distanceSli[sec] > bestDist {
|
||||
names = []string{name}
|
||||
bestDist = distanceSli[sec]
|
||||
} else if distanceSli[sec] == bestDist {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
reindeerScores[name]++
|
||||
}
|
||||
}
|
||||
|
||||
var bestScore int
|
||||
for _, v := range reindeerScores {
|
||||
bestScore = mathy.MaxInt(bestScore, v)
|
||||
}
|
||||
|
||||
return bestScore
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_reindeerOlympics(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, 2660},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, 1256},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := reindeerOlympics(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("reindeerOlympics() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/mathy"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
part1Ans, part2Ans := cookieScience(util.ReadFile("./input.txt"))
|
||||
fmt.Printf("Part1: %d\nPart2: %d\n", part1Ans, part2Ans)
|
||||
}
|
||||
|
||||
func cookieScience(input string) (int, int) {
|
||||
lines := strings.Split(input, "\n")
|
||||
cookieVals := [][]int{}
|
||||
|
||||
for _, line := range lines {
|
||||
var name string
|
||||
var cap, dur, fla, tex, cal int
|
||||
fmt.Sscanf(line, "%s capacity %d, durability %d, flavor %d, texture %d, calories %d",
|
||||
&name, &cap, &dur, &fla, &tex, &cal)
|
||||
|
||||
// trim off colons, fmt.Sscanf parses between spaces/whitespace
|
||||
// names end up going unused...
|
||||
name = strings.Trim(name, ":")
|
||||
|
||||
// cookie vals are simply all five values in order
|
||||
cookieVals = append(cookieVals, []int{cap, dur, fla, tex, cal})
|
||||
}
|
||||
|
||||
var bestScore, best500CalScore int
|
||||
for ing1 := 0; ing1 < 100; ing1++ {
|
||||
for ing2 := 0; ing2 < 100; ing2++ {
|
||||
for ing3 := 0; ing3 < 100; ing3++ {
|
||||
ing4 := 100 - ing1 - ing2 - ing3
|
||||
|
||||
cap := ing1*cookieVals[0][0] + ing2*cookieVals[1][0] + ing3*cookieVals[2][0] + ing4*cookieVals[3][0]
|
||||
dur := ing1*cookieVals[0][1] + ing2*cookieVals[1][1] + ing3*cookieVals[2][1] + ing4*cookieVals[3][1]
|
||||
fla := ing1*cookieVals[0][2] + ing2*cookieVals[1][2] + ing3*cookieVals[2][2] + ing4*cookieVals[3][2]
|
||||
tex := ing1*cookieVals[0][3] + ing2*cookieVals[1][3] + ing3*cookieVals[2][3] + ing4*cookieVals[3][3]
|
||||
|
||||
cal := ing1*cookieVals[0][4] + ing2*cookieVals[1][4] + ing3*cookieVals[2][4] + ing4*cookieVals[3][4]
|
||||
|
||||
// make negatives zero, without this two negative scores could
|
||||
// make a very large positive
|
||||
cap = mathy.MaxInt(0, cap)
|
||||
dur = mathy.MaxInt(0, dur)
|
||||
fla = mathy.MaxInt(0, fla)
|
||||
tex = mathy.MaxInt(0, tex)
|
||||
|
||||
score := cap * dur * fla * tex
|
||||
|
||||
if cal == 500 {
|
||||
best500CalScore = mathy.MaxInt(best500CalScore, score)
|
||||
}
|
||||
bestScore = mathy.MaxInt(bestScore, score)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestScore, best500CalScore
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_cookieScience(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want1 int
|
||||
want2 int
|
||||
}{
|
||||
{"actual both parts", util.ReadFile("input.txt"), 13882464, 11171160},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got1, got2 := cookieScience(tt.input)
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("cookieScience() part1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
if got2 != tt.want2 {
|
||||
t.Errorf("cookieScience() part2 = %v, want %v", got2, tt.want2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := auntSue(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
var targetSue = map[string]int{
|
||||
"children": 3,
|
||||
"cats": 7,
|
||||
"samoyeds": 2,
|
||||
"pomeranians": 3,
|
||||
"akitas": 0,
|
||||
"vizslas": 0,
|
||||
"goldfish": 5,
|
||||
"trees": 3,
|
||||
"cars": 2,
|
||||
"perfumes": 1,
|
||||
}
|
||||
|
||||
func auntSue(input string, part int) int {
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
var thing1, thing2, thing3 string
|
||||
var sueNum, amount1, amount2, amount3 int
|
||||
// Sue 1: goldfish: 6, trees: 9, akitas: 0
|
||||
_, err := fmt.Sscanf(line, "Sue %d: %s %d, %s %d, %s %d",
|
||||
&sueNum, &thing1, &amount1, &thing2, &amount2, &thing3, &amount3)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
thing1 = strings.Trim(thing1, ":")
|
||||
thing2 = strings.Trim(thing2, ":")
|
||||
thing3 = strings.Trim(thing3, ":")
|
||||
|
||||
// put it in a map for part 2 to make it easy to look up a particular
|
||||
// thing's count
|
||||
readingsMap := map[string]int{}
|
||||
readingsMap[thing1] = amount1
|
||||
readingsMap[thing2] = amount2
|
||||
readingsMap[thing3] = amount3
|
||||
|
||||
if part == 1 {
|
||||
allMatch := true
|
||||
for thing, amount := range readingsMap {
|
||||
if targetSue[thing] != amount {
|
||||
allMatch = false
|
||||
}
|
||||
}
|
||||
if allMatch {
|
||||
return sueNum
|
||||
}
|
||||
} else {
|
||||
allRulesMatched := true
|
||||
// check ranges where the scanned number is LESS than target's
|
||||
for _, check := range []string{"cats", "trees"} {
|
||||
if scanCount, found := readingsMap[check]; found {
|
||||
if scanCount <= targetSue[check] {
|
||||
allRulesMatched = false
|
||||
}
|
||||
delete(readingsMap, check)
|
||||
}
|
||||
}
|
||||
// check ranges where chaned number is MORE than target's
|
||||
for _, check := range []string{"pomeranians", "goldfish"} {
|
||||
if scanCount, found := readingsMap[check]; found {
|
||||
if scanCount >= targetSue[check] {
|
||||
allRulesMatched = false
|
||||
}
|
||||
delete(readingsMap, check)
|
||||
}
|
||||
}
|
||||
|
||||
// check literal amounts
|
||||
for thing, amount := range readingsMap {
|
||||
if targetSue[thing] != amount {
|
||||
allRulesMatched = false
|
||||
}
|
||||
}
|
||||
if allRulesMatched {
|
||||
return sueNum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic("expect return from loop")
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_auntSue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, 103},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, 405},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := auntSue(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("auntSue() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := eggnogCombinations(util.ReadFile("./input.txt"), 150, part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func eggnogCombinations(input string, target int, part int) int {
|
||||
var nums []int
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
nums = append(nums, cast.ToInt(line))
|
||||
}
|
||||
|
||||
allIndexCombinations := backtrack(nums, 0, target, []int{})
|
||||
// part 1, just return len
|
||||
if part == 1 {
|
||||
return len(allIndexCombinations)
|
||||
}
|
||||
|
||||
// part 2, get the number of combinations w/ the lowest length
|
||||
minLen := math.MaxInt32
|
||||
for _, comb := range allIndexCombinations {
|
||||
minLen = mathy.MinInt(minLen, len(comb))
|
||||
}
|
||||
|
||||
var count int
|
||||
for _, comb := range allIndexCombinations {
|
||||
if len(comb) == minLen {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func backtrack(nums []int, startingIndex, remaining int, usedIndices []int) [][]int {
|
||||
if remaining == 0 {
|
||||
return [][]int{append([]int{}, usedIndices...)}
|
||||
}
|
||||
if remaining < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var validReturns [][]int
|
||||
for i := startingIndex; i < len(nums); i++ {
|
||||
usedIndices = append(usedIndices, i)
|
||||
validReturns = append(validReturns, backtrack(nums, i+1, remaining-nums[i], usedIndices)...)
|
||||
usedIndices = usedIndices[:len(usedIndices)-1]
|
||||
}
|
||||
return validReturns
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `20
|
||||
15
|
||||
10
|
||||
5
|
||||
5`
|
||||
|
||||
func Test_eggnogCombinations(t *testing.T) {
|
||||
type args struct {
|
||||
input string
|
||||
target int
|
||||
part int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{"part1 example", args{example, 25, 1}, 4},
|
||||
{"part1 actual", args{util.ReadFile("input.txt"), 150, 1}, 1304},
|
||||
{"part2 example", args{example, 25, 2}, 3},
|
||||
{"part2 actual", args{util.ReadFile("input.txt"), 150, 2}, 18},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := eggnogCombinations(tt.args.input, tt.args.target, tt.args.part); got != tt.want {
|
||||
t.Errorf("eggnogCombinations() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := gameOfLight(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func gameOfLight(input string, part int) int {
|
||||
var grid [][]string
|
||||
for _, row := range strings.Split(input, "\n") {
|
||||
grid = append(grid, strings.Split(row, ""))
|
||||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
grid = tick(grid)
|
||||
if part == 2 {
|
||||
grid[0][0] = "#"
|
||||
grid[0][len(grid[0])-1] = "#"
|
||||
grid[len(grid)-1][0] = "#"
|
||||
grid[len(grid)-1][len(grid[0])-1] = "#"
|
||||
}
|
||||
}
|
||||
|
||||
var count int
|
||||
for _, row := range grid {
|
||||
for _, c := range row {
|
||||
if c == "#" {
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func tick(grid [][]string) [][]string {
|
||||
var nextGrid [][]string
|
||||
for r, row := range grid {
|
||||
nextGrid = append(nextGrid, make([]string, len(grid[0])))
|
||||
for c, cell := range row {
|
||||
var neighbors int
|
||||
for rDiff := -1; rDiff <= 1; rDiff++ {
|
||||
for cDiff := -1; cDiff <= 1; cDiff++ {
|
||||
if !(rDiff == 0 && cDiff == 0) {
|
||||
nextRow := r + rDiff
|
||||
nextCol := c + cDiff
|
||||
if nextRow >= 0 && nextRow < len(grid) && nextCol >= 0 && nextCol < len(grid[0]) &&
|
||||
grid[nextRow][nextCol] == "#" {
|
||||
neighbors++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if cell == "#" && (neighbors == 2 || neighbors == 3) {
|
||||
nextGrid[r][c] = "#"
|
||||
} else if cell == "." && neighbors == 3 {
|
||||
nextGrid[r][c] = "#"
|
||||
} else {
|
||||
nextGrid[r][c] = "."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nextGrid
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_gameOfLight(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, 768},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, 781},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := gameOfLight(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("gameOfLight() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans int
|
||||
if part == 1 {
|
||||
ans = part1(util.ReadFile("./input.txt"))
|
||||
} else {
|
||||
ans = part2(util.ReadFile("./input.txt"))
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
graph, starting := parseInput(input)
|
||||
|
||||
possibles := map[string]bool{}
|
||||
|
||||
for i, mol := range starting {
|
||||
if products, ok := graph[mol]; ok {
|
||||
for _, p := range products {
|
||||
starting[i] = p
|
||||
possibles[strings.Join(starting, "")] = true
|
||||
}
|
||||
}
|
||||
// reset
|
||||
starting[i] = mol
|
||||
}
|
||||
|
||||
return len(possibles)
|
||||
}
|
||||
|
||||
func parseInput(input string) (graph map[string][]string, startingMaterial []string) {
|
||||
blocks := strings.Split(input, "\n\n")
|
||||
startingMaterial = splitMolecules(blocks[1])
|
||||
|
||||
graph = map[string][]string{}
|
||||
|
||||
for _, l := range strings.Split(blocks[0], "\n") {
|
||||
parts := strings.Split(l, " => ")
|
||||
graph[parts[0]] = append(graph[parts[0]], parts[1])
|
||||
}
|
||||
|
||||
return graph, startingMaterial
|
||||
}
|
||||
|
||||
func splitMolecules(input string) []string {
|
||||
var molecules []string
|
||||
for _, char := range input {
|
||||
code := cast.ToASCIICode(char)
|
||||
if code >= cast.ASCIICodeCapA && code <= cast.ASCIICodeCapZ {
|
||||
molecules = append(molecules, string(char))
|
||||
} else {
|
||||
molecules[len(molecules)-1] += string(char)
|
||||
}
|
||||
}
|
||||
return molecules
|
||||
}
|
||||
|
||||
// This makes some very large assumptions about the answer, but it all revolves
|
||||
// around the fact that there is only one solution for the given input.
|
||||
// It also assumes that some products need to be replaced by their reactants
|
||||
// in a particular order, and when those replacements are made, ALL instances
|
||||
// of that product can be replaced by its reactant
|
||||
//
|
||||
// I should learn CYK...
|
||||
// Other things I tried initially were A* starting from 'e', A* from the final
|
||||
// molecule, but the space was huge.
|
||||
func part2(input string) int {
|
||||
reverseGraph, startingMols := parseInput(input)
|
||||
|
||||
// reverse the graph so it's products to reactants
|
||||
productToReactant := map[string]string{}
|
||||
for react, products := range reverseGraph {
|
||||
for _, p := range products {
|
||||
if _, ok := productToReactant[p]; ok {
|
||||
panic("dup found")
|
||||
}
|
||||
productToReactant[p] = react
|
||||
}
|
||||
}
|
||||
|
||||
// slice of all products to have an order of which products to replace
|
||||
var allProducts []string
|
||||
for prod := range productToReactant {
|
||||
allProducts = append(allProducts, prod)
|
||||
}
|
||||
|
||||
start := strings.Join(startingMols, "")
|
||||
mol := start
|
||||
|
||||
var steps int
|
||||
for mol != "e" {
|
||||
var changeMade bool
|
||||
for _, prod := range allProducts {
|
||||
count := strings.Count(mol, prod)
|
||||
if count <= 0 {
|
||||
continue
|
||||
}
|
||||
changeMade = true
|
||||
steps += count
|
||||
mol = strings.ReplaceAll(mol, prod, productToReactant[prod])
|
||||
// break out to restart from the beginning of allProducts slice
|
||||
break
|
||||
}
|
||||
// if no change was made, then this ordering of allProducts will not
|
||||
// resolve in an electron, shuffle and reset mol and steps
|
||||
if !changeMade {
|
||||
allProducts = shuffleSlice(allProducts)
|
||||
mol = start
|
||||
steps = 0
|
||||
}
|
||||
}
|
||||
|
||||
return steps
|
||||
}
|
||||
|
||||
var rn = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
func shuffleSlice(in []string) []string {
|
||||
// shuffle slice, lazy sorting method
|
||||
sort.Slice(in, func(i, j int) bool {
|
||||
return rn.Intn(2) == 1
|
||||
})
|
||||
return in
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `H => HO
|
||||
H => OH
|
||||
O => HH
|
||||
|
||||
HOH`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"example", example, 4},
|
||||
{"example", example + "OHO", 7},
|
||||
{"actual", util.ReadFile("input.txt"), 576},
|
||||
}
|
||||
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 part2Example = `e => H
|
||||
e => O
|
||||
H => HO
|
||||
H => OH
|
||||
O => HH
|
||||
|
||||
HOH`
|
||||
|
||||
func Test_part2(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"example", part2Example, 3},
|
||||
{"example", part2Example + "OHO", 6},
|
||||
{"actual", util.ReadFile("input.txt"), 207},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := infiniteElvesAndHouses(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func infiniteElvesAndHouses(input string, part int) int {
|
||||
targetNum := cast.ToInt(input)
|
||||
|
||||
for house := 1; house < math.MaxInt32; house++ {
|
||||
var gifts int
|
||||
for _, factor := range getFactors(house) {
|
||||
if part == 1 {
|
||||
gifts += factor * 10
|
||||
} else if part == 2 && house/factor <= 50 {
|
||||
// for part 2, ensure that this is the 50th or less house that
|
||||
// the elf has visited before adding gifts
|
||||
gifts += factor * 11
|
||||
}
|
||||
}
|
||||
if gifts >= targetNum {
|
||||
return house
|
||||
}
|
||||
}
|
||||
|
||||
panic("expect return from loop")
|
||||
}
|
||||
|
||||
func getFactors(num int) []int {
|
||||
var factors []int
|
||||
sqrt := int(math.Sqrt(float64(num)))
|
||||
for i := 1; i <= sqrt; i++ {
|
||||
if num%i == 0 {
|
||||
factors = append(factors, i, num/i)
|
||||
}
|
||||
}
|
||||
return factors
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_infiniteElvesAndHouses(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, 776160},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, 786240},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := infiniteElvesAndHouses(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("infiniteElvesAndHouses() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ans1, ans2 := rpgSimulator(util.ReadFile("./input.txt"))
|
||||
fmt.Printf("Part1: %d\nPart2: %d\n", ans1, ans2)
|
||||
}
|
||||
|
||||
func rpgSimulator(input string) (rpgSimulator, part2 int) {
|
||||
bossHP, bossDamage, bossArmor, shopWeapons, shopArmor, shopRings := parseInput(input)
|
||||
|
||||
// all attacks do at least 1 damage
|
||||
// damage equal to attack - defenders armor
|
||||
// armor is optional, limit 1
|
||||
// 0-2 rings allowed
|
||||
// player attacks first
|
||||
// must buy exactly 1 weapon
|
||||
var combinations [][]item
|
||||
for weapon := 0; weapon < len(shopWeapons); weapon++ {
|
||||
for armor := -1; armor < len(shopArmor); armor++ {
|
||||
for ring1 := -1; ring1 < len(shopRings); ring1++ {
|
||||
for ring2 := -1; ring2 < len(shopRings); ring2++ {
|
||||
comb := []item{shopWeapons[weapon]}
|
||||
if armor != -1 {
|
||||
comb = append(comb, shopArmor[armor])
|
||||
}
|
||||
if ring1 != -1 {
|
||||
comb = append(comb, shopRings[ring1])
|
||||
}
|
||||
if ring2 != -1 && ring2 != ring1 {
|
||||
comb = append(comb, shopRings[ring2])
|
||||
|
||||
}
|
||||
|
||||
combinations = append(combinations, comb)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
minCost := math.MaxInt32
|
||||
var maxCost int
|
||||
for _, comb := range combinations {
|
||||
myHP := 100
|
||||
var myDamage, myArmor, cost int
|
||||
for _, it := range comb {
|
||||
myDamage += it.damage
|
||||
myArmor += it.armor
|
||||
cost += it.cost
|
||||
}
|
||||
playerWins := simulateBattle(bossHP, bossDamage, bossArmor, myHP, myDamage, myArmor)
|
||||
if playerWins {
|
||||
// part 1, min cost to win
|
||||
minCost = mathy.MinInt(minCost, cost)
|
||||
} else {
|
||||
// part 2, max cost to still lose
|
||||
maxCost = mathy.MaxInt(maxCost, cost)
|
||||
}
|
||||
}
|
||||
|
||||
return minCost, maxCost
|
||||
}
|
||||
|
||||
func simulateBattle(bossHP, bossDamage, bossArmor, myHP, myDamage, myArmor int) (playerWins bool) {
|
||||
attackOnBoss := myDamage - bossArmor
|
||||
attackOnPlayer := bossDamage - myArmor
|
||||
attackOnBoss = mathy.MaxInt(attackOnBoss, 1)
|
||||
attackOnPlayer = mathy.MaxInt(attackOnPlayer, 1)
|
||||
for bossHP > 0 && myHP > 0 {
|
||||
bossHP -= attackOnBoss
|
||||
myHP -= attackOnPlayer
|
||||
}
|
||||
|
||||
// the boss takes damage first, so if it hit zero or less, then the player
|
||||
// won the round (potentially with very little HP left)
|
||||
return bossHP <= 0
|
||||
}
|
||||
|
||||
type item struct {
|
||||
name string
|
||||
cost, damage, armor int
|
||||
}
|
||||
|
||||
var shop = `Weapons: Cost Damage Armor
|
||||
Dagger 8 4 0
|
||||
Shortsword 10 5 0
|
||||
Warhammer 25 6 0
|
||||
Longsword 40 7 0
|
||||
Greataxe 74 8 0
|
||||
|
||||
Armor: Cost Damage Armor
|
||||
Leather 13 0 1
|
||||
Chainmail 31 0 2
|
||||
Splintmail 53 0 3
|
||||
Bandedmail 75 0 4
|
||||
Platemail 102 0 5
|
||||
|
||||
Rings: Cost Damage Armor
|
||||
Damage +1 25 1 0
|
||||
Damage +2 50 2 0
|
||||
Damage +3 100 3 0
|
||||
Defense +1 20 0 1
|
||||
Defense +2 40 0 2
|
||||
Defense +3 80 0 3`
|
||||
|
||||
func parseInput(input string) (hp, damage, armor int, shopWeapons, shopArmor, shopRings []item) {
|
||||
lines := strings.Split(input, "\n")
|
||||
hp = cast.ToInt(strings.Split(lines[0], ": ")[1])
|
||||
damage = cast.ToInt(strings.Split(lines[1], ": ")[1])
|
||||
armor = cast.ToInt(strings.Split(lines[2], ": ")[1])
|
||||
|
||||
shopBlocks := strings.Split(shop, "\n\n")
|
||||
for blockIndex := range shopBlocks {
|
||||
for _, line := range strings.Split(shopBlocks[blockIndex], "\n")[1:] {
|
||||
it := item{}
|
||||
if blockIndex == 2 {
|
||||
// gross, have to get rid of whitespace in name for the rings
|
||||
line = strings.ReplaceAll(line, "e +", "e+")
|
||||
}
|
||||
_, err := fmt.Sscanf(line, "%s %d %d %d", &it.name, &it.cost, &it.damage, &it.armor)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
switch blockIndex {
|
||||
case 0:
|
||||
shopWeapons = append(shopWeapons, it)
|
||||
case 1:
|
||||
shopArmor = append(shopArmor, it)
|
||||
case 2:
|
||||
shopRings = append(shopRings, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hp, damage, armor, shopWeapons, shopArmor, shopRings
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_rpgSimulator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want1 int
|
||||
want2 int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 121, 201},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got1, got2 := rpgSimulator(tt.input)
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("rpgSimulator() = %v, want1 %v", got1, tt.want1)
|
||||
}
|
||||
if got2 != tt.want2 {
|
||||
t.Errorf("rpgSimulator() = %v, want2 %v", got2, tt.want2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := wizardSimulator(util.ReadFile("./input.txt"), 50, 500, part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func wizardSimulator(input string, myHP, myMana, part int) int {
|
||||
lines := strings.Split(input, "\n")
|
||||
bossHP := cast.ToInt(strings.Split(lines[0], ": ")[1])
|
||||
bossDamage := cast.ToInt(strings.Split(lines[1], ": ")[1])
|
||||
|
||||
initState := newBattleState(myHP, myMana, bossHP, bossDamage, [5]int{}, true, 0)
|
||||
|
||||
return simBattle(initState, map[string]int{}, part)
|
||||
}
|
||||
|
||||
// Spell struct is used to generalize all spell types by leveraging zero values.
|
||||
// The zero value for ints is 0 (which can be added with no effect)
|
||||
type spell struct {
|
||||
name string // redundant, for debugging
|
||||
index int // for indexing in an array (which is easily passed by value)
|
||||
cost int
|
||||
effectLength int
|
||||
instantDamage int
|
||||
instantHeal int
|
||||
effectDamage int
|
||||
heal int
|
||||
armorBuff int
|
||||
manaRecharge int
|
||||
}
|
||||
|
||||
var spellsMap = map[string]spell{
|
||||
"Magic Missile": {
|
||||
name: "Magic Missile",
|
||||
index: 0,
|
||||
cost: 53,
|
||||
instantDamage: 4,
|
||||
},
|
||||
"Drain": {
|
||||
name: "Drain",
|
||||
index: 1,
|
||||
cost: 73,
|
||||
instantDamage: 2,
|
||||
instantHeal: 2,
|
||||
},
|
||||
"Shield": {
|
||||
name: "Shield",
|
||||
index: 2,
|
||||
cost: 113,
|
||||
effectLength: 6,
|
||||
armorBuff: 7, // does not stack for each turn
|
||||
},
|
||||
"Poison": {
|
||||
name: "Poison",
|
||||
index: 3,
|
||||
cost: 173,
|
||||
effectLength: 6,
|
||||
effectDamage: 3,
|
||||
},
|
||||
"Recharge": {
|
||||
name: "Recharge",
|
||||
index: 4,
|
||||
cost: 229,
|
||||
effectLength: 5,
|
||||
manaRecharge: 101,
|
||||
},
|
||||
}
|
||||
|
||||
type battleState struct {
|
||||
myHP int
|
||||
myMana int
|
||||
bossHP int
|
||||
bossDamage int
|
||||
effectDurations [5]int
|
||||
isMyTurn bool
|
||||
depth int // recursive branch depth, for debugging
|
||||
}
|
||||
|
||||
func newBattleState(myHP, myMana, bossHP, bossDamage int, effectDurations [5]int, isMyTurn bool, depth int) battleState {
|
||||
return battleState{
|
||||
myHP: myHP,
|
||||
myMana: myMana,
|
||||
bossHP: bossHP,
|
||||
bossDamage: bossDamage,
|
||||
effectDurations: effectDurations,
|
||||
isMyTurn: isMyTurn,
|
||||
depth: depth,
|
||||
}
|
||||
}
|
||||
|
||||
func (s battleState) hashKey() string {
|
||||
return fmt.Sprintf("%d_%d_%d_%v_%v", s.myHP, s.myMana, s.bossHP, s.effectDurations, s.isMyTurn)
|
||||
}
|
||||
|
||||
func simBattle(state battleState, memo map[string]int, part int) (minMana int) {
|
||||
// check cache
|
||||
hash := state.hashKey()
|
||||
if val, ok := memo[hash]; ok {
|
||||
return val
|
||||
}
|
||||
|
||||
if part == 2 && state.isMyTurn {
|
||||
state.myHP--
|
||||
}
|
||||
|
||||
// check myHP after a potential part 2 HP loss, if player dies, then return
|
||||
// a huge number which will essentially be ignored by a mathy.MinInt comparison
|
||||
if state.myHP <= 0 {
|
||||
return math.MaxInt32
|
||||
}
|
||||
|
||||
// apply any active spell effects
|
||||
var myArmor int
|
||||
for _, sp := range spellsMap {
|
||||
if state.effectDurations[sp.index] > 0 {
|
||||
state.effectDurations[sp.index]--
|
||||
// many of values will be zero for any given spell
|
||||
state.bossHP -= sp.effectDamage
|
||||
state.myHP += sp.heal
|
||||
myArmor += sp.armorBuff
|
||||
state.myMana += sp.manaRecharge
|
||||
}
|
||||
}
|
||||
|
||||
// check bossHP after effects take place, it could die form poison
|
||||
if state.bossHP <= 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// get minMana from the current state, to a player win
|
||||
minMana = math.MaxInt32
|
||||
if state.isMyTurn {
|
||||
// iterate through spells, create a recursive call for each spell that
|
||||
// can be called (i.e. its effectDuration index is zero)
|
||||
var spellCasted bool
|
||||
for _, sp := range spellsMap {
|
||||
if state.effectDurations[sp.index] == 0 {
|
||||
if state.myMana >= sp.cost {
|
||||
spellCasted = true
|
||||
// make new durations array & add effect duration for this spell
|
||||
newDurations := state.effectDurations
|
||||
newDurations[sp.index] += sp.effectLength
|
||||
|
||||
nextState := newBattleState(state.myHP+sp.instantHeal,
|
||||
state.myMana-sp.cost,
|
||||
state.bossHP-sp.instantDamage,
|
||||
state.bossDamage,
|
||||
newDurations,
|
||||
false,
|
||||
state.depth+1,
|
||||
)
|
||||
|
||||
castResult := sp.cost + simBattle(nextState, memo, part)
|
||||
|
||||
minMana = mathy.MinInt(minMana, castResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
// if cannot cast spell, player loses
|
||||
if !spellCasted {
|
||||
return math.MaxInt32
|
||||
}
|
||||
} else {
|
||||
// boss's turn, boss attacks w/ a minimum damage of 1
|
||||
attackDamage := mathy.MaxInt(1, state.bossDamage-myArmor)
|
||||
|
||||
// recurse w/ next state
|
||||
nextState := newBattleState(state.myHP-attackDamage,
|
||||
state.myMana,
|
||||
state.bossHP,
|
||||
state.bossDamage,
|
||||
state.effectDurations,
|
||||
true,
|
||||
state.depth+1,
|
||||
)
|
||||
bossAttackResult := simBattle(nextState, memo, part)
|
||||
|
||||
minMana = mathy.MinInt(minMana, bossAttackResult)
|
||||
}
|
||||
|
||||
// add to memoized to prevent unnecessary recursive branches, then return
|
||||
memo[hash] = minMana
|
||||
return minMana
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_wizardSimulator(t *testing.T) {
|
||||
type args struct {
|
||||
input string
|
||||
myHP int
|
||||
myMana int
|
||||
part int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "example",
|
||||
args: args{"Hit Points: 13\nDamage: 8", 10, 250, 1},
|
||||
want: spellsMap["Poison"].cost + spellsMap["Magic Missile"].cost,
|
||||
},
|
||||
{"part1 actual", args{util.ReadFile("input.txt"), 50, 500, 1}, 953},
|
||||
{"part2 actual", args{util.ReadFile("input.txt"), 50, 500, 2}, 1289},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := wizardSimulator(tt.args.input, tt.args.myHP, tt.args.myMana, tt.args.part); got != tt.want {
|
||||
t.Errorf("wizardSimulator() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := simpleAssemblyComputer(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func simpleAssemblyComputer(input string, part int) int {
|
||||
instructions := strings.Split(input, "\n")
|
||||
var index int
|
||||
|
||||
registers := map[string]int{}
|
||||
if part == 2 {
|
||||
registers["a"] = 1
|
||||
}
|
||||
|
||||
for index < len(instructions) {
|
||||
parts := strings.Split(instructions[index], " ")
|
||||
switch parts[0] {
|
||||
case "hlf":
|
||||
reg := parts[1]
|
||||
registers[reg] /= 2
|
||||
index++
|
||||
case "tpl":
|
||||
reg := parts[1]
|
||||
registers[reg] *= 3
|
||||
index++
|
||||
case "inc":
|
||||
reg := parts[1]
|
||||
registers[reg]++
|
||||
index++
|
||||
case "jmp":
|
||||
diff := cast.ToInt(parts[1])
|
||||
index += diff
|
||||
case "jie":
|
||||
reg := strings.Trim(parts[1], ",")
|
||||
diff := cast.ToInt(parts[2])
|
||||
if registers[reg]%2 == 0 {
|
||||
index += diff
|
||||
} else {
|
||||
index++
|
||||
}
|
||||
case "jio":
|
||||
reg := strings.Trim(parts[1], ",")
|
||||
diff := cast.ToInt(parts[2])
|
||||
if registers[reg] == 1 {
|
||||
index += diff
|
||||
} else {
|
||||
index++
|
||||
}
|
||||
default:
|
||||
panic("unhandled instruction type: " + parts[0])
|
||||
}
|
||||
}
|
||||
|
||||
return registers["b"]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_simpleAssemblyComputer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 1, 307},
|
||||
{"actual", util.ReadFile("input.txt"), 2, 160},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := simpleAssemblyComputer(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("simpleAssemblyComputer() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
// first solution to use all the packages?!
|
||||
"github.com/alexchao26/advent-of-code-go/algos"
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/mathy"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := balancingPackages(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func balancingPackages(input string, part int) int {
|
||||
var nums []int
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
nums = append(nums, cast.ToInt(line))
|
||||
}
|
||||
|
||||
sum := mathy.SumIntSlice(nums)
|
||||
|
||||
target := sum / 3
|
||||
if part == 2 {
|
||||
target = sum / 4
|
||||
}
|
||||
|
||||
// make the gross assumption that if a group is found, and adds up to the
|
||||
// target, the remaining elements will be able to be split into two equal
|
||||
// groups. This is not always true, but the inputs are nicely generated
|
||||
var individualGroups [][]int
|
||||
for groupLen := 2; len(individualGroups) == 0; groupLen++ {
|
||||
individualGroups = algos.CombinationsInts(nums, groupLen)
|
||||
|
||||
// validate that a combination adds up to the target sum
|
||||
var validGroups [][]int
|
||||
for _, gr := range individualGroups {
|
||||
if mathy.SumIntSlice(gr) == target {
|
||||
validGroups = append(validGroups, gr)
|
||||
}
|
||||
}
|
||||
// reassign individual groups, if len(validGroups) == 0; we need to rerun
|
||||
// with a larger groupLen
|
||||
individualGroups = validGroups
|
||||
}
|
||||
|
||||
individualGroups = sortGroups(individualGroups)
|
||||
|
||||
return quantumEntanglement(individualGroups[0])
|
||||
}
|
||||
|
||||
func sortGroups(groups [][]int) [][]int {
|
||||
clone := append([][]int{}, groups...)
|
||||
sort.Slice(clone, func(i, j int) bool {
|
||||
return quantumEntanglement(clone[i]) < quantumEntanglement(clone[j])
|
||||
})
|
||||
return clone
|
||||
}
|
||||
|
||||
var quantumEntanglement = mathy.MultiplyIntSlice
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_balancingPackages(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, 10439961859},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, 72050269},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := balancingPackages(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("balancingPackages() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := letItSnow(util.ReadFile("./input.txt"))
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func letItSnow(input string) int {
|
||||
var row, col int
|
||||
input = strings.ReplaceAll(input, ",", "")
|
||||
input = strings.ReplaceAll(input, ".", "")
|
||||
for _, part := range strings.Split(input, " ") {
|
||||
if regexp.MustCompile("[0-9]").MatchString(part) {
|
||||
if row == 0 { // jeez i am getting lazy
|
||||
row = cast.ToInt(part)
|
||||
} else {
|
||||
col = cast.ToInt(part)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the number of iterations to run can be calculated by:
|
||||
// - finding the number of cells that in the triangle that is formed by each
|
||||
// diagonal prior to the incomplete one that the target cell is on
|
||||
// This triangle is generated from adding 1+2+3+4...+(row+col) 0-indexed
|
||||
// - then the number of iterations for the incomplete diagonal, is equal to
|
||||
// the current column number (1-indexed)
|
||||
var triangleBefore int
|
||||
for i := 1; i <= row+col-2; i++ {
|
||||
triangleBefore += i
|
||||
}
|
||||
|
||||
numberOnThisDiagonal := col
|
||||
|
||||
// subtract one for starting cell
|
||||
iterations := triangleBefore + numberOnThisDiagonal - 1
|
||||
|
||||
// and thankfully this runs quickly
|
||||
code := 20151125
|
||||
for i := 0; i < iterations; i++ {
|
||||
code *= 252533
|
||||
code %= 33554393
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_letItSnow(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 19980801},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := letItSnow(tt.input); got != tt.want {
|
||||
t.Errorf("letItSnow() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/mathy"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := taxicab(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
var dirs = [][2]int{
|
||||
{-1, 0}, // north
|
||||
{0, 1}, // east
|
||||
{1, 0}, // south
|
||||
{0, -1}, // west
|
||||
}
|
||||
|
||||
func taxicab(input string, part int) int {
|
||||
var dirIndex int // start facing north
|
||||
var row, col int
|
||||
|
||||
visited := map[[2]int]bool{
|
||||
{0, 0}: true,
|
||||
}
|
||||
|
||||
for _, inst := range strings.Split(input, ", ") {
|
||||
var turn string
|
||||
var steps int
|
||||
fmt.Sscanf(inst, "%1s%d", &turn, &steps)
|
||||
if turn == "R" {
|
||||
dirIndex = (dirIndex + 1) % 4
|
||||
} else if turn == "L" {
|
||||
dirIndex = (dirIndex + 3) % 4
|
||||
} else {
|
||||
panic("unhandled turning direction " + turn)
|
||||
}
|
||||
|
||||
for i := 0; i < steps; i++ {
|
||||
// move forward one step at a time
|
||||
row += dirs[dirIndex][0]
|
||||
col += dirs[dirIndex][1]
|
||||
if visited[[2]int{row, col}] && part == 2 {
|
||||
return mathy.ManhattanDistance(0, 0, row, col)
|
||||
}
|
||||
visited[[2]int{row, col}] = true
|
||||
}
|
||||
}
|
||||
|
||||
return mathy.ManhattanDistance(0, 0, row, col)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_taxicab(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 1, 234},
|
||||
{"actual", util.ReadFile("input.txt"), 2, 113},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := taxicab(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("taxicab() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := part1(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func part1(input string, part int) string {
|
||||
directions := parseInput(input)
|
||||
|
||||
var pressedButtons string
|
||||
|
||||
// set starting row, col and phone pad based on part number
|
||||
// pad the borders of phonePad with spaces to make border collision logic
|
||||
// the same in both parts
|
||||
// used a space instead of empty string so it's easier to look at...
|
||||
row, col := 2, 2
|
||||
phonePad := [][]string{
|
||||
{" ", " ", " ", " ", " "},
|
||||
{" ", "1", "2", "3", " "},
|
||||
{" ", "4", "5", "6", " "},
|
||||
{" ", "7", "8", "9", " "},
|
||||
{" ", " ", " ", " ", " "},
|
||||
}
|
||||
if part == 2 {
|
||||
phonePad = [][]string{
|
||||
{" ", " ", " ", " ", " ", " ", " "},
|
||||
{" ", " ", " ", "1", " ", " ", " "},
|
||||
{" ", " ", "2", "3", "4", " ", " "},
|
||||
{" ", "5", "6", "7", "8", "9", " "},
|
||||
{" ", " ", "A", "B", "C", " ", " "},
|
||||
{" ", " ", " ", "D", " ", " ", " "},
|
||||
{" ", " ", " ", " ", " ", " ", " "},
|
||||
}
|
||||
row, col = 3, 1
|
||||
}
|
||||
for _, list := range directions {
|
||||
for _, direction := range list {
|
||||
switch direction {
|
||||
case "U":
|
||||
if phonePad[row-1][col] != " " {
|
||||
row--
|
||||
}
|
||||
case "D":
|
||||
if phonePad[row+1][col] != " " {
|
||||
row++
|
||||
}
|
||||
case "L":
|
||||
if phonePad[row][col-1] != " " {
|
||||
col--
|
||||
}
|
||||
case "R":
|
||||
if phonePad[row][col+1] != " " {
|
||||
col++
|
||||
}
|
||||
default:
|
||||
panic("unhandled direction: " + direction)
|
||||
}
|
||||
}
|
||||
pressedButtons += phonePad[row][col]
|
||||
}
|
||||
|
||||
return pressedButtons
|
||||
}
|
||||
|
||||
func parseInput(input string) (ans [][]string) {
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
ans = append(ans, strings.Split(line, ""))
|
||||
}
|
||||
return ans
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want string
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 1, "12578"},
|
||||
{"actual", util.ReadFile("input.txt"), 2, "516DD"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := part1(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("part1() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := countValidTriangles(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func countValidTriangles(input string, part int) int {
|
||||
triangleEdges := parseInput(input)
|
||||
|
||||
if part == 2 {
|
||||
triangleEdges = transformTriangles(triangleEdges)
|
||||
}
|
||||
|
||||
var valid int
|
||||
for _, tri := range triangleEdges {
|
||||
// lazy, just check the three scenarios
|
||||
if tri[0]+tri[1] <= tri[2] {
|
||||
continue
|
||||
}
|
||||
if tri[0]+tri[2] <= tri[1] {
|
||||
continue
|
||||
}
|
||||
if tri[1]+tri[2] <= tri[0] {
|
||||
continue
|
||||
}
|
||||
valid++
|
||||
}
|
||||
|
||||
return valid
|
||||
}
|
||||
|
||||
var multipleSpaces = regexp.MustCompile("[\\s]{2,}")
|
||||
|
||||
func parseInput(input string) (ans [][3]int) {
|
||||
lines := strings.Split(input, "\n")
|
||||
for _, l := range lines {
|
||||
l = multipleSpaces.ReplaceAllString(l, " ")
|
||||
var triangleEdges [3]int
|
||||
fmt.Sscanf(l, " %d %d %d", &triangleEdges[0], &triangleEdges[1], &triangleEdges[2])
|
||||
ans = append(ans, triangleEdges)
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
// for part 2 where rows are in columns of 3 for some stupid reason
|
||||
func transformTriangles(triangles [][3]int) [][3]int {
|
||||
var newTriangles [][3]int
|
||||
for i := 0; i < len(triangles); i += 3 {
|
||||
for col := 0; col < 3; col++ {
|
||||
var edge [3]int
|
||||
for row := 0; row < 3; row++ {
|
||||
edge[row] = triangles[i+row][col]
|
||||
}
|
||||
newTriangles = append(newTriangles, edge)
|
||||
}
|
||||
}
|
||||
return newTriangles
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_countValidTriangles(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 1, 862},
|
||||
{"actual", util.ReadFile("input.txt"), 2, 1577},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := countValidTriangles(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("countValidTriangles() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/algos"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
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(util.ReadFile("./input.txt"))
|
||||
fmt.Println("Output:", ans)
|
||||
} else {
|
||||
ans := part2(util.ReadFile("./input.txt"))
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
rooms := parseInput(input)
|
||||
|
||||
rooms = getValidRooms(rooms)
|
||||
var sum int
|
||||
for _, rm := range rooms {
|
||||
sum += rm.sectorID
|
||||
}
|
||||
|
||||
return sum
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
rooms := getValidRooms(parseInput(input))
|
||||
|
||||
for _, rm := range rooms {
|
||||
for i := 0; i < rm.sectorID; i++ {
|
||||
// rotate each character forward
|
||||
for i, part := range rm.nameParts {
|
||||
rm.nameParts[i] = algos.CaesarShift(part, rm.sectorID)
|
||||
}
|
||||
// printed all new name parts and searched for "north" to find what the goal text was
|
||||
if rm.nameParts[0] == "northpole" &&
|
||||
rm.nameParts[1] == "object" &&
|
||||
rm.nameParts[2] == "storage" {
|
||||
return rm.sectorID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic("loop should have returned sectorID")
|
||||
}
|
||||
|
||||
type room struct {
|
||||
nameParts []string
|
||||
sectorID int
|
||||
checksum string
|
||||
}
|
||||
|
||||
func parseInput(input string) (ans []room) {
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
r := room{}
|
||||
parts := strings.Split(line, "-")
|
||||
r.nameParts = parts[:len(parts)-1]
|
||||
fmt.Sscanf(parts[len(parts)-1], "%d[%5s]", &r.sectorID, &r.checksum)
|
||||
|
||||
ans = append(ans, r)
|
||||
}
|
||||
|
||||
return ans
|
||||
}
|
||||
|
||||
func getValidRooms(rooms []room) []room {
|
||||
var validRooms []room
|
||||
for _, rm := range rooms {
|
||||
countChars := map[string]int{}
|
||||
for _, part := range rm.nameParts {
|
||||
for _, char := range part {
|
||||
countChars[string(char)]++
|
||||
}
|
||||
}
|
||||
var allCounts []int
|
||||
for _, v := range countChars {
|
||||
allCounts = append(allCounts, v)
|
||||
}
|
||||
// sort in reverse order so five highest are at the front
|
||||
sort.Sort(sort.Reverse((sort.IntSlice(allCounts))))
|
||||
|
||||
isValid := true
|
||||
|
||||
var counts []int
|
||||
for i, char := range rm.checksum {
|
||||
counts = append(counts, countChars[string(char)])
|
||||
// compare to five highest
|
||||
if counts[i] != allCounts[i] {
|
||||
isValid = false
|
||||
}
|
||||
|
||||
// tie break equal counts
|
||||
if i != 0 {
|
||||
if counts[i-1] < counts[i] ||
|
||||
(counts[i-1] == counts[i] && string(rm.checksum[i-1]) > string(char)) {
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isValid {
|
||||
validRooms = append(validRooms, rm)
|
||||
}
|
||||
}
|
||||
return validRooms
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `aaaaa-bbb-z-y-x-123[abxyz]
|
||||
a-b-c-d-e-f-g-h-987[abcde]
|
||||
not-a-real-room-404[oarel]
|
||||
totally-real-room-200[decoy]`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"example", example, 1514},
|
||||
{"actual", util.ReadFile("input.txt"), 278221},
|
||||
}
|
||||
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
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 267},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := md5Chess(util.ReadFile("./input.txt"), part)
|
||||
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func md5Chess(input string, part int) string {
|
||||
passwordParts := map[int]string{}
|
||||
var part1Index int
|
||||
|
||||
for i := 0; len(passwordParts) < 8; i++ {
|
||||
in := fmt.Sprintf("%s%d", input, i)
|
||||
hash := fmt.Sprintf("%x", md5.Sum([]byte(in)))
|
||||
|
||||
if strings.HasPrefix(hash, "00000") {
|
||||
if part == 1 {
|
||||
passwordParts[part1Index] = hash[5:6]
|
||||
part1Index++
|
||||
} else {
|
||||
if regexp.MustCompile("[0-7]").MatchString(hash[5:6]) {
|
||||
index := cast.ToInt(hash[5:6])
|
||||
if _, ok := passwordParts[index]; !ok {
|
||||
value := hash[6:7]
|
||||
passwordParts[index] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var password string
|
||||
for i := 0; i < 8; i++ {
|
||||
password += passwordParts[i]
|
||||
}
|
||||
|
||||
return password
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_md5Chess(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want string
|
||||
}{
|
||||
{"example_part1", "abc", 1, "18f47a30"},
|
||||
{"actual_part1", util.ReadFile("input.txt"), 1, "801b56a7"},
|
||||
{"example_part2", "abc", 2, "05ace8e3"},
|
||||
{"actual_part2", util.ReadFile("input.txt"), 2, "424a0197"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := md5Chess(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("md5Chess() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := signalsAndNoise(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func signalsAndNoise(input string, part int) string {
|
||||
var grid [][]string
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
grid = append(grid, strings.Split(line, ""))
|
||||
}
|
||||
|
||||
var indexMaps []map[string]int
|
||||
for col := 0; col < len(grid[0]); col++ {
|
||||
indexMaps = append(indexMaps, map[string]int{})
|
||||
for row := 0; row < len(grid); row++ {
|
||||
char := grid[row][col]
|
||||
indexMaps[col][char]++
|
||||
}
|
||||
}
|
||||
|
||||
var mostVersion string // part 1
|
||||
var leastVersion string // part 2
|
||||
for col := 0; col < len(indexMaps); col++ {
|
||||
var (
|
||||
mostChar string
|
||||
mostLen int
|
||||
leastChar string
|
||||
leastLen int = math.MaxInt32
|
||||
)
|
||||
for k, count := range indexMaps[col] {
|
||||
if count > mostLen {
|
||||
mostLen = count
|
||||
mostChar = k
|
||||
}
|
||||
if count < leastLen {
|
||||
leastLen = count
|
||||
leastChar = k
|
||||
}
|
||||
}
|
||||
mostVersion += mostChar
|
||||
leastVersion += leastChar
|
||||
}
|
||||
if part == 1 {
|
||||
return mostVersion
|
||||
}
|
||||
return leastVersion
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `eedadn
|
||||
drvtee
|
||||
eandsr
|
||||
raavrd
|
||||
atevrs
|
||||
tsrnev
|
||||
sdttsa
|
||||
rasrtv
|
||||
nssdts
|
||||
ntnada
|
||||
svetve
|
||||
tesnvt
|
||||
vntsnd
|
||||
vrdear
|
||||
dvrsen
|
||||
enarar`
|
||||
|
||||
func Test_signalsAndNoise(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want string
|
||||
}{
|
||||
{"example", example, 1, "easter"},
|
||||
{"actual", util.ReadFile("input.txt"), 1, "afwlyyyq"},
|
||||
{"example", example, 2, "advent"},
|
||||
{"actual", util.ReadFile("input.txt"), 2, "bhkzekao"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := signalsAndNoise(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("signalsAndNoise() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans int
|
||||
if part == 1 {
|
||||
ans = part1(util.ReadFile("./input.txt"))
|
||||
} else {
|
||||
ans = part2(util.ReadFile("./input.txt"))
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
insideBraces, outsideBraces := parseInput(input)
|
||||
|
||||
var count int
|
||||
for i := range insideBraces {
|
||||
inside, outside := insideBraces[i], outsideBraces[i]
|
||||
var insidesHaveABBA bool
|
||||
for _, str := range inside {
|
||||
if hasABBA(str) {
|
||||
insidesHaveABBA = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !insidesHaveABBA {
|
||||
var outsidesHaveABBA bool
|
||||
for _, str := range outside {
|
||||
if hasABBA(str) {
|
||||
outsidesHaveABBA = true
|
||||
}
|
||||
}
|
||||
if outsidesHaveABBA {
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func hasABBA(str string) bool {
|
||||
for i := 3; i < len(str); i++ {
|
||||
// match outsides, match insides, ensure insides and outsides are different
|
||||
if str[i-3] == str[i] && str[i-2] == str[i-1] && str[i] != str[i-1] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
insideBraces, outsideBraces := parseInput(input)
|
||||
|
||||
var count int
|
||||
for i := range insideBraces {
|
||||
inside, outside := insideBraces[i], outsideBraces[i]
|
||||
|
||||
insideABAs := findABAs(inside)
|
||||
outsideABAs := findABAs(outside)
|
||||
|
||||
for aba := range insideABAs {
|
||||
// make new string in pattern BAB and see if it's in the outside hashmap
|
||||
bab := fmt.Sprintf("%s%s%s", aba[1:2], aba[0:1], aba[1:2])
|
||||
if outsideABAs[bab] {
|
||||
count++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func findABAs(strs []string) map[string]bool {
|
||||
found := map[string]bool{}
|
||||
for _, str := range strs {
|
||||
for i := 2; i < len(str); i++ {
|
||||
if str[i-2] == str[i] && str[i] != str[i-1] {
|
||||
found[str[i-2:i+1]] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func parseInput(input string) (insides, outsides [][]string) {
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
var collectChars string
|
||||
var insideBraces, outsideBraces []string
|
||||
|
||||
// lazy, add an open bracket at the end to add the last collected string
|
||||
// to the slice. A tricky input could've broken this logic
|
||||
for _, rn := range line + "[" {
|
||||
switch char := string(rn); char {
|
||||
case "[":
|
||||
outsideBraces = append(outsideBraces, collectChars)
|
||||
collectChars = ""
|
||||
case "]":
|
||||
insideBraces = append(insideBraces, collectChars)
|
||||
collectChars = ""
|
||||
default:
|
||||
collectChars += char
|
||||
}
|
||||
}
|
||||
insides = append(insides, insideBraces)
|
||||
outsides = append(outsides, outsideBraces)
|
||||
}
|
||||
return insides, outsides
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `abba[mnop]qrst
|
||||
abcd[bddb]xyyx
|
||||
aaaa[qwer]tyui
|
||||
ioxxoj[asdfgh]zxcvbn`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"example", example, 2},
|
||||
{"actual", util.ReadFile("input.txt"), 118},
|
||||
}
|
||||
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 = `aba[bab]xyz
|
||||
xyx[xyx]xyx
|
||||
aaa[kek]eke
|
||||
zazbz[bzb]cdb`
|
||||
|
||||
func Test_part2(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"example", example2, 3},
|
||||
// {"actual", util.ReadFile("input.txt"), ACTUAL_ANSWER},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
count, finalString := twoFA(util.ReadFile("./input.txt"), 6, 50)
|
||||
if part == 1 {
|
||||
fmt.Println("Output:", count)
|
||||
} else {
|
||||
fmt.Println("Output:")
|
||||
fmt.Println(finalString)
|
||||
}
|
||||
}
|
||||
|
||||
func twoFA(input string, height, width int) (int, string) {
|
||||
instructions := strings.Split(input, "\n")
|
||||
var grid [][]bool
|
||||
for i := 0; i < height; i++ {
|
||||
grid = append(grid, make([]bool, width))
|
||||
}
|
||||
|
||||
for _, inst := range instructions {
|
||||
if strings.HasPrefix(inst, "rect") {
|
||||
var row, col int
|
||||
fmt.Sscanf(inst, "rect %dx%d", &col, &row)
|
||||
for r := 0; r < row; r++ {
|
||||
for c := 0; c < col; c++ {
|
||||
grid[r][c] = true
|
||||
}
|
||||
}
|
||||
} else if strings.HasPrefix(inst, "rotate row") {
|
||||
var row, by int
|
||||
_, err := fmt.Sscanf(inst, "rotate row y=%d by %d", &row, &by)
|
||||
if err != nil {
|
||||
panic("parsing error on instruction: " + err.Error())
|
||||
}
|
||||
for count := 0; count < by; count++ {
|
||||
store := grid[row][width-1]
|
||||
for i := width - 1; i > 0; i-- {
|
||||
grid[row][i] = grid[row][i-1]
|
||||
}
|
||||
grid[row][0] = store
|
||||
}
|
||||
} else if strings.HasPrefix(inst, "rotate column") {
|
||||
var col, by int
|
||||
_, err := fmt.Sscanf(inst, "rotate column x=%d by %d", &col, &by)
|
||||
if err != nil {
|
||||
panic("parsing error on instruction: " + err.Error())
|
||||
}
|
||||
for count := 0; count < by; count++ {
|
||||
store := grid[height-1][col]
|
||||
for r := height - 1; r > 0; r-- {
|
||||
grid[r][col] = grid[r-1][col]
|
||||
}
|
||||
grid[0][col] = store
|
||||
}
|
||||
} else {
|
||||
panic("unhandled instruction: " + inst)
|
||||
}
|
||||
}
|
||||
|
||||
var count int
|
||||
var finalState string
|
||||
// count on pixels
|
||||
for _, row := range grid {
|
||||
for _, v := range row {
|
||||
if v {
|
||||
finalState += "#"
|
||||
count++
|
||||
} else {
|
||||
finalState += " "
|
||||
}
|
||||
}
|
||||
finalState += "\n"
|
||||
}
|
||||
|
||||
return count, finalState
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `rect 3x2
|
||||
rotate column x=1 by 1
|
||||
rotate row y=0 by 4
|
||||
rotate column x=1 by 1`
|
||||
|
||||
func Test_twoFA(t *testing.T) {
|
||||
type args struct {
|
||||
instructions string
|
||||
height, width int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantCount int
|
||||
wantOutputString string
|
||||
}{
|
||||
{"example", args{example, 3, 7}, 6, " # # #\n# # \n # \n"},
|
||||
{"actual", args{util.ReadFile("input.txt"), 6, 50}, 115, `#### #### #### # ## # #### ### #### ### ##
|
||||
# # # # ## # # # # # # #
|
||||
### ### ### # # ## ### # # ### # #
|
||||
# # # # # # # ### # # #
|
||||
# # # # # # # # # # # # #
|
||||
#### # #### # # # # # # # ### ##
|
||||
`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotCount, gotString := twoFA(tt.args.instructions, tt.args.height, tt.args.width)
|
||||
if gotCount != tt.wantCount {
|
||||
t.Errorf("twoFA().count = %v, want %v", gotCount, tt.wantCount)
|
||||
}
|
||||
if gotString != tt.wantOutputString {
|
||||
t.Errorf("twoFA().outputString = \n%q, want \n%q", gotString, tt.wantOutputString)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := decompressLength(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
// well...... this is gross.......
|
||||
func decompressLength(in string, part int) int {
|
||||
var decompressedLen int
|
||||
for i := 0; i < len(in); {
|
||||
switch in[i] {
|
||||
case '(':
|
||||
// find index of closing paren, then find total length of substring
|
||||
relativeCloseIndex := strings.Index(in[i:], ")")
|
||||
closeIndex := relativeCloseIndex + i
|
||||
|
||||
var copyLen, repeat int
|
||||
fmt.Sscanf(in[i:closeIndex+1], "(%dx%d)", ©Len, &repeat)
|
||||
|
||||
substring := in[closeIndex+1 : closeIndex+1+copyLen]
|
||||
patternLength := len(substring)
|
||||
if part == 2 {
|
||||
patternLength = decompressLength(substring, 2)
|
||||
}
|
||||
decompressedLen += patternLength * repeat
|
||||
// jump the closed paren (+1) the length of the substring from THIS
|
||||
// function call
|
||||
i = closeIndex + 1 + len(substring)
|
||||
default:
|
||||
decompressedLen++
|
||||
i++
|
||||
}
|
||||
}
|
||||
return decompressedLen
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_decompressLength(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"example part1_0", "ADVENT", 1, len("ADVENT")},
|
||||
{"example part1_1", "A(1x5)BC", 1, len("ABBBBBC")},
|
||||
{"example part1_2", "(6x1)(1x3)A", 1, len("(1x3)A")},
|
||||
{"actual part1", util.ReadFile("input.txt"), 1, 107035},
|
||||
{"example part2_1", "X(8x2)(3x3)ABCY", 2, len("XABCABCABCABCABCABCY")},
|
||||
{"example part2_2", "(25x3)(3x3)ABC(2x3)XY(5x2)PQRSTX(18x9)(3x2)TWO(5x7)SEVEN", 2, 445},
|
||||
{"actual part2", util.ReadFile("input.txt"), 2, 11451628995},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := decompressLength(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("decompressLength() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/algos"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans int
|
||||
if part == 1 {
|
||||
ans = balanceBots(util.ReadFile("./input.txt"), []int{17, 61})
|
||||
} else {
|
||||
ans = balanceBots(util.ReadFile("./input.txt"), nil)
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func balanceBots(input string, part1CompareValues []int) int {
|
||||
botsMap, rules := parseInput(input)
|
||||
outputs := map[int]int{}
|
||||
|
||||
// for loop conditional is for part 2. part 1 returns from inside the loop.
|
||||
for outputs[0] == 0 || outputs[1] == 0 || outputs[2] == 0 {
|
||||
for _, r := range rules {
|
||||
if len(botsMap[r.botID]) == 2 {
|
||||
sort.Ints(botsMap[r.botID])
|
||||
low, high := botsMap[r.botID][0], botsMap[r.botID][1]
|
||||
// part 1 return value
|
||||
if len(part1CompareValues) != 0 &&
|
||||
low == part1CompareValues[0] && high == part1CompareValues[1] {
|
||||
return r.botID
|
||||
}
|
||||
var outputIndex, receivingBot int
|
||||
if strings.Contains(r.lowRule, "output") {
|
||||
_, err := fmt.Sscanf(r.lowRule, "low to output %d", &outputIndex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
outputs[outputIndex] = low
|
||||
} else {
|
||||
_, err := fmt.Sscanf(r.lowRule, "low to bot %d", &receivingBot)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
botsMap[receivingBot] = append(botsMap[receivingBot], low)
|
||||
}
|
||||
if strings.Contains(r.highRule, "output") {
|
||||
_, err := fmt.Sscanf(r.highRule, "high to output %d", &outputIndex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
outputs[outputIndex] = high
|
||||
} else {
|
||||
_, err := fmt.Sscanf(r.highRule, "high to bot %d", &receivingBot)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
botsMap[receivingBot] = append(botsMap[receivingBot], high)
|
||||
}
|
||||
botsMap[r.botID] = []int{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// part 2 output
|
||||
return outputs[0] * outputs[1] * outputs[2]
|
||||
}
|
||||
|
||||
type rule struct {
|
||||
botID int
|
||||
lowRule string
|
||||
highRule string
|
||||
}
|
||||
|
||||
func parseInput(input string) (botsMap map[int][]int, rules []rule) {
|
||||
botsMap = map[int][]int{}
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
if strings.Contains(line, "value") {
|
||||
var value, botID int
|
||||
_, err := fmt.Sscanf(line, "value %d goes to bot %d", &value, &botID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
botsMap[botID] = append(botsMap[botID], value)
|
||||
} else {
|
||||
parts := algos.SplitStringOn(line, []string{" gives ", " and "})
|
||||
r := rule{lowRule: parts[1], highRule: parts[2]}
|
||||
_, err := fmt.Sscanf(parts[0], "bot %d", &r.botID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rules = append(rules, r)
|
||||
}
|
||||
}
|
||||
return botsMap, rules
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `value 5 goes to bot 2
|
||||
bot 2 gives low to bot 1 and high to bot 0
|
||||
value 3 goes to bot 1
|
||||
bot 1 gives low to output 1 and high to bot 0
|
||||
bot 0 gives low to output 2 and high to output 0
|
||||
value 2 goes to bot 2`
|
||||
|
||||
func Test_balanceBots(t *testing.T) {
|
||||
type args struct {
|
||||
input string
|
||||
part1CompareVals []int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{"example_part1", args{example, []int{2, 5}}, 2},
|
||||
{"actual_part1", args{util.ReadFile("input.txt"), []int{17, 61}}, 181},
|
||||
{"actual_part2", args{util.ReadFile("input.txt"), nil}, 12567},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := balanceBots(tt.args.input, tt.args.part1CompareVals); got != tt.want {
|
||||
t.Errorf("balanceBots() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := rtgHellDay(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func rtgHellDay(input string, part int) int {
|
||||
currentState := newInitialState(input)
|
||||
|
||||
if part == 2 {
|
||||
currentState.floors[0] = append(currentState.floors[0],
|
||||
halves{isChip: false, material: "elerium"},
|
||||
halves{isChip: true, material: "elerium"},
|
||||
halves{isChip: false, material: "dilithium"},
|
||||
halves{isChip: true, material: "dilithium"},
|
||||
)
|
||||
}
|
||||
|
||||
queue := []state{currentState}
|
||||
prevStates := map[string]bool{}
|
||||
for len(queue) > 0 {
|
||||
front := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
if front.isDone() {
|
||||
return front.steps
|
||||
}
|
||||
|
||||
// do not visit previous states
|
||||
// hashKey method does not differentiate material types because
|
||||
// they are effectively the same
|
||||
hash := front.hashKey()
|
||||
if prevStates[hash] {
|
||||
continue
|
||||
}
|
||||
prevStates[hash] = true
|
||||
|
||||
nextStates := front.getNextStates()
|
||||
queue = append(queue, nextStates...)
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// halves are either a chip or generator
|
||||
type halves struct {
|
||||
isChip bool // false if is generator
|
||||
material string
|
||||
}
|
||||
|
||||
// for easier debugging
|
||||
func (t halves) String() string {
|
||||
tType := " generator"
|
||||
if t.isChip {
|
||||
tType = " microchip"
|
||||
}
|
||||
return fmt.Sprint(t.material, tType)
|
||||
}
|
||||
|
||||
// state of the puzzle with a bunch of methods for getting next states, checking
|
||||
// validity of a state, if it represents a finish state...
|
||||
type state struct {
|
||||
floors [4][]halves
|
||||
elevatorLevel int
|
||||
steps int
|
||||
}
|
||||
|
||||
// parsing the input file, this probably would've been easier to do manually...
|
||||
func newInitialState(input string) state {
|
||||
s := state{}
|
||||
|
||||
for lineIndex, line := range strings.Split(input, "\n") {
|
||||
// The first floor contains a promethium generator and a promethium-compatible microchip.
|
||||
parts := strings.Split(line, " ")
|
||||
// trim commas and periods, this input is pretty inconsistent
|
||||
for i, v := range parts {
|
||||
parts[i] = strings.Trim(v, ",.")
|
||||
}
|
||||
|
||||
// iterate through the words and if generator or microchip is found
|
||||
// then parse the previous word for the material type
|
||||
for i, word := range parts {
|
||||
if word == "generator" {
|
||||
material := parts[i-1]
|
||||
s.floors[lineIndex] = append(s.floors[lineIndex], halves{
|
||||
isChip: false,
|
||||
material: material,
|
||||
})
|
||||
} else if word == "microchip" {
|
||||
// also parse off the "-compatible" portion
|
||||
material := parts[i-1][:strings.Index(parts[i-1], "-comp")]
|
||||
s.floors[lineIndex] = append(s.floors[lineIndex], halves{
|
||||
isChip: true,
|
||||
material: material,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// for printability & debugging
|
||||
func (s state) String() string {
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "Level %d x Steps %d\n", s.elevatorLevel, s.steps)
|
||||
for i, f := range s.floors {
|
||||
fmt.Fprintf(&sb, " %d: %v\n", i, f)
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Generates a hash for this state that is comparable, to not repeat states.
|
||||
// I spent over an hour figuring out that I left out the elevator level, which
|
||||
// is a key component of the state hash
|
||||
func (s state) hashKey() string {
|
||||
// get the indices for each generator and chip
|
||||
mapGenToIndex := map[string]int{}
|
||||
mapChipToIndex := map[string]int{}
|
||||
for flIndex, fl := range s.floors {
|
||||
for _, half := range fl {
|
||||
if half.isChip {
|
||||
mapChipToIndex[half.material] = flIndex
|
||||
} else {
|
||||
mapGenToIndex[half.material] = flIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then put that into slice form so it ignores the material types
|
||||
// this is b/c the types don't really matter... e.g.
|
||||
// 0: LithGen, LithChip 0: PluGen, PluChip
|
||||
// 1: PluGen == 1: LithGen
|
||||
// 2: PluChip 2: LithChip
|
||||
var genChipPairs [][2]int
|
||||
for material := range mapGenToIndex {
|
||||
genChipPairs = append(genChipPairs, [2]int{
|
||||
mapGenToIndex[material], mapChipToIndex[material],
|
||||
})
|
||||
}
|
||||
// sort it
|
||||
sort.Slice(genChipPairs, func(i, j int) bool {
|
||||
if genChipPairs[i][0] != genChipPairs[j][0] {
|
||||
return genChipPairs[i][0] < genChipPairs[j][0]
|
||||
}
|
||||
return genChipPairs[i][1] < genChipPairs[j][1]
|
||||
})
|
||||
|
||||
// fmt.Sprint is my best friend for making hashes
|
||||
return fmt.Sprint(s.elevatorLevel, genChipPairs)
|
||||
}
|
||||
|
||||
func (s state) isValid() bool {
|
||||
// check every level, I lost another hour here because I was only checking
|
||||
// the active level, but moving some halves off a level could make an old
|
||||
// level invalid
|
||||
for i := range s.floors {
|
||||
// make a hashmap of all the generators on this level
|
||||
gensSeen := map[string]bool{}
|
||||
for _, half := range s.floors[i] {
|
||||
if !half.isChip {
|
||||
gensSeen[half.material] = true
|
||||
}
|
||||
}
|
||||
// if there are no gens on this level, it's safe
|
||||
if len(gensSeen) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// there are generators, so if there is any chip that is not protected
|
||||
// then it is an invalid level & thus an invalid state
|
||||
for _, half := range s.floors[i] {
|
||||
if half.isChip && !gensSeen[half.material] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
// all chips protected, return true
|
||||
return true
|
||||
}
|
||||
|
||||
// is this the final state?
|
||||
func (s state) isDone() bool {
|
||||
var lenSum int
|
||||
for _, fl := range s.floors[:3] {
|
||||
lenSum += len(fl)
|
||||
}
|
||||
return lenSum == 0
|
||||
}
|
||||
|
||||
// get perms of INDICES that can be moved off of this level in the next perm
|
||||
// one or two things can be moved, this generates all perms of one or two
|
||||
// indices on the elevator's floor
|
||||
func (s state) getMovablePermIndices() [][]int {
|
||||
var permsToMove [][]int
|
||||
|
||||
currentLevel := s.floors[s.elevatorLevel]
|
||||
// get pairs first
|
||||
for i := 0; i < len(currentLevel); i++ {
|
||||
for j := i + 1; j < len(currentLevel); j++ {
|
||||
permsToMove = append(permsToMove, []int{i, j})
|
||||
}
|
||||
}
|
||||
// then get singles
|
||||
for i := range currentLevel {
|
||||
permsToMove = append(permsToMove, []int{i})
|
||||
}
|
||||
return permsToMove
|
||||
}
|
||||
|
||||
// make a deep clone
|
||||
func (s state) clone() state {
|
||||
cl := state{
|
||||
elevatorLevel: s.elevatorLevel,
|
||||
steps: s.steps,
|
||||
}
|
||||
// slices are effectively reference types in go... they need to be cloned...
|
||||
for i, fl := range s.floors {
|
||||
cl.floors[i] = append([]halves{}, fl...)
|
||||
}
|
||||
return cl
|
||||
}
|
||||
|
||||
// get all valid next states that can be reached from this state
|
||||
func (s state) getNextStates() []state {
|
||||
var futureStates []state
|
||||
|
||||
// all combinations of indices that can be moved from this level
|
||||
movablePermIndices := s.getMovablePermIndices()
|
||||
|
||||
// get diffs that the elevator can move in, i.e. don't let it move up from
|
||||
// the top level, or down from level 0
|
||||
var eleDiffs []int
|
||||
if s.elevatorLevel < len(s.floors)-1 {
|
||||
eleDiffs = append(eleDiffs, 1)
|
||||
}
|
||||
if s.elevatorLevel > 0 {
|
||||
eleDiffs = append(eleDiffs, -1)
|
||||
}
|
||||
|
||||
for _, eleDiff := range eleDiffs {
|
||||
// for any elevator direction, iterate over moveable perms and make a
|
||||
// clone that's modified with those halves moved to the target floor
|
||||
for _, permIndices := range movablePermIndices {
|
||||
cl := s.clone()
|
||||
cl.elevatorLevel += eleDiff
|
||||
cl.steps++ // increment steps
|
||||
oldLevel := s.elevatorLevel
|
||||
newLevel := cl.elevatorLevel
|
||||
|
||||
// move halves to the clone's active level
|
||||
for _, index := range permIndices {
|
||||
cl.floors[newLevel] = append(cl.floors[newLevel], cl.floors[oldLevel][index])
|
||||
}
|
||||
// remove halves from the current state's level (in the clone)
|
||||
// this code is gross...
|
||||
for in := len(permIndices) - 1; in >= 0; in-- {
|
||||
cl.floors[oldLevel][permIndices[in]] = cl.floors[oldLevel][len(cl.floors[oldLevel])-1]
|
||||
cl.floors[oldLevel] = cl.floors[oldLevel][:len(cl.floors[oldLevel])-1]
|
||||
}
|
||||
// add to final states if its valid
|
||||
if cl.isValid() {
|
||||
futureStates = append(futureStates, cl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return futureStates
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `The first floor contains a hydrogen-compatible microchip and a lithium-compatible microchip.
|
||||
The second floor contains a hydrogen generator.
|
||||
The third floor contains a lithium generator.
|
||||
The fourth floor contains nothing relevant.`
|
||||
|
||||
func Test_rtgHellDay(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"part1 example", example, 1, 11},
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, 33},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, 57},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := rtgHellDay(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("rtgHellDay() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := assemblyComputer(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func assemblyComputer(input string, part int) int {
|
||||
instructions := strings.Split(input, "\n")
|
||||
registers := map[string]int{} // a b c d = 0
|
||||
var instIndex int
|
||||
|
||||
if part == 2 {
|
||||
registers["c"] = 1
|
||||
}
|
||||
|
||||
for instIndex < len(instructions) {
|
||||
parts := strings.Split(instructions[instIndex], " ")
|
||||
|
||||
switch parts[0] {
|
||||
case "cpy":
|
||||
valX, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
valX = registers[parts[1]]
|
||||
}
|
||||
registers[parts[2]] = valX
|
||||
instIndex++
|
||||
case "inc":
|
||||
registers[parts[1]]++
|
||||
instIndex++
|
||||
case "dec":
|
||||
registers[parts[1]]--
|
||||
instIndex++
|
||||
case "jnz":
|
||||
valX, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
valX = registers[parts[1]]
|
||||
}
|
||||
if valX != 0 {
|
||||
instIndex += cast.ToInt(parts[2])
|
||||
} else {
|
||||
instIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return registers["a"]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_assemblyComputer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"actual_part1", util.ReadFile("input.txt"), 1, 318077},
|
||||
{"actual_part2", util.ReadFile("input.txt"), 2, 9227731},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := assemblyComputer(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("assemblyComputer() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := bfs(util.ReadFile("./input.txt"), [2]int{31, 39}, part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
var dirs = [][2]int{
|
||||
{-1, 0},
|
||||
{1, 0},
|
||||
{0, 1},
|
||||
{0, -1},
|
||||
}
|
||||
|
||||
func bfs(input string, destination [2]int, part int) int {
|
||||
inputNum := cast.ToInt(input)
|
||||
|
||||
// bfs queue
|
||||
queue := [][3]int{[3]int{1, 1, 0}} // x,y, DIST
|
||||
// to not re-visit cells
|
||||
visited := map[[2]int]bool{}
|
||||
|
||||
// for part 2
|
||||
uniqueVisitsUnder50 := map[[2]int]bool{}
|
||||
|
||||
for len(queue) > 0 {
|
||||
front := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
currentX, currentY := front[0], front[1]
|
||||
currentDist := front[2]
|
||||
|
||||
// part 1 return
|
||||
if part == 1 && currentX == destination[0] && currentY == destination[1] {
|
||||
return currentDist
|
||||
}
|
||||
// if already visited, skip
|
||||
if !visited[[2]int{currentX, currentY}] {
|
||||
// for part 2, check if distance is 50 or less
|
||||
if currentDist <= 50 {
|
||||
uniqueVisitsUnder50[[2]int{currentX, currentY}] = true
|
||||
}
|
||||
|
||||
if part == 2 && currentDist > 50 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, diff := range dirs {
|
||||
nextX, nextY := currentX+diff[0], currentY+diff[1]
|
||||
if nextX >= 0 && nextY >= 0 {
|
||||
if isOpenSpace(nextX, nextY, inputNum) {
|
||||
queue = append(queue, [3]int{nextX, nextY, currentDist + 1})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visited[[2]int{currentX, currentY}] = true
|
||||
}
|
||||
|
||||
return len(uniqueVisitsUnder50)
|
||||
}
|
||||
|
||||
func isOpenSpace(x, y, inputNum int) bool {
|
||||
num := x*x + 3*x + 2*x*y + y + y*y + inputNum
|
||||
binStr := fmt.Sprintf("%b", num)
|
||||
var ones int
|
||||
for _, char := range binStr {
|
||||
if char == '1' {
|
||||
ones++
|
||||
}
|
||||
}
|
||||
|
||||
return ones%2 == 0
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_bfs(t *testing.T) {
|
||||
type args struct {
|
||||
input string
|
||||
destination [2]int
|
||||
part int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{"example", args{"10", [2]int{7, 4}, 1}, 11},
|
||||
{"actual_part1", args{util.ReadFile("input.txt"), [2]int{31, 39}, 1}, 86},
|
||||
{"actual_part2", args{util.ReadFile("input.txt"), [2]int{31, 39}, 2}, 127},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := bfs(tt.args.input, tt.args.destination, tt.args.part); got != tt.want {
|
||||
t.Errorf("bfs() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := oneTimePad(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func oneTimePad(input string, part int) int {
|
||||
hashCycles := 1
|
||||
if part == 2 {
|
||||
hashCycles = 2016 + 1
|
||||
}
|
||||
|
||||
hashes := []string{}
|
||||
for i := 0; i < 1000; i++ {
|
||||
hashes = append(hashes, hash(input, i, hashCycles))
|
||||
}
|
||||
var keys []int
|
||||
for index := 0; len(keys) < 64; index++ {
|
||||
currentHash := hashes[0]
|
||||
|
||||
// maintain the next 1000 hashes
|
||||
hashes = append(hashes, hash(input, index+1000, hashCycles))
|
||||
hashes = hashes[1:]
|
||||
|
||||
if char := hasTriple(currentHash); char != "" {
|
||||
// this is slow... but it's simple
|
||||
pattern := regexp.MustCompile(fmt.Sprintf("[%s]{5}", char))
|
||||
if pattern.MatchString(strings.Join(hashes, ",")) {
|
||||
keys = append(keys, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keys[len(keys)-1]
|
||||
}
|
||||
|
||||
func hash(input string, index, cycles int) string {
|
||||
hashed := fmt.Sprintf("%s%d", input, index)
|
||||
for i := 0; i < cycles; i++ {
|
||||
hashed = fmt.Sprintf("%x", md5.Sum([]byte(hashed)))
|
||||
}
|
||||
return hashed
|
||||
}
|
||||
|
||||
func hasTriple(in string) string {
|
||||
for i := 2; i < len(in); i++ {
|
||||
if in[i-2] == in[i-1] && in[i-1] == in[i] {
|
||||
return string(in[i])
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"example_part1", "abc", 1, 22728},
|
||||
{"actual_part1", util.ReadFile("input.txt"), 1, 23769},
|
||||
{"example_part2", "abc", 2, 22551},
|
||||
{"actual_part2", util.ReadFile("input.txt"), 2, 20606},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if strings.Contains(tt.name, "part2") && testing.Short() {
|
||||
t.Skip("Skipping long test, 2016/day14, a lot of MD5 hashes")
|
||||
}
|
||||
if got := oneTimePad(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("part1() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := timingIsEverything(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func timingIsEverything(input string, part int) int {
|
||||
discs := parseInput(input)
|
||||
|
||||
if part == 2 {
|
||||
discs = append(discs, &disc{
|
||||
number: len(discs) + 1,
|
||||
positions: 11,
|
||||
starting: 0,
|
||||
})
|
||||
}
|
||||
|
||||
t := 0
|
||||
for {
|
||||
var capsuleCollides bool
|
||||
for _, d := range discs {
|
||||
// some math equation for position must equal zero for the capsule to pass through each disc
|
||||
timeSinceDrop := d.number
|
||||
position := d.starting + t + timeSinceDrop
|
||||
position %= d.positions
|
||||
if position != 0 {
|
||||
capsuleCollides = true
|
||||
}
|
||||
}
|
||||
if !capsuleCollides {
|
||||
break
|
||||
}
|
||||
t++
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
type disc struct {
|
||||
number int
|
||||
positions int
|
||||
starting int
|
||||
}
|
||||
|
||||
func parseInput(input string) []*disc {
|
||||
var discs []*disc
|
||||
for _, l := range strings.Split(input, "\n") {
|
||||
d := disc{}
|
||||
fmt.Sscanf(l, "Disc #%d has %d positions; at time=0, it is at position %d.",
|
||||
&d.number, &d.positions, &d.starting)
|
||||
discs = append(discs, &d)
|
||||
}
|
||||
return discs
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `Disc #1 has 5 positions; at time=0, it is at position 4.
|
||||
Disc #2 has 2 positions; at time=0, it is at position 1.`
|
||||
|
||||
func Test_timingIsEverything(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"example", example, 1, 5},
|
||||
{"actual", util.ReadFile("input.txt"), 1, 317371},
|
||||
{"actual", util.ReadFile("input.txt"), 2, 2080951},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := timingIsEverything(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("timingIsEverything() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := dragonChecksum(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func dragonChecksum(input string, part int) string {
|
||||
disk := input
|
||||
diskLength := 272
|
||||
if part == 2 {
|
||||
diskLength = 35651584
|
||||
}
|
||||
for len(disk) < diskLength {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(disk)
|
||||
sb.WriteByte('0')
|
||||
for i := len(disk) - 1; i >= 0; i-- {
|
||||
if disk[i] == '1' {
|
||||
sb.WriteByte('0')
|
||||
} else {
|
||||
sb.WriteByte('1')
|
||||
}
|
||||
}
|
||||
disk = sb.String()
|
||||
}
|
||||
|
||||
disk = disk[0:diskLength]
|
||||
for len(disk)%2 == 0 {
|
||||
var sb strings.Builder
|
||||
for i := 0; i < len(disk); i += 2 {
|
||||
if disk[i] == disk[i+1] {
|
||||
sb.WriteByte('1')
|
||||
} else {
|
||||
sb.WriteByte('0')
|
||||
}
|
||||
}
|
||||
disk = sb.String()
|
||||
}
|
||||
|
||||
return disk
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_dragonChecksum(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want string
|
||||
}{
|
||||
{"part1", util.ReadFile("input.txt"), 1, "10010110010011110"},
|
||||
{"part2", util.ReadFile("input.txt"), 2, "01101011101100011"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := dragonChecksum(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("dragonChecksum() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := md5Bfs(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
var openPattern = regexp.MustCompile("[b-f]")
|
||||
|
||||
type node struct {
|
||||
coords [2]int
|
||||
path string
|
||||
distance int
|
||||
}
|
||||
|
||||
func md5Bfs(input string, part int) string {
|
||||
queue := []node{{path: input}}
|
||||
var longestPath string
|
||||
for len(queue) > 0 {
|
||||
front := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
if front.coords == [2]int{3, 3} {
|
||||
validPath := front.path[len(input):]
|
||||
if part == 1 {
|
||||
return validPath
|
||||
}
|
||||
|
||||
if len(longestPath) < len(validPath) {
|
||||
longestPath = validPath
|
||||
}
|
||||
// cannot pass through the end point
|
||||
continue
|
||||
}
|
||||
|
||||
hash := fmt.Sprintf("%x", md5.Sum([]byte(front.path)))
|
||||
|
||||
for i, direction := range []struct {
|
||||
char string
|
||||
rowDiff int
|
||||
colDiff int
|
||||
}{
|
||||
{"U", -1, 0},
|
||||
{"D", 1, 0},
|
||||
{"L", 0, -1},
|
||||
{"R", 0, 1},
|
||||
} {
|
||||
nextRow := front.coords[0] + direction.rowDiff
|
||||
nextCol := front.coords[1] + direction.colDiff
|
||||
if nextRow >= 0 && nextRow < 4 && nextCol >= 0 && nextCol < 4 {
|
||||
if openPattern.MatchString(hash[i : i+1]) {
|
||||
queue = append(queue, node{
|
||||
coords: [2]int{nextRow, nextCol},
|
||||
path: front.path + direction.char,
|
||||
distance: front.distance + 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// part 2, stringified number...
|
||||
return cast.ToString(len(longestPath))
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_md5Bfs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want string
|
||||
}{
|
||||
{"part1_example1", "ihgpwlah", 1, "DDRRRD"},
|
||||
{"part1_actual", util.ReadFile("input.txt"), 1, "DDRUDLRRRD"},
|
||||
{"part2_example1", "ihgpwlah", 2, "370"},
|
||||
{"part2_actual", util.ReadFile("input.txt"), 2, "398"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := md5Bfs(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("md5Bfs() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans int
|
||||
if part == 1 {
|
||||
ans = likeARogue(util.ReadFile("./input.txt"), 40)
|
||||
} else {
|
||||
ans = likeARogue(util.ReadFile("./input.txt"), 400000)
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
// this could be sped up by memoizing rows to their next row, or their counts
|
||||
// but this is more than fast enough... ~3s to run both parts
|
||||
func likeARogue(input string, numRows int) int {
|
||||
lastRow := "." + input + "."
|
||||
patterns := "^^. .^^ ^.. ..^" // to use with strings.Contains
|
||||
|
||||
var actualRows []string
|
||||
for len(actualRows) < numRows {
|
||||
// add last row
|
||||
actualRows = append(actualRows, lastRow[1:len(lastRow)-1])
|
||||
|
||||
// generate the next row
|
||||
nextRow := "." // start w/ safe cell in the left wall
|
||||
for i := 1; i < len(lastRow)-1; i++ {
|
||||
threeAbove := lastRow[i-1 : i+2]
|
||||
if strings.Contains(patterns, threeAbove) {
|
||||
nextRow += "^"
|
||||
} else {
|
||||
nextRow += "."
|
||||
}
|
||||
}
|
||||
nextRow += "."
|
||||
// assign to last row
|
||||
lastRow = nextRow
|
||||
}
|
||||
|
||||
// count safe tiles
|
||||
var count int
|
||||
for _, row := range actualRows {
|
||||
for _, v := range row {
|
||||
if v == '.' {
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_likeARogue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
numRows int
|
||||
want int
|
||||
}{
|
||||
{"example", ".^^.^.^^^^", 10, 38},
|
||||
{"part1_actual", util.ReadFile("input.txt"), 40, 2005},
|
||||
{"part2_actual", util.ReadFile("input.txt"), 400000, 20008491},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := likeARogue(tt.input, tt.numRows); got != tt.want {
|
||||
t.Errorf("likeARogue() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := elephant(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
// LLNode represents an elf
|
||||
type LLNode struct {
|
||||
elfNum int
|
||||
presents int
|
||||
next *LLNode
|
||||
}
|
||||
|
||||
func elephant(input string, part int) int {
|
||||
startingElves := cast.ToInt(input)
|
||||
root := &LLNode{
|
||||
elfNum: 1,
|
||||
presents: 1,
|
||||
}
|
||||
iter := root
|
||||
for i := 2; i <= startingElves; i++ {
|
||||
iter.next = &LLNode{
|
||||
elfNum: i,
|
||||
presents: 1,
|
||||
}
|
||||
iter = iter.next
|
||||
}
|
||||
iter.next = root
|
||||
|
||||
if part == 1 {
|
||||
for root.next != root {
|
||||
root.presents += root.next.presents
|
||||
root.next = root.next.next
|
||||
root = root.next
|
||||
}
|
||||
return root.elfNum
|
||||
}
|
||||
|
||||
// initialize a pointer to the node before the node across from the start
|
||||
// need the node before b/c removing a node is like reassigning beforeNode.next
|
||||
// if there are an odd number of starting elves, this points to the first one
|
||||
// which is also the one of "the left."
|
||||
isOddLength := startingElves%2 == 1
|
||||
beforeAcross := root
|
||||
for i := 0; i < startingElves/2-1; i++ {
|
||||
beforeAcross = beforeAcross.next
|
||||
}
|
||||
|
||||
for root.next != root {
|
||||
root.presents += beforeAcross.next.presents
|
||||
// remove beforeAcross node
|
||||
beforeAcross.next = beforeAcross.next.next
|
||||
// if odd number of total nodes, beforeAcross node skips the node
|
||||
// that was previously the "right" side of the across pair
|
||||
if isOddLength {
|
||||
beforeAcross = beforeAcross.next
|
||||
}
|
||||
isOddLength = !isOddLength
|
||||
root = root.next
|
||||
}
|
||||
|
||||
return root.elfNum
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_elephant(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, 1834471},
|
||||
{"part2 example", "5", 2, 2},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, 1420064},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := elephant(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("elephant() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/mathy"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := firewall(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func firewall(input string, part int) int {
|
||||
var allBlockedRanges [][2]int
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
var r [2]int
|
||||
fmt.Sscanf(line, "%d-%d", &r[0], &r[1])
|
||||
allBlockedRanges = append(allBlockedRanges, r)
|
||||
}
|
||||
sort.Slice(allBlockedRanges, func(i, j int) bool {
|
||||
if allBlockedRanges[i][0] != allBlockedRanges[j][0] {
|
||||
return allBlockedRanges[i][0] < allBlockedRanges[j][0]
|
||||
}
|
||||
return allBlockedRanges[i][1] < allBlockedRanges[j][1]
|
||||
})
|
||||
|
||||
// merge allBlockedRanges
|
||||
merged := [][2]int{[2]int{}}
|
||||
for _, r := range allBlockedRanges {
|
||||
endOfLastRange := merged[len(merged)-1][1]
|
||||
if endOfLastRange >= r[0]-1 {
|
||||
merged[len(merged)-1][1] = mathy.MaxInt(endOfLastRange, r[1])
|
||||
} else {
|
||||
merged = append(merged, r)
|
||||
}
|
||||
}
|
||||
|
||||
if part == 1 {
|
||||
return merged[0][1] + 1
|
||||
}
|
||||
|
||||
if merged[len(merged)-1][1] != math.MaxUint32 {
|
||||
merged = append(merged, [2]int{math.MaxUint32, 0})
|
||||
}
|
||||
|
||||
var totalAllowed int
|
||||
for i := 1; i < len(merged); i++ {
|
||||
totalAllowed += merged[i][0] - merged[i-1][1] - 1
|
||||
}
|
||||
|
||||
return totalAllowed
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `5-8
|
||||
0-2
|
||||
4-7`
|
||||
|
||||
func Test_firewall(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"example part1", example, 1, 3},
|
||||
{"actual part1", util.ReadFile("input.txt"), 1, 23923783},
|
||||
{"actual part2", util.ReadFile("input.txt"), 2, 125},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := firewall(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("firewall() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans string
|
||||
if part == 1 {
|
||||
ans = part1(util.ReadFile("./input.txt"), "abcdefgh", "")
|
||||
} else {
|
||||
ans = part1(util.ReadFile("./input.txt"), "abcdefgh", "fbgdceah")
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func part1(input string, starting string, target string) string {
|
||||
runSteps := func(starting string) string {
|
||||
registers := strings.Split(starting, "")
|
||||
for _, inst := range strings.Split(input, "\n") {
|
||||
registers = modifySlice(inst, registers)
|
||||
}
|
||||
return strings.Join(registers, "")
|
||||
}
|
||||
|
||||
// part 1
|
||||
if target == "" {
|
||||
return runSteps(starting)
|
||||
}
|
||||
|
||||
for _, p := range algos.PermuteString(starting) {
|
||||
if runSteps(p) == target {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
panic("no perms matched")
|
||||
}
|
||||
|
||||
func modifySlice(line string, sli []string) []string {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "swap"):
|
||||
var i1, i2 int
|
||||
if strings.Contains(line, "position") {
|
||||
fmt.Sscanf(line, "swap position %d with position %d", &i1, &i2)
|
||||
} else {
|
||||
var c1, c2 string
|
||||
fmt.Sscanf(line, "swap letter %1s with letter %1s", &c1, &c2)
|
||||
i1, i2 = getIndex(sli, c1), getIndex(sli, c2)
|
||||
}
|
||||
sli[i1], sli[i2] = sli[i2], sli[i1]
|
||||
case strings.HasPrefix(line, "rotate"):
|
||||
var rightShift int
|
||||
parts := strings.Split(line, " ")
|
||||
if strings.Contains(line, "letter") {
|
||||
index := getIndex(sli, parts[6])
|
||||
if index >= 4 {
|
||||
index++
|
||||
}
|
||||
index++
|
||||
rightShift = index % len(sli)
|
||||
} else {
|
||||
// left or right
|
||||
rightShift = cast.ToInt(parts[2])
|
||||
if parts[1] == "left" {
|
||||
rightShift = len(sli) - rightShift
|
||||
}
|
||||
}
|
||||
// perform shift
|
||||
sli = append(sli[len(sli)-rightShift:], sli[:len(sli)-rightShift]...)
|
||||
case strings.HasPrefix(line, "reverse"):
|
||||
var i1, i2 int
|
||||
fmt.Sscanf(line, "reverse positions %d through %d", &i1, &i2)
|
||||
for i1 < i2 {
|
||||
sli[i1], sli[i2] = sli[i2], sli[i1]
|
||||
i1++
|
||||
i2--
|
||||
}
|
||||
case strings.HasPrefix(line, "move"):
|
||||
var i1, i2 int
|
||||
fmt.Sscanf(line, "move position %d to position %d", &i1, &i2)
|
||||
store := sli[i1]
|
||||
|
||||
// remove char at i1
|
||||
copy(sli[i1:], sli[i1+1:])
|
||||
|
||||
for i := len(sli) - 1; i >= i2+1; i-- {
|
||||
sli[i] = sli[i-1]
|
||||
}
|
||||
sli[i2] = store
|
||||
default:
|
||||
panic("unhandled instruction type: " + line)
|
||||
}
|
||||
|
||||
// return modified slice
|
||||
return sli
|
||||
}
|
||||
|
||||
func getIndex(letters []string, toFind string) int {
|
||||
for i, v := range letters {
|
||||
if v == toFind {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `swap position 4 with position 0
|
||||
swap letter d with letter b
|
||||
reverse positions 0 through 4
|
||||
rotate left 1 step
|
||||
move position 1 to position 4
|
||||
move position 3 to position 0
|
||||
rotate based on position of letter b
|
||||
rotate based on position of letter d`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
type args struct {
|
||||
input, starting, target string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{"part1 example", args{example, "abcde", ""}, "decab"},
|
||||
{"part1 actual", args{util.ReadFile("input.txt"), "abcdefgh", ""}, "bfheacgd"},
|
||||
{"part2 actual", args{util.ReadFile("input.txt"), "abcdefgh", "fbgdceah"}, "gcehdbfa"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := part1(tt.args.input, tt.args.starting, tt.args.target); got != tt.want {
|
||||
t.Errorf("part1() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/mathy"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
var ans int
|
||||
if part == 1 {
|
||||
ans = part1(util.ReadFile("./input.txt"))
|
||||
} else {
|
||||
ans = part2(util.ReadFile("./input.txt"))
|
||||
}
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
nodes := parseInput(input)
|
||||
|
||||
var viable int
|
||||
for i1, n1 := range nodes {
|
||||
for i2, n2 := range nodes {
|
||||
if i1 == i2 || n1.used == 0 {
|
||||
continue
|
||||
}
|
||||
if n2.avail >= n1.used {
|
||||
viable++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return viable
|
||||
}
|
||||
|
||||
// NOTE: this is not a generalized solution, this was done after solving it somewhat
|
||||
// manually by printing the entire grid and getting to the top right corner
|
||||
// there was a blocking row that had really large memory usage, these had to be routed around
|
||||
// then swapping the top right tile each step to the left, required 5 steps
|
||||
// 4 to get in front of it and 1 to do the actual swap
|
||||
func part2(input string) int {
|
||||
nodes := parseInput(input)
|
||||
|
||||
var maxX, maxY int
|
||||
var x, y int
|
||||
for c, n := range nodes {
|
||||
maxX = mathy.MaxInt(c[0], maxX)
|
||||
maxY = mathy.MaxInt(c[1], maxY)
|
||||
// getting the starting node, i.e. has zero used space
|
||||
if n.used == 0 {
|
||||
x = n.coord[1]
|
||||
y = n.coord[0]
|
||||
}
|
||||
}
|
||||
|
||||
// // uncomment to print a useable grid
|
||||
// grid := make([][]*node, maxY+1)
|
||||
// for i := range grid {
|
||||
// grid[i] = make([]*node, maxX+1)
|
||||
// }
|
||||
// for c, n := range nodes {
|
||||
// grid[c[1]][c[0]] = n
|
||||
// }
|
||||
// for _, line := range grid {
|
||||
// for _, n := range line {
|
||||
// fmt.Print(n)
|
||||
// }
|
||||
// fmt.Println()
|
||||
// }
|
||||
|
||||
var stepsTaken int
|
||||
// get to the cell that's needed
|
||||
for !(x == maxX && y == 0) {
|
||||
if y > 0 {
|
||||
if nodes[[2]int{x, y - 1}].used < 100 { // realizing that x/y are "flipped"..
|
||||
// grid[y-1][x].used < 100 {
|
||||
y--
|
||||
} else {
|
||||
// go left to get around the "blocking" chips who's used size
|
||||
// is so large that it cannot be copied into the zero chip
|
||||
x--
|
||||
}
|
||||
} else if y == 0 {
|
||||
x++
|
||||
}
|
||||
stepsTaken++
|
||||
}
|
||||
// decrement x because we shifted into it already
|
||||
x--
|
||||
|
||||
// then you need five steps to move the target cell to the left by one cell
|
||||
for x != 0 {
|
||||
stepsTaken += 5
|
||||
x--
|
||||
}
|
||||
|
||||
return stepsTaken
|
||||
}
|
||||
|
||||
type node struct {
|
||||
coord [2]int
|
||||
size int
|
||||
used int
|
||||
avail int
|
||||
}
|
||||
|
||||
func (n node) String() string {
|
||||
// str := fmt.Sprintf("%v: %d used of %d, %d avail", n.coord, n.used, n.size, n.avail)
|
||||
str := fmt.Sprintf("| %d/%d ", n.used, n.size)
|
||||
for len(str) < 10 {
|
||||
str += " "
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func parseInput(input string) map[[2]int]*node {
|
||||
allNodes := map[[2]int]*node{}
|
||||
|
||||
spaces := regexp.MustCompile("[ ]{2,}")
|
||||
for _, line := range strings.Split(input, "\n")[2:] {
|
||||
str := spaces.ReplaceAllString(line, " ")
|
||||
var percentage int
|
||||
n := node{}
|
||||
fmt.Sscanf(str, "/dev/grid/node-x%d-y%d %dT %dT %dT %d%",
|
||||
&n.coord[0], &n.coord[1], &n.size, &n.used, &n.avail, &percentage)
|
||||
allNodes[n.coord] = &n
|
||||
}
|
||||
|
||||
return allNodes
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 946},
|
||||
}
|
||||
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
|
||||
}{
|
||||
{"actual", util.ReadFile("input.txt"), 195},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := assemblyComputer(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func assemblyComputer(input string, part int) int {
|
||||
instructions := strings.Split(input, "\n")
|
||||
registers := map[string]int{}
|
||||
var instIndex int
|
||||
|
||||
registers["a"] = 7
|
||||
if part == 2 {
|
||||
registers["a"] = 12
|
||||
}
|
||||
|
||||
for instIndex < len(instructions) {
|
||||
// uncomment this to print out the instruction set and see how it's changed
|
||||
// fmt.Println(instIndex)
|
||||
// for in, i := range instructions {
|
||||
// fmt.Println(in, i)
|
||||
// }
|
||||
// fmt.Println()
|
||||
|
||||
inst := instructions[instIndex]
|
||||
parts := strings.Split(inst, " ")
|
||||
|
||||
// My instruction list gets transformed into this
|
||||
// all jump instructions can be optimized, in particular the ones
|
||||
// tagged here b/c they jump over another jump instruction
|
||||
// effectively becoming multiplication steps
|
||||
// 0 cpy a b
|
||||
// 1 dec b
|
||||
// 2 cpy a d
|
||||
// 3 cpy 0 a
|
||||
// 4 cpy b c
|
||||
// 5 inc a
|
||||
// 6 dec c
|
||||
// 7 jnz c -2
|
||||
// 8 dec d
|
||||
// 9 jnz d -5 // <-
|
||||
// 10 dec b
|
||||
// 11 cpy b c
|
||||
// 12 cpy c d
|
||||
// 13 dec d
|
||||
// 14 inc c
|
||||
// 15 jnz d
|
||||
// 16 tgl c
|
||||
// 17 cpy -16 c
|
||||
// 18 cpy 1 c
|
||||
// 19 cpy 89 c
|
||||
// 20 cpy 77 d
|
||||
// 21 inc a
|
||||
// 22 dec d
|
||||
// 23 jnz d
|
||||
// 24 dec c
|
||||
// 25 jnz c -5 // <-
|
||||
|
||||
// Hard coded multiplication skippers
|
||||
if inst == "jnz d -5" && instructions[instIndex-1] == "dec d" &&
|
||||
instructions[instIndex-2] == "jnz c -2" {
|
||||
registers["a"] += registers["b"] * registers["d"]
|
||||
registers["c"] = 0
|
||||
registers["d"] = 0
|
||||
}
|
||||
|
||||
if inst == "jnz c -5" && instructions[instIndex-1] == "dec c" {
|
||||
registers["a"] += 77 * registers["c"]
|
||||
registers["c"] = 0
|
||||
registers["d"] = 0
|
||||
}
|
||||
|
||||
switch parts[0] {
|
||||
case "cpy":
|
||||
valX := parseValueOrRegister(registers, parts[1])
|
||||
registers[parts[2]] = valX
|
||||
instIndex++
|
||||
case "inc":
|
||||
registers[parts[1]]++
|
||||
instIndex++
|
||||
case "dec":
|
||||
registers[parts[1]]--
|
||||
instIndex++
|
||||
case "jnz":
|
||||
valX := parseValueOrRegister(registers, parts[1])
|
||||
if valX != 0 {
|
||||
valY := parseValueOrRegister(registers, parts[2])
|
||||
instIndex += valY
|
||||
} else {
|
||||
instIndex++
|
||||
}
|
||||
case "tgl":
|
||||
// valX is an offset
|
||||
valX := parseValueOrRegister(registers, parts[1])
|
||||
indexToMod := instIndex + valX
|
||||
if indexToMod < len(instructions) {
|
||||
instToModParts := strings.Split(instructions[indexToMod], " ")
|
||||
var newType string
|
||||
if len(instToModParts) == 2 {
|
||||
newType = "inc"
|
||||
if instToModParts[0] == "inc" {
|
||||
newType = "dec"
|
||||
}
|
||||
} else if len(instToModParts) == 3 {
|
||||
newType = "jnz"
|
||||
if instToModParts[0] == "jnz" {
|
||||
newType = "cpy"
|
||||
}
|
||||
}
|
||||
instToModParts[0] = newType
|
||||
instructions[indexToMod] = strings.Join(instToModParts, " ")
|
||||
}
|
||||
instIndex++
|
||||
default:
|
||||
panic("unhanded instruction type " + parts[0])
|
||||
}
|
||||
}
|
||||
|
||||
return registers["a"]
|
||||
}
|
||||
|
||||
func parseValueOrRegister(registers map[string]int, part string) int {
|
||||
if regexp.MustCompile("[0-9]").MatchString(part) {
|
||||
return cast.ToInt(part)
|
||||
}
|
||||
return registers[part]
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `cpy 2 a
|
||||
tgl a
|
||||
tgl a
|
||||
tgl a
|
||||
cpy 1 a
|
||||
dec a
|
||||
dec a`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"example", example, 1, 3},
|
||||
{"actual", util.ReadFile("input.txt"), 1, 11893},
|
||||
{"actual", util.ReadFile("input.txt"), 2, 479008453},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := assemblyComputer(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("assemblyComputer() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := cleaningRobot(util.ReadFile("./input.txt"), part)
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func cleaningRobot(input string, part int) int {
|
||||
var grid [][]string
|
||||
for _, l := range strings.Split(input, "\n") {
|
||||
grid = append(grid, strings.Split(l, ""))
|
||||
}
|
||||
|
||||
// bfs from every numbered cell to every other
|
||||
// generate a weighted graph
|
||||
var graph [][]int
|
||||
for r, row := range grid {
|
||||
for c, cell := range row {
|
||||
if regexp.MustCompile("[0-9]").MatchString(cell) {
|
||||
poi := cell
|
||||
distancesFromPOI := bfsGetEdgeWeights(grid, [2]int{r, c})
|
||||
// initialize graph size
|
||||
if graph == nil {
|
||||
for i := 0; i < len(distancesFromPOI); i++ {
|
||||
graph = append(graph, make([]int, len(distancesFromPOI)))
|
||||
}
|
||||
}
|
||||
graph[cast.ToInt(poi)] = distancesFromPOI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then a recursive, backtracking dfs on that weighted graph to determine
|
||||
// the shortest total path
|
||||
returnToZero := part != 1
|
||||
return dfs(graph, 0, map[int]bool{0: true}, returnToZero)
|
||||
}
|
||||
|
||||
type bfsNode struct {
|
||||
row, col int // 0,0 is top left
|
||||
distance int
|
||||
}
|
||||
|
||||
// allows passing through points of interest
|
||||
func bfsGetEdgeWeights(grid [][]string, start [2]int) []int {
|
||||
// points of interest to distance to reach them from the starting coord
|
||||
poiToDistance := map[string]int{
|
||||
grid[start[0]][start[1]]: 0,
|
||||
}
|
||||
// run until all nodes have been seen...
|
||||
queue := []bfsNode{
|
||||
{start[0], start[1], 0},
|
||||
}
|
||||
visited := map[[2]int]bool{}
|
||||
for len(queue) > 0 {
|
||||
front := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
if visited[[2]int{front.row, front.col}] {
|
||||
continue
|
||||
}
|
||||
visited[[2]int{front.row, front.col}] = true
|
||||
|
||||
if regexp.MustCompile("[0-9]").MatchString(grid[front.row][front.col]) {
|
||||
poiToDistance[grid[front.row][front.col]] = front.distance
|
||||
}
|
||||
for _, d := range dirs {
|
||||
nextRow, nextCol := front.row+d[0], front.col+d[1]
|
||||
// don't need to check for going out of bounds because there are walls
|
||||
// surrounding everything
|
||||
if grid[nextRow][nextCol] != "#" {
|
||||
queue = append(queue, bfsNode{
|
||||
row: nextRow,
|
||||
col: nextCol,
|
||||
distance: front.distance + 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
distances := make([]int, len(poiToDistance))
|
||||
for numStr, dist := range poiToDistance {
|
||||
distances[cast.ToInt(numStr)] = dist
|
||||
}
|
||||
return distances
|
||||
}
|
||||
|
||||
var dirs = [][2]int{
|
||||
{0, -1},
|
||||
{0, 1},
|
||||
{1, 0},
|
||||
{-1, 0},
|
||||
}
|
||||
|
||||
func dfs(graph [][]int, entryIndex int, visited map[int]bool, returnToZero bool) (minWeightSum int) {
|
||||
// if all nodes have been visited, return zero for part 1, or the distance
|
||||
// from the entryIndex to the zero POI
|
||||
if len(graph) == len(visited) {
|
||||
if returnToZero {
|
||||
return graph[entryIndex][0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// get the minimum distance from a recursive call
|
||||
minDistance := math.MaxInt32
|
||||
for i, val := range graph[entryIndex] {
|
||||
if !visited[i] {
|
||||
visited[i] = true
|
||||
|
||||
dist := val + dfs(graph, i, visited, returnToZero)
|
||||
minDistance = mathy.MinInt(minDistance, dist)
|
||||
|
||||
delete(visited, i)
|
||||
}
|
||||
}
|
||||
|
||||
return minDistance
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
var example = `###########
|
||||
#0.1.....2#
|
||||
#.#######.#
|
||||
#4.......3#
|
||||
###########`
|
||||
|
||||
func Test_cleaningRobot(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int
|
||||
}{
|
||||
{"part1 example", example, 1, 14},
|
||||
{"part1 actual", util.ReadFile("input.txt"), 1, 442},
|
||||
{"part2 actual", util.ReadFile("input.txt"), 2, 660},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := cleaningRobot(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("cleaningRobot() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := part1(util.ReadFile("./input.txt"))
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
for i := 0; i < math.MaxInt32; i++ {
|
||||
if assemblyComputer(input, i) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func assemblyComputer(input string, registerAInitialValue int) bool {
|
||||
pattern := regexp.MustCompile("^(01){1,}$")
|
||||
var outputs string
|
||||
|
||||
var a, b, d int
|
||||
|
||||
a = registerAInitialValue
|
||||
d = a + 2532
|
||||
for {
|
||||
a = d
|
||||
for a != 0 {
|
||||
b = a % 2
|
||||
a /= 2
|
||||
outputs += cast.ToString(b)
|
||||
if len(outputs)%2 == 0 {
|
||||
if !pattern.MatchString(outputs) {
|
||||
return false
|
||||
} else if len(outputs) > 100 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic("should return from loop")
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
// {"actual", util.ReadFile("input.txt"), ACTUAL_ANSWER},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user