From: Trent Huber Date: Wed, 17 Dec 2025 22:51:25 +0000 (-0500) Subject: Initial commit X-Git-Url: https://trenthuber.com/code?a=commitdiff_plain;h=f9ba7707dc6e53053a70121bded0933ddb28c53e;p=adventOfGo2025.git Initial commit --- f9ba7707dc6e53053a70121bded0933ddb28c53e diff --git a/01.go b/01.go new file mode 100644 index 0000000..6a896e4 --- /dev/null +++ b/01.go @@ -0,0 +1,93 @@ +// https://adventofcode.com/2025/day/1 + +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" +) + +func part1(scanner *bufio.Scanner) { + var pos, zeros int64 = 50, 0 + for scanner.Scan() { + rot := scanner.Bytes() + dir := rot[0] + num, _ := strconv.ParseInt(string(rot[1:]), 0, 0) + if dir == 'L' { + num = 100 - num + } + if pos = (pos + num) % 100; pos == 0 { + zeros++ + } + } + println(zeros) +} + +func part2(scanner *bufio.Scanner) { + var pos, zeros int64 = 50, 0 + for scanner.Scan() { + rot := scanner.Bytes() + dir := rot[0] + num, _ := strconv.ParseInt(string(rot[1:]), 0, 0) + zeros += num / 100 + num %= 100 + if dir == 'L' { + num *= -1 + } + if pos == 0 { + pos = (num + 100) % 100 + } else { + pos += num + if dir == 'L' { + if pos < 1 { + pos = (pos + 100) % 100 + zeros++ + } + } else { + if pos > 99 { + pos %= 100 + zeros++ + } + } + } + } + println(zeros) +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/02.go b/02.go new file mode 100644 index 0000000..ecf1cfd --- /dev/null +++ b/02.go @@ -0,0 +1,95 @@ +// https://adventofcode.com/2025/day/2 + +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +func part1(scanner *bufio.Scanner) { + scanner.Scan() + idRanges := strings.Split(scanner.Text(), ",") + total := 0 + for _, idRange := range idRanges { + ids := strings.Split(idRange, "-") + start, _ := strconv.Atoi(ids[0]) + end, _ := strconv.Atoi(ids[1]) + for i := start; i <= end; i++ { + idString := strconv.Itoa(i) + l := len(idString) / 2 + if idString[:l] == idString[l:] { + total += i + } + } + } + println(total) +} + +func part2(scanner *bufio.Scanner) { + scanner.Scan() + idRanges := strings.Split(scanner.Text(), ",") + total := 0 + for _, idRange := range idRanges { + ids := strings.Split(idRange, "-") + start, _ := strconv.Atoi(ids[0]) + end, _ := strconv.Atoi(ids[1]) + for i := start; i <= end; i++ { + idString := strconv.Itoa(i) + l := len(idString) + nextDivision: + for numDiv := 2; numDiv <= l; numDiv++ { + if l%numDiv == 0 { + subLen := l / numDiv + for j := 0; j < numDiv; j++ { + if idString[:subLen] != idString[j*subLen:(j+1)*subLen] { + continue nextDivision + } + } + total += i + break + } + } + } + } + println(total) +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/03.go b/03.go new file mode 100644 index 0000000..d8b6d9e --- /dev/null +++ b/03.go @@ -0,0 +1,117 @@ +// https://adventofcode.com/2025/day/3 + +package main + +import ( + "bufio" + "fmt" + "os" + "slices" +) + +func part1(scanner *bufio.Scanner) { + total := 0 + for scanner.Scan() { + digits := scanner.Bytes() + left, right := getMaxIndex(digits), 0 + if left == len(digits)-1 { + right = left + left = getMaxIndex(digits[:right]) + } else { + right = getMaxIndex(digits[left+1:]) + left + 1 + } + total += 10*int(digits[left]-byte('0')) + int(digits[right]-'0') + } + println(total) +} + +func getMaxIndex(digits []byte) int { + if len(digits) == 0 { + return -1 + } + var maxDigit byte = '0' + maxIndex := 0 + for i := 0; i < len(digits); i++ { + if digits[i] > maxDigit { + maxDigit = digits[i] + maxIndex = i + } + } + return maxIndex +} + +func part2(scanner *bufio.Scanner) { + total := 0 + for scanner.Scan() { + numLeft = 12 + digits := scanner.Bytes() + indices := getMaxIndices(digits) + slices.Sort(indices) + subtotal := 0 + for _, i := range indices { + subtotal *= 10 + subtotal += int(digits[i] - '0') + } + total += subtotal + } + println(total) +} + +var numLeft int + +func getMaxIndices(digits []byte) []int { + if numLeft == 0 { + return nil + } + i := getMaxIndex(digits) + if i == -1 { + return nil + } + numLeft-- + right := getMaxIndices(digits[i+1:]) + for j := range right { + right[j] += i + 1 + } + var left []int + if numLeft != 0 { + left = getMaxIndices(digits[:i]) + } + left = append(left, i) + return append(left, right...) +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/04.go b/04.go new file mode 100644 index 0000000..03b1dae --- /dev/null +++ b/04.go @@ -0,0 +1,106 @@ +// https://adventofcode.com/2025/day/4 + +package main + +import ( + "bufio" + "fmt" + "os" +) + +func part1(scanner *bufio.Scanner) { + var grid [][]byte + for scanner.Scan() { + line := scanner.Bytes() + row := make([]byte, len(line)) + copy(row, line) + grid = append(grid, row) + } + + println(len(getAccessableRolls(grid))) +} + +func part2(scanner *bufio.Scanner) { + var grid [][]byte + for scanner.Scan() { + line := scanner.Bytes() + row := make([]byte, len(line)) + copy(row, line) + grid = append(grid, row) + } + + total := 0 + for rolls := getAccessableRolls(grid); len(rolls) != 0; rolls = getAccessableRolls(grid) { + total += len(rolls) + for _, roll := range rolls { + grid[roll[0]][roll[1]] = byte('.') + } + } + println(total) +} + +func getAccessableRolls(grid [][]byte) (result [][]int) { + numRows, numCols, roll := len(grid), len(grid[0]), byte('@') + for row, gridRow := range grid { + for col, spot := range gridRow { + if spot != roll { + continue + } + numNeighbors := 0 + for i := -1; i <= 1; i++ { + if row+i == -1 || row+i == numRows { + continue + } + for j := -1; j <= 1; j++ { + if col+j == -1 || col+j == numCols { + continue + } + if grid[row+i][col+j] == roll { + numNeighbors++ + } + } + } + numNeighbors-- + if numNeighbors < 4 { + result = append(result, []int{row, col}) + } + } + } + return result +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/05.go b/05.go new file mode 100644 index 0000000..88d6ca8 --- /dev/null +++ b/05.go @@ -0,0 +1,107 @@ +// https://adventofcode.com/2025/day/5 + +package main + +import ( + "bufio" + "cmp" + "fmt" + "os" + "slices" + "strconv" + "strings" +) + +func part1(scanner *bufio.Scanner) { + var fresh [][2]int + + scanner.Scan() + for line := scanner.Text(); line != ""; line = scanner.Text() { + idRange := strings.Split(line, "-") + start, _ := strconv.Atoi(idRange[0]) + end, _ := strconv.Atoi(idRange[1]) + fresh = append(fresh, [2]int{start, end}) + scanner.Scan() + } + + total := 0 + for scanner.Scan() { + id, _ := strconv.Atoi(scanner.Text()) + for _, idRange := range fresh { + if id >= idRange[0] && id <= idRange[1] { + total++ + break + } + } + } + println(total) +} + +func part2(scanner *bufio.Scanner) { + var fresh [][2]int + + scanner.Scan() + for line := scanner.Text(); line != ""; line = scanner.Text() { + idRange := strings.Split(line, "-") + start, _ := strconv.Atoi(idRange[0]) + end, _ := strconv.Atoi(idRange[1]) + fresh = append(fresh, [2]int{start, end}) + scanner.Scan() + } + + slices.SortFunc(fresh, func(a, b [2]int) int { + return cmp.Compare(a[0], b[0]) + }) + + for i := 0; i < len(fresh)-1; i++ { + if fresh[i+1][0] <= fresh[i][1] { + fresh[i+1][0] = fresh[i][0] + if fresh[i+1][1] < fresh[i][1] { + fresh[i+1][1] = fresh[i][1] + } + fresh[i] = [2]int{1, 0} + } + } + + total := 0 + for _, idRange := range fresh { + total += idRange[1] - idRange[0] + 1 + } + println(total) +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/06.go b/06.go new file mode 100644 index 0000000..85efffd --- /dev/null +++ b/06.go @@ -0,0 +1,126 @@ +// https://adventofcode.com/2025/day/6 + +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +func part1(scanner *bufio.Scanner) { + var lines [][]string + for scanner.Scan() { + lines = append(lines, strings.Fields(scanner.Text())) + } + + ops, total := lines[len(lines)-1], 0 + for j, op := range ops { + var ( + opFunc func(int, int) int + subtotal int + ) + switch op { + case "+": + opFunc = func(a, b int) int { return a + b } + subtotal = 0 + case "*": + opFunc = func(a, b int) int { return a * b } + subtotal = 1 + } + + for i := 0; i < len(lines)-1; i++ { + num, _ := strconv.Atoi(lines[i][j]) + subtotal = opFunc(subtotal, num) + } + total += subtotal + } + println(total) +} + +func part2(scanner *bufio.Scanner) { + var lines [][]byte + for scanner.Scan() { + line := scanner.Bytes() + row := make([]byte, len(line)) + copy(row, line) + lines = append(lines, row) + } + + nums, index := [][]int{[]int{}}, 0 + for j := 0; j < len(lines[0]); j++ { + num := 0 + for i := 0; i < len(lines)-1; i++ { + if lines[i][j] != byte(' ') { + num *= 10 + num += int(lines[i][j] - byte('0')) + } + } + if num == 0 { + nums = append(nums, []int{}) + index++ + continue + } + nums[index] = append(nums[index], num) + } + + ops, total := strings.Fields(string(lines[len(lines)-1])), 0 + for j, op := range ops { + var ( + opFunc func(int, int) int + subtotal int + ) + switch op { + case "+": + opFunc = func(a, b int) int { return a + b } + subtotal = 0 + case "*": + opFunc = func(a, b int) int { return a * b } + subtotal = 1 + } + + for _, num := range nums[j] { + subtotal = opFunc(subtotal, num) + } + total += subtotal + } + println(total) +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/07.go b/07.go new file mode 100644 index 0000000..ae93b79 --- /dev/null +++ b/07.go @@ -0,0 +1,101 @@ +// https://adventofcode.com/2025/day/7 + +package main + +import ( + "bufio" + "bytes" + "fmt" + "os" +) + +func part1(scanner *bufio.Scanner) { + for scanner.Scan() { + line := scanner.Bytes() + row := make([]byte, len(line)) + copy(row, line) + grid = append(grid, row) + } + + println(countSpliters(1, bytes.Index(grid[0], []byte{byte('S')}))) +} + +var grid [][]byte + +func countSpliters(i, j int) int { + for ; i < len(grid); i++ { + if grid[i][j] == byte('|') { + return 0 + } + if grid[i][j] == byte('^') { + return countSpliters(i, j-1) + countSpliters(i, j+1) + 1 + } + grid[i][j] = byte('|') + } + return 0 +} + +func part2(scanner *bufio.Scanner) { + for scanner.Scan() { + line := scanner.Bytes() + row := make([]byte, len(line)) + copy(row, line) + grid = append(grid, row) + } + cache = make([][]int, len(grid)) + for i := range cache { + cache[i] = make([]int, len(grid[0])) + } + + println(countTimelines(1, bytes.Index(grid[0], []byte{byte('S')}))) +} + +var cache [][]int + +func countTimelines(i, j int) int { + for ; i < len(grid); i++ { + if grid[i][j] == byte('^') { + if cache[i][j] == 0 { + cache[i][j] = countTimelines(i, j-1) + countTimelines(i, j+1) + } + return cache[i][j] + } + } + return 1 +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/08.go b/08.go new file mode 100644 index 0000000..53c1eca --- /dev/null +++ b/08.go @@ -0,0 +1,137 @@ +// https://adventofcode.com/2025/day/8 + +package main + +import ( + "bufio" + "cmp" + "fmt" + "os" + "slices" + "strconv" + "strings" +) + +func part1(scanner *bufio.Scanner) { + _, cables, adj := getData(scanner) + + for _, c := range cables[:numConnections] { + adj[c.i][c.j], adj[c.j][c.i] = 1, 1 + } + + total := 1 + for i, sizes := 0, getCompSizes(adj); i < 3; i++ { + total *= sizes[i] + } + println(total) +} + +func part2(scanner *bufio.Scanner) { + vecs, cables, adj := getData(scanner) + + c := 0 + for numComps := len(vecs); c < len(cables); { + for n := 0; n < numComps-1; n, c = n+1, c+1 { + adj[cables[c].i][cables[c].j], adj[cables[c].j][cables[c].i] = 1, 1 + } + if numComps = len(getCompSizes(adj)); numComps == 1 { + break + } + } + println(vecs[cables[c-1].i][0] * vecs[cables[c-1].j][0]) +} + +type cable struct { + i, j, d int +} + +func getData(scanner *bufio.Scanner) (vecs [][3]int, cables []cable, adj [][]int) { + for scanner.Scan() { + var vec [3]int + for i, num := range strings.Split(scanner.Text(), ",") { + vec[i], _ = strconv.Atoi(num) + } + vecs = append(vecs, vec) + } + + for i := 0; i < len(vecs); i++ { + for j := i + 1; j < len(vecs); j++ { + dx, dy, dz := vecs[j][0]-vecs[i][0], vecs[j][1]-vecs[i][1], vecs[j][2]-vecs[i][2] + cables = append(cables, cable{i, j, dx*dx + dy*dy + dz*dz}) + } + } + slices.SortFunc(cables, func(a, b cable) int { + return cmp.Compare(a.d, b.d) + }) + + adj = make([][]int, len(vecs)) + for i := range adj { + adj[i] = make([]int, len(vecs)) + } + + return +} + +func getCompSizes(adj [][]int) (sizes []int) { + for i, checklist, queue := 0, make([]int, len(adj)), []int{}; i < len(adj); i++ { + if checklist[i] == 1 { + continue + } + checklist[i] = 1 + queue = append(queue, i) + for n := 0; n < len(queue); n++ { + for j := 0; j < len(adj); j++ { + if adj[queue[n]][j] == 1 && checklist[j] == 0 { + checklist[j] = 1 + queue = append(queue, j) + } + } + } + sizes = append(sizes, len(queue)) + queue = nil + } + slices.SortFunc(sizes, func(a, b int) int { + return cmp.Compare(b, a) + }) + return +} + +var numConnections int + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + numConnections = 1000 + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + numConnections = 10 + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/09.go b/09.go new file mode 100644 index 0000000..3e8c020 --- /dev/null +++ b/09.go @@ -0,0 +1,114 @@ +// https://adventofcode.com/2025/day/9 + +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +func part1(scanner *bufio.Scanner) { + getMax(scanner, func(r rect, tiles []tile) bool { + return true + }) +} + +func part2(scanner *bufio.Scanner) { + getMax(scanner, func(r rect, tiles []tile) bool { + for i := range tiles { + bitcode := func(r rect, p tile) (b byte) { + if p.x <= r.min.x { + b |= 0b1000 + } else if p.x >= r.max.x { + b |= 0b0100 + } + if p.y <= r.min.y { + b |= 0b0010 + } else if p.y >= r.max.y { + b |= 0b0001 + } + return + } + if bitcode(r, tiles[i])&bitcode(r, tiles[(i+1)%len(tiles)]) == 0 { + return false + } + } + return true + }) +} + +type tile struct { + x, y int +} + +type rect struct { + min, max tile +} + +func getMax(scanner *bufio.Scanner, valid func(r rect, tiles []tile) bool) { + var tiles []tile + for scanner.Scan() { + coords := strings.Split(scanner.Text(), ",") + var t tile + t.x, _ = strconv.Atoi(coords[0]) + t.y, _ = strconv.Atoi(coords[1]) + tiles = append(tiles, t) + } + + max := 0 + for i := 0; i < len(tiles); i++ { + for j := i + 1; j < len(tiles); j++ { + r := rect{tiles[i], tiles[j]} + if r.min.x > r.max.x { + r.min.x, r.max.x = r.max.x, r.min.x + } + if r.min.y > r.max.y { + r.min.y, r.max.y = r.max.y, r.min.y + } + area := (r.max.x - r.min.x + 1) * (r.max.y - r.min.y + 1) + if area > max && valid(r, tiles) { + max = area + } + } + } + println(max) +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/10.go b/10.go new file mode 100644 index 0000000..50d1971 --- /dev/null +++ b/10.go @@ -0,0 +1,168 @@ +// https://adventofcode.com/2025/day/10 + +// NOTE: Part 2 runs too slow to check for correctness + +package main + +import ( + "bufio" + "fmt" + "math" + "os" + "slices" + "strconv" + "strings" +) + +func part1(scanner *bufio.Scanner) { + total := 0 + for scanner.Scan() { + tokens := strings.Split(scanner.Text(), " ") + + lights := tokens[0][1 : len(tokens[0])-1] + var goal uint16 + for i := range lights { + goal <<= 1 + if lights[len(lights)-i-1] == '#' { + goal |= 1 + } + } + + schemes := tokens[1 : len(tokens)-1] + buttons := make([]uint16, len(schemes)) + for i, scheme := range schemes { + digits := strings.Split(scheme[1:len(scheme)-1], ",") + for _, digit := range digits { + n, _ := strconv.Atoi(digit) + buttons[i] |= 1 << n + } + } + + for i := 1; i <= len(buttons); i++ { + if configureLights(goal, buttons, i) { + total += i + break + } + } + } + println(total) +} + +func configureLights(goal uint16, buttons []uint16, depth int) bool { + if goal == 0 { + return true + } + if depth == 0 { + return false + } + for i := range buttons { + temp := make([]uint16, len(buttons)) + copy(temp, buttons) + slices.Delete(temp, i, i+1) + temp = temp[:len(temp)-1] + if configureLights(goal^buttons[i], temp, depth-1) { + return true + } + } + return false +} + +func part2(scanner *bufio.Scanner) { + total := 0 + for scanner.Scan() { + tokens := strings.Split(scanner.Text(), " ") + + goalToken := tokens[len(tokens)-1] + var goal []int + for _, digit := range strings.Split(goalToken[1:len(goalToken)-1], ",") { + n, _ := strconv.Atoi(digit) + goal = append(goal, n) + } + + buttonTokens := tokens[1 : len(tokens)-1] + buttons := make([][]int, len(buttonTokens)) + for i, buttonToken := range buttonTokens { + buttons[i] = make([]int, len(goal)) + for _, digit := range strings.Split(buttonToken[1:len(buttonToken)-1], ",") { + n, _ := strconv.Atoi(digit) + buttons[i][n] = 1 + } + } + + total += distanceFromOrigin(goal, buttons) + } + println(total) +} + +func distanceFromOrigin(position []int, buttons [][]int) (distance int) { + if len(buttons) == 0 { + return -1 + } + + m := math.MaxInt + for i, n := range buttons[0] { + if n == 1 && position[i] < m { + m = position[i] + } + } + for i := range buttons[0] { + position[i] -= buttons[0][i] * m + } + if slices.ContainsFunc(position, func(a int) bool { return a != 0 }) { + distance = -1 + for i := m; i >= 0; i-- { + d := distanceFromOrigin(position, buttons[1:]) + if d != -1 && (distance == -1 || i+d < distance) { + distance = i + d + } + for j := range buttons[0] { + position[j] += buttons[0][j] + } + } + for i := range buttons[0] { + position[i] -= buttons[0][i] + } + } else { + distance = m + for i := range buttons[0] { + position[i] += buttons[0][i] * m + } + } + return +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/11.go b/11.go new file mode 100644 index 0000000..ba18ac2 --- /dev/null +++ b/11.go @@ -0,0 +1,93 @@ +// https://adventofcode.com/2025/day/11 + +package main + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +func part1(scanner *bufio.Scanner) { + initDevices(scanner) + + println(numPaths("you", "out", make(map[string]int))) +} + +func part2(scanner *bufio.Scanner) { + initDevices(scanner) + + a, b := "fft", "dac" + middle := numPaths(a, b, make(map[string]int)) + if middle == 0 { + a, b = b, a + middle = numPaths(a, b, make(map[string]int)) + } + println(numPaths("svr", a, make(map[string]int)) * + middle * numPaths(b, "out", make(map[string]int))) +} + +var devices map[string][]string + +func initDevices(scanner *bufio.Scanner) { + devices = make(map[string][]string) + for scanner.Scan() { + names := strings.Split(scanner.Text(), ":") + devices[names[0]] = strings.Split(names[1], " ")[1:] + } +} + +func numPaths(start, end string, cache map[string]int) int { + if start == end { + return 1 + } + if _, exists := cache[start]; !exists { + for _, output := range devices[start] { + cache[start] += numPaths(output, end, cache) + } + } + return cache[start] +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] != "test" { + usage() + } + switch os.Args[1] { + case "1": + filename = "test1.txt" + case "2": + filename = "test2.txt" + default: + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/12.go b/12.go new file mode 100644 index 0000000..15326d1 --- /dev/null +++ b/12.go @@ -0,0 +1,205 @@ +// https://adventofcode.com/2025/day/12 + +// NOTE: Part 1 runs too slow to check for correctness, and so Part 2 remains inaccessable to me. + +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +func part1(scanner *bufio.Scanner) { + total := 0 + for scanner.Scan() { + line := scanner.Text() + if line[len(line)-1] == ':' { + var s [3][3]bool + for i := 0; ; i++ { + scanner.Scan() + line = scanner.Text() + if len(line) == 0 { + break + } + + for j, c := range line { + if c == '#' { + s[i][j] = true + } + } + } + + var syms [8][3][3]bool + for rot := 0; rot < 4; rot++ { + for refl := 0; refl < 2; refl++ { + syms[2*rot+refl] = s + for i := range s { + s[i][0], s[i][2] = s[i][2], s[i][0] + } + } + temp := s + for i := range s { + for j := range s[0] { + s[i][j] = temp[2-j][i] + } + } + } + + var shape [][3]uint + nextShape: + for i := 0; i < 8; i++ { + nextPair: + for j := i + 1; j < 8; j++ { + for r := range syms[i] { + for c := range syms[i][r] { + if syms[i][r][c] != syms[j][r][c] { + continue nextPair + } + } + } + continue nextShape + } + + var data [3]uint + for r, row := range syms[i] { + for _, item := range row { + data[r] <<= 1 + if item { + data[r]++ + } + } + temp := data[r] + for i := 0; i < 16; i++ { + data[r] <<= 3 + data[r] |= temp + } + } + shape = append(shape, data) + } + shapes = append(shapes, shape) + } else { + parts := strings.Split(line, ":") + var dimensions, indices []uint8 + for _, value := range strings.Split(parts[0], "x") { + n, _ := strconv.Atoi(value) + dimensions = append(dimensions, uint8(n)) + } + data := make([]uint, dimensions[1]) + for _, value := range strings.Split(parts[1], " ")[1:] { + n, _ := strconv.Atoi(value) + indices = append(indices, uint8(n)) + } + region := region{dimensions[0], data, indices} + + if canFillRegion(region) { + total++ + } + } + } + println(total) +} + +var shapes [][][3]uint + +type region struct { + width uint8 + data []uint + indices []uint8 +} + +func canFillRegion(r region) bool { + var n int + for n = 0; n < len(r.indices); n++ { + if r.indices[n] != 0 { + break + } + } + if n == len(r.indices) { + return true + } + + r.indices[n]-- + for h := 0; h <= len(r.data)-3; h++ { + for _, shape := range shapes[n] { + var invalids [3]uint + for s := 0; s < 3; s++ { + invalids[s] = shape[0]&r.data[h] | shape[1]&r.data[h+1] | shape[2]&r.data[h+2] + shape[0] <<= 1 + shape[1] <<= 1 + shape[2] <<= 1 + } + shape[0] >>= 3 + shape[1] >>= 3 + shape[2] >>= 3 + + mask := uint(7) + for w, n := 0, 0; w <= int(r.width)-3; w, n = w+1, n+1 { + if mask&invalids[n] == 0 { + r.data[h] ^= shape[0] << w & mask + r.data[h+1] ^= shape[1] << w & mask + r.data[h+2] ^= shape[2] << w & mask + + if canFillRegion(r) { + return true + } + + r.data[h] ^= shape[0] << w & mask + r.data[h+1] ^= shape[1] << w & mask + r.data[h+2] ^= shape[2] << w & mask + } + mask <<= 1 + if n == 2 { + n = -1 + } + } + } + } + r.indices[n]++ + + return false +} + +func part2(scanner *bufio.Scanner) { + for scanner.Scan() { + println(scanner.Text()) + } +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4236baa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Trent Huber + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..45bf2be --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Advent of Go 2025 + +Completing [this year's Advent of Code](https://adventofcode.com/2025/) to the +best of my ability entirely in [Go](https://go.dev/doc/). Day 10, Part 2 and Day +12, Part 1 remain unable to be checked for correctness since my code runs too +slow. Day 12, Part 2 remains unimplemented since I couldn't complete Part 1 for +that day. + +## Building + +To build a specific file, just `go build` it. The resulting executable expects +to take input from a file named `input.txt` located in the same directory as +you're running the executable in. To specify whether you'd like to process that +input according to the first or second part of that day, you have to pass either +a `1` or a `2` on the command line. Additionally, since each day comes with an +example input used for testing, you can optionally pass `test` at the end of the +command line to take input from `test.txt` instead, which is where I would paste +the test input copied directly from the webpage. + +If you find this code at all useful, I've included a template, `template.go`, +that can be used for any other Advent of Code challenges. diff --git a/template.go b/template.go new file mode 100644 index 0000000..ecec109 --- /dev/null +++ b/template.go @@ -0,0 +1,55 @@ +package main + +import ( + "bufio" + "fmt" + "os" +) + +func part1(scanner *bufio.Scanner) { + for scanner.Scan() { + println(scanner.Text()) + } +} + +func part2(scanner *bufio.Scanner) { + for scanner.Scan() { + println(scanner.Text()) + } +} + +func main() { + var part func(*bufio.Scanner) + filename := "input.txt" + usage := func() { + fmt.Printf("usage: %v {1|2} [test]\n", os.Args[0]) + os.Exit(1) + } + switch len(os.Args) { + case 3: + if os.Args[2] == "test" { + filename = "test.txt" + } else { + usage() + } + fallthrough + case 2: + switch os.Args[1] { + case "1": + part = part1 + case "2": + part = part2 + default: + usage() + } + case 1: + usage() + } + file, err := os.Open(filename) + if err != nil { + fmt.Printf("Unable to open %v\n", filename) + os.Exit(1) + } + defer file.Close() + part(bufio.NewScanner(file)) +}