Compare commits

..

5 Commits

Author SHA1 Message Date
019510f0b6 Solve day 8 2025-12-08 21:55:03 +02:00
8b102c2c61 Solve day 7 2025-12-07 14:18:52 +02:00
ce1b496761 Solve day 6 2025-12-06 21:42:43 +02:00
2d5190c044 Solve day 5 2025-12-06 16:50:12 +02:00
b73303c965 Update README.md 2025-12-06 13:55:07 +02:00
10 changed files with 792 additions and 1 deletions

View File

@@ -8,7 +8,7 @@ using Golang.
General pattern to run the code is using **stdin** to pass the data file:
```sh
go run ./day1 < ./day1/input.txt
go run ./day1 < ./day1/example.txt
```
Majority of solutions support `-debug` flag.

11
day5/example.txt Normal file
View File

@@ -0,0 +1,11 @@
3-5
10-14
16-20
12-18
1
5
8
11
17
32

123
day5/main.go Normal file
View File

@@ -0,0 +1,123 @@
package main
import (
"bufio"
"cmp"
"fmt"
"io"
"log"
"os"
"slices"
"strconv"
"strings"
)
func main() {
f := os.Stdin
if len(os.Args) > 1 && os.Args[1] != "-" {
var err error
f, err = os.Open(os.Args[1])
if err != nil {
log.Fatal(err)
}
defer f.Close()
}
fresh, available, err := readIngredients(f)
if err != nil {
log.Fatal(err)
}
var availableFresh int
for _, id := range available {
for _, r := range fresh {
if r.Includes(id) {
availableFresh++
break
}
}
}
var freshIds int
compactFresh := compactedRanges(fresh)
for _, r := range compactFresh {
freshIds += r.to - r.from + 1
}
fmt.Println("available fresh:", availableFresh)
fmt.Println("total fresh:", freshIds)
}
type idsRange struct {
from, to int
}
func (r idsRange) Includes(n int) bool {
return r.from <= n && n <= r.to
}
func readIngredients(r io.Reader) (fresh []idsRange, available []int, err error) {
seenEmptyLine := false
sc := bufio.NewScanner(r)
for sc.Scan() {
if sc.Text() == "" {
seenEmptyLine = true
continue
}
if seenEmptyLine {
id, err := strconv.ParseUint(sc.Text(), 10, 0)
if err != nil {
return nil, nil, err
}
available = append(available, int(id))
} else {
r, err := parseRange(sc.Text())
if err != nil {
return nil, nil, err
}
fresh = append(fresh, r)
}
}
return fresh, available, nil
}
func parseRange(s string) (idsRange, error) {
before, after, ok := strings.Cut(s, "-")
if !ok {
return idsRange{}, fmt.Errorf("range %q: missing hyphen", s)
}
from, err := strconv.ParseUint(before, 10, 0)
if err != nil {
return idsRange{}, fmt.Errorf("range from: %w", err)
}
to, err := strconv.ParseUint(after, 10, 0)
if err != nil {
return idsRange{}, fmt.Errorf("range to: %w", err)
}
return idsRange{
from: int(from),
to: int(to),
}, nil
}
func compactedRanges(ranges []idsRange) []idsRange {
ranges = slices.Clone(ranges)
slices.SortFunc(ranges, func(a, b idsRange) int {
if x := cmp.Compare(a.from, b.from); x != 0 {
return x
}
return cmp.Compare(a.to, b.to)
})
i := 0
for j := 1; j < len(ranges); j++ {
if !ranges[i].Includes(ranges[j].from) {
i++
ranges[i] = ranges[j]
continue
}
ranges[i].to = max(ranges[i].to, ranges[j].to)
}
return ranges[:i+1]
}

68
day5/main_test.go Normal file
View File

@@ -0,0 +1,68 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_compactedRanges(t *testing.T) {
tests := []struct {
name string // description of this test case
ranges []idsRange
want []idsRange
}{
{
name: "single",
ranges: []idsRange{
{0, 10},
},
want: []idsRange{
{0, 10},
},
},
{
name: "identical",
ranges: []idsRange{
{0, 10},
{0, 10},
{0, 10},
{20, 30},
{20, 30},
},
want: []idsRange{
{0, 10},
{20, 30},
},
},
{
name: "simple",
ranges: []idsRange{
{0, 10},
{2, 11},
{20, 30},
},
want: []idsRange{
{0, 11},
{20, 30},
},
},
{
name: "swallows",
ranges: []idsRange{
{0, 10},
{2, 8},
{20, 30},
},
want: []idsRange{
{0, 10},
{20, 30},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, compactedRanges(tt.ranges))
})
}
}

