This page covers all the solutions I wrote for Advent of Code 2023 problems; they are all in Go and should be self-contained, expecting the input to be in 'input'. Occasionally I do some input preprocessing; I try to note it in the file if possible.
Syntax highlighting courtesy of codehost.
2023 Day 1a
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
ttl := 0
for s.Scan() {
first := 0
last := 0
for _, c := range s.Text() {
if c >= '0' && c <= '9' {
if first == 0 {
first = int(c - '0')
}
last = int(c - '0')
}
}
ttl += first*10 + last
}
fmt.Println(ttl)
}
2023 Day 1b
package main
import (
"bufio"
"fmt"
"os"
)
var (
digits = map[string]int{"one": 1, "two": 2, "three": 3, "four": 4,
"five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9}
)
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
ttl := 0
for s.Scan() {
first := 0
last := 0
buf := ""
off := 0
for _, c := range s.Text() {
if c >= '0' && c <= '9' {
if first == 0 {
first = int(c - '0')
}
last = int(c - '0')
buf = ""
off = 0
} else {
buf += string(c)
for i := off; i < len(buf); i++ {
if v, ok := digits[buf[i:]]; ok {
if first == 0 {
first = v
}
last = v
off = i
break
}
}
}
}
ttl += first*10 + last
}
fmt.Println(ttl)
}
2023 Day 2a
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
idx := 0
ttl := 0
for s.Scan() {
idx++
bad := false
for _, game := range strings.Split(s.Text(), ";") {
r := 0
g := 0
b := 0
for _, v := range strings.Split(game, ",") {
if s, f := cutSuffix(v, "red"); f {
r += numberOrDie(s)
} else if s, f := cutSuffix(v, "blue"); f {
b += numberOrDie(s)
} else if s, f := cutSuffix(v, "green"); f {
g += numberOrDie(s)
} else {
panic(fmt.Errorf("bad string seg %q", v))
}
}
if r > 12 || g > 13 || b > 14 {
bad = true
break
}
}
if !bad {
ttl += idx
}
}
fmt.Println(ttl)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
// added in go1.20, but i'm still running go 1.19
func cutSuffix(s string, suffix string) (string, bool) {
if strings.HasSuffix(s, suffix) {
return s[:len(s)-len(suffix)], true
}
return s, false
}
2023 Day 2b
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
ttl := 0
for s.Scan() {
rm := 0
gm := 0
bm := 0
for _, game := range strings.Split(s.Text(), ";") {
r := 0
g := 0
b := 0
for _, v := range strings.Split(game, ",") {
if s, f := cutSuffix(v, "red"); f {
r += numberOrDie(s)
} else if s, f := cutSuffix(v, "blue"); f {
b += numberOrDie(s)
} else if s, f := cutSuffix(v, "green"); f {
g += numberOrDie(s)
} else {
panic(fmt.Errorf("bad string seg %q", v))
}
}
if r > rm {
rm = r
}
if g > gm {
gm = g
}
if b > bm {
bm = b
}
}
ttl += rm*gm*bm
}
fmt.Println(ttl)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
// added in go1.20, but i'm still running go 1.19
func cutSuffix(s string, suffix string) (string, bool) {
if strings.HasSuffix(s, suffix) {
return s[:len(s)-len(suffix)], true
}
return s, false
}
2023 Day 3a
// an additional column of period characters was added to the input.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
ttl := 0
npttl := 0
num := ""
schematic := []string{}
for s.Scan() {
schematic = append(schematic, s.Text())
}
for i, row := range schematic {
for j, c := range row {
if c >= '0' && c <= '9' {
num += string(c)
} else if num != "" {
cv, err := strconv.Atoi(num)
if err != nil {
panic(err)
}
ttl += cv
part := false
for v := max(0, i-1); v <= min(len(schematic)-1, i+1); v++ {
for w := max(0, j-len(num)-1); w <= min(len(row)-1, j); w++ {
if (schematic[v][w] < '0' || schematic[v][w] > '9') && schematic[v][w] != '.' {
part = true
}
}
}
if !part {
npttl += cv
}
num = ""
}
}
}
fmt.Println(ttl - npttl)
}
// apparently with go1.21 these are no longer necessary! maybe i really should
// upgrade.
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
2023 Day 3b
// an additional column of period characters was added to the input.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
type id struct {
x int
y int
val int
}
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
ttl := 0
num := ""
numfinder := map[int]map[int]*id{}
gears := [][]int{}
row := 0
for s.Scan() {
sy := row
sx := 0
numfinder[row] = map[int]*id{}
gears = append(gears, []int{})
for j, c := range s.Text() {
if c >= '0' && c <= '9' {
if num == "" {
sx = j
}
num += string(c)
} else if num != "" {
cv, err := strconv.Atoi(num)
if err != nil {
panic(err)
}
nid := &id{
x: sx,
y: sy,
val: cv,
}
for iv := sx; iv < j; iv++ {
numfinder[row][iv] = nid
}
num = ""
}
if c == '*' {
gears[row] = append(gears[row], j)
}
}
row++
}
for i, r := range gears {
for _, j := range r {
gl := map[id]struct{}{}
for v := max(i-1, 0); v <= min(i+1, len(gears)); v++ {
for w := max(j-1, 0); w <= j+1; w++ {
if nid, ok := numfinder[v][w]; ok {
gl[*nid] = struct{}{}
}
}
}
if len(gl) == 2 {
st := 1
for v, _ := range gl {
st *= v.val
}
ttl += st
}
}
}
fmt.Println(ttl)
}
// apparently with go1.21 these are no longer necessary! maybe i really should
// upgrade.
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
2023 Day 4a
// "game ###" prefix removed. duplicate spaces replaced with a single space.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
ttl := 0
for s.Scan() {
winners := map[int]struct{}{}
count := 0
card := strings.Split(s.Text(), "|")
if len(card) != 2 {
panic(fmt.Errorf("couldn't parse card: %q", s.Text()))
}
for _, num := range strings.Split(strings.TrimSpace(card[0]), " ") {
winners[numberOrDie(num)] = struct{}{}
}
for _, num := range strings.Split(strings.TrimSpace(card[1]), " ") {
if _, ok := winners[numberOrDie(num)]; ok {
count++
}
}
if count > 0 {
ttl += 1 << (count - 1)
}
}
fmt.Println(ttl)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
2023 Day 4b
// "game ###" prefix removed. duplicate spaces replaced with a single space.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
type cc struct {
winners int
cards int
}
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
ttl := 0
points := []*cc{}
for s.Scan() {
winners := map[int]struct{}{}
count := 0
card := strings.Split(s.Text(), "|")
if len(card) != 2 {
panic(fmt.Errorf("couldn't parse card: %q", s.Text()))
}
for _, num := range strings.Split(strings.TrimSpace(card[0]), " ") {
winners[numberOrDie(num)] = struct{}{}
}
for _, num := range strings.Split(strings.TrimSpace(card[1]), " ") {
if _, ok := winners[numberOrDie(num)]; ok {
count++
}
}
points = append(points, &cc{count, 1})
}
for i, v := range points {
for j := 0; j < v.winners; j++ {
points[i+j+1].cards += v.cards
}
ttl += v.cards
}
fmt.Println(ttl)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
2023 Day 5a
// all non-number text removed from file.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
type rmap struct {
drs int
srs int
rl int
}
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
minloc := 99999999999999999
s.Scan()
seeds := []int{}
for _, s := range strings.Split(s.Text(), " ") {
seeds = append(seeds, numberOrDie(s))
}
s.Scan()
catmap := [][]*rmap{[]*rmap{}}
i := 0
j := 0
for s.Scan() {
if s.Text() == "" {
catmap = append(catmap, []*rmap{})
i++
j = 0
continue
}
data := strings.Split(s.Text(), " ")
if len(data) != 3 {
panic(fmt.Errorf("bad line %q", s.Text()))
}
catmap[i] = append(catmap[i], &rmap{
drs: numberOrDie(data[0]),
srs: numberOrDie(data[1]),
rl: numberOrDie(data[2]),
})
j++
}
for _, s := range seeds {
for _, ms := range catmap {
for _, m := range ms {
if s >= m.srs && s < m.srs+m.rl {
s = s - m.srs + m.drs
break
}
}
}
if s < minloc {
minloc = s
}
}
fmt.Println(minloc)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
2023 Day 5b
// all non-number text removed from file.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
type rng struct {
start int
ln int
}
type rmap struct {
drs int
srs int
rl int
}
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
minloc := 99999999999999999
s.Scan()
seeds := []int{}
for _, s := range strings.Split(s.Text(), " ") {
seeds = append(seeds, numberOrDie(s))
}
seedRanges := []*rng{}
for i := 0; i < len(seeds); i += 2 {
seedRanges = append(seedRanges, &rng{seeds[i],seeds[i+1]})
}
s.Scan()
catmap := [][]*rmap{[]*rmap{}}
i := 0
j := 0
for s.Scan() {
if s.Text() == "" {
catmap = append(catmap, []*rmap{})
i++
j = 0
continue
}
data := strings.Split(s.Text(), " ")
if len(data) != 3 {
panic(fmt.Errorf("bad line %q", s.Text()))
}
catmap[i] = append(catmap[i], &rmap{
drs: numberOrDie(data[0]),
srs: numberOrDie(data[1]),
rl: numberOrDie(data[2]),
})
j++
}
for _, r := range seedRanges {
ranges := []*rng{r}
for _, ms := range catmap {
nr := []*rng{}
for i := 0; i < len(ranges); i++ {
rs := ranges[i]
match := false
for _, m := range ms {
if rs.start < m.srs && rs.start+rs.ln > m.srs {
match = true
ranges = append(ranges, &rng{rs.start,m.srs-rs.start})
fmt.Printf("presplit %+v\n", rs)
rs = &rng{m.srs,rs.start+rs.ln-m.srs}
fmt.Printf("presplit %v %v %v\n", m, ranges[len(ranges)-1],rs)
}
if rs.start >= m.srs && rs.start < m.srs+m.rl {
match = true
rm := rs.start-m.srs+m.drs
rl := rs.ln
if rs.start + rs.ln > m.srs+m.rl {
rl = m.srs+m.rl-rs.start
fmt.Printf("split %v %v %d %d\n", rs, m, rm, rl)
ranges = append(ranges, &rng{m.srs+m.rl,rs.start+rs.ln-m.srs-m.rl})
fmt.Printf("nr: %v\n", ranges[len(ranges)-1])
}
nr = append(nr, &rng{rm,rl})
}
if match {
break
}
}
if !match {
nr = append(nr, rs)
}
}
ranges = nr
}
for _, r := range ranges {
if r.start < minloc {
minloc = r.start
}
}
}
fmt.Println(minloc)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
2023 Day 6a
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
dur := []int{}
dist := []int{}
ttl := 1
s.Scan()
for _, d := range strings.Split(s.Text(), " ") {
dur = append(dur, numberOrDie(d))
}
s.Scan()
for _, d := range strings.Split(s.Text(), " ") {
dist = append(dist, numberOrDie(d))
}
for idx, d := range dur {
poss := 0
for i := 0; i < d; i++ {
dc := i*(d-i)
if dc > dist[idx] {
poss++
}
}
ttl *= poss
}
fmt.Println(ttl)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
2023 Day 6b
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
dur := []int{}
dist := []int{}
s.Scan()
for _, d := range strings.Split(s.Text(), " ") {
dur = append(dur, numberOrDie(d))
}
s.Scan()
for _, d := range strings.Split(s.Text(), " ") {
dist = append(dist, numberOrDie(d))
}
poss := 0
for idx, d := range dur {
for i := 0; i < d; i++ {
dc := i*(d-i)
if dc > dist[idx] {
poss++
}
}
}
fmt.Println(poss)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
2023 Day 7a
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
"strings"
)
var (
order = map[string]int{
"2": 2,
"3": 3,
"4": 4,
"5": 5,
"6": 6,
"7": 7,
"8": 8,
"9": 9,
"T": 10,
"J": 11,
"Q": 12,
"K": 13,
"A": 14,
}
)
type hand struct {
deal string
bid int
sorted map[string]int
}
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
ttl := 0
s := bufio.NewScanner(f)
hands := []*hand{}
for s.Scan() {
in := strings.Split(s.Text(), " ")
if len(in) != 2 {
panic(fmt.Errorf("bad input %q", in))
}
h := &hand{
deal: in[0],
bid: numberOrDie(in[1]),
sorted: map[string]int{},
}
for _, c := range h.deal {
h.sorted[string(c)]++
}
hands = append(hands, h)
}
sort.Slice(hands, func(i, j int) bool {
is := score(hands[i])
js := score(hands[j])
if is != js {
return is < js
}
for v := 0; v < 5; v++ {
diff := order[string(hands[j].deal[v])] - order[string(hands[i].deal[v])]
if diff != 0 {
return diff > 0
}
}
panic(fmt.Errorf("couldn't handle: %+v %+v", hands[i], hands[j]))
})
for i, h := range hands {
ttl += (i+1)*h.bid
}
fmt.Println(ttl)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
func score(h *hand) int {
if len(h.sorted) == 1 {
return 7;
}
if len(h.sorted) == 2 {
for _, v := range h.sorted {
if v == 4 {
return 6;
}
}
return 5;
}
if len(h.sorted) == 3 {
for _, v := range h.sorted {
if v == 3 {
return 4;
}
}
return 3;
}
if len(h.sorted) == 4 {
return 2;
}
return 1;
}
2023 Day 7b
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
"strings"
)
var (
order = map[string]int{
"2": 2,
"3": 3,
"4": 4,
"5": 5,
"6": 6,
"7": 7,
"8": 8,
"9": 9,
"T": 10,
"J": 11,
"Q": 12,
"K": 13,
"A": 14,
}
)
type hand struct {
deal string
bid int
sorted map[string]int
}
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
ttl := 0
s := bufio.NewScanner(f)
hands := []*hand{}
for s.Scan() {
in := strings.Split(s.Text(), " ")
if len(in) != 2 {
panic(fmt.Errorf("bad input %q", in))
}
h := &hand{
deal: in[0],
bid: numberOrDie(in[1]),
sorted: map[string]int{},
}
for _, c := range h.deal {
h.sorted[string(c)]++
}
hands = append(hands, h)
}
sort.Slice(hands, func(i, j int) bool {
is := score(hands[i])
js := score(hands[j])
if is != js {
return is < js
}
for v := 0; v < 5; v++ {
diff := order[string(hands[j].deal[v])] - order[string(hands[i].deal[v])]
if diff != 0 {
return diff > 0
}
}
panic(fmt.Errorf("couldn't handle: %+v %+v", hands[i], hands[j]))
})
for i, h := range hands {
ttl += (i+1)*h.bid
}
fmt.Println(ttl)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
func score(h *hand) int {
if len(h.sorted) == 1 {
return 7;
}
if len(h.sorted) == 2 {
for _, v := range h.sorted {
if v == 4 {
return 6;
}
}
return 5;
}
if len(h.sorted) == 3 {
for _, v := range h.sorted {
if v == 3 {
return 4;
}
}
return 3;
}
if len(h.sorted) == 4 {
return 2;
}
return 1;
}
2023 Day 8a
package main
import (
"bufio"
"fmt"
"os"
)
type next struct {
l string
r string
}
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
steps := 0
ways := map[string]*next{}
pos := "AAA"
idx := 0
s.Scan()
path := s.Text()
s.Scan()
for s.Scan() {
ways[s.Text()[0:3]] = &next{s.Text()[7:10], s.Text()[12:15]}
}
for pos != "ZZZ" {
next := path[idx]
if next == 'L' {
pos = ways[pos].l
} else {
pos = ways[pos].r
}
idx = (idx + 1) % len(path)
steps++
}
fmt.Println(steps)
}
2023 Day 8b
// just cut the input off after 20 lines or so and find the LCM of each
// individual position cycle using wolfram alpha or something.
package main
import (
"bufio"
"fmt"
"os"
)
type next struct {
l string
r string
}
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
steps := 0
ways := map[string]*next{}
pos := []string{}
idx := 0
s.Scan()
path := s.Text()
s.Scan()
for s.Scan() {
ways[s.Text()[0:3]] = &next{s.Text()[7:10], s.Text()[12:15]}
if s.Text()[2] == 'A' {
pos = append(pos, s.Text()[0:3])
}
}
done := false
for !done {
done = true
nxt := path[idx]
for i := 0; i < len(pos); i++ {
if nxt == 'L' {
pos[i] = ways[pos[i]].l
} else {
pos[i] = ways[pos[i]].r
}
if pos[i][2] != 'Z' {
done = false
} else {
fmt.Printf("%d done processing step %d.\n", i, steps+1)
}
}
idx = (idx + 1) % len(path)
steps++
}
fmt.Println(steps)
}
2023 Day 9a
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
ttl := 0
for s.Scan() {
oasis := [][]int{[]int{}}
input := strings.Split(s.Text(), " ")
for _, i := range input {
oasis[0] = append(oasis[0], numberOrDie(i))
}
done := false
for !done {
done = true
oasis = append(oasis, []int{})
for i := 0; i < len(oasis[len(oasis)-2]) - 1; i++ {
next := oasis[len(oasis)-2][i+1]-oasis[len(oasis)-2][i]
if next != 0 {
done = false
}
oasis[len(oasis)-1] = append(oasis[len(oasis)-1], next)
}
}
oasis[len(oasis)-1] = append(oasis[len(oasis)-1], 0)
for i := len(oasis)-2; i >= 0; i-- {
oasis[i] = append(oasis[i], oasis[i][len(oasis[i])-1]+oasis[i+1][len(oasis[i+1])-1])
}
ttl += oasis[0][len(oasis[0])-1]
}
fmt.Println(ttl)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
2023 Day 9b
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
f, err := os.Open("input")
if err != nil {
panic(err)
}
s := bufio.NewScanner(f)
ttl := 0
for s.Scan() {
oasis := [][]int{[]int{}}
input := strings.Split(s.Text(), " ")
for _, i := range input {
oasis[0] = append(oasis[0], numberOrDie(i))
}
done := false
for !done {
done = true
oasis = append(oasis, []int{})
for i := 0; i < len(oasis[len(oasis)-2]) - 1; i++ {
next := oasis[len(oasis)-2][i+1]-oasis[len(oasis)-2][i]
if next != 0 {
done = false
}
oasis[len(oasis)-1] = append(oasis[len(oasis)-1], next)
}
}
oasis[len(oasis)-1] = append([]int{0}, oasis[len(oasis)-1]...)
for i := len(oasis)-2; i >= 0; i-- {
oasis[i] = append([]int{oasis[i][0]-oasis[i+1][0]}, oasis[i]...)
}
ttl += oasis[0][0]
}
fmt.Println(ttl)
}
func numberOrDie(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}