fresh start

This commit is contained in:
Thorben Klyn
2022-12-06 08:25:07 +01:00
parent 3bd03ec7b3
commit 032407a954
378 changed files with 17 additions and 35984 deletions
-35
View File
@@ -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
}
-26
View File
@@ -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)
}
})
}
}
-62
View File
@@ -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
}
-45
View File
@@ -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)
}
})
}
}
-71
View File
@@ -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)
}
-41
View File
@@ -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)
}
})
}
}
-38
View File
@@ -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")
}
-26
View File
@@ -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)
}
})
}
}
-87
View File
@@ -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
}
-41
View File
@@ -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)
}
})
}
}
-119
View File
@@ -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
}
-41
View File
@@ -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)
}
})
}
}
-75
View File
@@ -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
}
-47
View File
@@ -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)
}
})
}
}
-63
View File
@@ -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
}
-41
View File
@@ -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)
}
})
}
}
-81
View File
@@ -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
}
-29
View File
@@ -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)
}
})
}
}
-48
View File
@@ -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)
}
-26
View File
@@ -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)
}
})
}
}
-81
View File
@@ -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)
}
-26
View File
@@ -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)
}
})
}
}
-105
View File
@@ -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
}
-45
View File
@@ -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)
}
})
}
}
-80
View File
@@ -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
}
-26
View File
@@ -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)
}
})
}
}
-88
View File
@@ -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
}
-26
View File
@@ -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)
}
})
}
}
-65
View File
@@ -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
}
-29
View File
@@ -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)
}
})
}
}
-99
View File
@@ -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")
}
-26
View File
@@ -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)
}
})
}
}
-67
View File
@@ -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
}
-38
View File
@@ -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)
}
})
}
}
-78
View File
@@ -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
}
-26
View File
@@ -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)
}
})
}
}
-142
View File
@@ -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
}
-59
View File
@@ -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)
}
})
}
}
-53
View File
@@ -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
}
-26
View File
@@ -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)
}
})
}
}
-145
View File
@@ -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
}
-29
View File
@@ -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)
}
})
}
}
-201
View File
@@ -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
}
-36
View File
@@ -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)
}
})
}
}
-71
View File
@@ -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"]
}
-26
View File
@@ -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)
}
})
}
}
-71
View File
@@ -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
-26
View File
@@ -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)
}
})
}
}
-61
View File
@@ -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
}
-24
View File
@@ -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)
}
})
}
}
-61
View File
@@ -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)
}
-26
View File
@@ -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)
}
})
}
}
-84
View File
@@ -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
}
-26
View File
@@ -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)
}
})
}
}
-73
View File
@@ -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
}
-26
View File
@@ -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)
}
})
}
}
-120
View File
@@ -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
}
-47
View File
@@ -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)
}
})
}
}
-55
View File
@@ -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
}
-28
View File
@@ -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)
}
})
}
}
-63
View File
@@ -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
}
-45
View File
@@ -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)
}
})
}
}
-123
View File
@@ -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
}
-53
View File
@@ -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)
}
})
}
}
-89
View File
@@ -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
}
-45
View File
@@ -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)
}
})
}
}
-49
View File
@@ -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)", &copyLen, &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
}
-31
View File
@@ -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)
}
})
}
}
-106
View File
@@ -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
}
-37
View File
@@ -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)
}
})
}
}
-284
View File
@@ -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
}
-32
View File
@@ -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)
}
})
}
}
-63
View File
@@ -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"]
}
-26
View File
@@ -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)
}
})
}
}
-87
View File
@@ -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
}
-31
View File
@@ -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)
}
})
}
}
-68
View File
@@ -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 ""
}
-32
View File
@@ -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)
}
})
}
}
-68
View File
@@ -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
}
-30
View File
@@ -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)
}
})
}
}
-55
View File
@@ -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
}
-26
View File
@@ -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)
}
})
}
}
-79
View File
@@ -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))
}
-28
View File
@@ -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)
}
})
}
}
-63
View File
@@ -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
}
-27
View File
@@ -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)
}
})
}
}
-77
View File
@@ -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
}
-27
View File
@@ -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)
}
})
}
}
-63
View File
@@ -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
}
-31
View File
@@ -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)
}
})
}
}
-117
View File
@@ -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
}
-38
View File
@@ -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)
}
})
}
}
-140
View File
@@ -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
}
-41
View File
@@ -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)
}
})
}
}
-143
View File
@@ -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]
}
-35
View File
@@ -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)
}
})
}
}
-137
View File
@@ -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
}
-33
View File
@@ -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)
}
})
}
}
-57
View File
@@ -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")
}
-22
View File
@@ -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