4
day6/example.txt Normal file
View File

@@ -0,0 +1,4 @@
123 328 51 64
45 64 387 23
6 98 215 314
* + * +

217
day6/main.go Normal file
View File

@@ -0,0 +1,217 @@
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"iter"
"log"
"os"
"strconv"
"strings"
)
func main() {
f := os.Stdin
if len(os.Args) > 1 && os.Args[1] != "-" {
var err error
f, err = os.Open(os.Args[1])
if err != nil {
log.Fatal(err)
}
defer f.Close()
}
lines, err := readLines(f)
if err != nil {
log.Fatal(err)
}
problems1, err := parseProblemsPart1(lines)
if err != nil {
log.Fatal(err)
}
problems2, err := parseProblemsPart2(lines)
if err != nil {
log.Fatal(err)
}
var result1, result2 int
for _, p := range problems1 {
result1 += p.Solve()
}
for _, p := range problems2 {
result2 += p.Solve()
}
fmt.Println("part one:", result1)
fmt.Println("part two:", result2)
}
type Action int
const (
ActionAdd Action = iota
ActionMultiply
)
type problem struct {
Values []int
Action
}
func (p problem) Solve() int {
if len(p.Values) == 0 {
return 0
}
answer := p.Values[0]
for _, v := range p.Values[1:] {
switch p.Action {
case ActionAdd:
answer += v
case ActionMultiply:
answer *= v
default:
panic(fmt.Sprintf("unsupported action %d", p.Action))
}
}
return answer
}
func readLines(r io.Reader) ([]string, error) {
var lines []string
sc := bufio.NewScanner(r)
for sc.Scan() {
lines = append(lines, sc.Text())
}
return lines, sc.Err()
}
func parseFields(lines []string) [][]string {
cutAll := func(lines []string, sep string) (before, after []string, ok bool) {
var found bool
var i int
for _, line := range lines {
j := strings.Index(line, sep)
if j < 0 {
j = len(line)
} else {
found = true
}
i = max(i, j)
}
if !found {
return lines, nil, false
}
before = make([]string, 0, len(lines))
after = make([]string, 0, len(lines))
for _, line := range lines {
before = append(before, line[:min(i, len(line))])
after = append(after, line[min(i+len(sep), len(line)):])
}
return before, after, true
}
fields := make([][]string, len(lines))
for {
before, after, ok := cutAll(lines, " ")
if !ok {
break
}
for i, v := range before {
fields[i] = append(fields[i], v)
}
lines = after
}
return fields
}
func parseProblemsPart1(lines []string) ([]problem, error) {
if len(lines) == 0 {
return nil, nil
}
var data [][]string
for _, line := range lines {
data = append(data, strings.Fields(line))
}
problems := make([]problem, len(data[0]))
for i, row := range data {
if len(row) != len(problems) {
return nil, fmt.Errorf("line %d: unexpected number of fields", i+2)
}
last := i == len(data)-1
for j, v := range row {
p := &problems[j]
if last {
switch v {
case "+":
p.Action = ActionAdd
case "*":
p.Action = ActionMultiply
default:
return nil, fmt.Errorf("unexpected action %q", v)
}
} else {
n, err := strconv.Atoi(v)
if err != nil {
return nil, err
}
p.Values = append(p.Values, n)
}
}
}
return problems, nil
}
func parseProblemsPart2(lines []string) ([]problem, error) {
fields := parseFields(lines)
var problems []problem
for col := range columnsSeq(fields) {
var p problem
switch s := strings.TrimSpace(col[len(col)-1]); s {
case "+":
p.Action = ActionAdd
case "*":
p.Action = ActionMultiply
default:
return nil, fmt.Errorf("unexpected action %q", s)
}
var colBytes [][]byte
for _, v := range col[:len(col)-1] {
colBytes = append(colBytes, []byte(v))
}
for v := range columnsSeq(colBytes) {
s := string(bytes.TrimSpace(v))
n, err := strconv.Atoi(s)
if err != nil {
return nil, err
}
p.Values = append(p.Values, n)
}
problems = append(problems, p)
}
return problems, nil
}
func columnsSeq[T any](data [][]T) iter.Seq[[]T] {
return func(yield func([]T) bool) {
if len(data) < 1 {
return
}
for x := range len(data[0]) {
var col []T
for y := range data {
col = append(col, data[y][x])
}
if !yield(col) {
break
}
}
}
}

16
day7/example.txt Normal file
View File

@@ -0,0 +1,16 @@
.......S.......
...............
.......^.......
...............
......^.^......
...............
.....^.^.^.....
...............
....^.^...^....
...............
...^.^...^.^...
...............
..^...^.....^..
...............
.^.^.^.^.^...^.
...............

145
day7/main.go Normal file
View File

@@ -0,0 +1,145 @@
package main
import (
"bufio"
"flag"
"fmt"
"io"
"log"
"os"
"slices"
)
func main() {
debug := flag.Bool("debug", false, "Debug.")
flag.Parse()
f := os.Stdin
if flag.NArg() > 0 && flag.Arg(0) != "-" {
var err error
f, err = os.Open(flag.Arg(0))
if err != nil {
log.Fatal(err)
}
defer f.Close()
}
diagram, err := readDiagram(f)
if err != nil {
log.Fatal(err)
}
splits := diagram.Propagate()
var timelines int
for _, row := range slices.Backward(diagram) {
for _, c := range row {
if c.Marker != BeamMarker {
continue
}
timelines += c.WaysToReach
}
break
}
if *debug {
for _, row := range diagram {
for _, c := range row {
if c.Marker == BeamMarker {
if c.WaysToReach < 10 {
fmt.Print(c.WaysToReach)
} else {
b := 'A' + c.WaysToReach - 10
if b > 'Z' {
b = int(c.Marker)
}
fmt.Printf("%c", b)
}
} else {
fmt.Printf("%c", c.Marker)
}
}
fmt.Println()
}
}
fmt.Println("beam split", splits, "times into", timelines, "timelines")
}
type Marker byte
const (
BeamEmitterMarker Marker = 'S'
BeamMarker Marker = '|'
SplitterMarker Marker = '^'
EmptyMarker Marker = '.'
)
type Cell struct {
Marker Marker
WaysToReach int
}
type diagram [][]Cell
func (d diagram) Propagate() (splits int) {
splitoff := func(x, y, dx int) {
x1 := x + dx
if x1 < 0 || x1 >= len(d[y]) {
return // out of range
}
switch d[y][x1].Marker {
case EmptyMarker:
d[y][x1] = Cell{
Marker: BeamMarker,
WaysToReach: d[y-1][x].WaysToReach,
}
case BeamMarker:
d[y][x1].WaysToReach += d[y-1][x].WaysToReach
}
}
propagateCell := func(x, y int) {
switch d[y-1][x].Marker {
case BeamEmitterMarker:
d[y-1][x].WaysToReach = 1
case BeamMarker:
default:
return
}
// we have a beam above
switch d[y][x].Marker {
case EmptyMarker:
d[y][x] = Cell{
Marker: BeamMarker,
WaysToReach: d[y-1][x].WaysToReach,
}
case BeamMarker:
d[y][x].WaysToReach += d[y-1][x].WaysToReach
case SplitterMarker:
splitoff(x, y, -1)
splitoff(x, y, +1)
splits++
}
}
for y := 1; y < len(d); y++ {
for x := range d[y] {
propagateCell(x, y)
}
}
return splits
}
func readDiagram(r io.Reader) (diagram, error) {
var diagram diagram
sc := bufio.NewScanner(r)
for sc.Scan() {
cells := make([]Cell, 0, len(sc.Bytes()))
for _, b := range sc.Bytes() {
cells = append(cells, Cell{Marker: Marker(b)})
}
diagram = append(diagram, cells)
}
return diagram, sc.Err()
}

20
day8/example.txt Normal file
View File

@@ -0,0 +1,20 @@
162,817,812
57,618,57
906,360,560
592,479,940
352,342,300
466,668,158
542,29,236
431,825,988
739,650,466
52,470,668
216,146,977
819,987,18
117,168,530
805,96,715
346,949,466
970,615,88
941,993,340
862,61,35
984,92,344
425,690,689

