Skip to content

Commit f8902cf

Browse files
committed
Day 19 in Raku and Go because I spent 40 hours thinking rotation was commutative
1 parent 30cd664 commit f8902cf

File tree

6 files changed

+1401
-0
lines changed

6 files changed

+1401
-0
lines changed

2021/day19/day19.go

+299
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
/* Copyright 2021 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+
// https://adventofcode.com/2021/day/19 implementation in Go because day19.raku
8+
// was really slow and nondeterministically got the wrong answer. The Go
9+
// implementation also nondeterministically got the wrong answer because
10+
// iteration order through a map isn't consistent and I incorrectly believed
11+
// that rotation was a commutative operation.
12+
package main
13+
14+
import (
15+
"bufio"
16+
"fmt"
17+
"log"
18+
"os"
19+
"strings"
20+
)
21+
22+
type Direction int
23+
24+
const (
25+
Deosil Direction = 1 // clockwise
26+
Widdershins Direction = -1 // not actually used, but makes math more robust
27+
)
28+
29+
type Rotation struct{ x, y, z int }
30+
31+
var (
32+
face1 = Rotation{}
33+
face2 = Rotation{y: 1}
34+
face3 = Rotation{y: 2}
35+
face4 = Rotation{y: 3}
36+
face5 = Rotation{z: 1}
37+
face6 = Rotation{z: 3}
38+
allFaces = []Rotation{face1, face2, face3, face4, face5, face6}
39+
xTurns = []Rotation{Rotation{}, Rotation{x: 1}, Rotation{x: 2}, Rotation{x: 3}}
40+
)
41+
42+
func (r Rotation) String() string {
43+
return fmt.Sprintf("Rotation{x:%d,y:%d,z:%d}", r.x, r.y, r.z)
44+
}
45+
46+
func (r Rotation) union(o Rotation) Rotation {
47+
return Rotation{r.x + o.x, r.y + o.y, r.z + o.z}
48+
}
49+
50+
type Point struct{ x, y, z int }
51+
52+
func (p Point) rotate(axis string, dir Direction) Point {
53+
switch axis {
54+
case "x":
55+
return Point{x: p.x, y: p.z * int(dir), z: -p.y * int(dir)}
56+
case "y":
57+
return Point{x: -p.z * int(dir), y: p.y, z: p.x * int(dir)}
58+
case "z":
59+
return Point{x: p.y * int(dir), y: -p.x * int(dir), z: p.z}
60+
default:
61+
log.Fatalf("Unknown axis %s", axis)
62+
return p
63+
}
64+
}
65+
66+
func (p Point) rotateMultiple(axes Rotation) Point {
67+
q := p
68+
if axes.x != 0 {
69+
dir := Deosil
70+
if axes.x < 0 {
71+
dir = Widdershins
72+
}
73+
for i := 0; i < int(dir)*axes.x; i++ {
74+
q = q.rotate("x", dir)
75+
}
76+
}
77+
if axes.y != 0 {
78+
dir := Deosil
79+
if axes.y < 0 {
80+
dir = Widdershins
81+
}
82+
for i := 0; i < int(dir)*axes.y; i++ {
83+
q = q.rotate("y", dir)
84+
}
85+
}
86+
if axes.z != 0 {
87+
dir := Deosil
88+
if axes.z < 0 {
89+
dir = Widdershins
90+
}
91+
for i := 0; i < int(dir)*axes.z; i++ {
92+
q = q.rotate("z", dir)
93+
}
94+
}
95+
return q
96+
}
97+
98+
func (p Point) plus(o Point) Point {
99+
return Point{x: p.x + o.x, y: p.y + o.y, z: p.z + o.z}
100+
}
101+
102+
func (p Point) minus(o Point) Point {
103+
return Point{x: p.x - o.x, y: p.y - o.y, z: p.z - o.z}
104+
}
105+
106+
func (p Point) String() string {
107+
return fmt.Sprintf("%d,%d,%d", p.x, p.y, p.z)
108+
}
109+
110+
type Pointset struct {
111+
points map[Point]bool
112+
origin Point
113+
rotation Rotation
114+
}
115+
116+
func (s *Pointset) rotate(axes Rotation) *Pointset {
117+
res := make(map[Point]bool)
118+
for p := range s.points {
119+
res[p.rotateMultiple(axes)] = true
120+
}
121+
return &Pointset{points: res, origin: s.origin.rotateMultiple(axes), rotation: s.rotation.union(axes)}
122+
}
123+
124+
func (s *Pointset) allOrientations() []*Pointset {
125+
res := make([]*Pointset, 0, 24)
126+
for _, f := range allFaces {
127+
for _, x := range xTurns {
128+
res = append(res, s.rotate(f.union(x)))
129+
}
130+
}
131+
return res
132+
}
133+
134+
func (s *Pointset) offset(p Point) *Pointset {
135+
res := make(map[Point]bool)
136+
for q := range s.points {
137+
res[q.plus(p)] = true
138+
}
139+
return &Pointset{points: res, origin: s.origin.plus(p), rotation: s.rotation}
140+
}
141+
142+
func (s *Pointset) overlap(o *Pointset) *Pointset {
143+
for p := range s.points {
144+
for q := range o.points {
145+
t := o.offset(p.minus(q))
146+
matches := 0
147+
for r := range t.points {
148+
if s.points[r] {
149+
matches++
150+
}
151+
}
152+
if matches >= 12 {
153+
return t
154+
}
155+
}
156+
}
157+
return nil
158+
}
159+
160+
func (s *Pointset) String() string {
161+
points := make([]string, 0, len(s.points))
162+
for p := range s.points {
163+
points = append(points, p.String())
164+
}
165+
return fmt.Sprintf("Pointset( %s )", strings.Join(points, " "))
166+
}
167+
168+
type Device struct {
169+
name string
170+
pointset *Pointset
171+
orientations []*Pointset
172+
}
173+
174+
func (d *Device) String() string {
175+
points := make([]string, 0, len(d.pointset.points))
176+
for p := range d.pointset.points {
177+
points = append(points, p.String())
178+
}
179+
return fmt.Sprintf("%s\n%s", d.name, strings.Join(points, "\n"))
180+
}
181+
182+
func align(devices []*Device) map[int]*Pointset {
183+
found := make(map[int]*Pointset)
184+
found[0] = devices[0].pointset
185+
checked := make(map[int]map[int]bool)
186+
remaining := len(devices) - 1
187+
for remaining > 0 {
188+
thisloop := 0
189+
eachdevice:
190+
for i := range devices {
191+
if checked[i] == nil {
192+
checked[i] = make(map[int]bool)
193+
}
194+
if found[i] != nil {
195+
continue
196+
}
197+
for j := range devices {
198+
if i == j || found[j] == nil || checked[i][j] {
199+
continue
200+
}
201+
checked[i][j] = true
202+
source := found[j]
203+
for _, orient := range devices[i].orientations {
204+
o := source.overlap(orient)
205+
if o == nil {
206+
continue
207+
}
208+
found[i] = o
209+
remaining--
210+
thisloop++
211+
continue eachdevice
212+
}
213+
}
214+
}
215+
if thisloop == 0 {
216+
log.Fatalf("Couldn't make any progress after %d matches\n%v", len(found), found)
217+
}
218+
}
219+
return found
220+
}
221+
222+
// part1 counts the number of distinct points after aligning all scanner devices.
223+
func part1(devices []*Device) int {
224+
found := align(devices)
225+
all := make(map[Point]int)
226+
for _, s := range found {
227+
for p := range s.points {
228+
all[p]++
229+
}
230+
}
231+
return len(all)
232+
}
233+
234+
// part1 counts the maximum Manhattan distance between two scanner devices.
235+
func part2(devices []*Device) int {
236+
found := align(devices)
237+
max := 0
238+
for _, ps1 := range found {
239+
for _, ps2 := range found {
240+
p := ps1.origin.minus(ps2.origin)
241+
max = maxInt(max, absInt(p.x)+absInt(p.y)+absInt(p.z))
242+
}
243+
}
244+
return max
245+
}
246+
247+
func absInt(a int) int {
248+
if a >= 0 {
249+
return a
250+
}
251+
return -1 * a
252+
}
253+
func maxInt(a, b int) int {
254+
if b > a {
255+
return b
256+
}
257+
return a
258+
}
259+
260+
func runFile(fname string) {
261+
f, err := os.Open(fname)
262+
if err != nil {
263+
log.Fatalf("Error opening %s: %v", fname, err)
264+
}
265+
defer f.Close()
266+
devices := make([]*Device, 0)
267+
var dev *Device
268+
scanner := bufio.NewScanner(f)
269+
for scanner.Scan() {
270+
line := scanner.Text()
271+
if strings.Contains(line, "scanner") {
272+
dev = &Device{name: line, pointset: &Pointset{points: make(map[Point]bool)}}
273+
devices = append(devices, dev)
274+
} else if strings.ContainsRune(line, ',') {
275+
var x, y, z int
276+
if _, err := fmt.Sscanf(line, "%d,%d,%d", &x, &y, &z); err != nil {
277+
log.Fatalf("Could not parse %s", line)
278+
continue
279+
}
280+
dev.pointset.points[Point{x: x, y: y, z: z}] = true
281+
}
282+
}
283+
for _, d := range devices {
284+
d.orientations = d.pointset.allOrientations()
285+
}
286+
fmt.Printf("Running Part 1 on %s\n", fname)
287+
res1 := part1(devices)
288+
log.Printf("Part 1 got %d\n", res1)
289+
fmt.Printf("Running Part 2 on %s\n", fname)
290+
res2 := part2(devices)
291+
log.Printf("Part 2 got %d\n", res2)
292+
}
293+
294+
func main() {
295+
for _, fname := range os.Args[1:] {
296+
log.Printf("Running day19 on %s\n", fname)
297+
runFile(fname)
298+
}
299+
}

0 commit comments

Comments
 (0)