diff --git a/2015/day01/main.go b/2015/day01/main.go deleted file mode 100644 index 3f163d6..0000000 --- a/2015/day01/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day01/main_test.go b/2015/day01/main_test.go deleted file mode 100644 index dccc605..0000000 --- a/2015/day01/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day02/main.go b/2015/day02/main.go deleted file mode 100644 index 4c46f03..0000000 --- a/2015/day02/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day02/main_test.go b/2015/day02/main_test.go deleted file mode 100644 index 0cd6516..0000000 --- a/2015/day02/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day03/main.go b/2015/day03/main.go deleted file mode 100644 index 50d7fa5..0000000 --- a/2015/day03/main.go +++ /dev/null @@ -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) -} diff --git a/2015/day03/main_test.go b/2015/day03/main_test.go deleted file mode 100644 index e6daafd..0000000 --- a/2015/day03/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day04/main.go b/2015/day04/main.go deleted file mode 100644 index 4aef2c7..0000000 --- a/2015/day04/main.go +++ /dev/null @@ -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") -} diff --git a/2015/day04/main_test.go b/2015/day04/main_test.go deleted file mode 100644 index 8baa1cc..0000000 --- a/2015/day04/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day05/main.go b/2015/day05/main.go deleted file mode 100644 index b8aa2d4..0000000 --- a/2015/day05/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day05/main_test.go b/2015/day05/main_test.go deleted file mode 100644 index 5da6cec..0000000 --- a/2015/day05/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day06/main.go b/2015/day06/main.go deleted file mode 100644 index fbcb4a0..0000000 --- a/2015/day06/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day06/main_test.go b/2015/day06/main_test.go deleted file mode 100644 index cb64f08..0000000 --- a/2015/day06/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day07/main.go b/2015/day07/main.go deleted file mode 100644 index f4632f6..0000000 --- a/2015/day07/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day07/main_test.go b/2015/day07/main_test.go deleted file mode 100644 index b656850..0000000 --- a/2015/day07/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day08/main.go b/2015/day08/main.go deleted file mode 100644 index 09629ac..0000000 --- a/2015/day08/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day08/main_test.go b/2015/day08/main_test.go deleted file mode 100644 index 7a5240e..0000000 --- a/2015/day08/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day09/main.go b/2015/day09/main.go deleted file mode 100644 index 0ea7cae..0000000 --- a/2015/day09/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day09/main_test.go b/2015/day09/main_test.go deleted file mode 100644 index a1a47ad..0000000 --- a/2015/day09/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day10/main.go b/2015/day10/main.go deleted file mode 100644 index 552dd7f..0000000 --- a/2015/day10/main.go +++ /dev/null @@ -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) -} diff --git a/2015/day10/main_test.go b/2015/day10/main_test.go deleted file mode 100644 index fdbde69..0000000 --- a/2015/day10/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day11/main.go b/2015/day11/main.go deleted file mode 100644 index 0354262..0000000 --- a/2015/day11/main.go +++ /dev/null @@ -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) -} diff --git a/2015/day11/main_test.go b/2015/day11/main_test.go deleted file mode 100644 index 0200af8..0000000 --- a/2015/day11/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day12/main.go b/2015/day12/main.go deleted file mode 100644 index 8dc45bf..0000000 --- a/2015/day12/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day12/main_test.go b/2015/day12/main_test.go deleted file mode 100644 index 40341de..0000000 --- a/2015/day12/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day13/main.go b/2015/day13/main.go deleted file mode 100644 index 9ac2a27..0000000 --- a/2015/day13/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day13/main_test.go b/2015/day13/main_test.go deleted file mode 100644 index 33f9ef1..0000000 --- a/2015/day13/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day14/main.go b/2015/day14/main.go deleted file mode 100644 index 1e2a6bd..0000000 --- a/2015/day14/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day14/main_test.go b/2015/day14/main_test.go deleted file mode 100644 index f5c4019..0000000 --- a/2015/day14/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day15/main.go b/2015/day15/main.go deleted file mode 100644 index a1c66e3..0000000 --- a/2015/day15/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day15/main_test.go b/2015/day15/main_test.go deleted file mode 100644 index 595457f..0000000 --- a/2015/day15/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day16/main.go b/2015/day16/main.go deleted file mode 100644 index f15d81e..0000000 --- a/2015/day16/main.go +++ /dev/null @@ -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") -} diff --git a/2015/day16/main_test.go b/2015/day16/main_test.go deleted file mode 100644 index 5bb4fbe..0000000 --- a/2015/day16/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day17/main.go b/2015/day17/main.go deleted file mode 100644 index 699fdf9..0000000 --- a/2015/day17/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day17/main_test.go b/2015/day17/main_test.go deleted file mode 100644 index 286ebde..0000000 --- a/2015/day17/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day18/main.go b/2015/day18/main.go deleted file mode 100644 index 2432466..0000000 --- a/2015/day18/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day18/main_test.go b/2015/day18/main_test.go deleted file mode 100644 index c3734c1..0000000 --- a/2015/day18/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day19/main.go b/2015/day19/main.go deleted file mode 100644 index fbd69e2..0000000 --- a/2015/day19/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day19/main_test.go b/2015/day19/main_test.go deleted file mode 100644 index 7de3cee..0000000 --- a/2015/day19/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day20/main.go b/2015/day20/main.go deleted file mode 100644 index d4f8d18..0000000 --- a/2015/day20/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day20/main_test.go b/2015/day20/main_test.go deleted file mode 100644 index e50f72a..0000000 --- a/2015/day20/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day21/main.go b/2015/day21/main.go deleted file mode 100644 index 96c2b6c..0000000 --- a/2015/day21/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day21/main_test.go b/2015/day21/main_test.go deleted file mode 100644 index 7df3684..0000000 --- a/2015/day21/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day22/main.go b/2015/day22/main.go deleted file mode 100644 index 474a4b6..0000000 --- a/2015/day22/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day22/main_test.go b/2015/day22/main_test.go deleted file mode 100644 index c32f219..0000000 --- a/2015/day22/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day23/main.go b/2015/day23/main.go deleted file mode 100644 index 7d4c01f..0000000 --- a/2015/day23/main.go +++ /dev/null @@ -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"] -} diff --git a/2015/day23/main_test.go b/2015/day23/main_test.go deleted file mode 100644 index 3fa3d98..0000000 --- a/2015/day23/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day24/main.go b/2015/day24/main.go deleted file mode 100644 index 67a7b44..0000000 --- a/2015/day24/main.go +++ /dev/null @@ -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 diff --git a/2015/day24/main_test.go b/2015/day24/main_test.go deleted file mode 100644 index 540b4e6..0000000 --- a/2015/day24/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2015/day25/main.go b/2015/day25/main.go deleted file mode 100644 index a4f3aeb..0000000 --- a/2015/day25/main.go +++ /dev/null @@ -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 -} diff --git a/2015/day25/main_test.go b/2015/day25/main_test.go deleted file mode 100644 index 0f885b2..0000000 --- a/2015/day25/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day01/main.go b/2016/day01/main.go deleted file mode 100644 index 8b2efb5..0000000 --- a/2016/day01/main.go +++ /dev/null @@ -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) -} diff --git a/2016/day01/main_test.go b/2016/day01/main_test.go deleted file mode 100644 index 2c8db31..0000000 --- a/2016/day01/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day02/main.go b/2016/day02/main.go deleted file mode 100644 index 00ce0d3..0000000 --- a/2016/day02/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day02/main_test.go b/2016/day02/main_test.go deleted file mode 100644 index 9aec0ef..0000000 --- a/2016/day02/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day03/main.go b/2016/day03/main.go deleted file mode 100644 index c0ec861..0000000 --- a/2016/day03/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day03/main_test.go b/2016/day03/main_test.go deleted file mode 100644 index 703aaac..0000000 --- a/2016/day03/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day04/main.go b/2016/day04/main.go deleted file mode 100644 index d462ab8..0000000 --- a/2016/day04/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day04/main_test.go b/2016/day04/main_test.go deleted file mode 100644 index d1debb0..0000000 --- a/2016/day04/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day05/main.go b/2016/day05/main.go deleted file mode 100644 index 2e2a235..0000000 --- a/2016/day05/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day05/main_test.go b/2016/day05/main_test.go deleted file mode 100644 index 62864a8..0000000 --- a/2016/day05/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day06/main.go b/2016/day06/main.go deleted file mode 100644 index abac5e5..0000000 --- a/2016/day06/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day06/main_test.go b/2016/day06/main_test.go deleted file mode 100644 index daebf5c..0000000 --- a/2016/day06/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day07/main.go b/2016/day07/main.go deleted file mode 100644 index 1316adc..0000000 --- a/2016/day07/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day07/main_test.go b/2016/day07/main_test.go deleted file mode 100644 index b8f99b0..0000000 --- a/2016/day07/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day08/main.go b/2016/day08/main.go deleted file mode 100644 index bfdb6d9..0000000 --- a/2016/day08/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day08/main_test.go b/2016/day08/main_test.go deleted file mode 100644 index be4f500..0000000 --- a/2016/day08/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day09/main.go b/2016/day09/main.go deleted file mode 100644 index 12aba5c..0000000 --- a/2016/day09/main.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := decompressLength(util.ReadFile("./input.txt"), part) - fmt.Println("Output:", ans) -} - -// well...... this is gross....... -func decompressLength(in string, part int) int { - var decompressedLen int - for i := 0; i < len(in); { - switch in[i] { - case '(': - // find index of closing paren, then find total length of substring - relativeCloseIndex := strings.Index(in[i:], ")") - closeIndex := relativeCloseIndex + i - - var copyLen, repeat int - fmt.Sscanf(in[i:closeIndex+1], "(%dx%d)", ©Len, &repeat) - - substring := in[closeIndex+1 : closeIndex+1+copyLen] - patternLength := len(substring) - if part == 2 { - patternLength = decompressLength(substring, 2) - } - decompressedLen += patternLength * repeat - // jump the closed paren (+1) the length of the substring from THIS - // function call - i = closeIndex + 1 + len(substring) - default: - decompressedLen++ - i++ - } - } - return decompressedLen -} diff --git a/2016/day09/main_test.go b/2016/day09/main_test.go deleted file mode 100644 index 009d6f7..0000000 --- a/2016/day09/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day10/main.go b/2016/day10/main.go deleted file mode 100644 index 9133576..0000000 --- a/2016/day10/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day10/main_test.go b/2016/day10/main_test.go deleted file mode 100644 index 094d50f..0000000 --- a/2016/day10/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day11/main.go b/2016/day11/main.go deleted file mode 100644 index 3753400..0000000 --- a/2016/day11/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day11/main_test.go b/2016/day11/main_test.go deleted file mode 100644 index 52a1072..0000000 --- a/2016/day11/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day12/main.go b/2016/day12/main.go deleted file mode 100644 index 38ede8d..0000000 --- a/2016/day12/main.go +++ /dev/null @@ -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"] -} diff --git a/2016/day12/main_test.go b/2016/day12/main_test.go deleted file mode 100644 index 3e321bd..0000000 --- a/2016/day12/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day13/main.go b/2016/day13/main.go deleted file mode 100644 index cd0ddd4..0000000 --- a/2016/day13/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day13/main_test.go b/2016/day13/main_test.go deleted file mode 100644 index d1c1333..0000000 --- a/2016/day13/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day14/main.go b/2016/day14/main.go deleted file mode 100644 index 165c90f..0000000 --- a/2016/day14/main.go +++ /dev/null @@ -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 "" -} diff --git a/2016/day14/main_test.go b/2016/day14/main_test.go deleted file mode 100644 index e6b29b9..0000000 --- a/2016/day14/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day15/main.go b/2016/day15/main.go deleted file mode 100644 index 7363a6d..0000000 --- a/2016/day15/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day15/main_test.go b/2016/day15/main_test.go deleted file mode 100644 index 3bfeb7e..0000000 --- a/2016/day15/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day16/main.go b/2016/day16/main.go deleted file mode 100644 index c40caa9..0000000 --- a/2016/day16/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day16/main_test.go b/2016/day16/main_test.go deleted file mode 100644 index 6d8ffda..0000000 --- a/2016/day16/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day17/main.go b/2016/day17/main.go deleted file mode 100644 index a94a1c5..0000000 --- a/2016/day17/main.go +++ /dev/null @@ -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)) -} diff --git a/2016/day17/main_test.go b/2016/day17/main_test.go deleted file mode 100644 index 2c40788..0000000 --- a/2016/day17/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day18/main.go b/2016/day18/main.go deleted file mode 100644 index f00c19f..0000000 --- a/2016/day18/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day18/main_test.go b/2016/day18/main_test.go deleted file mode 100644 index ee79599..0000000 --- a/2016/day18/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day19/main.go b/2016/day19/main.go deleted file mode 100644 index de5b072..0000000 --- a/2016/day19/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day19/main_test.go b/2016/day19/main_test.go deleted file mode 100644 index 69008a2..0000000 --- a/2016/day19/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day20/main.go b/2016/day20/main.go deleted file mode 100644 index cd3229a..0000000 --- a/2016/day20/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day20/main_test.go b/2016/day20/main_test.go deleted file mode 100644 index fcfed82..0000000 --- a/2016/day20/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day21/main.go b/2016/day21/main.go deleted file mode 100644 index 6ab7259..0000000 --- a/2016/day21/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day21/main_test.go b/2016/day21/main_test.go deleted file mode 100644 index 2b45966..0000000 --- a/2016/day21/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day22/main.go b/2016/day22/main.go deleted file mode 100644 index 17d74fd..0000000 --- a/2016/day22/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day22/main_test.go b/2016/day22/main_test.go deleted file mode 100644 index 893b7ae..0000000 --- a/2016/day22/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day23/main.go b/2016/day23/main.go deleted file mode 100644 index 12ab518..0000000 --- a/2016/day23/main.go +++ /dev/null @@ -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] -} diff --git a/2016/day23/main_test.go b/2016/day23/main_test.go deleted file mode 100644 index 9c5d4c9..0000000 --- a/2016/day23/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day24/main.go b/2016/day24/main.go deleted file mode 100644 index 9b58668..0000000 --- a/2016/day24/main.go +++ /dev/null @@ -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 -} diff --git a/2016/day24/main_test.go b/2016/day24/main_test.go deleted file mode 100644 index 8d15030..0000000 --- a/2016/day24/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2016/day25/main.go b/2016/day25/main.go deleted file mode 100644 index 165953a..0000000 --- a/2016/day25/main.go +++ /dev/null @@ -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") -} diff --git a/2016/day25/main_test.go b/2016/day25/main_test.go deleted file mode 100644 index 77fb2fd..0000000 --- a/2016/day25/main_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/2017/day01/main.go b/2017/day01/main.go deleted file mode 100644 index ddcc5b9..0000000 --- a/2017/day01/main.go +++ /dev/null @@ -1,55 +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) - - 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 { - digits := parseInput(input) - var sum int - for i := 0; i < len(digits); i++ { - if digits[i] == digits[(i+1)%len(digits)] { - sum += digits[i] - } - } - return sum -} - -func part2(input string) int { - digits := parseInput(input) - var sum int - offset := len(digits) / 2 - for i := 0; i < len(digits); i++ { - if digits[i] == digits[(i+offset)%len(digits)] { - sum += digits[i] - } - } - return sum -} - -func parseInput(input string) (ans []int) { - for _, num := range strings.Split(input, "") { - ans = append(ans, cast.ToInt(num)) - } - return ans -} diff --git a/2017/day01/main_test.go b/2017/day01/main_test.go deleted file mode 100644 index 68690c5..0000000 --- a/2017/day01/main_test.go +++ /dev/null @@ -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 - }{ - {"example1", "1122", 3}, - {"example2", "91212129", 9}, - {"actual", util.ReadFile("input.txt"), 1393}, - } - 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 - }{ - {"example1", "1212", 6}, - {"example2", "123425", 4}, - {"actual", util.ReadFile("input.txt"), 1292}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day02/main.go b/2017/day02/main.go deleted file mode 100644 index b458dfd..0000000 --- a/2017/day02/main.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/mathy" - "github.com/alexchao26/advent-of-code-go/util" -) - -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 { - rows := parseInput(input) - var checksum int - for _, r := range rows { - checksum += mathy.MaxInt(r...) - mathy.MinInt(r...) - } - return checksum -} - -func part2(input string) int { - rows := parseInput(input) - var sumDivisible int - for _, r := range rows { - for i, val1 := range r { - for _, val2 := range r[i+1:] { - if val1%val2 == 0 { - sumDivisible += val1 / val2 - break - } else if val2%val1 == 0 { - sumDivisible += val2 / val1 - break - } - } - } - } - return sumDivisible -} - -func parseInput(input string) (ans [][]int) { - lines := strings.Split(input, "\n") - for i, l := range lines { - ans = append(ans, []int{}) - // split by tabs - for _, num := range strings.Split(l, "\t") { - ans[i] = append(ans[i], cast.ToInt(num)) - } - } - return ans -} diff --git a/2017/day02/main_test.go b/2017/day02/main_test.go deleted file mode 100644 index 3498eb4..0000000 --- a/2017/day02/main_test.go +++ /dev/null @@ -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"), 30994}, - } - 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"), 233}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day03/main.go b/2017/day03/main.go deleted file mode 100644 index 6d4bb32..0000000 --- a/2017/day03/main.go +++ /dev/null @@ -1,124 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "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) - - 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 { - inputNum := cast.ToInt(input) - - directions := [][2]int{ - {0, 1}, // right - {-1, 0}, // north - {0, -1}, // west - {1, 0}, // south - } - - coordsToSquareNum := map[[2]int]int{ - [2]int{0, 0}: 1, - [2]int{0, 1}: 2, - } - - // @ every step, check left, i.e. see if we can turn right - // if it's not in map, turn & move there - // otherwise continue adding in same direction - directionIndex := 0 // start facing right - row, col := 0, 1 - number := 2 - for len(coordsToSquareNum) < inputNum { - leftDiff := directions[(directionIndex+1)%4] - leftCoord := [2]int{row + leftDiff[0], col + leftDiff[1]} - if _, ok := coordsToSquareNum[leftCoord]; !ok { - // turn left first - directionIndex = (directionIndex + 1) % 4 - } - // move to coordinate in front & add it to the map - diff := directions[directionIndex] - row += diff[0] - col += diff[1] - next := [2]int{row, col} - coordsToSquareNum[next] = number - number++ // increment number - } - - return mathy.ManhattanDistance(0, 0, row, col) -} - -func part2(input string) int { - inputNum := cast.ToInt(input) - - directions := [][2]int{ - {0, 1}, // right - {-1, 0}, // north - {0, -1}, // west - {1, 0}, // south - } - - allNeighborDiffs := [][2]int{ - {-1, -1}, - {-1, 0}, - {-1, 1}, - {0, -1}, - {0, 1}, - {1, -1}, - {1, 0}, - {1, 1}, - } - - coordsToNeighborSum := map[[2]int]int{ - [2]int{0, 0}: 1, - [2]int{0, 1}: 1, - } - - // @ every step, check left, i.e. see if we can turn right - // if it's not in map, turn & move there - // otherwise continue adding in same direction - directionIndex := 0 // start facing right - row, col := 0, 1 - for len(coordsToNeighborSum) < inputNum { - leftDiff := directions[(directionIndex+1)%4] - leftCoord := [2]int{row + leftDiff[0], col + leftDiff[1]} - if _, ok := coordsToNeighborSum[leftCoord]; !ok { - // turn left first - directionIndex = (directionIndex + 1) % 4 - } - // move to coordinate in front & add it to the map - diff := directions[directionIndex] - row += diff[0] - col += diff[1] - next := [2]int{row, col} - - var sum int - for _, d := range allNeighborDiffs { - sum += coordsToNeighborSum[[2]int{row + d[0], col + d[1]}] - } - - if sum > inputNum { - return sum - } - - coordsToNeighborSum[next] = sum - } - - panic("uh something broke, return should occur in for loop") -} diff --git a/2017/day03/main_test.go b/2017/day03/main_test.go deleted file mode 100644 index 98d175a..0000000 --- a/2017/day03/main_test.go +++ /dev/null @@ -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"), 326}, - } - 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"), 363010}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day04/main.go b/2017/day04/main.go deleted file mode 100644 index 80df226..0000000 --- a/2017/day04/main.go +++ /dev/null @@ -1,78 +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) - - 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 { - phrases := parseInput(input) - var valid int - for _, phrase := range phrases { - seen := map[string]bool{} - valid++ // include by default, remove if it is not valid - for _, word := range phrase { - // if word is a duplicate, decrement valid & break out - if seen[word] { - valid-- - break - } - seen[word] = true - } - } - - return valid -} - -func part2(input string) int { - phrases := parseInput(input) - var valid int - for _, phrase := range phrases { - seen := map[string]bool{} - valid++ // include by default, remove if it is not valid - for _, word := range phrase { - sortedWord := orderCharacters(word) - // if word is a duplicate, decrement valid & break out - if seen[sortedWord] { - valid-- - break - } - seen[sortedWord] = true - } - } - - return valid -} - -func orderCharacters(str string) string { - chars := strings.Split(str, "") - sort.Strings(chars) - return strings.Join(chars, "") -} - -func parseInput(input string) (ans [][]string) { - lines := strings.Split(input, "\n") - for _, l := range lines { - ans = append(ans, strings.Split(l, " ")) - } - return ans -} diff --git a/2017/day04/main_test.go b/2017/day04/main_test.go deleted file mode 100644 index 9a99d84..0000000 --- a/2017/day04/main_test.go +++ /dev/null @@ -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"), 466}, - } - 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"), 251}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day05/main.go b/2017/day05/main.go deleted file mode 100644 index b2179c4..0000000 --- a/2017/day05/main.go +++ /dev/null @@ -1,64 +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) - - 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 { - maze := parseInput(input) - - var index, steps int - for index >= 0 && index < len(maze) { - maze[index]++ - index += maze[index] - 1 - steps++ - } - - return steps -} - -func part2(input string) int { - maze := parseInput(input) - - var index, steps int - for index >= 0 && index < len(maze) { - nextIndex := maze[index] - if maze[index] >= 3 { - maze[index]-- - } else { - maze[index]++ - } - index += nextIndex - steps++ - } - - return steps -} - -func parseInput(input string) (ans []int) { - lines := strings.Split(input, "\n") - for _, l := range lines { - ans = append(ans, cast.ToInt(l)) - } - return ans -} diff --git a/2017/day05/main_test.go b/2017/day05/main_test.go deleted file mode 100644 index 920f8e6..0000000 --- a/2017/day05/main_test.go +++ /dev/null @@ -1,42 +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"), 373160}, - } - 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 - }{ - {"example", "0\n3\n0\n1\n-3", 10}, - {"actual", util.ReadFile("input.txt"), 26395586}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day06/main.go b/2017/day06/main.go deleted file mode 100644 index e7dca93..0000000 --- a/2017/day06/main.go +++ /dev/null @@ -1,77 +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) - - if part == 1 { - ans := memoryReallocation(util.ReadFile("./input.txt"), 1) - fmt.Println("Output:", ans) - } else { - ans := memoryReallocation(util.ReadFile("./input.txt"), 2) - fmt.Println("Output:", ans) - } -} - -func memoryReallocation(input string, part int) int { - banks := parseInput(input) - - // [16]int arrays are comparable by values so can be used as map keys - seenBanks := map[[16]int]int{banks: 0} - var cycles int - for { - // find largest bank - var index, maxVal int - for i, val := range banks { - if val > maxVal { - index = i - maxVal = val - } - } - - // run a cycle - blocksToDistribute := banks[index] - banks[index] = 0 - // unoptimized but works just fine - for i := index + 1; blocksToDistribute > 0; i++ { - if blocksToDistribute == 0 { - break - } - banks[i%16]++ - blocksToDistribute-- - } - cycles++ - - // check if this set of banks has been seen before, if so return here - if val, ok := seenBanks[banks]; ok { - if part == 1 { - return cycles - } - // for part 2 take the difference with cycles when it was last seen - return cycles - val - } - // set the number of cycles that correspond to this state of banks - seenBanks[banks] = cycles - } - - panic("should resolve in for loop") -} - -func parseInput(input string) (ans [16]int) { - nums := strings.Split(input, "\t") - for i, num := range nums { - ans[i] = cast.ToInt(num) - } - return ans -} diff --git a/2017/day06/main_test.go b/2017/day06/main_test.go deleted file mode 100644 index f0bd7d0..0000000 --- a/2017/day06/main_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func Test_memoryReallocation(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"actual", util.ReadFile("input.txt"), 1, 6681}, - {"actual", util.ReadFile("input.txt"), 2, 2392}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := memoryReallocation(tt.input, tt.part); got != tt.want { - t.Errorf("memoryReallocation() = %v, want %v", got, tt.want) - } - }) - } - -} diff --git a/2017/day07/main.go b/2017/day07/main.go deleted file mode 100644 index 4be1a6f..0000000 --- a/2017/day07/main.go +++ /dev/null @@ -1,159 +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) - - 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) string { - graph := parseInput(input) - - allNames := map[string]bool{} - for k := range graph { - allNames[k] = true - } - - // iterate through all graph edges and remove any dependants from allNames - // map b/c that cannot be at the bottom of the stack - for _, node := range graph { - for _, name := range node.edges { - delete(allNames, name) - } - } - - if len(allNames) != 1 { - panic("Expected one name left, got" + cast.ToString(len(allNames))) - } - - // have to iterate over graph to get remaining name - var nameAtBottom string - for name := range allNames { - nameAtBottom = name - } - return nameAtBottom -} - -// NOTE this is not a fully generalized solution, there are graphs where this -// could fail. Although I suspect AoC inputs are tailored to make this solution -// sufficient -func part2(input string) int { - graph := parseInput(input) - currentNode := part1(input) - weightCalculator := memoCalcWeight(graph) - - var siblings []string - // should not run for more than the number of nodes in the graph - for indexToExit := 0; indexToExit < len(graph); indexToExit++ { - // store dependents which have a particular weight - weightToDependents := map[int][]string{} - for _, dependentName := range graph[currentNode].edges { - weight := weightCalculator(dependentName) - weightToDependents[weight] = append(weightToDependents[weight], dependentName) - } - - // one of the dependents has a different weight than the others, dive into it - if len(weightToDependents) > 1 { - // store its siblings b/c IF the next loop finds that all dependents - // are an equal weight, we need to compare the current set of siblings - siblings = graph[currentNode].edges - - // find the node to dive into - for _, names := range weightToDependents { - if len(names) == 1 { - currentNode = names[0] - } - } - } else if len(weightToDependents) == 1 { - // if dependents all have the same weight, this node is the problem. - // compare to its siblings to determine what its weight should be - currentWeight := weightCalculator(currentNode) - for _, sib := range siblings { - if sib != currentNode { - desiredWeight := weightCalculator(sib) - // apply diff to current node's weight and return it - return graph[currentNode].weight - (currentWeight - desiredWeight) - } - } - } else { - panic("unhandled case, weightToDependents == 0") - } - } - - panic("something's wrong in the loop...") -} - -// memoized: calculate the weight, including children, of a given node name -func memoCalcWeight(graph map[string]graphNode) func(string) int { - memo := make(map[string]int, len(graph)) - - var closureFunc func(string) int - closureFunc = func(rootName string) int { - // return from memo if possible - if wt, ok := memo[rootName]; ok { - return wt - } - - // otherwise calculate & set in memo - sum := graph[rootName].weight - for _, dependent := range graph[rootName].edges { - sum += closureFunc(dependent) - } - memo[rootName] = sum - - return sum - } - return closureFunc -} - -type graphNode struct { - name string // unused but useful for debugging - weight int - edges []string -} - -func parseInput(input string) map[string]graphNode { - lines := strings.Split(input, "\n") - graph := make(map[string]graphNode, len(lines)) - for _, l := range lines { - parts := strings.Split(l, " -> ") - - leftParts := strings.Split(parts[0], " ") - name := leftParts[0] - weight := cast.ToInt(leftParts[1][1 : len(leftParts[1])-1]) - - var edges []string - if len(parts) == 2 { - edges = strings.Split(parts[1], ", ") - // assumption confirmed that all nodes have zero or >= 2 edges - if len(edges) == 1 { - panic("Assumed no nodes have exactly one dependant, but got 1 for node: " + name) - } - } - graph[name] = graphNode{ - name: name, - weight: weight, - edges: edges, - } - } - - return graph -} diff --git a/2017/day07/main_test.go b/2017/day07/main_test.go deleted file mode 100644 index 1c6799e..0000000 --- a/2017/day07/main_test.go +++ /dev/null @@ -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 string - }{ - {"actual", util.ReadFile("input.txt"), "xegshds"}, - } - 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"), 299}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day08/main.go b/2017/day08/main.go deleted file mode 100644 index e66dac5..0000000 --- a/2017/day08/main.go +++ /dev/null @@ -1,96 +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 := calcRegisters(util.ReadFile("./input.txt"), part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func calcRegisters(input string, part int) int { - instructions := parseInput(input) - - registers := map[string]int{} - var highestEverRegister int // for part 2 - for _, inst := range instructions { - registerVal := registers[inst.conditional[0]] - compareVal := cast.ToInt(inst.conditional[2]) - var conditionalResult bool - switch inst.conditional[1] { - case "==": - conditionalResult = registerVal == compareVal - case "!=": - conditionalResult = registerVal != compareVal - case "<": - conditionalResult = registerVal < compareVal - case ">": - conditionalResult = registerVal > compareVal - case "<=": - conditionalResult = registerVal <= compareVal - case ">=": - conditionalResult = registerVal >= compareVal - default: - panic("unhandled operator") - } - if conditionalResult { - registers[inst.registerName] += inst.diff - } - highestEverRegister = mathy.MaxInt(highestEverRegister, registers[inst.registerName]) - } - - largestFinalRegister := -math.MaxInt32 - for _, v := range registers { - largestFinalRegister = mathy.MaxInt(largestFinalRegister, v) - } - - if part == 1 { - return largestFinalRegister - } - return highestEverRegister -} - -type instruction struct { - registerName string // register to modify - diff int // if instruction is a dec, multiply by -1 - conditional [3]string // contains all 3 parts @ end of each instruction -} -type compareFunc func(value int) bool - -func parseInput(input string) []instruction { - lines := strings.Split(input, "\n") - var instructions []instruction - - for _, l := range lines { - parts := strings.Split(l, " ") - if len(parts) != 7 { - panic("parts len to seven") - } - inst := instruction{ - registerName: parts[0], - diff: cast.ToInt(parts[2]), - conditional: [3]string{parts[4], parts[5], parts[6]}, - } - if parts[1] == "dec" { - inst.diff *= -1 - } - - instructions = append(instructions, inst) - } - - return instructions -} diff --git a/2017/day08/main_test.go b/2017/day08/main_test.go deleted file mode 100644 index c057930..0000000 --- a/2017/day08/main_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `b inc 5 if a > 1 -a inc 1 if b < 5 -c dec -10 if a >= 1 -c inc -20 if c == 10` - -func Test_calcRegisters(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"example-part1", example, 1, 1}, - {"actual-part1", util.ReadFile("input.txt"), 1, 4066}, - {"example-part2", example, 2, 10}, - {"actual-part2", util.ReadFile("input.txt"), 2, 4829}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := calcRegisters(tt.input, tt.part); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day09/main.go b/2017/day09/main.go deleted file mode 100644 index f501cb0..0000000 --- a/2017/day09/main.go +++ /dev/null @@ -1,55 +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 := streamProcessing(util.ReadFile("./input.txt"), part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func streamProcessing(input string, part int) int { - var totalScore, garbageCount int - - var inGarbage bool - var openCurlies int // equivalent to the current group's score - - for i := 0; i < len(input); i++ { - char := string(input[i]) - if inGarbage { - switch char { - case "!": - i++ - case ">": - inGarbage = false - default: - garbageCount++ // part 2 - } - } else { - switch char { - case "{": - openCurlies++ - case "}": - totalScore += openCurlies // part 1 - openCurlies-- - case "<": - inGarbage = true - } - } - } - - if part == 1 { - return totalScore - } - return garbageCount -} diff --git a/2017/day09/main_test.go b/2017/day09/main_test.go deleted file mode 100644 index 90d51dd..0000000 --- a/2017/day09/main_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func Test_streamProcessing(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"part1_example 1", "{}", 1, 1}, - {"part1_example 2", "{{}}", 1, 3}, - {"part1_example 1", "{{}, {}}", 1, 5}, - {"part1_example 1", "{{}, {}}", 1, 5}, - {"part1_actual", util.ReadFile("input.txt"), 1, 16021}, - {"part2_actual", util.ReadFile("input.txt"), 2, 7685}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := streamProcessing(tt.input, tt.part); got != tt.want { - t.Errorf("streamProcessing() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day10/main.go b/2017/day10/main.go deleted file mode 100644 index 0137cf1..0000000 --- a/2017/day10/main.go +++ /dev/null @@ -1,116 +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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt"), 256) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - fmt.Println("Output:", ans) - } -} - -func part1(input string, listLength int) int { - lengths := parseInput(input) - - nums := make([]int, listLength) - for i := range nums { - nums[i] = i - } - var position, skipSize int - for _, length := range lengths { - if length > 0 { - nums = reverse(nums, position, position+length-1) - } - position += skipSize + length - position %= len(nums) - skipSize++ - } - - return nums[0] * nums[1] -} - -func part2(input string) string { - lengths := parseInputASCII(input) - nums := make([]int, 256) - for i := range nums { - nums[i] = i - } - var position, skipSize int - - for round := 0; round < 64; round++ { - for _, length := range lengths { - if length > 0 { - nums = reverse(nums, position, position+length-1) - } - position += skipSize + length - position %= len(nums) - skipSize++ - } - } - - var denseHash []int - for i := 0; i < 16; i++ { - var xord int - for j := i * 16; j < (i+1)*16; j++ { - xord ^= nums[j] - } - denseHash = append(denseHash, xord) - } - - var hexdHash string - for _, dense := range denseHash { - // use %x to get hexadecimal version & 02 ensures leading 0 if needed - hexdHash += fmt.Sprintf("%02x", dense) - } - - return hexdHash -} - -func reverse(nums []int, left, right int) []int { - right %= len(nums) - if right < left { - right += len(nums) - } - - for left < right { - leftModded := left % len(nums) - rightModded := right % len(nums) - nums[leftModded], nums[rightModded] = nums[rightModded], nums[leftModded] - left++ - right-- - } - - return nums -} - -func parseInput(input string) (ans []int) { - nums := strings.Split(input, ",") - for _, num := range nums { - ans = append(ans, cast.ToInt(num)) - } - return ans -} - -func parseInputASCII(input string) (ans []int) { - for _, char := range input { - ans = append(ans, int(char)) - } - // add default lengths to end - ans = append(ans, 17, 31, 73, 47, 23) - return ans -} diff --git a/2017/day10/main_test.go b/2017/day10/main_test.go deleted file mode 100644 index 32f6c8a..0000000 --- a/2017/day10/main_test.go +++ /dev/null @@ -1,44 +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 - listLength int - want int - }{ - {"example", "3,4,1,5", 5, 12}, - {"actual", util.ReadFile("input.txt"), 256, 212}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input, tt.listLength); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want string - }{ - {"example", "1,2,3", "3efbe78a8d82f29979031a4aa0b16a9d"}, - {"actual", util.ReadFile("input.txt"), "96de9657665675b51cd03f0b3528ba26"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day11/main.go b/2017/day11/main.go deleted file mode 100644 index cd0c0c5..0000000 --- a/2017/day11/main.go +++ /dev/null @@ -1,75 +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 := hexEd(util.ReadFile("./input.txt"), part) - fmt.Println("Output:", ans) -} - -var dirIndices = map[string]int{ - "n": 0, - "ne": 1, - "se": 2, - "s": 3, - "sw": 4, - "nw": 5, -} - -func hexEd(input string, part int) int { - steps := strings.Split(input, ",") - tallyDirections := make([]int, 6) - var furthest int // for part 2 - for _, step := range steps { - tallyDirections[dirIndices[step]]++ - distanceFromStart := getDistanceFromOrigin(tallyDirections) - furthest = mathy.MaxInt(furthest, distanceFromStart) - } - - if part == 1 { - return getDistanceFromOrigin(tallyDirections) - } - return furthest -} - -func getDistanceFromOrigin(tally []int) int { - // zero out opposite indices, after this, there will be at most 3 positive - // values in the slice - for i := range tally { - if tally[i] != 0 { - oppositeIndex := (i + 3) % 6 - smaller := mathy.MinInt(tally[oppositeIndex], tally[i]) - tally[oppositeIndex] -= smaller - tally[i] -= smaller - } - } - - // handle neighbors, which collapse into the current direction - // e.g. sw,se == s - for i := range tally { - toLeft := (i + 5) % 6 - toRight := (i + 1) % 6 - if tally[toLeft] > 0 && tally[toRight] > 0 { - smaller := mathy.MinInt(tally[toLeft], tally[toRight]) - tally[toLeft] -= smaller - tally[toRight] -= smaller - tally[i] += smaller - } - } - - distanceFromOrigin := mathy.SumIntSlice(tally) - - return distanceFromOrigin -} diff --git a/2017/day11/main_test.go b/2017/day11/main_test.go deleted file mode 100644 index 9f7199d..0000000 --- a/2017/day11/main_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func Test_hexEd(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"actual-part1", util.ReadFile("input.txt"), 1, 794}, - {"actual-part2", util.ReadFile("input.txt"), 2, 1524}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := hexEd(tt.input, tt.part); got != tt.want { - t.Errorf("hexEd() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day12/main.go b/2017/day12/main.go deleted file mode 100644 index 89c05db..0000000 --- a/2017/day12/main.go +++ /dev/null @@ -1,98 +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) - - 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 := makeGraphFromInput(input) - - var count int - for k := range graph { - if dfsCanReachTarget(graph, k, 0, map[int]bool{}) { - count++ - } - } - - return count -} - -func part2(input string) int { - graph := makeGraphFromInput(input) - - allKeys := []int{} - for k := range graph { - allKeys = append(allKeys, k) - } - - var groupCount int - // nodes that have been added to a group (that has been counted) - hasBeenGrouped := map[int]bool{} - - for target := range graph { - if !hasBeenGrouped[target] { - // iterate through all graph nodes and check if they can be reached - for k := range graph { - // performance optimization: skip nodes that are already grouped - if k != target && !hasBeenGrouped[k] { - // if this group can reach the target, they're part of that group - if dfsCanReachTarget(graph, k, target, map[int]bool{}) { - hasBeenGrouped[k] = true - } - } - } - groupCount++ - } - } - - return groupCount -} - -func dfsCanReachTarget(graph map[int][]int, entry int, target int, visited map[int]bool) bool { - // break infinite loops - if visited[entry] { - return false - } - visited[entry] = true - - for _, child := range graph[entry] { - if child == target || dfsCanReachTarget(graph, child, target, visited) { - return true - } - } - // default to returning false - return false -} - -func makeGraphFromInput(input string) map[int][]int { - lines := strings.Split(input, "\n") - graph := make(map[int][]int, len(lines)) - for _, l := range lines { - parts := strings.Split(l, " <-> ") - ID := cast.ToInt(parts[0]) - for _, child := range strings.Split(parts[1], ", ") { - graph[ID] = append(graph[ID], cast.ToInt(child)) - } - } - return graph -} diff --git a/2017/day12/main_test.go b/2017/day12/main_test.go deleted file mode 100644 index 8d2f1ef..0000000 --- a/2017/day12/main_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "testing" - "time" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `0 <-> 2 -1 <-> 1 -2 <-> 0, 3, 4 -3 <-> 2, 4 -4 <-> 2, 3, 6 -5 <-> 6 -6 <-> 4, 5` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example, 6}, - {"actual", util.ReadFile("input.txt"), 169}, - } - 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 - }{ - {"example", example, 2}, - {"actual", util.ReadFile("input.txt"), 179}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - startTime := time.Now() - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - t.Logf("Run time for %s: %v", tt.name, time.Since(startTime)) - }) - } -} diff --git a/2017/day13/main.go b/2017/day13/main.go deleted file mode 100644 index 6ae9e8a..0000000 --- a/2017/day13/main.go +++ /dev/null @@ -1,85 +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) - - 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 { - freqs := parseFrequencies(input) - - var severity int - for _, freq := range freqs { - // depthIndex is also equivalent to the number of picoseconds that have - // elapsed when we reach a particular depth of the firewall - depthIndex := freq[0] - - // frequency to zero index is how many picoseconds it takes the scanner - // to return to the zero index position - frequencyToZeroIndex := (freq[1] - 1) * 2 - - // if frequency is evenly divisible by the time elapsed, then we're - // "caught" by this scanner, add to severity - if depthIndex%frequencyToZeroIndex == 0 { - severity += depthIndex * freq[1] - } - } - - return severity -} - -func part2(input string) int { - freqs := parseFrequencies(input) - - // same logic to part 1, but add a delay to the time elapsed and return which - // delay leads to not getting caught by a scanner - for delay := 0; delay < math.MaxInt32; delay++ { - var gotCaught bool - for _, freq := range freqs { - depthIndex := freq[0] - frequencyToZeroIndex := (freq[1] - 1) * 2 - // add delay to the time depthIndex, as it takes longer to get there - if (depthIndex+delay)%frequencyToZeroIndex == 0 { - gotCaught = true - } - } - if !gotCaught { - return delay - } - } - - panic("loop ended, increase limit?") -} - -func parseFrequencies(input string) [][2]int { - lines := strings.Split(input, "\n") - var freqs [][2]int // depth, range - for _, l := range lines { - // depth is equivalent to the index in the firewall - // range can be used to calculate the frequency in which a scanner - // returns to index zero - var depth, rng int - fmt.Sscanf(l, "%d: %d", &depth, &rng) - freqs = append(freqs, [2]int{depth, rng}) - } - return freqs -} diff --git a/2017/day13/main_test.go b/2017/day13/main_test.go deleted file mode 100644 index 87886de..0000000 --- a/2017/day13/main_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "testing" - "time" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `0: 3 -1: 2 -4: 4 -6: 4` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example, 24}, - {"actual", util.ReadFile("input.txt"), 2508}, - } - 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 - }{ - {"example", example, 10}, - {"actual", util.ReadFile("input.txt"), 3913186}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - startTime := time.Now() - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - t.Logf("Runtime for %s: %v", tt.name, time.Since(startTime)) - }) - } -} diff --git a/2017/day14/main.go b/2017/day14/main.go deleted file mode 100644 index 50b4545..0000000 --- a/2017/day14/main.go +++ /dev/null @@ -1,181 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "strconv" - "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 := diskDefrag(util.ReadFile("./input.txt"), part) - fmt.Println("Output:", ans) -} - -func diskDefrag(input string, part int) int { - // 128x128 grid, free (false) or used (true) - diskState := make([][]bool, 128) - for i := 0; i < 128; i++ { - // hash input = {puzzleInput}-{0 through 127} - hashKeyRaw := fmt.Sprintf("%s-%d", input, i) - hexHash := calcHexKnotHash(hashKeyRaw) - - // convert hex-hash to bits - bitsHash := transformHexToBits(hexHash) - - if len(bitsHash) != 128 { - panic(fmt.Sprintf("expected bit hash to be 128 chars long, got %d", len(bitsHash))) - } - - // set disk state for this row - diskState[i] = make([]bool, 128) - for b, bit := range bitsHash { - switch bit { - case '0': - diskState[i][b] = false // free - case '1': - diskState[i][b] = true // used - } - } - } - - if part == 1 { - // tally up used cells for part 1 - var usedCount int - for _, row := range diskState { - for _, b := range row { - if b { - usedCount++ - } - } - } - - return usedCount - } - - return numIslands(diskState) -} - -func parseInputASCII(input string) (ans []int) { - for _, char := range input { - ans = append(ans, int(char)) - } - // add default lengths to end - ans = append(ans, 17, 31, 73, 47, 23) - return ans -} - -func calcHexKnotHash(input string) string { - lengths := parseInputASCII(input) - nums := make([]int, 256) - for i := range nums { - nums[i] = i - } - var position, skipSize int - - // 64 rounds of hashing - for i := 0; i < 64; i++ { - for _, length := range lengths { - if length > 0 { - nums = reverse(nums, position, position+length-1) - } - position += skipSize + length - position %= len(nums) - skipSize++ - } - } - - var denseHash []int - for i := 0; i < 16; i++ { - var xord int - for j := i * 16; j < (i+1)*16; j++ { - xord ^= nums[j] - } - denseHash = append(denseHash, xord) - } - var hexdHash string - for _, dense := range denseHash { - // use %x to get hexadecimal version & 02 ensures leading 0 if needed - hexdHash += fmt.Sprintf("%02x", dense) - } - - return hexdHash -} - -// helper for knot hasher -func reverse(nums []int, left, right int) []int { - right %= len(nums) - if right < left { - right += len(nums) - } - - for left < right { - leftModded := left % len(nums) - rightModded := right % len(nums) - nums[leftModded], nums[rightModded] = nums[rightModded], nums[leftModded] - left++ - right-- - } - - return nums -} - -func transformHexToBits(hex string) string { - var bits string - for _, char := range strings.Split(hex, "") { - // parse hex (base 16) to an int type (always base 10) - baseTen, err := strconv.ParseInt(char, 16, 64) - if err != nil { - panic("strconv error " + err.Error()) - } - // add to bits string in binary form, 4 characters required - bits += fmt.Sprintf("%04b", baseTen) - } - return bits -} - -// for part 2, iterative algo for counting the number of connected islands -func numIslands(grid [][]bool) int { - var count int - - directions := [4][2]int{ - {0, 1}, - {0, -1}, - {1, 0}, - {-1, 0}, - } - for i := 0; i < len(grid); i++ { - for j := 0; j < len(grid[0]); j++ { - if grid[i][j] { - // zero out connected cells (i.e. zero out this island) - queue := [][2]int{{i, j}} - for len(queue) > 0 { - // pop off queue - currentRow, currentCol := queue[0][0], queue[0][1] - grid[currentRow][currentCol] = false - queue = queue[1:] - - // check neighbors, add to queue if true - for _, dir := range directions { - nextRow, nextCol := currentRow+dir[0], currentCol+dir[1] - if nextRow >= 0 && nextRow < len(grid) && nextCol >= 0 && nextCol < len(grid[0]) { - if grid[nextRow][nextCol] { - queue = append(queue, [2]int{nextRow, nextCol}) - } - } - } - } - count++ - } - } - } - - return count -} diff --git a/2017/day14/main_test.go b/2017/day14/main_test.go deleted file mode 100644 index 0e5cc5f..0000000 --- a/2017/day14/main_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func Test_diskDefrag(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"example_part1", "flqrgnkx", 1, 8108}, - {"actual_part1", util.ReadFile("input.txt"), 1, 8204}, - {"example_part2", "flqrgnkx", 2, 1242}, - // {"actual_part2", util.ReadFile("input.txt"), 2, 1089}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := diskDefrag(tt.input, tt.part); got != tt.want { - t.Errorf("diskDefrag() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day15/main.go b/2017/day15/main.go deleted file mode 100644 index 7bbdabc..0000000 --- a/2017/day15/main.go +++ /dev/null @@ -1,79 +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 := duelingGenerators(util.ReadFile("./input.txt"), part) - fmt.Println("Output:", ans) -} - -func duelingGenerators(input string, part int) int { - values := parseInput(input) - - factors := []int{16807, 48271} - divisor := 2147483647 - - // set criteria for passing a value to judge, and rounds based on part num - criteria := []int{1, 1} - rounds := 40000000 - if part == 2 { - rounds = 5000000 - criteria = []int{4, 8} - } - - var judgeCount int - for i := 0; i < rounds; i++ { - for i, v := range values { - values[i] = getNextValue(v, factors[i], divisor, criteria[i]) - } - - // do the back 16 line up ? - // XOR them together, the last 16 should not have bits because they should - // should be zero'ed out. - // (XOR result) % (2^16) will be zero (i.e. no remainder) if back 16 match - compareVal := values[0] ^ values[1] - twoPow16 := 1 << 16 - if (compareVal % twoPow16) == 0 { - judgeCount++ - } - } - - return judgeCount -} - -// iterate until a value that can be passed to the judge is reached -// for part 1 the criteria will be 1, so only one iteration is made -// for part 2 it will be 4 or 8, so the loop will run until a good value is reached -func getNextValue(value, factor, divisor, criteria int) int { - // run at least once - value *= factor - value %= divisor - - for value%criteria != 0 { - value *= factor - value %= divisor - } - - return value -} - -func parseInput(input string) (ans []int) { - lines := strings.Split(input, "\n") - for _, l := range lines { - split := strings.Split(l, " starts with ") - ans = append(ans, cast.ToInt(split[1])) - } - return ans -} diff --git a/2017/day15/main_test.go b/2017/day15/main_test.go deleted file mode 100644 index ff8d4c2..0000000 --- a/2017/day15/main_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `Generator A starts with 65 -Generator B starts with 8921` - -func Test_duelingGenerators(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"example_part1", example, 1, 588}, - {"actual_part1", util.ReadFile("input.txt"), 1, 592}, - {"example_part2", example, 2, 309}, - {"actual_part2", util.ReadFile("input.txt"), 2, 320}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := duelingGenerators(tt.input, tt.part); got != tt.want { - t.Errorf("duelingGenerators() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day16/main.go b/2017/day16/main.go deleted file mode 100644 index 56dd565..0000000 --- a/2017/day16/main.go +++ /dev/null @@ -1,96 +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 := permPromenade(util.ReadFile("./input.txt"), part) - fmt.Println("Output:", ans) -} - -func permPromenade(input string, part int) string { - // electing to be laxy and parse values out of each step when I get there - // instead of doing it all upfront - steps := strings.Split(input, ",") - - // init programs slice - var programs []string - for i := 0; i < 16; i++ { - programs = append(programs, string(rune('a'+i))) - } - - rounds := 1 - if part == 2 { - rounds = 1000000000 - } - - // for part 2 to determine if a state has been seen before, and leveraging - // that frequency to minimize operations of the steps between them - seenStateToIndex := map[string]int{} - for i := 0; i < rounds; i++ { - for _, step := range steps { - switch step[0] { - case 's': - countToSpin := cast.ToInt(step[1:]) - fromEnd := programs[len(programs)-countToSpin:] - fromFront := programs[:len(programs)-countToSpin] - programs = append(fromEnd, fromFront...) - case 'x': - var index1, index2 int - _, err := fmt.Sscanf(step, "x%d/%d", &index1, &index2) - if err != nil { - panic("error parsing an 'x' step " + err.Error()) - } - programs[index1], programs[index2] = programs[index2], programs[index1] - case 'p': - var char1, char2 string - _, err := fmt.Sscanf(step, "p%1s/%1s", &char1, &char2) - if err != nil { - panic("error parsing a 'p' step " + err.Error()) - } - // find index then swap. Programs is 16 elements so this isn't THAT slow - index1, index2 := -1, -1 - for i, v := range programs { - if v == char1 { - index1 = i - } else if v == char2 { - index2 = i - } - } - programs[index1], programs[index2] = programs[index2], programs[index1] - default: - panic("unfound step type " + string(step[0])) - } - } - // stringify so they're comparable and can be used as a map key - state := stringify(programs) - if lastSeenIndex, ok := seenStateToIndex[state]; ok { - diff := i - lastSeenIndex - for i+diff < rounds { - i += diff - } - } - seenStateToIndex[state] = i - } - - return stringify(programs) -} - -func stringify(programs []string) string { - var state string - for _, v := range programs { - state += v - } - return state -} diff --git a/2017/day16/main_test.go b/2017/day16/main_test.go deleted file mode 100644 index f5c7e33..0000000 --- a/2017/day16/main_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func Test_permPromenade(t *testing.T) { - tests := []struct { - name string - input string - part int - want string - }{ - {"actual", util.ReadFile("input.txt"), 1, "ebjpfdgmihonackl"}, - {"actual", util.ReadFile("input.txt"), 2, "abocefghijklmndp"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := permPromenade(tt.input, tt.part); got != tt.want { - t.Errorf("permPromenade() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day17/main.go b/2017/day17/main.go deleted file mode 100644 index 41c5f98..0000000 --- a/2017/day17/main.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - - "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 := spinlock(util.ReadFile("./input.txt"), part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -type llNode struct { - value int - next *llNode -} - -func spinlock(input string, part int) int { - steps := cast.ToInt(input) - - lastNumToAdd := 2017 - if part == 2 { - lastNumToAdd = 50000000 - } - - current := &llNode{value: 0} - current.next = current - for i := 1; i <= lastNumToAdd; i++ { - for j := 0; j < steps; j++ { - current = current.next - } - - saveNext := current.next - current.next = &llNode{ - value: i, - next: saveNext, - } - current = current.next - - // progress log for part 2 brute force... this is SLOW - if i%1000000 == 0 { - log.Println(i, "steps done") - } - } - - // iterate to the node to find, then return the value of the next node - valueToFind := 2017 - if part == 2 { - valueToFind = 0 - } - for current.value != valueToFind { - current = current.next - } - - return current.next.value -} diff --git a/2017/day17/main_test.go b/2017/day17/main_test.go deleted file mode 100644 index 0aab217..0000000 --- a/2017/day17/main_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "fmt" - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func Test_spinlock(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"example_part1", "3", 1, 638}, - {"actual_part1", util.ReadFile("input.txt"), 1, 1547}, - {"actual_part2", util.ReadFile("input.txt"), 2, 31154878}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Logf("Running %s", tt.name) - if tt.name == "actual_part2" { - // Print using fmt b/c it will be messaged to the terminal regardless - // of verbose flag. To warn (me) that this test takes forever... - fmt.Println("WARNING: REALLY LONG TEST, 50 million steps to run") - if testing.Short() { - t.Skip("Skipping long test in short mode") - } - } - if got := spinlock(tt.input, tt.part); got != tt.want { - t.Errorf("spinlock() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day18/main.go b/2017/day18/main.go deleted file mode 100644 index 1b2e906..0000000 --- a/2017/day18/main.go +++ /dev/null @@ -1,137 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "math" - "strconv" - "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 { - comp := newComputerFromInput(input, 0) - // prime until it gets a valid receive command, then return last outputted num - comp.step(noInput) - return comp.output[len(comp.output)-1] -} - -func part2(input string) int { - program0 := newComputerFromInput(input, 0) - program1 := newComputerFromInput(input, 1) - - // prime the computers - program0.step(noInput) - program1.step(noInput) - - var sentFrom1 int - - for len(program0.output)+len(program1.output) > 0 { - // run outputs from program zero through program 1 - for len(program0.output) > 0 { - v := program0.output[0] - program0.output = program0.output[1:] - program1.step(v) - } - // and vice versa - for len(program1.output) > 0 { - v := program1.output[0] - program1.output = program1.output[1:] - program0.step(v) - sentFrom1++ - } - } - - return sentFrom1 -} - -type computer struct { - instructions [][]string - pointer int - registers map[string]int - output []int -} - -func newComputerFromInput(input string, programID int) *computer { - comp := &computer{registers: map[string]int{"p": programID}} - for _, line := range strings.Split(input, "\n") { - comp.instructions = append(comp.instructions, strings.Split(line, " ")) - } - return comp -} - -var noInput = math.MinInt16 - -func (c *computer) step(inputNum int) { - for { - inst := c.instructions[c.pointer] - valX := inst[1] - var valY int - if len(inst) == 3 && inst[2] != "" { - if val, err := strconv.Atoi(inst[2]); err != nil { - // if there is an error parsing to an integer, value at index 1 is a register - valY = c.registers[inst[2]] - } else { - valY = val - } - } - - switch inst[0] { - case "snd": - c.output = append(c.output, c.registers[valX]) - c.pointer++ - case "set": - c.registers[valX] = valY - c.pointer++ - case "add": - c.registers[valX] += valY - c.pointer++ - case "mul": - c.registers[valX] *= valY - c.pointer++ - case "mod": - c.registers[valX] %= valY - c.pointer++ - case "rcv": - if inputNum == noInput { - return - } - c.registers[valX] = inputNum - inputNum = noInput - c.pointer++ - case "jgz": - var parsedX int - if num, err := strconv.Atoi(valX); err != nil { - // err converting, not a number - parsedX = c.registers[valX] - } else { - // no error then a number was parsed - parsedX = num - } - if parsedX > 0 { - c.pointer += valY + len(c.instructions) - c.pointer %= len(c.instructions) - } else { - c.pointer++ - } - default: - panic("unhandled operator " + inst[0]) - } - } -} diff --git a/2017/day18/main_test.go b/2017/day18/main_test.go deleted file mode 100644 index 3759f27..0000000 --- a/2017/day18/main_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `set a 1 -add a 2 -mul a a -mod a 5 -snd a -set a 0 -rcv a -jgz a -1 -set a 1 -jgz a -2` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example, 4}, - {"actual", util.ReadFile("input.txt"), 3188}, - } - 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) - } - }) - } -} - -// really frail example... -var example2 = `snd 1 -snd 2 -snd p -rcv a -rcv b -rcv c -rcv d` - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example2, 3}, - {"actual", util.ReadFile("input.txt"), 7112}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day19/main.go b/2017/day19/main.go deleted file mode 100644 index b3e2745..0000000 --- a/2017/day19/main.go +++ /dev/null @@ -1,108 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "math" - "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) - - visitedChars, steps := movePacket(util.ReadFile("./input.txt")) - if part == 1 { - fmt.Println("Output:", visitedChars) - } else { - fmt.Println("Output:", steps) - } -} - -var dirs = [4][2]int{ - {1, 0}, // down - {0, -1}, // left - {-1, 0}, // up - {0, 1}, // right -} - -var pathRegexp = regexp.MustCompile("^[-|+A-Z]$") -var capsRegexp = regexp.MustCompile("^[A-Z]$") - -func movePacket(input string) (visitedChars string, steps int) { - grid := parseInput(input) - // finding starting point in first row - var row, col int - for c := 0; c < len(grid[0]); c++ { - if grid[0][c] == "|" { - col = c - break - } - } - - // track which index in dirs is the current directions, start facing down - var dirIndex int - - // include starting tile... - steps = 1 - - // basically an infinite loop... - for i := 0; i < math.MaxInt64; i++ { - inFrontVal := getNextValue(grid, row, col, dirs[dirIndex]) - - if pathRegexp.MatchString(inFrontVal) { - row += dirs[dirIndex][0] - col += dirs[dirIndex][1] - steps++ - - // also check if it's a letter - if capsRegexp.MatchString(inFrontVal) { - visitedChars += inFrontVal - } - } else if inFrontVal == " " { - // just try to turn right then left, assuming no 3 way intersections... - dirIndex = (dirIndex + 1) % 4 - if pathRegexp.MatchString(getNextValue(grid, row, col, dirs[dirIndex])) { - continue - } - - // if right isn't a path, then try to turn left from original direction - // i.e. add 2 more & mod - dirIndex = (dirIndex + 2) % 4 - if pathRegexp.MatchString(getNextValue(grid, row, col, dirs[dirIndex])) { - continue - } - - // can't turn, break out of loop - break - } else { - panic("unhandled char " + inFrontVal) - } - } - - return visitedChars, steps -} - -func getNextValue(grid [][]string, row, col int, diff [2]int) string { - inFrontRow := row + diff[0] - inFrontCol := col + diff[1] - // if not in bounds, just return a space - if inFrontRow < 0 || inFrontRow >= len(grid) || inFrontCol < 0 || inFrontCol >= len(grid[0]) { - return " " - } - return grid[inFrontRow][inFrontCol] -} - -func parseInput(input string) [][]string { - var grid [][]string - - for _, line := range strings.Split(input, "\n") { - grid = append(grid, strings.Split(line, "")) - } - return grid -} diff --git a/2017/day19/main_test.go b/2017/day19/main_test.go deleted file mode 100644 index 74cb554..0000000 --- a/2017/day19/main_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "strings" - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -// easier to look at this input in the format, but need to remove leading/trailing newlines -var example = strings.Trim(` - | - | +--+ - A | C -F---|----E|--+ - | | | D - +B-+ +--+ -`, "\n") - -func Test_movePacket(t *testing.T) { - tests := []struct { - name string - input string - wantVisitedChars string - wantSteps int - }{ - {"example", example, "ABCDEF", 38}, - {"actual", util.ReadFile("input.txt"), "EPYDUXANIT", 17544}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotVisitedChars, gotSteps := movePacket(tt.input) - if gotVisitedChars != tt.wantVisitedChars { - t.Errorf("movePacket() gotVisitedChars = %v, want %v", gotVisitedChars, tt.wantVisitedChars) - } - if gotSteps != tt.wantSteps { - t.Errorf("movePacket() gotSteps = %v, want %v", gotSteps, tt.wantSteps) - } - }) - } -} diff --git a/2017/day20/main.go b/2017/day20/main.go deleted file mode 100644 index dfc3f0e..0000000 --- a/2017/day20/main.go +++ /dev/null @@ -1,125 +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) - - 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 { - particles := parseInput(input) - - sort.Slice(particles, func(i, j int) bool { - pI, pJ := particles[i], particles[j] - // prioritize acceleration, smaller accelerations will stay closest to origin - if pI.acceleration != pJ.acceleration { - return sumAbs(pI.acceleration) < sumAbs(pJ.acceleration) - } - - // then prioritize velocity - if pI.velocity != pJ.velocity { - return sumAbs(pI.velocity) < sumAbs(pJ.velocity) - } - - // hopefully none of these are equal... - if pI.positions == pJ.positions { - panic("equal positions") - } - return sumAbs(pI.positions) < sumAbs(pJ.positions) - }) - - return particles[0].index -} - -func sumAbs(nums [3]int) int { - return mathy.AbsInt(nums[0]) + mathy.AbsInt(nums[1]) + mathy.AbsInt(nums[2]) -} - -func part2(input string) int { - particles := parseInput(input) - - // this is a very literal solution and just run a large-ish number of times... - // there is a mathematical way to determine position coordinates for a given - // time, and then iterate from t = 0 -> large number... and remove like that - // but I'm not sure if it's actually any more efficient - for i := 0; i < math.MaxInt8; i++ { - particles = tick(particles) - particles = removeCollisions(particles) - } - return len(particles) -} - -type particle struct { - index int - positions [3]int - velocity [3]int - acceleration [3]int -} - -func tick(particles []particle) []particle { - var nextState []particle - for _, p := range particles { - for i, acc := range p.acceleration { - p.velocity[i] += acc - } - for i, vel := range p.velocity { - p.positions[i] += vel - } - nextState = append(nextState, p) - } - return nextState -} - -func removeCollisions(particles []particle) []particle { - set := map[[3]int]int{} - for _, p := range particles { - set[p.positions]++ - } - - var nextState []particle - for _, p := range particles { - if count, ok := set[p.positions]; ok && count == 1 { - nextState = append(nextState, p) - } - } - return nextState -} - -func parseInput(input string) (particles []particle) { - for i, line := range strings.Split(input, "\n") { - p := particle{index: i} - fmt.Sscanf(line, "p=<%d,%d,%d>, v=<%d,%d,%d>, a=<%d,%d,%d>", - &p.positions[0], - &p.positions[1], - &p.positions[2], - &p.velocity[0], - &p.velocity[1], - &p.velocity[2], - &p.acceleration[0], - &p.acceleration[1], - &p.acceleration[2], - ) - particles = append(particles, p) - } - return particles -} diff --git a/2017/day20/main_test.go b/2017/day20/main_test.go deleted file mode 100644 index 8ad8d68..0000000 --- a/2017/day20/main_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `p=< 3,0,0>, v=< 2,0,0>, a=<-1,0,0> -p=< 4,0,0>, v=< 0,0,0>, a=<-2,0,0>` - -var example2 = `p=<-6,0,0>, v=< 3,0,0>, a=< 0,0,0> -p=<-4,0,0>, v=< 2,0,0>, a=< 0,0,0> -p=<-2,0,0>, v=< 1,0,0>, a=< 0,0,0> -p=< 3,0,0>, v=<-1,0,0>, a=< 0,0,0>` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example, 0}, - {"actual", util.ReadFile("input.txt"), 170}, - } - 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 - }{ - {"example", example2, 1}, - {"actual", util.ReadFile("input.txt"), 571}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day21/main.go b/2017/day21/main.go deleted file mode 100644 index 92a3108..0000000 --- a/2017/day21/main.go +++ /dev/null @@ -1,137 +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 int - if part == 1 { - ans = fractalArt(util.ReadFile("./input.txt"), 5) - } else { - ans = fractalArt(util.ReadFile("./input.txt"), 18) - } - fmt.Println("Output:", ans) -} - -var startingPattern = `.#. -..# -###` - -func fractalArt(input string, rounds int) int { - var state [][]string - for _, line := range strings.Split(startingPattern, "\n") { - state = append(state, strings.Split(line, "")) - } - - rules := parseInput(input) - - for i := 0; i < rounds; i++ { - state = tick(state, rules) - } - - var count int - for _, row := range state { - for _, v := range row { - if v == "#" { - count++ - } - } - } - return count -} - -func parseInput(input string) map[string][][]string { - // some helper functions for generating the rules map - // need to parse the left sides of the enhancement rules - // then helper functions that rotate them (util.RotateStringGrid()) and - // one to mirror image it - makeGridFromString := func(str string) [][]string { - var grid [][]string - for _, line := range strings.Split(str, "/") { - grid = append(grid, strings.Split(line, "")) - } - return grid - } - stringifyGrid := func(grid [][]string) (str string) { - for _, row := range grid { - for _, v := range row { - str += v - } - } - return str - } - - rules := map[string][][]string{} - for _, line := range strings.Split(input, "\n") { - parts := strings.Split(line, " => ") - keyGrid := makeGridFromString(parts[0]) - resultGrid := makeGridFromString(parts[1]) - - for i := 0; i < 4; i++ { - keyGrid = algos.RotateStringGrid(keyGrid) - rules[stringifyGrid(keyGrid)] = resultGrid - rules[stringifyGrid((algos.MirrorStringGrid(keyGrid)))] = resultGrid - } - } - return rules -} - -func tick(grid [][]string, rules map[string][][]string) [][]string { - var nextState [][]string - - // determine the size of break up the grid by. prioritize 2x2 grids - var edgeSize int - if len(grid)%2 == 0 { - edgeSize = 2 - } else if len(grid)%3 == 0 { - edgeSize = 3 - } else { - panic("grid is not evenly divisible by 2 or 3, got " + cast.ToString(len(grid))) - } - - // iterate over like a sudoku grid, r and c iterate over the top left corner - // of each sub-square - for r := 0; r < len(grid); r += edgeSize { - // a new row of sub-squares is being iterated over, add edgeSize+1 number - // of empty slices onto the nextState grid - for i := 0; i < edgeSize+1; i++ { - nextState = append(nextState, []string{}) - } - for c := 0; c < len(grid[0]); c += edgeSize { - // generate the string to match a key in the rules map - var strToMatch string - for i := 0; i < edgeSize; i++ { - for j := 0; j < edgeSize; j++ { - // r+i and c+j point at coords within the original grid - strToMatch += grid[r+i][c+j] - } - } - - // finding the result of the enhancement rule for the string to match - resulting, ok := rules[strToMatch] - if !ok { - panic("No matching pattern found for " + strToMatch) - } - - // append the values from the result onto the appropriate nextState row - for i, vals := range resulting { - nextStateIndex := len(nextState) - edgeSize - 1 + i - nextState[nextStateIndex] = append(nextState[nextStateIndex], vals...) - } - } - } - - return nextState -} diff --git a/2017/day21/main_test.go b/2017/day21/main_test.go deleted file mode 100644 index b567127..0000000 --- a/2017/day21/main_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `../.# => ##./#../... -.#./..#/### => #..#/..../..../#..#` - -func Test_fractalArt(t *testing.T) { - type args struct { - input string - rounds int - } - tests := []struct { - name string - args args - want int - }{ - {"example", args{example, 2}, 12}, - {"actual_part1", args{util.ReadFile("input.txt"), 5}, 194}, - {"actual_part2", args{util.ReadFile("input.txt"), 18}, 2536879}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := fractalArt(tt.args.input, tt.args.rounds); got != tt.want { - t.Logf("Ruleset: %s", tt.args.input) - t.Logf("Rounds: %d", tt.args.rounds) - t.Errorf("fractalArt() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day22/main.go b/2017/day22/main.go deleted file mode 100644 index 19dfca0..0000000 --- a/2017/day22/main.go +++ /dev/null @@ -1,115 +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 { - infectedMap := newStateFromInput(input) - mid := len(strings.Split(input, "\n")) / 2 - current := [2]int{mid, mid} // assume square inputs - - // direction starts at 0, i.e. facing up - var dirIndex, countBursts int - - for i := 0; i < 10000; i++ { - switch infectedMap[current] { - case infected: - // is infected, turn right - dirIndex = (dirIndex + 1) % 4 - infectedMap[current] = clean - case clean: - // not infected, turn left - dirIndex = (dirIndex + 3) % 4 - infectedMap[current] = infected - - countBursts++ - } - // move forward - current = [2]int{current[0] + dirs[dirIndex][0], current[1] + dirs[dirIndex][1]} - } - - return countBursts -} - -func part2(input string) int { - state := newStateFromInput(input) - mid := len(strings.Split(input, "\n")) / 2 - current := [2]int{mid, mid} - - var dirIndex, countBursts int - - for i := 0; i < 10000000; i++ { - switch state[current] { - case clean: - dirIndex = (dirIndex + 3) % 4 - state[current] = weakened - case weakened: - // keep going in same direction - state[current] = infected - countBursts++ - case infected: - dirIndex = (dirIndex + 1) % 4 - state[current] = flagged - case flagged: - dirIndex = (dirIndex + 2) % 4 - state[current] = clean - default: - panic(fmt.Sprintf("unhandled infection type %v", state[current])) - } - // move forward - current = [2]int{current[0] + dirs[dirIndex][0], current[1] + dirs[dirIndex][1]} - } - - return countBursts -} - -var dirs = [][2]int{ - {-1, 0}, // up - {0, 1}, // right - {1, 0}, // down - {0, -1}, // left -} - -type infectedState int - -const ( - clean infectedState = iota - weakened - infected - flagged -) - -// this probably makes some cool visuals if a 2D slice is used... but this is easier -func newStateFromInput(input string) map[[2]int]infectedState { - ans := map[[2]int]infectedState{} - for r, line := range strings.Split(input, "\n") { - for c, v := range strings.Split(line, "") { - if v == "#" { - ans[[2]int{r, c}] = infected - } else if v == "." { - ans[[2]int{r, c}] = clean - } - } - } - return ans -} diff --git a/2017/day22/main_test.go b/2017/day22/main_test.go deleted file mode 100644 index 77c7bbe..0000000 --- a/2017/day22/main_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `..# -#.. -...` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example, 5587}, - {"actual", util.ReadFile("input.txt"), 5575}, - } - 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 - }{ - {"example", example, 2511944}, - {"actual", util.ReadFile("input.txt"), 2511991}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day23/main.go b/2017/day23/main.go deleted file mode 100644 index a9ab286..0000000 --- a/2017/day23/main.go +++ /dev/null @@ -1,122 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "strconv" - "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) -} - -// literal implementation for part 1 -func part1(input string) int { - comp := newComputerFromInput(input) - // prime until it gets a valid receive command, then return last outputted num - return comp.countMulsRun() -} - -// referenced this heavily: https://www.reddit.com/r/adventofcode/comments/7lms6p/2017_day_23_solutions/drnh5sx?utm_source=share&utm_medium=web2x&context=3 -func part2(input string) int { - b := 81 - c := 81 - - b = b*100 + 100000 - c = b + 17000 - var h int - for { - f := 1 - // effectively a prime number checker. - for d := 2; d*d <= b; d++ { - if b%d == 0 { - f = 0 - break - } - } - - if f == 0 { - h++ - } - if b == c { - break - } - b += 17 - } - - return h -} - -type computer struct { - instructions [][]string - pointer int - registers map[string]int - output []int -} - -func newComputerFromInput(input string) *computer { - comp := &computer{registers: map[string]int{}} - for _, line := range strings.Split(input, "\n") { - comp.instructions = append(comp.instructions, strings.Split(line, " ")) - } - - return comp -} - -func (c *computer) countMulsRun() (mulsRun int) { - for c.pointer < len(c.instructions) { - inst := c.instructions[c.pointer] - valX := inst[1] - var valFromY int - if val, err := strconv.Atoi(inst[2]); err != nil { - // if there is an error parsing to an integer, value at index 1 is a register - valFromY = c.registers[inst[2]] - } else { - valFromY = val - } - - switch inst[0] { - case "set": - c.registers[valX] = valFromY - c.pointer++ - case "sub": - c.registers[valX] -= valFromY - c.pointer++ - case "mul": - c.registers[valX] *= valFromY - c.pointer++ - mulsRun++ - case "jnz": - var parsedX int - if num, err := strconv.Atoi(valX); err != nil { - // err converting, not a number - parsedX = c.registers[valX] - } else { - // no error then a number was parsed - parsedX = num - } - if parsedX != 0 { - c.pointer += valFromY - } else { - c.pointer++ - } - default: - panic("unhandled operator " + inst[0]) - } - } - return mulsRun -} diff --git a/2017/day23/main_test.go b/2017/day23/main_test.go deleted file mode 100644 index edaa849..0000000 --- a/2017/day23/main_test.go +++ /dev/null @@ -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"), 6241}, - } - 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"), 909}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day24/main.go b/2017/day24/main.go deleted file mode 100644 index 54335ae..0000000 --- a/2017/day24/main.go +++ /dev/null @@ -1,107 +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 := magneticMoat(util.ReadFile("./input.txt"), part) - fmt.Println("Output:", ans) -} - -func magneticMoat(input string, part int) int { - edges := getEdges(input) - - // start the bridge with a zero - bridge := [][2]int{ - {0, 0}, - } - - usedEdges := map[[2]int]bool{} - for _, edge := range edges { - usedEdges[edge] = false - } - - bestStrength, longestBridge := backtrackBridge(bridge, usedEdges) - if part == 1 { - return bestStrength - } - return calcStrengthOfBridge(longestBridge) -} - -// backtracking algo that returns strongest bridge -func backtrackBridge(bridge [][2]int, usedEdges map[[2]int]bool) (bestStrength int, longestBridge [][2]int) { - lastVal := bridge[len(bridge)-1][1] - for edge, isUsed := range usedEdges { - // skip edges that are marked as used - if !isUsed { - clonedEdge := edge - // swap the edge vals if the first doesn't match - if clonedEdge[0] != lastVal { - clonedEdge[0], clonedEdge[1] = clonedEdge[1], clonedEdge[0] - } - // if match is found, add it to bridge, mark as used - // add to strength, recurse - if clonedEdge[0] == lastVal { - bridge = append(bridge, clonedEdge) - usedEdges[edge] = true - strength := clonedEdge[0] + clonedEdge[1] - - // recurse and bestStrength and longestLength - subStrength, subLongestBridge := backtrackBridge(bridge, usedEdges) - - strength += subStrength - - // if current bridge is longest (or wins tiebreak) set the longestBridge - if len(bridge) > len(longestBridge) || - (len(bridge) == len(longestBridge) && - calcStrengthOfBridge(bridge) > calcStrengthOfBridge(longestBridge)) { - // use this hacky append to create a copy of the bridge slice - // otherwise appends could modify the underlying array - longestBridge = append([][2]int{}, bridge...) - } - // also check if a recursive call had the longest bridge, update longest - if len(subLongestBridge) > len(longestBridge) || - (len(subLongestBridge) == len(longestBridge) && - calcStrengthOfBridge(subLongestBridge) > calcStrengthOfBridge(longestBridge)) { - longestBridge = append([][2]int{}, subLongestBridge...) - } - - // backtrack - usedEdges[edge] = false - bridge = bridge[:len(bridge)-1] - if strength > bestStrength { - bestStrength = strength - } - } - } - } - - return bestStrength, longestBridge -} - -func calcStrengthOfBridge(bridge [][2]int) int { - var sum int - for _, edge := range bridge { - sum += edge[0] + edge[1] - } - return sum -} - -func getEdges(input string) (edges [][2]int) { - for _, line := range strings.Split(input, "\n") { - var pair [2]int - fmt.Sscanf(line, "%d/%d", &pair[0], &pair[1]) - edges = append(edges, pair) - } - return edges -} diff --git a/2017/day24/main_test.go b/2017/day24/main_test.go deleted file mode 100644 index 0dd5281..0000000 --- a/2017/day24/main_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `0/2 -2/2 -2/3 -3/4 -3/5 -0/1 -10/1 -9/10` - -func Test_magneticMoat(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"example_part1", example, 1, 31}, - {"actual_part1", util.ReadFile("input.txt"), 1, 1868}, - {"example_part2", example, 2, 19}, - {"actual_part2", util.ReadFile("input.txt"), 2, 1841}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := magneticMoat(tt.input, tt.part); got != tt.want { - t.Errorf("magneticMoat() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2017/day25/main.go b/2017/day25/main.go deleted file mode 100644 index 5b7475e..0000000 --- a/2017/day25/main.go +++ /dev/null @@ -1,74 +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() { - ans := part1(util.ReadFile("./input.txt")) - fmt.Println("Output:", ans) -} - -func part1(input string) int { - steps, stateRules := parseInput(input) - - // lazy, use a huge array and just start in the middle - bigArray := make([]int, steps) - index := steps / 2 - currentStateName := "A" - - for i := 0; i < steps; i++ { - currentVal := bigArray[index] - rulesToFollow := stateRules[currentStateName][currentVal] - // write - bigArray[index] = rulesToFollow.valToWrite - if rulesToFollow.direction == "left" { - index-- - } else { - index++ - } - currentStateName = rulesToFollow.nextState - } - - return mathy.SumIntSlice(bigArray) -} - -type ruleset struct { - name string // for debugging only - valToWrite int - direction string - nextState string -} - -// assume all programs start in state A for now, one less thing to parse... -func parseInput(input string) (steps int, states map[string][2]ruleset) { - // a manual parse here would be faster... - blocks := strings.Split(input, "\n\n") - - fmt.Sscanf(strings.Split(blocks[0], "\n")[1], "Perform a diagnostic checksum after %d steps.", &steps) - - states = map[string][2]ruleset{} - for _, block := range blocks[1:] { - lines := strings.Split(block, "\n") - var stateName string - fmt.Sscanf(lines[0], "In state %1s:", &stateName) - - rulesIfZero := ruleset{name: stateName} - fmt.Sscanf(strings.Trim(lines[2], " -."), "Write the value %d", &rulesIfZero.valToWrite) - fmt.Sscanf(strings.Trim(lines[3], " -."), "Move one slot to the %s", &rulesIfZero.direction) - fmt.Sscanf(strings.Trim(lines[4], " -."), "Continue with state %1s", &rulesIfZero.nextState) - - rulesIfOne := ruleset{name: stateName} - fmt.Sscanf(strings.Trim(lines[6], " -."), "Write the value %d", &rulesIfOne.valToWrite) - fmt.Sscanf(strings.Trim(lines[7], " -."), "Move one slot to the %s", &rulesIfOne.direction) - fmt.Sscanf(strings.Trim(lines[8], " -."), "Continue with state %1s", &rulesIfOne.nextState) - - states[stateName] = [2]ruleset{rulesIfZero, rulesIfOne} - } - - return steps, states -} diff --git a/2017/day25/main_test.go b/2017/day25/main_test.go deleted file mode 100644 index 0e53bb9..0000000 --- a/2017/day25/main_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `Begin in state A. -Perform a diagnostic checksum after 6 steps. - -In state A: - If the current value is 0: - - Write the value 1. - - Move one slot to the right. - - Continue with state B. - If the current value is 1: - - Write the value 0. - - Move one slot to the left. - - Continue with state B. - -In state B: - If the current value is 0: - - Write the value 1. - - Move one slot to the left. - - Continue with state A. - If the current value is 1: - - Write the value 1. - - Move one slot to the right. - - Continue with state A.` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example, 3}, - {"actual", util.ReadFile("input.txt"), 5744}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day01/main.go b/2018/day01/main.go deleted file mode 100644 index ed97396..0000000 --- a/2018/day01/main.go +++ /dev/null @@ -1,46 +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 := part1(util.ReadFile("./input.txt")) - fmt.Println("Output:", ans) -} - -func part1(input string) int { - var sum int - for _, line := range strings.Split(input, "\n") { - sum += cast.ToInt(line) - - } - - return sum -} -func part2(input string) int { - var sum int - seen := map[int]bool{} - for { - for _, line := range strings.Split(input, "\n") { - sum += cast.ToInt(line) - - if seen[sum] { - return sum - } - seen[sum] = true - } - } - - panic("expect return from loop") -} diff --git a/2018/day01/main_test.go b/2018/day01/main_test.go deleted file mode 100644 index 432971b..0000000 --- a/2018/day01/main_test.go +++ /dev/null @@ -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"), 497}, - } - 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"), 558}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day02/main.go b/2018/day02/main.go deleted file mode 100644 index 2771c8b..0000000 --- a/2018/day02/main.go +++ /dev/null @@ -1,80 +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) - - 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 { - var twos, threes int - for _, boxID := range strings.Split(input, "\n") { - charCounts := getCharCount(boxID) - for _, v := range charCounts { - if v == 2 { - twos++ - break - } - } - for _, v := range charCounts { - if v == 3 { - threes++ - } - } - } - - return twos * threes -} - -func getCharCount(box string) map[rune]int { - chars := make(map[rune]int) - for _, c := range box { - chars[c]++ - } - return chars -} - -func part2(input string) string { - lines := strings.Split(input, "\n") - for i := 0; i < len(lines); i++ { - for j := i + 1; j < len(lines); j++ { - if sameChars := getSameCharacters(lines[i], lines[j]); sameChars != "" { - return sameChars - } - } - } - return "" -} -func getSameCharacters(str1, str2 string) string { - var mismatchSeen bool - var sameChars string - for i := 0; i < len(str1); i++ { - if str1[i] == str2[i] { - sameChars += string(str1[i]) - } else if mismatchSeen { - // if a mismatch has already been seen, then it's 2 characters off - // return an empty string - return "" - } else { - mismatchSeen = true - } - } - return sameChars -} diff --git a/2018/day02/main_test.go b/2018/day02/main_test.go deleted file mode 100644 index 33873dd..0000000 --- a/2018/day02/main_test.go +++ /dev/null @@ -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"), 5434}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want string - }{ - {"actual", util.ReadFile("input.txt"), "agimdjvlhedpsyoqfzuknpjwt"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day03/main.go b/2018/day03/main.go deleted file mode 100644 index d8e5438..0000000 --- a/2018/day03/main.go +++ /dev/null @@ -1,108 +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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - lines := strings.Split(input, "\n") - - seen := make(map[string]bool) - counted := make(map[string]bool) - var overlap int - - for _, line := range lines { - row := cast.ToInt(line[strings.Index(line, "@")+2 : strings.Index(line, ",")]) - col := cast.ToInt(line[strings.Index(line, ",")+1 : strings.Index(line, ":")]) - width := cast.ToInt(line[strings.Index(line, ":")+2 : strings.Index(line, "x")]) - height := cast.ToInt(line[strings.Index(line, "x")+1:]) - - for i := 0; i < width; i++ { - for j := 0; j < height; j++ { - coords := fmt.Sprintf("%vx%v", row+i, col+j) - if seen[coords] && !counted[coords] { - overlap++ - counted[coords] = true - } - seen[coords] = true - } - } - } - - return overlap -} - -func part2(input string) int { - lines := strings.Split(input, "\n") - - coords := makeMapOfCoordinates(lines) - - for _, line := range lines { - uniqueID := hasNoOverlap(coords, line) - if uniqueID != -1 { - return uniqueID - } - } - panic("expect return from loop") -} - -func makeMapOfCoordinates(lines []string) map[string]int { - seen := make(map[string]int) - for _, line := range lines { - // ID := line[:strings.Index(line, " @")] - row := cast.ToInt(line[strings.Index(line, "@")+2 : strings.Index(line, ",")]) - col := cast.ToInt(line[strings.Index(line, ",")+1 : strings.Index(line, ":")]) - width := cast.ToInt(line[strings.Index(line, ":")+2 : strings.Index(line, "x")]) - height := cast.ToInt(line[strings.Index(line, "x")+1:]) - - for i := 0; i < width; i++ { - for j := 0; j < height; j++ { - coords := fmt.Sprintf("%vx%v", row+i, col+j) - seen[coords]++ - } - } - } - return seen -} - -// if cut is unique returns the ID, otherwise -1 -func hasNoOverlap(seen map[string]int, line string) int { - ID := cast.ToInt(line[1:strings.Index(line, " @")]) - row := cast.ToInt(line[strings.Index(line, "@")+2 : strings.Index(line, ",")]) - col := cast.ToInt(line[strings.Index(line, ",")+1 : strings.Index(line, ":")]) - width := cast.ToInt(line[strings.Index(line, ":")+2 : strings.Index(line, "x")]) - height := cast.ToInt(line[strings.Index(line, "x")+1:]) - - for i := 0; i < width; i++ { - for j := 0; j < height; j++ { - coords := fmt.Sprintf("%vx%v", row+i, col+j) - if seen[coords] != 1 { - return -1 - } - } - } - return ID -} diff --git a/2018/day03/main_test.go b/2018/day03/main_test.go deleted file mode 100644 index e4ae32d..0000000 --- a/2018/day03/main_test.go +++ /dev/null @@ -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"), 118223}, - } - 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"), 412}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day04/main.go b/2018/day04/main.go deleted file mode 100644 index f4e92cc..0000000 --- a/2018/day04/main.go +++ /dev/null @@ -1,144 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "sort" - "strconv" - "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) int { - lines := strings.Split(input, "\n") - - // sort inputs by time stamp, string sorting is sufficient - sort.Strings(lines) - - timeEntries := make([]entry, len(lines)) - for i, line := range lines { - timeEntries[i] = makeEntry(line) - } - - // find which guard has slept the most total time - // then find which minute he is asleep at most frequently - mapIDGuard := make(map[int]*guard) - // for part 2, track each guard for the actual minute asleep - mapIDToMinutesArray := make(map[int]*[60]int) - lastGuardID := timeEntries[0].ID - for i, timeEntry := range timeEntries { - if timeEntry.ID != 0 { - lastGuardID = timeEntry.ID - } else { - // if the time entry is awake, then check the next one entry - // if the next entry is the same day, then assume its the same guard - // update that guard's stats - if !timeEntry.awake && i+1 != len(timeEntries) && - timeEntries[i+1].day == timeEntry.day { - endTime := timeEntries[i+1].minute - startTime := timeEntry.minute - if mapIDGuard[lastGuardID] == nil { - mapIDGuard[lastGuardID] = &guard{} - } - // part 2 parsing - if mapIDToMinutesArray[lastGuardID] == nil { - mapIDToMinutesArray[lastGuardID] = &[60]int{} - } - - mapIDGuard[lastGuardID].totalTimeAsleep += endTime - startTime - for startTime < endTime { - mapIDGuard[lastGuardID].minutesAsleep[startTime]++ - mapIDToMinutesArray[lastGuardID][startTime]++ - startTime++ - } - } - } - } - - if part == 1 { - // who sleeps the most - var IDOfSleepiestGuard, bestMinute, highestFreq int - for i, g := range mapIDGuard { - if IDOfSleepiestGuard == 0 { - IDOfSleepiestGuard = i - } - if g.totalTimeAsleep > mapIDGuard[IDOfSleepiestGuard].totalTimeAsleep { - IDOfSleepiestGuard = i - } - } - - // find the minute they are the asleep the most - for min, freq := range mapIDGuard[IDOfSleepiestGuard].minutesAsleep { - if freq > highestFreq { - bestMinute = min - highestFreq = freq - } - } - - // print ID * time (minute) - return IDOfSleepiestGuard * bestMinute - } - - // part 2 stuff - var highestFreq, ID, bestMinute int - // find the minute they are the asleep the most - for i, arr := range mapIDToMinutesArray { - for min, freq := range arr { - if freq > highestFreq { - bestMinute = min - highestFreq = freq - ID = i - } - } - } - - // print ID * time (minute) - return ID * bestMinute -} - -type entry struct { - ID, year, month, day, minute int - awake bool -} - -func makeEntry(line string) entry { - var e entry - e.year, _ = strconv.Atoi(line[1:5]) - e.month, _ = strconv.Atoi(line[6:8]) - e.day, _ = strconv.Atoi(line[9:11]) - - hour, _ := strconv.Atoi(line[12:14]) - // if started before midnight, zero out minute value - if hour != 0 { - e.minute = 0 - } else { - e.minute, _ = strconv.Atoi(line[15:17]) - } - - // if the instruction is that the guard has fallen asleep, leave awake=false - if strings.Contains(line, "falls asleep") { - return e - } - - e.awake = true - if strings.Contains(line, "Guard") { - e.ID, _ = strconv.Atoi(line[strings.Index(line, "#")+1 : strings.Index(line, " begins")]) - } - return e -} - -type guard struct { - totalTimeAsleep int - minutesAsleep [60]int -} diff --git a/2018/day04/main_test.go b/2018/day04/main_test.go deleted file mode 100644 index f905a93..0000000 --- a/2018/day04/main_test.go +++ /dev/null @@ -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 int - }{ - {"actual", util.ReadFile("input.txt"), 1, 38813}, - {"actual", util.ReadFile("input.txt"), 2, 141071}, - } - 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) - } - }) - } -} diff --git a/2018/day05/main.go b/2018/day05/main.go deleted file mode 100644 index d630ab9..0000000 --- a/2018/day05/main.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "errors" - "flag" - "fmt" - "math" - - "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 { - var err error - for err != ErrDone { - input, err = step(input) - } - return len(input) -} - -func part2(input string) int { - // find all units (lowercased) that can be removed - units := map[byte]bool{} - for i := 0; i < len(input); i++ { - if input[i] < byte(96) { - units[input[i]] = true - } else { - units[input[i]-ASCIIOffset] = true - } - } - - lowestResult := math.MaxInt16 - for u := range units { - // remove all unit & its corresponding capital/lowercase version - newStr := removeUnit(input, u) - - // run new string through part1 and update lowestResult if possible - if result := part1(newStr); result < lowestResult { - lowestResult = result - } - } - - return lowestResult -} - -const ASCIIOffset = byte('a' - 'A') - -var ErrDone = errors.New("no-op") - -func step(units string) (string, error) { - offset := byte('a' - 'A') - - for i := 1; i < len(units); i++ { - if units[i-1]-units[i] == offset || units[i-1]-units[i] == -offset { - // remove units i-1 and i - newStr := units[:i-1] + units[i+1:] - return newStr, nil - } - } - return units, ErrDone -} - -func removeUnit(input string, unit byte) string { - var newStr string - for i := 0; i < len(input); i++ { - if input[i] == unit || input[i] == unit+ASCIIOffset { - continue - } - newStr += string(input[i]) - } - return newStr -} diff --git a/2018/day05/main_test.go b/2018/day05/main_test.go deleted file mode 100644 index 35fddf9..0000000 --- a/2018/day05/main_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"ex1", 10, "dabAcCaCBAcCcaDA"}, - {"actual", 10180, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("want %v, got %v", test.want, got) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string -}{ - {"ex1", 4, "dabAcCaCBAcCcaDA"}, - {"actual", 5668, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input) - if got != test.want { - t.Errorf("want %v, got %v", test.want, got) - } - }) - } -} diff --git a/2018/day06/main.go b/2018/day06/main.go deleted file mode 100644 index 42bb821..0000000 --- a/2018/day06/main.go +++ /dev/null @@ -1,134 +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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt"), 10000) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - coords := parseInputCoords(input) - - boundLeft, boundRight, boundTop, boundBottom := getBounds(coords) - - // maps coordinates to the number (count) of cells that they are closest too - coordsToMinDistanceCounts := map[[2]int]int{} - - for r := boundTop; r <= boundBottom; r++ { - for c := boundLeft; c <= boundRight; c++ { - bestManhattan := math.MaxInt16 - var coordsToBest [2]int - distCounts := map[int]int{} // dedeupe equidistant cells - for _, coord := range coords { - man := mathy.ManhattanDistance(r, c, coord[0], coord[1]) - if man <= bestManhattan { - bestManhattan = man - coordsToBest = coord - distCounts[bestManhattan]++ - } - } - - // do not increment anything if there were two equidistant coords - if distCounts[bestManhattan] == 1 { - coordsToMinDistanceCounts[coordsToBest]++ - } - } - } - - var largest int - for coord, val := range coordsToMinDistanceCounts { - // exclude edges - if coord[0] == boundTop || coord[0] == boundBottom { - continue - } - if coord[1] == boundLeft || coord[1] == boundRight { - continue - } - - if val > largest { - largest = val - } - } - return largest -} - -func part2(input string, dist int) int { - coords := parseInputCoords(input) - - boundLeft, boundRight, boundTop, boundBottom := getBounds(coords) - - coordsToTotalDist := map[[2]int]int{} - var area int - for r := boundTop; r <= boundBottom; r++ { - for c := boundLeft; c <= boundRight; c++ { - point := [2]int{r, c} - for _, coord := range coords { - coordsToTotalDist[point] += mathy.ManhattanDistance(point[0], point[1], coord[0], coord[1]) - } - if coordsToTotalDist[point] < dist { - area++ - } - } - } - - return area -} - -func parseInputCoords(input string) [][2]int { - lines := strings.Split(input, "\n") - coords := [][2]int{} // [row, col] - for _, l := range lines { - c := strings.Split(l, ", ") - if len(c) == 2 { - coords = append(coords, [2]int{ - cast.ToInt(c[0]), - cast.ToInt(c[1]), - }) - } - } - return coords -} - -func getBounds(coords [][2]int) (left int, right int, top int, bottom int) { - var ( - boundLeft = math.MaxInt16 - boundRight = -math.MaxInt16 - boundTop = math.MaxInt16 - boundBottom = -math.MaxInt16 - ) - for _, c := range coords { - if c[0] < boundTop { - boundTop = c[0] - } - if c[0] > boundBottom { - boundBottom = c[0] - } - if c[1] < boundLeft { - boundLeft = c[1] - } - if c[1] > boundRight { - boundRight = c[1] - } - } - return boundLeft, boundRight, boundTop, boundBottom -} diff --git a/2018/day06/main_test.go b/2018/day06/main_test.go deleted file mode 100644 index 9b7269d..0000000 --- a/2018/day06/main_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var arg1 = `1, 1 -1, 6 -8, 3 -3, 4 -5, 5 -8, 9 -` - -var tests1 = []struct { - name string - want int - input string -}{ - {"example1", 17, arg1}, - {"actual", 5333, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("want %v, got %v", test.want, got) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string - distArg int -}{ - {"example1", 16, arg1, 32}, - {"actual", 35334, util.ReadFile("input.txt"), 10000}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input, test.distArg) - if got != test.want { - t.Errorf("want %v, got %v", test.want, got) - } - }) - } -} diff --git a/2018/day07/main.go b/2018/day07/main.go deleted file mode 100644 index 83adbb2..0000000 --- a/2018/day07/main.go +++ /dev/null @@ -1,163 +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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt"), 5, 60) - fmt.Println("Output:", ans) - } -} - -func part1(input string) string { - graph := parseInputs(input) - - completedSteps := make(map[string]bool) - var stepsOrder string - - for len(completedSteps) != len(graph) { - var readySteps []string - - for name, prereqs := range graph { - if completedSteps[name] { - continue - } - - prereqsReady := true - for _, v := range prereqs { - if !completedSteps[v] { - prereqsReady = false - break - } - } - if prereqsReady { - readySteps = append(readySteps, name) - } - } - - // run the lowest ready step - sort.Strings(readySteps) - completedSteps[readySteps[0]] = true - stepsOrder += readySteps[0] - } - - return stepsOrder -} - -func part2(input string, workers, fudgeTime int) int { - graph := parseInputs(input) - - stepsCompletedAt := make(map[string]int, len(graph)) - - workerLogs := make([][]string, workers) - for i := 0; i < workers; i++ { - workerLogs[i] = []string{} - } - - for time := 0; len(stepsCompletedAt) < len(graph); time++ { - availableWorkers := []int{} - for i, w := range workerLogs { - if time >= len(w) { - availableWorkers = append(availableWorkers, i) - } - } - - if len(availableWorkers) > 0 { - // get ready steps - var readySteps []string - - for name, prereqs := range graph { - if stepsCompletedAt[name] != 0 { - continue - } - - prereqsCompletionTimes := make([]int, len(prereqs)) - // might want more data here - for _, pre := range prereqs { - if stepsCompletedAt[pre] == 0 { - prereqsCompletionTimes = append(prereqsCompletionTimes, math.MaxInt32) - } else { - prereqsCompletionTimes = append(prereqsCompletionTimes, stepsCompletedAt[pre]+1) - } - } - - if len(prereqsCompletionTimes) == 0 { - readySteps = append(readySteps, name) - } else { - earliestScheduleTime := mathy.MaxInt(prereqsCompletionTimes...) - if earliestScheduleTime <= time { - readySteps = append(readySteps, name) - } - } - } - - // schedule steps that are ready - sort.Strings(readySteps) - - for _, step := range readySteps { - if len(availableWorkers) > 0 { - for i := timeForStep(step, fudgeTime); i > 0; i-- { - workerLogs[availableWorkers[0]] = append(workerLogs[availableWorkers[0]], step) - } - // set time this step is done at - stepsCompletedAt[step] = len(workerLogs[availableWorkers[0]]) - // remove worker from available "pool" - availableWorkers = availableWorkers[1:] - } - } - } - - // fill out empty time elements in any workerLogs - for i := range workerLogs { - for len(workerLogs[i]) < time { - workerLogs[i] = append(workerLogs[i], "-") - } - } - } - - var longestLength int - for _, w := range workerLogs { - if len(w) > longestLength { - longestLength = len(w) - } - } - return longestLength -} - -func parseInputs(input string) map[string][]string { - graphValToPrereqs := make(map[string][]string) - lines := strings.Split(input, "\n") - for _, inst := range lines { - words := strings.Split(inst, " ") - if len(words) < 8 { - continue - } - graphValToPrereqs[words[7]] = append(graphValToPrereqs[words[7]], words[1]) - if len(graphValToPrereqs[words[1]]) == 0 { - graphValToPrereqs[words[1]] = []string{} - } - } - return graphValToPrereqs -} - -func timeForStep(char string, fudgeFactor int) int { - byteDiff := []byte(char)[0] - byte('A') - return fudgeFactor + int(byteDiff) + 1 -} diff --git a/2018/day07/main_test.go b/2018/day07/main_test.go deleted file mode 100644 index 4b76ca6..0000000 --- a/2018/day07/main_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var exampleInput = `Step C must be finished before step A can begin. -Step C must be finished before step F can begin. -Step A must be finished before step B can begin. -Step A must be finished before step D can begin. -Step B must be finished before step E can begin. -Step D must be finished before step E can begin. -Step F must be finished before step E can begin. -` - -var tests1 = []struct { - name string - want string - input string -}{ - {"example", "CABDFE", exampleInput}, - {"actual", "JMQZELVYXTIGPHFNSOADKWBRUC", util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("want %v, got %v", test.want, got) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string - workers int - fudgeTime int -}{ - {"example", 15, exampleInput, 2, 0}, - {"actual", 1133, util.ReadFile("input.txt"), 5, 60}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input, test.workers, test.fudgeTime) - if got != test.want { - t.Errorf("got %v; want %v", got, test.want) - } - }) - } -} - -func TestTimeForStep(t *testing.T) { - for _, test := range []struct { - input string - want int - fudgeFactor int - }{ - {"A", 1, 0}, - {"F", 6, 0}, - {"J", 10, 0}, - {"Z", 26, 0}, - {"A", 61, 60}, - {"F", 66, 60}, - {"J", 70, 60}, - {"Z", 86, 60}, - } { - if got := timeForStep(test.input, test.fudgeFactor); got != test.want { - t.Errorf("timeForStep(%s, %d) = %d, want %d", test.input, test.fudgeFactor, got, test.want) - } - } -} diff --git a/2018/day08/main.go b/2018/day08/main.go deleted file mode 100644 index 2812ebe..0000000 --- a/2018/day08/main.go +++ /dev/null @@ -1,132 +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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - fmt.Println("Output:", ans) - } -} - -type TreeNode struct { - children []*TreeNode - metadata []int -} - -func part1(input string) int { - nums := parseInput(input) - - root, _ := makeTree(nums) - - return sumMetadataOnly(root) -} - -func part2(input string) int { - nums := parseInput(input) - - root, _ := makeTree(nums) - - return sumMetadataViaChildrenIndices(root) -} - -func parseInput(input string) []int { - split := strings.Split(input, " ") - - parsed := make([]int, len(split)) - - for i, v := range split { - parsed[i] = cast.ToInt(v) - } - - return parsed -} - -func makeTree(nums []int) (node *TreeNode, recursiveValuesHandled int) { - if len(nums) == 0 { - return nil, 0 - } - if len(nums) == 2 { - return &TreeNode{nil, []int{}}, 2 - } - - childrenCount := nums[0] - metadataCount := nums[1] - - newNode := TreeNode{} - - valuesHandled := 2 - for i := 2; childrenCount > 0 || metadataCount > 0; { - if childrenCount > 0 { - // recursively make child - child, subValuesHandled := makeTree(nums[i:]) - - newNode.children = append(newNode.children, child) - valuesHandled += subValuesHandled - - childrenCount-- - - i += subValuesHandled - } else { - newNode.metadata = append(newNode.metadata, nums[i]) - valuesHandled++ - - metadataCount-- - i++ - } - } - - return &newNode, valuesHandled -} - -// part1 -func sumMetadataOnly(node *TreeNode) int { - var sumMetadata int - - for _, v := range node.metadata { - sumMetadata += v - } - - for _, child := range node.children { - sumMetadata += sumMetadataOnly(child) - } - - return sumMetadata -} - -// part2 -func sumMetadataViaChildrenIndices(node *TreeNode) int { - var sumMetadata int - if len(node.children) == 0 { - for _, v := range node.metadata { - sumMetadata += v - } - return sumMetadata - } - - // ONE INDEXED - for _, valAsChildOneIndex := range node.metadata { - if valAsChildOneIndex == 0 || valAsChildOneIndex > len(node.children) { - continue - } - - sumMetadata += sumMetadataViaChildrenIndices(node.children[valAsChildOneIndex-1]) - } - - return sumMetadata -} diff --git a/2018/day08/main_test.go b/2018/day08/main_test.go deleted file mode 100644 index 42ad49d..0000000 --- a/2018/day08/main_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string -}{ - {"example", 138, "2 3 0 3 10 11 12 1 1 0 1 99 2 1 1 2"}, - {"actual", 48155, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("want %v, got %v", test.want, got) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string -}{ - {"example", 66, "2 3 0 3 10 11 12 1 1 0 1 99 2 1 1 2"}, - {"actual", 40292, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input) - if got != test.want { - t.Errorf("want %v, got %v", test.want, got) - } - }) - } -} diff --git a/2018/day09/main.go b/2018/day09/main.go deleted file mode 100644 index 3e2da30..0000000 --- a/2018/day09/main.go +++ /dev/null @@ -1,95 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "strconv" - "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) - - 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 { - players, lastPoints := parseInput(input) - - currentMarble := &CircularLinkedListNode{nil, nil, 0} - currentMarble.left = currentMarble - currentMarble.right = currentMarble - - playerTurn := 0 - playerScores := make([]int, players) - - for points := 1; points <= lastPoints; points++ { - if points%23 == 0 { - for i := 0; i < 7; i++ { - currentMarble = currentMarble.left - } - - playerScores[playerTurn] += points - playerScores[playerTurn] += currentMarble.val - currentMarble = currentMarble.RemoveSelf() - } else { - currentMarble = currentMarble.right - currentMarble.AddToRight(points) - currentMarble = currentMarble.right - } - - playerTurn++ - playerTurn %= players - } - - return mathy.MaxInt(playerScores...) -} - -func part2(input string) int { - // lazily modify input... - split := strings.Split(input, " ") - steps := cast.ToInt(split[6]) * 100 - split[6] = strconv.Itoa(steps) - - return part1(strings.Join(split, " ")) -} - -func parseInput(input string) (players int, lastPoints int) { - split := strings.Split(input, " ") - return cast.ToInt(split[0]), cast.ToInt(split[6]) -} - -type CircularLinkedListNode struct { - left, right *CircularLinkedListNode - val int -} - -func (c *CircularLinkedListNode) AddToRight(val int) { - newNode := CircularLinkedListNode{ - left: c, - right: c.right, - val: val, - } - c.right.left = &newNode - c.right = &newNode -} - -func (c *CircularLinkedListNode) RemoveSelf() *CircularLinkedListNode { - // not handling edge case of < 2 nodes because it's not going to get hit in this case - c.left.right = c.right - c.right.left = c.left - return c.right -} diff --git a/2018/day09/main_test.go b/2018/day09/main_test.go deleted file mode 100644 index f5a0b05..0000000 --- a/2018/day09/main_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example1", 8317, "10 players; last marble is worth 1618 points"}, - {"example2", 146373, "13 players; last marble is worth 7999 points"}, - {"example3", 2764, "17 players; last marble is worth 1104 points"}, - {"example4", 54718, "21 players; last marble is worth 6111 points"}, - {"example5", 37305, "30 players; last marble is worth 5807 points"}, - {"actual", 394486, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"actual", 3276488008, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} diff --git a/2018/day10/main.go b/2018/day10/main.go deleted file mode 100644 index 46360bc..0000000 --- a/2018/day10/main.go +++ /dev/null @@ -1,158 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "math" - "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) - - if part == 1 { - part1(util.ReadFile("./input.txt")) - } else { - part2(util.ReadFile("./input.txt")) - } -} - -func part1(input string) { - postions, velocities := parseInputs(input) - - steps := 0 - - prints := 0 - hasPrinted := false - for hasLoneIsland(postions) { - move(postions, velocities) - steps++ - fmt.Println("\nsteps run", steps) - - printable := printGrid(postions) - if printable != "" { - fmt.Println("Steps: ", steps) - fmt.Println(printable) - time.Sleep(time.Millisecond * 500) - hasPrinted = true - } - // stop printing after things have collided - if printable == "" && hasPrinted { - return - } - fmt.Println(prints) - } -} - -func part2(input string) { - // Note, reused part 1 with a print for the number of steps - part1(input) -} - -func parseInputs(input string) (positions [][2]int, velocities [][2]int) { - lines := strings.Split(input, "\n") - for _, l := range lines { - posX := strings.TrimSpace(l[10:16]) - posY := strings.TrimSpace(l[18:24]) - velX := strings.TrimSpace(l[36:38]) - velY := strings.TrimSpace(l[40:42]) - positions = append(positions, [2]int{cast.ToInt(posX), cast.ToInt(posY)}) - velocities = append(velocities, [2]int{cast.ToInt(velX), cast.ToInt(velY)}) - } - - return positions, velocities -} - -// this didn't work unfortunately, letters like K do have "lone islands" along -// the diagonals -func hasLoneIsland(grid [][2]int) bool { - coords := map[[2]int]bool{} - // generate map - for _, pos := range grid { - coords[pos] = true - } - - // iterate through coords again and check if each has a neighbor in the map - delta := [][2]int{ - {-1, 0}, - {1, 0}, - {0, -1}, - {0, 1}, - } - for _, pos := range grid { - hasNeighbor := false - for _, d := range delta { - pos[0] += d[0] - pos[1] += d[1] - - if coords[pos] { - hasNeighbor = true - } - - pos[0] -= d[0] - pos[1] -= d[1] - } - if !hasNeighbor { - return true // there is a lone island - } - } - return false -} - -func move(positions [][2]int, velocities [][2]int) { - for i := range positions { - positions[i][0] += velocities[i][0] - positions[i][1] += velocities[i][1] - } -} - -func printGrid(positions [][2]int) string { - // get bounds - left := math.MaxInt16 - right := -math.MaxInt16 - top := math.MaxInt16 - bottom := -math.MaxInt16 - - coords := map[[2]int]bool{} - - for _, p := range positions { - coords[p] = true - - if p[0] < top { - top = p[0] - } - if p[0] > bottom { - bottom = p[0] - } - if p[1] < left { - left = p[1] - } - if p[1] > right { - right = p[1] - } - } - - if right-left > 20 && bottom-top > 20 { - return "" - } - - ans := "" - for row := top; row <= bottom; row++ { - for col := left; col <= right; col++ { - if coords[[2]int{row, col}] { - ans += "0" - } else { - ans += " " - } - } - ans += "\n" - } - return ans -} diff --git a/2018/day10/main_test.go b/2018/day10/main_test.go deleted file mode 100644 index b77046f..0000000 --- a/2018/day10/main_test.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -NOTE This is not an easily testable problem, these tests are for helper functions -*/ - -package main - -import ( - "testing" -) - -func TestHasLoneIsland(t *testing.T) { - tests := []struct { - name string - grid [][2]int - want bool - }{ - {"simple_all_one_island", [][2]int{ - {1, 1}, - {1, 2}, - {2, 2}, - {2, 1}, - }, false}, - {"simple_has_lone_cell", [][2]int{ - {1, 1}, - {1, 2}, - {2, 2}, - {2, 1}, - {4, 1}, - }, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := hasLoneIsland(tt.grid); got != tt.want { - t.Errorf("hasLoneIsland() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestPrintGrid(t *testing.T) { - tests := []struct { - name string - positions [][2]int - want string - }{ - {"horizontal line", [][2]int{ - {0, 0}, - {0, 1}, - {0, 2}, - {0, -1}, - }, "0000\n"}, - {"horizontal line with gap", [][2]int{ - {0, 0}, - {0, 1}, - {0, 2}, - {0, -1}, - {0, -4}, - {0, -5}, - }, "00 0000\n"}, - {"box", [][2]int{ - {0, 0}, - {0, 1}, - {0, 2}, - {1, 0}, - {2, 0}, - {2, 1}, - {2, 2}, - {1, 2}, - }, "000\n0 0\n000\n"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := printGrid(tt.positions); got != tt.want { - t.Errorf("printGrid() = %q, want %q", got, tt.want) - } - }) - } -} diff --git a/2018/day11/main.go b/2018/day11/main.go deleted file mode 100644 index 964e356..0000000 --- a/2018/day11/main.go +++ /dev/null @@ -1,118 +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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - fmt.Println("Output:", ans) - } -} - -// 300x300 grid -func part1(input string) string { - gridSN := parseInputs(input) - - grid := generateGrid(gridSN) - - // find best 3x3 grid - deltas := [][2]int{ - {-1, -1}, - {-1, 0}, - {-1, 1}, - {0, -1}, - {0, 0}, - {0, 1}, - {1, -1}, - {1, 0}, - {1, 1}, - } - var best int - var topLeftCorner [2]int - for x := 2; x < 300; x++ { - for y := 2; y < 300; y++ { - sum := 0 - for _, d := range deltas { - sum += grid[x+d[0]][y+d[1]] - } - if sum > best { - best = sum - topLeftCorner = [2]int{x - 1, y - 1} - } - } - } - - return fmt.Sprintf("%d,%d", topLeftCorner[0], topLeftCorner[1]) -} - -func part2(input string) string { - gridSN := parseInputs(input) - - grid := generateGrid(gridSN) - - var bestPower int - var xYSize [3]int - - for x := 1; x <= 300; x++ { - for y := 1; y <= 300; y++ { - var sum int - for edge := 0; edge+x <= 300 && edge+y <= 300; edge++ { - sum += grid[x+edge][y+edge] - for add := 0; add < edge; add++ { - sum += grid[x+add][y+edge] - sum += grid[x+edge][y+add] - } - if sum > bestPower { - bestPower = sum - xYSize = [3]int{x, y, edge + 1} - } - } - } - } - - return fmt.Sprintf("%d,%d,%d", xYSize[0], xYSize[1], xYSize[2]) -} - -func parseInputs(input string) int { - return cast.ToInt(strings.TrimSpace(input)) -} - -func generateGrid(gridSN int) [][]int { - oneIndexedGrid := make([][]int, 301) // X, Y - - for i := 1; i <= 300; i++ { - oneIndexedGrid[i] = make([]int, 301) - } - - for x := 1; x <= 300; x++ { - for y := 1; y <= 300; y++ { - rackID := x + 10 - power := rackID * y - power += gridSN - power *= rackID - // keep hundreds - power /= 100 - power %= 10 - - power -= 5 - oneIndexedGrid[x][y] = power - } - } - - return oneIndexedGrid -} diff --git a/2018/day11/main_test.go b/2018/day11/main_test.go deleted file mode 100644 index f332fc6..0000000 --- a/2018/day11/main_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want string - input string -}{ - {"example 1", "33,45", "18"}, - {"example 2", "21,61", "42"}, - {"actual", "34,72", util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} - -var tests2 = []struct { - name string - want string - input string -}{ - {"example 1", "90,269,16", "18"}, - {"example 2", "232,251,12", "42"}, - {"actual", "233,187,13", util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} diff --git a/2018/day12/main.go b/2018/day12/main.go deleted file mode 100644 index 6463093..0000000 --- a/2018/day12/main.go +++ /dev/null @@ -1,149 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "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) - - 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 { - initialZeroIndex := 25 // lazy placement of empty pots to left and right of inputs - state, changesMap := parseInputs(input, initialZeroIndex) - - // fmt.Printf("%d\t%v\n", 0, state) - - for i := 0; i < 20; i++ { - state = step(state, changesMap) - // fmt.Printf("%d\t%v\n", i+1, state) - } - - ans := sumOfPotNumbers(state, initialZeroIndex) - - return ans -} - -func part2(input string) int { - initialZeroIndex := 300 // lazy placement of empty pots to left and right of inputs - state, changesMap := parseInputs(input, initialZeroIndex) - - patterns := map[string]int{} - sums := []int{sumOfPotNumbers(state, initialZeroIndex)} - - var patternIndices [2]int - - for gens := 1; gens < initialZeroIndex-100; gens++ { - state = step(state, changesMap) - // fmt.Printf("%d\t%v\n", gens, state) - - currentSum := sumOfPotNumbers(state, initialZeroIndex) - sums = append(sums, currentSum) - - trimmedPattern := strings.Trim(stringify(state), ".") - // fmt.Printf("%d\t%s\n", gens, trimmedPattern) - - if lastIndex, ok := patterns[trimmedPattern]; ok { - patternIndices = [2]int{lastIndex, gens} - break // break once a pattern is found - } else { - // store pattern to index so the pattern frequency can be found - patterns[trimmedPattern] = gens - } - } - - // calc to 50000000000 - // find the frequency and the sum's diff, then add that diff * number of generations left - freq := patternIndices[1] - patternIndices[0] - patternDiff := sums[patternIndices[1]] - sums[patternIndices[0]] - if freq != 1 { - log.Fatal("Pattern frequency is assumed to be 1, part2() needs to be updated to handle != 1 cases") - } - - fiveBillion := 50000000000 - ans := sums[patternIndices[1]] + (fiveBillion-patternIndices[1])*patternDiff - - return ans -} - -func parseInputs(input string, initialZeroIndex int) (state []string, changesMap map[string]string) { - lines := strings.Split(input, "\n") - first := strings.Split(lines[0], "state: ") - - // increment it by 3 to account for empty nodes at start - for i := 0; i < initialZeroIndex; i++ { - state = append(state, ".") - } - - for _, val := range first[1] { - state = append(state, string(val)) - } - - // add 3 onto end also - for i := 0; i < initialZeroIndex; i++ { - state = append(state, ".") - } - - changesMap = make(map[string]string) - for i := 2; i < len(lines); i++ { - line := lines[i] - if line != "" { - splitStep := strings.Split(line, " => ") - changesMap[splitStep[0]] = splitStep[1] - } - } - - return state, changesMap -} - -func step(state []string, changesMap map[string]string) (nextState []string) { - for i := range state { - fiveStr := "" - for index := i - 2; index >= 0 && index < len(state) && len(fiveStr) < 5; index++ { - fiveStr += state[index] - } - - if v, ok := changesMap[fiveStr]; ok { - nextState = append(nextState, v) - } else { - // missing case should only apply to examples or len < 5 - nextState = append(nextState, ".") - } - } - - return nextState -} - -func sumOfPotNumbers(state []string, zeroIndex int) int { - ans := 0 - for i, v := range state { - if v == "#" { - ans += i - zeroIndex - } - } - return ans -} - -func stringify(state []string) string { - ans := "" - for _, v := range state { - ans += v - } - return ans -} diff --git a/2018/day12/main_test.go b/2018/day12/main_test.go deleted file mode 100644 index eb0b1e7..0000000 --- a/2018/day12/main_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string -}{ - {"example", 325, `initial state: #..#.#..##......###...### - -...## => # -..#.. => # -.#... => # -.#.#. => # -.#.## => # -.##.. => # -.#### => # -#.#.# => # -#.### => # -##.#. => # -##.## => # -###.. => # -###.# => # -####. => # -`}, - {"actual", 2542, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string -}{ - {"actual", 2550000000883, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} diff --git a/2018/day13/main.go b/2018/day13/main.go deleted file mode 100644 index 590ffed..0000000 --- a/2018/day13/main.go +++ /dev/null @@ -1,267 +0,0 @@ -// Accidentally named all the carts miners for some reason... -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) - - 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) string { - grid, miners := parseInputs(input) - - var collisionCoords map[[2]int]bool - for len(collisionCoords) == 0 { - collisionCoords = tick(grid, miners) - // fmt.Println(printGridWithMiners(grid, miners)) - } - - var coordString string - // should only be one entry, but range over the map to make it easier - // it's a map only because of how I wanted to handle part 2 - for c := range collisionCoords { - coordString = fmt.Sprintf("%d,%d", c[1], c[0]) - } - return coordString // X,Y i.e. COL, ROW -} - -func part2(input string) string { - grid, miners := parseInputs(input) - - for len(miners) > 1 { - collisionCoords := tick(grid, miners) - - // remove all miners that have collided - if len(collisionCoords) > 0 { - // wholesale replacement of miners slice - remainingMiners := []*Miner{} - for _, m := range miners { - hasCollided := collisionCoords[m.coords] - if !hasCollided { - remainingMiners = append(remainingMiners, m) - } - } - - miners = remainingMiners - } - - // fmt.Println(printGridWithMiners(grid, miners)) - } - - finalAnsCoords := miners[0].coords - return fmt.Sprintf("%d,%d", finalAnsCoords[1], finalAnsCoords[0]) // X,Y i.e. COL, ROW -} - -type Miner struct { - coords [2]int - direction string - lastTurn int -} - -func (m *Miner) Move() { - switch m.direction { - case "up": - m.coords[0]-- - case "down": - m.coords[0]++ - case "left": - m.coords[1]-- - case "right": - m.coords[1]++ - } -} - -// HandleIntersection is for "+" cells -func (m *Miner) HandleIntersection() { - turns := []string{"left", "straight", "right"} - m.lastTurn++ - m.lastTurn %= 3 - - turnDirection := turns[m.lastTurn] - if turnDirection == "straight" { - // do nothing - return - } - - // get new direction - directions := []string{"left", "up", "right", "down"} - var index int - for i, d := range directions { - if d == m.direction { - index = i - break - } - } - if turnDirection == "left" { - index-- - } else if turnDirection == "right" { - index++ - } - - index %= 4 - if index < 0 { - index = 3 - } - - m.direction = directions[index] - - return -} - -func parseInputs(input string) (grid [][]string, miners []*Miner) { - // overwrite miners with their corresponding grid value - minerVals := map[rune]string{ - '^': "|", - 'v': "|", - '>': "-", - '<': "-", - } - minerDirections := map[rune]string{ - '^': "up", - 'v': "down", - '>': "right", - '<': "left", - } - lines := strings.Split(input, "\n") - for r, l := range lines { - if l == "" { - continue - } - row := []string{} - // each line represents a row - for col, v := range l { - if minerVals[v] != "" { - row = append(row, minerVals[v]) - miners = append(miners, &Miner{ - coords: [2]int{r, col}, - direction: minerDirections[v], - lastTurn: 2, - }) - } else { - row = append(row, string(v)) - } - } - - grid = append(grid, row) - } - - return grid, miners -} - -// collision will not be "" if there is a collision -func tick(grid [][]string, miners []*Miner) (collisionCoords map[[2]int]bool) { - // sort miners into order of who will move first - sort.Slice(miners, func(i, j int) bool { - iCoords := miners[i].coords - jCoords := miners[j].coords - if iCoords[0] != jCoords[0] { - return iCoords[0] < jCoords[0] - } - return iCoords[1] < jCoords[1] - }) - - // map to detect collisions - minerCoords := map[[2]int]bool{} - // populate all coords to start because collisions don't necessarily happen - // after one or the other has moved - for _, m := range miners { - minerCoords[m.coords] = true - } - - collisionCoords = make(map[[2]int]bool) - for _, m := range miners { - coords := m.coords - - // if a collision has already occured here, do not move this cart - if collisionCoords[coords] { - continue - } - - switch grid[coords[0]][coords[1]] { - case "/": - switch m.direction { - case "left": - m.direction = "down" - case "right": - m.direction = "up" - case "up": - m.direction = "right" - case "down": - m.direction = "left" - } - case "\\": - switch m.direction { - case "left": - m.direction = "up" - case "right": - m.direction = "down" - case "up": - m.direction = "left" - case "down": - m.direction = "right" - } - case "+": - m.HandleIntersection() - } - - // remove current coordinates (as moving off this cell) - minerCoords[m.coords] = false - - m.Move() - - // if a Miner is at the new coordinates, add a collision entry - if minerCoords[m.coords] { - collisionCoords[m.coords] = true - } - // update coordinates in map - minerCoords[m.coords] = true - } - - return collisionCoords -} - -// helper func to watch the miners move -func printGridWithMiners(grid [][]string, miners []*Miner) string { - minerDirections := map[string]string{ - "up": "^", - "down": "v", - "right": ">", - "left": "<", - } - str := "" - - mapMinerCoords := map[[2]int]*Miner{} - for _, m := range miners { - mapMinerCoords[m.coords] = m - } - - for r, row := range grid { - for c, val := range row { - if m, ok := mapMinerCoords[[2]int{r, c}]; ok { - str += minerDirections[m.direction] - } else { - str += val - } - } - str += "\n" - } - return str -} diff --git a/2018/day13/main_test.go b/2018/day13/main_test.go deleted file mode 100644 index 7fea1ab..0000000 --- a/2018/day13/main_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want string - input string - // add extra args if needed -}{ - {"example", "7,3", `/->-\ -| | /----\ -| /-+--+-\ | -| | | | v | -\-+-/ \-+--/ - \------/ -`}, - {"actual", "5,102", util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} - -var tests2 = []struct { - name string - want string - input string - // add extra args if needed -}{ - {"example", "6,4", `/>-<\ -| | -| /<+-\ -| | | v -\>+/ -`}, - {"actual", "46,45", util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} diff --git a/2018/day14/main.go b/2018/day14/main.go deleted file mode 100644 index ad85087..0000000 --- a/2018/day14/main.go +++ /dev/null @@ -1,125 +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) - - 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 { - recipesWanted := parseInput(input) - - recipes := []int{3, 7} - elf1, elf2 := 0, 1 - - for len(recipes) < recipesWanted+10 { - recipes, elf1, elf2 = step(recipes, elf1, elf2) - // fmt.Println("new recipes", recipes) - // fmt.Println("new elf indices", elf1, elf2) - } - - ans := 0 - for i := 0; i < 10; i++ { - ans *= 10 - ans += recipes[i+recipesWanted] - } - - return ans -} - -func part2(input string) int { - patternToFind := parseInput(input) - - recipes := []int{3, 7} - elf1, elf2 := 0, 1 - - var recipesToLeft int - for recipesToLeft == 0 { - recipes, elf1, elf2 = step(recipes, elf1, elf2) - - // check patterns - recipesToLeft = patternMatch(recipes, patternToFind) - } - - return recipesToLeft -} - -func parseInput(input string) int { - lines := strings.Split(input, "\n") - - return cast.ToInt(lines[0]) -} - -func step(recipes []int, elf1, elf2 int) ([]int, int, int) { - recipe1, recipe2 := recipes[elf1], recipes[elf2] - newRecipe := recipe1 + recipe2 - - // add new recipes onto slice - if newRecipe >= 10 { - recipes = append(recipes, 1) - } - recipes = append(recipes, newRecipe%10) - - // get new elf indices - elf1 += recipe1 + 1 - elf2 += recipe2 + 1 - elf1 %= len(recipes) - elf2 %= len(recipes) - - return recipes, elf1, elf2 -} - -func patternMatch(recipes []int, patternToFind int) int { - patternLength := len(strconv.Itoa(patternToFind)) - // not enough recipes to compare - if len(recipes) < patternLength { - return 0 - } - - // check last two recipes in recipes slice (because it will grow by at most - // 2 recipes per step - var pattern int - // get first pattern - for i := len(recipes) - patternLength; i < len(recipes); i++ { - pattern *= 10 - pattern += recipes[i] - } - - if pattern == patternToFind { - return len(recipes) - patternLength - } - - // check second pattern - // removes one's digit - pattern /= 10 - // add new 10^6 digit - if index := len(recipes) - patternLength - 1; index >= 0 { - pattern += 100000 * recipes[index] - } - - if pattern == patternToFind { - return len(recipes) - patternLength - 1 - } - - // return zero when no pattern is found - return 0 -} diff --git a/2018/day14/main_test.go b/2018/day14/main_test.go deleted file mode 100644 index f7d10ce..0000000 --- a/2018/day14/main_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string -}{ - {"example1", 5158916779, "9"}, - {"example2", 124515891, "5"}, // first digit is a zero... my solution doesn't really account for this - {"example2", 9251071085, "18"}, - {"example2", 5941429882, "2018"}, - {"actual", 3147574107, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("after %s recipes, got %v, want %v", test.input, got, test.want) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string -}{ - {"example1", 9, "51589"}, - {"example2", 18, "92510"}, - {"example2", 2018, "59414"}, - {"actual", 20280190, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} diff --git a/2018/day15/main.go b/2018/day15/main.go deleted file mode 100644 index ebc985e..0000000 --- a/2018/day15/main.go +++ /dev/null @@ -1,363 +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) - - 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 { - g := newGame(input) - return g.runFullGame() -} - -func part2(input string) int { - var outcome int - for elfPower := 4; ; elfPower++ { - g := newGame(input) - elvesBefore := g.countElves() - - // update all elves to the new attack power - for _, c := range g.coordsToChars { - if c.charType == "E" { - c.attackPower = elfPower - } - } - - // run the game until it ends... not optimized: could abort when an elf dies - // but it's good enough - outcome = g.runFullGame() - - // check if all elves are still alive - if elvesBefore == g.countElves() { - break - } - } - - return outcome -} - -type game struct { - grid [][]string - coordsToChars map[[2]int]*character - rounds int -} - -func (g game) String() string { - ans := fmt.Sprintf("Rounds: %d\n", g.rounds) - for rowNum, row := range g.grid { - ans += fmt.Sprintf("\n%02d: ", rowNum) - for _, v := range row { - ans += v - } - } - ans += "\nAlive characters:" - for coord, char := range g.coordsToChars { - ans += fmt.Sprintf("\n%v: Char: %v", coord, char) - } - return ans -} - -func (g *game) countElves() int { - var elves int - for _, c := range g.coordsToChars { - if c.charType == "E" { - elves++ - } - } - return elves -} - -type character struct { - coord [2]int - hp int - charType string // "E" or "G" - attackPower int -} - -func (c character) String() string { - return fmt.Sprintf("%v %v HP:%v", c.charType, c.coord, c.hp) -} - -func newGame(input string) *game { - lines := strings.Split(input, "\n") - var grid [][]string - coordsToChars := map[[2]int]*character{} - - for row, line := range lines { - grid = append(grid, make([]string, len(line))) - for col, val := range strings.Split(line, "") { - grid[row][col] = val - switch val { - case "E", "G": - coord := [2]int{row, col} - coordsToChars[coord] = &character{ - coord: coord, - hp: 200, - charType: val, - attackPower: 3, // default to 3 - } - } - } - } - - return &game{ - grid: grid, - coordsToChars: coordsToChars, - rounds: 0, - } -} - -func (g *game) runFullGame() int { - var gameover bool - for !gameover { - gameover = g.runTurn() - } - - var totalHp int - for _, c := range g.coordsToChars { - totalHp += c.hp - } - - return g.rounds * totalHp -} - -func (g *game) runTurn() (gameover bool) { - charsInOrder := g.getTurnOrder() - - for _, char := range charsInOrder { - // if char is already dead, just continue on - if char.hp <= 0 { - continue - } - - // check if there are enemies in entire game - enemyType := getEnemyType(char.charType) - var enemiesFound bool - for _, c := range g.coordsToChars { - if c.charType == enemyType { - enemiesFound = true - break - } - } - if !enemiesFound { - return true - } - - // check if the character has a unit next to ir right now - enemy := g.pickTarget(char.coord) - if enemy != nil { - // attack & move on - g.attack(g.coordsToChars[char.coord], enemy) - } else { - // else try to move, then try to pick an enemy again - inRangeCoordsMap := g.getInRangeOfEnemies(char.charType) - // if no in range coords, that does not mean all enemies are dead - // it just means there is no open floor around enemies - if len(inRangeCoordsMap) == 0 { - continue - } - // get next move - nextCoord, willMove := g.determineNextMove(char.coord, inRangeCoordsMap) - if willMove { - // update grid for this character - g.grid[nextCoord[0]][nextCoord[1]] = char.charType - g.grid[char.coord[0]][char.coord[1]] = "." - - // coords of this char have changed, update variables - delete(g.coordsToChars, char.coord) // delete old entry using char's outdated coords - g.coordsToChars[nextCoord] = char // add new entry - char.coord = nextCoord // update char's coords too - - // pick a target and attack it - enemy := g.pickTarget(nextCoord) - if enemy != nil { - g.attack(g.coordsToChars[nextCoord], enemy) - } - } - } - } - - g.rounds++ - return false -} - -// returns a slice of coordinates where there are characters, in turn order -func (g *game) getTurnOrder() []*character { - var charsInOrder []*character - for i, row := range g.grid { - for j, tile := range row { - if tile == "E" || tile == "G" { - charsInOrder = append(charsInOrder, g.coordsToChars[[2]int{i, j}]) - } - } - } - return charsInOrder -} - -var diffs = [][2]int{ - {-1, 0}, // up - {0, -1}, // left - {0, 1}, // right - {1, 0}, // down -} - -// checks the four directions around the given coordinate -func (g *game) pickTarget(currentCoords [2]int) *character { - enemyType := getEnemyType(g.grid[currentCoords[0]][currentCoords[1]]) - - var chosenEnemy *character - for _, d := range diffs { - nextRow := currentCoords[0] + d[0] - nextCol := currentCoords[1] + d[1] - next := [2]int{nextRow, nextCol} - enemy, ok := g.coordsToChars[next] - // fmt.Printf(" picking target, checking %v, enemy? %v\n", next, enemy) - if ok && enemy.charType == enemyType { - if chosenEnemy == nil || chosenEnemy.hp > enemy.hp { - chosenEnemy = enemy - } - // due to the ordering of diffs slice, the reading-order enemy will - // be chosen first if there is an HP tie... I think... - } - } - - return chosenEnemy -} - -func (g *game) attack(attacker, target *character) { - target.hp -= attacker.attackPower - if target.hp <= 0 { - // remove target from map and update grid - targetCoords := target.coord - delete(g.coordsToChars, target.coord) - g.grid[targetCoords[0]][targetCoords[1]] = "." - } -} - -type bfsNode struct { - coord [2]int - dist int - initialMove [2]int -} - -func (g *game) determineNextMove(startingCoord [2]int, inRangeCoordsMap map[[2]int]bool) (nextCoord [2]int, willMove bool) { - queue := []bfsNode{ - {coord: startingCoord, dist: 0, initialMove: [2]int{}}, // some zero values are redundant, but readable - } - visitedCoords := map[[2]int]bool{[2]int{0, 0}: true} - - // store the closest in range nodes to tie break - var closestInRange []bfsNode - - // run while the closet in range slice is still empty and queue is not empty - for checkDist := 0; len(closestInRange) == 0 && len(queue) > 0; checkDist++ { - // process front of queue while its distance is equal to the check distance - for len(queue) > 0 && queue[0].dist == checkDist { - front := queue[0] - queue = queue[1:] - - // if front is in range of an enemy, add to closest in range slice - if inRangeCoordsMap[front.coord] { - closestInRange = append(closestInRange, front) - } - - // if it has not been visited before, then check its four directions - if !visitedCoords[front.coord] { - for _, d := range diffs { - nextCoord := [2]int{d[0] + front.coord[0], d[1] + front.coord[1]} - // only proceed if next coordinate is walkable - if g.grid[nextCoord[0]][nextCoord[1]] == "." { - // add next coord to queue - node := bfsNode{ - coord: nextCoord, - dist: front.dist + 1, - initialMove: front.initialMove, - } - if front.dist == 0 { - node.initialMove = nextCoord - } - queue = append(queue, node) - } - } - } - visitedCoords[front.coord] = true - } - } - - if len(closestInRange) == 0 { - return [2]int{}, false - } - - // sort destination nodes via reading order of coords, break ties on initialMove - sort.Slice(closestInRange, func(i, j int) bool { - nodeI, nodeJ := closestInRange[i], closestInRange[j] - if nodeI.coord != nodeJ.coord { - return readingOrderSortFunc(nodeI.coord, nodeJ.coord) - } - return readingOrderSortFunc(nodeI.initialMove, nodeJ.initialMove) - }) - - // return the initial move of the winning bfs node, will be used to move - // the character - return closestInRange[0].initialMove, true -} - -// returns a slice of coordinates that are next to enemies and tile is floor -// to be run when a character wants to figure out where to move -// if the returned map is empty (len 0), that indicates no one should move -func (g *game) getInRangeOfEnemies(attackingType string) map[[2]int]bool { - enemyType := getEnemyType(attackingType) - - inRangeCoords := map[[2]int]bool{} - for row := 1; row < len(g.grid)-1; row++ { - for col := 1; col < len(g.grid[0])-1; col++ { - // if search type is found, check four neighbors for a ground - if g.grid[row][col] == enemyType { - for _, d := range diffs { - nextRow := row + d[0] - nextCol := col + d[1] - if g.grid[nextRow][nextCol] == "." { - inRangeCoords[[2]int{nextRow, nextCol}] = true - } - } - } - } - } - return inRangeCoords -} - -func getEnemyType(attacker string) string { - if attacker == "G" { - return "E" - } - return "G" -} - -// should i go before j in a slice where we're sorting by reading order -func readingOrderSortFunc(i, j [2]int) (iBeforeJ bool) { - // compare via first indices if not equal - if i[0] != j[0] { - return i[0] < j[0] - } - // otherwise tie break via second indices - return i[1] < j[1] -} diff --git a/2018/day15/main_test.go b/2018/day15/main_test.go deleted file mode 100644 index d61cf7b..0000000 --- a/2018/day15/main_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var exampleInput1 = `####### -#.G...# -#...EG# -#.#.#G# -#..G#E# -#.....# -#######` - -// this example is in part 1 but not part 2 -shrug- -var exampleInput2 = `####### -#G..#E# -#E#E.E# -#G.##.# -#...#E# -#...E.# -#######` -var exampleInput3 = `####### -#E..EG# -#.#G.E# -#E.##E# -#G..#.# -#..E#.# -#######` -var exampleInput4 = `####### -#E.G#.# -#.#G..# -#G.#.G# -#G..#.# -#...E.# -#######` -var exampleInput5 = `####### -#.E...# -#.#..G# -#.###.# -#E#G#G# -#...#G# -#######` -var exampleInput6 = `######### -#G......# -#.E.#...# -#..##..G# -#...##..# -#...#...# -#.G...G.# -#.....G.# -#########` - -var tests1 = []struct { - name string - input string - want int -}{ - {"example1", exampleInput1, 27730}, - {"example2", exampleInput2, 36334}, - {"example3", exampleInput3, 39514}, - {"example4", exampleInput4, 27755}, - {"example5", exampleInput5, 28944}, - {"example6", exampleInput6, 18740}, - {"actual", util.ReadFile("input.txt"), 183300}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} - -var reddit1 = `####### -####### -#.E..G# -#.##### -#G##### -####### -#######` - -// used a sample test from a reddit thread where I was failing this specific issue -// https://www.reddit.com/r/adventofcode/comments/a6r6kg/2018_day_15_part_1_what_am_i_missing/ebxjjuo?utm_source=share&utm_medium=web2x&context=3 -func TestMovement(t *testing.T) { - t.Log("Expect Elf's first move to go RIGHT") - part1(reddit1) -} - -var tests2 = []struct { - name string - input string - want int -}{ - {"example1", exampleInput1, 4988}, - {"example3", exampleInput3, 31284}, - {"example4", exampleInput4, 3478}, - {"example5", exampleInput5, 6474}, - {"example6", exampleInput6, 1140}, - {"actual", util.ReadFile("input.txt"), 40625}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} diff --git a/2018/day16/main.go b/2018/day16/main.go deleted file mode 100644 index 44b6c94..0000000 --- a/2018/day16/main.go +++ /dev/null @@ -1,220 +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) - - 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 { - examples, _ := parseInput(input) // ignore instructions for part 1 - - var threePlusBehaviors int - for _, e := range examples { - var matches int - for _, opcodeFunc := range opcodeNamesToFuncs { - before, instructions, after := e[0], e[1], e[2] - if after == opcodeFunc(before, instructions) { - matches++ - } - } - if matches >= 3 { - threePlusBehaviors++ - } - } - - return threePlusBehaviors -} - -func part2(input string) int { - examples, instructions := parseInput(input) // ignore instructions for part 1 - - // for each num, names that it _COULD_ be - opCodeNumToNameGraph := map[int]map[string]bool{} - - for _, e := range examples { - for name, opcodeFunc := range opcodeNamesToFuncs { - before, instructions, after := e[0], e[1], e[2] - if after == opcodeFunc(before, instructions) { - if opCodeNumToNameGraph[instructions[0]] == nil { - opCodeNumToNameGraph[instructions[0]] = map[string]bool{} - } - opCodeNumToNameGraph[instructions[0]][name] = true - } - } - } - - derivedOpcodeNumToFunc := map[int]opcodeFunc{} - for len(derivedOpcodeNumToFunc) < 16 { - for num, edges := range opCodeNumToNameGraph { - if len(edges) == 1 { - for name := range edges { // only way to get the one val out of a map? - derivedOpcodeNumToFunc[num] = opcodeNamesToFuncs[name] - - // delete name from all other graph edges b/c it's settled - for _, edges := range opCodeNumToNameGraph { - delete(edges, name) - } - } - - // break to restart the main loop form the beginning - break - } - } - } - - // run all instructions - var registers [4]int - for _, inst := range instructions { - opcodeFunc := derivedOpcodeNumToFunc[inst[0]] - registers = opcodeFunc(registers, inst) - } - - return registers[0] -} - -func parseInput(input string) ([][3][4]int, [][4]int) { - lines := strings.Split(input, "\n\n\n\n") - - inputExamples := lines[0] - inputInstructions := lines[1] - - var examples [][3][4]int - for _, e := range strings.Split(inputExamples, "\n\n") { - var before, op, after [4]int - fmt.Sscanf(e, "Before: [%d, %d, %d, %d]\n%d %d %d %d\nAfter: [%d, %d, %d, %d]", - &before[0], &before[1], &before[2], &before[3], - &op[0], &op[1], &op[2], &op[3], - &after[0], &after[1], &after[2], &after[3], - ) - examples = append(examples, [3][4]int{before, op, after}) - } - - var instructions [][4]int - for _, i := range strings.Split(inputInstructions, "\n") { - var inst [4]int - fmt.Sscanf(i, "%d %d %d %d", &inst[0], &inst[1], &inst[2], &inst[3]) - instructions = append(instructions, inst) - } - - return examples, instructions -} - -var opcodeNamesToFuncs = map[string]opcodeFunc{ - "addr": addr, "addi": addi, - "multr": multr, "multi": multi, - "banr": banr, "bani": bani, - "borr": borr, "bori": bori, - "setr": setr, "seti": seti, - "gtir": gtir, "gtri": gtri, "gtrr": gtrr, - "eqir": eqir, "eqri": eqri, "eqrr": eqrr, -} - -type opcodeFunc func([4]int, [4]int) [4]int - -func addr(registers [4]int, instructions [4]int) [4]int { - registers[instructions[3]] = registers[instructions[1]] + registers[instructions[2]] - return registers -} - -func addi(registers [4]int, instructions [4]int) [4]int { - registers[instructions[3]] = registers[instructions[1]] + instructions[2] - return registers -} -func multr(registers [4]int, instructions [4]int) [4]int { - registers[instructions[3]] = registers[instructions[1]] * registers[instructions[2]] - return registers -} -func multi(registers [4]int, instructions [4]int) [4]int { - registers[instructions[3]] = registers[instructions[1]] * instructions[2] - return registers -} -func banr(registers [4]int, instructions [4]int) [4]int { - registers[instructions[3]] = registers[instructions[1]] & registers[instructions[2]] - return registers -} -func bani(registers [4]int, instructions [4]int) [4]int { - registers[instructions[3]] = registers[instructions[1]] & instructions[2] - return registers -} -func borr(registers [4]int, instructions [4]int) [4]int { - registers[instructions[3]] = registers[instructions[1]] | registers[instructions[2]] - return registers -} -func bori(registers [4]int, instructions [4]int) [4]int { - registers[instructions[3]] = registers[instructions[1]] | instructions[2] - return registers -} -func setr(registers [4]int, instructions [4]int) [4]int { - registers[instructions[3]] = registers[instructions[1]] - return registers -} -func seti(registers [4]int, instructions [4]int) [4]int { - registers[instructions[3]] = instructions[1] - return registers -} -func gtir(registers [4]int, instructions [4]int) [4]int { - if instructions[1] > registers[instructions[2]] { - registers[instructions[3]] = 1 - } else { - registers[instructions[3]] = 0 - } - return registers -} -func gtri(registers [4]int, instructions [4]int) [4]int { - if registers[instructions[1]] > instructions[2] { - registers[instructions[3]] = 1 - } else { - registers[instructions[3]] = 0 - } - return registers -} -func gtrr(registers [4]int, instructions [4]int) [4]int { - if registers[instructions[1]] > registers[instructions[2]] { - registers[instructions[3]] = 1 - } else { - registers[instructions[3]] = 0 - } - return registers -} -func eqir(registers [4]int, instructions [4]int) [4]int { - if instructions[1] == registers[instructions[2]] { - registers[instructions[3]] = 1 - } else { - registers[instructions[3]] = 0 - } - return registers -} -func eqri(registers [4]int, instructions [4]int) [4]int { - if registers[instructions[1]] == instructions[2] { - registers[instructions[3]] = 1 - } else { - registers[instructions[3]] = 0 - } - return registers -} -func eqrr(registers [4]int, instructions [4]int) [4]int { - if registers[instructions[1]] == registers[instructions[2]] { - registers[instructions[3]] = 1 - } else { - registers[instructions[3]] = 0 - } - return registers -} diff --git a/2018/day16/main_test.go b/2018/day16/main_test.go deleted file mode 100644 index 01dfe56..0000000 --- a/2018/day16/main_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string -}{ - {"example", 1, `Before: [3, 2, 1, 1] -9 2 1 2 -After: [3, 2, 2, 1] - - - -9 2 0 0`}, - {"actual", 607, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string -}{ - {"actual", 577, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day17/main.go b/2018/day17/main.go deleted file mode 100644 index ac194e7..0000000 --- a/2018/day17/main.go +++ /dev/null @@ -1,292 +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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - grid, rowBounds := parseInputs(input) - - pour(grid) - - // // Uncomment to print part of the output - // end := len(grid[0]) - // if end > 550 { - // end = 550 - // } - // for i, v := range grid { - // fmt.Println(v[450:end]) - // if i >= 60 { - // break - // } - // } - - var ans int - for r, row := range grid { - for _, v := range row { - // NOTE: used x's instead of ~'s because they're easier to see - if (v == "|" || v == "x") && r <= rowBounds[1] && r >= rowBounds[0] { - ans++ - } - } - } - return ans -} - -func part2(input string) int { - grid, rowBounds := parseInputs(input) - - pour(grid) - - var ans int - for r, row := range grid { - for _, v := range row { - if v == "x" && r <= rowBounds[1] && r >= rowBounds[0] { - ans++ - } - } - } - return ans -} - -// x, y -> col, row... -func parseInputs(input string) (grid [][]string, rowBounds [2]int) { - lines := strings.Split(input, "\n") - - verts, horis := [][3]int{}, [][3]int{} - var largestX, largestY int - lowestY := math.MaxInt32 - - for _, l := range lines { - var char1, char2 string - var num1, start, end int - fmt.Sscanf(l, "%1s=%d, %1s=%d..%d", &char1, &num1, &char2, &start, &end) - - if char1 == "x" { // vert - verts = append(verts, [3]int{num1, start, end}) - if num1 > largestX { - largestX = num1 - } - if end > largestY { - largestY = end - } - if start < lowestY { - lowestY = start - } - } else { - horis = append(horis, [3]int{num1, start, end}) - if num1 > largestY { - largestY = num1 - } - if num1 < lowestY { - lowestY = num1 - } - if end > largestX { - largestX = end - } - } - } - - grid = make([][]string, largestY+1) - for i := range grid { - grid[i] = make([]string, largestX+1) - } - - for _, coords := range verts { - col, start, end := coords[0], coords[1], coords[2] - for i := start; i <= end; i++ { - grid[i][col] = "#" - } - } - for _, coords := range horis { - row, start, end := coords[0], coords[1], coords[2] - for i := start; i <= end; i++ { - grid[row][i] = "#" - } - } - - for i, row := range grid { - for j := range row { - if grid[i][j] != "#" { - grid[i][j] = "." - } - } - } - - // add an empty row ot the bottom - - grid = append(grid, make([]string, len(grid[0]))) - for c := range grid[0] { - grid[len(grid)-1][c] = "." - } - - return grid, [2]int{lowestY, largestY} -} - -func pour(grid [][]string) { - // stack stores the coordinates that have been poured into - // the stack is used ot backtrack to previous cells to see if they can - // pour into additional spaces - stack := [][2]int{{0, 500}} - - for len(stack) > 0 { - // take coordinate at top of stack - top := stack[len(stack)-1] - currentVal := grid[top[0]][top[1]] - - // if it's a wall, pop it and continue - if currentVal == "#" { - stack = stack[:len(stack)-1] - continue - } - - down := getNextCoord(top, "down") - - // ensure it's in bounds, if not pop and continue - if !isInBounds(grid, down) { - stack = stack[:len(stack)-1] - continue - } - - // this will happen on the second visit to a coordinate (it has to be - // changed into a pipe first @ the bottom of this loop) - if currentVal == "|" { - // transform will check if the row below (down variable) is water (pipes) - // bound by walls (assume that there isn't a sneaky hole in the floor) - // if it is bound by walls, it will replace all pipes with x's to - // indicate still water - transformStillWater(grid, down) - - // pop off stack - stack = stack[:len(stack)-1] - - // continue with the rest of the loop to add coords on the stack - // this handles two cases in particular - // | - // | pouring over to the right side here - // # | | # - // # | v # - // #-----###..# - // #-----# #..# - // #-----###..# - // #----------# - // ############ - // - // | - // | - // # | # - // # | # - // #..|.......# - // #..|.......# - // #..|.......# <- filling up this row - // #----------# - // ############ - } - - // if below is a wall, append left and right to the stack - valDown := getValAt(grid, top, "down") - - // add left and right to stack if they're sand - if valDown == "#" || valDown == "x" { - if getValAt(grid, top, "left") == "." { - stack = append(stack, getNextCoord(top, "left")) - } - if getValAt(grid, top, "right") == "." { - stack = append(stack, getNextCoord(top, "right")) - } - } - // if down is sand, add it to stack - if valDown == "." { - stack = append(stack, down) - } - - // make self a water pipe - grid[top[0]][top[1]] = "|" - } -} - -// helper functions to make getting the next coordinate or its value or if its in bounds -func isInBounds(grid [][]string, coord [2]int) bool { - return coord[0] < len(grid) && coord[1] < len(grid[0]) -} - -func getNextCoord(coord [2]int, direction string) [2]int { - if !strings.Contains("downleftright", direction) { - panic("invalid direction passed to getNextCoord") - } - - switch direction { - case "down": - return [2]int{coord[0] + 1, coord[1]} - case "left": - return [2]int{coord[0], coord[1] - 1} - case "right": - return [2]int{coord[0], coord[1] + 1} - } - - return [2]int{} // should never be hit... -} - -func getValAt(grid [][]string, coord [2]int, direction string) string { - nextCoord := getNextCoord(coord, direction) - - return grid[nextCoord[0]][nextCoord[1]] -} - -// check a particular row to see if it can be transformed into still water -// i.e. is it all water pipes bound by walls on either end -func transformStillWater(grid [][]string, coord [2]int) { - var left, right int - isWalled := true - for col := coord[1] - 1; isInBounds(grid, [2]int{coord[0], col}); col-- { - if grid[coord[0]][col] == "#" { - left = col + 1 - break - } - if grid[coord[0]][col] == "." { - isWalled = false - break - } - } - for col := coord[1] + 1; isInBounds(grid, [2]int{coord[0], col}); col++ { - if grid[coord[0]][col] == "#" { - right = col - 1 - break - } - if grid[coord[0]][col] == "." { - isWalled = false - break - } - } - - if isWalled { - for i := left; i <= right; i++ { - // only transform waters, not preexisting floors - if grid[coord[0]][i] == "|" { - grid[coord[0]][i] = "x" - } - } - } -} diff --git a/2018/day17/main_test.go b/2018/day17/main_test.go deleted file mode 100644 index 505887c..0000000 --- a/2018/day17/main_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example", 57, `x=495, y=2..7 -y=7, x=495..501 -x=501, y=3..7 -x=498, y=2..4 -x=506, y=1..2 -x=498, y=10..13 -x=504, y=10..13 -y=13, x=498..504`}, - {"actual", 38364, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - t.Run(tt.name, func(t *testing.T) { - got := part1(tt.input) - if got != tt.want { - t.Errorf("got %v, want %v", got, tt.want) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example", 29, `x=495, y=2..7 -y=7, x=495..501 -x=501, y=3..7 -x=498, y=2..4 -x=506, y=1..2 -x=498, y=10..13 -x=504, y=10..13 -y=13, x=498..504`}, - {"actual", 30551, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - got := part2(tt.input) - if got != tt.want { - t.Errorf("got %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day18/main.go b/2018/day18/main.go deleted file mode 100644 index 2dcb3de..0000000 --- a/2018/day18/main.go +++ /dev/null @@ -1,170 +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) - - 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 { - grid := parseInput(input) - - for i := 0; i < 10; i++ { - grid = step(grid) - - // fmt.Println(i) - // for _, v := range grid { - // fmt.Println(v) - // } - // fmt.Println("") - } - - finalTrees, finalLumbers := countTreesAndLumber(grid) - return finalTrees * finalLumbers -} - -func part2(input string) int { - grid := parseInput(input) - stepsWanted := 1000000000 - cacheStringifiedGridToIndex := make(map[string]int) - - for i := 0; i < stepsWanted; i++ { - grid = step(grid) - - stringified := stringify(grid) - if lastIndex, ok := cacheStringifiedGridToIndex[stringified]; ok { - freq := i - lastIndex - // skip steps - for i+freq < stepsWanted { - i += freq - } - } - - cacheStringifiedGridToIndex[stringified] = i - - } - // between 197616 and 209420 - finalTrees, finalLumbers := countTreesAndLumber(grid) - return finalTrees * finalLumbers -} - -func parseInput(input string) [][]string { - var ans [][]string - - lines := strings.Split(input, "\n") - - // to pad either edge - columnsPlusTwo := len(lines[0]) + 2 - - ans = append(ans, make([]string, columnsPlusTwo)) - - for _, l := range lines { - rowSli := append([]string{""}, strings.Split(l, "")...) - rowSli = append(rowSli, "") - ans = append(ans, rowSli) - } - - ans = append(ans, make([]string, columnsPlusTwo)) - - return ans -} - -func step(grid [][]string) [][]string { - nextGrid := make([][]string, len(grid)) - for i := range grid { - nextGrid[i] = make([]string, len(grid[0])) - } - - dir := [8][2]int{ - {-1, -1}, - {-1, 0}, - {-1, 1}, - {0, -1}, - {0, 1}, - {1, -1}, - {1, 0}, - {1, 1}, - } - - for row := 1; row < len(grid)-1; row++ { - for col := 1; col < len(grid[0])-1; col++ { - var adjTrees, adjOpen, adjLumber int - for _, d := range dir { - neighborRow, neighborCol := row+d[0], col+d[1] - switch grid[neighborRow][neighborCol] { - case "#": - adjLumber++ - case "|": - adjTrees++ - case ".": - adjOpen++ - } - } - - switch grid[row][col] { - case ".": - if adjTrees >= 3 { - nextGrid[row][col] = "|" - } else { - nextGrid[row][col] = "." - } - case "|": - if adjLumber >= 3 { - nextGrid[row][col] = "#" - } else { - nextGrid[row][col] = "|" - } - case "#": - if adjLumber >= 1 && adjTrees >= 1 { - nextGrid[row][col] = "#" - } else { - nextGrid[row][col] = "." - } - } - } - } - - return nextGrid -} - -func countTreesAndLumber(grid [][]string) (int, int) { - var trees, lumbers int - for _, row := range grid { - for _, v := range row { - switch v { - case "#": - lumbers++ - case "|": - trees++ - } - } - } - return trees, lumbers -} - -func stringify(grid [][]string) string { - var ans string - for _, row := range grid { - for _, v := range row { - ans += v - } - } - return ans -} diff --git a/2018/day18/main_test.go b/2018/day18/main_test.go deleted file mode 100644 index 2542d10..0000000 --- a/2018/day18/main_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string -}{ - {"example", 1147, `.#.#...|#. -.....#|##| -.|..|...#. -..|#.....# -#.#|||#|#| -...#.||... -.|....|... -||...#|.#| -|.||||..|. -...#.|..|.`}, - {"actual", 549936, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string -}{ - {"actual", 206304, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day19/main.go b/2018/day19/main.go deleted file mode 100644 index 6d0c7bc..0000000 --- a/2018/day19/main.go +++ /dev/null @@ -1,256 +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) - - 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 { - opcodeComputer := parseInput(input) - - for !opcodeComputer.tick() { - } - - return opcodeComputer.registers[0] -} - -func part2(input string) int { - opcodeComputer := parseInput(input) - - opcodeComputer.registers[0] = 1 - - for !opcodeComputer.tick() { - } - - return opcodeComputer.registers[0] -} - -// after deeply studying how the intcode cycles, in order to optimize it and -// run in any reasonable amount of time. It's become clear that the answer is -// just the sum of all the factors of the number that is generated after a few -// steps of the opcode computer running -func part2Cheeky(input string) int { - computer := parseInput(input) - computer.registers[0] = 1 - for i := 0; i < 20; i++ { - computer.tick() - } - - numberToFactorize := computer.registers[2] // this index varies based on inputs - - var ans int - for i := 1; i <= numberToFactorize; i++ { - if numberToFactorize%i == 0 { - ans += i - } - } - - return ans -} - -type opcodeComputer struct { - instructions []instruction - registers [6]int - instructionPointer int // an index the stores the index for which instruction to run -} -type instruction struct { - name string - abcValues [3]int -} - -func (o *opcodeComputer) tick() (done bool) { - // custom logic for the repetitive behavior - ipValue := o.registers[o.instructionPointer] - if ipValue == 4 { - for o.registers[4] == 4 { - if o.registers[1] >= o.registers[2] { - break - } - if o.registers[5] == o.registers[2] { - o.registers[0] += o.registers[1] - } - - o.registers[3]++ - reg3 := o.registers[3] - if reg3 > o.registers[2] { - o.registers[1]++ - // escape hatch - if o.registers[1] > o.registers[2] { - o.registers[4] *= o.registers[4] - o.registers[4]++ - break - } - o.registers[3] = 1 - o.registers[5] = o.registers[1] - } else if o.registers[2]%o.registers[1] == 0 { - // increase registers 5 to hit o.reg5 == o.reg2 - // side of 5 = 1 x 3 - o.registers[5] = o.registers[2] - o.registers[3] = o.registers[2] - } else { - o.registers[3] = o.registers[2] - } - } - } - - if o.registers[o.instructionPointer] >= len(o.instructions) { - return true - } - - inst := o.instructions[o.registers[o.instructionPointer]] - - opcodeFunc := opcodeNamesToFuncs[inst.name] - - o.registers = opcodeFunc(o.registers, inst.abcValues) - - // increment value @ instructionPointer, validate that it's still in range - o.registers[o.instructionPointer]++ - - if o.registers[o.instructionPointer] >= len(o.instructions) { - return true - } - - return false -} - -func parseInput(input string) opcodeComputer { - lines := strings.Split(input, "\n") - - var instructionPointer int - fmt.Sscanf(lines[0], "#ip %d", &instructionPointer) - - var instructions []instruction - for _, l := range lines[1:] { - var inst instruction - fmt.Sscanf(l, "%4s %d %d %d", &inst.name, &inst.abcValues[0], &inst.abcValues[1], &inst.abcValues[2]) - instructions = append(instructions, inst) - } - - // for i, v := range instructions { - // fmt.Println(i, v) - // } - return opcodeComputer{ - instructions: instructions, - instructionPointer: instructionPointer, - } -} - -var opcodeNamesToFuncs = map[string]opcodeFunc{ - "addr": addr, "addi": addi, - "mulr": mulr, "muli": muli, - "banr": banr, "bani": bani, - "borr": borr, "bori": bori, - "setr": setr, "seti": seti, - "gtir": gtir, "gtri": gtri, "gtrr": gtrr, - "eqir": eqir, "eqri": eqri, "eqrr": eqrr, -} - -type opcodeFunc func([6]int, [3]int) [6]int - -func addr(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] + registers[abcValues[1]] - return registers -} - -func addi(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] + abcValues[1] - return registers -} -func mulr(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] * registers[abcValues[1]] - return registers -} -func muli(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] * abcValues[1] - return registers -} -func banr(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] & registers[abcValues[1]] - return registers -} -func bani(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] & abcValues[1] - return registers -} -func borr(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] | registers[abcValues[1]] - return registers -} -func bori(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] | abcValues[1] - return registers -} -func setr(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] - return registers -} -func seti(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = abcValues[0] - return registers -} -func gtir(registers [6]int, abcValues [3]int) [6]int { - if abcValues[0] > registers[abcValues[1]] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} -func gtri(registers [6]int, abcValues [3]int) [6]int { - if registers[abcValues[0]] > abcValues[1] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} -func gtrr(registers [6]int, abcValues [3]int) [6]int { - if registers[abcValues[0]] > registers[abcValues[1]] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} -func eqir(registers [6]int, abcValues [3]int) [6]int { - if abcValues[0] == registers[abcValues[1]] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} -func eqri(registers [6]int, abcValues [3]int) [6]int { - if registers[abcValues[0]] == abcValues[1] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} -func eqrr(registers [6]int, abcValues [3]int) [6]int { - if registers[abcValues[0]] == registers[abcValues[1]] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} diff --git a/2018/day19/main_test.go b/2018/day19/main_test.go deleted file mode 100644 index 1474e3e..0000000 --- a/2018/day19/main_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example", 7, `#ip 0 -seti 5 0 1 -seti 6 0 2 -addi 0 1 0 -addr 1 2 3 -setr 1 0 0 -seti 8 0 4 -seti 9 0 5`}, - {"actual", 1350, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"actual", 15844608, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - 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) - } - }) - } -} - -func TestPart2Cheeky(t *testing.T) { - tests := []struct { - name string - args string - want int - }{ - {"actual", util.ReadFile("input.txt"), 15844608}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2Cheeky(tt.args); got != tt.want { - t.Errorf("part2Cheeky() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day20/main.go b/2018/day20/main.go deleted file mode 100644 index b4b53a5..0000000 --- a/2018/day20/main.go +++ /dev/null @@ -1,158 +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 := part1And2(util.ReadFile("./input.txt"), part) - fmt.Println("Output:", ans) -} - -// The code is nearly identical for both, so part # is passed in as an arg -func part1And2(input string, part int) int { - coordsToRooms := generateRoomMap(input) - - // dijkstra traverse - var furthest int // part 1 - var countFarRooms int // part 2 - queue := [][3]int{{0, 0, 0}} // queue node is [3]int{row, col, distance} - roomsVisited := make(map[[2]int]bool, len(coordsToRooms)) - for len(queue) != 0 { - front := queue[0] - currentCoords := [2]int{front[0], front[1]} - currentDistance := front[2] - currentRoom := coordsToRooms[currentCoords] - - // do not visit the same room twice - if roomsVisited[currentCoords] { - queue = queue[1:] - continue - } - - // part 1 check for furthest room - if furthest < currentDistance { - furthest = currentDistance - } - // part 2 check for rooms at least 1000 doors away - if currentDistance >= 1000 { - countFarRooms++ - } - - roomsVisited[currentCoords] = true - - if currentRoom.northDoor { - queue = append(queue, [3]int{currentCoords[0] - 1, currentCoords[1], currentDistance + 1}) - } - if currentRoom.southDoor { - queue = append(queue, [3]int{currentCoords[0] + 1, currentCoords[1], currentDistance + 1}) - } - if currentRoom.eastDoor { - queue = append(queue, [3]int{currentCoords[0], currentCoords[1] + 1, currentDistance + 1}) - } - if currentRoom.westDoor { - queue = append(queue, [3]int{currentCoords[0], currentCoords[1] - 1, currentDistance + 1}) - } - - queue = queue[1:] - } - - if part == 1 { - return furthest - } - return countFarRooms -} - -type room struct { - coords [2]int - northDoor, eastDoor, southDoor, westDoor bool -} - -// String receiver method to satisfy Stringer interface, for easier debugging -func (r room) String() string { - return fmt.Sprintf("%v: N %v S %v E %v W %v", r.coords, r.northDoor, r.southDoor, r.eastDoor, r.westDoor) -} - -// returns slice of rooms that represent the ends of child paths, these need -// to be extended upon -func generateRoomMap(input string) map[[2]int]*room { - coordsToRooms := map[[2]int]*room{ - [2]int{0, 0}: &room{}, // starting room, all zero values are applicable - } - iter := coordsToRooms[[2]int{0, 0}] - var stack []*room - - for _, r := range input[1 : len(input)-1] { - switch dir := string(r); dir { - case "N": - nextCoords := [2]int{iter.coords[0] - 1, iter.coords[1]} - // add room to map if it's no in there already - if _, ok := coordsToRooms[nextCoords]; !ok { - coordsToRooms[nextCoords] = &room{ - coords: nextCoords, - } - } - // update valid doors, next's south, current's north - nextRoom := coordsToRooms[nextCoords] - nextRoom.southDoor = true - iter.northDoor = true - // move to next room - iter = nextRoom - case "S": - nextCoords := [2]int{iter.coords[0] + 1, iter.coords[1]} - if _, ok := coordsToRooms[nextCoords]; !ok { - coordsToRooms[nextCoords] = &room{ - coords: nextCoords, - } - } - nextRoom := coordsToRooms[nextCoords] - nextRoom.northDoor = true - iter.southDoor = true - iter = nextRoom - case "E": - nextCoords := [2]int{iter.coords[0], iter.coords[1] + 1} - if _, ok := coordsToRooms[nextCoords]; !ok { - coordsToRooms[nextCoords] = &room{ - coords: nextCoords, - } - } - nextRoom := coordsToRooms[nextCoords] - nextRoom.westDoor = true - iter.eastDoor = true - iter = nextRoom - case "W": - nextCoords := [2]int{iter.coords[0], iter.coords[1] - 1} - if _, ok := coordsToRooms[nextCoords]; !ok { - coordsToRooms[nextCoords] = &room{ - coords: nextCoords, - } - } - nextRoom := coordsToRooms[nextCoords] - nextRoom.eastDoor = true - iter.westDoor = true - iter = nextRoom - case "(": - // push onto stack - stack = append(stack, iter) - case "|": - // reset to top of stack - iter = stack[len(stack)-1] - case ")": - // backtrack and pop off stack - iter = stack[len(stack)-1] - stack = stack[:len(stack)-1] - default: - panic("unhandled character: " + string(r)) - } - } - - return coordsToRooms -} diff --git a/2018/day20/main_test.go b/2018/day20/main_test.go deleted file mode 100644 index e8cdd81..0000000 --- a/2018/day20/main_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func Test_part1And2(t *testing.T) { - type args struct { - input string - part int - } - tests := []struct { - name string - args args - want int - }{ - {"part1 example 1", args{"^WNE$", 1}, 3}, - {"part1 example 2", args{"^ENWWW(NEEE|SSE(EE|N))$", 1}, 10}, - {"part1 example 3", args{"^ENNWSWW(NEWS|)SSSEEN(WNSE|)EE(SWEN|)NNN$", 1}, 18}, - {"part1 example 4", args{"^ESSWWN(E|NNENN(EESS(WNSE|)SSS|WWWSSSSE(SW|NNNE)))$", 1}, 23}, - {"part1 example 5", args{"^WSSEESWWWNW(S|NENNEEEENN(ESSSSW(NWSW|SSEN)|WSWWN(E|WWS(E|SS))))$", 1}, 31}, - {"part1 actual", args{util.ReadFile("input.txt"), 1}, 4121}, - {"part2 actual", args{util.ReadFile("input.txt"), 2}, 8636}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1And2(tt.args.input, tt.args.part); got != tt.want { - t.Errorf("part1And2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day21/main.go b/2018/day21/main.go deleted file mode 100644 index 59504bd..0000000 --- a/2018/day21/main.go +++ /dev/null @@ -1,217 +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) - - 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 { - opcodeComputer := parseInput(input) - - for !opcodeComputer.tick() { - // instruction 28 of my input is the only one that accesses register zero - // it is comparing reg 0 to reg 5. so to break out of loops asap, set - // reg 0 to the value found in reg 5 when 28 is hit for the first time - if opcodeComputer.registers[opcodeComputer.instructionPointer] == 28 { - break - } - } - - return opcodeComputer.registers[5] -} - -func part2(input string) int { - opcodeComputer := parseInput(input) - - // similar idea for part 2 but now we need to find the previous state of - // register 5 when register 5 REPEATS itself. this is a brute force solution - // using a map to store previous reg5 values, and stores the previous reg5 - var lastReg5 int - comparedRegister5s := map[int]bool{} - for !opcodeComputer.tick() { - if opcodeComputer.registers[opcodeComputer.instructionPointer] == 28 { - reg5 := opcodeComputer.registers[5] - if comparedRegister5s[reg5] { - break - } - comparedRegister5s[reg5] = true - lastReg5 = reg5 - } - } - - return lastReg5 -} - -type opcodeComputer struct { - instructions []instruction - registers [6]int - instructionPointer int // an index the stores the index for which instruction to run -} -type instruction struct { - name string - abcValues [3]int -} - -// literal opcode computer implementation, unoptimized -func (o *opcodeComputer) tick() (done bool) { - if o.registers[o.instructionPointer] >= len(o.instructions) { - fmt.Println("Out of range instruction, terminating...") - return true - } - instIndex := o.registers[o.instructionPointer] - inst := o.instructions[instIndex] - - // fmt.Println(strings.Repeat(" ", instIndex) + strconv.Itoa(instIndex)) - - opcodeFunc := opcodeNamesToFuncs[inst.name] - - o.registers = opcodeFunc(o.registers, inst.abcValues) - - // increment value @ instructionPointer, validate that it's still in range - o.registers[o.instructionPointer]++ - - if o.registers[o.instructionPointer] >= len(o.instructions) { - return true - } - - return false -} - -func parseInput(input string) opcodeComputer { - lines := strings.Split(input, "\n") - - var instructionPointer int - fmt.Sscanf(lines[0], "#ip %d", &instructionPointer) - - var instructions []instruction - for _, l := range lines[1:] { - var inst instruction - fmt.Sscanf(l, "%4s %d %d %d", &inst.name, &inst.abcValues[0], &inst.abcValues[1], &inst.abcValues[2]) - instructions = append(instructions, inst) - } - - return opcodeComputer{ - instructions: instructions, - instructionPointer: instructionPointer, - } -} - -var opcodeNamesToFuncs = map[string]opcodeFunc{ - "addr": addr, "addi": addi, - "mulr": mulr, "muli": muli, - "banr": banr, "bani": bani, - "borr": borr, "bori": bori, - "setr": setr, "seti": seti, - "gtir": gtir, "gtri": gtri, "gtrr": gtrr, - "eqir": eqir, "eqri": eqri, "eqrr": eqrr, -} - -type opcodeFunc func([6]int, [3]int) [6]int - -func addr(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] + registers[abcValues[1]] - return registers -} - -func addi(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] + abcValues[1] - return registers -} -func mulr(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] * registers[abcValues[1]] - return registers -} -func muli(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] * abcValues[1] - return registers -} -func banr(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] & registers[abcValues[1]] - return registers -} -func bani(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] & abcValues[1] - return registers -} -func borr(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] | registers[abcValues[1]] - return registers -} -func bori(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] | abcValues[1] - return registers -} -func setr(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = registers[abcValues[0]] - return registers -} -func seti(registers [6]int, abcValues [3]int) [6]int { - registers[abcValues[2]] = abcValues[0] - return registers -} -func gtir(registers [6]int, abcValues [3]int) [6]int { - if abcValues[0] > registers[abcValues[1]] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} -func gtri(registers [6]int, abcValues [3]int) [6]int { - if registers[abcValues[0]] > abcValues[1] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} -func gtrr(registers [6]int, abcValues [3]int) [6]int { - if registers[abcValues[0]] > registers[abcValues[1]] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} -func eqir(registers [6]int, abcValues [3]int) [6]int { - if abcValues[0] == registers[abcValues[1]] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} -func eqri(registers [6]int, abcValues [3]int) [6]int { - if registers[abcValues[0]] == abcValues[1] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} -func eqrr(registers [6]int, abcValues [3]int) [6]int { - if registers[abcValues[0]] == registers[abcValues[1]] { - registers[abcValues[2]] = 1 - } else { - registers[abcValues[2]] = 0 - } - return registers -} diff --git a/2018/day21/main_test.go b/2018/day21/main_test.go deleted file mode 100644 index fd481c1..0000000 --- a/2018/day21/main_test.go +++ /dev/null @@ -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"), 2884703}, - } - 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"), 15400966}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day22/main.go b/2018/day22/main.go deleted file mode 100644 index 941b793..0000000 --- a/2018/day22/main.go +++ /dev/null @@ -1,285 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/data-structures/heap" - "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")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - depth, targetX, targetY := parseInput(input) - - regionTypeCalculator := memoRegionTypeCalculator(depth, targetX, targetY) - var ans int - - for x := 0; x <= targetX; x++ { - for y := 0; y <= targetY; y++ { - riskLevel := int(regionTypeCalculator(x, y)) % 3 - ans += riskLevel - } - } - - return ans -} - -func parseInput(input string) (int, int, int) { - lines := strings.Split(input, "\n") - var depth, targetX, targetY int - _, err := fmt.Sscanf(lines[0], "depth: %d", &depth) - if err != nil { - panic("parsing depth from input" + err.Error()) - } - _, err = fmt.Sscanf(lines[1], "target: %d,%d", &targetX, &targetY) - if err != nil { - panic("parsing targetX and targetY from input" + err.Error()) - } - - return depth, targetX, targetY -} - -func memoErosionLevelCalculator(depth, targetX, targetY int) func(x, y int) int { - // map to memoize results and prevent branch recursion - xyToErosion := map[[2]int]int{} - - var closureGetErosionFunc func(x, y int) int - closureGetErosionFunc = func(x, y int) int { - coords := [2]int{x, y} - if e, ok := xyToErosion[coords]; ok { - return e - } - - var geologicIndex int - if coords == [2]int{0, 0} || coords == [2]int{targetX, targetY} { - geologicIndex = 0 - } else if y == 0 { - geologicIndex = x * 16807 - } else if x == 0 { - geologicIndex = y * 48271 - } else { - geologicIndex = closureGetErosionFunc(x-1, y) * closureGetErosionFunc(x, y-1) - } - erosionLevel := (geologicIndex + depth) % 20183 - - xyToErosion[coords] = erosionLevel - return erosionLevel - } - - return closureGetErosionFunc -} - -func memoRegionTypeCalculator(depth, targetX, targetY int) func(x, y int) regionType { - erosionCalculator := memoErosionLevelCalculator(depth, targetX, targetY) - - xyToRegionType := map[[2]int]regionType{} - var closureRegionFunc func(x, y int) regionType - closureRegionFunc = func(x, y int) regionType { - if x < 0 || y < 0 { - return -1 - } - coords := [2]int{x, y} - if rt, ok := xyToRegionType[coords]; ok { - return rt - } - erosion := erosionCalculator(x, y) - - rt := regionType(erosion % 3) - xyToRegionType[coords] = rt - return rt - } - - return closureRegionFunc -} - -func part2(input string) int { - depth, targetX, targetY := parseInput(input) - regionCalculator := memoRegionTypeCalculator(depth, targetX, targetY) - - heap := heap.NewMinHeap() - firstNode := node{ - coords: [2]int{0, 0}, - regionType: regionCalculator(0, 0), - equipped: torch, - totalTime: 0, - } - heap.Add(firstNode) - - var eqCoordsToMinDist = map[equipmentType]map[[2]int]int{ - neither: map[[2]int]int{}, - climbingGear: map[[2]int]int{}, - torch: map[[2]int]int{}, - } - - var currentNode node - for !(currentNode.coords[0] == targetX && currentNode.coords[1] == targetY) { - currentNode = step(heap, eqCoordsToMinDist, regionCalculator) - } - - if currentNode.regionType != rocky { - panic("target must be rocky") - } - - if currentNode.equipped != torch { - finalTime := currentNode.totalTime + 7 - return finalTime - } - - return currentNode.totalTime -} - -type regionType int -type equipmentType int - -const ( - rocky regionType = iota // climbing gear or torch - wet // neither or climbing gear - narrow // neither or torch -) -const ( - neither equipmentType = iota - climbingGear - torch -) - -type node struct { - coords [2]int - equipped equipmentType - regionType regionType - totalTime int -} - -func (n node) Value() int { - return n.totalTime -} - -func step(heap *heap.MinHeap, eqCoordsToMinDist map[equipmentType]map[[2]int]int, regionCalculator func(x, y int) regionType) node { - // remove node from heap, this will be returned at the end - minNodeInterface := heap.Remove() - if minNodeInterface == nil { - panic("Heap is empty, it shouldn't be empty...") - } - minNode, ok := minNodeInterface.(node) - if !ok { - panic("interface conversion error") - } - - // if we've already visited these coordinates with this equipment, check - // that the current time is - t, ok := eqCoordsToMinDist[minNode.equipped][minNode.coords] - if ok && t <= minNode.totalTime { - return node{} - } - eqCoordsToMinDist[minNode.equipped][minNode.coords] = minNode.totalTime - // fmt.Println("coords, region, equipped, time", minNode.coords, minNode.regionType, minNode.equipped, minNode.totalTime) - // fmt.Println(" Steps", minNode.steps) - // first check if movement is possible because this requires less time - // add those steps to the heap - nodesToTravelTo := getNextNodes(minNode, regionCalculator) - // fmt.Println("nodes to travel to", nodesToTravelTo) - for _, n := range nodesToTravelTo { - heap.Add(n) - } - - // then try to change equipment if possible - newEq := getSwitchableEquipment(minNode) - - // find travelable-to nodes with new equipment on & append if applicable - swappedEquipmentNode := node{ - coords: minNode.coords, - equipped: newEq, - regionType: minNode.regionType, - totalTime: minNode.totalTime + 7, // swapping takes 7 minutes - } - // in scope of for loop, just to be safe... - nodesToTravelTo = getNextNodes(swappedEquipmentNode, regionCalculator) - - for _, n := range nodesToTravelTo { - heap.Add(n) - } - - return minNode -} - -var directions = [4][2]int{ - {-1, 0}, - {1, 0}, - {0, -1}, - {0, 1}, -} - -// returns a slice of nodes that represent traveling in the four directions from -// the currentNode -// includes adding 1 to the time -func getNextNodes(currentNode node, regionCalculator func(x, y int) regionType) []node { - var nodesToTravelTo []node - for _, d := range directions { - nextX := currentNode.coords[0] + d[0] - nextY := currentNode.coords[1] + d[1] - nextCoord := [2]int{nextX, nextY} - nextRegionType := regionCalculator(nextX, nextY) - - // ensure next region is not out of range - if nextRegionType != -1 { - // check if it can be traveled to - if canTravelTo(nextRegionType, currentNode.equipped) { - nodesToTravelTo = append(nodesToTravelTo, node{ - coords: nextCoord, - equipped: currentNode.equipped, - regionType: nextRegionType, - totalTime: currentNode.totalTime + 1, - }) - } - } - } - - return nodesToTravelTo -} - -var mapRegionsToToolsMap = map[regionType]map[equipmentType]bool{ - rocky: map[equipmentType]bool{ - climbingGear: true, - torch: true, - }, - wet: map[equipmentType]bool{ - climbingGear: true, - neither: true, - }, - narrow: map[equipmentType]bool{ - torch: true, - neither: true, - }, -} - -func canTravelTo(nextRegionType regionType, equippedTool equipmentType) bool { - return mapRegionsToToolsMap[nextRegionType][equippedTool] -} - -func getSwitchableEquipment(n node) equipmentType { - for i := 0; i < 3; i++ { - eq := equipmentType(i) - if eq != n.equipped && mapRegionsToToolsMap[n.regionType][eq] { - return eq - } - } - - panic("must be one piece of equipment to switch to") -} diff --git a/2018/day22/main_test.go b/2018/day22/main_test.go deleted file mode 100644 index 17fc518..0000000 --- a/2018/day22/main_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "reflect" - "testing" - "time" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string -}{ - {"example", 114, "depth: 510\ntarget: 10,10"}, - {"actual", 11972, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - t.Run(tt.name, func(t *testing.T) { - startTime := time.Now() - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - t.Logf("Run time: %v", time.Since(startTime)) - }) - } -} - -var tests2 = []struct { - name string - want int - input string -}{ - {"example", 45, "depth: 510\ntarget: 10,10"}, - {"actual", 1092, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - startTime := time.Now() - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - t.Logf("Run time: %v", time.Since(startTime)) - }) - } -} - -func Test_memoRegionTypeCalculator(t *testing.T) { - depth, targetX, targetY := 510, 10, 10 - type args struct { - x int - y int - } - tests := []struct { - name string - args args - want regionType - }{ - {"0,0", args{0, 0}, rocky}, - {"1,0", args{1, 0}, wet}, - {"0,1", args{0, 1}, rocky}, - {"1,1", args{1, 1}, narrow}, - {"10,10", args{10, 10}, rocky}, - {"11,10", args{11, 10}, wet}, - {"0,6", args{0, 6}, narrow}, - } - for _, tt := range tests { - memoFunc := memoRegionTypeCalculator(depth, targetX, targetY) - t.Run(tt.name, func(t *testing.T) { - if got := memoFunc(tt.args.x, tt.args.y); !reflect.DeepEqual(got, tt.want) { - t.Errorf("memoRegionTypeCalculator() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day23/main.go b/2018/day23/main.go deleted file mode 100644 index 5ba5bec..0000000 --- a/2018/day23/main.go +++ /dev/null @@ -1,147 +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) - - 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 { - bots := parseInput(input) - - strongestBot := bots[0] - for _, b := range bots { - if b.strength > strongestBot.strength { - strongestBot = b - } - } - - var withinRange int - for _, b := range bots { - if manhattanDist(b.coords, strongestBot.coords) <= strongestBot.strength { - withinRange++ - } - } - - return withinRange -} - -func part2(input string) int { - bots := parseInput(input) - - // get the bounds of the cube that all bots are inside of - // the answer coordinate will be within this space - var minCoord, maxCoord [3]int - for i := range minCoord { - minCoord[i] = math.MaxInt16 - } - for _, b := range bots { - for i := 0; i < 3; i++ { - if minCoord[i] > b.coords[i] { - minCoord[i] = b.coords[i] - } - if maxCoord[i] < b.coords[i] { - maxCoord[i] = b.coords[i] - } - } - } - - var origin [3]int - - // width of the box - boxWidth := maxCoord[0] - minCoord[0] - - // 1. width is used to calculate the eight corners to check. - // 2. the reachable bots are counted from each corner - // 3. on each iteration, the box is centered around the best corner and the - // width is cut in half until it reaches zero - var bestGrid [3]int - for boxWidth > 0 { - var maxCount int - - for x := minCoord[0]; x < maxCoord[0]+1; x += boxWidth { - for y := minCoord[1]; y < maxCoord[1]+1; y += boxWidth { - for z := minCoord[2]; z < maxCoord[2]+1; z += boxWidth { - current := [3]int{x, y, z} - var countInRange int - for _, b := range bots { - if b.canReach(current) { - countInRange++ - } - } - if maxCount < countInRange || - (maxCount == countInRange && manhattanDist(bestGrid, origin) > manhattanDist(current, origin)) { - maxCount = countInRange - bestGrid = current - } - } - } - } - - // adjust box size, i.e. min and max coords - for i := 0; i < 3; i++ { - minCoord[i] = bestGrid[i] - boxWidth - maxCoord[i] = bestGrid[i] + boxWidth - } - - // shrink searchable box size - boxWidth /= 2 - } - - return manhattanDist(bestGrid, origin) -} - -type nanobot struct { - coords [3]int - strength int -} - -func (b nanobot) canReach(coord [3]int) bool { - return manhattanDist(coord, b.coords) <= b.strength -} - -func parseInput(input string) []nanobot { - var bots []nanobot - - lines := strings.Split(input, "\n") - for _, l := range lines { - bot := nanobot{} - _, err := fmt.Sscanf(l, "pos=<%d,%d,%d>, r=%d", &bot.coords[0], &bot.coords[1], &bot.coords[2], &bot.strength) - if err != nil { - panic("parsing input line " + err.Error()) - } - bots = append(bots, bot) - } - - return bots -} - -func manhattanDist(one, two [3]int) int { - var dist int - for i := range one { - diff := one[i] - two[i] - if diff < 0 { - diff *= -1 - } - dist += diff - } - return dist -} diff --git a/2018/day23/main_test.go b/2018/day23/main_test.go deleted file mode 100644 index c5fe457..0000000 --- a/2018/day23/main_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `pos=<0,0,0>, r=4 -pos=<1,0,0>, r=1 -pos=<4,0,0>, r=3 -pos=<0,2,0>, r=1 -pos=<0,5,0>, r=3 -pos=<0,0,3>, r=1 -pos=<1,1,1>, r=1 -pos=<1,1,2>, r=1 -pos=<1,3,1>, r=1` - -var tests1 = []struct { - name string - want int - input string -}{ - {"example", 7, example}, - {"actual", 573, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 = `pos=<10,12,12>, r=2 -pos=<12,14,12>, r=2 -pos=<16,12,12>, r=4 -pos=<14,14,14>, r=6 -pos=<50,50,50>, r=200 -pos=<10,10,10>, r=5` - -var tests2 = []struct { - name string - want int - input string -}{ - {"example", 36, example2}, - {"actual", 107279292, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day24/main.go b/2018/day24/main.go deleted file mode 100644 index b0bc1ef..0000000 --- a/2018/day24/main.go +++ /dev/null @@ -1,332 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "math" - "sort" - "strconv" - "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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - immuneGroup, infectionGroup := parseInput(input) - - for !(len(immuneGroup) == 0 || len(infectionGroup) == 0) { - immuneGroup, infectionGroup, _ = battle(immuneGroup, infectionGroup) - } - - var totalWinningUnits int - for _, g := range immuneGroup { - totalWinningUnits += g.units - } - for _, g := range infectionGroup { - totalWinningUnits += g.units - } - - return totalWinningUnits -} - -func part2(input string) int { - // binary search b/c this is kind of computationally expensive to test - immuneBoostLower, immuneBoostUpper := 0, math.MaxInt16 - - for immuneBoostLower < immuneBoostUpper { - immuneGroup, infectionGroup := parseInput(input) - boost := (immuneBoostUpper + immuneBoostLower) / 2 - - _, _, immuneSystemWon := runWithImmuneBoost(immuneGroup, infectionGroup, boost) - // if immune system won, try lower numbers - if immuneSystemWon { - immuneBoostUpper = boost - } else { - // otherwise boost more - immuneBoostLower = boost + 1 - } - } - - // run it back w/ the found immuneBoost to get final - var winningUnits int - - immuneGroup, infectionGroup := parseInput(input) - finalImmuneSystem, _, _ := runWithImmuneBoost(immuneGroup, infectionGroup, immuneBoostLower) - - for _, group := range finalImmuneSystem { - winningUnits += group.units - } - - return winningUnits -} - -type group struct { - groupType string // immune or infection - units int - hp int - weakTo map[string]bool - immuneTo map[string]bool - attackPower int - attackType string - initiative int - number int // for debugging if needed -} - -func (g *group) effectivePower() int { - return g.units * g.attackPower -} - -func (g *group) attackMultiplier(incomingAttackType string) int { - if g.weakTo[incomingAttackType] { - return 2 - } - if g.immuneTo[incomingAttackType] { - return 0 - } - return 1 -} - -func (g *group) calcDamageToTarget(targetGroup *group) int { - myEP := g.effectivePower() - multiplier := targetGroup.attackMultiplier(g.attackType) - return myEP * multiplier -} - -func (g *group) takeDamage(damage int, attackType string) { - // integer division removes whole units only, per the prompt - g.units -= g.attackMultiplier(attackType) * damage / g.hp -} - -func (g *group) String() string { - return fmt.Sprintf("{ No.%d; \tHP:%d;\tInitiative:%d\tEP:%d\tUnits:%d\tAttack:%s %d\tweaknesses:%v\timmunities:%v }", - g.number, g.hp, g.initiative, g.effectivePower(), g.units, g.attackType, g.attackPower, g.weakTo, g.immuneTo) -} - -func parseInput(input string) ([]*group, []*group) { - factions := strings.Split(input, "\n\n") - - immuneLines := strings.Split(factions[0], "\n")[1:] - infectLines := strings.Split(factions[1], "\n")[1:] - - immuneGroup := makeGroups(immuneLines, "immune") - infectGroup := makeGroups(infectLines, "infection") - - return immuneGroup, infectGroup -} - -func makeGroups(lines []string, groupType string) []*group { - var groups []*group - for i, str := range lines { - g := group{ - groupType: groupType, - number: i + 1, - weakTo: map[string]bool{}, - immuneTo: map[string]bool{}, - } - // units and hit points are at start of string - fmt.Sscanf(str, "%d units each with %d hit points", &g.units, &g.hp) - - if strings.Contains(str, "(") { - openIndex := strings.Index(str, "(") - closeIndex := strings.Index(str, ")") - - affinities := strings.Split(str[openIndex+1:closeIndex], "; ") // w/o parens - for _, aff := range affinities { - if strings.Contains(aff, "weak to ") { - weaknesses := strings.Split(aff[len("weak to "):], ", ") - for _, w := range weaknesses { - g.weakTo[w] = true - } - } - if strings.Contains(aff, "immune to") { - immunities := strings.Split(aff[len("immune to "):], ", ") - for _, imm := range immunities { - g.immuneTo[imm] = true - } - } - } - } - - // the rest of the string is fairly uniform, so this can be generalized - attackIndex := strings.Index(str, "attack that does ") + len("attack that does ") - restOfString := strings.Split(str[attackIndex:], " ") - g.attackPower, _ = strconv.Atoi(restOfString[0]) - g.attackType = restOfString[1] - g.initiative, _ = strconv.Atoi(restOfString[5]) - - groups = append(groups, &g) - } - - return groups -} - -func battle(immune, infection []*group) (immunesAfter []*group, infectionsAfter []*group, isStalemate bool) { - // target selection, using a slice so it can be easily sorted later - attackerToTarget := [][2]*group{} - hasBeenTargetted := map[*group]bool{} - - // sort via decreasing EP, ties broken by highest initiative - sort.Slice(immune, func(i, j int) bool { - haveEqualEP := immune[i].effectivePower() == immune[j].effectivePower() - if haveEqualEP { - return immune[i].initiative > immune[j].initiative - } - return immune[i].effectivePower() > immune[j].effectivePower() - }) - sort.Slice(infection, func(i, j int) bool { - haveEqualEP := infection[i].effectivePower() == infection[j].effectivePower() - if haveEqualEP { - return infection[i].initiative > infection[j].initiative - } - return infection[i].effectivePower() > infection[j].effectivePower() - }) - - for _, immuneGroup := range immune { - // target = who I'd deal the most damage to, ties broken via higher EP, then higher initiative - var bestTarget *group - for _, target := range infection { - // each unit can only be attacked once - if !hasBeenTargetted[target] { - if bestTarget == nil { - if immuneGroup.calcDamageToTarget(target) != 0 { - bestTarget = target - } - } else { - damageToBest := immuneGroup.calcDamageToTarget(bestTarget) - damageToCurrent := immuneGroup.calcDamageToTarget(target) - if damageToBest < damageToCurrent { - bestTarget = target - } else if damageToBest == damageToCurrent { - // break damage tie on higher target EP first, then initiative - epOfBest := bestTarget.effectivePower() - epOfCurrent := target.effectivePower() - if epOfBest < epOfCurrent { - bestTarget = target - } else if epOfBest == epOfCurrent && bestTarget.initiative < target.initiative { - bestTarget = target - } - } - } - } - } - - if bestTarget != nil { - attackerToTarget = append(attackerToTarget, [2]*group{immuneGroup, bestTarget}) - hasBeenTargetted[bestTarget] = true - } - } - - // identical logic - for _, infectGroup := range infection { - var bestTarget *group - for _, target := range immune { - if !hasBeenTargetted[target] { - if bestTarget == nil { - if infectGroup.calcDamageToTarget(target) != 0 { - bestTarget = target - } - } else { - damageToBest := infectGroup.calcDamageToTarget(bestTarget) - damageToCurrent := infectGroup.calcDamageToTarget(target) - if damageToBest < damageToCurrent { - bestTarget = target - } else if damageToBest == damageToCurrent { - epOfBest := bestTarget.effectivePower() - epOfCurrent := target.effectivePower() - if epOfBest < epOfCurrent { - bestTarget = target - } else if epOfBest == epOfCurrent && bestTarget.initiative < target.initiative { - bestTarget = target - } - } - } - } - } - - if bestTarget != nil { - attackerToTarget = append(attackerToTarget, [2]*group{infectGroup, bestTarget}) - hasBeenTargetted[bestTarget] = true - } - } - - // attack phase, iterate through selections & make attacks - // highest initiative attacks first - sort.Slice(attackerToTarget, func(i, j int) bool { - return attackerToTarget[i][0].initiative > attackerToTarget[j][0].initiative - }) - - isStalemate = true - for _, attack := range attackerToTarget { - attacker, defender := attack[0], attack[1] - // check units != 0 before attacking, they could have been killed off by - // a previous attack (these groups will be removed at the end) - if attacker.units > 0 { - targetUnitsBefore := defender.units - defender.takeDamage(attacker.effectivePower(), attacker.attackType) - // if units have died, then it is not a stalemate - if defender.units != targetUnitsBefore { - isStalemate = false - } - } - } - - // remove groups that have zero units - for i := 0; i < len(immune); { - if immune[i].units <= 0 { - immune[i] = immune[len(immune)-1] - immune = immune[:len(immune)-1] - } else { - i++ - } - } - - for i := 0; i < len(infection); { - if infection[i].units <= 0 { - infection[i] = infection[len(infection)-1] - infection = infection[:len(infection)-1] - } else { - i++ - } - } - - return immune, infection, isStalemate -} - -// helper function for part 2 -func runWithImmuneBoost(immuneGroup []*group, infectionGroup []*group, boost int) (immuneGroupAfter, infectionGroupAfter []*group, immuneWon bool) { - // boost attacks of all immune groups - for _, g := range immuneGroup { - g.attackPower += boost - } - - var isStalemate bool - for !(len(immuneGroup) == 0 || len(infectionGroup) == 0) { - immuneGroup, infectionGroup, isStalemate = battle(immuneGroup, infectionGroup) - if isStalemate { - break - } - } - - // if immune groups are all dead OR a stalemate has occurred, return false - if len(immuneGroup) == 0 || isStalemate { - return nil, nil, false - } - return immuneGroup, infectionGroup, true -} diff --git a/2018/day24/main_test.go b/2018/day24/main_test.go deleted file mode 100644 index 1613f97..0000000 --- a/2018/day24/main_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var exampleInput = `Immune System: -17 units each with 5390 hit points (weak to radiation, bludgeoning) with an attack that does 4507 fire damage at initiative 2 -989 units each with 1274 hit points (immune to fire; weak to bludgeoning, slashing) with an attack that does 25 slashing damage at initiative 3 - -Infection: -801 units each with 4706 hit points (weak to radiation) with an attack that does 116 bludgeoning damage at initiative 1 -4485 units each with 2961 hit points (immune to radiation; weak to fire, cold) with an attack that does 12 slashing damage at initiative 4` - -var tests1 = []struct { - name string - want int - input string -}{ - {"example", 5216, exampleInput}, - {"actual", 15392, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example", 51, exampleInput}, - // {"actual", 1092, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2018/day25/main.go b/2018/day25/main.go deleted file mode 100644 index bcd113d..0000000 --- a/2018/day25/main.go +++ /dev/null @@ -1,97 +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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - fmt.Println("Output:", ans) - } else { - panic("no part 2 :P") - } -} - -func part1(input string) int { - allCoords := parseInput(input) - - constellations := [][][4]int{} - - for _, iterCoord := range allCoords { - // check which constellations this coordinate is in range of - indicesWithinRange := []int{} - for constIndex, constellation := range constellations { - for _, constCoord := range constellation { - if manhattanDistance(constCoord, iterCoord) <= 3 { - indicesWithinRange = append(indicesWithinRange, constIndex) - break - } - } - } - - // if no existing constellations are within range, append a new one with - // just this current coordinate - if len(indicesWithinRange) == 0 { - constellations = append(constellations, [][4]int{iterCoord}) - } else { - // otherwise merge all constellations together (into the first one) - // add the current node - // then remove any other constellations that were merged into #1 - firstIndex := indicesWithinRange[0] - for i, indexToMerge := range indicesWithinRange { - if i != 0 { - current := constellations[indexToMerge] - constellations[firstIndex] = append(constellations[firstIndex], current...) - } - } - constellations[firstIndex] = append(constellations[firstIndex], iterCoord) - - // remove all but the first constellation, in reverse order b/c - // using this method for removal affects the end of the slice, and - // in some cases that may be the element that we want to remove - for i := len(indicesWithinRange) - 1; i > 0; i-- { - constIndex := indicesWithinRange[i] - lastIndex := len(constellations) - 1 - constellations[constIndex] = constellations[lastIndex] - constellations = constellations[:lastIndex] - } - } - } - - return len(constellations) -} - -func parseInput(input string) [][4]int { - lines := strings.Split(input, "\n") - allCoords := make([][4]int, len(lines)) - for i, l := range lines { - newCoord := [4]int{} - fmt.Sscanf(l, "%d,%d,%d,%d", &newCoord[0], &newCoord[1], &newCoord[2], &newCoord[3]) - - allCoords[i] = newCoord - } - - return allCoords -} - -func manhattanDistance(one, two [4]int) int { - var sum int - for i := range one { - diff := one[i] - two[i] - if diff < 0 { - diff *= -1 - } - sum += diff - } - return sum -} diff --git a/2018/day25/main_test.go b/2018/day25/main_test.go deleted file mode 100644 index b6aa1a0..0000000 --- a/2018/day25/main_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example 2", 2, `0,0,0,0 -3,0,0,0 -0,3,0,0 -0,0,3,0 -0,0,0,3 -0,0,0,6 -9,0,0,0 -12,0,0,0`}, - {"example 3", 3, `1,-1,0,1 -2,0,-1,0 -3,2,-1,0 -0,0,3,1 -0,0,-1,-1 -2,3,-2,0 --2,2,0,0 -2,-2,0,-1 -1,-1,0,-1 -3,2,0,2`}, - {"example 4", 4, `-1,2,2,0 -0,0,2,-2 -0,0,0,-2 --1,2,0,0 --2,-2,-2,2 -3,0,2,-1 --1,3,2,2 --1,0,-1,0 -0,2,1,-2 -3,0,0,0`}, - {"example 8", 8, `1,-1,-1,-2 --2,-2,0,1 -0,2,1,3 --2,3,-2,1 -0,2,3,-2 --1,-1,1,-2 -0,-2,-1,0 --2,2,3,-1 -1,2,2,0 --1,-2,0,-2`}, - {"actual", 422, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2019/README.md b/2019/README.md deleted file mode 100644 index c5d1b69..0000000 --- a/2019/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Advent of Code 2019 -Language: Go - -[https://adventofcode.com/2019](https://adventofcode.com/2019) - ---- -## Summary -Day | Name | Type of Algo & Notes ---- | --- | --- -1 | The Tyranny of the Rocket Equation | - Simple math problem -2 | Program Alarm | - Intro to the crazy Intcode problems that are half the AoC days...
- Array (slice...) manipulation
- I used recursion -3 | Crossed Wires | - Geometry kind of algo, finding intersections of lines on a grid -4 | Secure Container | - May appear math-y, but it's really a string manipulation problem -5 | Sunny with a Chance of Asteroids | - Yay more Intcode!........
- This gave me fits...
- Good application for recursion (in my opinion) -6 | Universal Orbit Map | - __Tree traversal__ and depth calculations. It's not quite a Graph, but it has a __directed graph__ algo feel too -7 | Amplification Circuit | - More Intcode... Piping together multiple Intcode computers 😳😳😳
- Refactored Intcode computer to an OOP approach so a single computer maintains its data
- Also requires making __permutations generator__
- Some gymnastics to make this circular, but its easier with this OOP approach and the "objects"/instances of a struct maintaining their own data
- Concurrency could be used to sync these Amps together... -8 | Space Image Format | 3D Array manipulation, pretty straight forward -9 | Sensor Boost | __MORE INTCODE. YAYY__ 🙃
- A new parameter mode and opcode.
- __Really feeling the (tech) debt of some earlier design choices here, went back to refactor day07 before jumping into this one__, then it was a small bit of code for the relative param/opcode & resizing computer memory if necessary -10 | Monitoring Station | - This (part2) is my favorite algo... Yes I have a favorite algo
Fundamentally it's a geometry problem, angles and trig
- Part 1: Calculated via slopes
- Part 2: Using Arctangent to find angles an asteroid makes against a vertical line from the home base asteroid. Then those angles can be used to determine if Asteroids are covering each other, AND iterating through all of them can find the next angle Asteroid to vaporize -11 | Space Police | - More Intcode stuff...
- __2D Array/Slice manipulation__ and a bit of maths/graphing
- Implemented a __RotateGrid__ algo -12 | The N-Body Problem | I like to call this a _(harmonic) frequency_ algo. Finding the harmonic frequency of multiple bodies/items and then finding the Least Common Multiple of those frequencies will tell you when all the bodies have returned to their initial state.
- I've used this approach for a leetcode problem about prisoners in jail cells too -13 | Care Package | Intcode again! It's basically every other day...
- part1: 2D array manipulation again
- part2: holy algo, some logic to basically play Bricks game.
- This is more of a design question for how you manage state -14 | Space Stoichiometry | __Weighted Graph and Breadth First Traversals__
- Because not all of the products have a quantity of 1, it complicates the graph's data model. I ended up mapping the product/chemical name to a map of its stoichiometry where the product's number is positive & reactants were negative.
- part2: not just a simple division because of "extra byproducts" of generating each piece of fuel. I just let this brute force thing run for ~30 seconds... -15 | Oxygen System | YAY INTCODE 🙄
- Combination of __2D grid searching algo__, __backtracking algo__ and the the Intcode...
- I've realized that I really need to stop using x and y for 2D grids and start using row and col because mathematically x is horizontal and y is vertical... My brain is all jumbled up
- Created a Robot struct/class that has a computer inside of it. It goes and searches around, collecting data on the floor types at various coordinates. That data is transformed into a 2D grid/array, and then finally fed into a backtracking, searching algorithm to determine the shortest path (turns out there's only one path to the O2 tank...)
- part2 is fairly straight forward 2D grid traversing and tagging a spread of oxygen to valid tiles/hallway spaces -16 | Flawed Frequency Transmission | - Some really weird, contrived (as if this whole thing isn't) phase calculator?..
- part2 broke my brain. Optimally calculate subsets by __precalculating running sums__ (linear), then getting subsets by subtracting two of the precalculated running sums. i.e. sub[2:4] = sub[0:4] - sub[0:2]
- And also switching pattern recognition to make calculating a particular output O(nlogn)... -17 | Set and Forget | More Intcode...
Robot walking around a scaffolding... Fairly similar to previous algos, and a 2D grid traversal again
- I feel like I cheated part2 by finding the pattern by eye after printing what the 2D grid looks like. Then it was a matter of giving the intcode computer the corresponding inputs (in a weird format).
- Good example of __iterative approaches being better than recursive approaches__ because the recursive approach in "continuous video mode" causes a stack overflow very quickly -18 | Many-Worlds Interpretation | - Oof, this is the hardest algo so far I think... Combination of __Best Pathfinding Algo__ (going to use a BFS/Dijkstra's) and a weighted __Graph Traversal__ with "locked" edges
- I toyed with the idea of using some kind of bitmap to track keys that were found, but ditched it so I wouldn't have to write my own wrapper around bitwise logic... Time complexity comparisons are roughly similar for comparing two maps as comparing two sets of bits, the space complexity is probably much better for 27-bits (@ and a-z) compared to a hashmap, but OH WELL.
- The graph traversal required memoizing in order to run in any reasonable amount of time.
- My part 2 assumptions would not work for every example which is unfortunate :/ I assumed that within a particular quadrant, if the quadrant does not have the key for a door, just remove the door. -19 | Tractor Beam | - Another Intcode outputting to a 2D grid
- __THERE NEEDS TO BE A CONVENTION FOR X AND Y WHEN WORKING WITH 2D GRIDS__ This is awful, should it be mathematical? Should it represent rows and columns? Who knows?!
- Little geometry (drawing squares) for part2 but nothing crazy -20 | Donut Maze | __Breadth first search__ path finding algo. Dijkstra's Algorithm to find the shortest path given a maze with "portals."
Part 2 is kind of wild with the maze become 3D, but the solution is effectively the same, just more complex to implement
- I remapped this in my head to more of a 3D cube instead of a donut...
- That was really cool to get working... Dare I say fun... -21 | Springdroid Adventure | Another simple-ish Intcode based problem, this time using some weird __Assembly language__ that gets inputs via writing ASCII values to the Intcode computer. -22 | Slam Shuffle | - Seems fairly easy at first... But the part 2 has numbers somewhere in the 32-bit number to 64-bit number range... So the part 1 code is pretty much useless...
I gave up on part 2. It's some crazy __linear algebra with modular inverses?..__ Theoretically it makes some sense.. but I had to read someone else's solution for hours to kind of understand the implementation... -23 | Category Six | Intcode computer NETWORK of 50 computers...
Oof that's a lot of stuff to coordinate, but not too hard, make a Network struct.
Part2 doesn't seem too different, but includes a NAT device, which is simple enough to just store in variables in the goroutine...
That has to be record time for me to finish a day :) Sigh of relief after day22. -24 | Planet of Discord | Almost done... When the input if 25 character you know it's going to be a crazy AoC day...
Part1 is fairly straightforward _2D slice traversal_.
Part2 makes it a recursive map... of course
I chose to utilize predefined-length arrays to minimize having to make lots of slices. If the number of minutes was not known ahead of time, then a doubly linked list could have worked to expand layers indefinitely. Or a map with int-indexes because those could be negative.
I wrote a lot of code to just handle recursive structures... There's probably a way to clean this up and I definitely didn't plan my approach well enough before writing code -25 | Cryostasis | One final Intcode problem
diff --git a/2019/day01/main.go b/2019/day01/main.go deleted file mode 100644 index 344c053..0000000 --- a/2019/day01/main.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import "fmt" - -// function to recursively find the value required for a single mass input -func calcSingleFuel(mass int) int { - if mass/3-2 <= 0 { - return 0 - } - // otherwise add this mass to recursive call of its own weight - return mass/3 - 2 + calcSingleFuel(mass/3-2) - -} -func calcFuel(masses []int) int { - var sumFuel int - for _, v := range masses { - // go automatically rounds down for int division - sumFuel += calcSingleFuel(v) - // this may be a case for spinning up goroutines & using channels :) - } - - return sumFuel -} - -func main() { - fuelSum := calcFuel([]int{137857, 121089, 138217, 109202, 82225, 65823, 110808, 82512, 71144, 129168, 142785, 103418, 99710, 84192, 115757, 52318, 137911, 56254, 76634, 66071, 127842, 109339, 98408, 139481, 95628, 78679, 111571, 131078, 79085, 72375, 56248, 60740, 85461, 144773, 55946, 114634, 120127, 72284, 140489, 141471, 122177, 102224, 91708, 120190, 119453, 62930, 64543, 53581, 69276, 111118, 57458, 135155, 85937, 93962, 98877, 101615, 107952, 139285, 55459, 113772, 121742, 79283, 98389, 61720, 110494, 110170, 125851, 111776, 122679, 91843, 94138, 63348, 71461, 75067, 62334, 62799, 59647, 86722, 82468, 91995, 133229, 111361, 140125, 63884, 65911, 135857, 61515, 82726, 66453, 106329, 89199, 68089, 136698, 117180, 97577, 68922, 130528, 72076, 73691, 65800}) - // print it to console (copy into advent of code manually...) - fmt.Println(fuelSum) -} diff --git a/2019/day02/part1/main.go b/2019/day02/part1/main.go deleted file mode 100644 index 674ddd8..0000000 --- a/2019/day02/part1/main.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file and place into slice of strings - inputFile := util.ReadFile("../input.txt") - splitStrings := strings.Split(inputFile, ", ") - - // convert to slice of numbers - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // start running the step function - step(inputNumbers, 0) - fmt.Println("Final value at address 0 is:", inputNumbers[0]) -} - -// step will read the next 4 values in the input `sli` and make updates according to the opcodes -func step(sli []int, index int) bool { - if sli[index] == 99 { - return false - } - opcode, two, three, four := read(sli, index) - switch opcode { - case 1: - sli[four] = sli[two] + sli[three] - case 2: - sli[four] = sli[two] * sli[three] - } - // recursively call itself & increment index value... - return step(sli, index+4) -} - -// this read function may be necessary later as the intcode thingy becomes more complex -func read(sli []int, index int) (int, int, int, int) { - return sli[index], sli[index+1], sli[index+2], sli[index+3] -} diff --git a/2019/day02/part2/main.go b/2019/day02/part2/main.go deleted file mode 100644 index e1d47c8..0000000 --- a/2019/day02/part2/main.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - splitStrings := strings.Split(inputFile, ", ") - inputNumbers := make([]int, len(splitStrings)) - - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // brute force to try all options for nouns and verbs - for i := 0; i < 100; i++ { - for j := 0; j < 100; j++ { - // crete a copy of the inputNumbers slice - clone := make([]int, 120) - copy(clone, inputNumbers) - clone[1] = i - clone[2] = j - - // run step on the cloned slice - step(clone, 0) - // check if the zero address is equal to the github.com/alexchao26/advent-of-code-go value - if clone[0] == 19690720 { - // print answers to console (manually add to advent of code) - fmt.Println("noun is", i, "verb is", j) - fmt.Println("actual result value noun * 10 + verb = ", i*100+j) - - // return to end main function - return - } - } - } -} - -// step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func step(sli []int, index int) bool { - if sli[index] == 99 { - return false - } - opcode, two, three, four := read(sli, index) - switch opcode { - case 1: - sli[four] = sli[two] + sli[three] - case 2: - sli[four] = sli[two] * sli[three] - } - // recursively call itself & increment index value... - return step(sli, index+4) -} - -func read(sli []int, index int) (int, int, int, int) { - return sli[index], sli[index+1], sli[index+2], sli[index+3] -} diff --git a/2019/day03/part1/main.go b/2019/day03/part1/main.go deleted file mode 100644 index 0617bda..0000000 --- a/2019/day03/part1/main.go +++ /dev/null @@ -1,102 +0,0 @@ -package main - -import ( - "fmt" - "math" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read input file, split by the new line, then split each line by commas - input := util.ReadFile("../input.txt") - sli := strings.Split(input, "\n") - - split1 := strings.Split(sli[0], ",") - split2 := strings.Split(sli[1], ",") - - coordsMap1 := makeCoordinatesMap(split1) - coordsMap2 := makeCoordinatesMap(split2) - - // the highest safe int32... https://golang.org/pkg/math/ - // start this at the highest possible value so it'll be easily overwriteable - minDist := math.MaxInt32 - - // iterate over all keys & values in coordsMap1 - // if that is also in the other map, check if it has a better manhattan dist - for key := range coordsMap1 { - if _, ok := coordsMap2[key]; ok { - if dist := manhattanDistance(key); dist < minDist { - // update minDist if applicable - minDist = dist - } - } - } - - fmt.Println("The lowest Manhattan Distance is", minDist) -} - -// make a map where string key represents coordinates -// value is an int for distance of wire to this point -func makeCoordinatesMap(directionsSlice []string) map[string]int { - gridOfCoordinates := map[string]int{} - prevX := 0 - prevY := 0 - runningLength := 0 - - for i := 0; i < len(directionsSlice); i++ { - // grab the current element/direction - v := directionsSlice[i] - - // split this element into a slice of runes... - runeSlice := []rune(v) - - // stores number parsed off of this element - num, _ := strconv.Atoi(string(runeSlice[1:len(runeSlice)])) - - // loop from 0 to num and add to the map/gridOfCoordinates - for num > 0 { - // on each loop increment the runningLength, decrement num - runningLength++ - num-- - switch runeSlice[0] { - case 'R': - prevX++ - case 'L': - prevX-- - case 'U': - prevY++ - case 'D': - prevY-- - } - - // set `${prevX}x${prevY}` to the map with runningLength as the value - newCoord := strconv.Itoa(prevX) + "x" + strconv.Itoa(prevY) - - if _, ok := gridOfCoordinates[newCoord]; !ok { - gridOfCoordinates[newCoord] = runningLength - } - } - } - - return gridOfCoordinates -} - -func manhattanDistance(coord string) int { - // parse coordinates off of the passed in string key - split := strings.Split(coord, "x") - x, _ := strconv.Atoi(split[0]) - y, _ := strconv.Atoi(split[1]) - - // ensure they're both positive - if x < 0 { - x *= -1 - } - if y < 0 { - y *= -1 - } - - return x + y -} diff --git a/2019/day03/part2/main.go b/2019/day03/part2/main.go deleted file mode 100644 index a2ece11..0000000 --- a/2019/day03/part2/main.go +++ /dev/null @@ -1,85 +0,0 @@ -package main - -import ( - "fmt" - "math" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read input file, split by the new line, then split each line by commas - input := util.ReadFile("../input.txt") - sli := strings.Split(input, "\n") - - split1 := strings.Split(sli[0], ",") - split2 := strings.Split(sli[1], ",") - - coordsMap1 := makeCoordinatesMap(split1) - coordsMap2 := makeCoordinatesMap(split2) - - lowestSumOfDistances := math.MaxInt32 - - // iterate over all keys & values in coordsMap1 - for key, value1 := range coordsMap1 { - // check if the same key is in coordsMap2 - if value2, ok := coordsMap2[key]; ok { - // update lowestSumOfDistances if applicable - if lowestSumOfDistances > value1+value2 { - lowestSumOfDistances = value1 + value2 - } - } - } - - fmt.Println("Lowest sum of distances", lowestSumOfDistances) -} - -// return a map that has keys of strings and a value of an int of steps to reach the coordinate -func makeCoordinatesMap(directionsSlice []string) map[string]int { - gridOfCoordinates := map[string]int{} - prevX := 0 - prevY := 0 - runningLength := 0 - - for i := 0; i < len(directionsSlice); i++ { - // grab the current element - v := directionsSlice[i] - - // split this element into a slice of runes... - runeSlice := []rune(v) - - // stores number parsed off of this element - num, _ := strconv.Atoi(string(runeSlice[1:])) - // fmt.Println(num) - - // loop from 0 to num and add to the map/gridOfCoordinates - for num > 0 { - // on each loop increment the runningLength, decrement num - runningLength++ - num-- - switch runeSlice[0] { - case 'R': - // if going right, increment prevX - prevX++ - case 'L': - prevX-- - case 'U': - prevY++ - case 'D': - prevY-- - } - - // set `${prevX}x${prevY}` to the map with runningLength as the value - newCoord := strconv.Itoa(prevX) + "x" + strconv.Itoa(prevY) - - _, ok := gridOfCoordinates[newCoord] - if ok == false { - gridOfCoordinates[newCoord] = runningLength - } - } - } - // fmt.Println(gridOfCoordinates) - return gridOfCoordinates -} diff --git a/2019/day04/part1/main.go b/2019/day04/part1/main.go deleted file mode 100644 index 6116614..0000000 --- a/2019/day04/part1/main.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "fmt" -) - -func main() { - start, end := 138307, 654504 - possibleCombinations := 0 - - for i := start; i <= end; i++ { - digits := makeDigitsSlice(i) - if isIncreasing(digits) && hasDuplicate(digits) { - possibleCombinations++ - } - } - - fmt.Println(possibleCombinations) -} - -func makeDigitsSlice(num int) []int { - result := make([]int, 6) - for i := 5; num > 0; i-- { - result[i] = num % 10 - num -= num % 10 - num /= 10 - } - return result -} - -func isIncreasing(digits []int) bool { - for i := 1; i < len(digits); i++ { - if digits[i] < digits[i-1] { - return false - } - } - return true -} - -func hasDuplicate(digits []int) bool { - for i := 1; i < len(digits); i++ { - if digits[i-1] == digits[i] { - return true - } - } - return false -} diff --git a/2019/day04/part2/main.go b/2019/day04/part2/main.go deleted file mode 100644 index 01883ca..0000000 --- a/2019/day04/part2/main.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Only change from part1 is adding a decorator to the digit slices to shrink large -groups (more than 2 digits) into a single digit. Then pass that into the same -checks for a duplicate & all decreasing -*/ - -package main - -import "fmt" - -func main() { - start, end := 138307, 654504 - possibleCombinations := 0 - - for i := start; i <= end; i++ { - digits := shrinkLargeGroups(makeDigitsSlice(i)) - if isIncreasing(digits) && hasDuplicate(digits) { - possibleCombinations++ - } - } - - fmt.Println(possibleCombinations) -} - -func makeDigitsSlice(num int) []int { - result := make([]int, 6) - for i := 5; num > 0; i-- { - result[i] = num % 10 - num -= num % 10 - num /= 10 - } - return result -} - -/* -shrinkLargeGroups will return a new slice with any groups larger than 2 shrinked -down to 1. e.g. 111223 -> 1223 -*/ -func shrinkLargeGroups(digits []int) []int { - // from start of number & ensure i+2 is within bounds of the digits slice - for i := 0; i+2 < len(digits); i++ { - if digits[i] == digits[i+1] && digits[i] == digits[i+2] { - // figure out how many items to remove - removeUpTo := i + 1 - for removeUpTo < len(digits) && digits[i] == digits[removeUpTo] { - removeUpTo++ - } - - // copy the values into a new slice - newSli := make([]int, 0) - for j := 0; j <= i; j++ { - newSli = append(newSli, digits[j]) - } - for removeUpTo < len(digits) { - newSli = append(newSli, digits[removeUpTo]) - removeUpTo++ - } - digits = newSli - } - } - return digits -} - -func isIncreasing(digits []int) bool { - for i := 1; i < len(digits); i++ { - if digits[i] < digits[i-1] { - return false - } - } - return true -} - -func hasDuplicate(digits []int) bool { - for i := 1; i < len(digits); i++ { - if digits[i-1] == digits[i] { - return true - } - } - return false -} diff --git a/2019/day05/part1/main.go b/2019/day05/part1/main.go deleted file mode 100644 index 4e3a0a2..0000000 --- a/2019/day05/part1/main.go +++ /dev/null @@ -1,87 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - input := 1 - step(inputNumbers, 0, input) -} - -// step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func step(sli []int, index, input int) int { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := getOpCodeAndIndexes(sli, index) - - var output, jump int - switch opcode { - // 99: Terminates program - case 99: - fmt.Println("Terminating") - return -1 - case 1: // 1: Add next two paramIndexes, store in third - sli[paramIndexes[2]] = sli[paramIndexes[0]] + sli[paramIndexes[1]] - jump = 4 - case 2: // 2: Multiply next two and store in third - sli[paramIndexes[2]] = sli[paramIndexes[0]] * sli[paramIndexes[1]] - jump = 4 - case 3: // 3: Takes one input and saves it to position of one parameter - sli[paramIndexes[0]] = input - jump = 2 - case 4: // 4: outputs its input value - output = sli[paramIndexes[0]] - fmt.Printf("Opcode 4 output: %v\n", output) - jump = 2 - default: - log.Fatal("Error: unknown opcode: ", opcode) - } - // recursively call itself & jump index value... - return step(sli, index+jump, output) -} - -/* -getOpCodeAndIndexes will parse the instruction at sli[index] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func getOpCodeAndIndexes(sli []int, index int) (int, [3]int) { - instruction := sli[index] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && index+i < len(sli); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 1: // immediate mode, the index itself - paramIndexes[i-1] = index + i - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = sli[index+i] - } - } - - return opcode, paramIndexes -} diff --git a/2019/day05/part2/main.go b/2019/day05/part2/main.go deleted file mode 100644 index 7cbf8f2..0000000 --- a/2019/day05/part2/main.go +++ /dev/null @@ -1,117 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - // inputFile := "3,3,1107,-1,8,3,4,3,99" - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // system ID is 5, that is the input to the intcode computer - input := 5 - step(inputNumbers, 0, input) -} - -// step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func step(sli []int, index, input int) int { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := getOpCodeAndIndexes(sli, index) - - var output int - switch opcode { - case 99: // 99: Terminates program - fmt.Println("Terminating...") - return input - case 1: // 1: Add next two paramIndexes, store in third - sli[paramIndexes[2]] = sli[paramIndexes[0]] + sli[paramIndexes[1]] - return step(sli, index+4, output) - case 2: // 2: Multiply next two and store in third - sli[paramIndexes[2]] = sli[paramIndexes[0]] * sli[paramIndexes[1]] - return step(sli, index+4, output) - case 3: // 3: Takes one input and saves it to position of one parameter - sli[paramIndexes[0]] = input - return step(sli, index+2, output) - case 4: // 4: outputs its input value - output = sli[paramIndexes[0]] - fmt.Printf("Opcode 4 output: %v\n", output) - return step(sli, index+2, output) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if sli[paramIndexes[0]] != 0 { - return step(sli, sli[paramIndexes[1]], output) - } - return step(sli, index+3, output) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if sli[paramIndexes[0]] == 0 { - return step(sli, sli[paramIndexes[1]], output) - } - return step(sli, index+3, output) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if sli[paramIndexes[0]] < sli[paramIndexes[1]] { - sli[paramIndexes[2]] = 1 - } else { - sli[paramIndexes[2]] = 0 - } - return step(sli, index+4, output) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if sli[paramIndexes[0]] == sli[paramIndexes[1]] { - sli[paramIndexes[2]] = 1 - } else { - sli[paramIndexes[2]] = 0 - } - return step(sli, index+4, output) - default: - log.Fatal("Error: unknown opcode: ", opcode) - } - // this should never be called b/c switch statement will always return - return -1 -} - -/* -getOpCodeAndIndexes will parse the instruction at sli[index] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func getOpCodeAndIndexes(sli []int, index int) (int, [3]int) { - instruction := sli[index] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && index+i < len(sli); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 1: // immediate mode, the index itself - paramIndexes[i-1] = index + i - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = sli[index+i] - } - } - - return opcode, paramIndexes -} diff --git a/2019/day06/part1/main.go b/2019/day06/part1/main.go deleted file mode 100644 index 4a84513..0000000 --- a/2019/day06/part1/main.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - input := util.ReadFile("../input.txt") - - // split it into a slice of all strings - inputSliced := strings.Split(input, ",") - - // send the input to a helper function that will return a map {planetName string: planetItOrbits string} - mapOfOrbits := makeGraphDependencyList(inputSliced) - - result := 0 - - // don't care about outer planet value - for _, innerPlanet := range mapOfOrbits { - result += recurseToCOM(mapOfOrbits, innerPlanet) - } - fmt.Println(result) -} - -func makeGraphDependencyList(inputSlice []string) map[string]string { - resultMap := make(map[string]string) - for _, v := range inputSlice { - slicedPlanets := strings.Split(v, ")") - inner, outer := slicedPlanets[0], slicedPlanets[1] - // set the key-value pair on the map/"object" - resultMap[outer] = inner - } - - return resultMap -} - -func recurseToCOM(mapOfOrbits map[string]string, planet string) int { - // start the result at one because triggering this function is using the outerPlanet - // and needs to include that initial orbit from innerPlanet to outerPlanet - result := 1 - for { - planetString, ok := mapOfOrbits[planet] - if ok == true { - result++ - planet = planetString - } else { - break - } - } - - return result -} diff --git a/2019/day06/part2/main.go b/2019/day06/part2/main.go deleted file mode 100644 index 7afa205..0000000 --- a/2019/day06/part2/main.go +++ /dev/null @@ -1,79 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - input := util.ReadFile("../input.txt") - - // split it into a slice of all strings - inputSliced := strings.Split(input, ",") - - // send the input to a helper function that will return a map {planetName string: planetItOrbits string} - mapOfOrbits := makeGraphDependencyList(inputSliced) - - sanOrbitsToCom, youOrbitsToCom := recurseToCOM(mapOfOrbits, "SAN"), recurseToCOM(mapOfOrbits, "YOU") - - // need to find where the two run over the same planet... - // these two variables will store the indexes within the slices, that corresponds to the same planet - var indexOfYouPlanet, indexOfSanPlanet int - - // iterate through the SAN orbits, nest a loop to search for the same planet in the YOU orbits - // a map would have better time complexity... due to lookup time - for indexSan, sanOrbitPlanetString := range sanOrbitsToCom { - // a boolean to track if the planet was found in the you orbit - var foundYouPlanet bool - // iterate through YOU orbits and if the same planet is found, update the indexYou and foundBool - for indexYou, youOrbitPlanetString := range youOrbitsToCom { - if youOrbitPlanetString == sanOrbitPlanetString { - indexOfYouPlanet, foundYouPlanet = indexYou, true - break - } - } - - // if it was found, set the indexOfSanPlanet and stop the looping - if foundYouPlanet { - indexOfSanPlanet = indexSan - break - } - } - - // subtract two for the SAN and YOU planets (they're pointers to a planet, not extra orbits to traverse) - fmt.Println("result is: ", indexOfYouPlanet+indexOfSanPlanet-2) -} - -func makeGraphDependencyList(inputSlice []string) map[string]string { - resultMap := make(map[string]string) - for _, v := range inputSlice { - slicedPlanets := strings.Split(v, ")") - inner, outer := slicedPlanets[0], slicedPlanets[1] - // set the key-value pair on the map/"object" - resultMap[outer] = inner - } - - return resultMap -} - -func recurseToCOM(mapOfOrbits map[string]string, planet string) []string { - // added to handle part2 - planetsToComSlice := make([]string, 0) - - // start the result at one because triggering this function is using the outerPlanet - // and needs to include that initial orbit from innerPlanet to outerPlanet - for { - planetString, ok := mapOfOrbits[planet] - if ok { - // if the planet is found in the map, place the planet on the slice and continue to loop - planetsToComSlice = append(planetsToComSlice, planet) - planet = planetString - } else { - break - } - } - - return planetsToComSlice -} diff --git a/2019/day07/part1/main.go b/2019/day07/part1/main.go deleted file mode 100644 index be201b4..0000000 --- a/2019/day07/part1/main.go +++ /dev/null @@ -1,197 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/algos" - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // Make perms via a util function - perms := algos.PermuteIntSlice([]int{0, 1, 2, 3, 4}) - - // iterate over all perms and run through a single pass of the Amps - // if the final output (from Amp E) is higher, update the highestOutput variable - highestOutput := 0 - for _, perm := range perms { - // initialize 5 computers - ampA := MakeComputer(inputNumbers, perm[0]) - ampB := MakeComputer(inputNumbers, perm[1]) - ampC := MakeComputer(inputNumbers, perm[2]) - ampD := MakeComputer(inputNumbers, perm[3]) - ampE := MakeComputer(inputNumbers, perm[4]) - - // first input (besides phase setting) to Amp A is zero - ampA.Step(0) - ampB.Step(ampA.LastOutput) - ampC.Step(ampB.LastOutput) - ampD.Step(ampC.LastOutput) - ampE.Step(ampD.LastOutput) - - if ampE.LastOutput > highestOutput { - highestOutput = ampE.LastOutput - } - } - - // print highest output found - fmt.Printf("Highest output is %v\n", highestOutput) -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PhaseSetting int // initial input: ID or number used to "prime"/setup the comp - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - LastOutput int // last output from an opcode 4 -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int, PhaseSetting int) Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - PhaseSetting, - puzzleInputCopy, - 0, - 0, - } - - // Prime the computer by running its initial phase setting through it - // This will update the comp's InstructionIndex so it's pointing to the next command - // will also update the PuzzleInput itself via opcode 3's insert - // AND will run the computer until it asks for the next input, _comp is now primed_ - comp.Step(PhaseSetting) - return comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func (comp *Intcode) Step(input int) int { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndIndexes() - - switch opcode { - case 99: // 99: Terminates program - // fmt.Println("Terminating...") - return input - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[paramIndexes[2]] = comp.PuzzleInput[paramIndexes[0]] + comp.PuzzleInput[paramIndexes[1]] - comp.InstructionIndex += 4 - return comp.Step(input) - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[paramIndexes[2]] = comp.PuzzleInput[paramIndexes[0]] * comp.PuzzleInput[paramIndexes[1]] - comp.InstructionIndex += 4 - return comp.Step(input) - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return the LastOutput - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return comp.LastOutput - } - - // otherwise use the input, then recurse with a -1 to signal the initial input has been used - comp.PuzzleInput[paramIndexes[0]] = input - comp.InstructionIndex += 2 - return comp.Step(-1) - case 4: // 4: outputs its input value - // set LastOutput of the computer & log it - comp.LastOutput = comp.PuzzleInput[paramIndexes[0]] - // fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput) - comp.InstructionIndex += 2 - - // continue running until terminates or asks for another input - return comp.Step(input) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[paramIndexes[0]] != 0 { - comp.InstructionIndex = comp.PuzzleInput[paramIndexes[1]] - } else { - comp.InstructionIndex += 3 - } - return comp.Step(input) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[paramIndexes[0]] == 0 { - comp.InstructionIndex = comp.PuzzleInput[paramIndexes[1]] - } else { - comp.InstructionIndex += 3 - } - return comp.Step(input) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[paramIndexes[0]] < comp.PuzzleInput[paramIndexes[1]] { - comp.PuzzleInput[paramIndexes[2]] = 1 - } else { - comp.PuzzleInput[paramIndexes[2]] = 0 - } - comp.InstructionIndex += 4 - return comp.Step(input) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[paramIndexes[0]] == comp.PuzzleInput[paramIndexes[1]] { - comp.PuzzleInput[paramIndexes[2]] = 1 - } else { - comp.PuzzleInput[paramIndexes[2]] = 0 - } - comp.InstructionIndex += 4 - return comp.Step(input) - default: - log.Fatal("Error: unknown opcode: ", opcode) - } - // this should never be called b/c switch statement will always return - return -1 -} - -/* -GetOpCodeAndIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - } - } - - return opcode, paramIndexes -} diff --git a/2019/day07/part2/main.go b/2019/day07/part2/main.go deleted file mode 100644 index 398c9d9..0000000 --- a/2019/day07/part2/main.go +++ /dev/null @@ -1,201 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/algos" - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // Make perms via a util function - perms := algos.PermuteIntSlice([]int{5, 6, 7, 8, 9}) - - // iterate over all perms and run through a single pass of the Amps - // if the final output (from Amp E) is higher, update the highestOutput variable - highestOutput := 0 - for _, perm := range perms { - // initialize 5 computers - ampA := MakeComputer(inputNumbers, perm[0]) - ampB := MakeComputer(inputNumbers, perm[1]) - ampC := MakeComputer(inputNumbers, perm[2]) - ampD := MakeComputer(inputNumbers, perm[3]) - ampE := MakeComputer(inputNumbers, perm[4]) - - // Iterate while the Amps are still running (i.e. not terminated) - for ampA.IsRunning && ampB.IsRunning && ampC.IsRunning && ampD.IsRunning && ampE.IsRunning { - // first input to Amp A is a zero, this is the zero-value of an int! - ampA.Step(ampE.LastOutput) - ampB.Step(ampA.LastOutput) - ampC.Step(ampB.LastOutput) - ampD.Step(ampC.LastOutput) - ampE.Step(ampD.LastOutput) - } - - if ampE.LastOutput > highestOutput { - highestOutput = ampE.LastOutput - } - } - - // print highest output found - fmt.Printf("Highest output is %v\n", highestOutput) -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PhaseSetting int // initial input: ID or number used to "prime"/setup the comp - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - LastOutput int // last output from an opcode 4 - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int, PhaseSetting int) Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - PhaseSetting, - puzzleInputCopy, - 0, - 0, - true, - } - - // Prime the computer by running its initial phase setting through it - // This will update the comp's InstructionIndex so it's pointing to the next command - // will also update the PuzzleInput itself via opcode 3's insert - // AND will run the computer until it asks for the next input, _comp is now primed_ - comp.Step(PhaseSetting) - return comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func (comp *Intcode) Step(input int) { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - switch opcode { - case 99: // 99: Terminates program - // fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - comp.Step(-1) - case 4: // 4: outputs its input value - // set LastOutput of the computer & log it - comp.LastOutput = comp.PuzzleInput[param1] - // fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput) - comp.InstructionIndex += 2 - - // continue running until terminates or asks for another input - comp.Step(input) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - } - } - - return opcode, paramIndexes -} diff --git a/2019/day08/part1/main.go b/2019/day08/part1/main.go deleted file mode 100644 index 7b461e8..0000000 --- a/2019/day08/part1/main.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - input := util.ReadFile("../input.txt") - - charsSlice := strings.Split(input, "") - - layers := makeLayers(charsSlice) - - minIndex := getMinIndex(layers) - - ones, twos := countOnesAndTwos(layers[minIndex]) - - // print solution - fmt.Println(ones * twos) -} - -func makeLayers(charsSlice []string) [][]string { - layers := make([][]string, 0) - // layers are 25x6 = 150 characters - // there are multiple layers of the same size - for i := 0; i*150 < len(charsSlice); i++ { - layers = append(layers, charsSlice[150*i:150*(i+1)]) - } - - return layers -} - -func getMinIndex(layers [][]string) int { - min, minIndex := 150, 0 // start at 150, max length of one of our nested arrays - for index, layerSlice := range layers { - countZeroes := 0 - for _, pixel := range layerSlice { - if pixel == "0" { - countZeroes++ - } - } - // update min and minIndex if countZeroes is less than the min value - if countZeroes < min { - min, minIndex = countZeroes, index - } - } - - return minIndex -} - -func countOnesAndTwos(layer []string) (int, int) { - ones, twos := 0, 0 - // count ones and twos of the layer with the least zeroes - for _, pixel := range layer { - switch pixel { - case "1": - ones++ - case "2": - twos++ - } - } - return ones, twos -} diff --git a/2019/day08/part2/main.go b/2019/day08/part2/main.go deleted file mode 100644 index f0d0f2d..0000000 --- a/2019/day08/part2/main.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file and put it into a slice for each character - pixelString := util.ReadFile("../input.txt") - charsSlice := strings.Split(pixelString, "") - - // make the characers into a 2D slice divided into layers - layers := makeLayers(charsSlice) - - // for each pixel (all layers combined), iterate through all layers of the pixel - // iterate from the top layer until a 1 or 0 is found, set it to that value - final := make([]string, 0) - for i := 0; i < 150; i++ { - final = append(final, "2") - for _, oneLayer := range layers { - if oneLayer[i] == "1" { - final[i] = "0" // zero to view the final image - break - } else if oneLayer[i] == "0" { - final[i] = " " // blank space so it's easier to see the final image - break - } - } - } - - finalString := strings.Join(final, "") - - // Print the six lines (25 characters at a time) individually so the word is legible - for i := 0; i < 6; i++ { - fmt.Println(finalString[i*25 : (i+1)*25]) - } -} - -func makeLayers(charsSlice []string) [][]string { - layers := make([][]string, 0) - // layers are 25x6 = 150 characters - // there are multiple layers of the same size - for i := 0; i*150 < len(charsSlice); i++ { - layers = append(layers, charsSlice[150*i:150*(i+1)]) - } - // fmt.Println(layers) - return layers -} diff --git a/2019/day09/part1/main.go b/2019/day09/part1/main.go deleted file mode 100644 index ed78170..0000000 --- a/2019/day09/part1/main.go +++ /dev/null @@ -1,206 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // initialize a computer with a test mode input of `1` - comp := MakeComputer(inputNumbers, 1) - fmt.Println("BOOST keycode is", comp.LastOutput) -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PhaseSetting int // initial input: ID or number used to "prime"/setup the comp - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - LastOutput int // last output from an opcode 4 - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int, PhaseSetting int) Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - PhaseSetting, - puzzleInputCopy, - 0, - 0, - 0, - true, - } - - // Prime the computer by running its initial phase setting through it - // This will update the comp's InstructionIndex so it's pointing to the next command - // will also update the PuzzleInput itself via opcode 3's insert - // AND will run the computer until it asks for the next input, _comp is now primed_ - comp.Step(PhaseSetting) - return comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func (comp *Intcode) Step(input int) { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - comp.ResizeMemory(param1, param2, param3) - - switch opcode { - case 99: // 99: Terminates program - // fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - comp.Step(-1) - case 4: // 4: outputs its input value - // set LastOutput of the computer & log it - comp.LastOutput = comp.PuzzleInput[param1] - // fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput) - comp.InstructionIndex += 2 - - // continue running until terminates or asks for another input - comp.Step(input) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - comp.Step(input) - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArgSize := sizes[0] - for _, v := range sizes { - if v > maxArgSize { - maxArgSize = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArgSize > len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArgSize) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day09/part2/main.go b/2019/day09/part2/main.go deleted file mode 100644 index 72b3b09..0000000 --- a/2019/day09/part2/main.go +++ /dev/null @@ -1,206 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // initialize a computer with a senor boost input of `2` - comp := MakeComputer(inputNumbers, 2) - fmt.Println("BOOST keycode is", comp.LastOutput) -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PhaseSetting int // initial input: ID or number used to "prime"/setup the comp - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - LastOutput int // last output from an opcode 4 - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int, PhaseSetting int) Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - PhaseSetting, - puzzleInputCopy, - 0, - 0, - 0, - true, - } - - // Prime the computer by running its initial phase setting through it - // This will update the comp's InstructionIndex so it's pointing to the next command - // will also update the PuzzleInput itself via opcode 3's insert - // AND will run the computer until it asks for the next input, _comp is now primed_ - comp.Step(PhaseSetting) - return comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func (comp *Intcode) Step(input int) { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - comp.ResizeMemory(param1, param2, param3) - - switch opcode { - case 99: // 99: Terminates program - // fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - comp.Step(-1) - case 4: // 4: outputs its input value - // set LastOutput of the computer & log it - comp.LastOutput = comp.PuzzleInput[param1] - // fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput) - comp.InstructionIndex += 2 - - // continue running until terminates or asks for another input - comp.Step(input) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - comp.Step(input) - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArgSize := sizes[0] - for _, v := range sizes { - if v > maxArgSize { - maxArgSize = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArgSize > len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArgSize) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day10/part1/main.go b/2019/day10/part1/main.go deleted file mode 100644 index 162be25..0000000 --- a/2019/day10/part1/main.go +++ /dev/null @@ -1,103 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // need to read the input.txt file and split each line into a slice - input := util.ReadFile("../input.txt") - stringSlice := strings.Split(input, "\n") - - // split into a 2D grid with each character - gridSlice := make([][]string, len(stringSlice)) - for i, str := range stringSlice { - gridSlice[i] = strings.Split(str, "") - } - - // will be the final result value - var result int - var finalCoords [2]int - // iterate through entire slice, for each asteroid found, call a helper function - for rowIndex, rowSlice := range gridSlice { - for colIndex, element := range rowSlice { - if element == "#" { - // # are "asteroids", . are empty space - // helper function will return how many asteroids are "findable" from the current asteroid - visibleFromElement := visibleFromAsteroid(gridSlice, rowIndex, colIndex) - - // take max of return of helper function at end of each loop - if result < visibleFromElement { - result = visibleFromElement - finalCoords[0], finalCoords[1] = rowIndex, colIndex - } - } - } - } - - // print out the max found - fmt.Printf("best asteroid for the station: row[%v] col[%v]\n", finalCoords[0], finalCoords[1]) // [13, 11] - fmt.Println("from 13, 11 (y, x)", result) -} - -// helper function will take, x and y coordinates, and the 2D slice -// will create a two maps of floats to booleans -// (one map to cover left side of asteroid, one map to cover right side of asteroid) -// so that anything that is blocked will not be double counted -// and edge case handling for planets vertically above or below the current asteroid -func visibleFromAsteroid(grid [][]string, row, col int) (result int) { - // make the two maps - leftMap, rightMap := make(map[float64]bool), make(map[float64]bool) - // make the two booleans for up and down. zero value is false - var upBool, downBool bool - - // iterate through every element of the grid slices - for rowIndex, rowSlice := range grid { - for colIndex, element := range rowSlice { - // NOTE this control flow is _GROSS_. Better solution in part2 solution - // ensure element is an asteroid & not the asteroid that the helper function is being run on - if element == "#" && !(row == rowIndex && col == colIndex) { - rise := rowIndex - row - run := colIndex - col - - // handle if the found asteroid is directly above the inputted row/col asteroid - if run == 0 { - if rise < 0 { - // check down - // note that up and down are semantically "flipped" due to the 2 row being "above" the 0 row - if !downBool { - downBool = true - result++ - } - } else { - if !upBool { - upBool = true - result++ - } - } - } else { - slope := float64(rise) / float64(run) - // handle left or right map - if run < 0 { - // leftMap - if _, inLeftMap := leftMap[slope]; !inLeftMap { - leftMap[slope] = true - result++ - } - } else { - // rightMap - if _, inRightMap := rightMap[slope]; !inRightMap { - rightMap[slope] = true - result++ - } - } - } - } - } - } - - return result -} diff --git a/2019/day10/part2/main.go b/2019/day10/part2/main.go deleted file mode 100644 index be715b0..0000000 --- a/2019/day10/part2/main.go +++ /dev/null @@ -1,128 +0,0 @@ -package main - -import ( - "fmt" - "math" - "strings" - - "github.com/alexchao26/advent-of-code-go/2019/day10/part2/trig" - "github.com/alexchao26/advent-of-code-go/util" -) - -/* - Overall approach: - - need to make a map of some kind - make it a slice where each element is a struct - - each struct will contain: - x - y - degOffVert float64 (degrees 0 -> 360) - distance float64 - - iterate through the slice of structs - - store the index of the minimum distance - - - remove it from the slice of structs - - if this is the 200th iteration, store the x and y to return at the end - - NOTE there are a limited number of asteroids because of the fixed size of the input, so having a O(200 * n) where n is the number of Asteroids, is not a _terrible_ time complexity -*/ - -// Asteroid data -type Asteroid struct { - x int - y int - degOffVert float64 - distance float64 -} - -func main() { - // read input.txt file, split it into a slice of lines - input := util.ReadFile("../input.txt") - stringSlice := strings.Split(input, "\n") - - // generate 2D grid of each character from stringSlice - inputGrid := make([][]string, len(stringSlice)) - for i, str := range stringSlice { - inputGrid[i] = strings.Split(str, "") - } - - allAsteroids := makeAsteroidsSlice(inputGrid) - - // need to start this just to the left of zero to get that as the first input - lastDegreeUsed := 359.999999 - // to store the last vaporized asteroid to output its coordinates - var lastAsteroid Asteroid - - for i := 0; i < 200; i++ { - // iterate through all of allAsteroids and find the next closest degree - var indexOfAsteroidToDelete int // will be updated by iMin - - // reset the minDegDiff and minDist for each run of the outer loop - minDegDiff, minDist := math.Inf(1), math.Inf(1) // I can use inf now b/c I'm using float64's! - - // iterate over the entire slice of asteroids - for iMin, eAsteroid := range allAsteroids { - // calculate the degrees difference - degDiff := eAsteroid.degOffVert - lastDegreeUsed - if degDiff <= 0 { // account for the diff passing over zero - degDiff += 360 - } - // if this asteroid has a smaller degrees difference, update all min values - if degDiff < minDegDiff { - minDist = eAsteroid.distance - minDegDiff = degDiff - indexOfAsteroidToDelete = iMin - } else if degDiff == minDegDiff && minDist > eAsteroid.distance { - // OR if the degDiff is the same but the distance to 13,11 is smaller - // update just the minDistance and index for this asteroid - minDist = eAsteroid.distance - indexOfAsteroidToDelete = iMin - } - } - - // remove the element at index indexOfAst - // this doesn't maintain order, but I don't care about order right now - lastAsteroid = allAsteroids[indexOfAsteroidToDelete] - - // swap last element to indexToDelete - allAsteroids[indexOfAsteroidToDelete] = allAsteroids[len(allAsteroids)-1] - // re-size slice to effectively pop last element off of slice - allAsteroids = allAsteroids[:len(allAsteroids)-1] - - // update last deg used by adding the diff to it - lastDegreeUsed += minDegDiff - if lastDegreeUsed >= 360 { - // if we pass over 360, subtract 360 - lastDegreeUsed -= 360 - } - } - - // print the last used asteroid - fmt.Println("Last asteroid", lastAsteroid) - // print the github.com/alexchao26/advent-of-code-go-formatted answer - fmt.Println("Advent of code answer: ", lastAsteroid.y*100+lastAsteroid.x) -} - -func makeAsteroidsSlice(grid [][]string) []Asteroid { - result := make([]Asteroid, 0) - - // iterate through the entire grid - for rowIndex, rowSlice := range grid { - for colIndex, element := range rowSlice { - // if an asteroid is found... - if element == "#" && !(rowIndex == 13 && colIndex == 11) { - // calculate the degree and dist - // degree, dist := trig.TangentAndDistance(13, 11, rowIndex, colIndex) - // create an instance of an Asteroid struct and append it to the result slice - ast := Asteroid{ - x: rowIndex, - y: colIndex, - degOffVert: trig.AngleOffVertical(13, 11, rowIndex, colIndex), - distance: trig.Distance(13, 11, rowIndex, colIndex), - } - result = append(result, ast) - } - } - } - - return result -} diff --git a/2019/day10/part2/trig/trig.go b/2019/day10/part2/trig/trig.go deleted file mode 100644 index 6c9a739..0000000 --- a/2019/day10/part2/trig/trig.go +++ /dev/null @@ -1,45 +0,0 @@ -package trig - -import "math" - -/* -AngleOffVertical takes in two 2D points, it calculates the angle -between the line and a vertical line (straight up from origin) -NOTE: "up"/"top" and "down" are lexically flipped b/c of drawing a grid -where 0, 0 is the top left corner and higher numbers physically go DOWN -but lexically increase/go UP 🤦‍♂️ -*/ -func AngleOffVertical(startX, startY, endX, endY int) float64 { - rise := float64(endX) - float64(startX) - run := float64(endY) - float64(startY) - - var angle float64 - // basically a big if/elseif/else block - switch { - case run == 0 && rise < 0: // up - angle = 0 - case run == 0 && rise > 0: // down - angle = 180 - case rise == 0 && run < 0: // left - angle = 270 - case rise == 0 && run > 0: // right - angle = 90 - case rise < 0 && run > 0: // top right - angle = -1 * math.Atan(run/rise) * 180 / math.Pi - case rise > 0 && run > 0: // bottom right - angle = 90 + math.Atan(rise/run)*180/math.Pi - case rise > 0 && run < 0: // bottom left - angle = 180 + -1*math.Atan(run/rise)*180/math.Pi - case rise < 0 && run < 0: // top left - angle = 270 + math.Atan(rise/run)*180/math.Pi - } - - return angle -} - -// Distance calculates the distance between two sets of 2D coordinates via Pythagorean's theorem -func Distance(startX, startY, endX, endY int) float64 { - dx := startX - endX - dy := startY - endY - return math.Sqrt(float64(dx*dx) + float64(dy*dy)) -} diff --git a/2019/day11/part1/main.go b/2019/day11/part1/main.go deleted file mode 100644 index d11e3e7..0000000 --- a/2019/day11/part1/main.go +++ /dev/null @@ -1,269 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // initialize a computer with a senor boost input of `2` - robotBrain := MakeComputer(inputNumbers) - robot := MakeRobot(0, 0) - - for robotBrain.IsRunning { - // get the current color from the robot's map - currentCoords := fmt.Sprintf("%v,%v", robot.x, robot.y) - currentColor := robot.MapCoordsToColor[currentCoords] - robotBrain.Step(currentColor) - - // get outputs from the robot's brain (Intcode) - lenOutputs := len(robotBrain.Outputs) - color := robotBrain.Outputs[lenOutputs-2] - direction := robotBrain.Outputs[lenOutputs-1] - - // "paint"/update robot's Map and move the robot - robot.MapCoordsToColor[currentCoords] = color - robot.MoveRobot(direction) - } - - fmt.Printf("Tiles painted %v\n", len(robot.MapCoordsToColor)) -} - -// Robot struct, x and y are coordinate system based, NOT 2D array 0-indexed -type Robot struct { - x int - y int - Direction string - MapCoordsToColor map[string]int -} - -// MakeRobot holds info on the location and direction of the robot only -func MakeRobot(startX, startY int) *Robot { - return &Robot{ - startX, - startY, - "up", - make(map[string]int), - } -} - -// MoveRobot moves the Robot -func (robot *Robot) MoveRobot(direction int) { - // direction is the same as the output from the robot brain - // i.e. 0 to turn left, 1 to turn right, then step forward 1 space - turnLeft := map[string]string{ - "up": "left", - "left": "down", - "down": "right", - "right": "up", - } - turnRight := map[string]string{ - "up": "right", - "right": "down", - "down": "left", - "left": "up", - } - - if direction == 0 { - robot.Direction = turnLeft[robot.Direction] - } else { - robot.Direction = turnRight[robot.Direction] - } - - switch robot.Direction { - case "up": - robot.y++ - case "down": - robot.y-- - case "left": - robot.x-- - case "right": - robot.x++ - } -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func (comp *Intcode) Step(input int) { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - comp.ResizeMemory(param1, param2, param3) - - switch opcode { - case 99: // 99: Terminates program - fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - comp.Step(-1) - case 4: // 4: outputs its input value - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, comp.PuzzleInput[param1]) - // fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput) - comp.InstructionIndex += 2 - - // continue running until terminates or asks for another input - comp.Step(input) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - comp.Step(input) - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg > len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day11/part2/main.go b/2019/day11/part2/main.go deleted file mode 100644 index 7ed86e8..0000000 --- a/2019/day11/part2/main.go +++ /dev/null @@ -1,390 +0,0 @@ -/* -Intcode struct is defined within this file -Robot struct contains coordinates and robot's directions - - methods can Move the robot based on its brain's (intcode comp.) output -Draw function generates a string to display in terminal - - helper functions remove some whitespace and rotate the grid/matrix -*/ - -package main - -import ( - "fmt" - "log" - "strconv" - "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() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // initialize a computer with a senor boost input of `2` - robotBrain := MakeComputer(inputNumbers) - robot := MakeRobot(0, 0) - - // initialize starting coordinates to a white panel, i.e. 1 - robot.MapCoordsToColor["0,0"] = 1 - - // let robot paint entire map - for robotBrain.IsRunning { - // get the current color from the robot's map - currentCoords := fmt.Sprintf("%v,%v", robot.x, robot.y) - currentColor := robot.MapCoordsToColor[currentCoords] - robotBrain.Step(currentColor) - - // get outputs from the robot's brain (Intcode) - lenOutputs := len(robotBrain.Outputs) - color := robotBrain.Outputs[lenOutputs-2] - direction := robotBrain.Outputs[lenOutputs-1] - - // "paint"/update robot's Map and move the robot - robot.MapCoordsToColor[currentCoords] = color - robot.MoveRobot(direction) - } - - // pass robot.MapCoordsToColor to a "drawing" function - output := Draw(robot.MapCoordsToColor) - fmt.Println(output) -} - -// Draw will return a multiline string using mapCoordsToColor to determine -// which cells should be colored white (1) or black/empty (0) -func Draw(mapCoordsToColor map[string]int) string { - var lowX, highX, lowY, highY int - for key := range mapCoordsToColor { - coords := strings.Split(key, ",") - x, _ := strconv.Atoi(coords[0]) - y, _ := strconv.Atoi(coords[1]) - switch { - case x < lowX: - lowX = x - case x > highX: - highX = x - } - switch { - case y < lowY: - lowY = y - case y > highY: - highY = y - } - } - - // Determine the bounds of the grid - edgeLength := 2 * mathy.MaxInt(-lowY, -lowX, highY, highX) - - grid := make([][]string, edgeLength) - for i := 0; i < edgeLength; i++ { - // each character will initialize as a space character - grid[i] = make([]string, edgeLength) - for j := range grid[i] { - grid[i][j] = " " - } - } - - // Iterate through all coordinates and transcribe x,y onto a 2D grid - // where the math is a little different... - for key, val := range mapCoordsToColor { - // key is string coords - coords := strings.Split(key, ",") - x, _ := strconv.Atoi(coords[0]) - y, _ := strconv.Atoi(coords[1]) - x += edgeLength / 2 - y += edgeLength / 2 - // val is color to paint (1 or 0) - if val == 1 { - grid[x][y] = "#" - } - } - - // trim off due to making the initial grid too large - grid = trim(grid) - // rotate it because of how I coded up the robot's coordinates :/ - grid = algos.RotateStringGrid(grid) - // retrim - grid = trim(grid) - - // combine each layer into an individual string via join w/ empty string - helpMakeFinalString := make([]string, len(grid)) - for i := range helpMakeFinalString { - helpMakeFinalString[i] = strings.Join(grid[i], "") - } - - // join all levels together with newlines - return strings.Join(helpMakeFinalString, "\n") -} - -// helper function for Draw to remove whitespace from overestimating the size -// of the drawing space -func trim(grid [][]string) [][]string { - // remove all empty rows at top and bottom -removeRowsTop: - for i := 0; i < len(grid); { - for j := 0; j < len(grid[i]); j++ { - if grid[i][j] != " " { - break removeRowsTop - } - } - grid = grid[1:] - } - - // remove empty columns on left -removeColsLeft: - for i := 0; i < len(grid[0]); { - for j := 0; j < len(grid); j++ { - if grid[j][i] != " " { - break removeColsLeft - } - } - // if loop hasn't broken out, iterate over first "column" and slice off "0-index" - for j := 0; j < len(grid); j++ { - grid[j] = grid[j][1:] - } - } - - return grid -} - -// Robot struct, x and y are coordinate system based, NOT 2D array 0-indexed -type Robot struct { - x int - y int - Direction string - MapCoordsToColor map[string]int -} - -// MakeRobot holds info on the location and direction of the robot only -func MakeRobot(startX, startY int) *Robot { - return &Robot{ - startX, - startY, - "up", - make(map[string]int), - } -} - -// MoveRobot moves the Robot -func (robot *Robot) MoveRobot(direction int) { - // direction is the same as the output from the robot brain - // i.e. 0 to turn left, 1 to turn right, then step forward 1 space - turnLeft := map[string]string{ - "up": "left", - "left": "down", - "down": "right", - "right": "up", - } - turnRight := map[string]string{ - "up": "right", - "right": "down", - "down": "left", - "left": "up", - } - - if direction == 0 { - robot.Direction = turnLeft[robot.Direction] - } else { - robot.Direction = turnRight[robot.Direction] - } - - switch robot.Direction { - case "up": - robot.y++ - case "down": - robot.y-- - case "left": - robot.x-- - case "right": - robot.x++ - } -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func (comp *Intcode) Step(input int) { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - // Note: need to optimize this to not resize if the params are not being used - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - comp.Step(-1) - case 4: // 4: outputs its input value - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, comp.PuzzleInput[param1]) - // fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput) - comp.InstructionIndex += 2 - - // continue running until terminates or asks for another input - comp.Step(input) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - comp.Step(input) - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day12/part1/main.go b/2019/day12/part1/main.go deleted file mode 100644 index fc10cf2..0000000 --- a/2019/day12/part1/main.go +++ /dev/null @@ -1,103 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -// Moon stores coordinates and velocities of a moon -type Moon struct { - x, y, z, dx, dy, dz int -} - -func main() { - input := util.ReadFile("../input.txt") - stringSlice := strings.Split(input, "\n") - - // need to set x y z, dx, dy, dz of each of the starting moons - sliceMoons := makeMoonSlice(stringSlice) - - // iterate through for each step - for i := 0; i < 1000; i++ { - // iterate through all the moons in the slice - for start := 0; start < len(sliceMoons); start++ { - // update velocities dx dy dz - // requires iterating through all of the moons again... - for restIndex := start + 1; restIndex < len(sliceMoons); restIndex++ { - if sliceMoons[start].x < sliceMoons[restIndex].x { - sliceMoons[start].dx++ - sliceMoons[restIndex].dx-- - } else if sliceMoons[start].x > sliceMoons[restIndex].x { - sliceMoons[start].dx-- - sliceMoons[restIndex].dx++ - } - if sliceMoons[start].y < sliceMoons[restIndex].y { - sliceMoons[start].dy++ - sliceMoons[restIndex].dy-- - } else if sliceMoons[start].y > sliceMoons[restIndex].y { - sliceMoons[start].dy-- - sliceMoons[restIndex].dy++ - } - if sliceMoons[start].z < sliceMoons[restIndex].z { - sliceMoons[start].dz++ - sliceMoons[restIndex].dz-- - } else if sliceMoons[start].z > sliceMoons[restIndex].z { - sliceMoons[start].dz-- - sliceMoons[restIndex].dz++ - } - } - } - - // then update coordinates x y z - for i2, e := range sliceMoons { - sliceMoons[i2].x += e.dx - sliceMoons[i2].y += e.dy - sliceMoons[i2].z += e.dz - } - } - - // get final kinetic energy - fmt.Println(kinetic(sliceMoons)) -} - -func makeMoonSlice(stringSlice []string) []Moon { - sliceMoons := make([]Moon, 0) - for _, str := range stringSlice { - xStart := strings.Index(str, "x=") + 2 - xEnd := strings.Index(str, ",") - yStart := xEnd + 4 - zStart := strings.Index(str, "z=") + 2 - yEnd := zStart - 4 - zEnd := len(str) - 1 - - x := str[xStart:xEnd] - y := str[yStart:yEnd] - z := str[zStart:zEnd] - - intX, _ := strconv.Atoi(x) - intY, _ := strconv.Atoi(y) - intZ, _ := strconv.Atoi(z) - sliceMoons = append(sliceMoons, Moon{intX, intY, intZ, 0, 0, 0}) - } - return sliceMoons -} - -// get total "kinetic energy" of a slice of Moons -func kinetic(moons []Moon) (result int) { - for _, e := range moons { - sumXYZ := abs(e.x) + abs(e.y) + abs(e.z) - velXYZ := abs(e.dx) + abs(e.dy) + abs(e.dz) - result += (sumXYZ * velXYZ) - } - return result -} - -func abs(value int) int { - if value < 0 { - return value * -1 - } - return value -} diff --git a/2019/day12/part2/main.go b/2019/day12/part2/main.go deleted file mode 100644 index f9b86f6..0000000 --- a/2019/day12/part2/main.go +++ /dev/null @@ -1,199 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -// Moon stores coordinates and velocities of a moon -type Moon struct { - x, y, z, dx, dy, dz int -} -type oneDim struct { - m, dm int -} - -func main() { - input := util.ReadFile("../input.txt") - stringSlice := strings.Split(input, "\n") - - // need to set x y z, dx, dy, dz of each of the starting moons - sliceMoons := makeMoonSlice(stringSlice) - // fmt.Println(sliceMoons) - - // manually make the three dimension slices, to be passed into stringify & iterate helper functions - xDim := make([]oneDim, 0) - yDim := make([]oneDim, 0) - zDim := make([]oneDim, 0) - - for _, m := range sliceMoons { - xDim = append(xDim, oneDim{m.x, m.dx}) - yDim = append(yDim, oneDim{m.y, m.dy}) - zDim = append(zDim, oneDim{m.z, m.dz}) - } - - // fmt.Println(xDim, yDim, zDim) - - // "stringify" them so they are easy to compare later - initialX, initialY, initialZ := stringifyOneDim(xDim), stringifyOneDim(yDim), stringifyOneDim(zDim) - - // find the number of steps for each dimension to reach it's initial position & velocity - xSteps, ySteps, zSteps := iterate(xDim, initialX), iterate(yDim, initialY), iterate(zDim, initialZ) - // fmt.Println(xSteps, ySteps, zSteps) - - // print the final least common multiple of the three number of steps - fmt.Println(lcm(xSteps, ySteps, zSteps)) - fmt.Println(lcm2([]int{xSteps, ySteps, zSteps})) -} - -// helper function to take the slice of strings and return a slice of Moon structs -func makeMoonSlice(stringSlice []string) []Moon { - sliceMoons := make([]Moon, 0) - for _, str := range stringSlice { - xStart := strings.Index(str, "x=") + 2 - xEnd := strings.Index(str, ",") - yStart := xEnd + 4 - zStart := strings.Index(str, "z=") + 2 - yEnd := zStart - 4 - zEnd := len(str) - 1 - - x := str[xStart:xEnd] - y := str[yStart:yEnd] - z := str[zStart:zEnd] - - intX, _ := strconv.Atoi(x) - intY, _ := strconv.Atoi(y) - intZ, _ := strconv.Atoi(z) - sliceMoons = append(sliceMoons, Moon{intX, intY, intZ, 0, 0, 0}) - } - return sliceMoons -} - -// helper function that updates the velocity then coordinate of a slice of oneDim structs -func updateVelThenCoords(sliceOneDim []oneDim) { - for start := 0; start < len(sliceOneDim); start++ { - // update velocity - // requires iterating through all of the moons again... - for restIndex := start + 1; restIndex < len(sliceOneDim); restIndex++ { - if sliceOneDim[start].m < sliceOneDim[restIndex].m { - sliceOneDim[start].dm++ - sliceOneDim[restIndex].dm-- - } else if sliceOneDim[start].m > sliceOneDim[restIndex].m { - sliceOneDim[start].dm-- - sliceOneDim[restIndex].dm++ - } - } - } - - // then update coordinates x y z - for i2, e := range sliceOneDim { - sliceOneDim[i2].m += e.dm - } -} - -// helper function that will stringify a slice of oneDims to compare its values to another -func stringifyOneDim(sliceOneDim []oneDim) (result string) { - for _, m := range sliceOneDim { - result += strconv.Itoa(m.m) + "," - result += strconv.Itoa(m.dm) + "," - } - return result -} - -// helper function that will return the number of steps until the initial state is reached -// uses string comparison and the stringifyOneDim helper function -func iterate(dims []oneDim, initialString string) int { - for i := 0; ; i++ { - updateVelThenCoords(dims) - if stringifyOneDim(dims) == initialString { - return i + 1 - } - } -} - -// helper function that returns the least common multiple of three integers -func lcm(x, y, z int) int { - pFactX, pFactY, pFactZ := primeFactorization(x), primeFactorization(y), primeFactorization(z) - fmt.Println(pFactX, pFactY, pFactZ) - - ans := 1 - - // multiple by every value in every slice, but do not count duplicates - for i, j, k := 0, 0, 0; i < len(pFactX) || j < len(pFactY) || k < len(pFactZ); { - if i < len(pFactX) { - ans *= pFactX[i] - if pFactX[i] == pFactY[j] { - j++ - } - if pFactX[i] == pFactZ[k] { - k++ - } - i++ - } else if j < len(pFactY) { - ans *= pFactY[j] - if pFactY[j] == pFactZ[k] { - k++ - } - j++ - } else if k < len(pFactZ) { - ans *= pFactZ[k] - k++ - } - } - - return ans -} - -func primeFactorization(num int) []int { - ans := make([]int, 0) - for i := 2; num > 1; { - if num%i == 0 { - ans = append(ans, i) - num /= i - } else { - i++ - } - } - return ans -} - -// Better solution to finding a least common multiple -func lcm2(sNum []int) int { - ans := 1 - - // start iterating from the number 2 - for i := 2; ; { - // booleans to track if a number has been added to the ans (LCM) - // and if all of the values in the slice are 1, in which case we are done - changeMade, allOnes := false, true - // iterate over all elements in the sNum slice - for index, num := range sNum { - if num%i == 0 && !changeMade { - changeMade = true // update this boolean flag - ans *= i // increment answer - sNum[index] /= i // need to use this notation because num is pass by value - // do not increment i - } else if num%i == 0 { - // need to divide the element, but not increment ans (same prime factor as a previous element) - sNum[index] /= i - } - - // if any of the values in the slice are not one, flip the allOnes flag to false - // to indicate that we're not done with our outer loop - if num != 1 { - allOnes = false - } - } - - // if all values in sNum are 1, we're done & can return the answer - if allOnes { - return ans - } else if !changeMade { - // if a change was not made & not all values are one, increment i - i++ - } - } -} diff --git a/2019/day13/part1/main.go b/2019/day13/part1/main.go deleted file mode 100644 index 4eda582..0000000 --- a/2019/day13/part1/main.go +++ /dev/null @@ -1,269 +0,0 @@ -/* -Intcode struct is defined within this file -Robot struct contains coordinates and robot's directions - - methods can Move the robot based on its brain's (intcode comp.) output -Draw function generates a string to display in terminal - - helper functions remove some whitespace and rotate the grid/matrix -*/ - -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // initialize a computer - comp := MakeComputer(inputNumbers) - - // no input asked for in this computer, so just fire it off once (it will terminate & not ask for an input) - comp.Step(0) - - // initialize game - game := MakeGame(comp.Outputs) - - // iterate through screen and find number of block tiles (i.e. a 2) - var blocks int - for _, row := range game.screen { - for _, tileID := range row { - if tileID == 2 { - blocks++ - } - } - } - - fmt.Println("Number of blocks", blocks) -} - -// Game stores the screen appearance of the game -type Game struct { - screen [][]int -} - -// MakeGame returns an instance of a Game struct -func MakeGame(setupInstructions []int) Game { - // determine the size of the screen, i.e. iterate through all instructions 3 at a time and find - // the max x (2nd of triplet) and max y (3rd of triplet) - // note: assuming all x and y values are positive, i.e. drawing down & right from top left corner - var maxX, maxY int - for i := 0; i+2 < len(setupInstructions); i += 3 { - x := setupInstructions[i] - y := setupInstructions[i+1] - if maxX < x { - maxX = x - } - if maxY < y { - maxY = y - } - } - - // make screen, x is distance from left, y is distance from top, so Y is the number of rows - // X is number of columns - screen := make([][]int, maxY+1) - for i := range screen { - screen[i] = make([]int, maxX+1) - } - - // fill screen - for i := 0; i+2 < len(setupInstructions); i += 3 { - fromLeft, fromTop, tileID := setupInstructions[i], setupInstructions[i+1], setupInstructions[i+2] - screen[fromTop][fromLeft] = tileID - } - - return Game{screen} -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func (comp *Intcode) Step(input int) { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - // Note: need to optimize this to not resize if the params are not being used - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - comp.Step(-1) - case 4: // 4: outputs its input value - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, comp.PuzzleInput[param1]) - // fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput) - comp.InstructionIndex += 2 - - // continue running until terminates or asks for another input - comp.Step(input) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - comp.Step(input) - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day13/part2/main.go b/2019/day13/part2/main.go deleted file mode 100644 index f4fff4c..0000000 --- a/2019/day13/part2/main.go +++ /dev/null @@ -1,327 +0,0 @@ -/* -Intcode struct is defined within this file -Game struct maintains information about the ball and paddle coordinates & state of the screen -NOTE: the "computer" and "game" are decoupled in this solution which is probably not _ideal_, -NOTE but reusing the old code, it was simpler like this - -NOTE: The "hold for an input" value was changed from -1 to -2 in the Intcode computer -*/ - -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // set first puzzle input value to 2 to "play the game for free" - inputNumbers[0] = 2 - - // initialize a computer - comp := MakeComputer(inputNumbers) - - // Get the setup Outputs for the game screen - // NOTE inputting -2 because this is the input that "holds" the computer when it's asking for an input - comp.Step(-2) - - // initialize game - game := MakeGame(comp.Outputs) - - // loop while the intcode computer is asking for an input - for comp.IsRunning { - // clear previous outputs of computer - comp.Outputs = []int{} - - // Find joystick input based on ball vs paddle x coord: 0 (zero value), -1 (left) or 1 (right) - var joystickInput int - if game.ballX < game.paddleX { - joystickInput = -1 - } else if game.ballX > game.paddleX { - joystickInput = 1 - } - - // move joystick by giving computer input - comp.Step(joystickInput) - - // handle all trios of outputs from the computer - for i := 0; i < len(comp.Outputs); i += 3 { - game.HandleOutput(comp.Outputs[i], comp.Outputs[i+1], comp.Outputs[i+2]) - } - - // // Uncomment to print board at every "step" - // for _, level := range game.screen { - // // pretty print game screen - // var strLevel string - // for _, tileID := range level { - // if tileID == 0 { - // strLevel += " " - // } else { - // strLevel += strconv.Itoa(tileID) - // } - // } - // fmt.Println(strLevel) - // } - // fmt.Println("") // print an extra line to separate out the new game screens - // time.Sleep(time.Millisecond * 10) - } - - fmt.Println("Final score", game.score) -} - -// Game stores the screen appearance of the game -type Game struct { - screen [][]int - score int - ballX, ballY int - paddleX, paddleY int -} - -// MakeGame returns an instance of a Game struct -func MakeGame(setupInstructions []int) Game { - // determine the size of the screen, i.e. iterate through all instructions 3 at a time and find - // the max x (2nd of triplet) and max y (3rd of triplet) - // note: assuming all x and y values are positive, i.e. drawing down & right from top left corner - var maxX, maxY int - for i := 0; i+2 < len(setupInstructions); i += 3 { - x := setupInstructions[i] - y := setupInstructions[i+1] - if x < 0 || y < 0 { - continue - } - if maxX < x { - maxX = x - } - if maxY < y { - maxY = y - } - } - - // make screen, x is distance from left, y is distance from top, so Y is the number of rows - // X is number of columns. +1 to convert max index to length of slice - screen := make([][]int, maxY+1) - for i := range screen { - screen[i] = make([]int, maxX+1) - } - - // initialize Game - game := Game{screen, 0, 0, 0, 0, 0} - - // fill screen with initial outputs - for i := 0; i+2 < len(setupInstructions); i += 3 { - game.HandleOutput(setupInstructions[i], setupInstructions[i+1], setupInstructions[i+2]) - } - - return game -} - -// HandleOutput handles one trio of outputs from intcode -func (game *Game) HandleOutput(fromLeft, fromTop, tileID int) { - if fromLeft == -1 && fromTop == 0 { - game.score = tileID - } else { - game.screen[fromTop][fromLeft] = tileID - if tileID == 3 { - game.paddleX = fromLeft - game.paddleY = fromTop - } - if tileID == 4 { - game.ballX = fromLeft - game.ballY = fromTop - } - } -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func (comp *Intcode) Step(input int) { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - // Note: need to optimize this to not resize if the params are not being used - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -2 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - comp.Step(-2) - case 4: // 4: outputs its input value - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, comp.PuzzleInput[param1]) - // fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput) - comp.InstructionIndex += 2 - - // continue running until terminates or asks for another input - comp.Step(input) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - comp.Step(input) - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day14/part1/main.go b/2019/day14/part1/main.go deleted file mode 100644 index 03894f9..0000000 --- a/2019/day14/part1/main.go +++ /dev/null @@ -1,103 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - input := util.ReadFile("../input.txt") - stoichiometryGraph := makeDependencyGraph(strings.Split(input, "\n")) - - neededChemicals := map[string]int{"FUEL": 1} - - // fmt.Println(stoichiometryGraph, neededChemicals) - - // while the neededChemicals map has positive values for keys besides "ORE" - for !isOnlyOres(neededChemicals) { - // iterate through neededChemicals - for neededChemical, quantityNeeded := range neededChemicals { - // if a positive value is found for a neededChemical besides "ORE" - if quantityNeeded > 0 && neededChemical != "ORE" { - // find its reaction in the stoichiometryGraph - reactionStoichiometry := stoichiometryGraph[neededChemical] - - // determine the number of times the reactionStoichiometry must be run - timesToRun := quantityNeeded / reactionStoichiometry[neededChemical] - if quantityNeeded%reactionStoichiometry[neededChemical] > 0 { - timesToRun++ - } - - // decrement all of the values in neededChemicals map with this reaction's details (* timesToRun) - for reactionChemical, chemicalStoich := range reactionStoichiometry { - neededChemicals[reactionChemical] -= chemicalStoich * timesToRun - } - } - } - } - - // Print final output - fmt.Println("ORE needed", neededChemicals["ORE"]) -} - -// Creates a graph that maps the products name to its full reaction stoichiometry -func makeDependencyGraph(reactions []string) map[string]map[string]int { - graph := make(map[string]map[string]int) - - for _, reaction := range reactions { - product, reactionStoichiometry := parseReaction(reaction) - graph[product] = reactionStoichiometry - } - - return graph -} - -// parseReaction takes in a line of the input i.e. a reaction in a string -// parses all its details and returns the generated product as a string -// and a map of all products to the quantity used/produced by the reaction -// for produced chemicals, map value will be > 0, inputs will be < 0 -func parseReaction(reaction string) (string, map[string]int) { - reactionStoichiometry := make(map[string]int) - - splitByArrow := strings.Split(reaction, " => ") - productStr := splitByArrow[1] - reactantsStr := splitByArrow[0] - - // handle product - productQty, productName := parseQtyAndName(productStr) - - reactionStoichiometry[productName] = productQty - - // split reactants via comma first - reactantsSli := strings.Split(reactantsStr, ", ") - for _, str := range reactantsSli { - reactantQuantity, reactantName := parseQtyAndName(str) - reactionStoichiometry[reactantName] = -1 * reactantQuantity - } - - return productName, reactionStoichiometry -} - -// parse an inputted string of the for " " and return the int & string -func parseQtyAndName(input string) (int, string) { - split := strings.Split(input, " ") - - quantity, _ := strconv.Atoi(split[0]) - name := split[1] - - return quantity, name -} - -// helper function to determine if the neededChemicals graph is "complete" -// it is complete if there only positive value is for the chemical "ORE" -func isOnlyOres(neededChemicals map[string]int) bool { - for key, val := range neededChemicals { - if key != "ORE" && val > 0 { - return false - } - } - return true -} diff --git a/2019/day14/part2/main.go b/2019/day14/part2/main.go deleted file mode 100644 index fad08ce..0000000 --- a/2019/day14/part2/main.go +++ /dev/null @@ -1,116 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -// const will be comparable to any int type? -const trillion int = 1000000000000 - -func main() { - input := util.ReadFile("../input.txt") - stoichiometryGraph := makeDependencyGraph(strings.Split(input, "\n")) - - // give the brute force algo a big headstart? 1 million fuels to start? - var fuelMade int = 1000000 - neededChemicals := map[string]int{"FUEL": fuelMade} - - // brute force this while the ore count is below 1 trillion - for neededChemicals["ORE"] <= trillion { - neededChemicals["FUEL"]++ - // while the neededChemicals map has positive values for keys besides "ORE" - for !isOnlyOres(neededChemicals) { - // iterate through neededChemicals - for neededChemical, quantityNeeded := range neededChemicals { - // if a positive value is found for a neededChemical besides "ORE" - if quantityNeeded > 0 && neededChemical != "ORE" { - // find its reaction in the stoichiometryGraph - reactionStoichiometry := stoichiometryGraph[neededChemical] - - // determine the number of times the reactionStoichiometry must be run - timesToRun := quantityNeeded / reactionStoichiometry[neededChemical] - if quantityNeeded%reactionStoichiometry[neededChemical] > 0 { - timesToRun++ - } - - // decrement all of the values in neededChemicals map with this reaction's details (* timesToRun) - for reactionChemical, chemicalStoich := range reactionStoichiometry { - neededChemicals[reactionChemical] -= chemicalStoich * timesToRun - } - } - } - } - - // if you don't exceed the 1 trillion ORE, increment fuelMade - if neededChemicals["ORE"] < trillion { - fuelMade++ - } - fmt.Println("Making FUEL... Remaining ORE:", trillion-neededChemicals["ORE"]) - } - - // Print final output - fmt.Println("\nFinal amount of FUEL", fuelMade) -} - -// Creates a graph that maps the products name to its full reaction stoichiometry -func makeDependencyGraph(reactions []string) map[string]map[string]int { - graph := make(map[string]map[string]int) - - for _, reaction := range reactions { - product, reactionStoichiometry := parseReaction(reaction) - graph[product] = reactionStoichiometry - } - - return graph -} - -// parseReaction takes in a line of the input i.e. a reaction in a string -// parses all its details and returns the generated product as a string -// and a map of all products to the quantity used/produced by the reaction -// for produced chemicals, map value will be > 0, inputs will be < 0 -func parseReaction(reaction string) (string, map[string]int) { - reactionStoichiometry := make(map[string]int) - - splitByArrow := strings.Split(reaction, " => ") - productStr := splitByArrow[1] - reactantsStr := splitByArrow[0] - - // handle product - productQty, productName := parseQtyAndName(productStr) - - reactionStoichiometry[productName] = productQty - - // split reactants via comma first - reactantsSli := strings.Split(reactantsStr, ", ") - for _, str := range reactantsSli { - reactantQuantity, reactantName := parseQtyAndName(str) - reactionStoichiometry[reactantName] = -1 * reactantQuantity - } - - return productName, reactionStoichiometry -} - -// parse an inputted string of the for " " and return the int & string -func parseQtyAndName(input string) (int, string) { - split := strings.Split(input, " ") - - quantity, _ := strconv.Atoi(split[0]) - name := split[1] - - return quantity, name -} - -// helper function to determine if the neededChemicals graph is "complete" -// it is complete if there only positive value is for the chemical "ORE" -func isOnlyOres(neededChemicals map[string]int) bool { - for key, val := range neededChemicals { - if key != "ORE" && val > 0 { - return false - } - } - return true -} diff --git a/2019/day15/part1/main.go b/2019/day15/part1/main.go deleted file mode 100644 index 4735b07..0000000 --- a/2019/day15/part1/main.go +++ /dev/null @@ -1,429 +0,0 @@ -package main - -import ( - "fmt" - "log" - "math" - "strconv" - "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() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - robot := MakeRobot(inputNumbers) - - // fire off recursive move function to populate the robot's floorDetails property - robot.RecursiveMove() - - // make grid from the map of coordinates to floor types - grid := Draw(robot.floorDetails) - - // find the origin coordinates to know where to start recursive searcher from, it was tagged with a 5 - var originX, originY int - for y, row := range grid { - for x, tileType := range row { - if tileType == 5 { - originX = x - originY = y - break - } - } - } - - shortestLength := findShortestLength(grid, originX, originY) - fmt.Println("Shortest length is", shortestLength) -} - -// Robot struct to maintain detail's on the Robot's coordinates, path -type Robot struct { - fromTop, fromLeft int - floorDetails map[string]int // maps coordinates and type of tile (0 == wall, 1 == path, 2 == oxygen) - computer *Intcode -} - -// MakeRobot returns an instance of a Robot -func MakeRobot(intcodeInput []int) *Robot { - return &Robot{ - 0, - 0, - map[string]int{"0,0": 5}, // mark the origin specially with a 5 - MakeComputer(intcodeInput), - } -} - -var backtrack map[int]int = map[int]int{ - 1: 2, // north (1), south (2) - 2: 1, - 3: 4, // west (3), east(4) - 4: 3, -} - -// dx is the difference to add when traveling in the given direction -// i.e. add 0 for north and south, for west decrement 1, for east add 1 -var dx map[int]int = map[int]int{ - 1: 0, - 2: 0, - 3: -1, - 4: 1, -} - -// dy is the vertical distance traveled -var dy map[int]int = map[int]int{ - 1: 1, - 2: -1, - 3: 0, - 4: 0, -} - -// RecursiveMove will populate a robot's floor details property by traveling in all directions -// and -func (robot *Robot) RecursiveMove() { - for i := 1; i <= 4; i++ { - // if next coordinates have already been detailed, skip all calculations - nextCoords := fmt.Sprintf("%v,%v", robot.fromTop+dy[i], robot.fromLeft+dx[i]) - - if robot.floorDetails[nextCoords] == 0 { - robot.computer.Step(i) - computerOutput := robot.computer.Outputs[len(robot.computer.Outputs)-1] - - switch computerOutput { - case 0: // hit a wall, do not recurse - // update robot's wall coords to include the wall - // note representing walls with a -1 to avoid the zero value detection - robot.floorDetails[nextCoords] = -1 - case 1, 2: // walked and hit the O2 tank or not - // update floorDetails - robot.floorDetails[nextCoords] = computerOutput - - // continue to walk the robot. walk the robot into the nextCoords spot - robot.fromLeft += dx[i] - robot.fromTop += dy[i] - - // recurse - robot.RecursiveMove() - - // backtrack so the robot walks in the remainder of directions from this output - robot.fromLeft -= dx[i] - robot.fromTop -= dy[i] - // backtrack the computer - robot.computer.Step(backtrack[i]) - } - } - } -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) *Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return &comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func (comp *Intcode) Step(input int) { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - // Note: need to optimize this to not resize if the params are not being used - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - comp.Step(-1) - case 4: // 4: outputs its input value - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, comp.PuzzleInput[param1]) - // fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput) - comp.InstructionIndex += 2 - - // continue running until terminates or asks for another input - comp.Step(input) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - comp.Step(input) - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} - -// Draw was copied from day11. It converts a map of points mapped from a (0,0) origin to a 2D grid -// The origin loses its reference... -func Draw(mapCoordsToType map[string]int) [][]int { - var lowX, highX, lowY, highY int - for key := range mapCoordsToType { - coords := strings.Split(key, ",") - x, _ := strconv.Atoi(coords[0]) - y, _ := strconv.Atoi(coords[1]) - switch { - case x < lowX: - lowX = x - case x > highX: - highX = x - } - switch { - case y < lowY: - lowY = y - case y > highY: - highY = y - } - } - - // Determine the bounds of the grid - edgeLength := 2 * mathy.MaxInt(-lowY, -lowX, highY, highX) - - grid := make([][]int, edgeLength) - for i := 0; i < edgeLength; i++ { - // each character will initialize as a space character - grid[i] = make([]int, edgeLength) - } - - // Iterate through all coordinates and transcribe x,y onto a 2D grid - // where the math is a little different... - for key, val := range mapCoordsToType { - // key is string coords - coords := strings.Split(key, ",") - x, _ := strconv.Atoi(coords[0]) - y, _ := strconv.Atoi(coords[1]) - x += edgeLength / 2 - y += edgeLength / 2 - // val is color to paint (1 or 0) - if val != -1 { - grid[x][y] = val - } - } - - // trim off due to making the initial grid too large - grid = trim(grid) - // rotate it because of how I coded up the robot's coordinates :/ - grid = algos.RotateIntGrid(grid) - // retrim - grid = trim(grid) - - return grid -} - -// helper function for Draw to remove whitespace from overestimating the size -// of the drawing space -func trim(grid [][]int) [][]int { - // remove all empty rows at top and bottom -removeRowsTop: - for i := 0; i < len(grid); { - for j := 0; j < len(grid[i]); j++ { - if grid[i][j] != 0 { - break removeRowsTop - } - } - grid = grid[1:] - } - - // remove empty columns on left -removeColsLeft: - for i := 0; i < len(grid[0]); { - for j := 0; j < len(grid); j++ { - if grid[j][i] != 0 { - break removeColsLeft - } - } - // if loop hasn't broken out, iterate over first "column" and slice off "0-index" - for j := 0; j < len(grid); j++ { - grid[j] = grid[j][1:] - } - } - - return grid -} - -// findShortestLength is a (fairly) standard brute force searching algorithm that will return the shortest path from origin to the O2 tank (cell with a 2) -func findShortestLength(grid [][]int, startX, startY int) int { - shortestLength := math.MaxInt32 - - var recurse func(x, y, pathLength int) - recurse = func(x, y, pathLength int) { - if grid[y][x] == 2 { - if pathLength < shortestLength { - shortestLength = pathLength - } - return - } - - for i := 1; i <= 4; i++ { - nextX, nextY := x+dx[i], y+dy[i] - // if it is inbounds and the next coordinate is not a wall or something already traversed - inBounds := nextX >= 0 && nextX < len(grid[0]) && nextY >= 0 && nextY < len(grid) - if inBounds && grid[nextY][nextX] != 0 { - oldVal := grid[y][x] - - // mark as "seen" by making it a "fake" wall - grid[y][x] = 0 - - // recurse into the next coordinate - recurse(nextX, nextY, pathLength+1) - - // undo fake wall - grid[y][x] = oldVal - } - } - } - - recurse(startX, startY, 0) - return shortestLength -} diff --git a/2019/day15/part2/main.go b/2019/day15/part2/main.go deleted file mode 100644 index f30af07..0000000 --- a/2019/day15/part2/main.go +++ /dev/null @@ -1,455 +0,0 @@ -/* -Intcode struct is defined within this file -Robot struct houses an Intcode computer and its RecursiveMove method populates a map of - coordinates to the floor type (-1: wall, 1: hallway, 2: O2 tank, 5: origin) - That map is converted into a 2D grid (slice) - - The shortest length is no longer needed - - Two functions added that determine if the space is full of O2 and to spreadOxygen for one minute -*/ - -package main - -import ( - "fmt" - "log" - "strconv" - "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() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - robot := MakeRobot(inputNumbers) - - // fire off recursive move function to populate the robot's floorDetails property - robot.RecursiveMove() - - // make grid from the map of coordinates to floor types - grid := Draw(robot.floorDetails) - - // overwrite the origin with a 1 (open floor space) - for y, row := range grid { - for x, tileType := range row { - if tileType == 5 { - grid[y][x] = 1 - } - } - } - - // while the grid is not full of oxygen, spread oxygen and increment minutes - var minutes int - for !isFullOfOxygen(grid) { - spreadOxygen(grid) - minutes++ - } - - fmt.Println("Minutes elapsed", minutes) -} - -func isFullOfOxygen(grid [][]int) bool { - for _, row := range grid { - for _, tile := range row { - // if there is a hallway space that is not filled with O2, return false - if tile == 1 { - return false - } - } - } - // if entire looping passes, return true - return true -} - -var dRow []int = []int{0, 0, -1, 1} -var dCol []int = []int{-1, 1, 0, 0} - -// spreadOxygen will spread all oxygen to one neighboring cell -// returns boolean true if O2 has not spread everywhere (i.e. run again), false if O2 is everywhere -func spreadOxygen(grid [][]int) { - // traverse through grid and mark all cells that are a 1 and have a neighboring 2 - // tag with -1 - for i := 0; i < len(grid); i++ { - for j := 0; j < len(grid[0]); j++ { - if grid[i][j] == 1 { - // traverse around neighbors - for d := 0; d < 4; d++ { - neighborRow := i + dRow[d] - neighborCol := j + dCol[d] - inBounds := neighborRow >= 0 && neighborRow < len(grid) && neighborCol >= 0 && neighborCol < len(grid[0]) - // if a neighboring cell is a 2 (i.e. filled with oxygen), then mark this cell - if inBounds && grid[neighborRow][neighborCol] == 2 { - grid[i][j] = -1 - break - } - } - } - } - } - - // then iterate through again changing all -1's to a 2 - for i := 0; i < len(grid); i++ { - for j := 0; j < len(grid); j++ { - if grid[i][j] == -1 { - grid[i][j] = 2 - } - } - } -} - -// Robot struct to maintain detail's on the Robot's coordinates, path -type Robot struct { - fromTop, fromLeft int - floorDetails map[string]int // maps coordinates and type of tile (0 == wall, 1 == path, 2 == oxygen) - computer *Intcode -} - -// MakeRobot returns an instance of a Robot -func MakeRobot(intcodeInput []int) *Robot { - return &Robot{ - 0, - 0, - map[string]int{"0,0": 5}, // mark the origin specially with a 5 - MakeComputer(intcodeInput), - } -} - -var backtrack map[int]int = map[int]int{ - 1: 2, // north (1), south (2) - 2: 1, - 3: 4, // west (3), east(4) - 4: 3, -} - -// dx is the difference to add when traveling in the given direction -// i.e. add 0 for north and south, for west decrement 1, for east add 1 -var dx map[int]int = map[int]int{ - 1: 0, - 2: 0, - 3: -1, - 4: 1, -} - -// dy is the vertical distance traveled -var dy map[int]int = map[int]int{ - 1: 1, - 2: -1, - 3: 0, - 4: 0, -} - -// RecursiveMove will populate a robot's floor details property by traveling in all directions -// and -func (robot *Robot) RecursiveMove() { - for i := 1; i <= 4; i++ { - // if next coordinates have already been detailed, skip all calculations - nextCoords := fmt.Sprintf("%v,%v", robot.fromTop+dy[i], robot.fromLeft+dx[i]) - - if robot.floorDetails[nextCoords] == 0 { - robot.computer.Step(i) - computerOutput := robot.computer.Outputs[len(robot.computer.Outputs)-1] - - switch computerOutput { - case 0: // hit a wall, do not recurse - // update robot's wall coords to include the wall - // note representing walls with a -1 to avoid the zero value detection - robot.floorDetails[nextCoords] = -1 - case 1, 2: // walked and hit the O2 tank or not - // update floorDetails - robot.floorDetails[nextCoords] = computerOutput - - // continue to walk the robot. walk the robot into the nextCoords spot - robot.fromLeft += dx[i] - robot.fromTop += dy[i] - - // recurse - robot.RecursiveMove() - - // backtrack so the robot walks in the remainder of directions from this output - robot.fromLeft -= dx[i] - robot.fromTop -= dy[i] - // backtrack the computer - robot.computer.Step(backtrack[i]) - } - } - } -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) *Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return &comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func (comp *Intcode) Step(input int) { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - // Note: need to optimize this to not resize if the params are not being used - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - comp.Step(-1) - case 4: // 4: outputs its input value - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, comp.PuzzleInput[param1]) - // fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput) - comp.InstructionIndex += 2 - - // continue running until terminates or asks for another input - comp.Step(input) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - comp.Step(input) - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} - -// Draw was copied from day11. It converts a map of points mapped from a (0,0) origin to a 2D grid -// The origin loses its reference... -func Draw(mapCoordsToType map[string]int) [][]int { - var lowX, highX, lowY, highY int - for key := range mapCoordsToType { - coords := strings.Split(key, ",") - x, _ := strconv.Atoi(coords[0]) - y, _ := strconv.Atoi(coords[1]) - switch { - case x < lowX: - lowX = x - case x > highX: - highX = x - } - switch { - case y < lowY: - lowY = y - case y > highY: - highY = y - } - } - - // Determine the bounds of the grid - edgeLength := 2 * mathy.MaxInt(-lowY, -lowX, highY, highX) - - grid := make([][]int, edgeLength) - for i := 0; i < edgeLength; i++ { - // each character will initialize as a space character - grid[i] = make([]int, edgeLength) - } - - // Iterate through all coordinates and transcribe x,y onto a 2D grid - // where the math is a little different... - for key, val := range mapCoordsToType { - // key is string coords - coords := strings.Split(key, ",") - x, _ := strconv.Atoi(coords[0]) - y, _ := strconv.Atoi(coords[1]) - x += edgeLength / 2 - y += edgeLength / 2 - // val is color to paint (1 or 0) - if val != -1 { - grid[x][y] = val - } - } - - // trim off due to making the initial grid too large - grid = trim(grid) - // rotate it because of how I coded up the robot's coordinates :/ - grid = algos.RotateIntGrid(grid) - // retrim - grid = trim(grid) - - return grid -} - -// helper function for Draw to remove whitespace from overestimating the size -// of the drawing space -func trim(grid [][]int) [][]int { - // remove all empty rows at top and bottom -removeRowsTop: - for i := 0; i < len(grid); { - for j := 0; j < len(grid[i]); j++ { - if grid[i][j] != 0 { - break removeRowsTop - } - } - grid = grid[1:] - } - - // remove empty columns on left -removeColsLeft: - for i := 0; i < len(grid[0]); { - for j := 0; j < len(grid); j++ { - if grid[j][i] != 0 { - break removeColsLeft - } - } - // if loop hasn't broken out, iterate over first "column" and slice off "0-index" - for j := 0; j < len(grid); j++ { - grid[j] = grid[j][1:] - } - } - - return grid -} diff --git a/2019/day16/part1/main.go b/2019/day16/part1/main.go deleted file mode 100644 index faaaaa5..0000000 --- a/2019/day16/part1/main.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // parse input file into a slice of numbers - input := util.ReadFile("../input.txt") - characters := strings.Split(input, "") - digits := make([]int, len(characters)) - for i, v := range characters { - digits[i], _ = strconv.Atoi(v) - } - - // generate all patterns once at start - patterns := make([][]int, len(digits)) - for i := range digits { - patterns[i] = generatePattern(i+1, len(digits)) - } - - // run through 100 phases, overwriting digits - for i := 0; i < 100; i++ { - digits = getNextOutputNumber(digits, patterns) - } - - // Transform into github.com/alexchao26/advent-of-code-go output - var firstEightDigits int - for i := 0; i < 8; i++ { - firstEightDigits *= 10 - firstEightDigits += digits[i] - } - fmt.Printf("First 8 digits after 100 phases: %v\n", firstEightDigits) -} - -// generatePattern takes in the index (one indexed, not zero) that is being considered, and returns the pattern to multiply -// digits by -func generatePattern(oneIndex, lengthNeeded int) []int { - if oneIndex < 1 { - log.Fatal("Input to generatePattern must be a positive int") - } - basePattern := []int{0, 1, 0, -1} - pattern := make([]int, 0, 4*(oneIndex+1)) - for len(pattern)-1 < lengthNeeded { - for _, v := range basePattern { - for i := 0; i < oneIndex; i++ { - pattern = append(pattern, v) - } - } - } - return pattern[1 : lengthNeeded+1] -} - -// takes in digits and all patterns, generates next set of digits -func getNextOutputNumber(digits []int, patterns [][]int) []int { - output := make([]int, len(digits)) - for index := range digits { - // for this index, sum up all products of digits and this index's pattern - var sum int - for i := 0; i < len(digits); i++ { - sum += patterns[index][i] * digits[i] - } - - // ensure the sum is positive & take the single's place digit - if sum < 0 { - sum *= -1 - } - sum %= 10 - - // assign to output slice for the same index - output[index] = sum - } - - return output -} diff --git a/2019/day16/part2/main.go b/2019/day16/part2/main.go deleted file mode 100644 index 77226d7..0000000 --- a/2019/day16/part2/main.go +++ /dev/null @@ -1,95 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - "time" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // parse input file into a slice of numbers - input := util.ReadFile("../input.txt") - // input := "03036732577212944063491565474664" // test should output "84462026" - characters := strings.Split(input, "") - - digits := make([]int, len(characters)*10000) - for i := 0; i < 10000; i++ { - for j, v := range characters { - digits[i*len(characters)+j], _ = strconv.Atoi(v) - } - } - - var offsetIndex int - for i := 0; i < 7; i++ { - offsetIndex *= 10 - offsetIndex += digits[i] - } - fmt.Println("offsetIndex", offsetIndex) - - // run through 100 phases, overwriting digits - for i := 0; i < 100; i++ { - digits = getNextOutputNumber(digits) - // output a time to make sure this is running fast enough - fmt.Printf("output received at %v, %v to go\n", time.Now(), 100-i-1) - } - - // Transform into github.com/alexchao26/advent-of-code-go output - var firstEightDigits int - for i := 0; i < 8; i++ { - firstEightDigits *= 10 - firstEightDigits += digits[i+offsetIndex] - } - fmt.Printf("\nOffset 8 digits after 100 phases: %v\n", firstEightDigits) - fmt.Println("Expect 84462026 for test, 36265589 for actual input") -} - -// takes in digits and all patterns, generates next set of digits -func getNextOutputNumber(digits []int) []int { - // ONE INDEX THE PARTIAL SUMS, so partials[0] = 0, partials[1] = digits[0] - partials := make([]int, len(digits)+1) - for i := range digits { - // add previous partial subset if i is not zero - partials[i+1] += partials[i] - // add digit on as well - partials[i+1] += digits[i] - } - - output := make([]int, len(digits)) - for i := range digits { - chunkLength := i + 1 - - adding := true - for start := i; start < len(digits); start += chunkLength * 2 { - // calculate chunk sum - startOfChunkIndex := chunkLength - 1 - endOfChunkIndex := start + chunkLength - 1 - if endOfChunkIndex >= len(digits) { - endOfChunkIndex = len(digits) - 1 - } - chunkSum := partials[endOfChunkIndex] - partials[startOfChunkIndex] - - // increment or decrements output index - if adding { - output[i] += chunkSum - } else { - output[i] -= chunkSum - } - adding = !adding - } - } - - // make all output digits positive & single digits - // NOTE this is not equivalent to taking the mod of a negative number! - // This has to be done because of the problem's spec to just take the 1's digit - for i, v := range output { - if v < 0 { - output[i] *= -1 - } - output[i] %= 10 - } - - return output -} diff --git a/2019/day17/part1/main.go b/2019/day17/part1/main.go deleted file mode 100644 index ecec9ad..0000000 --- a/2019/day17/part1/main.go +++ /dev/null @@ -1,275 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - robot := MakeRobot(inputNumbers) - - // fire off function to populate the robot's floorGrid property - robot.GetFloorGrid() - - // find all intersections and sum up the products of its row and col - 0-indexed - // helper directions to traverse in all 4 directions - dRow := []int{0, 0, -1, 1} - dCol := []int{-1, 1, 0, 0} - - var sumOfAlignmentParameters int - for row, rowSlice := range robot.floorGrid { - for col, floorType := range rowSlice { - // traverse to the four directions around the particular cell, increment surroundingScaffolds - // by 1 for every neighbor that is a scaffold, - // if this is equal to 4 after looping, then an intersection was found - var surroundingScaffolds int - for i := 0; i < 4; i++ { - neighborRow, neighborCol := row+dRow[i], col+dCol[i] - isInbounds := neighborRow >= 0 && neighborRow < len(robot.floorGrid) && neighborCol >= 0 && neighborCol < len(robot.floorGrid[0]) - if isInbounds && floorType == "#" && robot.floorGrid[neighborRow][neighborCol] == "#" { - surroundingScaffolds++ - } - } - - if surroundingScaffolds == 4 { - sumOfAlignmentParameters += row * col - } - } - } - - fmt.Println("Sum of alignment parameters: ", sumOfAlignmentParameters) -} - -// Robot struct to maintain detail's on the Robot's coordinates, path -type Robot struct { - row, col int - floorGrid [][]string - computer *Intcode -} - -// MakeRobot returns an instance of a Robot -func MakeRobot(intcodeInput []int) *Robot { - return &Robot{ - computer: MakeComputer(intcodeInput), - } -} - -// GetFloorGrid will fire off the computer and populate the robot's floor details -func (robot *Robot) GetFloorGrid() { - robot.computer.Step(-1) - robot.floorGrid = append(robot.floorGrid, []string{}) - row := 0 - - for _, v := range robot.computer.Outputs { - switch v { - case 10: - row++ - robot.floorGrid = append(robot.floorGrid, []string{}) - default: - tileType := cast.ASCIIIntToChar(v) - robot.floorGrid[row] = append(robot.floorGrid[row], tileType) - } - } - - // parse off empty slices @ end - for i := len(robot.floorGrid) - 1; i >= 0; i-- { - if len(robot.floorGrid[i]) == 0 { - robot.floorGrid = robot.floorGrid[:len(robot.floorGrid)-1] - } - } -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) *Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return &comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -func (comp *Intcode) Step(input int) { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - comp.Step(input) - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - comp.Step(-1) - case 4: // 4: outputs its input value - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, comp.PuzzleInput[param1]) - // fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput) - comp.InstructionIndex += 2 - - // continue running until terminates or asks for another input - comp.Step(input) - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - comp.Step(input) - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - comp.Step(input) - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - comp.Step(input) - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day17/part2/main.go b/2019/day17/part2/main.go deleted file mode 100644 index aa84f38..0000000 --- a/2019/day17/part2/main.go +++ /dev/null @@ -1,345 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - "time" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // Modify the first address from a 1 to a 2 to start the vacuum robot - inputNumbers[0] = 2 - - robot := MakeRobot(inputNumbers) - // fire off function to populate the robot's floorGrid property - robot.GetFloorGrid() - - // NOTE the computer accepts numbers as inputs, but these numbers correlate to ASCII - // computer will ask for main movement routine. allows: [A-C,] i.e. A B C separated by , - // end with a newline, e.g. integer 10 in ASCII - A, B, C := int('A'), int('B'), int('C') - comma, newline := int(','), int('\n') - - mainRoutineOrder := []int{A, B, A, C, B, C, B, C, A, C} - for i, routine := range mainRoutineOrder { - robot.computer.Step(routine) - if i != len(mainRoutineOrder)-1 { - robot.computer.Step(comma) - } else { - robot.computer.Step(newline) - } - } - - // computer then asks for details of each movement function - // accepts [LR0-9,] ended with a newline - // NOTE I determined these paths manually after printing the floor and finding a pattern by eye... - L, R := int('L'), int('R') - numbers := make([]int, 10) - for i := range numbers { - numbers[i] = int('0') + i - } - // A is L10 R12 R12 - patternA := []int{ - L, comma, numbers[1], numbers[0], comma, - R, comma, numbers[1], numbers[2], comma, - R, comma, numbers[1], numbers[2], - newline, - } - // B is R6 R10 L10 - patternB := []int{ - R, comma, numbers[6], comma, - R, comma, numbers[1], numbers[0], comma, - L, comma, numbers[1], numbers[0], - newline, - } - // C is R10 L10 L12 R6 - patternC := []int{ - R, comma, numbers[1], numbers[0], comma, - L, comma, numbers[1], numbers[0], comma, - L, comma, numbers[1], numbers[2], comma, - R, comma, numbers[6], - newline, - } - // fmt.Println("pattern A: ") - for _, v := range patternA { - // fmt.Printf("%v,", v) - robot.computer.Step(v) - } - for _, v := range patternB { - robot.computer.Step(v) - } - for _, v := range patternC { - robot.computer.Step(v) - } - - // finally, asks for continuous video feed or not 'y' or 'n' and a new line - robot.computer.Step(int('y')) - robot.computer.Step(newline) - - fmt.Println("Dust collected", robot.computer.Outputs[len(robot.computer.Outputs)-1]) -} - -// Robot struct to maintain detail's on the Robot's coordinates, path -type Robot struct { - row, col int - floorGrid [][]string - computer *Intcode -} - -// MakeRobot returns an instance of a Robot -func MakeRobot(intcodeInput []int) *Robot { - return &Robot{ - 0, - 0, - make([][]string, 0), - MakeComputer(intcodeInput), - } -} - -// GetFloorGrid will fire off the computer and populate the robot's floor details -func (robot *Robot) GetFloorGrid() { - robot.computer.Step(-1) - robot.floorGrid = append(robot.floorGrid, []string{}) - row := 0 - - for _, v := range robot.computer.Outputs { - switch v { - case 10: - row++ - robot.floorGrid = append(robot.floorGrid, []string{}) - default: - tileType := cast.ASCIIIntToChar(v) - robot.floorGrid[row] = append(robot.floorGrid[row], tileType) - } - } - - // parse off empty slices @ end - for i := len(robot.floorGrid) - 1; i >= 0; i-- { - if len(robot.floorGrid[i]) == 0 { - robot.floorGrid = robot.floorGrid[:len(robot.floorGrid)-1] - } - } -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) *Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return &comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -// Update to run iteratively (while the computer is running) -// it will also return out if a -1 input is asked for -// then call Step again to provide the next input, or run with -1 from the start -// to run the computer until it asks for an input OR terminates -func (comp *Intcode) Step(input int) { - for comp.IsRunning { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - - // change the input value so the next time a 3 opcode is hit, will return out - input = -1 - case 4: // 4: outputs its input value - output := comp.PuzzleInput[param1] - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, output) - - // NOTE this is specific to day17 to print the robot walking around the scaffold - // if the last two outputs are newlines (ASCII 10's), print out the output - // then just clear the output to make life easy - if output == 10 && comp.Outputs[len(comp.Outputs)-2] == 10 { - Print2DGrid(comp.Outputs) - // clear outputs slice, sleep for 100ms - comp.Outputs = []int{} - time.Sleep(time.Millisecond * 100) - } - - comp.InstructionIndex += 2 - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} - -// Print2DGrid is day17 specific. allValues are ASCII ints including 10 for newline -func Print2DGrid(allValues []int) { - var row int - floorGrid := [][]string{[]string{}} - for _, v := range allValues { - switch v { - case 10: - row++ - floorGrid = append(floorGrid, []string{}) - default: - tileType := cast.ASCIIIntToChar(v) - floorGrid[row] = append(floorGrid[row], tileType) - } - } - - for _, v := range floorGrid { - fmt.Println(v) - } -} diff --git a/2019/day18/part1/main.go b/2019/day18/part1/main.go deleted file mode 100644 index 1da9c81..0000000 --- a/2019/day18/part1/main.go +++ /dev/null @@ -1,283 +0,0 @@ -package main - -import ( - "fmt" - "math" - "strings" - "time" - - "github.com/alexchao26/advent-of-code-go/cast" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - input := util.ReadFile("../input.txt") - linesSli := strings.Split(input, "\n") - - grid := make([][]string, len(linesSli)) // string might be excessive, but it will be easier to look at - for i, line := range linesSli { - grid[i] = strings.Split(line, "") - } - - // get all the coordinates of keys. will be used as starting points for dijkstra's searches - keyToCoordinates := getCoordinatesOfKeys(grid) - - // initialize Graph - graph := MakeGraph() - - // for every key, generate a new dijkstra grid where all distances are set to MAX SAFE INT, and the key's distance is set to 0 - for key, sourceCoordinates := range keyToCoordinates { - // initialize a dijkstra grid for each key - dijkstraGrid := MakeDijkstraGrid(grid, sourceCoordinates) - - // step through grid until complete, handleFrontOfQueue returns true when the queue is empty... - for !dijkstraGrid.handleFrontOfQueue() { - } - // small space optimization, overwrite the dijkstra queue b/c the underlying array is alive - dijkstraGrid.queue = nil - - // update graph for all edges from `key` to all other keys - for destinationKey, destinationCoordinates := range keyToCoordinates { - if key != destinationKey && destinationKey != "@" { - row, col := destinationCoordinates[0], destinationCoordinates[1] - // pass the key being pathed FROM and the dijkstraNode (found on the grid) of the key found - graph.AddEdges(key, dijkstraGrid.grid[row][col]) - } - } - } - - dfsStartTimestamp := time.Now() - // first off (recursive, memoized) dfs method on graph that finds minimum distance - fmt.Printf("DFS result: %v\n\nFinished in %v\n\n", graph.dfsMinmumDistance(), time.Since(dfsStartTimestamp)) -} - -// get a map of the coordinates of points of interests -func getCoordinatesOfKeys(grid [][]string) map[string][2]int { - pointsOfInterest := make(map[string][2]int) - for row, rowSli := range grid { - for col, cell := range rowSli { - switch { - case cell == "@": - pointsOfInterest["@"] = [2]int{row, col} - // typecase cell to its ASCII value - case int(cell[0]) >= 'a' && int(cell[0]) <= 'z': - pointsOfInterest[cell] = [2]int{row, col} - } - } - } - return pointsOfInterest -} - -/******************************************************************************************** -DIJKSTRA GRID Code -*********************************************************************************************/ - -// DijkstraGrid stores the grid itself and also the needed queue to traverse through the grid -type DijkstraGrid struct { - grid [][]*dijkstraNode - queue [][2]int // coordinates to be traversed next -} - -// dijkstraNode is each cell within a DijkstraGrid.grid -type dijkstraNode struct { - value string // string of the floor type (key, door, floor?, wall) - distance int // distance from the source - keysFound map[string]bool // keys that have been run into - keysNeeded map[string]bool // keys needed to get to this node, i.e. all doors passed thorugh - seen bool -} - -// MakeDijkstraGrid initializes a 2D grid of dijkstraNodes with distances initialized to the max safe integer -func MakeDijkstraGrid(grid [][]string, startCoords [2]int) *DijkstraGrid { - finalGrid := make([][]*dijkstraNode, len(grid)) - startKey := grid[startCoords[0]][startCoords[1]] - for row, rowSli := range grid { - // initialize the row's slice in the finalGrid - finalGrid[row] = make([]*dijkstraNode, len(grid[0])) - for col, cellString := range rowSli { - finalGrid[row][col] = &dijkstraNode{ - value: cellString, - distance: math.MaxInt32, // maximum safe integer, effectively 2^31 - 1 - keysFound: map[string]bool{startKey: true}, // initialize with the starting key - keysNeeded: make(map[string]bool), // empty map for now - seen: false, // initialize as false - } - // remove the "@" key because it's not a key... it's just the starting point - delete(finalGrid[row][col].keysFound, "@") - } - } - - // set properties of starting coordinate - // distance = 0 - finalGrid[startCoords[0]][startCoords[1]].distance = 0 - finalGrid[startCoords[0]][startCoords[1]].seen = true - queue := [][2]int{{startCoords[0], startCoords[1]}} - return &DijkstraGrid{finalGrid, queue} -} - -var dRow [4]int = [4]int{0, 0, -1, 1} -var dCol [4]int = [4]int{-1, 1, 0, 0} - -// handleFrontOfQueue does just that, returns a true if queue is empty upon completion -func (dijk *DijkstraGrid) handleFrontOfQueue() (queueIsEmpty bool) { - row, col := dijk.queue[0][0], dijk.queue[0][1] - cell := dijk.grid[row][col] - - // mark as seen - cell.seen = true - - // if key is found, add to cell details - if ascii := int(cell.value[0]) - 'a'; ascii >= 0 && ascii < 26 { - cell.keysFound[cell.value] = true - } - - // if door is found, add to keysNeeded as a lowercase (easier comparison later) - if ascii := int(cell.value[0]) - 'A'; ascii >= 0 && ascii < 26 { - cell.keysNeeded[cast.ASCIIIntToChar('a'+ascii)] = true - } - - // push neighbors into queue IF not already seen and not walls - for i := 0; i < 4; i++ { - neighborRow, neighborCol := row+dRow[i], col+dCol[i] - neighborCell := dijk.grid[neighborRow][neighborCol] - if !neighborCell.seen && neighborCell.value != "#" { - // add to queue - dijk.queue = append(dijk.queue, [2]int{neighborRow, neighborCol}) - - // increment its distance by 1 - neighborCell.distance = cell.distance + 1 - - // NOTE manually copying these, otherwise they'll be pointing to the same underlying map pointer - // copy keysFound (still carrying the same keys) - for key := range cell.keysFound { - neighborCell.keysFound[key] = true - } - - // copy keysNeeded as well - for key := range cell.keysNeeded { - neighborCell.keysNeeded[key] = true - } - } - } - - // dequeue by rescoping the slice - dijk.queue = dijk.queue[1:] - - // if queue is empty, there are no more paths to generate, return a true - return len(dijk.queue) == 0 -} - -/******************************************************************************************** -GRAPH Code -*********************************************************************************************/ - -// Graph stores the edges between keys by storing all paths from a particular key to all other keys -// all cells in this 2D MAP will be a dijkstraNode because those contain all the needed data -// such as distance from last key, the keysNeeded to take this path -type Graph struct { - keysToKeys map[string]map[string]*dijkstraNode -} - -// MakeGraph initializes a Graph instance and returns a pointer to it -func MakeGraph() *Graph { - return &Graph{ - keysToKeys: make(map[string]map[string]*dijkstraNode), - } -} - -// AddEdges takes in the key being pathed FROM, and the dijkstraNode of a key pathed TO -// and adds that edge to the graph -func (graph *Graph) AddEdges(startKey string, destinationNode *dijkstraNode) { - // initialize this "row" of the map if it does not exist yet - if graph.keysToKeys[startKey] == nil { - graph.keysToKeys[startKey] = make(map[string]*dijkstraNode) - } - // add the destination node to the graph - keyFound := destinationNode.value - graph.keysToKeys[startKey][keyFound] = destinationNode -} - -func (graph *Graph) dfsMinmumDistance() int { - // recursive function that dives through graph - var traverse func(string, map[string]bool) int - - // Implement cache for traverse function - // caches the ":" to resulting distance - cache := map[string]int{} - - // NOTE helper function that leverages the above cache - // Traverse should return the minimum distance to a completion for these inputs - // inputs are: 1. the entry point (the key to generate a path FROM) - // 2. the keys that have been found so far - traverse = func(entry string, keysFound map[string]bool) int { - shortestFromThisNode := math.MaxInt32 - - cacheKey := makeCacheKey(entry, keysFound, len(graph.keysToKeys)-1) - - // cache hit - if cacheVal, found := cache[cacheKey]; found { - return cacheVal - } - - // base case: all keys found, no more distance to be traveled, i.e. zero - if len(keysFound) == len(graph.keysToKeys)-1 { - return 0 - } - - // iterate over all possible destination nodes. - nextEdges := graph.keysToKeys[entry] - for nextKey, node := range nextEdges { - // only traverse if key has not been found yet AND all needed keys have been collected already - if !keysFound[nextKey] && haveNeededKeys(keysFound, node.keysNeeded) { - // add the nextKey - keysFound[nextKey] = true - - // update the shortestFromThisNode if applicable - distanceToEnd := node.distance + traverse(nextKey, keysFound) - if distanceToEnd < shortestFromThisNode { - shortestFromThisNode = distanceToEnd - } - - // backtrack - remove the key - delete(keysFound, nextKey) - } - } - - cache[cacheKey] = shortestFromThisNode - return shortestFromThisNode - } - - // fire off initially - return traverse("@", map[string]bool{}) -} - -// helper function to generate a cache string key -// cache string is of the form entryKey:allKeysToFind -func makeCacheKey(entry string, keysFound map[string]bool, totalKeys int) string { - allKeys := make([]string, totalKeys) - for i := 0; i < totalKeys; i++ { - allKeys[i] = cast.ASCIIIntToChar(int('a') + i) - } - - cacheKey := entry - // generate - for i := 0; i < totalKeys; i++ { - char := cast.ASCIIIntToChar(int('a') + i) - if !keysFound[char] { - cacheKey += char - } - } - - return cacheKey -} - -// simple helper function to make sure that all keysNeeded are in keysFound -func haveNeededKeys(keysFound, keysNeeded map[string]bool) bool { - for key := range keysNeeded { - if !keysFound[key] { - return false - } - } - return true -} diff --git a/2019/day18/part2/main.go b/2019/day18/part2/main.go deleted file mode 100644 index a5a1bb8..0000000 --- a/2019/day18/part2/main.go +++ /dev/null @@ -1,388 +0,0 @@ -package main - -import ( - "fmt" - "math" - "sort" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - input := util.ReadFile("../input.txt") - linesSli := strings.Split(input, "\n") - - // generate four quadrants - quad1, quad2, quad3, quad4 := generateQuadrants(linesSli) - - // handle an individual grid, sum answers together to print - one := handleGrid(quad1) - two := handleGrid(quad2) - three := handleGrid(quad3) - four := handleGrid(quad4) - - fmt.Printf("Sum for four robots is %v\n", one+two+three+four) -} - -// Helper function to do all the things to a grid... -func handleGrid(grid [][]string) int { - removeDoorsWithoutKeys(grid) - - keysToCoordinates := getCoordinatesOfKeys(grid) - - graph := MakeGraph() - - for key, sourceCoordinates := range keysToCoordinates { - dijkstraGrid := MakeDijkstraGrid(grid, sourceCoordinates) - - // step through grid until complete, handleFrontOfQueue returns true when the queue is empty... - for !dijkstraGrid.handleFrontOfQueue() { - } - // small space optimization, overwrite the dijkstra queue b/c the underlying array is alive - dijkstraGrid.queue = nil - - // update graph for all edges from `key` to all other keys - for otherKey, destinationCoordinates := range keysToCoordinates { - if key != otherKey && otherKey != "@" { - row, col := destinationCoordinates[0], destinationCoordinates[1] - // pass the key being pathed FROM and the dijkstraNode (found on the grid) of the key found - graph.AddEdges(key, dijkstraGrid.grid[row][col]) - } - } - } - - // then concurrently write back to the result chan - return graph.dfsMinmumDistance() -} - -// get a map of the coordinates of points of interests -func getCoordinatesOfKeys(grid [][]string) map[string][2]int { - pointsOfInterest := make(map[string][2]int) - for row, rowSli := range grid { - for col, cell := range rowSli { - switch { - case cell == "@": - pointsOfInterest["@"] = [2]int{row, col} - // typecase cell to its ASCII value - case int(cell[0]) >= 'a' && int(cell[0]) <= 'z': - pointsOfInterest[cell] = [2]int{row, col} - } - } - } - return pointsOfInterest -} - -/**************************************************************************************** -DIJKSTRA GRID Code -*****************************************************************************************/ - -// DijkstraGrid stores the grid itself and also the needed queue to traverse through the grid -type DijkstraGrid struct { - grid [][]*dijkstraNode - queue [][2]int // coordinates to be traversed next -} - -// dijkstraNode is each cell within a DijkstraGrid.grid -type dijkstraNode struct { - value string // string of the floor type (key, door, floor?, wall) - distance int // distance from the source - keysFound map[string]bool // keys that have been run into - keysNeeded map[string]bool // keys needed to get to this node, i.e. all doors passed thorugh - seen bool -} - -// MakeDijkstraGrid initializes a 2D grid of dijkstraNodes with distances initialized to the max safe integer -func MakeDijkstraGrid(grid [][]string, startCoords [2]int) *DijkstraGrid { - finalGrid := make([][]*dijkstraNode, len(grid)) - startKey := grid[startCoords[0]][startCoords[1]] - for row, rowSli := range grid { - // initialize the row's slice in the finalGrid - finalGrid[row] = make([]*dijkstraNode, len(grid[0])) - for col, cellString := range rowSli { - finalGrid[row][col] = &dijkstraNode{ - value: cellString, - distance: math.MaxInt32, // maximum safe integer, effectively 2^31 - 1 - keysFound: map[string]bool{startKey: true}, // initialize with the starting key - keysNeeded: make(map[string]bool), // empty map for now - seen: false, // initialize as false - } - // remove the "@" key because it's not a key... it's just the starting point - delete(finalGrid[row][col].keysFound, "@") - } - } - - // set properties of starting coordinate - // distance = 0 - finalGrid[startCoords[0]][startCoords[1]].distance = 0 - finalGrid[startCoords[0]][startCoords[1]].seen = true - queue := [][2]int{ - [2]int{startCoords[0], startCoords[1]}, - } - return &DijkstraGrid{finalGrid, queue} -} - -var dRow [4]int = [4]int{0, 0, -1, 1} -var dCol [4]int = [4]int{-1, 1, 0, 0} - -// handleFrontOfQueue does just that, returns a true if queue is empty upon completion -func (dijk *DijkstraGrid) handleFrontOfQueue() (queueIsEmpty bool) { - row, col := dijk.queue[0][0], dijk.queue[0][1] - cell := dijk.grid[row][col] - - // mark as seen - cell.seen = true - - // if key is found, add to cell details - if ascii := int(cell.value[0]) - 'a'; ascii >= 0 && ascii < 26 { - cell.keysFound[cell.value] = true - } - - // if door is found, add to keysNeeded as a lowercase (easier comparison later) - if ascii := int(cell.value[0]) - 'A'; ascii >= 0 && ascii < 26 { - cell.keysNeeded[cast.ASCIIIntToChar('a'+ascii)] = true - } - - // push neighbors into queue IF not already seen and not walls - for i := 0; i < 4; i++ { - neighborRow, neighborCol := row+dRow[i], col+dCol[i] - neighborCell := dijk.grid[neighborRow][neighborCol] - if !neighborCell.seen && neighborCell.value != "#" { - // add to queue - dijk.queue = append(dijk.queue, [2]int{neighborRow, neighborCol}) - - // increment its distance by 1 - neighborCell.distance = cell.distance + 1 - - // NOTE manually copying these, otherwise they'll be pointing to the same underlying map pointer - // copy keysFound (still carrying the same keys) - for key := range cell.keysFound { - neighborCell.keysFound[key] = true - } - - // copy keysNeeded as well - for key := range cell.keysNeeded { - neighborCell.keysNeeded[key] = true - } - } - } - - // dequeue by rescoping the slice - dijk.queue = dijk.queue[1:] - - // if queue is empty, there are no more paths to generate, return a true - if len(dijk.queue) == 0 { - return true - } - return false -} - -/**************************************************************************************** -GRAPH Code -*****************************************************************************************/ - -// Graph stores the edges between keys by storing all paths from a particular key to all other keys -// all cells in this 2D MAP will be a dijkstraNode because those contain all the needed data -// such as distance from last key, the keysNeeded to take this path -type Graph struct { - keysToKeys map[string]map[string]*dijkstraNode - allKeysNeeded map[string]bool -} - -// MakeGraph initializes a Graph instance and returns a pointer to it -func MakeGraph() *Graph { - return &Graph{ - keysToKeys: make(map[string]map[string]*dijkstraNode), - allKeysNeeded: make(map[string]bool), - } -} - -// AddEdges takes in the key being pathed FROM, and the dijkstraNode of a key pathed TO -// and adds that edge to the graph -func (graph *Graph) AddEdges(startKey string, destinationNode *dijkstraNode) { - // add startKey to the allKeysNeeded hashmap - graph.allKeysNeeded[startKey] = true - - // initialize this "row" of the map if it does not exist yet - if graph.keysToKeys[startKey] == nil { - graph.keysToKeys[startKey] = make(map[string]*dijkstraNode) - } - // add the destination node to the graph - keyFound := destinationNode.value - graph.keysToKeys[startKey][keyFound] = destinationNode -} - -func (graph *Graph) dfsMinmumDistance() int { - // recursive function that dives through graph - var traverse func(string, map[string]bool) int - - // Implement cache for traverse function - // caches the ":" to resulting distance - cache := map[string]int{} - - // NOTE helper function that leverages the above cache - // Traverse should return the minimum distance to a completion for these inputs - // inputs are: 1. the entry point (the key to generate a path FROM) - // 2. the keys that have been found so far - traverse = func(entry string, keysFound map[string]bool) int { - shortestFromThisNode := math.MaxInt32 - - cacheKey := makeCacheKey(entry, keysFound, graph.allKeysNeeded) - - // cache hit - if cacheVal, found := cache[cacheKey]; found { - return cacheVal - } - - // base case: all keys found, no more distance to be traveled, i.e. zero - if len(keysFound) == len(graph.keysToKeys)-1 { - return 0 - } - - // iterate over all possible destination nodes. - nextEdges := graph.keysToKeys[entry] - for nextKey, node := range nextEdges { - // only traverse if key has not been found yet AND all needed keys have been collected already - if !keysFound[nextKey] && haveNeededKeys(keysFound, node.keysNeeded) { - // add the nextKey - keysFound[nextKey] = true - - // update the shortestFromThisNode if applicable - distanceToEnd := node.distance + traverse(nextKey, keysFound) - if distanceToEnd < shortestFromThisNode { - shortestFromThisNode = distanceToEnd - } - - // backtrack - remove the key - delete(keysFound, nextKey) - } - } - - cache[cacheKey] = shortestFromThisNode - return shortestFromThisNode - } - - // fire off initially - return traverse("@", map[string]bool{}) -} - -// helper function to generate a cache string key -// cache string is of the form entryKey:allKeysToFind -func makeCacheKey(entry string, keysFound map[string]bool, allKeysNeeded map[string]bool) string { - cacheKey := entry - // generate sorted list of the keys that have NOT been found yet - var needToFind []string - for key := range allKeysNeeded { - if !keysFound[key] { - needToFind = append(needToFind, key) - } - } - sort.Strings(needToFind) - - return cacheKey + strings.Join(needToFind, "") -} - -// simple helper function to make sure that all keysNeeded are in keysFound -func haveNeededKeys(keysFound, keysNeeded map[string]bool) bool { - for key := range keysNeeded { - if !keysFound[key] { - return false - } - } - return true -} - -/**************************************************************************************** -INPUT SANITIZATION FUNCTIONS -*****************************************************************************************/ - -/* Quadrant numbers :) mathematical because why not - II | I - | - --------+--------- - | - III | IV - | */ -// This function is gross but it does what it says it does... returns four quadrants -func generateQuadrants(linesSli []string) ([][]string, [][]string, [][]string, [][]string) { - quad1 := make([][]string, len(linesSli)) - quad2 := make([][]string, len(linesSli)) - quad3 := make([][]string, len(linesSli)) - quad4 := make([][]string, len(linesSli)) - for i, line := range linesSli { - quad1[i] = strings.Split(line, "") - quad2[i] = strings.Split(line, "") - quad3[i] = strings.Split(line, "") - quad4[i] = strings.Split(line, "") - } - - // really only need the "@" coordinates, but might as well use this function that I alreade have.. - keyToCoordinates := getCoordinatesOfKeys(quad1) - - originRow, originCol := keyToCoordinates["@"][0], keyToCoordinates["@"][1] - for i := 0; i < 3; i++ { - for j := 0; j < 3; j++ { - row := originRow - 1 + i - col := originCol - 1 + j - if (row+col)%2 == 0 { - quad1[row][col] = "@" - quad2[row][col] = "@" - quad3[row][col] = "@" - quad4[row][col] = "@" - } else { - quad1[row][col] = "#" - quad2[row][col] = "#" - quad3[row][col] = "#" - quad4[row][col] = "#" - } - } - } - // replace origin with wall - quad1[originRow][originCol] = "#" - quad2[originRow][originCol] = "#" - quad3[originRow][originCol] = "#" - quad4[originRow][originCol] = "#" - - // rescope quadrants to point to correct scope of underlying arrays - quad1 = quad1[:originRow+1] - quad2 = quad2[:originRow+1] - for i := range quad1 { - quad1[i] = quad1[i][originCol:] - quad2[i] = quad2[i][:originCol+1] - } - quad3 = quad3[originRow:] - quad4 = quad4[originRow:] - for i := range quad3 { - quad3[i] = quad3[i][:originCol+1] - quad4[i] = quad4[i][originCol:] - } - - // return the four quads - return quad1, quad2, quad3, quad4 -} - -// NOTE this is a BIG assumption that will not work on all inputs, in fact it fails on a lot of the -// note examples... :( -// removes doors from a quadrant if the associated key is not in the quadrant -func removeDoorsWithoutKeys(quadrant [][]string) { - // put all the keys in this quadrant in a hashmap (along with walls, floors and the origin "@") - valuesToKeep := map[string]bool{"#": true, ".": true, "@": true} - for _, rowSli := range quadrant { - for _, cell := range rowSli { - if ascii := int(cell[0] - 'a'); ascii >= 0 && ascii < 26 { - valuesToKeep[cast.ASCIIIntToChar(ascii+'a')] = true - } - } - } - - // iterate through the quadrant again, this time removing any capital letters - // who's lower case representation is NOT in the valuesToKeep hashmap - for row, rowSli := range quadrant { - for col, cell := range rowSli { - if lower := strings.ToLower(cell); !valuesToKeep[lower] { - quadrant[row][col] = "." // replace with an empty hallway - } - } - } -} diff --git a/2019/day19/part1/main.go b/2019/day19/part1/main.go deleted file mode 100644 index 8dbf94f..0000000 --- a/2019/day19/part1/main.go +++ /dev/null @@ -1,226 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - grid := make([][]string, 50) - var pulledDrones int - for y := 0; y < 50; y++ { - grid[y] = make([]string, 50) - for x := 0; x < 50; x++ { - // NOTE Every drone gets its own computer... - comp := MakeComputer(inputNumbers) - comp.Step(x) - comp.Step(y) - - lastOutput := comp.Outputs[len(comp.Outputs)-1] - if lastOutput == 0 { - grid[y][x] = "." - } else { - grid[y][x] = "#" - pulledDrones++ - } - } - } - - fmt.Println("Total pulled drones", pulledDrones) - - for _, v := range grid { - fmt.Println(v) - } -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) *Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return &comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -// Update to run iteratively (while the computer is running) -// it will also return out if a -1 input is asked for -// then call Step again to provide the next input, or run with -1 from the start -// to run the computer until it asks for an input OR terminates -func (comp *Intcode) Step(input int) { - for comp.IsRunning { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - // fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - - // change the input value so the next time a 3 opcode is hit, will return out - input = -1 - case 4: // 4: outputs its input value - output := comp.PuzzleInput[param1] - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, output) - - comp.InstructionIndex += 2 - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day19/part2/main.go b/2019/day19/part2/main.go deleted file mode 100644 index 0b97b3b..0000000 --- a/2019/day19/part2/main.go +++ /dev/null @@ -1,235 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // wrapper to produce drones - deployDrone := droneFactory(inputNumbers) - - // follow the bottom edge of the tractor beam - for y := 100; ; { - for x := 0; ; { - // fmt.Printf("Coordinates x: %v, y %v\n", x, y) - if !deployDrone(x, y) { - // cell not being pulled, move right - x++ - // cell being pulled, check 2 spots, 100 up & 100 up and right - } else { - if deployDrone(x, y-99) && deployDrone(x+99, y-99) { - fmt.Println("Top left of 100x100 square is:", x, y-99) - return - } - // otherwise move down - y++ - } - } - } -} - -// takes in the inputNumbers, returns a function that initializes a new intcode computer -// returned function takes in x and y values, returns true if that cell is being "pulled" -func droneFactory(inputNumbers []int) func(int, int) bool { - return func(x, y int) bool { - drone := MakeComputer(inputNumbers) - drone.Step(x) - drone.Step(y) - lastOutput := drone.Outputs[len(drone.Outputs)-1] - - return lastOutput == 1 - } -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) *Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return &comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -// Update to run iteratively (while the computer is running) -// it will also return out if a -1 input is asked for -// then call Step again to provide the next input, or run with -1 from the start -// to run the computer until it asks for an input OR terminates -func (comp *Intcode) Step(input int) { - for comp.IsRunning { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - // fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - - // change the input value so the next time a 3 opcode is hit, will return out - input = -1 - case 4: // 4: outputs its input value - output := comp.PuzzleInput[param1] - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, output) - - comp.InstructionIndex += 2 - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day20/part1/main.go b/2019/day20/part1/main.go deleted file mode 100644 index fc7f71f..0000000 --- a/2019/day20/part1/main.go +++ /dev/null @@ -1,194 +0,0 @@ -package main - -import ( - "fmt" - "math" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - input := util.ReadFile("../input.txt") - lines := strings.Split(string(input), "\n") - grid := make([][]string, len(lines)) - for i, v := range lines { - grid[i] = strings.Split(v, "") - - // * Uncomment to print the input - // fmt.Println(grid[i]) - } - - dijkstra := MakeDijkstraGrid(grid) - // fmt.Println("initial dijkstra queue", dijkstra.queue) - - for !dijkstra.handleFrontOfQueue() { - // * watch the queue grow and shrink - // fmt.Println(" Queue", dijkstra.queue) - } - // fmt.Println("Final Queue", dijkstra.queue) - - fmt.Println("Distance to ZZ portal", dijkstra.grid[dijkstra.finishCoordinates[0]][dijkstra.finishCoordinates[1]].distance) -} - -// Dijkstra struct stores the 2D grid of nodes and a queue of next points to check -// and a portal map to add jumps to the queue -type Dijkstra struct { - grid [][]*Node - queue [][2]int - portalMap map[string][2]int // map to [2]int{} - startCoordinates [2]int - finishCoordinates [2]int -} - -// Node data type is custom built for this algo, i.e. also stores if this is a portal cell -type Node struct { - value string - distance int - portalName string // , will be used to jump to other indexes - jumpCoordinates [2]int // coordinates of its paired portal (if applicable) -} - -// MakeDijkstraGrid does just that -func MakeDijkstraGrid(inputGrid [][]string) *Dijkstra { - dijkstra := Dijkstra{} - portalMapHelper := make(map[string][2]int) - - grid := make([][]*Node, len(inputGrid)-4) - // iterate starting at 2,2 to skip the top and left and end len-2 to skip bottom & right - for row := 2; row < len(inputGrid)-2; row++ { - grid[row-2] = make([]*Node, len(inputGrid[0])-4) - for col := 2; col < len(inputGrid[0])-2; col++ { - // make a node for each cell - switch value := inputGrid[row][col]; value { - case "#": // wall - grid[row-2][col-2] = &Node{"#", math.MaxInt32, "", [2]int{0, 0}} - // if this is a hallway node, use a helper function to determine if there this is a portal - case ".": // hallway - hallwayNode := &Node{ - value: ".", - distance: math.MaxInt32, - portalName: "", - jumpCoordinates: [2]int{0, 0}, - } - portalName := getPortalName(inputGrid, row, col) - if len(portalName) != 0 { - // assign portal name for this node - hallwayNode.portalName = portalName - - // generatine the portal maps for each node is a pain... - // if this is portal's pair hasn't been found yet (i.e. equal to zero value of [2]int), add it to a map - if pairedPortal := portalMapHelper[portalName]; pairedPortal == [2]int{0, 0} { - portalMapHelper[portalName] = [2]int{row - 2, col - 2} - } else { - // else it has been found, set the jumpCoordinates on this node to pair's coords - hallwayNode.jumpCoordinates = pairedPortal - // set its pair's jumpCoordinates to this node's coords - grid[pairedPortal[0]][pairedPortal[1]].jumpCoordinates = [2]int{row - 2, col - 2} - } - } - grid[row-2][col-2] = hallwayNode - // if it is AA, update the distance of this node to zero, initialize queue - if portalName == "AA" { - // !! unused - dijkstra.startCoordinates = [2]int{row - 2, col - 2} - - hallwayNode.distance = 0 - dijkstra.queue = [][2]int{ - [2]int{row - 2, col - 2}, - } - } - // if end portal, set finish coordinates - if portalName == "ZZ" { - dijkstra.finishCoordinates = [2]int{row - 2, col - 2} - } - } - } - } - - // set grid field - dijkstra.grid = grid - - return &dijkstra -} - -// helper function to run in 4 directions and see if any of them are a capital letter -// if that's true, then grab the portal name in that direction and return it (two char string) -func getPortalName(grid [][]string, row, col int) string { - // NOTE I'm hard coding directions - leftTwo := grid[row][col-2] + grid[row][col-1] - rightTwo := grid[row][col+1] + grid[row][col+2] - upTwo := grid[row-2][col] + grid[row-1][col] - downTwo := grid[row+1][col] + grid[row+2][col] - - isPortalString := func(str string) bool { - ascii1 := str[0] - 'A' - ascii2 := str[1] - 'A' - - if ascii1 >= 0 && ascii1 < 26 && ascii2 >= 0 && ascii2 < 26 { - return true - } - return false - } - - // if both characters are capital letters - switch { - case isPortalString(leftTwo): - return leftTwo - case isPortalString(rightTwo): - return rightTwo - case isPortalString(upTwo): - return upTwo - case isPortalString(downTwo): - return downTwo - } - - return "" -} - -// returns true if the queue is empty OR the ZZ portal has been reached -func (dijkstra *Dijkstra) handleFrontOfQueue() (done bool) { - dRow := [4]int{0, 0, -1, 1} - dCol := [4]int{-1, 1, 0, 0} - - row, col := dijkstra.queue[0][0], dijkstra.queue[0][1] - currentNode := dijkstra.grid[row][col] - - if currentNode.portalName == "ZZ" { - return true - } - - for i := 0; i < 4; i++ { - nextRow, nextCol := row+dRow[i], col+dCol[i] - isInbounds := nextRow >= 0 && nextRow < len(dijkstra.grid) && nextCol >= 0 && nextCol < len(dijkstra.grid[0]) - if isInbounds { - // if the nextNode is a hallway & has not been traveled to yet - if nextNode := dijkstra.grid[nextRow][nextCol]; nextNode != nil && nextNode.value == "." && nextNode.distance == math.MaxInt32 { - // update the distance of the nextNode - nextNode.distance = currentNode.distance + 1 - // add its coordinates to the queue - dijkstra.queue = append(dijkstra.queue, [2]int{nextRow, nextCol}) - } - } - } - - // check if a portal jump is possible! - if currentNode.portalName != "" { - // find coordinates to jump to and the node itself - jumpRow := currentNode.jumpCoordinates[0] - jumpCol := currentNode.jumpCoordinates[1] - jumpNode := dijkstra.grid[jumpRow][jumpCol] - - // update distance - jumpNode.distance = currentNode.distance + 1 - // add to queue - dijkstra.queue = append(dijkstra.queue, currentNode.jumpCoordinates) - } - - // dequeue, return true if queue is now empty - dijkstra.queue = dijkstra.queue[1:] - if len(dijkstra.queue) == 0 { - return true - } - return false -} diff --git a/2019/day20/part2/main.go b/2019/day20/part2/main.go deleted file mode 100644 index 25326d8..0000000 --- a/2019/day20/part2/main.go +++ /dev/null @@ -1,289 +0,0 @@ -package main - -import ( - "fmt" - "math" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - input := util.ReadFile("../input.txt") - lines := strings.Split(string(input), "\n") - grid := make([][]string, len(lines)) - for i, v := range lines { - grid[i] = strings.Split(v, "") - - // * Uncomment to print the input - // fmt.Println(i-2, grid[i]) - } - - dijkstra := MakeDijkstraRecursive(grid) - - for !dijkstra.handleFrontOfQueue() { - // * Uncomment to watch the queue length grow - // fmt.Println(" QUEUE LENGTH ", len(dijkstra.queue)) - } - - finalLayer, finalRow, finalCol := 0, dijkstra.finishCoordinates[0], dijkstra.finishCoordinates[1] - - fmt.Println("Distance to ZZ", dijkstra.layers[finalLayer].grid[finalRow][finalCol].distance) -} - -// DijkstraRecursive struct stores the 2D grid of nodes and a queue of next points to check -// and a portal map to add jumps to the queue -type DijkstraRecursive struct { - sanitizedGrid [][]string // stores the raw 2D grid to be passed into a Layer factory - layers []*Layer - queue [][3]int - outerPortalCoords map[string][2]int // map portal name to coordinates on outer edge - innerPortalCoords map[string][2]int // map portal name to coordinates on inner edge - mapCoordsToPortals map[[2]int]string // arrays pass by value, so this can be used as a key - startCoordinates [2]int // coordinates on first layer - finishCoordinates [2]int // coordinates on first layer -} - -// Layer is a single layer of the 3D maze -// assuming the maze grows downwards -type Layer struct { - grid [][]*Node - layerIndex int // ground level == 0, one down == 1, two down == 2, etc. -} - -// Node data type is custom built for this algo, i.e. also stores if this is a portal cell -type Node struct { - value string - distance int - portalName string // , will be used to jump to other indexes - jumpCoordinates [3]int // coordinates of its paired portal if applicable -} - -// MakeDijkstraRecursive does just that -func MakeDijkstraRecursive(inputGrid [][]string) *DijkstraRecursive { - dijkstra := DijkstraRecursive{ - sanitizedGrid: make([][]string, len(inputGrid)-4), // preprocess the inputGrid to make future layer creation easier - layers: []*Layer{}, - queue: make([][3]int, 1), - outerPortalCoords: map[string][2]int{}, - innerPortalCoords: map[string][2]int{}, - mapCoordsToPortals: map[[2]int]string{}, - } - - for i := range dijkstra.sanitizedGrid { - dijkstra.sanitizedGrid[i] = inputGrid[i+2][2 : len(inputGrid[0])-2] - } - - // populate outer/innerPortalCoords, critical when generating Layers - // populating maps of jump coordinates - for row := 2; row < len(inputGrid)-2; row++ { - for col := 2; col < len(inputGrid[0])-2; col++ { - // if a hallway and portalName is not an empty string - portalName := getPortalName(inputGrid, row, col) - - if inputGrid[row][col] == "." && portalName != "" { - // add to map of coordinates to portal name - dijkstra.mapCoordsToPortals[[2]int{row - 2, col - 2}] = portalName - - // add to outer or inner portal coords maps - if onEdgeOfGrid(inputGrid, row-2, col-2) || onEdgeOfGrid(inputGrid, row+2, col+2) { - dijkstra.outerPortalCoords[portalName] = [2]int{row - 2, col - 2} - } else { - dijkstra.innerPortalCoords[portalName] = [2]int{row - 2, col - 2} - } - - // Initial and final portal detection - if portalName == "AA" { - dijkstra.startCoordinates = [2]int{row - 2, col - 2} - dijkstra.queue[0] = [3]int{0, row - 2, col - 2} - } - if portalName == "ZZ" { - dijkstra.finishCoordinates = [2]int{row - 2, col - 2} - } - } - } - } - - // create first layer - dijkstra.AddLayer() - - return &dijkstra -} - -// AddLayer will add a new layer to the dijkstra layers slice -// will be called as out of range layers are jumped to -func (dijkstra *DijkstraRecursive) AddLayer() (layerCount int) { - sanitizedGrid := dijkstra.sanitizedGrid - grid := make([][]*Node, len(sanitizedGrid)) - layerIndex := len(dijkstra.layers) - - // make copies of the outer/innerPortalMaps - innerPortalCoords, outerPortalCoords := map[string][3]int{}, map[string][3]int{} - // For all layers copy all outer portal coordinates except for AA and ZZ - for key, val := range dijkstra.outerPortalCoords { - // if jumping TO an outer portal, that means we're going DOWN a level - // so increment the first coordinate - outerPortalCoords[key] = [3]int{ - layerIndex + 1, - val[0], - val[1], - } - } - - // disallow jumping to an inner (lower) layer from layer0 because all outer edges are "blocked" - if layerIndex != 0 { - for key, val := range dijkstra.innerPortalCoords { - innerPortalCoords[key] = [3]int{ - layerIndex - 1, - val[0], - val[1]} - } - } - - for row := 0; row < len(sanitizedGrid); row++ { - grid[row] = make([]*Node, len(sanitizedGrid[0])) - for col := 0; col < len(sanitizedGrid); col++ { - switch value := sanitizedGrid[row][col]; value { - case "#": - grid[row][col] = &Node{"#", math.MaxInt32, "", [3]int{0, 0, 0}} - case ".": - grid[row][col] = &Node{ - value: ".", - distance: math.MaxInt32, - } - // get portal name and jump coord from maps if applicable - portalName, found := dijkstra.mapCoordsToPortals[[2]int{row, col}] - if found { - // ! this may go unused - grid[row][col].portalName = portalName - - // determine if inner or outer coordinates are the ones being jumped to - if onEdgeOfGrid(sanitizedGrid, row, col) { - grid[row][col].jumpCoordinates = innerPortalCoords[portalName] - } else { - grid[row][col].jumpCoordinates = outerPortalCoords[portalName] - } - } - // set initial distance for AA cell - if portalName == "AA" && layerIndex == 0 { - grid[row][col].distance = 0 - } - } - } - } - - dijkstra.layers = append(dijkstra.layers, &Layer{grid, layerIndex}) - return len(dijkstra.layers) -} - -// dequeues a set of coordinates, enqueues any of its appropriate neighbors (including potential -// portals/jumps and adds layers if necessary) -// returns true if the queue is empty OR the ZZ portal on layer0 has been reached -func (dijkstra *DijkstraRecursive) handleFrontOfQueue() (done bool) { - dRow := [4]int{0, 0, -1, 1} - dCol := [4]int{-1, 1, 0, 0} - - dequeued := dijkstra.queue[0] - layer, row, col := dequeued[0], dequeued[1], dequeued[2] - currentNode := dijkstra.layers[layer].grid[row][col] - - // return out of the final node has been found! - if currentNode.portalName == "ZZ" && layer == 0 { - return true - } - - // add layers on the same layer if they are hallways to traverse into - currentLayersGrid := dijkstra.layers[layer].grid - for i := 0; i < 4; i++ { - nextRow, nextCol := row+dRow[i], col+dCol[i] - isInbounds := nextRow >= 0 && nextRow < len(currentLayersGrid) && nextCol >= 0 && nextCol < len(currentLayersGrid[0]) - if isInbounds { - // if the nextNode is a hallway & has not been traveled to yet - if nextNode := currentLayersGrid[nextRow][nextCol]; nextNode != nil && nextNode.value == "." && nextNode.distance == math.MaxInt32 { - // update the distance of the nextNode - nextNode.distance = currentNode.distance + 1 - // add its coordinates to the queue, will always be on the same layer b/c this is NOT handling jumps - dijkstra.queue = append(dijkstra.queue, [3]int{layer, nextRow, nextCol}) - } - } - } - - // check if a portal jump is possible! - // also check if the jumpCoordinates are the zero value of a [3]int - if currentNode.portalName != "" { - // find coordinates to jump to and the node itself - jumpLayer := currentNode.jumpCoordinates[0] - jumpRow := currentNode.jumpCoordinates[1] - jumpCol := currentNode.jumpCoordinates[2] - - // if jump is going to be on an out of range layer, fire off an AddLayer - if jumpLayer == len(dijkstra.layers) { - dijkstra.AddLayer() - } - - // update distance of node being jumped to - jumpNode := dijkstra.layers[jumpLayer].grid[jumpRow][jumpCol] - jumpNode.distance = currentNode.distance + 1 - - // add to queue - dijkstra.queue = append(dijkstra.queue, currentNode.jumpCoordinates) - } - - // dequeue, return true if queue is now empty - dijkstra.queue = dijkstra.queue[1:] - if len(dijkstra.queue) == 0 { - fmt.Println("EMPTY QUEUE") - return true - } - return false -} - -/************************************************************************************* -*** SMALL HELPER FUNCTIONS -*************************************************************************************/ - -// helper function to run in 4 directions and see if any of them are a capital letter -// if that's true, then grab the portal name in that direction and return it (two char string) -func getPortalName(grid [][]string, row, col int) string { - // NOTE I'm hard coding directions - leftTwo := grid[row][col-2] + grid[row][col-1] - rightTwo := grid[row][col+1] + grid[row][col+2] - upTwo := grid[row-2][col] + grid[row-1][col] - downTwo := grid[row+1][col] + grid[row+2][col] - - isPortalString := func(str string) bool { - ascii1 := str[0] - 'A' - ascii2 := str[1] - 'A' - - if ascii1 >= 0 && ascii1 < 26 && ascii2 >= 0 && ascii2 < 26 { - return true - } - return false - } - - // if both characters are capital letters - switch { - case isPortalString(leftTwo): - return leftTwo - case isPortalString(rightTwo): - return rightTwo - case isPortalString(upTwo): - return upTwo - case isPortalString(downTwo): - return downTwo - } - - return "" -} - -func onEdgeOfGrid(grid [][]string, row, col int) bool { - if row == 0 || col == 0 { - return true - } - - if row == len(grid)-1 || col == len(grid[0])-1 { - return true - } - - return false -} diff --git a/2019/day21/part1/main.go b/2019/day21/part1/main.go deleted file mode 100644 index 8e6c387..0000000 --- a/2019/day21/part1/main.go +++ /dev/null @@ -1,230 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - comp := MakeComputer(inputNumbers) - - // Boolean, assembly logic... - // if B OR C are holes but D is safe. jump - // otherwise jump if A is a hole - instruction := convertStringToASCII("NOT B T\nNOT C J\nOR J T\nAND D J\nAND J T\n") - instruction = append(instruction, convertStringToASCII("NOT A J\nOR T J\nWALK\n")...) - for _, v := range instruction { - // fmt.Println(v) - comp.Step(v) - } - - // Print the output TEXT - for _, v := range comp.Outputs { - fmt.Printf("%v", cast.ASCIIIntToChar(v)) - } - - fmt.Printf("Hull damage: %v\n", comp.Outputs[len(comp.Outputs)-1]) -} - -// helper function to convert a string to its character's ASCII values -func convertStringToASCII(input string) []int { - result := make([]int, len(input)) - for i, v := range input { - // cast rune to int - result[i] = int(v) - } - return result -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) *Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return &comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -// Update to run iteratively (while the computer is running) -// it will also return out if a -1 input is asked for -// then call Step again to provide the next input, or run with -1 from the start -// to run the computer until it asks for an input OR terminates -func (comp *Intcode) Step(input int) { - for comp.IsRunning { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - // fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - - // change the input value so the next time a 3 opcode is hit, will return out - input = -1 - case 4: // 4: outputs its input value - output := comp.PuzzleInput[param1] - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, output) - - comp.InstructionIndex += 2 - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day21/part2/main.go b/2019/day21/part2/main.go deleted file mode 100644 index aeaaa2c..0000000 --- a/2019/day21/part2/main.go +++ /dev/null @@ -1,234 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - comp := MakeComputer(inputNumbers) - - // Boolean, assembly logic... - // if B OR C are holes but D is safe. jump - // modify this logic to also include "is H ground" - instruction := convertStringToASCII("NOT B J\nNOT C T\nOR T J\nAND D J\nAND H J\n") - // otherwise jump if A is a hole - instruction = append(instruction, convertStringToASCII("NOT A T\nOR T J\n")...) - instruction = append(instruction, convertStringToASCII("RUN\n")...) - - // write instructions to intcode/ascii computer - for _, v := range instruction { - // fmt.Println(v) - comp.Step(v) - } - - // Print the output TEXT - for _, v := range comp.Outputs { - fmt.Printf("%v", cast.ASCIIIntToChar(v)) - } - - fmt.Printf("Hull damage: %v\n", comp.Outputs[len(comp.Outputs)-1]) -} - -// helper function to convert a string to its character's ASCII values -func convertStringToASCII(input string) []int { - result := make([]int, len(input)) - for i, v := range input { - // cast rune to int - result[i] = int(v) - } - return result -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) *Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return &comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -// Update to run iteratively (while the computer is running) -// it will also return out if a -1 input is asked for -// then call Step again to provide the next input, or run with -1 from the start -// to run the computer until it asks for an input OR terminates -func (comp *Intcode) Step(input int) { - for comp.IsRunning { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - // fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - - // change the input value so the next time a 3 opcode is hit, will return out - input = -1 - case 4: // 4: outputs its input value - output := comp.PuzzleInput[param1] - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, output) - - comp.InstructionIndex += 2 - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day22/part1/main.go b/2019/day22/part1/main.go deleted file mode 100644 index 5661cf6..0000000 --- a/2019/day22/part1/main.go +++ /dev/null @@ -1,97 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -type instruction struct { - name string // name of instruction - number int // number -} - -func main() { - input := util.ReadFile("../input.txt") - lines := strings.Split(input, "\n") - - // make slice of all instructions - instructions := make([]instruction, len(lines)) - for i, line := range lines { - splitLine := strings.Split(line, " ") - if splitLine[len(splitLine)-1] == "stack" { - instructions[i] = instruction{name: "deal into new stack"} - } else { - name := strings.Join(splitLine[0:len(splitLine)-1], " ") - num, _ := strconv.Atoi(splitLine[len(splitLine)-1]) - - instructions[i] = instruction{ - name, - num, - } - } - } - - // make the deck - deck := make([]int, 10007) - for i := range deck { - deck[i] = i - } - - // iterate through instructions and apply the correct function to the deck - for _, inst := range instructions { - switch inst.name { - case "deal into new stack": - deck = dealIntoNewStack(deck) - case "deal with increment": - deck = dealWithIncrement(deck, inst.number) - case "cut": - deck = cut(deck, inst.number) - } - } - - // Find the 2019 card and print its index - for i, v := range deck { - if v == 2019 { - fmt.Println("position of card 2019 is", i) - } - } -} - -// side effects to handle "deal into new stack" command -func dealIntoNewStack(deck []int) []int { - // effectively just reverses the entire deck.. - for i := 0; i < len(deck)/2; i++ { - deck[i], deck[len(deck)-1-i] = deck[len(deck)-1-i], deck[i] - } - - // technically changed by side effects but will keep syntax similar - return deck -} - -// index could be negative -func cut(deck []int, index int) []int { - newDeck := make([]int, len(deck)) - - if index < 0 { - index = len(deck) + index - } - - for i := 0; i < len(deck); i++ { - newDeck[i] = deck[(i+index)%len(deck)] - } - return newDeck -} - -func dealWithIncrement(deck []int, jump int) []int { - newDeck := make([]int, len(deck)) - - for i := 0; i < len(deck); i++ { - newDeckIndex := jump * i % len(deck) - newDeck[newDeckIndex] = deck[i] - } - - return newDeck -} diff --git a/2019/day22/part2/main.go b/2019/day22/part2/main.go deleted file mode 100644 index df494ed..0000000 --- a/2019/day22/part2/main.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "math/big" - "strconv" - "strings" -) - -const CmdDeal string = "deal into new stack" -const CmdCutN string = "cut" -const CmdDealN string = "deal with increment" - -func MatchCmd(cmd, cmdToMatch string) (bool, int) { - if len(cmd) < len(cmdToMatch) || cmd[:len(cmdToMatch)] != cmdToMatch { - return false, 0 - } - - if len(cmd) == len(cmdToMatch) { - return true, 0 - } - - i := strings.LastIndex(cmd, " ") - x := cmd[i+1:] - n, err := strconv.Atoi(x) - if err != nil { - panic(err) - } - return true, n -} - -func ReadCommands(path string) []string { - dat, err := ioutil.ReadFile(path) - if err != nil { - panic(err) - } - dat = dat[:len(dat)] - - txt := string(dat) - txt = strings.TrimRight(txt, "\n") - lines := strings.Split(txt, "\n") - - return lines -} - -func IndexShuffle(index int64, l int64, times int64, cmds []string) int64 { - v := big.NewInt(index) - m := big.NewInt(l) - t := big.NewInt(times) - - i := big.NewInt(0) - d := big.NewInt(1) - - for _, cmd := range cmds { - if cmd == CmdDeal { - d.Neg(d) - i.Add(i, d) - } else if ok, n := MatchCmd(cmd, CmdCutN); ok { - x := big.NewInt(int64(n)) - i.Add(i, x.Mul(x, d)) - } else if ok, n := MatchCmd(cmd, CmdDealN); ok { - x := big.NewInt(int64(n)) - x.ModInverse(x, m) - d.Mul(d, x) - } - } - - // (1-d)**(m-2) % m - a := big.NewInt(1) - a.Sub(a, d).ModInverse(a, m) - - // d = d**t % m - d.Exp(d, t, m) - // it = (1-d) * a * i - it := big.NewInt(1) - it.Sub(it, d).Mul(it, a).Mul(it, i) - v.Mul(v, d).Add(v, it).Mod(v, m) - - return v.Int64() -} - -func main() { - cmds := ReadCommands("../input.txt") - - l := int64(119315717514047) - times := int64(101741582076661) - index := IndexShuffle(2020, l, times, cmds) - fmt.Println(index) -} diff --git a/2019/day23/part1/main.go b/2019/day23/part1/main.go deleted file mode 100644 index 447ef07..0000000 --- a/2019/day23/part1/main.go +++ /dev/null @@ -1,266 +0,0 @@ -/* -Intcode struct is defined within this file - -*/ - -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // make the network - network := Network{} - network.Init(inputNumbers) - - // run indefinitely... - for { - // assuming that I can step through each computer oen at a time, - // that they don't truly need to be running concurrently - for i := 0; i < 50; i++ { - // if this computer's queue is empty, use -1 as an input - if len(network.queues[i]) == 0 { - network.computers[i].Step(-1) - } else { - // Process off of the front of this computer's queue - front := network.queues[i][0] - network.computers[i].Step(front[0]) - network.computers[i].Step(front[1]) - - // dequeue - network.queues[i] = network.queues[i][1:] - } - - // while there are unhandled outputs of this computer, add them to the - // receiving computers's queues - for len(network.computers[i].Outputs) > 2 { - destination := network.computers[i].Outputs[0] - packet := [2]int{network.computers[i].Outputs[1], - network.computers[i].Outputs[2]} - - // if destination is 255, print Y of packet and exit out PART 1 Answer - if destination == 255 { - fmt.Println(packet[1]) - return - } - - // otherwise, add to queues - network.queues[destination] = append(network.queues[destination], packet) - - // remove three from Outputs slice - network.computers[i].Outputs = network.computers[i].Outputs[3:] - } - } - } -} - -// Network will hold all 50 NIC computers -type Network struct { - computers []*Intcode - queues [][][2]int // each element will be a packet for the same-index computer to handle -} - -// Init sets up the 50 computers and queues -func (network *Network) Init(puzzleInput []int) { - network.computers, network.queues = make([]*Intcode, 50), make([][][2]int, 50) - for i := 0; i < 50; i++ { - // Make and prime computer with its NIC number - network.computers[i] = MakeComputer(puzzleInput) - network.computers[i].Step(i) - } -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) *Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return &comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -// Update to run iteratively (while the computer is running) -// it will also return out if a -1 input is asked for -// then call Step again to provide the next input, or run with -1 from the start -// to run the computer until it asks for an input OR terminates -func (comp *Intcode) Step(input int) { - for comp.IsRunning { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - // fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -2 will never be an input... - // Note: changed the exit number to -2 because -1 is used in these computers for no-input/empty queues - if input == -2 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - - // change the input value so the next time a 3 opcode is hit, will return out - input = -2 - case 4: // 4: outputs its input value - output := comp.PuzzleInput[param1] - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, output) - - comp.InstructionIndex += 2 - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day23/part2/main.go b/2019/day23/part2/main.go deleted file mode 100644 index e68be11..0000000 --- a/2019/day23/part2/main.go +++ /dev/null @@ -1,291 +0,0 @@ -/* -Intcode struct is defined within this file -Network struct to store 50 instances of Intcode computers and 50 queues for their inputs - NAT variables are just stored in main goroutine instead of in another struct -*/ - -package main - -import ( - "fmt" - "log" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - // make the network - network := Network{} - network.Init(inputNumbers) - - // NAT packet (gets overwritten anytime a write to 255 happens) - // lastNatY value stored here and compared everytime the NAT will write to 0 - natPacket := [2]int{} - var lastNatY int - - // run indefinitely... - // assuming that I can step through each computer one at a time, - // that they don't truly need to be running concurrently - for { - // declare a boolean flag to signal when all computers are waiting for input - allNatsWaiting := true - - // iterate over all 50 computers - for i := 0; i < 50; i++ { - // if this computer's queue is empty, use -1 as an input - if len(network.queues[i]) == 0 { - network.computers[i].Step(-1) - } else { - // Flip boolean for allNatsWaiting if any - allNatsWaiting = false - - // Process off of the front of this computer's queue - front := network.queues[i][0] - network.computers[i].Step(front[0]) - network.computers[i].Step(front[1]) - - // dequeue - network.queues[i] = network.queues[i][1:] - } - - // while there are unhandled outputs of this computer, add them to the - // receiving computers's queues OR the NAT - for len(network.computers[i].Outputs) > 2 { - destination := network.computers[i].Outputs[0] - packet := [2]int{network.computers[i].Outputs[1], - network.computers[i].Outputs[2]} - - // remove three from Outputs slice - network.computers[i].Outputs = network.computers[i].Outputs[3:] - - // if destination is 255, overwrite NAT packet - if destination == 255 { - natPacket = packet - } else { - // otherwise, add to queue of correct NIC computer - network.queues[destination] = append(network.queues[destination], packet) - } - } - } - - // if all nat computers are waiting for inputs, write the natpacket to - // the zero-th computer's queue - if allNatsWaiting { - // check if this packet has a duplicate Y value, if so print github.com/alexchao26/advent-of-code-go output - if lastNatY == natPacket[1] { - fmt.Println(lastNatY, "written to NAT twice") - // stop the infinite loop - return - } - network.queues[0] = append(network.queues[0], natPacket) - lastNatY = natPacket[1] - } - } -} - -// Network will hold all 50 NIC computers -type Network struct { - computers []*Intcode - queues [][][2]int // each element will be a packet for the same-index computer to handle -} - -// Init sets up the 50 computers and queues -func (network *Network) Init(puzzleInput []int) { - network.computers, network.queues = make([]*Intcode, 50), make([][][2]int, 50) - for i := 0; i < 50; i++ { - // Make and prime computer with its NIC number - network.computers[i] = MakeComputer(puzzleInput) - network.computers[i].Step(i) - } -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) *Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return &comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -// Update to run iteratively (while the computer is running) -// it will also return out if a -1 input is asked for -// then call Step again to provide the next input, or run with -1 from the start -// to run the computer until it asks for an input OR terminates -func (comp *Intcode) Step(input int) { - for comp.IsRunning { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - // fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -2 will never be an input... - // Note: changed the exit number to -2 because -1 is used in these computers for no-input/empty queues - if input == -2 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - - // change the input value so the next time a 3 opcode is hit, will return out - input = -2 - case 4: // 4: outputs its input value - output := comp.PuzzleInput[param1] - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, output) - - comp.InstructionIndex += 2 - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day24/part1/main.go b/2019/day24/part1/main.go deleted file mode 100644 index 9c86a56..0000000 --- a/2019/day24/part1/main.go +++ /dev/null @@ -1,99 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - input := util.ReadFile("../input.txt") - - lines := strings.Split(input, "\n") - - grid := make([][]string, len(lines)) - for i, v := range lines { - grid[i] = strings.Split(v, "") - } - - // can map biodiversity scores because they're essentially bitmaps - previousBiodiversities := map[int]bool{getBiodiversity(grid): true} - - // run indefinitely - for { - // step through a minute - grid = stepMinute(grid) - newBiodiversity := getBiodiversity(grid) - - // if new biodiversity score is already in the map, print it and exit - if previousBiodiversities[newBiodiversity] { - fmt.Println("Repeated biodiversity score", newBiodiversity) - return - } - - // set biodiversity score into in map - previousBiodiversities[newBiodiversity] = true - } -} - -// steps through one minute and returns the next grid -func stepMinute(grid [][]string) [][]string { - result := make([][]string, len(grid)) - - dRow := [4]int{0, 0, -1, 1} - dCol := [4]int{-1, 1, 0, 0} - - for row, rowSli := range grid { - // initialize the rows for the result slice - result[row] = make([]string, len(grid[0])) - - for col, val := range rowSli { - // count up the neighbors that are bugs - var countNeighborBugs int - for i := 0; i < 4; i++ { - nextRow, nextCol := row+dRow[i], col+dCol[i] - isInbounds := nextRow >= 0 && nextCol >= 0 && nextRow < len(grid) && nextCol < len(grid[0]) - if isInbounds && grid[nextRow][nextCol] == "#" { - countNeighborBugs++ - } - } - - // determine future state of cell - switch val { - case "#": - // if bug has ONE neighbor only, it lives, otherwise becomes empty - if countNeighborBugs == 1 { - result[row][col] = "#" - } else { - result[row][col] = "." - } - case ".": - // if empty, becomes a bug if has ONE or TWO neighbors only - if countNeighborBugs == 1 || countNeighborBugs == 2 { - result[row][col] = "#" - } else { - result[row][col] = "." - } - } - } - } - - return result -} - -func getBiodiversity(grid [][]string) int { - var biodiversity int - for i, row := range grid { - for j, val := range row { - // dumb cheeky way to get power of two... 1 is 2^0, then shift the power - // 1 << 0 == 2^0 == 1; 1 << 1 == 2^1 - powerOfTwo := 1 << (5*i + j) - if val == "#" { - biodiversity += powerOfTwo - } - } - } - - return biodiversity -} diff --git a/2019/day24/part2/main.go b/2019/day24/part2/main.go deleted file mode 100644 index ec4c0e1..0000000 --- a/2019/day24/part2/main.go +++ /dev/null @@ -1,172 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -// RecursiveWorld stores a big 3D matrix & will have associated methods -type RecursiveWorld struct { - // 401 so there are 200 layers above and below initial layer - // using ints to expedite calculating neighbor sums and initializing as 0s - levels [401][5][5]int -} - -func main() { - input := util.ReadFile("../input.txt") - lines := strings.Split(input, "\n") - - var initialGrid [5][5]int - for i, line := range lines { - for j, v := range line { - if v == '#' { - initialGrid[i][j] = 1 - } - } - } - - // initialize recursive world - zero values of array will start every cell at 0 - var world RecursiveWorld - // set the "middle" layer at 200 - this works because we're only running for 200 minutes - world.levels[200] = initialGrid - - // run for 200 minutes - for i := 0; i < 200; i++ { - world.minute() - } - - // print the final count - fmt.Println("Final count", world.countBugs()) -} - -func (world *RecursiveWorld) minute() { - nextMinuteLevels := [401][5][5]int{} - - for i := 0; i < 401; i++ { - for row := 0; row < 5; row++ { - for col := 0; col < 5; col++ { - sumNeighbors := world.getSumOfNeighbors(i, row, col) - nextMinuteLevels[i][row][col] = world.nextCellValue( - world.levels[i][row][col], sumNeighbors) - } - } - } - - // reassign levels - world.levels = nextMinuteLevels -} - -// get the sum of neighbors, including recursive layers -func (world *RecursiveWorld) getSumOfNeighbors(i, j, k int) int { - // center should always remain zero - if j == 2 && k == 2 { - return 0 - } - - dx, dy := [4]int{0, 0, -1, 1}, [4]int{-1, 1, 0, 0} - - var sumOfNeighbors int - for d := 0; d < 4; d++ { - nextRow, nextCol := j+dx[d], k+dy[d] - isInBounds := nextRow >= 0 && nextCol >= 0 && nextRow < 5 && nextCol < 5 - - // if not in bounds, this cell is trying to access the layer "outside" of it - if !isInBounds { - sumOfNeighbors += world.getNeighborsOut(i+1, nextRow, nextCol) - } else if nextRow == 2 && nextCol == 2 { - // if a neighbor cell has 2,2 coordinates, it is trying to recurse "in" - sumOfNeighbors += world.getNeighborsIn(i-1, j, k) - } else if isInBounds { - // otherwise if it is inbounds, add from this layer - sumOfNeighbors += world.levels[i][nextRow][nextCol] - } - } - - return sumOfNeighbors -} - -// Assuming going outwards moves UP level indexes -// nextRow and nextCol are the requested coordinates from the origin/cell calling this function -func (world *RecursiveWorld) getNeighborsOut(level, nextRow, nextCol int) int { - // edge case for "recursive" calls asking for -1 layer or 401 layer - if level == -1 || level == 401 { - return 0 - } - currentLevel := world.levels[level] - - // origin cell is asking for "above" itself - if nextRow == -1 { - return currentLevel[1][2] - } - // origin cell asking for "below" itself - if nextRow == 5 { - return currentLevel[3][2] - } - // asking for "left" - if nextCol == -1 { - return currentLevel[2][1] - } - // asking for "right" - return currentLevel[2][3] -} - -// Assume going inwards moves DOWN level indices -// originRow and Col are coordinates of the cell requesting its neighboring values -func (world *RecursiveWorld) getNeighborsIn(level, originRow, originCol int) int { - // edge case for "recursive" calls asking for -1 layer or 401 layer - if level == -1 || level == 401 { - return 0 - } - // sum up 5 values of the argument level - currentLevel := world.levels[level] - var left, right, top, bottom int - for i := 0; i < 5; i++ { - left += currentLevel[i][0] - right += currentLevel[i][4] - top += currentLevel[0][i] - bottom += currentLevel[4][i] - } - - // if originRow is 1, then it was above this layer, return top values - if originRow == 1 { - return top - } - // if originRow is 3, it is below this layer - if originRow == 3 { - return bottom - } - // if originCol is 1, it is left of this layer - if originCol == 1 { - return left - } - // otherwise only remaining direction is right - return right -} - -// gets the next values of a cell given the old value of the cell & sum of its neighbors -func (world *RecursiveWorld) nextCellValue(oldVal, sumNeighbors int) int { - if oldVal == 1 && sumNeighbors == 1 { - return 1 - } - if oldVal == 0 && (sumNeighbors == 1 || sumNeighbors == 2) { - return 1 - } - - return 0 -} - -// count up the bugs in every level and return it -func (world *RecursiveWorld) countBugs() int { - var bugs int - for _, grid := range world.levels { - for _, row := range grid { - for _, val := range row { - bugs += val - } - } - } - - return bugs -} diff --git a/2019/day25/floorplan.md b/2019/day25/floorplan.md deleted file mode 100644 index 83955a0..0000000 --- a/2019/day25/floorplan.md +++ /dev/null @@ -1,38 +0,0 @@ - +------------+------------+ -+------------+ | | | -| | | HOT | NAVIGATION | -| __WEIGH__ | | CHOCOLATE | *easter | -|__STATION__ | | FOUNTAIN | egg | -| | +------------+------------+ -+------------+------------+ | | -| | | | STORAGE | -| SECURITY | KITCHEN | | *coin | -| CHECKPOINT | *shell | | | -| | | +--------+------------+ -+------------+------------+ | | | - | | |HOLODECK| PASSAGES | - | CORRIDOR | | | *hypercube | - | *spool of | +--------| | - | cat6 | | | - +------------+------------+------------+ - | | | - | CREW | ARCADE | - | QUARTERS | *giant | - | | electro | -+------------+-----+--------------+----+ magnet | -| | | ENGINE ROOM | | | -| HALLWAY | | *molten lava | +------------+ -| *sand | +--------------+ | | -| | | | -+------------+------------+------------+ OBSERVATORY+------------+ -| | | *asterisk | | -| STABLES | __HULL BREACH__ | | GIFT | -| *fixed | | | WRAPPING | -| point | | | STATION | -+------------+-----+--------------+----+------------+------------+------------+ - | SICK BAY | | | | - |*infinite loop| | WARP | SCIENCE | - +--------------+ | DRIVE | LAB | - | *escape pod| *photons | - +------------+------------+ - \ No newline at end of file diff --git a/2019/day25/part1/main.go b/2019/day25/part1/main.go deleted file mode 100644 index 5900e4b..0000000 --- a/2019/day25/part1/main.go +++ /dev/null @@ -1,243 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "log" - "os" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - // read the input file, modify it to a slice of numbers - inputFile := util.ReadFile("../input.txt") - - splitStrings := strings.Split(inputFile, ",") - - inputNumbers := make([]int, len(splitStrings)) - for i, v := range splitStrings { - inputNumbers[i], _ = strconv.Atoi(v) - } - - comp := MakeComputer(inputNumbers) - comp.Step(-1) - - for comp.IsRunning { - // get line from the terminal, convert it to ascii and write it to the intcode computer - text := readFromCommandLine() - - instruction := convertStringToASCII(text) - - for _, v := range instruction { - comp.Step(v) - } - - // NOTE: Collect four items: coin, spool of cat6, sand, and fixed point, - // NOTE: then proceed through the weigh station - // NOTE: I figured this out by picking up everything and just eliminating - // NOTE: options using the feedback of "too heavy or too light" - - // ! Code is 2181046280 - } -} - -func readFromCommandLine() string { - reader := bufio.NewReader(os.Stdin) - text, _ := reader.ReadString('\n') - return text -} - -// helper function to convert a string to its character's ASCII values -func convertStringToASCII(input string) []int { - result := make([]int, len(input)) - for i, v := range input { - // cast rune to int - result[i] = int(v) - } - return result -} - -/* -Intcode is an OOP approach ************************************************* -MakeComputer is equivalent to the constructor -Step takes in an input int and updates properties in the computer: - - InstructionIndex: where to read the next instruction from - - LastOutput, what the last opcode 4 outputted - - PuzzleIndex based if the last instruction modified the puzzle at all -****************************************************************************/ -type Intcode struct { - PuzzleInput []int // file/puzzle input parsed into slice of ints - InstructionIndex int // stores the index where the next instruction is - RelativeBase int // relative base for opcode 9 and param mode 2 - Outputs []int // stores all outputs - IsRunning bool // will be true until a 99 opcode is hit -} - -// MakeComputer initializes a new comp -func MakeComputer(PuzzleInput []int) *Intcode { - puzzleInputCopy := make([]int, len(PuzzleInput)) - copy(puzzleInputCopy, PuzzleInput) - - comp := Intcode{ - puzzleInputCopy, - 0, - 0, - make([]int, 0), - true, - } - return &comp -} - -// Step will read the next 4 values in the input `sli` and make updates -// according to the opcodes -// Update to run iteratively (while the computer is running) -// it will also return out if a -1 input is asked for -// then call Step again to provide the next input, or run with -1 from the start -// to run the computer until it asks for an input OR terminates -func (comp *Intcode) Step(input int) { - for comp.IsRunning { - // read the instruction, opcode and the indexes where the params point to - opcode, paramIndexes := comp.GetOpCodeAndParamIndexes() - param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2] - - // ensure params are within the bounds of PuzzleInput, resize if necessary - switch opcode { - case 1, 2, 7, 8: - comp.ResizeMemory(param1, param2, param3) - case 5, 6: - comp.ResizeMemory(param1, param2) - case 3, 4, 9: - comp.ResizeMemory(param1) - } - - switch opcode { - case 99: // 99: Terminates program - // fmt.Println("Terminating...") - comp.IsRunning = false - case 1: // 1: Add next two paramIndexes, store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 2: // 2: Multiply next two and store in third - comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2] - comp.InstructionIndex += 4 - case 3: // 3: Takes one input and saves it to position of one parameter - // check if input has already been used (i.e. input == -1) - // if it's been used, return out to prevent further Steps - // NOTE: making a big assumption that -1 will never be an input... - if input == -1 { - return - } - - // else recurse with a -1 to signal the initial input has been processed - comp.PuzzleInput[param1] = input - comp.InstructionIndex += 2 - - // change the input value so the next time a 3 opcode is hit, will return out - input = -1 - case 4: // 4: outputs its input value - output := comp.PuzzleInput[param1] - // set LastOutput of the computer & log it - comp.Outputs = append(comp.Outputs, output) - - // NOTE: day25 specific, print out the output message to play the game - fmt.Print(cast.ASCIIIntToChar(output)) - - comp.InstructionIndex += 2 - // 5: jump-if-true: if first param != 0, move pointer to second param, else nothing - case 5: - if comp.PuzzleInput[param1] != 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing - case 6: - if comp.PuzzleInput[param1] == 0 { - comp.InstructionIndex = comp.PuzzleInput[param2] - } else { - comp.InstructionIndex += 3 - } - // 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0 - case 7: - if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0 - case 8: - if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] { - comp.PuzzleInput[param3] = 1 - } else { - comp.PuzzleInput[param3] = 0 - } - comp.InstructionIndex += 4 - // 9: adjust relative base - case 9: - comp.RelativeBase += comp.PuzzleInput[param1] - comp.InstructionIndex += 2 - default: - log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex]) - } - } -} - -/* -GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex] -- opcode will be the left two digits, mod by 100 will get that -- rest of instructions will be grabbed via mod 10 - - these also have to be parsed for the -*/ -func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) { - instruction := comp.PuzzleInput[comp.InstructionIndex] - - // opcode is the lowest two digits, so mod by 100 - opcode := instruction % 100 - instruction /= 100 - - // assign the indexes that need to be read by reading the parameter modes - var paramIndexes [3]int - for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ { - // grab the mode with a mod, last digit - mode := instruction % 10 - instruction /= 10 - - switch mode { - case 0: // position mode, index will be the value at the index - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] - case 1: // immediate mode, the index itself - paramIndexes[i-1] = comp.InstructionIndex + i - case 2: // relative mode, like position mode but index is added to relative base - paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase - } - } - - return opcode, paramIndexes -} - -// ResizeMemory will take any number of integers and resize the computer's memory appropriately -func (comp *Intcode) ResizeMemory(sizes ...int) { - // get largest of input sizes - maxArg := sizes[0] - for _, v := range sizes { - if v > maxArg { - maxArg = v - } - } - - // resize if PuzzleInput's length is shorter - if maxArg >= len(comp.PuzzleInput) { - // make empty slice to copy into, of the new, larger size - resizedPuzzleInput := make([]int, maxArg+1) - // copy old puzzle input values in - copy(resizedPuzzleInput, comp.PuzzleInput) - - // overwrite puzzle input - comp.PuzzleInput = resizedPuzzleInput - } -} diff --git a/2019/day25/part2/main.go b/2019/day25/part2/main.go deleted file mode 100644 index 996a6c1..0000000 --- a/2019/day25/part2/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "fmt" - -func main() { - fmt.Println("There is no part2, the 50th star is the sun!") -} diff --git a/2020/day01/main.go b/2020/day01/main.go deleted file mode 100644 index 0907907..0000000 --- a/2020/day01/main.go +++ /dev/null @@ -1,68 +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) - - 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 { - parsed := parseInputs(input) - - seen := map[int]bool{} - - for _, n := range parsed { - if seen[n] { - return n * (2020 - n) - } - seen[2020-n] = true - } - - return -1 // should not be hit -} - -func part2(input string) int { - parsed := parseInputs(input) - - // O(n^3) is fast enough - for i := 0; i < len(parsed); i++ { - for j := i + 1; j < len(parsed); j++ { - for k := j + 1; k < len(parsed); k++ { - if parsed[i]+parsed[j]+parsed[k] == 2020 { - return parsed[i] * parsed[j] * parsed[k] - } - } - } - } - - return -1 // should not be hit -} - -func parseInputs(input string) []int { - split := strings.Split(input, "\n") - - nums := []int{} - for _, n := range split { - nums = append(nums, cast.ToInt(n)) - } - - return nums -} diff --git a/2020/day01/main_test.go b/2020/day01/main_test.go deleted file mode 100644 index 7438091..0000000 --- a/2020/day01/main_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example", 514579, `1721 -979 -366 -299 -675 -1456`}, - {"actual", 1019371, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example", 241861950, `1721 -979 -366 -299 -675 -1456`}, - {"actual", 278064990, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} diff --git a/2020/day02/main.go b/2020/day02/main.go deleted file mode 100644 index fb1d3be..0000000 --- a/2020/day02/main.go +++ /dev/null @@ -1,81 +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) - - 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 { - lines := strings.Split(input, "\n") - - var ans int - - for _, l := range lines { - var lower, upper int - var char, pw string - - _, err := fmt.Sscanf(l, "%d-%d %1s: %s", &lower, &upper, &char, &pw) - if err != nil { - panic("scanning line " + err.Error()) - } - - count := 0 - for _, v := range pw { - if string(v) == char { - count++ - } - } - if count <= upper && count >= lower { - ans++ - } - } - - return ans -} - -func part2(input string) int { - lines := strings.Split(input, "\n") - - var ans int - for _, l := range lines { - var lower, upper int - var char, pw string - - _, err := fmt.Sscanf(l, "%d-%d %1s: %s", &lower, &upper, &char, &pw) - if err != nil { - panic("scanning line " + err.Error()) - } - - count := 0 - if string(pw[lower-1]) == char { - count++ - } - if string(pw[upper-1]) == char { - count++ - } - - if count == 1 { - ans++ - } - } - - return ans -} diff --git a/2020/day02/main_test.go b/2020/day02/main_test.go deleted file mode 100644 index 75f1b03..0000000 --- a/2020/day02/main_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example", 2, `1-3 a: abcde -1-3 b: cdefg -2-9 c: ccccccccc`}, - {"actual", 645, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(*testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example", 1, `1-3 a: abcde -1-3 b: cdefg -2-9 c: ccccccccc`}, - {"actual", 737, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(*testing.T) { - got := part2(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} diff --git a/2020/day03/main.go b/2020/day03/main.go deleted file mode 100644 index 978ab6a..0000000 --- a/2020/day03/main.go +++ /dev/null @@ -1,74 +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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - grid := parseInput(input) - return rideSlopes(grid, 3, 1) -} - -func part2(input string) int { - grid := parseInput(input) - - slopes := [][2]int{ - // right, down - {1, 1}, - {3, 1}, - {5, 1}, - {7, 1}, - {1, 2}, - } - - ans := 1 - for _, slope := range slopes { - ans *= rideSlopes(grid, slope[0], slope[1]) - } - - return ans -} - -func parseInput(input string) (grid [][]string) { - lines := strings.Split(input, "\n") - - grid = make([][]string, len(lines)) - for i, l := range lines { - grid[i] = strings.Split(l, "") - } - - return grid -} - -func rideSlopes(grid [][]string, right, down int) int { - var ans int - - for row, col := 0, 0; row < len(grid); row, col = row+down, col+right { - if grid[row][col%len(grid[0])] == "#" { - ans++ - } - } - - return ans -} diff --git a/2020/day03/main_test.go b/2020/day03/main_test.go deleted file mode 100644 index 585779b..0000000 --- a/2020/day03/main_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string -}{ - {"example", 7, `..##....... -#...#...#.. -.#....#..#. -..#.#...#.# -.#...##..#. -..#.##..... -.#.#.#....# -.#........# -#.##...#... -#...##....# -.#..#...#.#`}, - {"actual", 209, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, test := range tests1 { - t.Run(test.name, func(t *testing.T) { - got := part1(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string -}{ - {"example", 336, `..##....... -#...#...#.. -.#....#..#. -..#.#...#.# -.#...##..#. -..#.##..... -.#.#.#....# -.#........# -#.##...#... -#...##....# -.#..#...#.#`}, - {"actual", 1574890240, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, test := range tests2 { - t.Run(test.name, func(t *testing.T) { - got := part2(test.input) - if got != test.want { - t.Errorf("got %v, want %v", got, test.want) - } - }) - } -} diff --git a/2020/day04/main.go b/2020/day04/main.go deleted file mode 100644 index 815a03d..0000000 --- a/2020/day04/main.go +++ /dev/null @@ -1,148 +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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - parsed := parseInput(input) - - requiredFields := []string{ - "byr", - "iyr", - "eyr", - "hgt", - "hcl", - "ecl", - "pid", - // "cid", // optional - } - var ans int - for _, entry := range parsed { - hasAllFields := true - for _, field := range requiredFields { - if entry[field] == "" { - hasAllFields = false - break - } - } - if hasAllFields { - ans++ - } - } - - return ans -} - -func part2(input string) int { - passports := parseInput(input) - - var ans int - for _, entry := range passports { - if validateFields(entry) { - ans++ - } - } - - return ans -} - -func parseInput(input string) []map[string]string { - var passports []map[string]string - - lines := strings.Split(input, "\n\n") - - for _, l := range lines { - passportDetails := map[string]string{} - - oneLine := strings.ReplaceAll(l, "\n", " ") - for _, entry := range strings.Split(oneLine, " ") { - splitEntry := strings.Split(entry, ":") - passportDetails[splitEntry[0]] = splitEntry[1] - } - passports = append(passports, passportDetails) - } - - return passports -} - -func validateFields(entry map[string]string) bool { - byr := entry["byr"] - if byr > "2002" || byr < "1920" || len(byr) != 4 { - return false - } - - iyr := entry["iyr"] - if iyr > "2020" || iyr < "2010" || len(iyr) != 4 { - return false - } - - eyr := entry["eyr"] - if eyr > "2030" || eyr < "2020" || len(eyr) != 4 { - return false - } - - hgt := entry["hgt"] - if hgt == "" { - return false - } - - var num int - var unit string - fmt.Sscanf(hgt, "%d%s", &num, &unit) - - switch unit { - case "cm": - if num < 150 || num > 193 { - return false - } - case "in": - if num < 59 || num > 76 { - return false - } - default: - return false - } - - hcl := entry["hcl"] - reg := regexp.MustCompile("^#[0-9a-f]{6}$") - if !reg.Match([]byte(hcl)) { - return false - } - - ecl := entry["ecl"] - reg = regexp.MustCompile("^(amb|blu|brn|gry|grn|hzl|oth)$") - if !reg.Match([]byte(ecl)) { - return false - } - - pid := entry["pid"] - reg = regexp.MustCompile("^[0-9]{9}$") - if !reg.Match([]byte(pid)) { - return false - } - - return true -} diff --git a/2020/day04/main_test.go b/2020/day04/main_test.go deleted file mode 100644 index 37de4dd..0000000 --- a/2020/day04/main_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example", 2, `ecl:gry pid:860033327 eyr:2020 hcl:#fffffd -byr:1937 iyr:2017 cid:147 hgt:183cm - -iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884 -hcl:#cfa07d byr:1929 - -hcl:#ae17e1 iyr:2013 -eyr:2024 -ecl:brn pid:760753108 byr:1931 -hgt:179cm - -hcl:#cfa07d eyr:2025 pid:166559648 -iyr:2011 ecl:brn hgt:59in`}, - {"actual", 233, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"all valid", 4, `pid:087499704 hgt:74in ecl:grn iyr:2012 eyr:2030 byr:1980 -hcl:#623a2f - -eyr:2029 ecl:blu cid:129 byr:1989 -iyr:2014 pid:896056539 hcl:#a97842 hgt:165cm - -hcl:#888785 -hgt:164cm byr:2001 iyr:2015 cid:88 -pid:545766238 ecl:hzl -eyr:2022 - -iyr:2010 hgt:158cm hcl:#b6652a ecl:blu byr:1944 eyr:2021 pid:093154719`}, - {"all invalid", 0, `eyr:1972 cid:100 -hcl:#18171d ecl:amb hgt:170 pid:186cm iyr:2018 byr:1926 - -iyr:2019 -hcl:#602927 eyr:1967 hgt:170cm -ecl:grn pid:012533040 byr:1946 - -hcl:dab227 iyr:2012 -ecl:brn hgt:182cm pid:021572410 eyr:2020 byr:1992 cid:277 - -hgt:59cm ecl:zzz -eyr:2038 hcl:74454a iyr:2023 -pid:3556412378 byr:2007`}, - {"actual", 111, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day05/main.go b/2020/day05/main.go deleted file mode 100644 index 71e37a0..0000000 --- a/2020/day05/main.go +++ /dev/null @@ -1,113 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "log" - "strings" -) - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - bytes, err := ioutil.ReadFile("input.txt") - if err != nil { - log.Fatalf("Reading file: %s", err) - } - - if part == 1 { - ans := part1(string(bytes)) - fmt.Println("Output:", ans) - } else { - ans := part2(string(bytes)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - var best int - for _, seatDetails := range strings.Split(input, "\n") { - if len(seatDetails) == 0 { - continue - } - rowLeft, rowRight := 0, 127 - for _, char := range seatDetails[0:6] { - if char == 'F' { - rowRight -= (rowRight - rowLeft + 1) / 2 - } else { - rowLeft += (rowRight - rowLeft + 1) / 2 - } - } - if seatDetails[6] == 'B' { - rowLeft = rowRight - } - - colLeft, colRight := 0, 7 - for _, col := range seatDetails[7:9] { - if col == 'L' { - colRight -= (colRight - colLeft + 1) / 2 - } else { - colLeft += (colRight - colLeft + 1) / 2 - } - } - - if seatDetails[9] == 'R' { - colLeft = colRight - } - - id := rowLeft*8 + colLeft - if id > best { - best = id - } - } - - return best -} - -func part2(input string) int { - allSeatIDs := make(map[int]bool) - - for _, seatDetails := range strings.Split(input, "\n") { - rowLeft, rowRight := 0, 127 - for _, char := range seatDetails[0:6] { - if char == 'F' { - rowRight -= (rowRight - rowLeft + 1) / 2 - } else { - rowLeft += (rowRight - rowLeft + 1) / 2 - } - } - if seatDetails[6] == 'B' { - rowLeft = rowRight - } - - colLeft, colRight := 0, 7 - for _, col := range seatDetails[7:9] { - if col == 'L' { - colRight -= (colRight - colLeft + 1) / 2 - } else { - colLeft += (colRight - colLeft + 1) / 2 - } - } - - if seatDetails[9] == 'R' { - colLeft = colRight - } - - id := rowLeft*8 + colLeft - allSeatIDs[id] = true - } - - var mySeatID int - for id := range allSeatIDs { - if allSeatIDs[id] && allSeatIDs[id+2] && !allSeatIDs[id+1] { - mySeatID = id + 1 - break - } - } - - return mySeatID -} diff --git a/2020/day05/main_test.go b/2020/day05/main_test.go deleted file mode 100644 index 718e329..0000000 --- a/2020/day05/main_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string -}{ - {"actual", 822, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string -}{ - {"actual", 705, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day06/main.go b/2020/day06/main.go deleted file mode 100644 index f75b63e..0000000 --- a/2020/day06/main.go +++ /dev/null @@ -1,72 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "strings" -) - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - fileBytes, err := ioutil.ReadFile("input.txt") - if err != nil { - panic("Reading file" + err.Error()) - } - - if part == 1 { - ans := part1(string(fileBytes)) - fmt.Println("Output:", ans) - } else { - ans := part2(string(fileBytes)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - var sum int - - groups := strings.Split(input, "\n\n") - for _, group := range groups { - questionsSeen := map[string]bool{} - - people := strings.Split(group, "\n") - for _, person := range people { - for _, question := range strings.Split(person, "") { - questionsSeen[question] = true - } - } - - sum += len(questionsSeen) - } - - return sum -} - -func part2(input string) int { - var sum int - - groups := strings.Split(input, "\n\n") - for _, group := range groups { - questionsToCount := map[string]int{} - - people := strings.Split(group, "\n") - for _, person := range people { - for _, question := range strings.Split(person, "") { - questionsToCount[question]++ - } - } - - for _, count := range questionsToCount { - if count == len(people) { - sum++ - } - } - } - - return sum -} diff --git a/2020/day06/main_test.go b/2020/day06/main_test.go deleted file mode 100644 index 72a9352..0000000 --- a/2020/day06/main_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string -}{ - {"actual", 6387, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string -}{ - {"actual", 6, `abc - -a -b -c - -ab -ac - -a -a -a -a - -b`}, - {"actual", 3039, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day07/main.go b/2020/day07/main.go deleted file mode 100644 index 884abeb..0000000 --- a/2020/day07/main.go +++ /dev/null @@ -1,95 +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) - - 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 { - graph := parseInput(input) - delete(graph, "shiny gold") // shouldn't be allowed in this graph just in case - - var outers []string - // traverse edges until a "shiny gold bag" is found as a content? - for bag := range graph { - if dfsFindShinyGold(graph, bag) { - outers = append(outers, bag) - } - } - - return len(outers) -} - -func part2(input string) int { - graph := parseInput(input) - - // subtract one for the shiny gold bag counting itself - return countSubbags(graph, "shiny gold") - 1 -} - -// not a fun input to parse through, there has to be a better way to do this in go... -func parseInput(input string) map[string]map[string]int { - graph := map[string]map[string]int{} - for _, line := range strings.Split(input, "\n") { - split := strings.Split(line, " contain ") - color := split[0][:strings.Index(split[0], " bags")] - graph[color] = map[string]int{} - - for _, content := range strings.Split(split[1], ", ") { - if content == "no other bags." { - continue - } - parts := strings.Split(content, " ") - graph[color][parts[1]+" "+parts[2]] = cast.ToInt(parts[0]) - } - } - return graph -} - -func dfsFindShinyGold(graph map[string]map[string]int, entry string) bool { - // shiny gold is contained within this bag, return true & collapse call stack - if _, ok := graph[entry]["shiny gold"]; ok { - return true - } - - // otherwise traverse into its edges and if it returns true, send down callstack - for subBags := range graph[entry] { - if dfsFindShinyGold(graph, subBags) { - return true - } - } - - // otherwise shiny gold was not reached, just return false - return false -} - -func countSubbags(graph map[string]map[string]int, entry string) int { - // count itself - bags := 1 - - // traverse into its subbags and add time its count - for subBag, count := range graph[entry] { - bags += count * countSubbags(graph, subBag) - } - - return bags -} diff --git a/2020/day07/main_test.go b/2020/day07/main_test.go deleted file mode 100644 index 58e440d..0000000 --- a/2020/day07/main_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example", 4, `light red bags contain 1 bright white bag, 2 muted yellow bags. -dark orange bags contain 3 bright white bags, 4 muted yellow bags. -bright white bags contain 1 shiny gold bag. -muted yellow bags contain 2 shiny gold bags, 9 faded blue bags. -shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags. -dark olive bags contain 3 faded blue bags, 4 dotted black bags. -vibrant plum bags contain 5 faded blue bags, 6 dotted black bags. -faded blue bags contain no other bags. -dotted black bags contain no other bags.`}, - {"actual", 119, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string - // add extra args if needed -}{ - {"example", 32, `light red bags contain 1 bright white bag, 2 muted yellow bags. -dark orange bags contain 3 bright white bags, 4 muted yellow bags. -bright white bags contain 1 shiny gold bag. -muted yellow bags contain 2 shiny gold bags, 9 faded blue bags. -shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags. -dark olive bags contain 3 faded blue bags, 4 dotted black bags. -vibrant plum bags contain 5 faded blue bags, 6 dotted black bags. -faded blue bags contain no other bags. -dotted black bags contain no other bags.`}, - {"example 2", 126, `shiny gold bags contain 2 dark red bags. -dark red bags contain 2 dark orange bags. -dark orange bags contain 2 dark yellow bags. -dark yellow bags contain 2 dark green bags. -dark green bags contain 2 dark blue bags. -dark blue bags contain 2 dark violet bags. -dark violet bags contain no other bags.`}, - {"actual", 155802, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day08/intcode.go b/2020/day08/intcode.go deleted file mode 100644 index 426cb4b..0000000 --- a/2020/day08/intcode.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "fmt" - "strings" -) - -func newComputerFromInput(input string) computer { - var instructions []instruction - - for _, l := range strings.Split(input, "\n") { - inst := instruction{} - fmt.Sscanf(l, "%s %d", &inst.operation, &inst.argument) - instructions = append(instructions, inst) - } - - return computer{instructions: instructions} -} - -type computer struct { - instructions []instruction - index int - accumulator int -} - -type instruction struct { - operation string - argument int -} - -func (c *computer) step() { - switch inst := c.instructions[c.index]; inst.operation { - case "acc": - c.accumulator += inst.argument - c.index++ - case "jmp": - c.index += inst.argument - case "nop": - c.index++ - default: - panic("unhandled operation type" + inst.operation) - } -} - -// func isInfiniteLoop(comp computer) (finalAccumulatorVal int, isLoop bool) { -// ranInstructionsIndices := map[int]bool{} -// for comp.index < len(comp.instructions) { -// nextInst := comp.index -// // is an infinite loop, return out -// if ranInstructionsIndices[nextInst] { -// return 0, true -// } -// ranInstructionsIndices[nextInst] = true - -// comp.step() -// } - -// // instructions finished, return final accumulator & indicate it was not an -// // infinite loop -// return comp.accumulator, false -// } diff --git a/2020/day08/main.go b/2020/day08/main.go deleted file mode 100644 index cdec30f..0000000 --- a/2020/day08/main.go +++ /dev/null @@ -1,89 +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) - - 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 { - comp := newComputerFromInput(input) - - // keep track of all the indices of instructions that have run - // if it has already been run, break - ranInstructionsIndices := map[int]bool{} - for { - nextInst := comp.index - if ranInstructionsIndices[nextInst] { - break - } - ranInstructionsIndices[nextInst] = true - - comp.step() - } - - return comp.accumulator -} - -func part2(input string) int { - comp := newComputerFromInput(input) - - // iterate through instruction indices - for i := range comp.instructions { - // make new computer each time - newComputer := newComputerFromInput(input) - - // flip this index's instruction if a jmp or nop - switch newComputer.instructions[i].operation { - case "jmp": - newComputer.instructions[i].operation = "nop" - case "nop": - newComputer.instructions[i].operation = "jmp" - case "acc": - continue - } - - // run isInfiniteLoop check which returns final global value - if ans, isLoop := isInfiniteLoop(newComputer); !isLoop { - return ans - } - } - - // this should never be hit - fmt.Println("ERROR: No terminating set of instructions found") - return -1 -} - -func isInfiniteLoop(comp computer) (finalAccumulatorVal int, isLoop bool) { - ranInstructionsIndices := map[int]bool{} - for comp.index < len(comp.instructions) { - nextInst := comp.index - // is an infinite loop, return out - if ranInstructionsIndices[nextInst] { - return 0, true - } - ranInstructionsIndices[nextInst] = true - - comp.step() - } - - // instructions finished, return final accumulator & indicate it was not an - // infinite loop - return comp.accumulator, false -} diff --git a/2020/day08/main_test.go b/2020/day08/main_test.go deleted file mode 100644 index e9fc1b9..0000000 --- a/2020/day08/main_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var tests1 = []struct { - name string - want int - input string -}{ - {"actual", 1137, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string -}{ - {"actual", 1125, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day09/main.go b/2020/day09/main.go deleted file mode 100644 index e237b0e..0000000 --- a/2020/day09/main.go +++ /dev/null @@ -1,141 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "math" - "strconv" - "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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt"), 25) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt"), 25) - fmt.Println("Output:", ans) - } -} - -func part1(input string, preambleLength int) int { - nums := parseInput(input) - - for i := preambleLength; i < len(nums); i++ { - current := nums[i] - - var good bool - seen := map[int]bool{} - for back := i - preambleLength; back < i; back++ { - wanted := current - nums[back] - if seen[wanted] { - good = true - break - } - seen[nums[back]] = true - } - if !good { - return nums[i] - } - } - - fmt.Println("ERROR: No invalid number found for part 1") - return -1 -} - -// better time-complexity solution using a sliding window -func part2(input string, preambleLength int) int { - numToFind := part1(input, preambleLength) - nums := parseInput(input) - - var left, right, sum int - for right < len(nums) { - switch { - case left == right: - sum += nums[right] - right++ - case sum > numToFind: - sum -= nums[left] - left++ - case sum < numToFind: - sum += nums[right] - right++ - } - // if sum found, and more than 1 num is in it, break - if sum == numToFind && left+1 != right { - break - } - } - - // find smallest and largest values, wasted 20 minutes here when the smallest - // number was greater than MaxInt16... - smallest, largest := math.MaxInt32, -math.MaxInt32 - for _, v := range nums[left:right] { - if smallest > v { - smallest = v - } - if largest < v { - largest = v - } - } - - return smallest + largest -} - -func part2BruteForce(input string, preambleLength int) int { - numToFind := part1(input, preambleLength) - fmt.Println(numToFind) - nums := parseInput(input) - - var left, right int - for i, firstVal := range nums { - sum := firstVal - for j := i + 1; j < len(nums); j++ { - sum += nums[j] - if sum == numToFind { - left = i - right = j - break - } - } - } - - if left == 0 && right == 0 { - fmt.Println("ERROR: No valid answer found for part 2") - return -1 - } - - smallest, largest := nums[left], nums[left] - for _, v := range nums[left:right] { - if smallest > v { - smallest = v - } - if largest < v { - largest = v - } - } - - return smallest + largest -} - -func parseInput(input string) []int { - var ans []int - - lines := strings.Split(input, "\n") - for _, l := range lines { - num, err := strconv.Atoi(l) - if err != nil { - panic("parsing numbers in input " + err.Error()) - } - ans = append(ans, num) - } - - return ans -} diff --git a/2020/day09/main_test.go b/2020/day09/main_test.go deleted file mode 100644 index 87e88c9..0000000 --- a/2020/day09/main_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var exampleInput = `35 -20 -15 -25 -47 -40 -62 -55 -65 -95 -102 -117 -150 -182 -127 -219 -299 -277 -309 -576` - -var tests1 = []struct { - name string - want int - input string - preambleLength int -}{ - {"example", 127, exampleInput, 5}, - {"actual", 15690279, util.ReadFile("input.txt"), 25}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input, tt.preambleLength); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -var tests2 = []struct { - name string - want int - input string - preambleLength int -}{ - {"example", 62, exampleInput, 5}, - {"actual", 2174232, util.ReadFile("input.txt"), 25}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input, tt.preambleLength); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day10/main.go b/2020/day10/main.go deleted file mode 100644 index a95e967..0000000 --- a/2020/day10/main.go +++ /dev/null @@ -1,133 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "sort" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/mathy" - "github.com/alexchao26/advent-of-code-go/util" -) - -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")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - nums := parseInput(input) - nums = append(nums, mathy.MaxInt(nums...)+3) - sort.Ints(nums) - - var oneDiff, threeDiff int - var currentJoltage int - for _, v := range nums { - switch v - currentJoltage { - case 1: // check for 1 diff first, so no adapters are skipped - oneDiff++ - case 3: - threeDiff++ - default: - panic("adpaters not connected by 3 or 1") - } - currentJoltage = v - } - - return oneDiff * threeDiff -} - -func part2(input string) int { - nums := parseInput(input) - nums = append(nums, mathy.MaxInt(nums...)+3) - sort.Ints(nums) - - // return dynamicProgramming(input) - return memoCountPossibilities(nums, 0) -} - -func parseInput(input string) []int { - var ans []int - - lines := strings.Split(input, "\n") - for _, l := range lines { - ans = append(ans, cast.ToInt(l)) - } - - return ans -} - -// storing memo in global state isn't ideal... but it's fastser to code -var memo = map[string]int{} - -func memoCountPossibilities(nums []int, lastJolt int) int { - // if in memo, return that value - str := makeMemoKey(nums, lastJolt) - if v, ok := memo[str]; ok { - return v - } - - // if all adapters used up, return 1 - if len(nums) == 0 { - return 1 - } - - // create a recursive call for each adapter within 3 of the lastJoltage - var count int - for i, v := range nums { - if v-lastJolt <= 3 { - count += memoCountPossibilities(nums[i+1:], v) - } else { // stop counting if the joltage diff is too larger (>3) - break - } - } - - // update memo - memo[str] = count - - return count -} -func makeMemoKey(nums []int, lastJolt int) string { - ans := cast.ToString(lastJolt) + "x" - for _, v := range nums { - ans += cast.ToString(v) - } - return ans -} - -func dynamicProgramming(input string) int { - nums := parseInput(input) - nums = append(nums, mathy.MaxInt(nums...)+3, 0) - sort.Ints(nums) - - // initialize table with "1 way" to get to zero jolts - table := make([]int, len(nums)) - table[0] = 1 - - for i := 1; i < len(nums); i++ { - currentJolts := nums[i] - for j := i - 1; j >= 0; j-- { - // add the ways to get to currentJolts that are within 3 jolts - if currentJolts-nums[j] <= 3 { - table[i] += table[j] - } else { - break - } - } - } - - return table[len(table)-1] -} diff --git a/2020/day10/main_test.go b/2020/day10/main_test.go deleted file mode 100644 index 5deeb2e..0000000 --- a/2020/day10/main_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var exampleInputs = []string{`16 -10 -15 -5 -1 -11 -7 -19 -6 -12 -4`, - `28 -33 -18 -42 -31 -14 -46 -20 -48 -47 -24 -23 -49 -45 -19 -38 -39 -11 -1 -32 -25 -35 -8 -17 -7 -9 -4 -2 -34 -10 -3`, -} - -var tests1 = []struct { - name string - want int - input string -}{ - {"example 1", 35, exampleInputs[0]}, - {"example 2", 220, exampleInputs[1]}, - {"actual", 2176, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string -}{ - {"example 1", 8, exampleInputs[0]}, - {"example 2", 19208, exampleInputs[1]}, - {"actual", 18512297918464, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - 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) - } - }) - } -} - -func Test_dynamicProgramming(t *testing.T) { - tests := []struct { - name string - want int - input string - }{ - {"example 1", 8, exampleInputs[0]}, - {"example 2", 19208, exampleInputs[1]}, - {"actual", 18512297918464, util.ReadFile("input.txt")}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := dynamicProgramming(tt.input); got != tt.want { - t.Errorf("dynamicProgramming() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day11/main.go b/2020/day11/main.go deleted file mode 100644 index 935d246..0000000 --- a/2020/day11/main.go +++ /dev/null @@ -1,167 +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) - - 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 { - grid := parseInput(input) - - var lastState string - for { - grid = step(grid, 4, true) - - // check if last grid matches current, break out if they match - str := stringify(grid) - if str == lastState { - break - } - lastState = str - } - - var ans int - for _, row := range grid { - for _, v := range row { - if v == "#" { - ans++ - } - } - } - - return ans -} - -func part2(input string) int { - grid := parseInput(input) - - var lastState string - for { - grid = step(grid, 5, false) - - // check if last grid matches current, break out if they match - str := stringify(grid) - if str == lastState { - break - } - lastState = str - } - - var ans int - for _, row := range grid { - for _, v := range row { - if v == "#" { - ans++ - } - } - } - - return ans -} - -func parseInput(input string) [][]string { - var ans [][]string - - lines := strings.Split(input, "\n") - for _, l := range lines { - ans = append(ans, strings.Split(l, "")) - } - - return ans -} - -// justNeighbors differentiates part 1 (true) from part 2 -// tolerance = 4 for part 1, 5 for part 2 -func step(grid [][]string, tolerance int, justNeighbors bool) [][]string { - var nextGrid [][]string - - for r, row := range grid { - nextGrid = append(nextGrid, make([]string, len(grid[0]))) - for c, v := range row { - if v == "." { - nextGrid[r][c] = "." - } else { - neighbors := countNeighbors(grid, r, c, justNeighbors) - // check if seats should be updated - if v == "L" && neighbors == 0 { - nextGrid[r][c] = "#" - } else if v == "#" && neighbors >= tolerance { - nextGrid[r][c] = "L" - } else { - nextGrid[r][c] = v - } - } - } - - } - return nextGrid -} - -var directions = [8][2]int{ - {-1, -1}, - {-1, 0}, - {-1, 1}, - {0, -1}, - {0, 1}, - {1, -1}, - {1, 0}, - {1, 1}, -} - -func countNeighbors(grid [][]string, row, col int, justNeighbors bool) int { - var countNeighbors int - for _, d := range directions { - nextR, nextC := row, col - for { - nextR += d[0] - nextC += d[1] - if nextR < 0 || nextR >= len(grid) || nextC < 0 || nextC >= len(grid[0]) { - break - } - if grid[nextR][nextC] == "L" { - break - } - if grid[nextR][nextC] == "#" { - countNeighbors++ - break - } - - // break out after first pass if only checking immediate neighbors (part 1) - if justNeighbors { - break - } - } - } - - return countNeighbors -} - -// stringifies grid so it can be compared to its former state -func stringify(grid [][]string) string { - var str string - for _, row := range grid { - for _, v := range row { - - str += v - } - } - return str -} diff --git a/2020/day11/main_test.go b/2020/day11/main_test.go deleted file mode 100644 index 2099f9d..0000000 --- a/2020/day11/main_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var exampleInput = `L.LL.LL.LL -LLLLLLL.LL -L.L.L..L.. -LLLL.LL.LL -L.LL.LL.LL -L.LLLLL.LL -..L.L..... -LLLLLLLLLL -L.LLLLLL.L -L.LLLLL.LL` - -var tests1 = []struct { - name string - want int - input string -}{ - {"example", 37, exampleInput}, - {"actual", 2108, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string -}{ - {"example", 26, exampleInput}, - {"actual", 1897, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day12/main.go b/2020/day12/main.go deleted file mode 100644 index 7aa0581..0000000 --- a/2020/day12/main.go +++ /dev/null @@ -1,136 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/mathy" - "github.com/alexchao26/advent-of-code-go/util" -) - -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) - } -} - -// globals for part 1 -var directions = []string{"N", "E", "S", "W"} -var directionsToDiff = map[string][2]int{ - // !! X and Y like a coordinate system! Not typical 2D matrices in algos - "N": [2]int{0, 1}, - "E": [2]int{1, 0}, - "S": [2]int{0, -1}, - "W": [2]int{-1, 0}, -} - -func part1(input string) int { - instructions := parseInput(input) - - // X and Y like a coordinate system - var shipX, shipY int - dirIndex := 1 // index in directions slice - for _, inst := range instructions { - switch inst.action { - case "N": - shipY += inst.value - case "S": - shipY -= inst.value - case "E": - shipX += inst.value - case "W": - shipX -= inst.value - case "L": - // rotate ship left, this is equivalent - // -1 + 4 to keeping dirIndex positive for the modding - dirIndex += (-1 + 4) * inst.value / 90 - dirIndex %= 4 - case "R": - dirIndex += inst.value / 90 - dirIndex %= 4 - case "F": - d := directionsToDiff[directions[dirIndex]] - shipX += d[0] * inst.value - shipY += d[1] * inst.value - default: - panic("unexpected action") - } - } - - return mathy.ManhattanDistance(0, 0, shipX, shipY) -} - -func part2(input string) int { - instructions := parseInput(input) - - // X and Y like a coordinate system - waypointX := 10 - waypointY := 1 - var shipX, shipY int - - for _, inst := range instructions { - switch inst.action { - case "N": - waypointY += inst.value - case "S": - waypointY -= inst.value - case "E": - waypointX += inst.value - case "W": - waypointX -= inst.value - case "L": - // rotate waypoint left around ship (origin) - turns := inst.value / 90 - for turns > 0 { - // this simple bit is all it needs to rotate around the origin - // I had a 20 line if/else block... - waypointX, waypointY = -waypointY, waypointX - turns-- - } - case "R": - turns := inst.value / 90 - for turns > 0 { - waypointX, waypointY = waypointY, -waypointX - turns-- - } - case "F": - shipX += inst.value * waypointX - shipY += inst.value * waypointY - default: - panic("unexpected action") - } - } - - return mathy.ManhattanDistance(0, 0, shipX, shipY) -} - -type instruction struct { - action string - value int -} - -func parseInput(input string) []instruction { - var ans []instruction - - lines := strings.Split(input, "\n") - for _, l := range lines { - inst := instruction{ - action: l[:1], - value: cast.ToInt(l[1:]), - } - ans = append(ans, inst) - } - - return ans -} diff --git a/2020/day12/main_test.go b/2020/day12/main_test.go deleted file mode 100644 index ed3fe01..0000000 --- a/2020/day12/main_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var exampleInput = `F10 -N3 -F7 -R90 -F11` - -var tests1 = []struct { - name string - want int - input string -}{ - {"example", 25, exampleInput}, - {"actual", 820, util.ReadFile("input.txt")}, -} - -func TestPart1(t *testing.T) { - for _, tt := range tests1 { - 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 tests2 = []struct { - name string - want int - input string -}{ - {"example", 286, exampleInput}, - {"actual", 66614, util.ReadFile("input.txt")}, -} - -func TestPart2(t *testing.T) { - for _, tt := range tests2 { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day13/main.go b/2020/day13/main.go deleted file mode 100644 index 3259992..0000000 --- a/2020/day13/main.go +++ /dev/null @@ -1,79 +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) - - 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 { - estimate, busses := parseInput(input) - - var busID, waited int - for timeValue := estimate; busID == 0; timeValue++ { - for _, inter := range busses { - if timeValue%inter[1] == 0 { - busID = inter[1] - waited = timeValue - estimate - break - } - } - } - - return busID * waited -} - -// i didn't come up with this myself... generally speaking i understand it... -func part2(input string) int { - _, busses := parseInput(input) - - var timeValue int - runningProduct := 1 - for _, bus := range busses { - index, busID := bus[0], bus[1] - // this for loop adjusts the time until the constaint for this bus is met - // i.e. ensure (time + index) is divisible by the busID to ensure the bus arrives - for (timeValue+index)%busID != 0 { - // running product is used to increment because it will not affect - // the modulo of any of the previously scheduled busses, we've found - // the frequency to match them. - // e.g. if busID: 5 & index: 2, min timeValue is 3 b/c (3+2)%5 == 0 - // if the running product were 5, adding 5 means (8+2)%5 == 0 - // and (3 + 5x + 2) % 5 == 0 for any x - timeValue += runningProduct - } - runningProduct *= busID - } - - return timeValue -} - -// busses are [2]int{index, busID}, not the best way to parse stuff but it works -func parseInput(input string) (estimate int, busses [][2]int) { - lines := strings.Split(input, "\n") - estimate = cast.ToInt(lines[0]) - for index, busID := range strings.Split(lines[1], ",") { - if busID != "x" { - busses = append(busses, [2]int{index, cast.ToInt(busID)}) - } - } - return estimate, busses -} diff --git a/2020/day13/main_test.go b/2020/day13/main_test.go deleted file mode 100644 index b270d7b..0000000 --- a/2020/day13/main_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `939 -7,13,x,x,59,x,31,19` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example, 295}, - {"actual", util.ReadFile("input.txt"), 2092}, - } - 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 - }{ - {"example", example, 1068781}, - {"actual", util.ReadFile("input.txt"), 702970661767766}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day14/main.go b/2020/day14/main.go deleted file mode 100644 index 82167ab..0000000 --- a/2020/day14/main.go +++ /dev/null @@ -1,127 +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 { - groups := parseGroups(input) - memory := map[int]int{} - for _, group := range groups { - for _, instruction := range group.instructions { - address, value := instruction[0], instruction[1] - // overwrite val w/ bit shifts and shit - for index, overwrite := range group.mask { - if overwrite == "1" { - // run bitwise or - value |= 1 << index - } else if overwrite == "0" { - // OR w/ a 1 -> 1; XOR w/ a 1 -> 0 - value |= 1 << index - value ^= 1 << index - } - } - - // set the overwritten value into memory - memory[address] = value - } - } - - return getTotal(memory) -} - -func part2(input string) int { - groups := parseGroups(input) - memory := map[int]int{} - for _, group := range groups { - for _, instruction := range group.instructions { - address, value := instruction[0], instruction[1] - - var floatingIndices []int - for index, overwrite := range group.mask { - if overwrite == "1" { - address |= 1 << index - } else if overwrite == "X" { - floatingIndices = append(floatingIndices, index) - } - } - - // ugly way to get all the permutations (with one extra) of all possible addresses - perms := []int{address} - for _, index := range floatingIndices { - for _, perm := range perms { - // for each existing permutation, get the permutation with this floating index - // flipped to a 1, and to a zero - with1 := perm | 1< 1 - with0 := with1 ^ 1< 0 - perms = append(perms, with1, with0) - } - } - // make writes to memory for each perm - for _, index := range perms { - memory[index] = value - } - } - } - - return getTotal(memory) -} - -// each grouping contains a mask at the start, then a pairs of memory addressed to values -type group struct { - mask map[int]string - instructions [][2]int // memory address, overwrite value -} - -func parseGroups(input string) []*group { - var groups []*group - - for _, line := range strings.Split(input, "\n") { - if strings.HasPrefix(line, "mask") { - // start a new group - newGroup := &group{mask: map[int]string{}} - groups = append(groups, newGroup) - - var maskValue string - fmt.Sscanf(line, "mask = %s", &maskValue) - - // iterate backwards so that keys in the mask map will correspond to powers of two - for i := len(maskValue) - 1; i >= 0; i-- { - newGroup.mask[len(maskValue)-i-1] = string(maskValue[i]) - } - } else { - currentGroup := groups[len(groups)-1] - var index, val int - fmt.Sscanf(line, "mem[%d] = %d", &index, &val) - currentGroup.instructions = append(currentGroup.instructions, [2]int{index, val}) - } - } - return groups -} - -func getTotal(memory map[int]int) int { - var sum int - for _, v := range memory { - sum += v - } - return sum -} diff --git a/2020/day14/main_test.go b/2020/day14/main_test.go deleted file mode 100644 index 74dd9b1..0000000 --- a/2020/day14/main_test.go +++ /dev/null @@ -1,47 +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"), 14925946402938}, - } - 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 example = `mask = 000000000000000000000000000000X1001X -mem[42] = 100 -mask = 00000000000000000000000000000000X0XX -mem[26] = 1` - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example, 208}, - {"actual", util.ReadFile("input.txt"), 3706820676200}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day15/main.go b/2020/day15/main.go deleted file mode 100644 index 8432a6d..0000000 --- a/2020/day15/main.go +++ /dev/null @@ -1,61 +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) - - var ans int - if part == 1 { - ans = rambunctiousRecitation(util.ReadFile("./input.txt"), 2020) - } else { - // brute force, takes ~5seconds to run - ans = rambunctiousRecitation(util.ReadFile("./input.txt"), 30000000) - } - fmt.Println("Output:", ans) -} - -func rambunctiousRecitation(input string, turnToReturn int) int { - var startingNums []int - for _, num := range strings.Split(input, ",") { - startingNums = append(startingNums, cast.ToInt(num)) - } - - said := map[int][]int{} - var numSaidLast int - // populate the starting map with the numbers from the input - for i, num := range startingNums { - said[num] = append(said[num], i+1) - numSaidLast = num - } - - // then picking up from the next turn (len of input), continue until the - // desired turn is reached - for i := len(startingNums); i <= turnToReturn; i++ { - indexSlice := said[numSaidLast] - // if the length of the slice of incides is 1, that means it was only - // called once. Say zero, add to the zero slice of indices - if len(indexSlice) == 1 { - numSaidLast = 0 - said[0] = append(said[0], i) - } else { - // otherwise determine the diff between the last 2 times it was said - length := len(indexSlice) - numSaidLast = indexSlice[length-1] - indexSlice[length-2] - // add this index i to the indices slice for the number that is said - said[numSaidLast] = append(said[numSaidLast], i) - } - } - - return numSaidLast -} diff --git a/2020/day15/main_test.go b/2020/day15/main_test.go deleted file mode 100644 index 5bc8d4f..0000000 --- a/2020/day15/main_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "testing" - "time" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func Test_rambunctiousRecitation(t *testing.T) { - tests := []struct { - name string - input string - desiredStep int - want int - }{ - {"actual", util.ReadFile("input.txt"), 2020, 1259}, - {"actual", util.ReadFile("input.txt"), 30000000, 689}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - startTime := time.Now() - if got := rambunctiousRecitation(tt.input, tt.desiredStep); got != tt.want { - t.Errorf("rambunctiousRecitation() = %v, want %v", got, tt.want) - } - t.Log("Op time: ", time.Since(startTime)) - }) - } -} diff --git a/2020/day16/main.go b/2020/day16/main.go deleted file mode 100644 index 2cff046..0000000 --- a/2020/day16/main.go +++ /dev/null @@ -1,149 +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 := ticketTranslation(util.ReadFile("./input.txt"), part) - fmt.Println("Output:", ans) -} - -func ticketTranslation(input string, part int) int { - rules, myTicket, nearbyTickets := parseInput(input) - - var validTickets [][]int // for part 2 - var errorRate int - for _, ticket := range nearbyTickets { - isValidTicket := true - for _, ticketValue := range ticket { - valuePassesRules := false - for _, ruleBounds := range rules { - if valuePassesRule(ticketValue, ruleBounds) { - valuePassesRules = true - break - } - } - if !valuePassesRules { - errorRate += ticketValue - isValidTicket = false // for part 2 - break - } - } - // filter out valid tickets for part 2 - if isValidTicket { - validTickets = append(validTickets, ticket) - } - } - - if part == 1 { - return errorRate - } - - // part 2, figure out which field belongs to which - fieldNameToIndex := map[string]int{} - skipTicketIndices := map[int]bool{} - // run until all the rules are accounted for - for len(rules) > 0 { - // iterate over "columns" of the valid tickets matrix - for ticketValIndex := range validTickets[0] { - if skipTicketIndices[ticketValIndex] { - continue - } - // run all the rules against each ticket, store which ones pass for - // all values at this ticket index. if only one rule applies, it - // must be for this index within a ticket - var passingNames []string - for ruleName, ruleBounds := range rules { - allValuesPassed := true - // iterate over all tickets and if any fail for this rule, break out - for _, ticket := range validTickets { - ticketValue := ticket[ticketValIndex] - if !valuePassesRule(ticketValue, ruleBounds) { - allValuesPassed = false - break - } - } - - // append this rule name as one that passed for these values - if allValuesPassed { - passingNames = append(passingNames, ruleName) - } - } - - // if only one rule passes, assign it to this ticket value index - // remove it from the rules list - if len(passingNames) == 1 { - fieldNameToIndex[passingNames[0]] = ticketValIndex - // remove the rule from the map b/c we've determined its index - delete(rules, passingNames[0]) - // remember which indices have already been taken by a rule - skipTicketIndices[ticketValIndex] = true - } - } - } - - // get final answer by multiplying all ticket details/rules prefixed "departure" - departureProduct := 1 - for rule, valueIndex := range fieldNameToIndex { - if strings.HasPrefix(rule, "departure") { - departureProduct *= myTicket[valueIndex] - } - } - - return departureProduct -} - -func valuePassesRule(value int, ruleBounds [2][2]int) bool { - firstBounds := ruleBounds[0] - secondBounds := ruleBounds[1] - return ((value >= firstBounds[0] && value <= firstBounds[1]) || - (value >= secondBounds[0] && value <= secondBounds[1])) -} - -func parseInput(input string) (map[string][2][2]int, []int, [][]int) { - blocks := strings.Split(input, "\n\n") - - // parse rules from first block - rules := map[string][2][2]int{} - for _, rule := range strings.Split(blocks[0], "\n") { - parts := strings.Split(rule, ": ") - name := parts[0] - - var r1L, r1H, r2L, r2H int - fmt.Sscanf(parts[1], "%d-%d or %d-%d", &r1L, &r1H, &r2L, &r2H) - rules[name] = [2][2]int{ - [2]int{r1L, r1H}, - [2]int{r2L, r2H}, - } - } - - // my ticket values in second block - splitTicket := strings.Split(blocks[1], "\n") - var myTicket []int - for _, v := range strings.Split(splitTicket[1], ",") { - myTicket = append(myTicket, cast.ToInt(v)) - } - - // all values for nearby tickets - var nearbyTickets [][]int - for _, nearby := range strings.Split(blocks[2], "\n")[1:] { - var near []int - for _, v := range strings.Split(nearby, ",") { - near = append(near, cast.ToInt(v)) - } - nearbyTickets = append(nearbyTickets, near) - } - - return rules, myTicket, nearbyTickets -} diff --git a/2020/day16/main_test.go b/2020/day16/main_test.go deleted file mode 100644 index 95826d3..0000000 --- a/2020/day16/main_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example1 = `class: 1-3 or 5-7 -row: 6-11 or 33-44 -seat: 13-40 or 45-50 - -your ticket: -7,1,14 - -nearby tickets: -7,3,47 -40,4,50 -55,2,20 -38,6,12` - -func Test_ticketTranslation(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"example_part1", example1, 1, 71}, - {"actual", util.ReadFile("input.txt"), 1, 32835}, - {"actual", util.ReadFile("input.txt"), 2, 514662805187}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := ticketTranslation(tt.input, tt.part); got != tt.want { - t.Errorf("ticketTranslation() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day17/main.go b/2020/day17/main.go deleted file mode 100644 index d0d3dac..0000000 --- a/2020/day17/main.go +++ /dev/null @@ -1,107 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -// finished 933/~700 - jeez... -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := conwayCubes(util.ReadFile("./input.txt"), part) - fmt.Println("Output:", ans) -} - -var diffs = [3]int{-1, 0, 1} - -func conwayCubes(input string, part int) int { - activeNodes := parseInput(input) - - diffsW := []int{0} - if part == 2 { - diffsW = []int{-1, 0, 1} - } - - for cycles := 0; cycles < 6; cycles++ { - toCheck := map[[4]int]bool{} - - for coord := range activeNodes { - for _, dx := range diffs { - for _, dy := range diffs { - for _, dz := range diffs { - for _, dw := range diffsW { - toCheck[[4]int{ - coord[0] + dx, - coord[1] + dy, - coord[2] + dz, - coord[3] + dw}] = true - } - } - } - } - } - - nextState := map[[4]int]bool{} - for coord := range toCheck { - // check all neighbors around this coord - var countNeighbors int - for _, dx := range diffs { - for _, dy := range diffs { - for _, dz := range diffs { - for _, dw := range diffsW { - if dx != 0 || dy != 0 || dz != 0 || dw != 0 { - x, y, z, w := coord[0]+dx, coord[1]+dy, coord[2]+dz, coord[3]+dw - neighCoord := [4]int{x, y, z, w} - if isActive, ok := activeNodes[neighCoord]; ok && isActive { - countNeighbors++ - } - } - } - } - } - } - - if wasActive, ok := activeNodes[coord]; ok && wasActive { - if countNeighbors == 2 || countNeighbors == 3 { - nextState[coord] = true - } - } else { - // inactive originally - if countNeighbors == 3 { - nextState[coord] = true - } - } - } - - activeNodes = nextState - } - - // cubes after 6 cycles - return len(activeNodes) -} - -// this is not perfectly generalized because arrays in go have to be sized at compile -// time, and slices can't be used to map keys because they're not trivial to compare -// they could be compared by converting it into a string... but that's annoying -func parseInput(input string) map[[4]int]bool { - setActiveNodes := map[[4]int]bool{} - - for i, line := range strings.Split(input, "\n") { - for j, cell := range strings.Split(line, "") { - - if cell == "#" { - // start z and w coords at zero - n := [4]int{i, j, 0, 0} - setActiveNodes[n] = true - } - } - } - return setActiveNodes -} diff --git a/2020/day17/main_test.go b/2020/day17/main_test.go deleted file mode 100644 index acaa163..0000000 --- a/2020/day17/main_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `.#. -..# -###` - -func Test_conwayCubes(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"example_part1", example, 1, 112}, - {"actual_part1", util.ReadFile("input.txt"), 1, 388}, - {"example_part2", example, 2, 848}, - {"actual_part2", util.ReadFile("input.txt"), 2, 2280}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := conwayCubes(tt.input, tt.part); got != tt.want { - t.Errorf("conwayCubes() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day18/main.go b/2020/day18/main.go deleted file mode 100644 index 25369f4..0000000 --- a/2020/day18/main.go +++ /dev/null @@ -1,179 +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) - - var ans int - if part == 1 { - ans = part1(util.ReadFile("./input.txt")) - } else { - ans = part2(util.ReadFile("./input.txt")) - } - fmt.Println("Output:", ans) -} - -// 392 -func part1(input string) int { - parsed := parseInput(input) - - var total int - for _, line := range parsed { - // sum up this line, addressing things inside parens first... - total += doMaths(line, calcFlatSlicePart1) - } - - return total -} - -// ~1700 :( -func part2(input string) int { - lines := parseInput(input) - var total int - - for _, line := range lines { - total += doMaths(line, calcFlatSlicePart2) - } - - return total -} - -func parseInput(input string) (ans [][]string) { - lines := strings.Split(input, "\n") - for _, l := range lines { - // I got lucky that they were all single digit numbers... - ans = append(ans, strings.Split(strings.ReplaceAll(l, " ", ""), "")) - } - return ans -} - -func doMaths(input []string, flatteningFunc func([]string) string) int { - var stackOpenIndices []int - var stackFlattened []string - for i := 0; i < len(input); i++ { - // iterate through input, always append onto the flattened stack - // track open paren indices (in the flattened stack) - // on closing parens, use the top of the stackOpenIndices to flatten - // the most recent set of values/operations within parens, and replace - // their opening paren with the flattneed value - stackFlattened = append(stackFlattened, input[i]) - switch input[i] { - case "(": - stackOpenIndices = append(stackOpenIndices, len(stackFlattened)-1) - case ")": - // on close parens, pass a section tohandleFlat - // then remove a bunch of shit from input, - openIndex := stackOpenIndices[len(stackOpenIndices)-1] - stackOpenIndices = stackOpenIndices[:len(stackOpenIndices)-1] - - // do not include leading or trailing paren - sliToFlatten := stackFlattened[openIndex+1 : len(stackFlattened)-1] - stackFlattened[openIndex] = flatteningFunc(sliToFlatten) - - // remove the values that were flattened off the top of the stack - stackFlattened = stackFlattened[:openIndex+1] - } - - } - // slice should now be flat - return cast.ToInt(flatteningFunc(stackFlattened)) -} - -func calcFlatSlicePart1(input []string) string { - for _, v := range input { - if v == "(" || v == ")" { - panic(fmt.Sprintf("unexpected paren in flat input, %v", input)) - } - } - - result := cast.ToInt(input[0]) - - for i := range input { - if i+2 < len(input) { - switch input[i+1] { - case "+": - result += cast.ToInt(input[i+2]) - case "*": - result *= cast.ToInt(input[i+2]) - } - } - } - - return cast.ToString(result) -} - -func calcFlatSlicePart2(input []string) string { - for _, v := range input { - if v == "(" || v == ")" { - panic(fmt.Sprintf("unexpected paren in flat input, %v", input)) - } - } - - // handle all additions - for i := 1; i < len(input)-1; i++ { - if input[i] == "+" { - toLeft := input[i-1] - toRight := input[i+1] - if isNum(toLeft) && isNum(toRight) { - input[i-1] = addStrings(toLeft, toRight) - input = splice(input, i, 2) - i-- - } - } - } - - // then handle all multiplications - for i := 1; i < len(input)-1; i++ { - if input[i] == "*" { - toLeft := input[i-1] - toRight := input[i+1] - if isNum(toLeft) && isNum(toRight) { - input[i-1] = multiplyStrings(toLeft, toRight) - input = splice(input, i, 2) - i-- - } - } - } - - return input[0] -} - -var numReg = regexp.MustCompile("[0-9]") - -func isNum(str string) bool { - return numReg.MatchString(str) -} - -func addStrings(strs ...string) string { - var sum int - for _, str := range strs { - sum += cast.ToInt(str) - } - return cast.ToString(sum) -} -func multiplyStrings(strs ...string) string { - sum := 1 - for _, str := range strs { - sum *= cast.ToInt(str) - } - return cast.ToString(sum) -} - -// removes a particular number of elements from the middle of the slice -func splice(sli []string, startIndex, items int) []string { - copy(sli[startIndex:], sli[startIndex+items:]) - sli = sli[:len(sli)-items] - return sli -} diff --git a/2020/day18/main_test.go b/2020/day18/main_test.go deleted file mode 100644 index df4fba7..0000000 --- a/2020/day18/main_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "reflect" - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - // {"example1", "2 * 3 + (4 * 5)", 26}, - {"example2", "((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2", 13632}, - {"actual", util.ReadFile("input.txt"), 53660285675207}, - } - 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"), 141993988282687}, - } - 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) - } - }) - } -} - -func Test_calcFlatSlicePart2(t *testing.T) { - type args struct { - input []string - } - tests := []struct { - name string - args args - want string - }{ - { - "example", - args{input: []string{"1", "+", "3", "*", "4", "+", "5"}}, - "36", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := calcFlatSlicePart2(tt.args.input); !reflect.DeepEqual(got, tt.want) { - t.Errorf("calcFlatSlicePart2() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_splice(t *testing.T) { - type args struct { - sli []string - startIndex int - items int - } - tests := []struct { - name string - args args - want []string - }{ - {"example1", args{[]string{"a", "b", "c", "d", "e"}, 1, 1}, []string{"a", "c", "d", "e"}}, - {"example1", args{[]string{"a", "b", "c", "d", "e"}, 1, 4}, []string{"a"}}, - {"example1", args{[]string{"a", "b", "c", "d", "e"}, 0, 1}, []string{"b", "c", "d", "e"}}, - {"example1", args{[]string{"a", "b", "c", "d", "e"}, 0, 5}, []string{}}, - {"example1", args{[]string{"a", "b", "c", "d", "e"}, 3, 1}, []string{"a", "b", "c", "e"}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := splice(tt.args.sli, tt.args.startIndex, tt.args.items); !reflect.DeepEqual(got, tt.want) { - t.Errorf("splice() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day19/main.go b/2020/day19/main.go deleted file mode 100644 index 59f7f3e..0000000 --- a/2020/day19/main.go +++ /dev/null @@ -1,159 +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) - - var ans int - if part == 1 { - ans = part1(util.ReadFile("./input.txt")) - } else { - ans = part2(util.ReadFile("./input.txt")) - } - fmt.Println("Output:", ans) -} - -// ~1200 -func part1(input string) int { - graph, messages := parseInput(input) - - fillInGraph(graph, 0) - - var matchRuleZero int - for _, m := range messages { - for _, opt := range graph[0].resolved { - if m == opt { - matchRuleZero++ - break - } - } - } - - return matchRuleZero -} - -// 1018 @ 1:19AM -func part2(input string) int { - graph, messages := parseInput(input) - - // these are the dependencies of rules 8 & 11 (which are the only dependencies - // of rule 0). fill in the resolved fields for these graph nodes - // b/c these definitions are not circular (downstream of changes), they will - // not infinite loop - fillInGraph(graph, 42) - fillInGraph(graph, 31) - - // generate regexp strings that will be used to match against rules 8 and 11 - // and ultimate match rule 0 - part42 := fmt.Sprintf("(%s)", strings.Join(graph[42].resolved, "|")) - part31 := fmt.Sprintf("(%s)", strings.Join(graph[31].resolved, "|")) - - // rule 8 is essentially 1 or more instances of rule 42 - rule8String := fmt.Sprintf("(%s)+", part42) - - // note: i'm unaware of how to make two regexp portions have the same number - // of segments, so I made this helper function that changes that number - // then I just run it ten times because that should be large enough to cover - // any of the rules in the input... - makeRegexp := func(num int) *regexp.Regexp { - // rule 11 is an equal number of 42 and 31 rules - return regexp.MustCompile(fmt.Sprintf("^%s%s{%d}%s{%d}$", rule8String, part42, num, part31, num)) - } - - var matchRuleZero int - for _, m := range messages { - for i := 1; i < 10; i++ { - pattern := makeRegexp(i) - if pattern.MatchString(m) { - matchRuleZero++ - break - } - } - } - - return matchRuleZero -} - -func fillInGraph(graph map[int]*rule, entry int) []string { - if len(graph[entry].resolved) != 0 { - // return a copy of resolved otherwise there's all kinds of side effect errors - return append([]string{}, graph[entry].resolved...) - } - - // iterate through options, resolve children and append resolved paths - // for the current entry point - for _, option := range graph[entry].options { - // this will be all permutations generated from recursive calls to fillInGraph - // Note: there's probably a cleaner algorithm to do this kind of perm generation... - resolved := []string{""} - for _, entryPoint := range option { - nestedResolveVals := fillInGraph(graph, entryPoint) - var newResolved []string - for _, nextPiece := range nestedResolveVals { - for _, prev := range resolved { - newResolved = append(newResolved, prev+nextPiece) - } - } - resolved = newResolved - } - graph[entry].resolved = append(graph[entry].resolved, resolved...) - } - - return graph[entry].resolved -} - -type rule struct { - resolved []string - options [][]int -} - -// Stringer interface for debugging -func (r rule) String() string { - var ans string - ans += fmt.Sprintf("OPTIONS: %v\n", r.options) - ans += fmt.Sprintf(" RESOLVED: %v\n", r.resolved) - return ans -} - -func parseInput(input string) (rules map[int]*rule, messages []string) { - parts := strings.Split(input, "\n\n") - - rules = map[int]*rule{} - for _, r := range strings.Split(parts[0], "\n") { - if regexp.MustCompile("[a-z]").MatchString(r) { - var num int - var char string - fmt.Sscanf(r, "%d: \"%1s\"", &num, &char) - rules[num] = &rule{resolved: []string{char}} - } else { - split := strings.Split(r, ": ") - key := cast.ToInt(split[0]) - newRule := rule{} - for _, ruleNums := range strings.Split(split[1], " | ") { - nums := strings.Split(ruleNums, " ") - var option []int - for _, n := range nums { - option = append(option, cast.ToInt(n)) - } - newRule.options = append(newRule.options, option) - } - rules[key] = &newRule - } - } - - messages = strings.Split(parts[1], "\n") - - return rules, messages -} diff --git a/2020/day19/main_test.go b/2020/day19/main_test.go deleted file mode 100644 index a7ddee9..0000000 --- a/2020/day19/main_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `0: 4 1 5 -1: 2 3 | 3 2 -2: 4 4 | 5 5 -3: 4 5 | 5 4 -4: "a" -5: "b" - -ababbb -bababa -abbbab -aaabbb -aaaabbb` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example, 2}, - {"actual", util.ReadFile("input.txt"), 136}, - } - 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 = `42: 9 14 | 10 1 -9: 14 27 | 1 26 -10: 23 14 | 28 1 -1: "a" -11: 42 31 | 42 11 31 -5: 1 14 | 15 1 -19: 14 1 | 14 14 -12: 24 14 | 19 1 -16: 15 1 | 14 14 -31: 14 17 | 1 13 -6: 14 14 | 1 14 -2: 1 24 | 14 4 -0: 8 11 -13: 14 3 | 1 12 -15: 1 | 14 -17: 14 2 | 1 7 -23: 25 1 | 22 14 -28: 16 1 -4: 1 1 -20: 14 14 | 1 15 -3: 5 14 | 16 1 -27: 1 6 | 14 18 -14: "b" -21: 14 1 | 1 14 -25: 1 1 | 1 14 -22: 14 14 -8: 42 | 42 8 -26: 14 22 | 1 20 -18: 15 15 -7: 14 5 | 1 21 -24: 14 1 - -abbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa -bbabbbbaabaabba -babbbbaabbbbbabbbbbbaabaaabaaa -aaabbbbbbaaaabaababaabababbabaaabbababababaaa -bbbbbbbaaaabbbbaaabbabaaa -bbbababbbbaaaaaaaabbababaaababaabab -ababaaaaaabaaab -ababaaaaabbbaba -baabbaaaabbaaaababbaababb -abbbbabbbbaaaababbbbbbaaaababb -aaaaabbaabaaaaababaa -aaaabbaaaabbaaa -aaaabbaabbaaaaaaabbbabbbaaabbaabaaa -babaaabbbaaabaababbaabababaaab -aabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba` - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example2, 12}, - {"actual", util.ReadFile("input.txt"), 256}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day20/main.go b/2020/day20/main.go deleted file mode 100644 index a438911..0000000 --- a/2020/day20/main.go +++ /dev/null @@ -1,276 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "math" - "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 = part1(util.ReadFile("./input.txt")) - } else { - ans = part2(util.ReadFile("./input.txt")) - } - fmt.Println("Output:", ans) -} - -func part1(input string) int { - tiles := parseTilesFromInput(input) - edgeSize := int(math.Sqrt(float64(len(tiles)))) - - assembledTiles := backtrackAssemble(tiles, nil, map[int]bool{}) - - // return product of corners - product := assembledTiles[0][0].id - product *= assembledTiles[0][edgeSize-1].id - product *= assembledTiles[edgeSize-1][0].id - product *= assembledTiles[edgeSize-1][edgeSize-1].id - return product -} - -func part2(input string) int { - tiles := parseTilesFromInput(input) - edgeSize := int(math.Sqrt(float64(len(tiles)))) - - assembledTiles := backtrackAssemble(tiles, nil, map[int]bool{}) - - // remove ALL borders from all tiles - for r := range assembledTiles { - for c, cell := range assembledTiles[r] { - assembledTiles[r][c].contents = removeBordersFromGrid(cell.contents) - } - } - - // generate image from assembledTiles... - // assembledTiles is effectively a 2D grid where each cell is a 2D grid representing a tile - // there has to be an easier way to "flatten" a 4D grid into a 2D grid... - var image [][]string - for bigRow := 0; bigRow < edgeSize; bigRow++ { - for subRow := 0; subRow < len(assembledTiles[0][0].contents); subRow++ { - image = append(image, []string{}) - for bigCol := 0; bigCol < edgeSize; bigCol++ { - subLine := assembledTiles[bigRow][bigCol].contents[subRow] - image[len(image)-1] = append(image[len(image)-1], subLine...) - } - } - } - - // get the coordinates of all monsters by iterating over all possible - // orientations of the image - var monsterCoords [][2]int - for _, opt := range algos.AllGridOrientations(image) { - monsterCoords = findMonsterCoords(opt) - // assuming there's only one orientation of image with valid monsters - if len(monsterCoords) > 0 { - image = opt - break - } - } - - // modify all monster coordinates to "O" characters (anything but "#") - for _, coord := range monsterCoords { - image[coord[0]][coord[1]] = "O" - } - - // count up remaining "#" cells - var roughWatersCount int - for _, row := range image { - for _, cell := range row { - if cell == "#" { - roughWatersCount++ - } - } - } - - return roughWatersCount -} - -type tile struct { - contents [][]string - id int -} - -func parseTilesFromInput(input string) []*tile { - ans := []*tile{} - for _, block := range strings.Split(input, "\n\n") { - split := strings.Split(block, "\n") - var tileID int - _, err := fmt.Sscanf(split[0], "Tile %d:", &tileID) - if err != nil { - panic(err) - } - - var contents [][]string - for _, line := range split[1:] { - contents = append(contents, strings.Split(line, "")) - } - ans = append(ans, &tile{id: tileID, contents: contents}) - } - return ans -} - -func backtrackAssemble(tiles []*tile, assembledTiles [][]*tile, usedIndices map[int]bool) [][]*tile { - // pray it's a square... - edgeSize := int(math.Sqrt(float64(len(tiles)))) - if assembledTiles == nil { - assembledTiles = make([][]*tile, edgeSize) - for i := 0; i < edgeSize; i++ { - assembledTiles[i] = make([]*tile, edgeSize) - } - } - - // iterate through all cells, skipping cells that have already been set - for row := 0; row < edgeSize; row++ { - for col := 0; col < edgeSize; col++ { - // skip cells that have already been assigned - if assembledTiles[row][col] == nil { - // iterate over all available tiles (skip ones that are tagged "used") - for i, t := range tiles { - if !usedIndices[i] { - // iterate over the OPTIONS for a particular tile, i.e. all 8 images of it... - for _, opt := range algos.AllGridOrientations(t.contents) { - // check if setting this tile is okay with (if applicable) tiles above - // and to the left - if row != 0 { // check above - currentTopRow := getRow(opt, true) - bottomOfAbove := getRow(assembledTiles[row-1][col].contents, false) - // if they don't match, continue onto next option b/c this one doesn't match - if currentTopRow != bottomOfAbove { - continue - } - } - if col != 0 { // check left, same logic checking above - currentLeftCol := getCol(opt, true) - rightColOfLeft := getCol(assembledTiles[row][col-1].contents, false) - if currentLeftCol != rightColOfLeft { - continue - } - } - // set tile, mark tile as used, recurse - t.contents = opt // side effects apply b/c t is a pointer - assembledTiles[row][col] = t - // if non-nil response, relay the return value up the call stack - usedIndices[i] = true - recurseResult := backtrackAssemble(tiles, assembledTiles, usedIndices) - if recurseResult != nil { - return recurseResult - } - // backtrack if nil response from recursing - assembledTiles[row][col] = nil - usedIndices[i] = false - } - } - } - // Note: this is a key part of backtracking, to escape if there are - // no valid options for to set for this row/col - if assembledTiles[row][col] == nil { - return nil - } - } - } - } - - // if entire loop finishes, that means every cell has been assigned - // return the assembled tiles to collapse the call stack - return assembledTiles -} - -// helper functions to get a string (easily comparable) of a single side -func getCol(grid [][]string, firstCol bool) string { - var str string - for i := range grid { - if firstCol { - str += grid[i][0] - } else { - str += grid[i][len(grid[0])-1] - } - } - return str -} - -func getRow(grid [][]string, firstRow bool) string { - var str string - for i := range grid[0] { - if firstRow { - str += grid[0][i] - } else { - str += grid[len(grid)-1][i] - } - } - return str -} - -func removeBordersFromGrid(grid [][]string) [][]string { - var result [][]string - - for i := 1; i < len(grid)-1; i++ { - result = append(result, []string{}) - for j := 1; j < len(grid[0])-1; j++ { - result[i-1] = append(result[i-1], grid[i][j]) - } - } - - return result -} - -// returns all coordinates that make up any valid monsters... -// valid monsters are the '#' like so... dots are for visual effect -// ..#. -// #....##....##....### -// .#..#..#..#..#..#... -var monster = ` # -# ## ## ### - # # # # # # ` - -func findMonsterCoords(image [][]string) [][2]int { - var monsterOffsets [][2]int - var monsterHeight, monsterLength int - for r, line := range strings.Split(monster, "\n") { - for c, char := range line { - if char == '#' { - monsterOffsets = append(monsterOffsets, [2]int{r, c}) - } - monsterLength = c + 1 - } - monsterHeight++ - } - - // determine the top left corners of a found monster - var monsterStartingCoords [][2]int - for r := 0; r < len(image)-monsterHeight+1; r++ { - for c := 0; c < len(image[0])-monsterLength+1; c++ { - monsterFound := true - for _, diff := range monsterOffsets { - rowToCheck := r + diff[0] - colToCheck := c + diff[1] - if image[rowToCheck][colToCheck] != "#" { - monsterFound = false - } - } - if monsterFound { - monsterStartingCoords = append(monsterStartingCoords, [2]int{r, c}) - } - } - } - - // generate a list of all the coordinates that are monsters - var monsterCoords [][2]int - for _, startingCoord := range monsterStartingCoords { - for _, diff := range monsterOffsets { - monsterCoords = append(monsterCoords, [2]int{startingCoord[0] + diff[0], startingCoord[1] + diff[1]}) - } - } - - return monsterCoords -} diff --git a/2020/day20/main_test.go b/2020/day20/main_test.go deleted file mode 100644 index 73cecad..0000000 --- a/2020/day20/main_test.go +++ /dev/null @@ -1,151 +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", example, 20899048083289}, - {"actual", util.ReadFile("input.txt"), 29125888761511}, - } - 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 - }{ - {"example", example, 273}, - {"actual", util.ReadFile("input.txt"), 2219}, - } - 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) - } - }) - } -} - -var example = `Tile 2311: -..##.#..#. -##..#..... -#...##..#. -####.#...# -##.##.###. -##...#.### -.#.#.#..## -..#....#.. -###...#.#. -..###..### - -Tile 1951: -#.##...##. -#.####...# -.....#..## -#...###### -.##.#....# -.###.##### -###.##.##. -.###....#. -..#.#..#.# -#...##.#.. - -Tile 1171: -####...##. -#..##.#..# -##.#..#.#. -.###.####. -..###.#### -.##....##. -.#...####. -#.##.####. -####..#... -.....##... - -Tile 1427: -###.##.#.. -.#..#.##.. -.#.##.#..# -#.#.#.##.# -....#...## -...##..##. -...#.##### -.#.####.#. -..#..###.# -..##.#..#. - -Tile 1489: -##.#.#.... -..##...#.. -.##..##... -..#...#... -#####...#. -#..#.#.#.# -...#.#.#.. -##.#...##. -..##.##.## -###.##.#.. - -Tile 2473: -#....####. -#..#.##... -#.##..#... -######.#.# -.#...#.#.# -.######### -.###.#..#. -########.# -##...##.#. -..###.#.#. - -Tile 2971: -..#.#....# -#...###... -#.#.###... -##.##..#.. -.#####..## -.#..####.# -#..#.#..#. -..####.### -..#.#.###. -...#.#.#.# - -Tile 2729: -...#.#.#.# -####.#.... -..#.#..... -....#..#.# -.##..##.#. -.#.####... -####.#.#.. -##.####... -##..#.##.. -#.##...##. - -Tile 3079: -#.#.#####. -.#..###### -..#....... -######.... -####.#..#. -.#...#.##. -#.#####.## -..#.###... -..#....... -..#.###...` diff --git a/2020/day21/main.go b/2020/day21/main.go deleted file mode 100644 index 333dc98..0000000 --- a/2020/day21/main.go +++ /dev/null @@ -1,102 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "sort" - "strings" - - "github.com/alexchao26/advent-of-code-go/data-structures/slice" - - "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) - - part1Ans, part2Ans := allergenAssessment(util.ReadFile("./input.txt")) - if part == 1 { - fmt.Println("Output:", part1Ans) - } else { - fmt.Println("Output:", part2Ans) - } -} - -// leaderboard: 225/127, closest yet! -func allergenAssessment(input string) (part1Ans int, part2Ans string) { - allergensToPossibleIngredients := map[string][]string{} - ingredientCounts := map[string]int{} - - for _, line := range strings.Split(input, "\n") { - parts := strings.Split(line, " (contains ") - ingredients := strings.Split(parts[0], " ") - allergens := strings.Split(strings.Trim(parts[1], ")"), ", ") - - // count up the appearances for each ingredient - for _, ingred := range ingredients { - ingredientCounts[ingred]++ - } - - // generate all possible ingredients that could be a particular allergen - for _, a := range allergens { - // if no ingredients are there, set this as the initial list - if allergensToPossibleIngredients[a] == nil { - allergensToPossibleIngredients[a] = ingredients - } else { - // otherwise take the inner join/overlap to eliminate ingredients - allergensToPossibleIngredients[a] = slice.IntersectionStrings(allergensToPossibleIngredients[a], ingredients) - } - } - } - - // iterate through the allergens to possible map and if a slice of length 1 - // is found, remove that ingredient from all other value slices - // do this until every slice has only one possible ingredient - for { - allSingle := true - for allergen, possible := range allergensToPossibleIngredients { - if len(possible) != 1 { - allSingle = false - } else { - // remove this name from all lists - for otherAllergen, otherIngredients := range allergensToPossibleIngredients { - if otherAllergen != allergen { - allergensToPossibleIngredients[otherAllergen] = slice.RemoveAllStrings(otherIngredients, possible[0]) - } - } - } - } - if allSingle { - break - } - } - - // remove the allergens from the ingredientsCount map - for _, hashedName := range allergensToPossibleIngredients { - delete(ingredientCounts, hashedName[0]) - } - - // for part 1: count up the total occurrences of non-allergen ingredients - var count int - for _, ct := range ingredientCounts { - count += ct - } - - // for part 2: get a list of all allergens, sort them, then in order, add the - // hashed name to a canonical dangerous list - var names []string - for k := range allergensToPossibleIngredients { - names = append(names, k) - } - sort.Strings(names) - - var canonical []string - for _, n := range names { - canonical = append(canonical, allergensToPossibleIngredients[n][0]) - } - - return count, strings.Join(canonical, ",") -} diff --git a/2020/day21/main_test.go b/2020/day21/main_test.go deleted file mode 100644 index 1b58740..0000000 --- a/2020/day21/main_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func Test_allergenAssessment(t *testing.T) { - tests := []struct { - name string - input string - wantPart1 int - wantPart2 string - }{ - {"actual", util.ReadFile("input.txt"), 1815, "kllgt,jrnqx,ljvx,zxstb,gnbxs,mhtc,hfdxb,hbfnkq"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotPart1, gotPart2 := allergenAssessment(tt.input) - if gotPart1 != tt.wantPart1 { - t.Errorf("allergenAssessment() = %v, want %v", gotPart1, tt.wantPart1) - } - if gotPart2 != tt.wantPart2 { - t.Errorf("allergenAssessment() = %v, want %v", gotPart2, tt.wantPart2) - } - }) - } -} diff --git a/2020/day21/so-close.png b/2020/day21/so-close.png deleted file mode 100644 index 64b3c0f..0000000 Binary files a/2020/day21/so-close.png and /dev/null differ diff --git a/2020/day22/main.go b/2020/day22/main.go deleted file mode 100644 index bd0ccd3..0000000 --- a/2020/day22/main.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/mathy" - - "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) -} - -// leaderboard: 537 -func part1(input string) int { - deck1, deck2 := parseInput(input) - - for !(len(deck1) == 0 || len(deck2) == 0) { - top1, top2 := deck1[0], deck2[0] - if top1 > top2 { - deck1 = append(deck1, top1, top2) - } else { - deck2 = append(deck2, top2, top1) - } - deck1 = deck1[1:] - deck2 = deck2[1:] - } - - winningDeck := append(deck1, deck2...) - var sumOfProducts int - multiplier := 1 - for i := len(winningDeck) - 1; i >= 0; i-- { - sumOfProducts += multiplier * winningDeck[i] - multiplier++ - } - - return sumOfProducts -} - -func part2(input string) int { - deck1, deck2 := parseInput(input) - winningScore, _ := recursiveGame(deck1, deck2, true) - return winningScore -} - -// leaderboard: 997 -func recursiveGame(deck1, deck2 []int, isMainGame bool) (finalScore int, player1Wins bool) { - // after the fact optimization from: https://www.reddit.com/r/adventofcode/comments/khyjgv/2020_day_22_solutions/ggpcsnd - // reduces part2 time from ~30s to <0.5s - // IF player 1 has the largest present card AND the card's value is greater - // than the largest number of cards that a subgame could contain (lengths - 2) - // THEN a subgame will never start when player1's top card is that max card, - // and player 1 can never lose that card, so at some point, a pattern will - // repeat which leads to player 1 winning - if !isMainGame { - max1, max2 := mathy.MaxInt(deck1...), mathy.MaxInt(deck2...) - if max1 > max2 && max1 >= len(deck1)+len(deck2)-2 { - return 0, true - } - } - - previousHands1 := map[string]bool{} - previousHands2 := map[string]bool{} - - for !(len(deck1) == 0 || len(deck2) == 0) { - top1, top2 := deck1[0], deck2[0] - - if previousHands1[fmt.Sprintf("%v", deck1)] || previousHands2[fmt.Sprintf("%v", deck2)] { - player1Wins = true - } else { - previousHands1[fmt.Sprintf("%v", deck1)] = true - previousHands2[fmt.Sprintf("%v", deck2)] = true - - // if not enough cards in either deck, just compare cards - if top1 > len(deck1)-1 || top2 > len(deck2)-1 { - player1Wins = top1 > top2 - } else { - // otherwise recurse - _, player1Wins = recursiveGame(append([]int{}, deck1[1:top1+1]...), append([]int{}, deck2[1:top2+1]...), false) - } - } - - if player1Wins { - deck1 = append(deck1, top1, top2) - } else { - deck2 = append(deck2, top2, top1) - } - - deck1 = deck1[1:] - deck2 = deck2[1:] - } - - if !isMainGame { - // player1Wins boolean is equivalent to if their deck does not have zero cards - return 0, len(deck1) != 0 - } - - winningDeck := append(deck1, deck2...) - var sumOfProducts int - multiplier := 1 - for i := len(winningDeck) - 1; i >= 0; i-- { - sumOfProducts += multiplier * winningDeck[i] - multiplier++ - } - - return sumOfProducts, false // 997 -} - -func parseInput(input string) ([]int, []int) { - players := strings.Split(input, "\n\n") - var deck1, deck2 []int - for _, l := range strings.Split(players[0], "\n")[1:] { - deck1 = append(deck1, cast.ToInt(l)) - } - for _, l := range strings.Split(players[1], "\n")[1:] { - deck2 = append(deck2, cast.ToInt(l)) - } - return deck1, deck2 -} diff --git a/2020/day22/main_test.go b/2020/day22/main_test.go deleted file mode 100644 index 439a9a8..0000000 --- a/2020/day22/main_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `Player 1: -9 -2 -6 -3 -1 - -Player 2: -5 -8 -4 -7 -10` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {"example", example, 306}, - {"actual", util.ReadFile("input.txt"), 33403}, - } - 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 - }{ - {"example", example, 291}, - {"actual", util.ReadFile("input.txt"), 29177}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day23/main.go b/2020/day23/main.go deleted file mode 100644 index 80ae335..0000000 --- a/2020/day23/main.go +++ /dev/null @@ -1,117 +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 := crabCups(util.ReadFile("./input.txt"), part) - fmt.Println("Output:", ans) -} - -// LLNode is a doubly linked list node -type LLNode struct { - val int - left, right *LLNode -} - -// ~1250/ 199 -func crabCups(input string, part int) int { - cups := strings.Split(input, "") - - currentCup := &LLNode{val: cast.ToInt(cups[0])} - // LRU caches are a hashmap of integers pointing to a pointer of a (doubly) LL node - // so finding a given node is constant time through the hash map - lruCacheMap := map[int]*LLNode{currentCup.val: currentCup} - - // setup initial cups - iter := currentCup - for _, v := range cups[1:] { - iter.right = &LLNode{ - val: cast.ToInt(v), - left: iter, - } - iter = iter.right - lruCacheMap[iter.val] = iter - } - rounds := 100 // part 1 default - - // for part 2 make rounds higher and add a bunch of extra nodes - if part == 2 { - rounds = 10000000 - for i := 10; i <= 1000000; i++ { - iter.right = &LLNode{ - val: i, - left: iter, - } - iter = iter.right - lruCacheMap[iter.val] = iter - } - } - - iter.right = currentCup - currentCup.left = iter - - for i := 0; i < rounds; i++ { - valToFind := currentCup.val - 1 - - ignoreVals := map[int]bool{} - for it := currentCup.right; len(ignoreVals) < 3; it = it.right { - ignoreVals[it.val] = true - } - - // if the val to find is in ignore vals or is less than smallest possible - // value, then loop and decrement until a valid value is found - for ignoreVals[valToFind] || valToFind <= 0 { - valToFind-- - // loop back to highest numbered cup if value goes below zero - if valToFind <= 0 { - valToFind = len(lruCacheMap) - } - } - - // next 3 - startOfThree := currentCup.right - endOfThree := currentCup.right.right.right - - // remove the three off the end of the current cup - currentCup.right = endOfThree.right - currentCup.right.left = currentCup - - // find the LL node to attach the 3 cups to (directly from LRU cache) - attachAfterMe := lruCacheMap[valToFind] - // mash the 3 cups into the doubly LL - endOfThree.right = attachAfterMe.right - attachAfterMe.right.left = endOfThree - attachAfterMe.right = startOfThree - startOfThree.left = attachAfterMe - - // move currentCup pointer - currentCup = currentCup.right - } - - oneCup := lruCacheMap[1] - - if part == 2 { - return oneCup.right.val * oneCup.right.right.val - } - - var ans int - for i := 0; i < 8; i++ { - oneCup = oneCup.right - ans *= 10 - ans += oneCup.val - } - - return ans -} diff --git a/2020/day23/main_test.go b/2020/day23/main_test.go deleted file mode 100644 index ab60e91..0000000 --- a/2020/day23/main_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -func Test_crabCups(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"example_part1", "389125467", 1, 67384529}, - {"actual_part1", util.ReadFile("input.txt"), 1, 47382659}, - {"example_part2", "389125467", 2, 149245887792}, - {"actual_part2", util.ReadFile("input.txt"), 2, 42271866720}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := crabCups(tt.input, tt.part); got != tt.want { - t.Errorf("crabCups() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2020/day24/main.go b/2020/day24/main.go deleted file mode 100644 index 64ecd8e..0000000 --- a/2020/day24/main.go +++ /dev/null @@ -1,143 +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 := part1(util.ReadFile("./input.txt"), part) - fmt.Println("Output:", ans) -} - -// 1240/668 -func part1(input string, part int) int { - instructions := strings.Split(input, "\n") - tileIsBlack := map[[6]int]bool{} - for _, inst := range instructions { - // tally the steps taken in each of the hex directions - var tallyDirections [6]int - - // iterate thorugh the NONDELIMITED instructions - for i := 0; i < len(inst); { - // set the direction to the next two characters if possible - dir := string(inst[i]) - if i+2 <= len(inst) { - dir = inst[i : i+2] - } - - // if the two character direction is se, sw, nw or ne, both characters - // must be used, because using one is invalid ("n" or "s") - switch dir { - case "se", "sw", "nw", "ne": - tallyDirections[dirIndices[dir]]++ - i += 2 - default: - tallyDirections[dirIndices[dir[0:1]]]++ - i++ - } - // collapse and directions that cancel out, such as sw/nw -> w - tallyDirections = zeroOutHexDirections(tallyDirections) - } - // flip that tile - tileIsBlack[tallyDirections] = !tileIsBlack[tallyDirections] - } - - // for part 2, play game of life 100 times - if part == 2 { - for i := 0; i < 100; i++ { - // flip based on neighbors - nextState := map[[6]int]bool{} - - // collect all coordinates to check - toCheck := map[[6]int]bool{} - for i := 0; i < 6; i++ { - for k := range tileIsBlack { - k[i]++ - toCheck[zeroOutHexDirections(k)] = true - } - } - - for coord := range toCheck { - // count neighbors - var neighbors int - for i := 0; i < 6; i++ { - clone := coord // don't want to modify the original coord - clone[i]++ // generates the six directions around coord - clone = zeroOutHexDirections(clone) - if tileIsBlack[clone] { - neighbors++ - } - } - // flipping logic: - // back with zero or more than 2 neighbors becomes white - if tileIsBlack[coord] && (neighbors == 0 || neighbors > 2) { - nextState[coord] = false - } else if !tileIsBlack[coord] && neighbors == 2 { - // white with exactly 2 neighbors becomes black - nextState[coord] = true - } else { - // stays the same - nextState[coord] = tileIsBlack[coord] - } - } - tileIsBlack = nextState - } - } - - var count int - for _, b := range tileIsBlack { - if b { - count++ - } - } - - return count - -} - -var dirIndices = map[string]int{ - "e": 0, - "se": 1, - "sw": 2, - "w": 3, - "nw": 4, - "ne": 5, -} - -// borrowed from my 2017 day 11 code which calculated hex coordinate manhattan distances -func zeroOutHexDirections(tally [6]int) [6]int { - // zero out opposite indices - for i := range tally { - if tally[i] != 0 { - oppositeIndex := (i + 3) % 6 - smaller := mathy.MinInt(tally[oppositeIndex], tally[i]) - tally[oppositeIndex] -= smaller - tally[i] -= smaller - } - } - - // handle neighbors which collapse into the current direction - // e.g. sw,se == s - for i := range tally { - toLeft := (i + 5) % 6 - toRight := (i + 1) % 6 - if tally[toLeft] > 0 && tally[toRight] > 0 { - smaller := mathy.MinInt(tally[toLeft], tally[toRight]) - tally[toLeft] -= smaller - tally[toRight] -= smaller - tally[i] += smaller - } - } - - return tally -} diff --git a/2020/day24/main_test.go b/2020/day24/main_test.go deleted file mode 100644 index 54a2691..0000000 --- a/2020/day24/main_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "testing" - - "github.com/alexchao26/advent-of-code-go/util" -) - -var example = `sesenwnenenewseeswwswswwnenewsewsw -neeenesenwnwwswnenewnwwsewnenwseswesw -seswneswswsenwwnwse -nwnwneseeswswnenewneswwnewseswneseene -swweswneswnenwsewnwneneseenw -eesenwseswswnenwswnwnwsewwnwsene -sewnenenenesenwsewnenwwwse -wenwwweseeeweswwwnwwe -wsweesenenewnwwnwsenewsenwwsesesenwne -neeswseenwwswnwswswnw -nenwswwsewswnenenewsenwsenwnesesenew -enewnwewneswsewnwswenweswnenwsenwsw -sweneswneswneneenwnewenewwneswswnese -swwesenesewenwneswnwwneseswwne -enesenwswwswneneswsenwnewswseenwsese -wnwnesenesenenwwnenwsewesewsesesew -nenewswnwewswnenesenwnesewesw -eneswnwswnwsenenwnwnwwseeswneewsenese -neswnwewnwnwseenwseesewsenwsweewe -wseweeenwnesenwwwswnew` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - {"part1_example", example, 1, 10}, - {"part1_actual", util.ReadFile("input.txt"), 1, 277}, - {"part2_example", example, 2, 2208}, - {"part2_actual", util.ReadFile("input.txt"), 2, 3531}, - } - 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) - } - }) - } -} diff --git a/2020/day25/main.go b/2020/day25/main.go deleted file mode 100644 index dc62139..0000000 --- a/2020/day25/main.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -func main() { - ans := part1(util.ReadFile("./input.txt")) - fmt.Println("Output:", ans) -} - -// ~1900.. -func part1(input string) int { - var publicKeys []int - for _, l := range strings.Split(input, "\n") { - publicKeys = append(publicKeys, cast.ToInt(l)) - } - - var loopSizes [2]int - val := 1 - // calculate both loop sizes in one pass to avoid recalculating over and over... - for loops := 1; loopSizes[0] == 0 || loopSizes[1] == 0; loops++ { - val *= 7 - val %= 20201227 - if val == publicKeys[0] { - loopSizes[0] = loops - } - if val == publicKeys[1] { - loopSizes[1] = loops - } - } - - // ensure both loop sizes are correct by reaching the same encryption key - key1 := runTranformations(publicKeys[0], loopSizes[1]) - key2 := runTranformations(publicKeys[1], loopSizes[0]) - if key1 != key2 { - panic(fmt.Sprintf("encryption keys should be the same, got %d and %d", key1, key2)) - } - - return key1 -} - -func runTranformations(subject, loops int) int { - val := 1 - for i := 0; i < loops; i++ { - val *= subject - val %= 20201227 - } - return val -} diff --git a/2020/day25/main_test.go b/2020/day25/main_test.go deleted file mode 100644 index c8f0f4a..0000000 --- a/2020/day25/main_test.go +++ /dev/null @@ -1,25 +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", "5764801\n17807724", 14897079}, - {"actual", util.ReadFile("input.txt"), 18608573}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day01/main.go b/2021/day01/main.go deleted file mode 100644 index 0b98048..0000000 --- a/2021/day01/main.go +++ /dev/null @@ -1,64 +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) - - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - parsed := parseInput(input) - - var increased int - for i, num := range parsed { - if i > 0 { - if num > parsed[i-1] { - increased++ - } - } - } - - return increased -} - -func part2(input string) int { - var increased int - parsed := parseInput(input) - - for i := 0; i < len(parsed)-3; i++ { - left := parsed[i] + parsed[i+1] + parsed[i+2] - right := parsed[i+1] + parsed[i+2] + parsed[i+3] - if right > left { - increased++ - } - } - - return increased -} - -func parseInput(input string) (ans []int) { - for _, l := range strings.Split(input, "\n") { - ans = append(ans, cast.ToInt(l)) - } - return ans -} diff --git a/2021/day01/main_test.go b/2021/day01/main_test.go deleted file mode 100644 index bf0c080..0000000 --- a/2021/day01/main_test.go +++ /dev/null @@ -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"), 1754}, - } - 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"), 1789}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day02/main.go b/2021/day02/main.go deleted file mode 100644 index 75ef99f..0000000 --- a/2021/day02/main.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := day2(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func day2(input string, part int) int { - var horiz, depth int - var aim int - - for _, line := range strings.Split(input, "\n") { - parts := strings.Split(line, " ") - dir := parts[0] - dist := cast.ToInt(parts[1]) - - if part == 1 { - switch dir { - case "down": - depth += dist - case "up": - depth -= dist - case "forward": - horiz += dist - } - } else { - switch dir { - case "down": - aim += dist - case "up": - aim -= dist - case "forward": - horiz += dist - depth += aim * dist - } - } - } - - return horiz * depth -} diff --git a/2021/day02/main_test.go b/2021/day02/main_test.go deleted file mode 100644 index 3d0f8d2..0000000 --- a/2021/day02/main_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `forward 5 -down 5 -forward 8 -up 3 -down 8 -forward 2` - -func Test_day2(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - { - name: "example", - input: example, - part: 1, - want: 150, - }, - { - name: "actual", - input: input, - part: 1, - want: 1813801, - }, - { - name: "example", - input: example, - part: 2, - want: 900, - }, - { - name: "actual", - input: input, - part: 2, - want: 1960569556, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := day2(tt.input, tt.part); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day03/main.go b/2021/day03/main.go deleted file mode 100644 index 82aad8e..0000000 --- a/2021/day03/main.go +++ /dev/null @@ -1,150 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - var gamma, epsilon string - binaries := strings.Split(input, "\n") - for i := 0; i < len(binaries[0]); i++ { - var zeroes, ones int - for _, b := range binaries { - if b[i] == '0' { - zeroes++ - } else { - ones++ - } - } - - // gamma is the most common bit at each index (accross all bits) - // epsilon is the opposite - if zeroes > ones { - gamma += "0" - epsilon += "1" - } else { - gamma += "1" - epsilon += "0" - - } - } - - // multiply together for final ans - // actual answer in decimal, not binary - - e, err := strconv.ParseInt(epsilon, 2, 64) - if err != nil { - panic(err) - } - g, err := strconv.ParseInt(gamma, 2, 64) - if err != nil { - panic(err) - } - return int(e * g) -} - -func part2(input string) int { - // filtering values until one remains - - // consider just first bit of each number - // only keep numbers for the correct bit criteria - // stop when only one number is left, otherwise continue onto the next bit - nums := strings.Split(input, "\n") - // assume this will work and len(nums) will eventually hit 1 - for i := 0; len(nums) > 1; i++ { - var zeroes, ones int - for _, n := range nums { - if n[i] == '0' { - zeroes++ - } else { - ones++ - } - } - // bit criteria: - // oxygen: most common value of the bit, 1s win ties - // CO2: opposite, 0s win ties - keepChar := "1" // 1s win ties for oxygen - if zeroes > ones { - keepChar = "0" - } - - var newNums []string - for _, n := range nums { - if string(n[i]) == keepChar { - newNums = append(newNums, n) - } - } - nums = newNums - } - oxygen := nums[0] - - // copy pasta for co2 - nums = strings.Split(input, "\n") - // assume this will work and len(nums) will eventually hit 1 - for i := 0; len(nums) > 1; i++ { - var zeroes, ones int - for _, n := range nums { - if n[i] == '0' { - zeroes++ - } else { - ones++ - } - } - // bit criteria: - // oxygen: most common value of the bit, 1s win ties - // CO2: opposite, 0s win ties - keepChar := "0" // 0s win ties for co2 - if ones < zeroes { - keepChar = "1" - } - - var newNums []string - for _, n := range nums { - if string(n[i]) == keepChar { - newNums = append(newNums, n) - } - } - nums = newNums - } - co2 := nums[0] - - // multiplying the oxygen generator rating by the CO2 scrubber rating. - o, _ := strconv.ParseInt(oxygen, 2, 64) - c, _ := strconv.ParseInt(co2, 2, 64) - - return int(c * o) -} diff --git a/2021/day03/main_test.go b/2021/day03/main_test.go deleted file mode 100644 index 3e958fb..0000000 --- a/2021/day03/main_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `00100 -11110 -10110 -10111 -10101 -01111 -00111 -11100 -10000 -11001 -00010 -01010` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 198, - }, - { - name: "actual", - input: input, - want: 3320834, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 230, - }, - { - name: "actual", - input: input, - want: 4481199, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day04/main.go b/2021/day04/main.go deleted file mode 100644 index 3d9c6d9..0000000 --- a/2021/day04/main.go +++ /dev/null @@ -1,175 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - nums, boards := parseInput(input) - - for _, n := range nums { - for _, b := range boards { - didWin := b.PickNum(n) - if didWin { - // multiply score of winning board by number that was just called - return b.Score() * n - } - } - } - - panic("a board should've won and returned from the loop") -} - -func part2(input string) int { - nums, boards := parseInput(input) - - lastWinningScore := -1 - alreadyWon := map[int]bool{} - for _, n := range nums { - for bi, b := range boards { - if alreadyWon[bi] { - continue - } - didWin := b.PickNum(n) - if didWin { - // WHICH BOARD WINS LAST - lastWinningScore = b.Score() * n - - // mark board as already won - alreadyWon[bi] = true - } - } - } - - return lastWinningScore - -} - -// BoardState maintains a parsed board and a boolean matrix of cells that have -// been picked/marked -type BoardState struct { - board [][]int - picked [][]bool -} - -func NewBoardState(board [][]int) BoardState { - picked := make([][]bool, len(board)) - for i := range picked { - picked[i] = make([]bool, len(board[0])) - } - return BoardState{ - board: board, - picked: picked, - } -} - -func (b *BoardState) PickNum(num int) bool { - for r, rows := range b.board { - for c, v := range rows { - if v == num { - b.picked[r][c] = true - } - } - } - - // is this fast enough to do on every "cycle"? - // guess so. probably a constant time way to do this but oh well - for i := 0; i < len(b.board); i++ { - isFullRow, isFullCol := true, true - // board is square so this works fine, otherwise would need another pair of nested loops - for j := 0; j < len(b.board); j++ { - // check row at index i - if !b.picked[i][j] { - isFullRow = false - } - // check col at index j - if !b.picked[j][i] { - isFullCol = false - } - } - if isFullRow || isFullCol { - // returns true if is winning board - return true - } - } - - // false for incomplete board - return false -} - -func (b *BoardState) Score() int { - var score int - - for r, rows := range b.board { - for c, v := range rows { - // adds up all the non-picked/marked cells - if !b.picked[r][c] { - score += v - } - } - } - - return score -} - -func parseInput(input string) (nums []int, boards []BoardState) { - lines := strings.Split(input, "\n\n") - - for _, v := range strings.Split(lines[0], ",") { - nums = append(nums, cast.ToInt(v)) - } - - for _, grid := range lines[1:] { - b := [][]int{} - for _, line := range strings.Split(grid, "\n") { - line = strings.ReplaceAll(line, " ", " ") - for line[0] == ' ' { - line = line[1:] - } - parts := strings.Split(line, " ") - - row := []int{} - for _, p := range parts { - row = append(row, cast.ToInt(p)) - } - b = append(b, row) - } - - boards = append(boards, NewBoardState(b)) - } - return nums, boards -} diff --git a/2021/day04/main_test.go b/2021/day04/main_test.go deleted file mode 100644 index 4087f74..0000000 --- a/2021/day04/main_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1 - -22 13 17 11 0 - 8 2 23 4 24 -21 9 14 16 7 - 6 10 3 18 5 - 1 12 20 15 19 - - 3 15 0 2 22 - 9 18 13 17 5 -19 8 7 25 23 -20 11 10 24 4 -14 21 16 12 6 - -14 21 17 24 4 -10 16 15 9 19 -18 8 23 26 20 -22 11 13 6 5 - 2 0 12 3 7` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 4512, - }, - { - name: "actual", - input: input, - want: 58838, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 1924, - }, - { - name: "actual", - input: input, - want: 6256, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day05/main.go b/2021/day05/main.go deleted file mode 100644 index af04c26..0000000 --- a/2021/day05/main.go +++ /dev/null @@ -1,130 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := countIntersections(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func countIntersections(input string, part int) int { - var coords [][4]int - for _, line := range strings.Split(input, "\n") { - var x1, y1, x2, y2 int - _, err := fmt.Sscanf(line, "%d,%d -> %d,%d", &x1, &y1, &x2, &y2) - if err != nil { - panic("parsing error: " + err.Error()) - } - coords = append(coords, [4]int{x1, y1, x2, y2}) - } - - // find largest x and y - // Note: different x,y as prompt, x = rows, y = cols instead - var endRow, endCol int - for _, c := range coords { - if c[0] > endRow { - endRow = c[0] - } - if c[2] > endRow { - endRow = c[2] - } - if c[1] > endCol { - endCol = c[1] - } - if c[3] > endCol { - endCol = c[3] - } - } - - grid := make([][]int, endRow+1) - for i := range grid { - grid[i] = make([]int, endCol+1) - } - - for _, c := range coords { - if c[0] == c[2] { - // horiz line, row1 = row2 - row := c[0] - start, end := c[1], c[3] - // swap start and end if needed - if c[1] > c[3] { - start, end = end, start - } - for col := start; col <= end; col++ { - grid[row][col]++ - } - } else if c[1] == c[3] { - // vert line, col1 = col2 - col := c[1] - start, end := c[0], c[2] - // swap start and end if needed - if c[0] > c[2] { - start, end = end, start - } - for row := start; row <= end; row++ { - grid[row][col]++ - } - } else if part == 2 { - // check diagonals for part 2 only - - // get left most pair first by comparing column coords - if c[1] > c[3] { - c = [4]int{ - c[2], c[3], - c[0], c[1], - } - } - - // compare row coords next, will be going rightwards regardless - // if going down and right - if c[0] < c[2] { - for row := c[0]; row <= c[2]; row++ { - col := c[1] + row - c[0] - grid[row][col]++ - } - } else { - // going up and right - for row := c[0]; row >= c[2]; row-- { - col := c[1] + (c[0] - row) - grid[row][col]++ - } - } - } - } - - // count up intersections - var ans int - for _, rows := range grid { - for _, v := range rows { - if v >= 2 { - ans++ - } - } - } - - return ans -} diff --git a/2021/day05/main_test.go b/2021/day05/main_test.go deleted file mode 100644 index aff6e55..0000000 --- a/2021/day05/main_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `0,9 -> 5,9 -8,0 -> 0,8 -9,4 -> 3,4 -2,2 -> 2,1 -7,0 -> 7,4 -6,4 -> 2,0 -0,9 -> 2,9 -3,4 -> 1,4 -0,0 -> 8,8 -5,5 -> 8,2` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - { - name: "example", - input: example, - part: 1, - want: 5, - }, - { - name: "actual", - input: input, - part: 1, - want: 4993, - }, - { - name: "example", - input: example, - part: 2, - want: 12, - }, - { - name: "actual", - input: input, - part: 2, - want: 21101, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.part == 0 { - t.Error("part number cannot be zero") - } - if got := countIntersections(tt.input, tt.part); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day06/main.go b/2021/day06/main.go deleted file mode 100644 index 22ef7ee..0000000 --- a/2021/day06/main.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -// 180/1133 -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := step(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func step(input string, part int) int { - state := make([]int, 9) - for _, num := range strings.Split(input, ",") { - daysLeft := cast.ToInt(num) - state[daysLeft]++ - } - - // different number of days for part 1 vs part 2 - days := 80 - if part == 2 { - days = 256 - } - - // count down to zero - // then it makes a new lanternfish with a count of 8 - for d := 0; d < days; d++ { - save := state[0] - for i := 0; i < len(state)-1; i++ { - state[i] = state[i+1] - } - state[8] = save - state[6] += save - } - - // count up final sum - var sum int - for _, n := range state { - sum += n - } - return sum -} diff --git a/2021/day06/main_test.go b/2021/day06/main_test.go deleted file mode 100644 index d5a7a94..0000000 --- a/2021/day06/main_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `3,4,3,1,2` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - { - name: "example", - input: example, - part: 1, - want: 5934, // after 80 days - }, - { - name: "actual", - input: input, - part: 1, - want: 362666, - }, - { - name: "example", - input: example, - part: 2, - want: 26984457539, - }, - { - name: "actual", - input: input, - part: 2, - want: 1640526601595, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.part == 0 { - t.Error("part value cannot be zero") - } - if got := step(tt.input, tt.part); got != tt.want { - t.Errorf("step() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day07/main.go b/2021/day07/main.go deleted file mode 100644 index d382d68..0000000 --- a/2021/day07/main.go +++ /dev/null @@ -1,84 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "math" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := calcMinFuel(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func calcMinFuel(input string, part int) int { - var startingPositions []int - for _, val := range strings.Split(input, ",") { - startingPositions = append(startingPositions, cast.ToInt(val)) - } - - // horizontal positions of each crab - // limited fuel, so all horizontal positions need to match - // part1: 1 fuel to move 1 space - // part2: 1 fuel to move 1st space, 2 for 2nd, 3 for 3rd, etc... - - // find bounds to run loop through - lowest, highest := startingPositions[0], startingPositions[0] - for _, v := range startingPositions { - if v < lowest { - lowest = v - } - if v > highest { - highest = v - } - } - - bestFuelCost := math.MaxInt64 - for finalIndex := lowest; finalIndex <= highest; finalIndex++ { - // calculate diffs to all - cost := 0 - for _, startIndex := range startingPositions { - horizDiff := int(math.Abs(float64(startIndex - finalIndex))) - if part == 1 { - cost += horizDiff - } else { - cost += calcSummationFromOneToEnd(horizDiff) - } - } - - if cost < bestFuelCost { - bestFuelCost = cost - } - } - - return bestFuelCost -} - -func calcSummationFromOneToEnd(end int) int { - // 1 2 3 4 5 - // (1 + 5) * 2.5 - ans := float64(end+1) * float64(end) / 2 - return int(ans) -} diff --git a/2021/day07/main_test.go b/2021/day07/main_test.go deleted file mode 100644 index b134212..0000000 --- a/2021/day07/main_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - _ "embed" - "testing" -) - -var example = `16,1,2,0,4,2,7,1,2,14` - -func Test_calcMinFuel(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - { - name: "example", - input: example, - part: 1, - want: 37, - }, - { - name: "actual", - input: input, - part: 1, - want: 329389, - }, - { - name: "example", - input: example, - part: 2, - want: 168, - }, - { - name: "actual", - input: input, - part: 2, - want: 86397080, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := calcMinFuel(tt.input, tt.part); got != tt.want { - t.Errorf("calcMinFuel() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_calcSummationFromOneToEnd(t *testing.T) { - // auto generated these tests because math is rough... - type args struct { - end int - } - tests := []struct { - name string - args args - want int - }{ - { - args: args{end: 4}, - want: 10, - }, - { - args: args{end: 5}, - want: 15, - }, - { - args: args{end: 6}, - want: 21, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := calcSummationFromOneToEnd(tt.args.end); got != tt.want { - t.Errorf("calcSummationFromOneToEnd() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day08/main.go b/2021/day08/main.go deleted file mode 100644 index 62fe7fc..0000000 --- a/2021/day08/main.go +++ /dev/null @@ -1,229 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "log" - "regexp" - "sort" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := jumbledSevenSegment(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func jumbledSevenSegment(input string, part int) int { - // four digit seven segment displays - // seven segments are letters - // need to unjumble the ten mappings (pre-bar) to outputs (last 4 post-bar) - var parsedInput [][]string - for i, line := range strings.Split(input, "\n") { - // regexp capture group to just get characters - parts := regexp.MustCompile(`([a-g]+)`).FindAllString(line, -1) - - if len(parts) != 14 { - log.Fatalf("should be 14 parts in each input line, got %d for line %d", len(parts), i) - } - - // return them as just all 14 mappings together, alphabetize them for easier comparison later - var fourteen []string - for _, v := range parts { - fourteen = append(fourteen, alphabetizeString(v)) - } - parsedInput = append(parsedInput, fourteen) - } - - // PART 1 just count the outputs that are "easy" to determine - // aka only one displayed number uses that number of segments - if part == 1 { - var ans int - for _, set := range parsedInput { - // check the back 4: output - for _, o := range set[10:] { - switch len(o) { - // 1, 4, 7, 8 have a unique number of segments lit - // 2, 4, 4, 7 segments respectively - case 2, 4, 3, 7: - ans++ - } - } - } - return ans - } - - // PART 2 - var ans int - indexToCharacters := make([]string, 10) - for _, set := range parsedInput { - - workingSet := set[:10] - var killIndices []int // store the indices that will need to be removed in batches - - // 1, 4, 7 and 8 are all easy to find because they have a unique number of segments - for i, mapping := range workingSet { - switch len(mapping) { - case 2: - // these two make up the 1 - indexToCharacters[1] = mapping - killIndices = append(killIndices, i) - case 4: // the 4 - indexToCharacters[4] = mapping - killIndices = append(killIndices, i) - case 3: // the 7 - indexToCharacters[7] = mapping - killIndices = append(killIndices, i) - case 7: // the 8 - indexToCharacters[8] = mapping - killIndices = append(killIndices, i) - } - } - - // remove them from the workingSet - workingSet = removeSliceIndices(workingSet, killIndices...) - - // only 0 3 and 9 will fully overlap the 1 characters - var zeroThreeOrNine []string - killIndices = []int{} // reset these... - for i, mapping := range workingSet { - if checkStringOverlap(mapping, indexToCharacters[1]) { - zeroThreeOrNine = append(zeroThreeOrNine, mapping) - killIndices = append(killIndices, i) - } - } - if len(zeroThreeOrNine) != 3 { - log.Fatalf("one three or nine does not have three matches: got %d", len(zeroThreeOrNine)) - } - - // find the 3 based on segment length - for i, maybe039 := range zeroThreeOrNine { - if len(maybe039) == 5 { - // must be the 3 - indexToCharacters[3] = maybe039 - zeroThreeOrNine = removeSliceIndices(zeroThreeOrNine, i) - break - } - } - - // the 9 will have a full overlap with 4 - for i, maybe09 := range zeroThreeOrNine { - if checkStringOverlap(maybe09, indexToCharacters[4]) { - indexToCharacters[9] = maybe09 - zeroThreeOrNine = removeSliceIndices(zeroThreeOrNine, i) - } - } - - // 0 is only one left of the triplet - indexToCharacters[0] = zeroThreeOrNine[0] - - // trim down working set again - workingSet = removeSliceIndices(workingSet, killIndices...) - if len(workingSet) != 3 { - log.Fatalf("expected length of 3 at this stage, got %d", len(workingSet)) - } - - // 6 is the last one with a length of 6 - for i, mapping := range workingSet { - if len(mapping) == 6 { - indexToCharacters[6] = mapping - workingSet = removeSliceIndices(workingSet, i) - } - } - - // 5 will be fully contained within the 9 - for i, mapping := range workingSet { - if checkStringOverlap(indexToCharacters[9], mapping) { - indexToCharacters[5] = mapping - workingSet = removeSliceIndices(workingSet, i) - } - } - - if len(workingSet) != 1 { - log.Fatalf("expected length of 1 at this stage, got %d", len(workingSet)) - } - - // 2 is the last remaining mapping - indexToCharacters[2] = workingSet[0] - - // finally, collect the four digits in the output & add it to the answer - var num int - for _, out := range set[10:] { - for i, mapping := range indexToCharacters { - // because they were all alphabetized, we can just do a direct comparison - if out == mapping { - // shift all digits to the left and add the new digit to the end - num *= 10 - num += i - } - } - } - ans += num - } - - return ans -} - -func removeSliceIndices(sli []string, indices ...int) []string { - m := map[int]bool{} - for _, v := range indices { - m[v] = true - } - - var ans []string - for i, v := range sli { - if !m[i] { - ans = append(ans, v) - } - } - return ans -} - -func checkStringOverlap(larger, smaller string) bool { - // safeguard, a smaller string can never contain a larger string - // i think every use of this function already passes args in the correct order, but this makes - // the checkStringOverlap algo more robust in general - if len(larger) < len(smaller) { - larger, smaller = smaller, larger - } - - largeMap := map[rune]bool{} - for _, r := range larger { - largeMap[r] = true - } - - // check all runes in the smaller string, return false if not in largeMap - for _, r := range smaller { - if !largeMap[r] { - return false - } - } - return true -} - -// alphabetize the strings to make them easier to compare later -func alphabetizeString(input string) string { - chars := strings.Split(input, "") - sort.Strings(chars) - return strings.Join(chars, "") -} diff --git a/2021/day08/main_test.go b/2021/day08/main_test.go deleted file mode 100644 index e3b3529..0000000 --- a/2021/day08/main_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "testing" -) - -var shortExample = `acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb fcadb cdfeb cdbaf` -var example = `be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe -edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc -fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg -fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb -aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea -fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb -dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe -bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef -egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb -gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce` - -func Test_jumbledSevenSegment(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - { - name: "example", - input: example, - part: 1, - want: 26, - }, - { - name: "actual", - input: input, - part: 1, - want: 367, - }, - { - name: "short_example", - input: shortExample, - part: 2, - want: 5353, - }, - { - name: "example", - input: example, - part: 2, - want: 61229, - }, - { - name: "actual", - input: input, - part: 2, - want: 974512, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := jumbledSevenSegment(tt.input, tt.part); got != tt.want { - t.Errorf("jumbledSevenSegment() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day09/main.go b/2021/day09/main.go deleted file mode 100644 index 7fda21e..0000000 --- a/2021/day09/main.go +++ /dev/null @@ -1,166 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "sort" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - grid := parseInput(input) - - diffs := [][]int{ - {-1, 0}, - {1, 0}, - {0, -1}, - {0, 1}, - } - - var totalRisk int - for r, rows := range grid { - for c, v := range rows { - lowerThanAll := true - for _, d := range diffs { - dr, dc := r+d[0], c+d[1] - // in bounds - if dr >= 0 && dr < len(grid) && dc >= 0 && dc < len(grid[0]) { - // neighbor is higher or even, r,c is not a low point - if grid[dr][dc] <= v { - lowerThanAll = false - break - } - } - } - - if lowerThanAll { - totalRisk += 1 + v - } - } - } - - return totalRisk -} - -func part2(input string) int { - grid := parseInput(input) - - diffs := [][]int{ - {-1, 0}, - {1, 0}, - {0, -1}, - {0, 1}, - } - - // copy pasta but collecting the coordinates to check - var lowPoints [][2]int - for r, rows := range grid { - for c, v := range rows { - lowerThanAll := true - for _, d := range diffs { - dr, dc := r+d[0], c+d[1] - if dr >= 0 && dr < len(grid) && dc >= 0 && dc < len(grid[0]) { - if grid[dr][dc] <= v { - lowerThanAll = false - break - } - } - } - - if lowerThanAll { - lowPoints = append(lowPoints, [2]int{r, c}) - } - } - } - - // go through all lowpoints and get basin sizes via helper func - var basins []int - for _, lp := range lowPoints { - basins = append(basins, getBasinSize(grid, lp[0], lp[1], map[[2]int]bool{})) - } - - // return 3 largest basins multiplied together - ans := 1 - sort.Ints(basins) - for i := 0; i < 3; i++ { - ans *= basins[len(basins)-1-i] - } - - return ans -} - -var diffs = [][]int{ - {-1, 0}, - {1, 0}, - {0, -1}, - {0, 1}, -} - -func getBasinSize(grid [][]int, r, c int, basinCoords map[[2]int]bool) int { - // assume that every cell will be involved in one basin, just have to stop at nines - if grid[r][c] == 9 { - return 0 - } - - coord := [2]int{r, c} - // stop if already seen - if basinCoords[coord] { - return 0 - } - // mark as seen - basinCoords[coord] = true - - for _, d := range diffs { - dr, dc := r+d[0], c+d[1] - if dr >= 0 && dr < len(grid) && dc >= 0 && dc < len(grid[0]) { - // recurse - getBasinSize(grid, dr, dc, basinCoords) - } - } - - // final size of coords map is the basin size - return len(basinCoords) -} - -func parseInput(input string) (ans [][]int) { - for _, line := range strings.Split(input, "\n") { - var nums []int - for _, v := range strings.Split(line, "") { - nums = append(nums, cast.ToInt(v)) - } - ans = append(ans, nums) - } - return ans -} diff --git a/2021/day09/main_test.go b/2021/day09/main_test.go deleted file mode 100644 index 786f623..0000000 --- a/2021/day09/main_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `2199943210 -3987894921 -9856789892 -8767896789 -9899965678` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 15, - }, - { - name: "actual", - input: input, - want: 580, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 1134, - }, - { - name: "actual", - input: input, - want: 856716, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day10/main.go b/2021/day10/main.go deleted file mode 100644 index a7974cb..0000000 --- a/2021/day10/main.go +++ /dev/null @@ -1,110 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "sort" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -// 1470/1321 -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := syntaxScoring(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func syntaxScoring(input string, part int) int { - var lines [][]string - for _, l := range strings.Split(input, "\n") { - lines = append(lines, strings.Split(l, "")) - } - - closedToOpens := map[string]string{ - ")": "(", - "]": "[", - ">": "<", - "}": "{", - } - - // for part 1 - illegalScores := map[string]int{ - ")": 3, - "]": 57, - "}": 1197, - ">": 25137, - } - - // for part 2 - autoCompleteScores := map[string]int{ - // needed to invert these to match what was left on the stack - // alternatively could have used the closedToOpens map - "(": 1, - "[": 2, - "{": 3, - "<": 4, - } - - // balanced parens - var syntaxErrorScore int // part 1 - var part2Scores []int // part 2 - for _, line := range lines { - var stack []string - var isCorrupted bool - for _, char := range line { - // part1: assign score for first illegal character - opp, ok := closedToOpens[char] - if !ok { - stack = append(stack, char) - } else { - if len(stack) == 0 { - panic("empty stack") - } - top := stack[len(stack)-1] - stack = stack[:len(stack)-1] - if top != opp { - syntaxErrorScore += illegalScores[char] - isCorrupted = true - } - } - } - - // part 2: calculate score of the string needed to make the string valid - if !isCorrupted { - // stack contains all unmatched chars... match them in reverse order - score := 0 - for i := len(stack) - 1; i >= 0; i-- { - score *= 5 - score += autoCompleteScores[stack[i]] - } - part2Scores = append(part2Scores, score) - } - } - - if part == 1 { - return syntaxErrorScore - } - - /// sort and return middle one, always an odd number of scores so just divide by 2 and rely on integer division - sort.Ints(part2Scores) - return part2Scores[len(part2Scores)/2] -} diff --git a/2021/day10/main_test.go b/2021/day10/main_test.go deleted file mode 100644 index 1ce7601..0000000 --- a/2021/day10/main_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `[({(<(())[]>[[{[]{<()<>> -[(()[<>])]({[<{<<[]>>( -{([(<{}[<>[]}>{[]{[(<()> -(((({<>}<{<{<>}{[]{[]{} -[[<[([]))<([[{}[[()]]] -[{[{({}]{}}([{[{{{}}([] -{<[[]]>}<{[{[{[]{()[[[] -[<(<(<(<{}))><([]([]() -<{([([[(<>()){}]>(<<{{ -<{([{{}}[<[[[<>{}]]]>[]]` - -func Test_syntaxScoring(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - { - name: "example", - input: example, - part: 1, - want: 26397, - }, - { - name: "actual", - input: input, - part: 1, - want: 387363, - }, - { - name: "example", - input: example, - part: 2, - want: 288957, - }, - { - name: "actual", - input: input, - part: 2, - want: 4330777059, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := syntaxScoring(tt.input, tt.part); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day11/main.go b/2021/day11/main.go deleted file mode 100644 index bb3bb9e..0000000 --- a/2021/day11/main.go +++ /dev/null @@ -1,122 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "math" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := flashingOctopiLol(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func flashingOctopiLol(input string, part int) int { - grid := parseInput(input) - - var flashed int - - adjacentDiffs := [][2]int{ - {-1, 0}, - {-1, -1}, - {-1, 1}, - {0, -1}, - {0, 1}, - {1, -1}, - {1, 0}, - {1, 1}, - } - - steps := 100 - if part == 2 { - // assume it'll run in less steps than 2^31-1... - steps = math.MaxInt32 - } - - for s := 0; s < steps; s++ { - // initial increment and store who will flash - var queueToFlash [][2]int - for i, row := range grid { - for j := range row { - grid[i][j]++ - if grid[i][j] > 9 { - queueToFlash = append(queueToFlash, [2]int{i, j}) - } - } - } - - // map tracks who has flashed, bc it's a map it'll also help dedupe and its length will be - // how many flashed on this step - seen := map[[2]int]bool{} - for len(queueToFlash) > 0 { - front := queueToFlash[0] - queueToFlash = queueToFlash[1:] - if seen[front] { - continue - } - seen[front] = true - - // increment neighbors - for _, d := range adjacentDiffs { - r, c := front[0]+d[0], front[1]+d[1] - // check in bounds - if r < 0 || r >= len(grid) || c < 0 || c >= len(grid[0]) { - continue - } - grid[r][c]++ - // check if neighbor should flash, seen map will dedupe so don't safeguard here - if grid[r][c] > 9 { - queueToFlash = append(queueToFlash, [2]int{r, c}) - } - } - } - - // part1, track how many have flashed in total - flashed += len(seen) - // set them all to zero - for c := range seen { - grid[c[0]][c[1]] = 0 - } - - // if all octopi flashed, return the step number (1-indexed) - if part == 2 && len(seen) == len(grid)*len(grid[0]) { - return s + 1 - } - } - - // for part 1 - return flashed -} - -func parseInput(input string) (ans [][]int) { - for _, line := range strings.Split(input, "\n") { - var row []int - for _, char := range strings.Split(line, "") { - row = append(row, cast.ToInt(char)) - } - ans = append(ans, row) - } - return ans -} diff --git a/2021/day11/main_test.go b/2021/day11/main_test.go deleted file mode 100644 index ce48290..0000000 --- a/2021/day11/main_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `5483143223 -2745854711 -5264556173 -6141336146 -6357385478 -4167524645 -2176841721 -6882881134 -4846848554 -5283751526` - -func Test_flashingOctopiLol(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - { - name: "example", - input: example, - part: 1, - want: 1656, - }, - { - name: "actual", - input: input, - part: 1, - want: 1723, - }, - { - name: "example", - input: example, - part: 2, - want: 195, - }, - { - name: "actual", - input: input, - part: 2, - want: 327, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := flashingOctopiLol(tt.input, tt.part); got != tt.want { - t.Errorf("flashingOctopiLol() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day12/main.go b/2021/day12/main.go deleted file mode 100644 index e679a70..0000000 --- a/2021/day12/main.go +++ /dev/null @@ -1,153 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -// 610/1663 -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - parsed := parseInput(input) - - // part 1 - // cannot visit SMALL caves more than once (lowercase ones) - // how many paths are there from start -> end - - graph := map[string]map[string]bool{} - for _, pair := range parsed { - if graph[pair[0]] == nil { - graph[pair[0]] = map[string]bool{} - } - if graph[pair[1]] == nil { - graph[pair[1]] = map[string]bool{} - } - graph[pair[0]][pair[1]] = true - graph[pair[1]][pair[0]] = true - } - - return walk(graph, "start", map[string]bool{"start": true}, []string{"start"}) -} - -func walk(graph map[string]map[string]bool, current string, visited map[string]bool, path []string) int { - if current == "end" { - return 1 - } - - var pathsToEnd int - - for visitable := range graph[current] { - if visited[visitable] && strings.ToUpper(visitable) != visitable { - continue - } - visited[current] = true - // path is basically unused (and wasting memory), but useful to debug sometimes - path = append(path, visitable) - - pathsToEnd += walk(graph, visitable, visited, path) - - // backtrack - visited[visitable] = false - path = path[:len(path)-1] - } - - return pathsToEnd -} - -func part2(input string) int { - parsed := parseInput(input) - - // cannot visit SMALL caves more than once (lowercase ones) - // how many paths are there from start -> end - - graph := map[string]map[string]bool{} - for _, pair := range parsed { - if graph[pair[0]] == nil { - graph[pair[0]] = map[string]bool{} - } - if graph[pair[1]] == nil { - graph[pair[1]] = map[string]bool{} - } - graph[pair[0]][pair[1]] = true - graph[pair[1]][pair[0]] = true - } - - return walk2(graph, "start", map[string]int{"start": 5}, []string{"start"}, false) -} - -func walk2(graph map[string]map[string]bool, current string, visited map[string]int, path []string, doubleUsed bool) int { - if current == "end" { - fmt.Println("path", path) - return 1 - } - - visited[current]++ - - var pathsToEnd int - - for visitable := range graph[current] { - if visitable == "start" { - continue - } - - if strings.ToUpper(visitable) != visitable && visited[visitable] > 0 { - if doubleUsed { - continue - } else { - doubleUsed = true - } - } - - path = append(path, visitable) - pathsToEnd += walk2(graph, visitable, visited, path, doubleUsed) - - // backtrack - visited[visitable]-- - path = path[:len(path)-1] - // backtrack doubleUsed IF this is a smallcave and reducing its visited count still has the - // cave marked as visited (aka count == 1) - if strings.ToUpper(visitable) != visitable && visited[visitable] == 1 { - doubleUsed = false - } - } - - return pathsToEnd -} - -func parseInput(input string) (ans [][]string) { - for _, line := range strings.Split(input, "\n") { - ans = append(ans, strings.Split(line, "-")) - } - return ans -} diff --git a/2021/day12/main_test.go b/2021/day12/main_test.go deleted file mode 100644 index 0f911e1..0000000 --- a/2021/day12/main_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `start-A -start-b -A-c -A-b -b-d -A-end -b-end` - -var example2 = `dc-end -HN-start -start-kj -dc-start -dc-HN -LN-dc -HN-end -kj-sa -kj-HN -kj-dc` - -var example3 = `fs-end -he-DX -fs-he -start-DX -pj-DX -end-zg -zg-sl -zg-pj -pj-he -RW-he -fs-DX -pj-RW -zg-RW -start-pj -he-WI -zg-he -pj-fs -start-RW` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 10, - }, - { - name: "example2", - input: example2, - want: 19, - }, - { - name: "example3", - input: example3, - want: 226, - }, - { - name: "actual", - input: input, - want: 3421, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 36, - }, - { - name: "example2", - input: example2, - want: 103, - }, - { - name: "example3", - input: example3, - want: 3509, - }, - { - name: "actual", - input: input, - want: 84870, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day13/main.go b/2021/day13/main.go deleted file mode 100644 index 845c564..0000000 --- a/2021/day13/main.go +++ /dev/null @@ -1,102 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "regexp" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/halp" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := transparentOrigamiDay13(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func transparentOrigamiDay13(input string, part int) int { - parts := strings.Split(input, "\n\n") - - coords := map[[2]int]bool{} - // parse coords - for _, line := range strings.Split(parts[0], "\n") { - sp := strings.Split(line, ",") - coords[[2]int{cast.ToInt(sp[0]), cast.ToInt(sp[1])}] = true - } - - for _, fold := range strings.Split(parts[1], "\n") { - cap := regexp.MustCompile(`fold along (x|y)=(\d+)`).FindStringSubmatch(fold) - // remove full match - cap = cap[1:] - - dir := cap[0] - foldCoord := cast.ToInt(cap[1]) - - // dots will never appear exactly on a fold line - isFoldOnX := dir == "x" - nextMap := map[[2]int]bool{} - if isFoldOnX { - for c := range coords { - if c[0] > foldCoord { - folded := [2]int{ - foldCoord - (c[0] - foldCoord), - c[1], - } - nextMap[folded] = true - } else { - nextMap[c] = true - } - } - } else { - // fold on y - for c := range coords { - if c[1] > foldCoord { - folded := [2]int{ - c[0], - foldCoord - (c[1] - foldCoord), - } - nextMap[folded] = true - } else { - nextMap[c] = true - } - } - } - - coords = nextMap - - // return after one fold for part 1? - if part == 1 { - return len(coords) - } - } - - // printing is a pita but necessary for reading part2 - if part == 2 { - // NOTE: as usual my printing is rotated and mirrored because my mental - // mapping of x/y uses rows/cols and ends up different than AOC :/ - // Maybe next year I'll finally change my mental map... or not :) - halp.PrintInfiniteGridBools(coords, "#", ".") - } - - return 0 -} diff --git a/2021/day13/main_test.go b/2021/day13/main_test.go deleted file mode 100644 index 2d9d193..0000000 --- a/2021/day13/main_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "fmt" - "testing" -) - -var example = `6,10 -0,14 -9,10 -0,3 -10,4 -4,11 -6,0 -6,12 -4,1 -0,13 -10,12 -3,4 -3,0 -8,4 -1,10 -2,14 -8,10 -9,0 - -fold along y=7 -fold along x=5` - -func Test_transparentOrigamiDay13(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - { - name: "example", - input: example, - part: 1, - want: 17, - }, - { - name: "actual", - part: 1, - input: input, - want: 731, - }, - } - for _, tt := range tests { - t.Run(fmt.Sprintf("%s part%d", tt.name, tt.part), func(t *testing.T) { - if got := transparentOrigamiDay13(tt.input, tt.part); got != tt.want { - t.Errorf("transparentOrigamiDay13() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day14/main.go b/2021/day14/main.go deleted file mode 100644 index 4bfbb6f..0000000 --- a/2021/day14/main.go +++ /dev/null @@ -1,144 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "math" - "strings" - - "github.com/alexchao26/advent-of-code-go/mathy" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -// naive/brute force solution that does not work for part 2 :( -func part1(input string) int { - template, rules := parseInput(input) - - for step := 0; step < 10; step++ { - sp := strings.Split(template, "") - var next []string - for i := 0; i < len(sp)-1; i++ { - two := sp[i] + sp[i+1] - rule := rules[two] - next = append(next, sp[i]) - next = append(next, rule) - } - next = append(next, sp[len(sp)-1]) - - template = strings.Join(next, "") - } - - // most common element minus least common element - most, least := 0, math.MaxInt32 - count := map[rune]int{} - for _, r := range template { - count[r]++ - } - for _, ct := range count { - if ct > most { - most = ct - } - if ct < least { - least = ct - } - } - - return most - least -} - -func part2(input string) int { - template, rules := parseInput(input) - - initialCount := map[string]int{} - for _, r := range template { - initialCount[string(r)]++ - } - - addlCountAfter40Steps := grow(template, 40, rules, map[string]map[string]int{}) - // have to add the initial template back in because grow only accounts for characters that are - // added in ADDITION to the passed in template - for k, v := range initialCount { - addlCountAfter40Steps[k] += v - } - - // most common element minus least common element - most, least := 0, math.MaxInt64 - for _, v := range addlCountAfter40Steps { - most = mathy.MaxInt(most, v) - least = mathy.MinInt(least, v) - } - return most - least -} - -func grow(template string, stepsLeft int, rules map[string]string, memo map[string]map[string]int) (addlCounts map[string]int) { - addlCounts = map[string]int{} - - if stepsLeft == 0 { - return addlCounts - } - - key := fmt.Sprint(template, stepsLeft) - if res, ok := memo[key]; ok { - return res - } - - for i := 0; i < len(template)-1; i++ { - pair := template[i : i+2] - between := rules[pair] - addlCounts[between]++ - - // get the additional characters for recursing just this three character section - // calling grow will memoize that result to eliminate duplicate work - recurse := grow(pair[:1]+between+pair[1:], stepsLeft-1, rules, memo) - - // merge those additional characters into this (parent function call) addlCounts map - for k, v := range recurse { - addlCounts[k] += v - } - } - - // store it, return it - memo[key] = addlCounts - return addlCounts -} - -func parseInput(input string) (template string, rules map[string]string) { - parts := strings.Split(input, "\n\n") - template = parts[0] - - rules = map[string]string{} - for _, line := range strings.Split(parts[1], "\n") { - sp := strings.Split(line, " -> ") - rules[sp[0]] = sp[1] - } - return template, rules -} diff --git a/2021/day14/main_test.go b/2021/day14/main_test.go deleted file mode 100644 index 5032b2f..0000000 --- a/2021/day14/main_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `NNCB - -CH -> B -HH -> N -CB -> H -NH -> C -HB -> C -HC -> B -HN -> C -NN -> C -BH -> H -NC -> B -NB -> B -BN -> B -BB -> N -BC -> B -CC -> N -CN -> C` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 1588, - }, - { - name: "actual", - input: input, - want: 2851, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 2188189693529, - }, - { - name: "actual", - input: input, - want: 10002813279337, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day15/main.go b/2021/day15/main.go deleted file mode 100644 index 2070c4e..0000000 --- a/2021/day15/main.go +++ /dev/null @@ -1,173 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/data-structures/heap" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - grid := parseInput(input) - _ = grid - - for i, rows := range grid { - for j := range rows { - if i == 0 && j == 0 { - continue - } else if i == 0 { - grid[i][j] += grid[i][j-1] - } else if j == 0 { - grid[i][j] += grid[i-1][j] - } else { - if grid[i-1][j] < grid[i][j-1] { - grid[i][j] += grid[i-1][j] - } else { - grid[i][j] += grid[i][j-1] - } - } - } - } - return grid[len(grid)-1][len(grid[0])-1] - grid[0][0] - -} - -func part2(input string) int { - // make the grid 5 times larger - // add 1 to every cell when moving right OR down (so diagonal is +2) - // 9 wraps back to 1 - grid := parseInput(input) - bigGrid := make([][]int, len(grid)*5) - for i := range bigGrid { - bigGrid[i] = make([]int, len(grid[0])*5) - } - for r, row := range grid { - for c, v := range row { - bigGrid[r][c] = v - } - } - - assignGrid := func(baseGrid [][]int, newGrid [][]int, r, c int) { - for i := 0; i < len(newGrid); i++ { - for j := 0; j < len(newGrid[0]); j++ { - baseGrid[r+i][c+j] = newGrid[i][j] - } - } - } - - incrementGrid := func(baseGrid [][]int, by int) [][]int { - newGrid := make([][]int, len(baseGrid)) - for i := range newGrid { - newGrid[i] = make([]int, len(baseGrid[0])) - } - for i := range baseGrid { - for j := range baseGrid[0] { - newGrid[i][j] = baseGrid[i][j] + by - for newGrid[i][j] > 9 { - newGrid[i][j] -= 9 - } - } - } - return newGrid - } - - for r := 0; r < 5; r++ { - for c := 0; c < 5; c++ { - if r == 0 && c == 0 { - continue - } - nextGrid := incrementGrid(grid, r+c) - assignGrid(bigGrid, nextGrid, r*len(grid), c*len(grid[0])) - } - } - - minHeap := heap.NewMinHeap() - minHeap.Add(node{0, 0, 0}) - visited := map[[2]int]bool{} - for minHeap.Length() > 0 { - front := minHeap.Remove().(node) - coord := [2]int{front.row, front.col} - // moving this check here instead of in the heap.Add() makes a HUGE difference - // by reducing the swell of the queue and following computations - if visited[coord] { - continue - } - visited[[2]int{front.row, front.col}] = true - - if front.row == len(bigGrid)-1 && front.col == len(bigGrid[0])-1 { - return front.risk - } - - // travel to all of front's neighbors - for _, d := range [][2]int{ - {0, 1}, - {1, 0}, - {0, -1}, - {-1, 0}, - } { - nr, nc := front.row+d[0], front.col+d[1] - if nr >= 0 && nr < len(bigGrid) && nc >= 0 && nc < len(bigGrid[0]) { - minHeap.Add(node{ - row: nr, - col: nc, - risk: front.risk + bigGrid[nr][nc], - }) - } - } - } - - panic("should return from loop") -} - -// A* node -type node struct { - row, col int - risk int -} - -func (n node) Value() int { - return n.risk -} - -func parseInput(input string) (ans [][]int) { - for _, line := range strings.Split(input, "\n") { - var row []int - for _, char := range strings.Split(line, "") { - row = append(row, cast.ToInt(char)) - } - ans = append(ans, row) - } - return ans -} diff --git a/2021/day15/main_test.go b/2021/day15/main_test.go deleted file mode 100644 index b8ee311..0000000 --- a/2021/day15/main_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `1163751742 -1381373672 -2136511328 -3694931569 -7463417111 -1319128137 -1359912421 -3125421639 -1293138521 -2311944581` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 40, - }, - { - name: "actual", - input: input, - want: 811, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 315, - }, - { - name: "actual", - input: input, - want: 3012, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day16/main.go b/2021/day16/main.go deleted file mode 100644 index 65c563d..0000000 --- a/2021/day16/main.go +++ /dev/null @@ -1,198 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/mathy" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int64 { - bin := parseInput(input) - - versionTotal, _, _ := handlePacket(bin) - - return versionTotal -} - -// bitsRead is often just called n in Go. See io.Read() for example -func handlePacket(pack string) (versionTotal int64, expressionValue int, bitsRead int) { - version, err := strconv.ParseInt(pack[0:3], 2, 64) - versionTotal += version - if err != nil { - panic("version strconv.ParseInt(): " + err.Error()) - } - typeID, err := strconv.ParseInt(pack[3:6], 2, 64) - if err != nil { - panic("typeID strconv.ParseInt(): " + err.Error()) - } - - read := 6 // version and typeID - - switch typeID { - case 4: // literal value - // parse 5 bits at a time - var bits string - for i := 6; i < len(pack); i += 5 { - fiveBits := pack[i : i+5] - read += 5 - - bits += fiveBits[1:] - - if fiveBits[0] == '0' { - break - } - } - - decimalVal, err := strconv.ParseInt(bits, 2, 64) - if err != nil { - panic("parsing packet bits: " + err.Error()) - } - return version, int(decimalVal), read - default: // operator types? - // contains one or more packets - lengthTypeID := pack[6:7] - read++ - var bitsToRead int - switch lengthTypeID { - case "0": - // next 15 bits == total length in bits for REST of subpackets - bitsToRead = 15 - case "1": - // next 11 bits == NUMBER of subpackets - bitsToRead = 11 - } - - rawLength := pack[7 : 7+bitsToRead] - read += bitsToRead - length, err := strconv.ParseInt(rawLength, 2, 64) - if err != nil { - panic("parsing 0 lengthTypeID: " + err.Error()) - } - - // followed by the subpackets themselves - var subPacketExpressionValues []int - switch lengthTypeID { - case "0": - // next 15 bits == total length in bits for REST of subpackets - for length > 0 { - // continue reading until length of bits are read - ver, expVal, n := handlePacket(pack[read:]) - read += n - length -= int64(n) - subPacketExpressionValues = append(subPacketExpressionValues, expVal) - versionTotal += ver - } - case "1": - // next 11 bits == NUMBER of subpackets - for length > 0 { - // continue reading until number of packets are read - ver, expVal, n := handlePacket(pack[read:]) - read += n - length-- // reduce length by 1 (ie one packet read) - subPacketExpressionValues = append(subPacketExpressionValues, expVal) - versionTotal += ver - } - } - - switch typeID { - // note: case 0 already handled above, literal value - case 0: // sum - return versionTotal, mathy.SumIntSlice(subPacketExpressionValues), read - case 1: // product - return versionTotal, mathy.MultiplyIntSlice(subPacketExpressionValues), read - case 2: // min - return versionTotal, mathy.MinInt(subPacketExpressionValues...), read - case 3: // max - return versionTotal, mathy.MaxInt(subPacketExpressionValues...), read - // 4 is literal... - case 5: // greater than (first subpacket > second, will always have exactly 2) - var ans int - if subPacketExpressionValues[0] > subPacketExpressionValues[1] { - ans = 1 // otherwise int zero val works - } - return versionTotal, ans, read - case 6: // less than (opposite) - var ans int - if subPacketExpressionValues[0] < subPacketExpressionValues[1] { - ans = 1 // otherwise int zero val works - } - return versionTotal, ans, read - case 7: // equal to - var ans int - if subPacketExpressionValues[0] == subPacketExpressionValues[1] { - ans = 1 // otherwise int zero val works - } - return versionTotal, ans, read - default: - panic(fmt.Sprintf("unknown typeID: %d", typeID)) - } - } -} - -func part2(input string) int { - bin := parseInput(input) - - _, expVal, _ := handlePacket(bin) - - return expVal -} - -var hexToBin = map[string]string{ - "0": "0000", - "1": "0001", - "2": "0010", - "3": "0011", - "4": "0100", - "5": "0101", - "6": "0110", - "7": "0111", - "8": "1000", - "9": "1001", - "A": "1010", - "B": "1011", - "C": "1100", - "D": "1101", - "E": "1110", - "F": "1111", -} - -func parseInput(input string) string { - var binarySb strings.Builder - for _, char := range strings.Split(input, "") { - binarySb.WriteString(hexToBin[char]) - } - return binarySb.String() -} diff --git a/2021/day16/main_test.go b/2021/day16/main_test.go deleted file mode 100644 index bfe2422..0000000 --- a/2021/day16/main_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package main - -import ( - _ "embed" - "testing" -) - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int64 - }{ - { - name: "example1", - input: "8A004A801A8002F478", - want: 16, - }, - { - name: "example2", - input: "620080001611562C8802118E34", - want: 12, - }, - { - name: "example3", - input: "C0015000016115A2E0802F182340", - want: 23, - }, - { - name: "example4", - input: "A0016C880162017C3686B18A3D4780", - want: 31, - }, - { - name: "actual", - input: input, - want: 953, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: "C200B40A82", - want: 3, - }, - { - name: "example", - input: "04005AC33890", - want: 54, - }, - { - name: "example", - input: "880086C3E88112", - want: 7, - }, - { - name: "example", - input: "CE00C43D881120", - want: 9, - }, - { - name: "example", - input: "D8005AC2A8F0", - want: 1, - }, - { - name: "example", - input: "F600BC2D8F", - want: 0, - }, - { - name: "example", - input: "9C005AC2F8F0", - want: 0, - }, - { - name: "example", - input: "9C0141080250320F1802104A08", - want: 1, - }, - - { - name: "actual", - input: input, - want: 246225449979, - }, - } - 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) - } - }) - } -} - -// func Test_handlePacket(t *testing.T) { -// tests := []struct { -// name string -// pack string -// want int -// }{ -// { -// pack: "110100101111111000101000", -// want: 2021, -// }, -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// if got := handlePacket(tt.pack); got != tt.want { -// t.Errorf("handlePacket() = %v, want %v", got, tt.want) -// } -// }) -// } -// } diff --git a/2021/day17/main.go b/2021/day17/main.go deleted file mode 100644 index 96fc2f2..0000000 --- a/2021/day17/main.go +++ /dev/null @@ -1,156 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/mathy" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := trickShot(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -// x = forward -// y = up/down - -// starts at 0,0 - -// must be within target area (input) EVENTUALLY (aka on some step) - -// find MAX Y position that can be reached for a valid initial velocity? - -// ACTUAL: target area: x=88..125, y=-157..-103 -// EXAMPLE: target area: x=20..30, y=-10..-5 - -func trickShot(input string, part int) int { - // dummy state just to get some reasonable bounds for starting x velocities - dummyState := newState(input, -1, -1) - // hypothesis: the starting x velocity will be positive, (likely) under xmax+1 because then it - // will pass the entire box within a single "step" - // search bounds... (binary?) <- ended up being a trap - leftXVel, rightXVel := 0, dummyState.xmax+1 - - var highestY int // part 1 - var totalValidStartingVelocities int // part 2 - - for xVel := leftXVel; xVel <= rightXVel; xVel++ { - // brute force the starting y velocities :/ - for yVel := -1000; yVel <= 1000; yVel++ { - st := newState(input, xVel, yVel) - maybeHigherY, inBox := st.run() - if inBox { - highestY = mathy.MaxInt(highestY, maybeHigherY) - - // part2 - totalValidStartingVelocities++ - } - } - } - - if part == 1 { - return highestY - } - - return totalValidStartingVelocities -} - -type state struct { - xmin, xmax, ymin, ymax int - x, y int - xvel, yvel int - highestY int -} - -func newState(input string, startingXVel, startingYVel int) *state { - //target area: x=88..125, y=-157..-103 - var xmin, xmax, ymin, ymax int - n, err := fmt.Sscanf(input, "target area: x=%d..%d, y=%d..%d", &xmin, &xmax, &ymin, &ymax) - if n != 4 || err != nil { - panic(fmt.Sprintf("%d read, want 4; error? %s", n, err)) - } - - return &state{ - xmin: xmin, - xmax: xmax, - ymin: ymin, - ymax: ymax, - xvel: startingXVel, - yvel: startingYVel, - // zero values handle the rest - } -} - -func (s *state) step() (reached, done bool) { - // The probe's x position increases by its x velocity. - s.x += s.xvel - // The probe's y position increases by its y velocity. - s.y += s.yvel - - s.highestY = mathy.MaxInt(s.highestY, s.y) - - // the probe's x velocity changes by 1 toward the value 0, stays zero if 0 - if s.xvel > 0 { - s.xvel-- - } else if s.xvel < 0 { - s.xvel++ - } - - // the probe's y velocity decreases by 1. - s.yvel-- - - // check if within bounds of (x|y)(min|max) - if s.x >= s.xmin && s.x <= s.xmax && s.y >= s.ymin && s.y <= s.ymax { - return true, true - } - - // done overshot to the right - if s.x > s.xmax { - return false, true - } - - // done: undershot to the left and no velocity to get more right - if s.xvel == 0 && s.x < s.xmin { - return false, true - } - - // done: y is getting lower and lower and will never recover - if s.y < s.ymin { - return false, true - } - - // indicate not reached but also not done, so call again - return false, false -} - -func (s *state) run() (maxY int, inBox bool) { - var reached, done bool - for !done { - reached, done = s.step() - if reached { - return s.highestY, true - } - } - return -1, false -} diff --git a/2021/day17/main_test.go b/2021/day17/main_test.go deleted file mode 100644 index 8a8d9ec..0000000 --- a/2021/day17/main_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - _ "embed" - "testing" -) - -var example = `target area: x=20..30, y=-10..-5` - -func Test_trickShot(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - { - name: "example", - input: example, - part: 1, - want: 45, - }, - { - name: "actual", - input: input, - part: 1, - want: 12246, - }, - { - name: "part2 example", - input: example, - part: 2, - want: 112, - }, - { - name: "actual", - input: input, - part: 2, - want: 3528, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := trickShot(tt.input, tt.part); got != tt.want { - t.Errorf("trickShot() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day18/main.go b/2021/day18/main.go deleted file mode 100644 index fbadd05..0000000 --- a/2021/day18/main.go +++ /dev/null @@ -1,321 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - nums := parseInput(input) - - base := nums[0] - for i := 1; i < len(nums); i++ { - base = addNodes(base, nums[i]) - } - - return base.magnitude() -} - -func part2(input string) int { - nums := parseInput(input) - - var best int - for i, n1 := range nums { - for j, n2 := range nums { - if i == j { - continue - } - // these operations are destructive to the lists so make copies every time - cp1, cp2 := copyList(n1), copyList(n2) - - n1PlusN2 := addNodes(cp1, cp2) - mag := n1PlusN2.magnitude() - if mag > best { - best = mag - } - cp1, cp2 = copyList(n1), copyList(n2) - - n2PlusN1 := addNodes(cp2, cp1) - mag = n2PlusN1.magnitude() - if mag > best { - best = mag - } - } - } - - return best -} - -// doubly linked list node -type node struct { - val int - prev, next *node - depth int -} - -func addNodes(n1, n2 *node) *node { - // increase the depth on all nodes in n1 and n2 bc they will form a pair - n := n1 - for n != nil { - n.depth++ - n = n.next - } - n = n2 - for n != nil { - n.depth++ - n = n.next - } - - // point last of n1 to first of n2 AND in reverse - lastNode1 := n1 - for lastNode1.next != nil { - lastNode1 = lastNode1.next - } - lastNode1.next = n2 - n2.prev = lastNode1 // reverse - - n1 = n1.reduce() - return n1 -} - -func copyList(n *node) *node { - var head, last *node - - for p := n; p != nil; p = p.next { - cp := &node{ - val: p.val, - prev: last, - next: nil, - depth: p.depth, - } - if head == nil { - head = cp - last = cp - } else { - last.next = cp - last = cp - } - } - - return head -} - -func (n *node) reduce() (head *node) { - for pointer := n; pointer != nil; pointer = pointer.next { - // nested inside 4 pairs means depth is 5 or more - if pointer.depth >= 5 { - // should explode - - pairRight := pointer.next // pointer to right node of pair - if pairRight.depth != pointer.depth { - panic(fmt.Sprintf("exploding pair should have same depth, got %d and %d", pointer.depth, pairRight.depth)) - } - - // pair will become a node with zero value - replacement := &node{ - val: 0, - depth: pointer.depth - 1, - prev: pointer.prev, // may be nil - next: pairRight.next, // may be nil - } - - if pointer.prev != nil { - pointer.prev.val += pointer.val - // reassignments to remove old pair - pointer.prev.next = replacement - } - if pairRight.next != nil { - pairRight.next.val += pairRight.val - // reassignments to remove old pair - pairRight.next.prev = replacement - } - - // recursively call reduce on n again and RETURN to stop further reductions (reset logic) - - // edge case for head is exploding - if n == pointer { - return replacement.reduce() - } - - return n.reduce() - } - } - - for pointer := n; pointer != nil; pointer = pointer.next { - if pointer.val >= 10 { - // should split - - replacementLeft := &node{ - val: pointer.val / 2, // integer division will round down - prev: pointer.prev, - next: nil, // will be replacementRight - depth: pointer.depth + 1, - } - replacementRight := &node{ - val: pointer.val / 2, // need to round up - prev: replacementLeft, - next: pointer.next, - depth: pointer.depth + 1, - } - - // adjustments to inits - replacementLeft.next = replacementRight - if pointer.val%2 == 1 { - replacementRight.val++ - } - - toLeft, toRight := pointer.prev, pointer.next - if toLeft != nil { - toLeft.next = replacementLeft - } - if toRight != nil { - toRight.prev = replacementRight - } - - // recursively call reduce on n again and RETURN to stop further reductions (reset logic) - - // edge case for head is splitting - if n == pointer { - return replacementLeft.reduce() - } - - return n.reduce() - } - } - - // just return self if none of the reduce actions occurred - return n -} - -func (n *node) String() string { - var sb strings.Builder - - for p := n; p != nil; p = p.next { - sb.WriteString(fmt.Sprintf("v: %d, depth: %d -> ", p.val, p.depth)) - } - - // this would be prettier, but my brain hurts - // for p := n; p != nil; p = p.next { - // if p == n { - // sb.WriteString(strings.Repeat("[", p.depth)) - // sb.WriteString(cast.ToString(p.val)) - // sb.WriteString(",") - // } else { - // if p.depth == p.prev.depth { - // // right of a pair - // sb.WriteString(cast.ToString(p.val)) - // sb.WriteString("]") - // } else { - - // } - - // if p.next == nil { - // sb.WriteString(strings.Repeat("]", p.prev.depth - p.depth)) - // } - // } - // } - - return sb.String() -} -func (n *node) magnitude() int { - // recursive calculation, 3*left + 2*right - // regular numbers are just their number so [2,5] -> 3*2 + 2*5 = 16 - - // make copy because calculation works by collapsing the list's pairs - cp := copyList(n) - for depth := 4; depth > 0; depth-- { - - for p := cp; p != nil; p = p.next { - if p.depth == depth && p.next != nil && p.next.depth == depth { - left, right := p, p.next - newNode := node{ - val: 3*left.val + 2*right.val, - prev: left.prev, - next: right.next, - depth: depth - 1, - } - if left == cp { - cp = &newNode - } else { - left.prev.next = &newNode - } - if right.next != nil { - right.next.prev = &newNode - } - } - } - } - - return cp.val -} - -func parseInput(input string) []*node { - var snailfishNums []*node - for _, line := range strings.Split(input, "\n") { - var depth int - - var pointer, head *node - - for _, r := range line { - switch r { - case '[': - depth++ - case ']': - depth-- - case ',': // do nothing - - default: // all single digit numbers - newNode := node{ - val: cast.ToInt(string(r)), - prev: pointer, - next: nil, - depth: depth, - } - // assign head and pointer if none already - if pointer == nil { - head = &newNode - pointer = &newNode - } else { - // otherwise assign pointer's next to new node, reassign pointer to new node - pointer.next = &newNode - pointer = &newNode - } - } - } - snailfishNums = append(snailfishNums, head) - } - return snailfishNums -} diff --git a/2021/day18/main_test.go b/2021/day18/main_test.go deleted file mode 100644 index 17bb98f..0000000 --- a/2021/day18/main_test.go +++ /dev/null @@ -1,197 +0,0 @@ -package main - -import ( - _ "embed" - "testing" -) - -var shortExample = `[1,1] -[2,2] -[3,3] -[4,4] -[5,5]` - -/* -[[[[[1,1],[2,2]],[3,3]],[4,4]],[5,5]] -[[[[ 0, [3,2]],[3,3]],[4,4]],[5,5]] -[[[[ 3, 0 ], [5,3]],[4,4]],[5,5]] - -// reduces to [[[[3,0],[5,3]],[4,4]],[5,5]] - -*/ - -var bigExample = `[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]] -[[[5,[2,8]],4],[5,[[9,9],0]]] -[6,[[[6,2],[5,6]],[[7,6],[4,7]]]] -[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]] -[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]] -[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]] -[[[[5,4],[7,7]],8],[[8,3],8]] -[[9,3],[[9,9],[6,[4,9]]]] -[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]] -[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]` - -var example1 = `[[[0,[4,5]],[0,0]],[[[4,5],[2,6]],[9,5]]] -[7,[[[3,7],[4,3]],[[6,3],[8,8]]]] -[[2,[[0,8],[3,4]]],[[[6,7],1],[7,[1,6]]]] -[[[[2,4],7],[6,[0,5]]],[[[6,8],[2,8]],[[2,1],[4,5]]]] -[7,[5,[[3,8],[1,4]]]] -[[2,[2,2]],[8,[8,1]]] -[2,9] -[1,[[[9,3],9],[[9,0],[0,7]]]] -[[[5,[7,4]],7],1] -[[[[4,2],2],6],[8,7]]` - -/* - -[[[[7,7],[7,7]],[[8,7],[8,7]]],[[[7,0],[7,7]],9]] -+ [[[[4,2],2],6],[8,7]] -[[[[[7,7],[7,7]],[[8,7],[8,7]]],[[[7,0],[7,7]],9]],[[[[4,2],2],6],[8,7]]] -[[[[0,[14,7]],[[8,7],[8,7]]],[[[7,0],[7,7]],9]],[[[[4,2],2],6],[8,7]]] -[[[[14,0],[[15,7],[8,7]]],[[[7,0],[7,7]],9]],[[[[4,2],2],6],[8,7]]] -[[[[14,15],[0,[15,7]]],[[[7,0],[7,7]],9]],[[[[4,2],2],6],[8,7]]] -[[[[14,15],[15,0]],[[[14,0],[7,7]],9]],[[[[4,2],2],6],[8,7]]] -[[[[14,15],[15,14]],[[0,[7,7]],9]],[[[[4,2],2],6],[8,7]]] -[[ [[14,15],[15,14]] , [[7,0],16]],[[[[4,2],2],6],[8,7]]] - ^ this 16 was not getting added to - indicating that the prev pointers were broken - the bug was in the addNodes function to set n2.prev to lastN1 😭 -[[ [[14,15],[15,14]] , [[7,0],20] ],[[[0,4],6],[8,7]]] - - -*/ - -var splitExample = `[[[[4,3],4],4],[7,[[8,4],9]]] -[1,1]` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: "[[1,2],[[3,4],5]]", - want: 143, - }, - { - name: "short", - input: shortExample, - // reduces to [[[[3,0],[5,3]],[4,4]],[5,5]] - want: 791, - }, - { - name: "short plus 6s", - input: shortExample + "\n[6,6]", - // reduces to [[[[5,0],[7,4]],[5,5]],[6,6]] - want: 1137, - }, - { - name: "split example", - input: splitExample, - // reduces to [[[[0,7],4],[[7,8],[6,0]]],[8,1]] - want: 1384, - }, - - { - name: "example1", - input: example1, - want: 3488, - }, - - { - name: "bigExample", - input: bigExample, - want: 4140, - }, - - { - name: "actual", - input: input, - want: 4202, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: bigExample, - want: 3993, - }, - { - name: "actual", - input: input, - want: 4779, - }, - } - 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) - } - }) - } -} - -func Test_node_reduce(t *testing.T) { - tests := []struct { - name string - input string - wantString string - }{ - { - name: "simple", - input: "[[[[[9,8],1],2],3],4]", - wantString: "v: 0, depth: 4 -> v: 9, depth: 4 -> v: 2, depth: 3 -> v: 3, depth: 2 -> v: 4, depth: 1 -> ", - // wantString: "[[[[0,9],2],3],4]", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - nums := parseInput(tt.input) - head := nums[0] - // fmt.Println(head) - gotHead := head.reduce() - if gotHead.String() != tt.wantString { - t.Errorf("node.reduce() = %q, want %q", gotHead.String(), tt.wantString) - } - }) - } -} - -func Test_node_magnitude(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - {input: `[[1,2],[[3,4],5]]`, want: 143}, - {input: `[[[[0,7],4],[[7,8],[6,0]]],[8,1]]`, want: 1384}, - {input: `[[[[1,1],[2,2]],[3,3]],[4,4]]`, want: 445}, - {input: `[[[[3,0],[5,3]],[4,4]],[5,5]]`, want: 791}, - {input: `[[[[5,0],[7,4]],[5,5]],[6,6]]`, want: 1137}, - {input: `[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]`, want: 3488}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - n := parseInput(tt.input)[0] - if got := n.magnitude(); got != tt.want { - t.Errorf("node.magnitude() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day19/main.go b/2021/day19/main.go deleted file mode 100644 index 9186338..0000000 --- a/2021/day19/main.go +++ /dev/null @@ -1,332 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/mathy" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans1, ans2 := part1(input) - if part == 1 { - util.CopyToClipboard(fmt.Sprintf("%v", ans1)) - fmt.Println("Output:", ans1) - } else { - util.CopyToClipboard(fmt.Sprintf("%v", ans2)) - fmt.Println("Output:", ans2) - } -} - -func part1(input string) (part1, part2 int) { - scanners := parseInput(input) - - // determine scanner locations by finding scanners that see 12 of the same beacons - // everything will be relative from scanner 0, so it is "settled" and its abs & rel coords are the same - settled := []scanner{scanners[0]} - settled[0].absoluteCoords = settled[0].relativeCoords - settled[0].fillAbsoluteCoordsMap() - - // create helper functions that can create all the rotated versions of the seen beacons - // scanner 0 will have KNOWN coordinates (0,0,0) - // maintain a list of all UNKNOWN scanners (all but 0 at the start) - undetermined := scanners[1:] - // iterate while it has a non zero length - for len(undetermined) > 0 { - fmt.Printf("progress: %d/%d\n", len(settled), len(scanners)) - - for i, undet := range undetermined { - maybeUpdated, ok := findAbsoluteCoordsForScanner(undet, settled) - if ok { - settled = append(settled, maybeUpdated) - // remove the determined scanner from undetermined list - copy(undetermined[i:], undetermined[i+1:]) - // undetermined[i] = undetermined[len(undetermined)-1] - undetermined = undetermined[:len(undetermined)-1] - // restart checks from start of undetermined list - break - } - } - } - - allBeacons := map[[3]int]bool{} - for _, s := range settled { - for c := range s.absoluteCoordsMap { - allBeacons[c] = true - } - } - - var furthest int - for i, s1 := range settled { - for j, s2 := range settled { - if i == j { - continue - } - manhattanDist := mathy.AbsInt(s1.x-s2.x) + mathy.AbsInt(s1.y-s2.y) + mathy.AbsInt(s1.z-s2.z) - if manhattanDist > furthest { - furthest = manhattanDist - } - } - } - return len(allBeacons), furthest -} - -type scanner struct { - number int - x, y, z int - relativeCoords [][3]int - rotations [][][3]int - absoluteCoords [][3]int - absoluteCoordsMap map[[3]int]bool -} - -func (s *scanner) fillAbsoluteCoordsMap() { - s.absoluteCoordsMap = map[[3]int]bool{} - if len(s.absoluteCoords) == 0 { - panic(fmt.Sprintf("absolute coords not set for scanner %d", s.number)) - } - for _, ac := range s.absoluteCoords { - s.absoluteCoordsMap[ac] = true - } -} - -// create the 24 rotations given a slice of coords (3-length arrays) -func (s *scanner) fillRotations() { - // facing negative x - posX := s.relativeCoords - var dir2, dir3, dir4, dir5, dir6 [][3]int - for _, c := range posX { - x, y, z := c[0], c[1], c[2] - dir2 = append(dir2, [3]int{x, -y, -z}) - dir3 = append(dir3, [3]int{x, -z, y}) - dir4 = append(dir4, [3]int{-y, -z, x}) - dir5 = append(dir5, [3]int{-x, -z, -y}) - dir6 = append(dir6, [3]int{y, -z, -x}) - } - sixRotations := [][][3]int{ - posX, dir2, - dir3, dir4, - dir5, dir6, - } - - // apply 4 rotations around the axis that the scanner is "staring down" - var finalRotations [][][3]int - for _, rotation := range sixRotations { - var r2, r3, r4 [][3]int // r1 is rotation itself - for _, c := range rotation { - x, y, z := c[0], c[1], c[2] - r2 = append(r2, [3]int{-y, x, z}) - r3 = append(r3, [3]int{-x, -y, z}) - r4 = append(r4, [3]int{y, -x, z}) - } - finalRotations = append(finalRotations, rotation, r2, r3, r4) - } - s.rotations = finalRotations -} - -func findAbsoluteCoordsForScanner(undet scanner, settled []scanner) (maybeUpdated scanner, didUpdate bool) { - // for all orientations of the unknown's beacon list - for _, rotatedCoords := range undet.rotations { - // for each beacon in known list - for _, set := range settled { - for _, absCoord := range set.absoluteCoords { - // for each beacon in unknown list - for _, relativeCoord := range rotatedCoords { - // assume the known and unknown beacon are the same, calculate the absolute coords of the unknown's scanner coords - // convert all of unknown list to their absolute coords, check against known list - unsettledAbsoluteCoords := makeAbsoluteCoordsList(absCoord, relativeCoord, rotatedCoords) - - var matchingCount int - // var matched [][3]int // ! - for _, ac := range unsettledAbsoluteCoords { - if set.absoluteCoordsMap[ac] { - // matched = append(matched, ac) // ! - matchingCount++ - } - } - - // if true return a true or something, modify the scanner param pointer - if matchingCount >= 12 { - undet.relativeCoords = rotatedCoords - undet.absoluteCoords = unsettledAbsoluteCoords - undet.fillAbsoluteCoordsMap() - undet.x = absCoord[0] - relativeCoord[0] - undet.y = absCoord[1] - relativeCoord[1] - undet.z = absCoord[2] - relativeCoord[2] - return undet, true - } - } - } - } - } - - // did not update - return undet, false -} - -func makeAbsoluteCoordsList(absolute, relative [3]int, relativeCoords [][3]int) [][3]int { - // assuming absolute and relative are pointing to the same coord - // generate the all of the abolute coords - - // diff to the scanner's coordinates, then calculate all beacons from scanner's coords - diff := [3]int{ - absolute[0] - relative[0], - absolute[1] - relative[1], - absolute[2] - relative[2], - } - - var absCoords [][3]int - for _, c := range relativeCoords { - absCoords = append(absCoords, [3]int{ - diff[0] + c[0], - diff[1] + c[1], - diff[2] + c[2], - }) - } - - return absCoords -} - -func parseInput(input string) (ans []scanner) { - for _, rawScanner := range strings.Split(input, "\n\n") { - var number int - lines := strings.Split(rawScanner, "\n") - _, err := fmt.Sscanf(lines[0], "--- scanner %d ---", &number) - if err != nil { - panic("parsing error " + err.Error()) - } - - var coords [][3]int - for _, line := range lines[1:] { - var x, y, z int - _, err := fmt.Sscanf(line, "%d,%d,%d", &x, &y, &z) - if err != nil { - panic("parsing error " + err.Error()) - } - coords = append(coords, [3]int{x, y, z}) - } - - sc := scanner{ - number: number, - x: 0, - y: 0, - z: 0, - relativeCoords: coords, - absoluteCoords: nil, - absoluteCoordsMap: map[[3]int]bool{}, - } - sc.fillRotations() - ans = append(ans, sc) - } - - return ans -} - -// var exampleBeaconsList [][3]int = func() [][3]int { -// var coords [][3]int -// for _, l := range strings.Split(rawBeaconsList, "\n") { -// var x, y, z int -// fmt.Sscanf(l, "%d,%d,%d", &x, &y, &z) -// coords = append(coords, [3]int{x, y, z}) -// } -// return coords -// }() - -// var rawBeaconsList = `-892,524,684 -// -876,649,763 -// -838,591,734 -// -789,900,-551 -// -739,-1745,668 -// -706,-3180,-659 -// -697,-3072,-689 -// -689,845,-530 -// -687,-1600,576 -// -661,-816,-575 -// -654,-3158,-753 -// -635,-1737,486 -// -631,-672,1502 -// -624,-1620,1868 -// -620,-3212,371 -// -618,-824,-621 -// -612,-1695,1788 -// -601,-1648,-643 -// -584,868,-557 -// -537,-823,-458 -// -532,-1715,1894 -// -518,-1681,-600 -// -499,-1607,-770 -// -485,-357,347 -// -470,-3283,303 -// -456,-621,1527 -// -447,-329,318 -// -430,-3130,366 -// -413,-627,1469 -// -345,-311,381 -// -36,-1284,1171 -// -27,-1108,-65 -// 7,-33,-71 -// 12,-2351,-103 -// 26,-1119,1091 -// 346,-2985,342 -// 366,-3059,397 -// 377,-2827,367 -// 390,-675,-793 -// 396,-1931,-563 -// 404,-588,-901 -// 408,-1815,803 -// 423,-701,434 -// 432,-2009,850 -// 443,580,662 -// 455,729,728 -// 456,-540,1869 -// 459,-707,401 -// 465,-695,1988 -// 474,580,667 -// 496,-1584,1900 -// 497,-1838,-617 -// 527,-524,1933 -// 528,-643,409 -// 534,-1912,768 -// 544,-627,-890 -// 553,345,-567 -// 564,392,-477 -// 568,-2007,-577 -// 605,-1665,1952 -// 612,-1593,1893 -// 630,319,-379 -// 686,-3108,-505 -// 776,-3184,-501 -// 846,-3110,-434 -// 1135,-1161,1235 -// 1243,-1093,1063 -// 1660,-552,429 -// 1693,-557,386 -// 1735,-437,1738 -// 1749,-1800,1813 -// 1772,-405,1572 -// 1776,-675,371 -// 1779,-442,1789 -// 1780,-1548,337 -// 1786,-1538,337 -// 1847,-1591,415 -// 1889,-1729,1762 -// 1994,-1805,1792` diff --git a/2021/day19/main_test.go b/2021/day19/main_test.go deleted file mode 100644 index 80b5cc8..0000000 --- a/2021/day19/main_test.go +++ /dev/null @@ -1,282 +0,0 @@ -package main - -import ( - _ "embed" - "testing" -) - -// 0 and 1 overlap -// scanner 1 must be at 68,-1246,-43 (relative to scanner 0) -// 1 and 4 overlap, scanner 4 is at -20,-1133,1061 (relative to scanner 0) -// scanner 2 must be at 1105,-1205,1229 (relative to scanner 0) and scanner 3 must be at -92,-2380,-20 (relative to scanner 0 -var example = `--- scanner 0 --- -404,-588,-901 -528,-643,409 --838,591,734 -390,-675,-793 --537,-823,-458 --485,-357,347 --345,-311,381 --661,-816,-575 --876,649,763 --618,-824,-621 -553,345,-567 -474,580,667 --447,-329,318 --584,868,-557 -544,-627,-890 -564,392,-477 -455,729,728 --892,524,684 --689,845,-530 -423,-701,434 -7,-33,-71 -630,319,-379 -443,580,662 --789,900,-551 -459,-707,401 - ---- scanner 1 --- -686,422,578 -605,423,415 -515,917,-361 --336,658,858 -95,138,22 --476,619,847 --340,-569,-846 -567,-361,727 --460,603,-452 -669,-402,600 -729,430,532 --500,-761,534 --322,571,750 --466,-666,-811 --429,-592,574 --355,545,-477 -703,-491,-529 --328,-685,520 -413,935,-424 --391,539,-444 -586,-435,557 --364,-763,-893 -807,-499,-711 -755,-354,-619 -553,889,-390 - ---- scanner 2 --- -649,640,665 -682,-795,504 --784,533,-524 --644,584,-595 --588,-843,648 --30,6,44 --674,560,763 -500,723,-460 -609,671,-379 --555,-800,653 --675,-892,-343 -697,-426,-610 -578,704,681 -493,664,-388 --671,-858,530 --667,343,800 -571,-461,-707 --138,-166,112 --889,563,-600 -646,-828,498 -640,759,510 --630,509,768 --681,-892,-333 -673,-379,-804 --742,-814,-386 -577,-820,562 - ---- scanner 3 --- --589,542,597 -605,-692,669 --500,565,-823 --660,373,557 --458,-679,-417 --488,449,543 --626,468,-788 -338,-750,-386 -528,-832,-391 -562,-778,733 --938,-730,414 -543,643,-506 --524,371,-870 -407,773,750 --104,29,83 -378,-903,-323 --778,-728,485 -426,699,580 --438,-605,-362 --469,-447,-387 -509,732,623 -647,635,-688 --868,-804,481 -614,-800,639 -595,780,-596 - ---- scanner 4 --- -727,592,562 --293,-554,779 -441,611,-461 --714,465,-776 --743,427,-804 --660,-479,-426 -832,-632,460 -927,-485,-438 -408,393,-506 -466,436,-512 -110,16,151 --258,-428,682 --393,719,612 --211,-452,876 -808,-476,-593 --575,615,604 --485,667,467 --680,325,-822 --627,-443,-432 -872,-547,-609 -833,512,582 -807,604,487 -839,-516,451 -891,-625,532 --652,-548,-490 -30,-46,-14` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want1 int - want2 int - isLong bool - }{ - { - name: "example", - input: example, - want1: 79, // total number of beacons - want2: 3621, // manhattan distance - }, - { - name: "actual", - input: input, - want1: 408, - want2: 13348, - isLong: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - /* - // could use testing.Short() and run via go test -v -short - if testing.Short() { - t.Skip("reason") - } - */ - if testing.Short() { - t.Skip("Skipping long test, 2021/day19, O(n^ALOT)") - } else if tt.isLong { - t.Log("WARNING: Running very slow tests for 2021/day19. Disable with -short testing flag") - } - got1, got2 := part1(tt.input) - if got1 != tt.want1 { - t.Errorf("part1() = %v, want %v", got1, tt.want1) - } - if got1 != tt.want1 { - t.Errorf("part2() = %v, want %v", got2, tt.want2) - } - }) - } -} - -var allTheSameScanner = `--- scanner 0 --- --1,-1,1 --2,-2,2 --3,-3,3 --2,-3,1 -5,6,-4 -8,0,7 - ---- scanner 0 --- -1,-1,1 -2,-2,2 -3,-3,3 -2,-1,3 --5,4,-6 --8,-7,0 - ---- scanner 0 --- --1,-1,-1 --2,-2,-2 --3,-3,-3 --1,-3,-2 -4,6,5 --7,0,8 - ---- scanner 0 --- -1,1,-1 -2,2,-2 -3,3,-3 -1,3,-2 --4,-6,5 -7,0,8 - ---- scanner 0 --- -1,1,1 -2,2,2 -3,3,3 -3,1,2 --6,-4,-5 -0,7,-8` - -func Test_fillRotations(t *testing.T) { - // using a table test but there is only one case (a given example) - tests := []struct { - name string - input string - }{ - {input: allTheSameScanner}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - scanners := parseInput(tt.input) - firstScanner := scanners[0] - // create rotations of the first scanner - firstScanner.fillRotations() - - // faster lookup for checking if all the original scanners are in the created rotations - rotationMaps := []map[[3]int]bool{} - for _, r := range firstScanner.rotations { - m := map[[3]int]bool{} - for _, c := range r { - m[c] = true - } - rotationMaps = append(rotationMaps, m) - } - - for i, scanner := range scanners { - oneMapMatches := false - for _, m := range rotationMaps { - if !oneMapMatches { - allFound := true - for _, c := range scanner.relativeCoords { - if !m[c] { - allFound = false - } - } - if allFound { - oneMapMatches = true - } - } - } - - if !oneMapMatches { - t.Errorf("scanner with index %d is not within createRotations() result", i) - } - } - }) - } -} diff --git a/2021/day20/main.go b/2021/day20/main.go deleted file mode 100644 index 9cf748a..0000000 --- a/2021/day20/main.go +++ /dev/null @@ -1,158 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/mathy" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := trenchMap(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func trenchMap(input string, part int) int { - alg, img := parseInput(input) - - // determine what the infinite space will do, if alg's zero index is "." then it will STAY "." - infiniteSpaceStaysOff := alg[0] == "." - - totalSteps := 2 - if part == 2 { - totalSteps = 50 - } - for steps := 0; steps < totalSteps; steps++ { - // initial state is off, so index 0 -> false - infiniteSpaceIsOn := steps%2 == 1 - img = enhanceImg(img, alg, infiniteSpaceStaysOff, infiniteSpaceIsOn) - } - - var count int - for _, pix := range img { - if pix == "#" { - count++ - } - } - return count -} - -func enhanceImg(img map[[2]int]string, alg []string, infiniteSpaceStaysOff, infiniteSpaceIsOn bool) map[[2]int]string { - // get bounds - var firstRow, lastRow, firstCol, lastCol int - for coord := range img { - firstRow = mathy.MinInt(firstRow, coord[0]) - lastRow = mathy.MaxInt(lastRow, coord[0]) - firstCol = mathy.MinInt(firstCol, coord[1]) - lastCol = mathy.MaxInt(lastCol, coord[1]) - } - - // just extend the entire img by 3 spaces around all borders (up, down, left, right) - // choose which character to extend with based on if the infinite space stays off AND this is a - // where it would be toggled on - infChar := "." - if !infiniteSpaceStaysOff && infiniteSpaceIsOn { - infChar = "#" - } - // fmt.Println("inf Char", infChar) - - for c := firstCol - 3; c <= lastCol+3; c++ { - img[[2]int{firstRow - 3, c}] = infChar - img[[2]int{firstRow - 2, c}] = infChar - img[[2]int{firstRow - 1, c}] = infChar - img[[2]int{lastRow + 1, c}] = infChar - img[[2]int{lastRow + 2, c}] = infChar - img[[2]int{lastRow + 3, c}] = infChar - } - for r := firstRow - 3; r <= lastRow+3; r++ { - img[[2]int{r, firstCol - 3}] = infChar - img[[2]int{r, firstCol - 2}] = infChar - img[[2]int{r, firstCol - 1}] = infChar - img[[2]int{r, lastCol + 1}] = infChar - img[[2]int{r, lastCol + 2}] = infChar - img[[2]int{r, lastCol + 3}] = infChar - } - - // fmt.Println("BEFORE") - // debugging helper to print an infinite grid for - // halp.PrintInfiniteGridStrings(img, ".") - - // now only need to check within firstRow - 2 through lastRow + 2 (same for cols) - // because flickers will kill that third row/col out - next := map[[2]int]string{} - for r := firstRow - 2; r <= lastRow+2; r++ { - for c := firstCol - 2; c <= lastCol+2; c++ { - ind := getAlgIndex(img, r, c) - char := alg[ind] - next[[2]int{r, c}] = char - } - } - - // fmt.Println("AFTER") - // halp.PrintInfiniteGridStrings(next, ".") - - return next -} - -func getAlgIndex(img map[[2]int]string, r, c int) int { - var rawBinary string - for _, d := range [][2]int{ - {-1, -1}, - {-1, 0}, - {-1, 1}, - {0, -1}, - {0, 0}, - {0, 1}, - {1, -1}, - {1, 0}, - {1, 1}, - } { - coord := [2]int{r + d[0], c + d[1]} - if img[coord] == "#" { - rawBinary += "1" - } else { - // else also captures indexes that are in the infinite space that are not in img map yet - rawBinary += "0" - } - } - dec, err := strconv.ParseInt(rawBinary, 2, 64) - if err != nil { - panic("parsing rawBinary " + err.Error()) - } - return int(dec) -} - -func parseInput(input string) (alg []string, img map[[2]int]string) { - parts := strings.Split(input, "\n\n") - alg = strings.Split(parts[0], "") - - img = map[[2]int]string{} - for r, line := range strings.Split(parts[1], "\n") { - for c, char := range strings.Split(line, "") { - img[[2]int{r, c}] = char - } - } - - return alg, img -} diff --git a/2021/day20/main_test.go b/2021/day20/main_test.go deleted file mode 100644 index f68e81d..0000000 --- a/2021/day20/main_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - _ "embed" - "fmt" - "testing" -) - -var example = `..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..###..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#..#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.....####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#.......##..####..#...#.#.#...##..#.#..###..#####........#..####......#..# - -#..#. -#.... -##..# -..#.. -..###` - -func Test_trenchMap(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - { - name: "example", - input: example, - part: 1, - want: 35, - }, - { - name: "actual", - input: input, - part: 1, - want: 4917, - }, - { - name: "example", - input: example, - part: 2, - want: 3351, - }, - { - name: "actual", - input: input, - part: 2, - want: 16389, - }, - } - for _, tt := range tests { - t.Run(fmt.Sprint(tt.name, tt.part), func(t *testing.T) { - if got := trenchMap(tt.input, tt.part); got != tt.want { - t.Errorf("trenchMap() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_getAlgIndex(t *testing.T) { - type args struct { - img map[[2]int]string - r int - c int - } - tests := []struct { - name string - args args - want int - }{ - { - name: "around middle of example", - args: args{ - img: func() map[[2]int]string { - _, img := parseInput(example) - return img - }(), - r: 2, - c: 2, - }, - want: 34, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := getAlgIndex(tt.args.img, tt.args.r, tt.args.c); got != tt.want { - t.Errorf("getAlgIndex() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day21/main.go b/2021/day21/main.go deleted file mode 100644 index 9bddbf3..0000000 --- a/2021/day21/main.go +++ /dev/null @@ -1,160 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - positions := parseInput(input) - - // die, two pawns, circular track w/ 10 spaces (1-10) - - // players take turns - // roll die 3 times, take sum - // move forward that number of spaces - // increase score by space they stop on - // game ends when someone has 1000 points - - // deterministic die, 100 sided, rolls 1, then 2, 3, 4, 5, 6, etc - die := 0 // starts at zero, first time it rolls will produce a 1 - isPlayer1sTurn := true - var scores [2]int - var rolls int - for scores[0] < 1000 && scores[1] < 1000 { - var threeRolls int - for i := 0; i < 3; i++ { - rolls++ - die++ - if die == 101 { - die = 1 - } - threeRolls += die - } - - if isPlayer1sTurn { - positions[0] += threeRolls - for positions[0] > 10 { - positions[0] -= 10 - } - scores[0] += positions[0] - } else { - positions[1] += threeRolls - for positions[1] > 10 { - positions[1] -= 10 - } - scores[1] += positions[1] - } - // switch turns - isPlayer1sTurn = !isPlayer1sTurn - } - - loser := scores[0] - if scores[1] < 1000 { - loser = scores[1] - } - - // final answer is the losing player's score TIMES number of times the die was rolled - return loser * rolls -} - -func part2(input string) int64 { - // who wins in more universes - // dirac die, 3 sided, splits into 3 copies, one for each result, on EVERY roll - positions := parseInput(input) - w1, w2 := play([2]int{positions[0], positions[1]}, [2]int{}, 3, true, map[string][2]int64{}) - - if w1 > w2 { - return w1 - } - return w2 -} - -func play(positions, scores [2]int, rollsLeftInTurn int, isPlayer1sTurn bool, memo map[string][2]int64) (wins1, wins2 int64) { - key := fmt.Sprint(positions, scores, rollsLeftInTurn, isPlayer1sTurn) - if res, ok := memo[key]; ok { - return res[0], res[1] - } - - // NOTE 0-indexed array, so player 2 is at index 1... - playerIndex := 1 - if isPlayer1sTurn { - playerIndex = 0 - } - - scoresCopy := [2]int{scores[0], scores[1]} - if rollsLeftInTurn == 0 { - scoresCopy[playerIndex] += positions[playerIndex] - // TERMINATION CASE - if scoresCopy[playerIndex] >= 21 { - if playerIndex == 0 { - return 1, 0 - } - return 0, 1 - } - - isPlayer1sTurn = !isPlayer1sTurn - rollsLeftInTurn = 3 - // update playerIndex because they're switching now! (not getting that hour of my life back...) - playerIndex++ - playerIndex %= 2 - } - - for roll := 1; roll <= 3; roll++ { - // recurse with a given roll - // copy positions so each recurse gets its own copy - positionsCopy := [2]int{positions[0], positions[1]} - positionsCopy[playerIndex] += roll - if positionsCopy[playerIndex] > 10 { - positionsCopy[playerIndex] -= 10 - } - r1, r2 := play(positionsCopy, scoresCopy, rollsLeftInTurn-1, isPlayer1sTurn, memo) - wins1 += r1 - wins2 += r2 - } - - memo[key] = [2]int64{wins1, wins2} - return wins1, wins2 -} - -func parseInput(input string) (ans []int) { - for _, line := range strings.Split(input, "\n") { - // Player 1 starting position: 5 - var player, startingPosition int - fmt.Sscanf(line, "Player %d starting position: %d", &player, &startingPosition) - ans = append(ans, startingPosition) - } - return ans -} diff --git a/2021/day21/main_test.go b/2021/day21/main_test.go deleted file mode 100644 index 93a6016..0000000 --- a/2021/day21/main_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `Player 1 starting position: 4 -Player 2 starting position: 8` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 739785, - }, - { - name: "actual", - input: input, - want: 1067724, - }, - } - 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 int64 - }{ - { - name: "example", - input: example, - want: 444356092776315, - }, - { - name: "actual", - input: input, - want: 630947104784464, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day22/main.go b/2021/day22/main.go deleted file mode 100644 index af57afb..0000000 --- a/2021/day22/main.go +++ /dev/null @@ -1,192 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/mathy" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -// naive brute force for part1 -func part1(input string) int { - cubes := parseInput(input) - - onCoords := map[[3]int]bool{} - - for _, c := range cubes { - if part1OutOfBounds(c.x1, c.x2, c.y1, c.y2, c.z1, c.z2) { - continue - } - - for x := c.x1; x <= c.x2; x++ { - for y := c.y1; y <= c.y2; y++ { - for z := c.z1; z <= c.z2; z++ { - coord := [3]int{x, y, z} - onCoords[coord] = c.isOn - } - } - } - } - - var count int - for _, b := range onCoords { - if b { - count++ - } - } - - return count -} - -func part1OutOfBounds(nums ...int) bool { - for _, n := range nums { - if n < -50 || n > 50 { - return true - } - } - return false -} - -func part2(input string) int { - cubes := parseInput(input) - - var finalList []cube - // iterate through all cubes, keep a final list of cubes - // as new cubes are added, check against the entire final list and add any - // intersections that are found - for _, c := range cubes { - // add these at the end of the step to prevent duplicate checks - var toAdd []cube - - for _, finalCube := range finalList { - intersection, didIntersect := finalCube.getIntersection(c) - if didIntersect { - toAdd = append(toAdd, intersection) - } - } - - // if cube is an "on" cube, it needs to be added to final list - if c.isOn { - toAdd = append(toAdd, c) - } - - finalList = append(finalList, toAdd...) - } - - var total int - for _, c := range finalList { - total += c.volume() - } - - return total -} - -type cube struct { - isOn bool - x1, x2 int - y1, y2 int - z1, z2 int -} - -// NOTE: must be called in correct order (cube_from_final_list).getIntersection(cube_being_added) -// because of how the isOn bool is determined -func (c cube) getIntersection(c2 cube) (intersection cube, hasIntersection bool) { - // larger of x1s has to be smaller than smaller of x2s for there to be an overlap - x1 := mathy.MaxInt(c.x1, c2.x1) - x2 := mathy.MinInt(c.x2, c2.x2) - y1 := mathy.MaxInt(c.y1, c2.y1) - y2 := mathy.MinInt(c.y2, c2.y2) - z1 := mathy.MaxInt(c.z1, c2.z1) - z2 := mathy.MinInt(c.z2, c2.z2) - - if x1 > x2 || y1 > y2 || z1 > z2 { - return cube{}, false - } - - var intersectionState bool - if c.isOn && c2.isOn { - intersectionState = false - } else if !c.isOn && !c2.isOn { - intersectionState = true - } else { - // ! default to second cube's on/off state. This makes the order of which cube's method is - // called very important. but that's what unit tests are for :) - // alternatively the caller could deal with it.. that might be more clear... - intersectionState = c2.isOn - } - - return cube{ - isOn: intersectionState, - x1: x1, x2: x2, - y1: y1, y2: y2, - z1: z1, z2: z2, - }, true -} - -func (c cube) volume() int { - vol := (c.x2 - c.x1 + 1) * (c.y2 - c.y1 + 1) * (c.z2 - c.z1 + 1) - if c.isOn { - return vol - } - return -vol -} - -func parseInput(input string) (ans []cube) { - for _, line := range strings.Split(input, "\n") { - // off x=-29..-12,y=-13..5,z=-17..-3 - parts := strings.Split(line, " ") - - var x1, x2, y1, y2, z1, z2 int - n, err := fmt.Sscanf(parts[1], "x=%d..%d,y=%d..%d,z=%d..%d", &x1, &x2, &y1, &y2, &z1, &z2) - if err != nil || n != 6 { - panic(fmt.Sprintf("parsing error %v, vals parsed %d", err, n)) - } - - if x1 > x2 || y1 > y2 || z1 > z2 { - // note: they can be equal - panic("didn't expect input to have backwards coords, sort them...") - } - - ans = append(ans, cube{ - isOn: parts[0] == "on", - x1: x1, - x2: x2, - y1: y1, - y2: y2, - z1: z1, - z2: z2, - }) - } - return ans -} diff --git a/2021/day22/main_test.go b/2021/day22/main_test.go deleted file mode 100644 index cb1c525..0000000 --- a/2021/day22/main_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package main - -import ( - _ "embed" - "testing" -) - -var smallExample = `on x=10..12,y=10..12,z=10..12 -on x=11..13,y=11..13,z=11..13 -off x=9..11,y=9..11,z=9..11 -on x=10..10,y=10..10,z=10..10` - -var largeExample = `on x=-20..26,y=-36..17,z=-47..7 -on x=-20..33,y=-21..23,z=-26..28 -on x=-22..28,y=-29..23,z=-38..16 -on x=-46..7,y=-6..46,z=-50..-1 -on x=-49..1,y=-3..46,z=-24..28 -on x=2..47,y=-22..22,z=-23..27 -on x=-27..23,y=-28..26,z=-21..29 -on x=-39..5,y=-6..47,z=-3..44 -on x=-30..21,y=-8..43,z=-13..34 -on x=-22..26,y=-27..20,z=-29..19 -off x=-48..-32,y=26..41,z=-47..-37 -on x=-12..35,y=6..50,z=-50..-2 -off x=-48..-32,y=-32..-16,z=-15..-5 -on x=-18..26,y=-33..15,z=-7..46 -off x=-40..-22,y=-38..-28,z=23..41 -on x=-16..35,y=-41..10,z=-47..6 -off x=-32..-23,y=11..30,z=-14..3 -on x=-49..-5,y=-3..45,z=-29..18 -off x=18..30,y=-20..-8,z=-3..13 -on x=-41..9,y=-7..43,z=-33..15 -on x=-54112..-39298,y=-85059..-49293,z=-27449..7877 -on x=967..23432,y=45373..81175,z=27513..53682` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "smallExample", - input: smallExample, - want: 39, - }, - { - name: "largeExample", - input: largeExample, - want: 590784, - }, - { - name: "actual", - input: input, - want: 648681, - }, - } - 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 = `on x=-5..47,y=-31..22,z=-19..33 -on x=-44..5,y=-27..21,z=-14..35 -on x=-49..-1,y=-11..42,z=-10..38 -on x=-20..34,y=-40..6,z=-44..1 -off x=26..39,y=40..50,z=-2..11 -on x=-41..5,y=-41..6,z=-36..8 -off x=-43..-33,y=-45..-28,z=7..25 -on x=-33..15,y=-32..19,z=-34..11 -off x=35..47,y=-46..-34,z=-11..5 -on x=-14..36,y=-6..44,z=-16..29 -on x=-57795..-6158,y=29564..72030,z=20435..90618 -on x=36731..105352,y=-21140..28532,z=16094..90401 -on x=30999..107136,y=-53464..15513,z=8553..71215 -on x=13528..83982,y=-99403..-27377,z=-24141..23996 -on x=-72682..-12347,y=18159..111354,z=7391..80950 -on x=-1060..80757,y=-65301..-20884,z=-103788..-16709 -on x=-83015..-9461,y=-72160..-8347,z=-81239..-26856 -on x=-52752..22273,y=-49450..9096,z=54442..119054 -on x=-29982..40483,y=-108474..-28371,z=-24328..38471 -on x=-4958..62750,y=40422..118853,z=-7672..65583 -on x=55694..108686,y=-43367..46958,z=-26781..48729 -on x=-98497..-18186,y=-63569..3412,z=1232..88485 -on x=-726..56291,y=-62629..13224,z=18033..85226 -on x=-110886..-34664,y=-81338..-8658,z=8914..63723 -on x=-55829..24974,y=-16897..54165,z=-121762..-28058 -on x=-65152..-11147,y=22489..91432,z=-58782..1780 -on x=-120100..-32970,y=-46592..27473,z=-11695..61039 -on x=-18631..37533,y=-124565..-50804,z=-35667..28308 -on x=-57817..18248,y=49321..117703,z=5745..55881 -on x=14781..98692,y=-1341..70827,z=15753..70151 -on x=-34419..55919,y=-19626..40991,z=39015..114138 -on x=-60785..11593,y=-56135..2999,z=-95368..-26915 -on x=-32178..58085,y=17647..101866,z=-91405..-8878 -on x=-53655..12091,y=50097..105568,z=-75335..-4862 -on x=-111166..-40997,y=-71714..2688,z=5609..50954 -on x=-16602..70118,y=-98693..-44401,z=5197..76897 -on x=16383..101554,y=4615..83635,z=-44907..18747 -off x=-95822..-15171,y=-19987..48940,z=10804..104439 -on x=-89813..-14614,y=16069..88491,z=-3297..45228 -on x=41075..99376,y=-20427..49978,z=-52012..13762 -on x=-21330..50085,y=-17944..62733,z=-112280..-30197 -on x=-16478..35915,y=36008..118594,z=-7885..47086 -off x=-98156..-27851,y=-49952..43171,z=-99005..-8456 -off x=2032..69770,y=-71013..4824,z=7471..94418 -on x=43670..120875,y=-42068..12382,z=-24787..38892 -off x=37514..111226,y=-45862..25743,z=-16714..54663 -off x=25699..97951,y=-30668..59918,z=-15349..69697 -off x=-44271..17935,y=-9516..60759,z=49131..112598 -on x=-61695..-5813,y=40978..94975,z=8655..80240 -off x=-101086..-9439,y=-7088..67543,z=33935..83858 -off x=18020..114017,y=-48931..32606,z=21474..89843 -off x=-77139..10506,y=-89994..-18797,z=-80..59318 -off x=8476..79288,y=-75520..11602,z=-96624..-24783 -on x=-47488..-1262,y=24338..100707,z=16292..72967 -off x=-84341..13987,y=2429..92914,z=-90671..-1318 -off x=-37810..49457,y=-71013..-7894,z=-105357..-13188 -off x=-27365..46395,y=31009..98017,z=15428..76570 -off x=-70369..-16548,y=22648..78696,z=-1892..86821 -on x=-53470..21291,y=-120233..-33476,z=-44150..38147 -off x=-93533..-4276,y=-16170..68771,z=-104985..-24507` - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "part2Example", - input: part2Example, - want: 2758514936282235, - }, - { - name: "simple, no overlap", - input: `on x=1..2,y=1..2,z=1..2 -on x=5..6,y=5..6,z=5..6`, - want: 16, - }, - { - name: "simple, one overlap", - input: `on x=1..3,y=1..3,z=1..3 -on x=2..4,y=2..4,z=2..4`, - want: 46, // 27 + 27 - 8 - }, - { - name: "simple, turn some off", - input: `on x=1..3,y=1..3,z=1..3 -off x=2..4,y=2..4,z=2..4`, - want: 19, // 27 - 8 - }, - { - name: "actual", - input: input, - want: 1302784472088899, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day23/main.go b/2021/day23/main.go deleted file mode 100644 index 4e6452b..0000000 --- a/2021/day23/main.go +++ /dev/null @@ -1,307 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/data-structures/heap" - "github.com/alexchao26/advent-of-code-go/mathy" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := amphipodDay23(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -var roomCoordToWantCharPart1 = map[[2]int]string{ - {2, 3}: "A", {3, 3}: "A", - {2, 5}: "B", {3, 5}: "B", - {2, 7}: "C", {3, 7}: "C", - {2, 9}: "D", {3, 9}: "D", -} -var roomCoordToWantCharPart2 = map[[2]int]string{ - {2, 3}: "A", {3, 3}: "A", {4, 3}: "A", {5, 3}: "A", - {2, 5}: "B", {3, 5}: "B", {4, 5}: "B", {5, 5}: "B", - {2, 7}: "C", {3, 7}: "C", {4, 7}: "C", {5, 7}: "C", - {2, 9}: "D", {3, 9}: "D", {4, 9}: "D", {5, 9}: "D", -} - -func amphipodDay23(input string, part int) int { - start := parseInput(input) - - roomCoordToWantChar := roomCoordToWantCharPart1 - if part == 2 { - roomCoordToWantChar = roomCoordToWantCharPart2 - - // update the grid with the 2 new rows, move old ones down - start.grid = append(start.grid, nil, nil) - start.grid[6] = start.grid[4] - start.grid[5] = start.grid[3] - - start.grid[3] = strings.Split(" #D#C#B#A# ", "") - start.grid[4] = strings.Split(" #D#B#A#C# ", "") - } - - minHeap := heap.NewMinHeap() - - minHeap.Add(start) - seenGrids := map[string]bool{} - for minHeap.Length() > 0 { - front := minHeap.Remove().(*state) - - key := fmt.Sprint(front.grid) - if seenGrids[key] { - continue - } - seenGrids[key] = true - - if front.allDone(roomCoordToWantChar) { - return front.energyUsed - } - - unsettledCoords := front.getUnsettledCoords(roomCoordToWantChar) - for _, unsettledCoord := range unsettledCoords { - ur, uc := unsettledCoord[0], unsettledCoord[1] - nextMoves := front.getNextPossibleMoves(unsettledCoord, roomCoordToWantChar) - for _, nextCoord := range nextMoves { - nr, nc := nextCoord[0], nextCoord[1] - if front.grid[nr][nc] != "." { - panic(fmt.Sprintf("should only be moving to walkable spaces, got %q at %d,%d", front.grid[nr][nc], nr, nc)) - } - - cp := front.copy() - // add the energy that will be used, swap the two coords - cp.energyUsed += calcEnergy(cp.grid[ur][uc], unsettledCoord, nextCoord) - cp.path += fmt.Sprintf("%s%v->%v{%d},", front.grid[ur][uc], unsettledCoord, nextCoord, cp.energyUsed) - cp.grid[nr][nc], cp.grid[ur][uc] = cp.grid[ur][uc], cp.grid[nr][nc] - - // add it to the min heap - minHeap.Add(cp) - } - } - } - - panic("should return from loop") -} - -type state struct { - grid [][]string - energyUsed int - path string // for debugging -} - -func parseInput(input string) *state { - grid := [][]string{} - for _, line := range strings.Split(input, "\n") { - grid = append(grid, strings.Split(line, "")) - } - return &state{ - grid: grid, - } -} - -// Value is to implement the heap.heapNode interface so I can dump states into a Min Heap -func (s *state) Value() int { - return s.energyUsed -} - -func (s *state) String() string { - var sb strings.Builder - for _, row := range s.grid { - for _, unsettledChar := range row { - sb.WriteString(unsettledChar) - } - sb.WriteRune('\n') - } - - sb.WriteString(fmt.Sprintf("nrg: %d, ,path: %s\n", s.energyUsed, s.path)) - - return sb.String() -} - -// copy method to generate copies to make future heap nodes -func (s *state) copy() *state { - cp := state{ - grid: make([][]string, len(s.grid)), - energyUsed: s.energyUsed, - path: s.path, - } - - // need to directly copy grid or else underlying arrays will be the same & interfere - for i := range cp.grid { - cp.grid[i] = make([]string, len(s.grid[i])) - copy(cp.grid[i], s.grid[i]) - } - - return &cp -} - -func (s *state) allDone(roomCoordToWantChar map[[2]int]string) bool { - for coord, want := range roomCoordToWantChar { - if s.grid[coord[0]][coord[1]] != want { - return false - } - } - return true -} - -func (s *state) getUnsettledCoords(roomCoordToWantChar map[[2]int]string) [][2]int { - var unsettled [][2]int - // check entire hallway - for col := 1; col < len(s.grid[0]); col++ { - if strings.Contains("ABCD", s.grid[1][col]) { - unsettled = append(unsettled, [2]int{1, col}) - } - } - - for _, col := range []int{3, 5, 7, 9} { - roomFullFromBack := true - for row := len(s.grid) - 2; row >= 2; row-- { - coord := [2]int{row, col} - wantChar := roomCoordToWantChar[coord] - gotChar := s.grid[row][col] - if gotChar != "." { - if gotChar != wantChar { - roomFullFromBack = false - unsettled = append(unsettled, coord) - } else if gotChar == wantChar && !roomFullFromBack { - // need to get out of the way of someone in the wrong room - unsettled = append(unsettled, coord) - } - } - } - } - return unsettled -} - -// cannot stop in front of a room, still applicable for part2 -var coordsInFrontOfRooms = map[[2]int]bool{ - {1, 3}: true, - {1, 5}: true, - {1, 7}: true, - {1, 9}: true, -} - -func isInHallway(coord [2]int) bool { - return coord[0] == 1 -} - -func (s *state) getNextPossibleMoves(unsettledCoord [2]int, roomCoordToWantChar map[[2]int]string) [][2]int { - // get all the eligible locations for this coord to go to - unsettledChar := s.grid[unsettledCoord[0]][unsettledCoord[1]] - - if !strings.Contains("ABCD", unsettledChar) { - panic("unexpected character to get next moves for " + unsettledChar) - } - - var possible [][2]int - - startedInHallway := isInHallway(unsettledCoord) - - queue := [][2]int{unsettledCoord} - seen := map[[2]int]bool{} - for len(queue) > 0 { - front := queue[0] - queue = queue[1:] - - if seen[front] { - continue - } - seen[front] = true - - if front != unsettledCoord { - // is not a coord in front of a room - if !coordsInFrontOfRooms[front] { - wantChar, isRoomCoord := roomCoordToWantChar[front] - // if NOT in a room, append it - if !isRoomCoord { - // ONLY add a hallway if it started in a room bc of rule 3 - if !startedInHallway { - possible = append(possible, front) - } - } else if wantChar == unsettledChar { - // found the correct room - // check if there is a deeper part of the room (aka lower) - - // if there is a "stuck" amphipod deeper in the room, cannot stop here - // if not deepest empty coord, cannot stop here - // in both cases walking further is handles all cases, whether that's - // to walk further in or out of the room - isStuckAmphipod := false - roomHasDeeperOpenSpaces := false - for r := front[0] + 1; r < len(s.grid)-1; r++ { - char := s.grid[r][front[1]] - if char == "." { - roomHasDeeperOpenSpaces = true - } - if char != "." && char != unsettledChar { - isStuckAmphipod = true - break - } - } - - if !roomHasDeeperOpenSpaces && !isStuckAmphipod { - possible = append(possible, front) - } - } - } - } - - for _, d := range [][2]int{ - // up down left right - {-1, 0}, - {1, 0}, - {0, -1}, - {0, 1}, - } { - // do not need to check in range because the entire walkable area is surrounded by walls - next := [2]int{front[0] + d[0], front[1] + d[1]} - if s.grid[next[0]][next[1]] == "." { - // add to queue to keep walking regardless of whether or not it gets added to the possible slice - queue = append(queue, next) - } - } - } - - return possible -} - -func calcEnergy(char string, start, end [2]int) int { - // start with cols distance - dist := mathy.AbsInt(end[1] - start[1]) - // add distance to hallway for start and end? - dist += start[0] - 1 - dist += end[0] - 1 - - energyPerType := map[string]int{ - "A": 1, - "B": 10, - "C": 100, - "D": 1000, - } - - if _, ok := energyPerType[char]; !ok { - panic(char + " should not call calcEnergy()") - } - return energyPerType[char] * dist -} diff --git a/2021/day23/main_test.go b/2021/day23/main_test.go deleted file mode 100644 index 7d00aae..0000000 --- a/2021/day23/main_test.go +++ /dev/null @@ -1,378 +0,0 @@ -package main - -import ( - _ "embed" - "fmt" - "reflect" - "strings" - "testing" -) - -var example = `############# -#...........# -###B#C#B#D### - #A#D#C#A# - ######### ` - -var doneInput = `############# -#...........# -###A#B#C#D### - #A#B#C#D# - ######### ` - -func Test_amphipodDay23(t *testing.T) { - tests := []struct { - name string - input string - part int - want int - }{ - { - name: "part1 example", - input: example, - part: 1, - want: 12521, - }, - { - name: "part1 simple", - input: `############# -#.A.........# -###.#B#C#D### - #A#B#C#D# - ######### `, - part: 1, - want: 2, - }, - { - name: "part1 simple: A then B", - input: `############# -#BA.........# -###.#.#C#D### - #A#B#C#D# - ######### `, - part: 1, - want: 52, - }, - { - // NOTE found a bug! A moving from a deep room to another room is calculating energy - // NOTE as if it is walking through the wall - name: "part1 reversed B room", // B has to get out of A's way first - input: `############# -#.B.........# -###.#B#C#D### - #A#A#C#D# - ######### `, - part: 1, - want: 95, - }, - { - name: "part1 some shuffling", - input: `############# -#...........# -###A#B#C#D### - #A#C#B#D# - ######### `, - part: 1, - want: 1120, - }, - { - name: "part1 doneInput", - input: doneInput, - part: 1, - want: 0, - }, - { - name: "part1 actual", - input: input, - part: 1, - want: 15299, - }, - - { - name: "example part 2", - input: example, - part: 2, - want: 44169, - }, - { - name: "part2 actual", - input: input, - part: 2, - want: 47193, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if testing.Short() && strings.Contains(tt.name, "actual") { - t.Skip(fmt.Sprintf("skipping %q in -short mode", tt.name)) - } - if got := amphipodDay23(tt.input, tt.part); got != tt.want { - t.Errorf("amphipodDay23() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_state_getUnsettledCoords(t *testing.T) { - tests := []struct { - name string - input string - roomCoordToWantChar map[[2]int]string - want [][2]int - }{ - { - name: "already done", - input: doneInput, - roomCoordToWantChar: roomCoordToWantCharPart1, - want: nil, - }, - { - name: "example - 2 \"done\" amphipods", - input: example, - /* - ############# - #...........# - ###B#C#B#D### - #A#D#C#A# - ######### - */ - roomCoordToWantChar: roomCoordToWantCharPart1, - want: [][2]int{{2, 3}, {3, 5}, {2, 5}, {2, 7}, {3, 9}, {2, 9}}, - }, - { - name: "four unsettled coords", - input: `############# -#AB.....D..D# -###.#.#C#.### - #A#B#C#.# - ######### `, - roomCoordToWantChar: roomCoordToWantCharPart1, - want: [][2]int{{1, 1}, {1, 2}, {1, 8}, {1, 11}}, - }, - { - name: "part2 test", - input: `############# -#AB.....D..D# -###.#.#C#.### - #A#A#C#.# - #B#B#C#D# - #A#B#C#D# - ######### `, - roomCoordToWantChar: roomCoordToWantCharPart2, - want: [][2]int{ - // hallway - {1, 1}, {1, 2}, {1, 8}, {1, 11}, - {4, 3}, {3, 3}, // A room - {3, 5}, // B room - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := parseInput(tt.input) - if got := s.getUnsettledCoords(tt.roomCoordToWantChar); !reflect.DeepEqual(got, tt.want) { - t.Errorf("state.getUnsettledCoords() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_state_getNextPossibleMoves(t *testing.T) { - tests := []struct { - name string - input string - unsettledCoord [2]int - roomCoordToWantChar map[[2]int]string - want [][2]int - }{ - { - name: "example, deep room is stuck", - input: example, - /* - ############# - #...........# - ###B#C#B#D### - #A#D#C#A# - ######### - */ - unsettledCoord: [2]int{3, 3}, // DEEP room in A slot - roomCoordToWantChar: roomCoordToWantCharPart1, - want: nil, - }, - { - name: "example-frontA", - input: example, - unsettledCoord: [2]int{2, 3}, // FRONT room in A slot - roomCoordToWantChar: roomCoordToWantCharPart1, - want: [][2]int{{1, 2}, {1, 4}, {1, 1}, {1, 6}, {1, 8}, {1, 10}, {1, 11}}, - }, - { - name: "example-deepA-should be stuck", - input: example, - unsettledCoord: [2]int{3, 3}, // FRONT room in A slot - roomCoordToWantChar: roomCoordToWantCharPart1, - want: nil, - }, - { - name: "hallways to rooms ONLY", - input: `############# -#AB.....D..D# -###.#.#C#.### - #A#B#C#.# - ######### `, - unsettledCoord: [2]int{1, 2}, - roomCoordToWantChar: roomCoordToWantCharPart1, - want: [][2]int{{2, 5}}, - }, - { - name: "hallway to DEEP room", - input: `############# -#AB.....D..D# -###.#.#C#.### - #A#B#C#.# - ######### `, - unsettledCoord: [2]int{1, 8}, - roomCoordToWantChar: roomCoordToWantCharPart1, - want: [][2]int{{3, 9}}, - }, - { - name: "part2 simple", - input: `############# -#B..........# -###A#.#C#D### - #A#B#C#D# - #A#B#C#D# - #A#B#C#D# - ######### `, - unsettledCoord: [2]int{1, 1}, - roomCoordToWantChar: roomCoordToWantCharPart2, - want: [][2]int{{2, 5}}, - }, - { - name: "part2 back of room", - input: `############# -#B......B.BB# -###A#.#C#D### - #A#.#C#D# - #A#.#C#D# - #A#.#C#D# - ######### `, - unsettledCoord: [2]int{1, 1}, - roomCoordToWantChar: roomCoordToWantCharPart2, - want: [][2]int{{5, 5}}, - }, - { - name: "part2 back of room", - input: `############# -#B......B..B# -###A#.#C#D### - #A#.#C#D# - #A#.#C#D# - #A#B#C#D# - ######### `, - unsettledCoord: [2]int{1, 8}, - roomCoordToWantChar: roomCoordToWantCharPart2, - want: [][2]int{{4, 5}}, - }, - { - name: "part2 bug moving C from B room to C room", - input: `############# -#AA.....B.BD# -###B#.#.#.### - #D#C#.#.# - #D#B#C#C# - #A#D#C#A# - ######### `, - unsettledCoord: [2]int{3, 5}, - roomCoordToWantChar: roomCoordToWantCharPart2, - want: [][2]int{{1, 4}, {1, 6}, {3, 7}}, - }, - { - name: "part2 bug moving B out of B room bc of a blocked D", - input: `############# -#AA.....B.BD# -###B#.#.#.### - #D#.#C#.# - #D#B#C#C# - #A#D#C#A# - ######### `, - unsettledCoord: [2]int{4, 5}, - roomCoordToWantChar: roomCoordToWantCharPart2, - want: [][2]int{{1, 4}, {1, 6}}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := parseInput(tt.input) - if got := s.getNextPossibleMoves(tt.unsettledCoord, tt.roomCoordToWantChar); !reflect.DeepEqual(got, tt.want) { - t.Errorf("state.getNextPossibleMoves() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_calcEnergy(t *testing.T) { - type args struct { - char string - start [2]int - end [2]int - } - tests := []struct { - name string - args args - want int - }{ - { - name: "A 4 spaces away", - args: args{ - char: "A", - start: [2]int{1, 1}, - end: [2]int{1, 5}, - }, - want: 4, - }, - { - name: "A goes from B's room to A's room", - args: args{ - char: "A", - start: [2]int{3, 5}, - end: [2]int{2, 3}, - }, - want: 5, - }, - { - name: "D 6 spaces away", - args: args{ - char: "D", - start: [2]int{3, 3}, - end: [2]int{2, 8}, - }, - want: 8000, - }, - { - name: "C", - args: args{ - char: "C", - start: [2]int{1, 11}, - end: [2]int{3, 7}, - }, - want: 600, - }, - { - name: "part2 C", - args: args{ - char: "C", - start: [2]int{5, 11}, - end: [2]int{3, 7}, - }, - want: 1000, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := calcEnergy(tt.args.char, tt.args.start, tt.args.end); got != tt.want { - t.Errorf("calcEnergy() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day24/main.go b/2021/day24/main.go deleted file mode 100644 index cd77470..0000000 --- a/2021/day24/main.go +++ /dev/null @@ -1,224 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strconv" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := aluDay24(input, part) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func aluDay24(input string, part int) int64 { - instLines := strings.Split(input, "\n") - - /* - "Compiled" the assembly to understand that it was 14 separate "programs" - one for each input character. - z is the only stateful variable, doing some algebra shows that this is - basically a stack pairing a=1 and a=26 - - determining the relationships between the input characters reduces the - search size, so it can be brute forced - */ - - /* - NOTE: this does not need to be looped to find the largest and smallest - because the differences are all known so it's just a matter of finding - the largest or smallest digits from the start - w0 + 4 = w13 -> max w0 is 5, so w13 = 9 - */ - - // w10 + 8 = w11 - // w5 - 8 = w6 - // do not need to loop these bc they are 8 apart so only one possibility - w11, w10 := 9, 1 - w5, w6 := 9, 1 - - var largest, smallest int64 = 0, 99999999999999 - // w0 + 4 = w13 - for w13, w0 := 9, 5; w0 >= 1; w13, w0 = w13-1, w0-1 { - // w1 - 6 = w12 - for w12, w1 := 3, 9; w12 >= 1; w12, w1 = w12-1, w1-1 { - // w3 - 3 = w4 - for w3, w4 := 9, 6; w4 >= 1; w3, w4 = w3-1, w4-1 { - // w2 - 1 = w9 - for w2, w9 := 9, 8; w9 >= 1; w2, w9 = w2-1, w9-1 { - // w7 + 7 = w8 - for w7, w8 := 2, 9; w7 >= 1; w7, w8 = w7-1, w8-1 { - str := fmt.Sprintf("%d%d%d%d%d%d%d%d%d%d%d%d%d%d", - w0, w1, w2, w3, w4, w5, w6, - w7, w8, w9, w10, w11, w12, w13, - ) - num, err := strconv.ParseInt(str, 10, 64) - if err != nil { - panic("parseint" + err.Error()) - } - - // check against hardcoded alu and actual alu implementation - lastZ := hardcodedALU(num) - lastZ2 := runALU(num, instLines) - - if lastZ == 0 && lastZ2 == 0 { - if num > largest { - largest = num - } - if num < smallest { - smallest = num - } - } - } - } - } - } - } - - if part == 1 { - return largest - } - return smallest -} - -// a literal implementation of the ALU, useful for checking the final answer? -func runALU(model int64, instLines []string) (z int64) { - registers := map[string]int64{} - // ALU will only have w,x,y,z keys - // sub model numbers to test are 14 digit numbers (1-9 ONLY) - // uses 14 separate inp instructions, each a single digit of the model number (starting from left) - // 1357... has inputs 1, 3, 5, 7... - // model number is valid IF variable z == 0 - modelNumberIndex := 0 - modelString := fmt.Sprint(model) - if len(modelString) != 14 { - panic("model string should be 14 characters: " + modelString) - } - - for _, line := range instLines { - parts := strings.Split(line, " ") - - var bVal int64 - if len(parts) == 3 { - maybeInt64, err := strconv.Atoi(parts[2]) // could use a regexp too... - if err != nil { - // parts[2] is not a number, look up its register - bVal = registers[parts[2]] - } else { - bVal = int64(maybeInt64) - } - } - switch parts[0] { - case "inp": - val := cast.ToInt(modelString[modelNumberIndex : modelNumberIndex+1]) - modelNumberIndex++ - registers[parts[1]] = int64(val) - case "add": - a := parts[1] - registers[a] += bVal - case "mul": - a := parts[1] - registers[a] *= bVal - case "div": - a := parts[1] - registers[a] /= bVal - case "mod": - a := parts[1] - registers[a] %= bVal - case "eql": - a := parts[1] - if registers[a] == bVal { - registers[a] = 1 - } else { - registers[a] = 0 - } - default: - panic("unexpected command type " + parts[0]) - } - } - - return registers["z"] -} - -func hardcodedALU(model int64) (z int64) { - registers := map[string]int64{} - modelChars := strings.Split(fmt.Sprint(model), "") - if len(modelChars) != 14 { - panic(fmt.Sprintf("expected 14 digit number, got %d", len(modelChars))) - } - - // all 14 steps are the same except for 3 values - /* - inp w - mul x 0 - add x z - mod x 26 - div z 1 <-- - add x 14 <-- - eql x w - eql x 0 - mul y 0 - add y 25 - mul y x - add y 1 - mul z y - mul y 0 - add y w - add y 16 <-- - mul y x - add z y - */ - // div z, add x, add y - differentVals := [][3]int{ - {1, 14, 16}, // 0 - {1, 11, 3}, // 1 - {1, 12, 2}, // 2 - {1, 11, 7}, // 3 - {26, -10, 13}, // 4 - {1, 15, 6}, // 5 - {26, -14, 10}, // 6 - {1, 10, 11}, // 7 - {26, -4, 6}, // 8 - {26, -3, 5}, // 9 - {1, 13, 11}, // 10 - {26, -3, 4}, // 11 - {26, -9, 4}, // 12 - {26, -12, 6}, // 13 - } - for i, char := range modelChars { - inp := int64(cast.ToInt(char)) - - x := registers["z"]%26 + int64(differentVals[i][1]) - - // always divided by 1 or 26... 1 is a no-op - registers["z"] /= int64(differentVals[i][0]) - - if x != inp { - registers["z"] = 26*registers["z"] + (inp + int64(differentVals[i][2])) - } - } - - return registers["z"] -} diff --git a/2021/day24/main_test.go b/2021/day24/main_test.go deleted file mode 100644 index f0fd419..0000000 --- a/2021/day24/main_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "testing" -) - -func Test_aluDay24(t *testing.T) { - tests := []struct { - name string - input string - part int - want int64 - }{ - { - name: "actual", - input: input, - part: 1, - want: 59996912981939, - }, - { - name: "actual", - input: input, - part: 2, - want: 17241911811915, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := aluDay24(tt.input, tt.part); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2021/day25/main.go b/2021/day25/main.go deleted file mode 100644 index 474f4af..0000000 --- a/2021/day25/main.go +++ /dev/null @@ -1,125 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "math" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) -} - -func part1(input string) int { - // east cucumbers try to move first - // then south cucumbers - // individually they only move if the space in front is empty - // EVEN if the guy in front is facing the same direction, don't move forward - // ALSO wrap right->left, bottom->top - - // find first step when they stop moving - - var grid [][]string - for _, line := range strings.Split(input, "\n") { - grid = append(grid, strings.Split(line, "")) - } - - for steps := 1; steps < math.MaxInt64; steps++ { - hash := fmt.Sprint(grid) - next := map[[2]int]string{} - - // traverse east - for r := range grid { - for c := range grid[0] { - if grid[r][c] == ">" { - nextC := (c + 1) % len(grid[0]) - if grid[r][nextC] == "." { - next[[2]int{r, nextC}] = ">" - next[[2]int{r, c}] = "." - } else { - // do not move forward - next[[2]int{r, c}] = ">" - } - } - } - } - - var nextGrid [][]string - for range grid { - nextGrid = append(nextGrid, make([]string, len(grid[0]))) - } - for c, v := range next { - nextGrid[c[0]][c[1]] = v - } - for r := range grid { - for c := range grid[0] { - if nextGrid[r][c] == "" { - nextGrid[r][c] = grid[r][c] - } - } - } - grid = nextGrid - - next = map[[2]int]string{} - // traverse south - for r := range grid { - for c := range grid[0] { - if grid[r][c] == "v" { - nextR := (r + 1) % len(grid) - if grid[nextR][c] == "." { - next[[2]int{nextR, c}] = "v" - next[[2]int{r, c}] = "." - } else { - // do not move forward - next[[2]int{r, c}] = "v" - } - } - } - } - - nextGrid = [][]string{} - for range grid { - nextGrid = append(nextGrid, make([]string, len(grid[0]))) - } - for c, v := range next { - nextGrid[c[0]][c[1]] = v - } - for r := range grid { - for c := range grid[0] { - if nextGrid[r][c] == "" { - nextGrid[r][c] = grid[r][c] - } - } - } - - if hash == fmt.Sprint(nextGrid) { - // nothing moved, return the STEP NUMBER IT IS - return steps - } - grid = nextGrid - } - - panic("should return from loop") -} diff --git a/2021/day25/main_test.go b/2021/day25/main_test.go deleted file mode 100644 index f658ace..0000000 --- a/2021/day25/main_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `v...>>.vv> -.vv>>.vv.. ->>.>v>...v ->>v>>.>.v. -v>v.vv.v.. ->.>>..v... -.vv..>.>v. -v.v..>>v.v -....v..v.>` - -var doneExample = `..>>v>vv.. -..v.>>vv.. -..>>v>>vv. -..>>>>>vv. -v......>vv -v>v....>>v -vvv.....>> ->vv......> -.>v.vv.v..` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 58, - }, - { - name: "doneExample", - input: doneExample, - want: 1, - }, - { - name: "actual", - input: input, - want: 579, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2022/day01/main.go b/2022/day01/main.go deleted file mode 100644 index fd0228a..0000000 --- a/2022/day01/main.go +++ /dev/null @@ -1,79 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "sort" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/mathy" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - elves := parseInput(input) - - totals := []int{} - for _, items := range elves { - totals = append(totals, mathy.SumIntSlice(items)) - } - - return mathy.MaxInt(totals...) -} - -func part2(input string) int { - elves := parseInput(input) - - totals := []int{} - for _, items := range elves { - totals = append(totals, mathy.SumIntSlice(items)) - } - sort.Ints(totals) - - topThree := 0 - for i := 0; i < 3; i++ { - topThree += totals[len(totals)-1-i] - } - return topThree -} - -func parseInput(input string) (ans [][]int) { - for _, group := range strings.Split(input, "\n\n") { - row := []int{} - for _, line := range strings.Split(group, "\n") { - row = append(row, cast.ToInt(line)) - } - ans = append(ans, row) - } - return ans -} diff --git a/2022/day01/main_test.go b/2022/day01/main_test.go deleted file mode 100644 index f6ec271..0000000 --- a/2022/day01/main_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "testing" -) - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "actual", - input: input, - want: 66719, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "actual", - input: input, - want: 198551, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2022/day02/main.go b/2022/day02/main.go deleted file mode 100644 index 134e977..0000000 --- a/2022/day02/main.go +++ /dev/null @@ -1,178 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -const ( - Win = 6 - Loss = 0 - Draw = 3 - - Rock = 1 - Paper = 2 - Scissors = 3 -) - -// this code is heinoussssss -// there's some kind of cheeky map to circular LL nodes to determine if you won or lost -// but there are so few branches to handle that it's faster to just code it manually... - -func part1(input string) int { - lines := parseInput(input) - - // opp choices: ABC rock-paper-scissors - // my choices: XYZ rock-paper-scissors - choices := map[string]int{ - "X": Rock, - "Y": Paper, - "Z": Scissors, - } - - totalScore := 0 - for _, l := range lines { - if _, ok := choices[l[1]]; !ok { - panic("choice not in choices map") - } - totalScore += choices[l[1]] - switch l[1] { - case "X": // i played rock - switch l[0] { - case "A": - totalScore += Draw - case "B": - totalScore += Loss - case "C": - totalScore += Win - default: - panic("unacceptable opp choice " + l[0]) - } - case "Y": // i played paper - switch l[0] { - case "A": // rock - totalScore += Win - case "B": // paper - totalScore += Draw - case "C": // scissors - totalScore += Loss - default: - panic("unacceptable opp choice " + l[0]) - } - case "Z": // i played scissors - switch l[0] { - case "A": // rock - totalScore += Loss - case "B": // paper - totalScore += Win - case "C": // scissors - totalScore += Draw - default: - panic("unacceptable opp choice " + l[0]) - } - } - } - - return totalScore -} - -func part2(input string) int { - /* - second column is result, not your choice - X -> you lose - Y -> draw - Z -> win - */ - lines := parseInput(input) - - winningScores := map[string]int{ - "X": Loss, - "Y": Draw, - "Z": Win, - } - - totalScore := 0 - for _, l := range lines { - if _, ok := winningScores[l[1]]; !ok { - panic("unacceptable result " + l[1]) - } - totalScore += winningScores[l[1]] - // switch on opp choice instead - switch l[0] { - case "A": // opp: rock - switch l[1] { - case "X": // lose - totalScore += Scissors - case "Y": // draw - totalScore += Rock - case "Z": // win - totalScore += Paper - default: - panic("unacceptable choice " + l[1]) - } - case "B": // opp: paper - switch l[1] { - case "X": // lose - totalScore += Rock - case "Y": // draw - totalScore += Paper - case "Z": // win - totalScore += Scissors - default: - panic("unacceptable choice " + l[1]) - } - case "C": // opp: scissors - switch l[1] { - case "X": // lose - totalScore += Paper - case "Y": // draw - totalScore += Scissors - case "Z": // win - totalScore += Rock - default: - panic("unacceptable choice " + l[1]) - } - } - } - - return totalScore -} - -func parseInput(input string) (ans [][]string) { - for _, line := range strings.Split(input, "\n") { - ans = append(ans, strings.Split(line, " ")) - } - return ans -} diff --git a/2022/day02/main_test.go b/2022/day02/main_test.go deleted file mode 100644 index 0d38a44..0000000 --- a/2022/day02/main_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `A Y -B X -C Z` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 15, - }, - { - name: "actual", - input: input, - want: 14531, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 12, - }, - { - name: "actual", - input: input, - want: 11258, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2022/day03/main.go b/2022/day03/main.go deleted file mode 100644 index 5c73389..0000000 --- a/2022/day03/main.go +++ /dev/null @@ -1,95 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -var priorities = map[string]int{} - -// lol why am i using init like this... -func init() { - // generate priorities - for i := 0; i < 26; i++ { - priorities[cast.ASCIIIntToChar('a'+i)] = i + 1 - priorities[cast.ASCIIIntToChar('A'+i)] = i + 27 - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - prioritiesSum := 0 - for _, l := range strings.Split(input, "\n") { - rightHalf := l[len(l)/2:] - if len(rightHalf) != len(l)/2 { - panic("did not divide in half: " + l) - } - for i := 0; i < len(l)/2; i++ { - leftChar := l[i : i+1] - // obv unnecessary n^2 but easily fast enough... - if strings.Contains(rightHalf, leftChar) { - prioritiesSum += priorities[leftChar] - break - } - } - } - - return prioritiesSum -} - -func part2(input string) int { - prioritiesSum := 0 - sacks := strings.Split(input, "\n") - - for i := 0; i < len(sacks); i += 3 { - set2, set3 := stringToSet(sacks[i+1]), stringToSet(sacks[i+2]) - for _, char := range strings.Split(sacks[i], "") { - if set2[char] && set3[char] { - prioritiesSum += priorities[char] - break - } - } - } - - return prioritiesSum -} - -func stringToSet(s string) map[string]bool { - set := map[string]bool{} - for _, char := range strings.Split(s, "") { - set[char] = true - } - return set -} diff --git a/2022/day03/main_test.go b/2022/day03/main_test.go deleted file mode 100644 index bee386c..0000000 --- a/2022/day03/main_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `vJrwpWtwJgWrhcsFMMfFFhFp -jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL -PmmdzqPrVvPwwTWBwg -wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn -ttgJtRGJQctTZtZT -CrZsJsPPZsGzwwsLwLmpwMDw` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 157, - }, - { - name: "actual", - input: input, - want: 7553, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 70, - }, - { - name: "actual", - input: input, - want: 2758, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/2022/day04/main.go b/2022/day04/main.go deleted file mode 100644 index 843fdba..0000000 --- a/2022/day04/main.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - _ "embed" - "flag" - "fmt" - "strings" - - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" -) - -//go:embed input.txt -var input string - -func init() { - // do this in init (not main) so test file has same input - input = strings.TrimRight(input, "\n") - if len(input) == 0 { - panic("empty input.txt file") - } -} - -func main() { - var part int - flag.IntVar(&part, "part", 1, "part 1 or 2") - flag.Parse() - fmt.Println("Running part", part) - - if part == 1 { - ans := part1(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(input) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } -} - -func part1(input string) int { - lines := parseInput(input) - ans := 0 - - for _, l := range lines { - if doesPair2ContainPair1(l[:2], l[2:]) || doesPair2ContainPair1(l[2:], l[:2]) { - ans++ - } - } - - return ans -} - -func doesPair2ContainPair1(pair1, pair2 []int) bool { - return pair1[0] >= pair2[0] && pair1[1] <= pair2[1] -} - -func part2(input string) int { - lines := parseInput(input) - ans := 0 - - for _, l := range lines { - if doesOverlap(l[:2], l[2:]) { - ans++ - } - } - - return ans -} - -func doesOverlap(pair1, pair2 []int) bool { - // sort - if pair1[0] > pair2[0] { - pair1, pair2 = pair2, pair1 - } - return pair1[1] >= pair2[0] -} - -func parseInput(input string) (ans [][]int) { - for _, line := range strings.Split(input, "\n") { - parts := strings.Split(line, ",") - leftParts := strings.Split(parts[0], "-") - rightParts := strings.Split(parts[1], "-") - ans = append(ans, []int{ - cast.ToInt(leftParts[0]), cast.ToInt(leftParts[1]), - cast.ToInt(rightParts[0]), cast.ToInt(rightParts[1]), - }) - } - return ans -} diff --git a/2022/day04/main_test.go b/2022/day04/main_test.go deleted file mode 100644 index d3aca3d..0000000 --- a/2022/day04/main_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "testing" -) - -var example = `2-4,6-8 -2-3,4-5 -5-7,7-9 -2-8,3-7 -6-6,4-6 -2-6,4-8` - -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 2, - }, - { - name: "actual", - input: input, - want: 526, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - { - name: "example", - input: example, - want: 4, - }, - { - name: "actual", - input: input, - want: 886, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/algos/split-string-on_test.go b/algos/split-string-on_test.go index 50a5860..e3e1785 100644 --- a/algos/split-string-on_test.go +++ b/algos/split-string-on_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/alexchao26/advent-of-code-go/algos" + "github.com/Threnklyn/advent-of-code-go/algos" ) func TestSplitStringOn(t *testing.T) { diff --git a/cast/cast_test.go b/cast/cast_test.go index 5028fb1..30bbec9 100644 --- a/cast/cast_test.go +++ b/cast/cast_test.go @@ -3,7 +3,7 @@ package cast_test import ( "testing" - "github.com/alexchao26/advent-of-code-go/cast" + "github.com/Threnklyn/advent-of-code-go/cast" ) func TestToInt(t *testing.T) { diff --git a/data-structures/heap/heap_test.go b/data-structures/heap/heap_test.go index 5709f9c..b2c66df 100644 --- a/data-structures/heap/heap_test.go +++ b/data-structures/heap/heap_test.go @@ -3,7 +3,7 @@ package heap_test import ( "testing" - "github.com/alexchao26/advent-of-code-go/data-structures/heap" + "github.com/Threnklyn/advent-of-code-go/data-structures/heap" ) type mockNode int diff --git a/data-structures/set/set_test.go b/data-structures/set/set_test.go index 6cd8c1b..c8175ba 100644 --- a/data-structures/set/set_test.go +++ b/data-structures/set/set_test.go @@ -5,7 +5,7 @@ import ( "sort" "testing" - "github.com/alexchao26/advent-of-code-go/data-structures/set" + "github.com/Threnklyn/advent-of-code-go/data-structures/set" ) func TestIntSet(t *testing.T) { diff --git a/data-structures/slice/slice_test.go b/data-structures/slice/slice_test.go index b8de470..379d976 100644 --- a/data-structures/slice/slice_test.go +++ b/data-structures/slice/slice_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/alexchao26/advent-of-code-go/data-structures/slice" + "github.com/Threnklyn/advent-of-code-go/data-structures/slice" ) func TestDedupeStrings(t *testing.T) { diff --git a/go.mod b/go.mod index e564503..097674e 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/alexchao26/advent-of-code-go +module github.com/Threnklyn/advent-of-code-go go 1.16 diff --git a/halp/print-infinite-grid.go b/halp/print-infinite-grid.go index f05a5f3..fb8984c 100644 --- a/halp/print-infinite-grid.go +++ b/halp/print-infinite-grid.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/alexchao26/advent-of-code-go/mathy" + "github.com/Threnklyn/advent-of-code-go/mathy" ) // PrintInfiniteGridStrings supports the type map[[2]int]string, determines the diff --git a/learning/regexp-capture-groups.go b/learning/regexp-capture-groups.go index cedf6c1..306ee20 100644 --- a/learning/regexp-capture-groups.go +++ b/learning/regexp-capture-groups.go @@ -4,7 +4,7 @@ import ( "regexp" "strings" - "github.com/alexchao26/advent-of-code-go/cast" + "github.com/Threnklyn/advent-of-code-go/cast" ) func CaptureBingoBoard(board string) [][]int { diff --git a/scripts/aoc/aoc.go b/scripts/aoc/aoc.go index 6163225..c25ee66 100644 --- a/scripts/aoc/aoc.go +++ b/scripts/aoc/aoc.go @@ -64,7 +64,7 @@ func GetWithAOCCookie(url string, cookie string) []byte { // specific error message from AOC site if strings.HasPrefix(string(body), "Please don't repeatedly") { - log.Fatalf("Repeated request github.com/alexchao26/advent-of-code-go error") + log.Fatalf("Repeated request github.com/Threnklyn/advent-of-code-go error") } return body diff --git a/scripts/aoc/input.go b/scripts/aoc/input.go index 167b021..c0186fc 100644 --- a/scripts/aoc/input.go +++ b/scripts/aoc/input.go @@ -5,7 +5,7 @@ import ( "path/filepath" "strings" - "github.com/alexchao26/advent-of-code-go/util" + "github.com/Threnklyn/advent-of-code-go/util" ) func GetInput(day, year int, cookie string) { diff --git a/scripts/aoc/prompt.go b/scripts/aoc/prompt.go index 9e27884..e65ed8c 100644 --- a/scripts/aoc/prompt.go +++ b/scripts/aoc/prompt.go @@ -8,7 +8,7 @@ import ( "golang.org/x/net/html" - "github.com/alexchao26/advent-of-code-go/util" + "github.com/Threnklyn/advent-of-code-go/util" ) func GetPrompt(day, year int, cookie string) { diff --git a/scripts/cmd/input/main.go b/scripts/cmd/input/main.go index 5707710..78b561e 100644 --- a/scripts/cmd/input/main.go +++ b/scripts/cmd/input/main.go @@ -1,6 +1,6 @@ package main -import "github.com/alexchao26/advent-of-code-go/scripts/aoc" +import "github.com/Threnklyn/advent-of-code-go/scripts/aoc" func main() { day, year, cookie := aoc.ParseFlags() diff --git a/scripts/cmd/prompt/main.go b/scripts/cmd/prompt/main.go index 907694a..ce421d1 100644 --- a/scripts/cmd/prompt/main.go +++ b/scripts/cmd/prompt/main.go @@ -1,6 +1,6 @@ package main -import "github.com/alexchao26/advent-of-code-go/scripts/aoc" +import "github.com/Threnklyn/advent-of-code-go/scripts/aoc" func main() { day, year, cookie := aoc.ParseFlags() diff --git a/scripts/cmd/skeleton/main.go b/scripts/cmd/skeleton/main.go index 0a27bf0..2c73c77 100644 --- a/scripts/cmd/skeleton/main.go +++ b/scripts/cmd/skeleton/main.go @@ -4,7 +4,7 @@ import ( "flag" "time" - "github.com/alexchao26/advent-of-code-go/scripts/skeleton" + "github.com/Threnklyn/advent-of-code-go/scripts/skeleton" ) func main() { diff --git a/scripts/skeleton/skeleton.go b/scripts/skeleton/skeleton.go index 336cd9f..076dffb 100644 --- a/scripts/skeleton/skeleton.go +++ b/scripts/skeleton/skeleton.go @@ -9,7 +9,7 @@ import ( "path/filepath" "text/template" - "github.com/alexchao26/advent-of-code-go/util" + "github.com/Threnklyn/advent-of-code-go/util" ) //go:embed tmpls/*.go diff --git a/scripts/skeleton/tmpls/main.go b/scripts/skeleton/tmpls/main.go index bbb7f8a..dcba84f 100644 --- a/scripts/skeleton/tmpls/main.go +++ b/scripts/skeleton/tmpls/main.go @@ -6,8 +6,8 @@ import ( "fmt" "strings" - "github.com/alexchao26/advent-of-code-go/cast" - "github.com/alexchao26/advent-of-code-go/util" + "github.com/Threnklyn/advent-of-code-go/cast" + "github.com/Threnklyn/advent-of-code-go/util" ) //go:embed input.txt