tangled
alpha
login
or
join now
evan.jarrett.net
/
aoc2025
1
fork
atom
no this isn't about alexandria ocasio-cortez
1
fork
atom
overview
issues
pulls
pipelines
day 2, day 3
evan.jarrett.net
3 months ago
09607325
fecd7a2b
verified
This commit was signed with the committer's
known signature
.
evan.jarrett.net
SSH Key Fingerprint:
SHA256:bznk0uVPp7XFOl67P0uTM1pCjf2A4ojeP/lsUE7uauQ=
+425
-33
7 changed files
expand all
collapse all
unified
split
cmd
one
main.go
main_test.go
three
main.go
main_test.go
two
main.go
main_test.go
newday.sh
+8
-11
cmd/one/main.go
···
2
2
3
3
import (
4
4
"fmt"
5
5
+
"log/slog"
5
6
"strconv"
6
7
"strings"
7
8
···
31
32
func (d *DayOne) Part1() (int, error) {
32
33
position := 50
33
34
zeroCount := 0
34
34
-
fmt.Printf("The dial starts by pointing at %d.\n", position)
35
35
+
slog.Debug("The dial starts by pointing at", "position", position)
35
36
36
37
for _, rot := range d.rotations {
37
38
newPosition, _, err := Rotate(position, rot)
38
39
if err != nil {
39
40
return 0, err
40
41
}
41
41
-
fmt.Printf("The dial is rotated %d to point at %d.\n", rot, newPosition)
42
42
+
slog.Debug("The dial is rotated", "rotation", rot, "newPosition", newPosition)
42
43
position = newPosition
43
44
if position == 0 {
44
45
zeroCount++
45
46
}
46
47
}
47
48
48
48
-
fmt.Printf("Final position: %d\n", position)
49
49
+
slog.Debug("Final position", "position", position)
49
50
return zeroCount, nil
50
51
}
51
52
52
53
func (d *DayOne) Part2() (int, error) {
53
54
position := 50
54
55
zeroCount := 0
55
55
-
fmt.Printf("The dial starts by pointing at %d.\n", position)
56
56
+
slog.Debug("The dial starts by pointing at", "position", position)
56
57
57
58
for _, rot := range d.rotations {
58
59
newPosition, passes, err := Rotate(position, rot)
59
60
if err != nil {
60
61
return 0, err
61
62
}
62
62
-
fmt.Printf("The dial is rotated %d to point at %d.", rot, newPosition)
63
63
+
slog.Debug("The dial is rotated", "rotation", rot, "newPosition", newPosition, "passes", passes)
63
64
if passes > 0 {
64
64
-
if newPosition != 0 || passes > 2 {
65
65
-
fmt.Printf(" during this rotation, it points at 0 %d times.", passes)
66
66
-
}
67
65
zeroCount += passes
68
66
}
69
69
-
fmt.Println()
70
70
-
fmt.Printf("count is now: %d\n", zeroCount)
67
67
+
slog.Debug("count updated", "zeroCount", zeroCount)
71
68
position = newPosition
72
69
}
73
70
74
74
-
fmt.Printf("Final position: %d\n", position)
71
71
+
slog.Debug("Final position", "position", position)
75
72
return zeroCount, nil
76
73
}
77
74
+57
-2
cmd/one/main_test.go
···
1
1
package main
2
2
3
3
import (
4
4
+
"os"
4
5
"testing"
6
6
+
7
7
+
"tangled.org/evan.jarrett.net/aoc2025/internal"
5
8
)
6
9
7
10
const testInput = `L68
···
54
57
input string
55
58
want int
56
59
}{
57
57
-
{"R555", 6},
60
60
+
{"R555", 6},
58
61
{"L432", 4},
59
62
{"L555", 6},
60
63
}
···
84
87
position int
85
88
amount int
86
89
wantPos int
87
87
-
wantCount int
90
90
+
wantCount int
88
91
}{
89
92
// Starting at 0, no crossing
90
93
{"from 0, R50", 0, 50, 50, 0},
···
122
125
})
123
126
}
124
127
}
128
128
+
129
129
+
// getRealInput loads the real puzzle input, caching it to a file to avoid
130
130
+
// repeated network calls during benchmarks.
131
131
+
func getRealInput(b *testing.B) string {
132
132
+
b.Helper()
133
133
+
const cacheFile = "testdata/input.txt"
134
134
+
135
135
+
// Try to read from cache first
136
136
+
if data, err := os.ReadFile(cacheFile); err == nil {
137
137
+
return string(data)
138
138
+
}
139
139
+
140
140
+
// Fetch from AOC and cache it
141
141
+
input, err := internal.GetInput(1)
142
142
+
if err != nil {
143
143
+
b.Skipf("Could not fetch real input: %v", err)
144
144
+
}
145
145
+
146
146
+
// Cache for future runs
147
147
+
_ = os.MkdirAll("testdata", 0755)
148
148
+
_ = os.WriteFile(cacheFile, []byte(input), 0644)
149
149
+
150
150
+
return input
151
151
+
}
152
152
+
153
153
+
func BenchmarkPart1(b *testing.B) {
154
154
+
input := getRealInput(b)
155
155
+
156
156
+
d := &DayOne{}
157
157
+
if err := d.ParseInput(input); err != nil {
158
158
+
b.Fatalf("ParseInput failed: %v", err)
159
159
+
}
160
160
+
161
161
+
b.ResetTimer()
162
162
+
for i := 0; i < b.N; i++ {
163
163
+
d.Part1()
164
164
+
}
165
165
+
}
166
166
+
167
167
+
func BenchmarkPart2(b *testing.B) {
168
168
+
input := getRealInput(b)
169
169
+
170
170
+
d := &DayOne{}
171
171
+
if err := d.ParseInput(input); err != nil {
172
172
+
b.Fatalf("ParseInput failed: %v", err)
173
173
+
}
174
174
+
175
175
+
b.ResetTimer()
176
176
+
for i := 0; i < b.N; i++ {
177
177
+
d.Part2()
178
178
+
}
179
179
+
}
+75
cmd/three/main.go
···
1
1
+
package main
2
2
+
3
3
+
import (
4
4
+
"log/slog"
5
5
+
"strings"
6
6
+
7
7
+
"tangled.org/evan.jarrett.net/aoc2025/internal/puzzle"
8
8
+
)
9
9
+
10
10
+
type DayThree struct {
11
11
+
batteries []string
12
12
+
}
13
13
+
14
14
+
func (d *DayThree) ParseInput(input string) error {
15
15
+
for line := range strings.SplitSeq(strings.TrimSpace(input), "\n") {
16
16
+
line = strings.TrimSpace(line)
17
17
+
if line == "" {
18
18
+
continue
19
19
+
}
20
20
+
d.batteries = append(d.batteries, line)
21
21
+
}
22
22
+
return nil
23
23
+
}
24
24
+
25
25
+
// exactly 2
26
26
+
func (d *DayThree) Part1() (int, error) {
27
27
+
sum := 0
28
28
+
for _, battery := range d.batteries {
29
29
+
first := 0
30
30
+
second := 0
31
31
+
for i, ch := range battery {
32
32
+
digit := int(ch - '0')
33
33
+
if digit > first && i < len(battery)-1 {
34
34
+
first = digit
35
35
+
second = 0 // reset our second digit since it needs to come after the first
36
36
+
} else if digit > second {
37
37
+
second = digit
38
38
+
}
39
39
+
}
40
40
+
combined := first*10 + second
41
41
+
slog.Debug("joltage", "largest", combined, "first", first, "second", second)
42
42
+
sum += combined
43
43
+
}
44
44
+
return sum, nil
45
45
+
}
46
46
+
47
47
+
// exactly 12
48
48
+
func (d *DayThree) Part2() (int, error) {
49
49
+
max := 12
50
50
+
sum := 0
51
51
+
for _, battery := range d.batteries {
52
52
+
arr := make([]int, max)
53
53
+
for i, ch := range battery {
54
54
+
digit := int(ch - '0')
55
55
+
for j := range max {
56
56
+
if digit > arr[j] && i < len(battery)-(max-1-j) {
57
57
+
arr[j] = digit
58
58
+
clear(arr[j+1:])
59
59
+
break
60
60
+
}
61
61
+
}
62
62
+
}
63
63
+
combined := 0
64
64
+
for _, digit := range arr {
65
65
+
combined = combined*10 + digit
66
66
+
}
67
67
+
slog.Debug("joltage", "largest", combined)
68
68
+
sum += combined
69
69
+
}
70
70
+
return sum, nil
71
71
+
}
72
72
+
73
73
+
func main() {
74
74
+
puzzle.Run(3, &DayThree{})
75
75
+
}
+51
cmd/three/main_test.go
···
1
1
+
package main
2
2
+
3
3
+
import (
4
4
+
"testing"
5
5
+
)
6
6
+
7
7
+
const testInput = `987654321111111
8
8
+
811111111111119
9
9
+
234234234234278
10
10
+
818181911112111
11
11
+
`
12
12
+
13
13
+
func TestPart1(t *testing.T) {
14
14
+
// slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
15
15
+
// Level: slog.LevelDebug,
16
16
+
// })))
17
17
+
d := &DayThree{}
18
18
+
if err := d.ParseInput(testInput); err != nil {
19
19
+
t.Fatalf("ParseInput failed: %v", err)
20
20
+
}
21
21
+
22
22
+
got, err := d.Part1()
23
23
+
if err != nil {
24
24
+
t.Fatalf("Part1 failed: %v", err)
25
25
+
}
26
26
+
27
27
+
want := 357
28
28
+
if got != want {
29
29
+
t.Errorf("Part1() = %d, want %d", got, want)
30
30
+
}
31
31
+
}
32
32
+
33
33
+
func TestPart2(t *testing.T) {
34
34
+
// slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
35
35
+
// Level: slog.LevelDebug,
36
36
+
// })))
37
37
+
d := &DayThree{}
38
38
+
if err := d.ParseInput(testInput); err != nil {
39
39
+
t.Fatalf("ParseInput failed: %v", err)
40
40
+
}
41
41
+
42
42
+
got, err := d.Part2()
43
43
+
if err != nil {
44
44
+
t.Fatalf("Part2 failed: %v", err)
45
45
+
}
46
46
+
47
47
+
want := 3121910778619 // TODO: set expected value
48
48
+
if got != want {
49
49
+
t.Errorf("Part2() = %d, want %d", got, want)
50
50
+
}
51
51
+
}
+85
cmd/two/main.go
···
1
1
+
package main
2
2
+
3
3
+
import (
4
4
+
"log/slog"
5
5
+
"strconv"
6
6
+
"strings"
7
7
+
8
8
+
"tangled.org/evan.jarrett.net/aoc2025/internal/puzzle"
9
9
+
)
10
10
+
11
11
+
type ProductId struct {
12
12
+
firstId int
13
13
+
lastId int
14
14
+
}
15
15
+
16
16
+
type DayTwo struct {
17
17
+
productIds []ProductId
18
18
+
}
19
19
+
20
20
+
func (d *DayTwo) ParseInput(input string) error {
21
21
+
for productId := range strings.SplitSeq(strings.TrimSpace(input), ",") {
22
22
+
productId = strings.TrimSpace(productId)
23
23
+
if productId == "" {
24
24
+
continue
25
25
+
}
26
26
+
parts := strings.Split(productId, "-")
27
27
+
first, _ := strconv.Atoi(parts[0])
28
28
+
last, _ := strconv.Atoi(parts[1])
29
29
+
30
30
+
product := ProductId{
31
31
+
firstId: first,
32
32
+
lastId: last,
33
33
+
}
34
34
+
35
35
+
d.productIds = append(d.productIds, product)
36
36
+
}
37
37
+
return nil
38
38
+
}
39
39
+
40
40
+
func findPattern(productId int, j int) int {
41
41
+
s := strconv.Itoa(productId)
42
42
+
n := len(s)
43
43
+
if n%j == 0 {
44
44
+
piece := s[:n/j]
45
45
+
if strings.Repeat(piece, j) == s {
46
46
+
slog.Debug("found invalid ID", "id", s)
47
47
+
return productId
48
48
+
}
49
49
+
}
50
50
+
return 0
51
51
+
}
52
52
+
53
53
+
// find ids that have a repeating pattern. 55 = 5 twice, 6464 = 64 twice.
54
54
+
func (d *DayTwo) Part1() (int, error) {
55
55
+
sum := 0
56
56
+
for _, product := range d.productIds {
57
57
+
for i := product.firstId; i <= product.lastId; i++ {
58
58
+
sum += findPattern(i, 2)
59
59
+
}
60
60
+
}
61
61
+
62
62
+
return sum, nil
63
63
+
}
64
64
+
65
65
+
func (d *DayTwo) Part2() (int, error) {
66
66
+
sum := 0
67
67
+
for _, product := range d.productIds {
68
68
+
for i := product.firstId; i <= product.lastId; i++ {
69
69
+
s := strconv.Itoa(i)
70
70
+
n := len(s)
71
71
+
for j := 2; j <= n; j++ {
72
72
+
if result := findPattern(i, j); result > 0 {
73
73
+
sum += result
74
74
+
break
75
75
+
}
76
76
+
}
77
77
+
}
78
78
+
}
79
79
+
80
80
+
return sum, nil
81
81
+
}
82
82
+
83
83
+
func main() {
84
84
+
puzzle.Run(2, &DayTwo{})
85
85
+
}
+96
cmd/two/main_test.go
···
1
1
+
package main
2
2
+
3
3
+
import (
4
4
+
"os"
5
5
+
"testing"
6
6
+
7
7
+
"tangled.org/evan.jarrett.net/aoc2025/internal"
8
8
+
)
9
9
+
10
10
+
const testInput = `11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124`
11
11
+
12
12
+
func TestPart1(t *testing.T) {
13
13
+
d := &DayTwo{}
14
14
+
if err := d.ParseInput(testInput); err != nil {
15
15
+
t.Fatalf("ParseInput failed: %v", err)
16
16
+
}
17
17
+
18
18
+
got, err := d.Part1()
19
19
+
if err != nil {
20
20
+
t.Fatalf("Part1 failed: %v", err)
21
21
+
}
22
22
+
23
23
+
want := 1227775554
24
24
+
if got != want {
25
25
+
t.Errorf("Part1() = %d, want %d", got, want)
26
26
+
}
27
27
+
}
28
28
+
29
29
+
func TestPart2(t *testing.T) {
30
30
+
d := &DayTwo{}
31
31
+
if err := d.ParseInput(testInput); err != nil {
32
32
+
t.Fatalf("ParseInput failed: %v", err)
33
33
+
}
34
34
+
35
35
+
got, err := d.Part2()
36
36
+
if err != nil {
37
37
+
t.Fatalf("Part2 failed: %v", err)
38
38
+
}
39
39
+
40
40
+
want := 4174379265
41
41
+
if got != want {
42
42
+
t.Errorf("Part2() = %d, want %d", got, want)
43
43
+
}
44
44
+
}
45
45
+
46
46
+
// getRealInput loads the real puzzle input, caching it to a file to avoid
47
47
+
// repeated network calls during benchmarks.
48
48
+
func getRealInput(b *testing.B) string {
49
49
+
b.Helper()
50
50
+
const cacheFile = "testdata/input.txt"
51
51
+
52
52
+
// Try to read from cache first
53
53
+
if data, err := os.ReadFile(cacheFile); err == nil {
54
54
+
return string(data)
55
55
+
}
56
56
+
57
57
+
// Fetch from AOC and cache it
58
58
+
input, err := internal.GetInput(2)
59
59
+
if err != nil {
60
60
+
b.Skipf("Could not fetch real input: %v", err)
61
61
+
}
62
62
+
63
63
+
// Cache for future runs
64
64
+
_ = os.MkdirAll("testdata", 0755)
65
65
+
_ = os.WriteFile(cacheFile, []byte(input), 0644)
66
66
+
67
67
+
return input
68
68
+
}
69
69
+
70
70
+
func BenchmarkPart1(b *testing.B) {
71
71
+
input := getRealInput(b)
72
72
+
73
73
+
d := &DayTwo{}
74
74
+
if err := d.ParseInput(input); err != nil {
75
75
+
b.Fatalf("ParseInput failed: %v", err)
76
76
+
}
77
77
+
78
78
+
b.ResetTimer()
79
79
+
for i := 0; i < b.N; i++ {
80
80
+
d.Part1()
81
81
+
}
82
82
+
}
83
83
+
84
84
+
func BenchmarkPart2(b *testing.B) {
85
85
+
input := getRealInput(b)
86
86
+
87
87
+
d := &DayTwo{}
88
88
+
if err := d.ParseInput(input); err != nil {
89
89
+
b.Fatalf("ParseInput failed: %v", err)
90
90
+
}
91
91
+
92
92
+
b.ResetTimer()
93
93
+
for i := 0; i < b.N; i++ {
94
94
+
d.Part2()
95
95
+
}
96
96
+
}
+53
-20
newday.sh
···
2
2
3
3
set -e
4
4
5
5
-
if [ -z "$1" ]; then
6
6
-
echo "Usage: $0 <day>"
7
7
-
echo "Example: $0 5"
5
5
+
# Determine current day based on EST timezone (puzzles unlock at midnight EST)
6
6
+
EST_DAY=$(TZ="America/New_York" date +%-d)
7
7
+
EST_MONTH=$(TZ="America/New_York" date +%-m)
8
8
+
9
9
+
if [ "$EST_MONTH" -ne 12 ]; then
10
10
+
echo "Error: Advent of Code runs in December"
8
11
exit 1
9
12
fi
10
13
11
11
-
DAY=$1
12
12
-
13
13
-
if ! [[ "$DAY" =~ ^[0-9]+$ ]] || [ "$DAY" -lt 1 ] || [ "$DAY" -gt 12 ]; then
14
14
-
echo "Error: Day must be a number between 1 and 12"
14
14
+
if [ "$EST_DAY" -gt 12 ]; then
15
15
+
echo "Error: This event only has 12 days (current EST day: $EST_DAY)"
15
16
exit 1
16
17
fi
17
18
18
18
-
# Check if the puzzle is available (unlocks at midnight EST / 11pm CST)
19
19
-
EST_DAY=$(TZ="America/New_York" date +%-d)
20
20
-
EST_MONTH=$(TZ="America/New_York" date +%-m)
21
21
-
EST_YEAR=$(TZ="America/New_York" date +%Y)
22
22
-
23
23
-
if [ "$EST_YEAR" -eq 2025 ] && [ "$EST_MONTH" -eq 12 ]; then
24
24
-
if [ "$DAY" -gt "$EST_DAY" ]; then
25
25
-
echo "Error: Day $DAY puzzle not yet available."
26
26
-
echo "Puzzles unlock at midnight EST (11pm CST)."
27
27
-
echo "Current EST date: December $EST_DAY, 2025"
28
28
-
exit 1
29
29
-
fi
30
30
-
fi
19
19
+
DAY=$EST_DAY
31
20
32
21
declare -a WORDS=("" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" "ten"
33
22
"eleven" "twelve")
···
87
76
puzzle.Run($DAY, &Day${DAY_WORD_CAP}{})
88
77
}
89
78
EOF
79
79
+
cat > "$CMD_DIR/main_test.go" << EOF
80
80
+
package main
81
81
+
82
82
+
import (
83
83
+
"testing"
84
84
+
)
85
85
+
86
86
+
const testInput = \`\`
87
87
+
88
88
+
func TestPart1(t *testing.T) {
89
89
+
d := &Day${DAY_WORD_CAP}{}
90
90
+
if err := d.ParseInput(testInput); err != nil {
91
91
+
t.Fatalf("ParseInput failed: %v", err)
92
92
+
}
93
93
+
94
94
+
got, err := d.Part1()
95
95
+
if err != nil {
96
96
+
t.Fatalf("Part1 failed: %v", err)
97
97
+
}
98
98
+
99
99
+
want := 0 // TODO: set expected value
100
100
+
if got != want {
101
101
+
t.Errorf("Part1() = %d, want %d", got, want)
102
102
+
}
103
103
+
}
104
104
+
105
105
+
func TestPart2(t *testing.T) {
106
106
+
d := &Day${DAY_WORD_CAP}{}
107
107
+
if err := d.ParseInput(testInput); err != nil {
108
108
+
t.Fatalf("ParseInput failed: %v", err)
109
109
+
}
110
110
+
111
111
+
got, err := d.Part2()
112
112
+
if err != nil {
113
113
+
t.Fatalf("Part2 failed: %v", err)
114
114
+
}
115
115
+
116
116
+
want := 0 // TODO: set expected value
117
117
+
if got != want {
118
118
+
t.Errorf("Part2() = %d, want %d", got, want)
119
119
+
}
120
120
+
}
121
121
+
EOF
90
122
echo "Created $CMD_DIR/main.go"
123
123
+
echo "Created $CMD_DIR/main_test.go"
91
124
fi