diff --git a/2021/aoc/aoc.go b/2021/aoc/aoc.go index 69fa93a..a35d4fc 100644 --- a/2021/aoc/aoc.go +++ b/2021/aoc/aoc.go @@ -2,6 +2,7 @@ package aoc import ( "bufio" + "fmt" "io" "io/fs" "os" @@ -103,3 +104,9 @@ func Ints(text string) (ns []int) { func ExLines(text string) []string { return strings.Split(strings.TrimSpace(text), "\n") } + +func MustScan(text, format string, dests ...interface{}) { + if _, err := fmt.Sscanf(text, format, dests...); err != nil { + panic(err) + } +} diff --git a/2021/day19/main.go b/2021/day19/main.go new file mode 100644 index 0000000..3f6e610 --- /dev/null +++ b/2021/day19/main.go @@ -0,0 +1,244 @@ +package main + +import ( + "fmt" + "log" + "sort" + "strings" + + "github.com/jdkaplan/advent-of-code/aoc" +) + +func init() { + log.SetFlags(0) +} + +func main() { + text := aoc.Input().ReadFile("day19.txt") + fmt.Println(part1(text)) +} + +func part1(text string) int { + scanners := parseInput(text) + + offsets := make(map[int]Offset) + offsets[0] = Offset{tx: V{0, 0, 0}, rot: noRot} + +solve: + for len(offsets) < len(scanners) { + log.Println("Solved", len(offsets), "/", len(scanners)) + for _, s2 := range scanners { + if _, ok := offsets[s2.id]; ok { + // Already solved + continue + } + + for id, o1 := range offsets { + s1 := scanners[id] + tx2, r2, ok := match(s1, s2, 12) + if ok { + offsets[s2.id] = Offset{ + tx: tx2.rot(o1.rot).plus(o1.tx), + rot: rrot(o1.rot, r2), + } + continue solve + } + } + } + } + return uniqPoints(scanners, offsets) +} + +type Offset struct { + tx V + rot R +} + +func uniqPoints(scanners map[int]Scanner, offsets map[int]Offset) int { + uniq := make(map[V]struct{}) + for _, s := range scanners { + o := offsets[s.id] + for p := range s.readings { + u := p.rot(o.rot).plus(o.tx) + uniq[u] = struct{}{} + } + } + var ps []V + for p := range uniq { + ps = append(ps, p) + } + sort.Slice(ps, func(i, j int) bool { + pi, pj := ps[i], ps[j] + if pi.x != pj.x { + return pi.x < pj.x + } + if pi.y != pj.y { + return pi.y < pj.y + } + if pi.z != pj.z { + return pi.z < pj.z + } + return false + }) + return len(uniq) +} + +func parseInput(text string) (scanners map[int]Scanner) { + scanners = make(map[int]Scanner) + for _, block := range strings.Split(text, "\n\n") { + lines := strings.Split(block, "\n") + + var id int + aoc.MustScan(lines[0], "--- scanner %d ---", &id) + + readings := make(map[V]struct{}) + for _, line := range lines[1:] { + nums := strings.Split(line, ",") + x := aoc.MustInt(nums[0]) + y := aoc.MustInt(nums[1]) + z := aoc.MustInt(nums[2]) + readings[V{x, y, z}] = struct{}{} + } + + scanners[id] = Scanner{id, readings} + } + return +} + +func match(s1, s2 Scanner, req int) (translation V, rotation R, ok bool) { + for a1 := range s1.readings { + for a2 := range s2.readings { + for _, rot := range uniqRot { + tx := a1.minus(a2.rot(rot)) + + var matches int + for p2 := range s2.readings { + p1 := p2.rot(rot).plus(tx) + if _, ok := s1.readings[p1]; ok { + matches++ + } + } + + if matches >= req { + return tx, rot, true + } + } + } + } + return V{}, R{}, false +} + +type Scanner struct { + id int + readings map[V]struct{} +} + +type V struct{ x, y, z int } + +func (v V) plus(u V) V { + return V{v.x + u.x, v.y + u.y, v.z + u.z} +} + +func (v V) minus(u V) V { + return V{v.x - u.x, v.y - u.y, v.z - u.z} +} + +func (v V) dot(u V) int { + return v.x*u.x + v.y*u.y + v.z*u.z +} + +func (v V) rot(r R) V { + return rot(r, v) +} + +type R struct { + xx, xy, xz int + yx, yy, yz int + zx, zy, zz int +} + +func rot(r R, v V) V { + x := r.xx*v.x + r.xy*v.y + r.xz*v.z + y := r.yx*v.x + r.yy*v.y + r.yz*v.z + z := r.zx*v.x + r.zy*v.y + r.zz*v.z + return V{x, y, z} +} + +func rrot(r1, r2 R) R { + rx := V{r1.xx, r1.xy, r1.xz} + ry := V{r1.yx, r1.yy, r1.yz} + rz := V{r1.zx, r1.zy, r1.zz} + + cx := V{r2.xx, r2.yx, r2.zx} + cy := V{r2.xy, r2.yy, r2.zy} + cz := V{r2.xz, r2.yz, r2.zz} + + return R{ + rx.dot(cx), rx.dot(cy), rx.dot(cz), + ry.dot(cx), ry.dot(cy), ry.dot(cz), + rz.dot(cx), rz.dot(cy), rz.dot(cz), + } +} + +var uniqRot []R = genUniqRot() + +var noRot = R{ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, +} + +func genUniqRot() []R { + id := R{ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + } + rx := R{ + 1, 0, 0, + 0, 0, -1, + 0, 1, 0, + } + ry := R{ + 0, 0, 1, + 0, 1, 0, + -1, 0, 0, + } + rz := R{ + 0, -1, 0, + 1, 0, 0, + 0, 0, 1, + } + + uniq := make(map[R]struct{}) + + push := func(ops ...R) { + r := id + for _, op := range ops { + r = rrot(op, r) + } + uniq[r] = struct{}{} + } + + rotations := []R{rx, ry, rz} + + push() + for _, r1 := range rotations { + push(r1) + for _, r2 := range rotations { + push(r1, r2) + for _, r3 := range rotations { + push(r1, r2, r3) + for _, r4 := range rotations { + push(r1, r2, r3, r4) + } + } + } + } + + var rs []R + for r := range uniq { + rs = append(rs, r) + } + return rs +}