Skip to content

Instantly share code, notes, and snippets.

@echojc
Created December 19, 2021 23:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save echojc/7ac68851c81a3bd99084d0266f1503c6 to your computer and use it in GitHub Desktop.
Save echojc/7ac68851c81a3bd99084d0266f1503c6 to your computer and use it in GitHub Desktop.
My solution for Day 19 of Advent of Code 2021.
package main
import (
"fmt"
"log"
"math"
"strings"
)
// ScanResult is just a type alias - we treat this as a set
type ScanResult map[Vector3]bool
// Vector3 holds a point in 3D space and has basic helper functions such as
// Add(), Sub(), Inverse(), and so on
type Vector3 struct {
X, Y, Z int
}
func main() {
scans := parseData(exampleData())
//scans := parseData(actualData())
// init the ocean with points from scans[0] - this will be our reference
// coordinate system and all other points are mapped to this
ocean := ScanResult{}
for p := range scans[0] {
ocean[p] = true
}
// track all scanner locations
scannerLocs := []Vector3{Vector3{0, 0, 0}}
// scans is what is left to map
// remove scans[0] since we have already added that to the ocean
scans = scans[1:]
// until all scans are mapped
for len(scans) > 0 {
outer:
// we want to keep trying to map every scan, but we don't know the order, so
// we just keep trying them until there is enough data in the ocean to map
// them all
for i := len(scans) - 1; i >= 0; i-- {
// loop backwards so we can delete from the array as we go
// for each scan, try all rotations to find a fit
for rotID := 0; rotID < 24; rotID++ {
// To determine a fit:
// - take a pair of points, one from each scan result and assume they
// are the same point
// - find the offset for the current rotation and store it
// - if this rotation is a fit, multiple points (>12) will have the
// same offset
offsets := map[Vector3]int{}
for knownPoint := range ocean {
for p := range scans[i] {
offset := p.Rotate(rotID).Sub(knownPoint)
offsets[offset]++
}
}
for offset, count := range offsets {
if count >= 12 { // found a valid mapping
// scanner is at the inverse of the translation vector
scanner := offset.Inverse()
scannerLocs = append(scannerLocs, scanner)
// adjust each point in this scan relative to scans[0] and add it
// to the ocean for other scans to match against
for p := range scans[i] {
mappedPoint := p.Rotate(rotID).Add(scanner)
ocean[mappedPoint] = true
}
fmt.Println(rotID, scanner, count)
// done with the current scanresult, remove it
scans = append(scans[:i], scans[i+1:]...)
continue outer
}
}
}
}
}
fmt.Println("Part 1:", len(ocean))
maxDist := 0
for i := 0; i < len(scannerLocs); i++ {
for j := 1; j < len(scannerLocs); j++ {
dist := scannerLocs[i].Dist(scannerLocs[j])
if dist > maxDist {
maxDist = dist
}
}
}
fmt.Println("Part 2:", maxDist)
}
func (v Vector3) Dist(w Vector3) int {
return int(math.Abs(float64(v.X-w.X)) +
math.Abs(float64(v.Y-w.Y)) +
math.Abs(float64(v.Z-w.Z)))
}
func (v Vector3) Add(w Vector3) Vector3 {
return Vector3{
X: v.X + w.X,
Y: v.Y + w.Y,
Z: v.Z + w.Z,
}
}
func (v Vector3) Sub(w Vector3) Vector3 {
return Vector3{
X: v.X - w.X,
Y: v.Y - w.Y,
Z: v.Z - w.Z,
}
}
func (v Vector3) Inverse() Vector3 {
return Vector3{-v.X, -v.Y, -v.Z}
}
/// Rotate takes an int 0-23 to represent one of the 24 3D rotations
func (v Vector3) Rotate(id int) Vector3 {
switch id {
case 0:
return Vector3{v.X, v.Y, v.Z}
case 1:
return Vector3{v.X, -v.Z, v.Y}
case 2:
return Vector3{v.X, -v.Y, -v.Z}
case 3:
return Vector3{v.X, v.Z, -v.Y}
case 4:
return Vector3{-v.X, -v.Y, v.Z}
case 5:
return Vector3{-v.X, -v.Z, -v.Y}
case 6:
return Vector3{-v.X, v.Y, -v.Z}
case 7:
return Vector3{-v.X, v.Z, v.Y}
case 8:
return Vector3{v.Y, v.X, -v.Z}
case 9:
return Vector3{v.Y, -v.X, v.Z}
case 10:
return Vector3{v.Y, v.Z, v.X}
case 11:
return Vector3{v.Y, -v.Z, -v.X}
case 12:
return Vector3{-v.Y, v.X, v.Z}
case 13:
return Vector3{-v.Y, -v.X, -v.Z}
case 14:
return Vector3{-v.Y, -v.Z, v.X}
case 15:
return Vector3{-v.Y, v.Z, -v.X}
case 16:
return Vector3{v.Z, v.X, v.Y}
case 17:
return Vector3{v.Z, -v.X, -v.Y}
case 18:
return Vector3{v.Z, -v.Y, v.X}
case 19:
return Vector3{v.Z, v.Y, -v.X}
case 20:
return Vector3{-v.Z, v.X, -v.Y}
case 21:
return Vector3{-v.Z, -v.X, v.Y}
case 22:
return Vector3{-v.Z, v.Y, v.X}
case 23:
return Vector3{-v.Z, -v.Y, -v.X}
default:
panic(id)
}
}
func actualData() []string {
// load your input
return nil
}
func exampleData() []string {
return []string{
"--- scanner 0 ---",
"404,-588,-901",
"528,-643,409",
"-838,591,734",
"390,-675,-793",
"-537,-823,-458",
"-485,-357,347",
"-345,-311,381",
"-661,-816,-575",
"-876,649,763",
"-618,-824,-621",
"553,345,-567",
"474,580,667",
"-447,-329,318",
"-584,868,-557",
"544,-627,-890",
"564,392,-477",
"455,729,728",
"-892,524,684",
"-689,845,-530",
"423,-701,434",
"7,-33,-71",
"630,319,-379",
"443,580,662",
"-789,900,-551",
"459,-707,401",
"",
"--- scanner 1 ---",
"686,422,578",
"605,423,415",
"515,917,-361",
"-336,658,858",
"95,138,22",
"-476,619,847",
"-340,-569,-846",
"567,-361,727",
"-460,603,-452",
"669,-402,600",
"729,430,532",
"-500,-761,534",
"-322,571,750",
"-466,-666,-811",
"-429,-592,574",
"-355,545,-477",
"703,-491,-529",
"-328,-685,520",
"413,935,-424",
"-391,539,-444",
"586,-435,557",
"-364,-763,-893",
"807,-499,-711",
"755,-354,-619",
"553,889,-390",
"",
"--- scanner 2 ---",
"649,640,665",
"682,-795,504",
"-784,533,-524",
"-644,584,-595",
"-588,-843,648",
"-30,6,44",
"-674,560,763",
"500,723,-460",
"609,671,-379",
"-555,-800,653",
"-675,-892,-343",
"697,-426,-610",
"578,704,681",
"493,664,-388",
"-671,-858,530",
"-667,343,800",
"571,-461,-707",
"-138,-166,112",
"-889,563,-600",
"646,-828,498",
"640,759,510",
"-630,509,768",
"-681,-892,-333",
"673,-379,-804",
"-742,-814,-386",
"577,-820,562",
"",
"--- scanner 3 ---",
"-589,542,597",
"605,-692,669",
"-500,565,-823",
"-660,373,557",
"-458,-679,-417",
"-488,449,543",
"-626,468,-788",
"338,-750,-386",
"528,-832,-391",
"562,-778,733",
"-938,-730,414",
"543,643,-506",
"-524,371,-870",
"407,773,750",
"-104,29,83",
"378,-903,-323",
"-778,-728,485",
"426,699,580",
"-438,-605,-362",
"-469,-447,-387",
"509,732,623",
"647,635,-688",
"-868,-804,481",
"614,-800,639",
"595,780,-596",
"",
"--- scanner 4 ---",
"727,592,562",
"-293,-554,779",
"441,611,-461",
"-714,465,-776",
"-743,427,-804",
"-660,-479,-426",
"832,-632,460",
"927,-485,-438",
"408,393,-506",
"466,436,-512",
"110,16,151",
"-258,-428,682",
"-393,719,612",
"-211,-452,876",
"808,-476,-593",
"-575,615,604",
"-485,667,467",
"-680,325,-822",
"-627,-443,-432",
"872,-547,-609",
"833,512,582",
"807,604,487",
"839,-516,451",
"891,-625,532",
"-652,-548,-490",
"30,-46,-14",
}
}
func parseData(data []string) []ScanResult {
scans := []ScanResult{}
var scan ScanResult
for i := 0; i < len(data); i++ {
if data[i] == "" {
continue
}
if strings.Index(data[i], "scanner") >= 0 {
scan = ScanResult{}
scans = append(scans, scan)
continue
}
v := Vector3{}
fmt.Sscanf(data[i], "%d,%d,%d", &v.X, &v.Y, &v.Z)
scan[v] = true
}
return scans
}
func (s ScanResult) String() string {
var sb strings.Builder
sb.WriteByte('[')
sep := ""
for p := range s {
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("%v", p))
sep = " "
}
sb.WriteByte(']')
return sb.String()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment