Skip to content

Commit 3478551

Browse files
committed
Runner & generator infra in Go, use it for 2022 day 20
1 parent d35916f commit 3478551

File tree

5 files changed

+300
-51
lines changed

5 files changed

+300
-51
lines changed

2022/day20/day20.go

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,51 +10,24 @@
1010
day20 in Go since I could not figure out what was wrong with my Elixir
1111
solution so I figured I'd reimplement it in a different language to see if I
1212
had a hard-to-spot bug. Turns out it was an unclear specification.
13+
XXX This program implements the incorrect interpretation of the problem
14+
(tea party guests move but chairs stay put); day20.exs implements the correct
15+
interpretation (tea party guests carry their chair around the table).
16+
https://www.reddit.com/r/adventofcode/comments/zrggym/2022_day_20_alice_in_wonderland_explains_the_two/
1317
*/
1418
package main
1519

1620
import (
17-
"bufio"
18-
"fmt"
1921
"log"
20-
"os"
21-
"time"
22+
"strconv"
2223
)
2324

2425
func main() {
25-
for _, arg := range os.Args[1:] {
26-
fmt.Printf("Reading %s\n", arg)
27-
f, err := os.Open(arg)
28-
if err != nil {
29-
log.Fatalf("Could not open %s: %v", arg, err)
30-
}
31-
defer f.Close()
32-
ints := make([]int, 0, 5000)
33-
s := bufio.NewScanner(f)
34-
for s.Scan() {
35-
var i int
36-
if _, err := fmt.Sscanf(s.Text(), "%d", &i); err != nil {
37-
log.Fatalf("Could not parse int %s: %v", s.Text(), err)
38-
}
39-
ints = append(ints, i)
40-
}
41-
42-
fmt.Printf("Running part1 on %s (%d lines)\n", arg, len(ints))
43-
start := time.Now()
44-
res := part1(ints)
45-
dur := time.Since(start)
46-
fmt.Printf("part1: %d\n", res)
47-
fmt.Printf("Finished part1 in %s on %s\n", dur, arg)
48-
49-
fmt.Printf("Running part2 on %s (%d lines)\n", arg, len(ints))
50-
start = time.Now()
51-
res = part2(ints)
52-
dur = time.Since(start)
53-
fmt.Printf("part2: %d\n", res)
54-
fmt.Printf("Finished part2 in %s on %s\n", dur, arg)
55-
}
26+
runMain(part1, part2)
5627
}
5728

29+
const dayName = "day20"
30+
5831
type Node struct {
5932
value int
6033
prev, next *Node
@@ -115,18 +88,32 @@ func (n *Node) toSlice() []int {
11588
return ints
11689
}
11790

118-
func part1(ints []int) int {
91+
func stringsToInts(strs []string) []int {
92+
res := make([]int, len(strs))
93+
for i, s := range strs {
94+
x, err := strconv.Atoi(s)
95+
if err != nil {
96+
log.Fatalf("Invalid int %q at line %d: %v", s, i+1, err)
97+
}
98+
res[i] = x
99+
}
100+
return res
101+
}
102+
103+
func part1(lines []string) string {
104+
ints := stringsToInts(lines)
119105
size := len(ints)
120106
nodes := buildList(ints, 1)
121107
for _, n := range nodes {
122108
n.move(n.value % (size - 1))
123109
}
124-
return score(nodes)
110+
return strconv.Itoa(score(nodes))
125111
}
126112

127113
const part2Multiplier = 811589153
128114

129-
func part2(ints []int) int {
115+
func part2(lines []string) string {
116+
ints := stringsToInts(lines)
130117
size := len(ints)
131118
nodes := buildList(ints, part2Multiplier)
132119
for i := 0; i < 10; i++ {
@@ -135,7 +122,7 @@ func part2(ints []int) int {
135122
n.move(steps)
136123
}
137124
}
138-
return score(nodes)
125+
return strconv.Itoa(score(nodes))
139126
}
140127

141128
func buildList(ints []int, multiplier int) []*Node {

2022/day20/runner.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../lang/go/runner.go

README.md

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,30 @@ think could be done better.
3131

3232
## Directory Structure
3333

34-
Each year has one directory for each day’s solution, named `day1` through `day25`.
34+
Each year’s solutions are in a directory named after the year. Within that dir
35+
is one directory for each day’s solution, named `day1` through `day25`.
3536
Each day directory has the solution code in one or more languages, a file named
3637
`input.example.txt` with an example taken from the problem description and
3738
`input.example.expected` with two lines (prefixed `part1: ` and `part2: `) with
3839
the expected output when the solution code is run `input.example.txt`. If the
3940
problem description has multiple examples, they’ll be in `input.example2.txt`
40-
and `input.example2.expected`, etc. The `input.actual.txt` and
41-
`input.actual.expected` files are symlinks into a non-public directory with
42-
input specific to my AoC account. If you’d like to test my code against your
43-
personal input, provide the path to your input as a command line argument:
44-
`day1/day1.exs -v path/to/my/input.txt`. If you’d like to run a whole suite of
45-
solutions against your input, make sure they’re in a directory with numbered
46-
subdirectories, e.g. `1/input.actual.txt` and replace my `input` directory with
47-
a symlink to yours, e.g. `rm 2023/input; ln -s path/to/my/2023 2023/input` which
48-
will cause `runday` and `testday` scripts to use yours. Alternatively,
49-
substitute the `input` submodule at the base of the repository, as in the next
50-
section.
41+
and `input.example2.expected`, etc.
42+
43+
The `input.actual.txt` and `input.actual.expected` files are symlinks into a
44+
non-public directory with input specific to my AoC account. If you’d like to
45+
test my code against your personal input, provide the path to your input as a
46+
command line argument: `day1/day1.exs -v path/to/my/input.txt`. If you’d like
47+
to run a whole suite of solutions against your input, make sure they’re in a
48+
directory with numbered subdirectories, e.g. `1/input.actual.txt` and replace
49+
my `input` directory with a symlink to yours, e.g. `rm 2023/input; ln -s
50+
path/to/my/2023 2023/input` which will cause `runday` and `testday` scripts to
51+
use yours. Alternatively, substitute the `input` submodule at the base of the
52+
repository, as in the next section.
53+
54+
There is also a `lang` directory for runner and generator infrastructure for
55+
programming languages which aren’t “the main language” for any year, but which I
56+
sometimes use for fun or because I got stuck on a problem with the main new
57+
language for a year.
5158

5259
## Hiding Private Advent of Code Input with Git Submodules
5360

lang/go/generate.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Use of this source code is governed by an MIT-style
4+
// license that can be found in the LICENSE file or at
5+
// https://opensource.org/licenses/MIT.
6+
7+
// generate creates a skeletal go program to solve a day's Advent of Code
8+
// problem, using runner.go to read input and log results.
9+
package main
10+
11+
import (
12+
"errors"
13+
"fmt"
14+
"log"
15+
"os"
16+
"path/filepath"
17+
"runtime"
18+
)
19+
20+
const shebang = "///usr/bin/true; exec /usr/bin/env go run \"$0\" \"`dirname $0`/runner.go\" \"$@\""
21+
const dayCode = `// Copyright 2023 Google LLC
22+
//
23+
// Use of this source code is governed by an MIT-style
24+
// license that can be found in the LICENSE file or at
25+
// https://opensource.org/licenses/MIT.
26+
27+
package main
28+
29+
func part1(lines []string) string {
30+
return "TODO"
31+
}
32+
33+
func part2(lines []string) string {
34+
return "TODO"
35+
}
36+
37+
func main() {
38+
runMain(part1, part2)
39+
}
40+
`
41+
42+
func main() {
43+
if len(os.Args) != 2 {
44+
log.Fatalf("Usage: %s path/to/dayX", os.Args[0])
45+
}
46+
outdir := os.Args[1]
47+
if err := os.MkdirAll(outdir, 0755); err != nil {
48+
log.Fatalf("Could not create %s: %v", outdir, err)
49+
}
50+
dayname := filepath.Base(outdir)
51+
dayvar := fmt.Sprintf("const dayName = %q", dayname)
52+
code := shebang + "\n" + dayCode + "\n" + dayvar + "\n"
53+
gofile := filepath.Join(outdir, dayname+".go")
54+
if fileExists(gofile) {
55+
log.Fatalf("%s already exists, exiting", gofile)
56+
}
57+
if err := os.WriteFile(gofile, []byte(code), 0755); err != nil {
58+
log.Fatalf("Error writing to %s: %v", gofile, err)
59+
}
60+
// create a symlink to runner.go, which is adjacent to generate.go
61+
runnerlink := filepath.Join(outdir, "runner.go")
62+
if !fileExists(runnerlink) {
63+
_, prog, _, ok := runtime.Caller(0)
64+
if !ok {
65+
log.Fatalf("Could not determine executable path, make sure to symlink runner.go")
66+
}
67+
runner := filepath.Join(filepath.Dir(prog), "runner.go")
68+
abs, err := filepath.Abs(outdir)
69+
if err != nil {
70+
log.Fatalf("Can't determine absolute path of %s: %v", outdir, err)
71+
}
72+
rel, err := filepath.Rel(abs, runner)
73+
if err != nil {
74+
log.Fatalf("Can't determine relative path of %s from %s: %v", runner, abs, err)
75+
}
76+
if err = os.Symlink(rel, runnerlink); err != nil {
77+
log.Fatalf("Error creating symlink to %s as %s: %v", rel, runnerlink, err)
78+
}
79+
}
80+
// doesn't create input.example.txt, input.example.expected, etc. because the
81+
// year's main generator has typically already made them
82+
}
83+
84+
func fileExists(fname string) bool {
85+
_, err := os.Stat(fname)
86+
if errors.Is(err, os.ErrNotExist) {
87+
return false
88+
}
89+
if err != nil {
90+
log.Printf("Error checking %s: %v", fname, err)
91+
}
92+
return true
93+
}

0 commit comments

Comments
 (0)