187
day8/main.go Normal file
View File

@@ -0,0 +1,187 @@
package main
import (
"bufio"
"flag"
"fmt"
"io"
"log"
"maps"
"math"
"os"
"slices"
"strconv"
"strings"
"time"
)
func main() {
connections := flag.Int("connect", -1, "Number of connections to perform (-1 unlimited).")
debug := flag.Bool("debug", false, "Debug.")
flag.Parse()
f := os.Stdin
if flag.NArg() > 0 && flag.Arg(0) != "-" {
var err error
f, err = os.Open(flag.Arg(0))
if err != nil {
log.Fatal(err)
}
defer f.Close()
}
positions, err := readPositions(f)
if err != nil {
log.Fatal(err)
}
circuit := make([]int, len(positions))
for i := range positions {
circuit[i] = i
}
start := time.Now()
distances := computeDistances(positions)
var last connection
fmt.Println("distances computed in", time.Since(start))
start = time.Now()
for i := 0; ; i++ {
if *connections >= 0 && i >= *connections {
break
}
c, ok := distances.FindMin()
if !ok {
break
}
bCircuit := circuit[c.B]
for i := range circuit {
if circuit[i] == bCircuit {
circuit[i] = circuit[c.A]
}
}
if *debug {
fmt.Println("connect", positions[c.A], "and", positions[c.B])
}
if allEqual(circuit) {
last = c
break
}
delete(distances, c)
}
fmt.Println("connections computed in", time.Since(start))
countsMap := make(map[int]int)
for _, c := range circuit {
countsMap[c]++
}
counts := slices.Sorted(maps.Values(countsMap))
// if *debug {
// fmt.Println("counts:", counts)
// }
ans := 1
for _, c := range counts[max(0, len(counts)-3):] {
ans *= c
}
fmt.Println("three largest mult:", ans)
fmt.Println("last connection X mult:", positions[last.A].X*positions[last.B].X)
}
type position struct {
X, Y, Z int
}
func (p position) String() string {
return fmt.Sprintf("%d,%d,%d", p.X, p.Y, p.Z)
}
func (p position) DistanceTo(p1 position) float64 {
compX := float64((p.X - p1.X) * (p.X - p1.X))
compY := float64((p.Y - p1.Y) * (p.Y - p1.Y))
compZ := float64((p.Z - p1.Z) * (p.Z - p1.Z))
return math.Sqrt(compX + compY + compZ)
}
func readPositions(r io.Reader) ([]position, error) {
var positions []position
sc := bufio.NewScanner(r)
for sc.Scan() {
fields := strings.Split(sc.Text(), ",")
if len(fields) != 3 {
return nil, fmt.Errorf("position %q: unexpected number of fields", sc.Text())
}
x, err := strconv.Atoi(fields[0])
if err != nil {
return nil, fmt.Errorf("position %q: x: %w", sc.Text(), err)
}
y, err := strconv.Atoi(fields[1])
if err != nil {
return nil, fmt.Errorf("position %q: y: %w", sc.Text(), err)
}
z, err := strconv.Atoi(fields[2])
if err != nil {
return nil, fmt.Errorf("position %q: z: %w", sc.Text(), err)
}
positions = append(positions, position{
X: x,
Y: y,
Z: z,
})
}
return positions, sc.Err()
}
type connection struct {
A, B int
}
func (c connection) Reverse() connection {
return connection{c.B, c.A}
}
type distances map[connection]float64
func (d distances) Get(aIndex, bIndex int) float64 {
c := connection{aIndex, bIndex}
if dist, ok := d[c]; ok {
return dist
}
return d[c.Reverse()]
}
func (d distances) Set(aIndex, bIndex int, dist float64) {
d[connection{aIndex, bIndex}] = dist
}
func (d distances) FindMin() (c connection, ok bool) {
for pair, dist := range d {
if !ok || dist < d[c] {
c = pair
ok = true
}
}
return c, ok
}
func computeDistances(positions []position) distances {
table := make(distances)
for i, p := range positions {
for j := i + 1; j < len(positions); j++ {
dist := p.DistanceTo(positions[j])
table.Set(i, j, dist)
}
}
return table
}
func allEqual(s []int) bool {
if len(s) < 1 {
return true
}
for _, v := range s[1:] {
if v != s[0] {
return false
}
}
return true
}