diff --git a/2021/day19/main.go b/2021/day19/main.go index affd41e..81fa470 100644 --- a/2021/day19/main.go +++ b/2021/day19/main.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "os" "strings" @@ -10,44 +11,75 @@ import ( func main() { inp := h.StdinToStringSlice() - scanners := parseScanners(inp) - part1(scanners) -} -func part1(scanners []scanner) { - foundBeacons := scanners[0].beacons - foundScannerPositions := []beacon{{0, 0, 0}} - - remaining := scanners[1:] - for len(remaining) > 0 { - fmt.Println(len(remaining), "Remaining") - var wrk scanner - wrk, remaining = remaining[0], remaining[1:] - s, err := NewScanner(foundBeacons).getTransformedIfOverlap(wrk) - if err != nil { - remaining = append(remaining, wrk) - } else { - foundBeacons = append(foundBeacons, s.beacons...) - foundScannerPositions = append(foundScannerPositions, s.position) - } - fmt.Println(" ", foundBeacons) - } + ans1, ans2 := solve(inp) fmt.Println("# Part 1") - fmt.Println(len(foundScannerPositions)) + fmt.Println(ans1) + fmt.Println() + fmt.Println("# Part 2") + fmt.Println(ans2) } -func parseScanners(inp []string) []scanner { - var scanners []scanner +func solve(input []string) (int, int) { + scanners := parseScanners(input) + + solved := []Scanner{scanners[0]} + solved[0].absoluteCoords = solved[0].relativeCoords + solved[0].fillAbsMap() + + // Scanners with a position we don't know yet + unk := scanners[1:] + // loop until we know them all + for len(unk) > 0 { + fmt.Print(h.CLEAR_SCREEN, "Processing...\n") + c, t := len(solved), len(scanners) + h.PrintProgress(c, t) + fmt.Printf(" %d/%d %d%%\n", c, t, int(float64(c)/float64(t)*100)) + + for i, undet := range unk { + ok := undet.findCoords(solved) + if ok { + solved = append(solved, undet) + // remove the determined scanner from the unknown list + copy(unk[i:], unk[i+1:]) + unk = unk[:len(unk)-1] + break + } + } + } + + allBeacons := map[[3]int]bool{} + for _, s := range solved { + for c := range s.absoluteCoordsMap { + allBeacons[c] = true + } + } + + var furthest int + for i, s1 := range solved { + for j, s2 := range solved { + if i == j { + continue + } + dist := h.Abs(s1.x-s2.x) + h.Abs(s1.y-s2.y) + h.Abs(s1.z-s2.z) + if dist > furthest { + furthest = dist + } + } + } + return len(allBeacons), furthest +} + +func parseScanners(inp []string) []Scanner { + var scanners []Scanner for i := range inp { if strings.HasPrefix(inp[i], "---") { - // Find the next empty line end := i for ; end < len(inp); end++ { if inp[end] == "" { break } } - // a new scanner wrk, err := NewScannerFromInput(inp[i:end]) if err == nil { scanners = append(scanners, wrk) @@ -59,3 +91,127 @@ func parseScanners(inp []string) []scanner { } return scanners } + +type Scanner struct { + number int + x, y, z int + relativeCoords [][3]int + rotations [][][3]int + absoluteCoords [][3]int + absoluteCoordsMap map[[3]int]bool +} + +func NewScannerFromInput(inp []string) (Scanner, error) { + s := Scanner{ + absoluteCoordsMap: make(map[[3]int]bool), + } + r := strings.NewReader(inp[0]) + _, err := fmt.Fscanf(r, "--- scanner %d ---", &s.number) + if err != nil { + return s, errors.New("No Scanner ID") + } + for _, v := range inp[1:] { + var x, y, z int + r = strings.NewReader(v) + _, err := fmt.Fscanf(r, "%d,%d,%d", &x, &y, &z) + if err != nil { + return s, err + } + s.relativeCoords = append(s.relativeCoords, [3]int{x, y, z}) + } + s.fillRotations() + return s, nil +} + +func (s *Scanner) fillAbsMap() { + s.absoluteCoordsMap = map[[3]int]bool{} + if len(s.absoluteCoords) == 0 { + panic(fmt.Sprintf("absolute coords not set for scanner %d", s.number)) + } + for _, ac := range s.absoluteCoords { + s.absoluteCoordsMap[ac] = true + } +} + +// Fill in all coordinates for all beacon rotations +func (s *Scanner) fillRotations() { + dir1 := s.relativeCoords + var dir2, dir3, dir4, dir5, dir6 [][3]int + for _, c := range dir1 { + x, y, z := c[0], c[1], c[2] + dir2 = append(dir2, [3]int{x, -y, -z}) + dir3 = append(dir3, [3]int{x, -z, y}) + dir4 = append(dir4, [3]int{-y, -z, x}) + dir5 = append(dir5, [3]int{-x, -z, -y}) + dir6 = append(dir6, [3]int{y, -z, -x}) + } + sixRotations := [][][3]int{ + dir1, dir2, + dir3, dir4, + dir5, dir6, + } + + var finalRotations [][][3]int + for _, rotation := range sixRotations { + var r2, r3, r4 [][3]int + for _, c := range rotation { + x, y, z := c[0], c[1], c[2] + r2 = append(r2, [3]int{-y, x, z}) + r3 = append(r3, [3]int{-x, -y, z}) + r4 = append(r4, [3]int{y, -x, z}) + } + finalRotations = append(finalRotations, rotation, r2, r3, r4) + } + s.rotations = finalRotations +} + +// Find the absolute coordinates for this scanner, if we can. +func (s *Scanner) findCoords(solved []Scanner) bool { + for _, rotatedCoords := range s.rotations { + for _, set := range solved { + for _, absCoord := range set.absoluteCoords { + for _, relativeCoord := range rotatedCoords { + unsolvedAbsoluteCoords := fillAbsCoords(absCoord, relativeCoord, rotatedCoords) + + var matchingCount int + for _, ac := range unsolvedAbsoluteCoords { + if set.absoluteCoordsMap[ac] { + matchingCount++ + } + } + + if matchingCount >= 12 { + s.relativeCoords = rotatedCoords + s.absoluteCoords = unsolvedAbsoluteCoords + s.fillAbsMap() + s.x = absCoord[0] - relativeCoord[0] + s.y = absCoord[1] - relativeCoord[1] + s.z = absCoord[2] - relativeCoord[2] + return true + } + } + } + } + } + return false +} + +// Assume that absolute and relative are actually the same coordinate, calc the diff of all relative Coords +func fillAbsCoords(absolute, relative [3]int, relativeCoords [][3]int) [][3]int { + diff := [3]int{ + absolute[0] - relative[0], + absolute[1] - relative[1], + absolute[2] - relative[2], + } + + var absCoords [][3]int + for _, c := range relativeCoords { + absCoords = append(absCoords, [3]int{ + diff[0] + c[0], + diff[1] + c[1], + diff[2] + c[2], + }) + } + + return absCoords +} diff --git a/2021/day19/main.go.old.2 b/2021/day19/main.go.old.2 deleted file mode 100644 index 933ef37..0000000 --- a/2021/day19/main.go.old.2 +++ /dev/null @@ -1,122 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" - - h "git.bullercodeworks.com/brian/adventofcode/helpers" -) - -func main() { - fmt.Println("vim-go") - inp := h.StdinToStringSlice() - part1(inp) -} - -func part1(inp []string) { - scanners := parseScanners(inp) - m, l := matchScanners(scanners) - fmt.Println(m) - fmt.Println(l) -} - -func parseScanners(inp []string) []Scanner { - var scanners []Scanner - for i := range inp { - if strings.HasPrefix(inp[i], "---") { - // Find the next empty line - end := i - for ; end < len(inp); end++ { - if inp[end] == "" { - break - } - } - // a new scanner - wrk, err := NewScannerFromInput(inp[i:end]) - if err == nil { - scanners = append(scanners, wrk) - } else { - fmt.Println("Error parsing scanners") - os.Exit(1) - } - } - } - return scanners -} - -type Match struct { - beacon Beacon - rotation int -} - -func matchScanners(scanners []Scanner) (map[Pair]Match, []int) { - matched := make(map[Pair]Match) - newMatch := []int{0} - remaining := make(map[int]bool) - for i := 1; i < len(scanners); i++ { - remaining[i] = true - } - prev := make([]int, len(scanners)) - for len(remaining) > 0 { - fmt.Println("Remaining:", len(remaining)) - next := []int{} - for _, mID := range newMatch { - fmt.Println(" Working", mID) - for uID := range remaining { - fmt.Println(" ->", uID) - if _, ok := matched[Pair{mID, uID}]; ok { - continue - } - for _, finger := range scanners[mID].prints { - for _, fWrk := range scanners[uID].prints { - if finger.area == fWrk.area && finger.circumference == fWrk.circumference { - // Found a matching print in both scanners, figure out orientation - tx := findTranslations(finger, fWrk) - fmt.Println("Translations:", tx) - //fb, fRotID := findTranslation(finger, fWrk) - for k, v := range tx { - if len(v) >= 3 { - fmt.Println("Found:", v) - prev[uID] = mID - matched[Pair{mID, uID}] = Match{k, v[0].rotID} - next = append(next, uID) - delete(remaining, uID) - break - } - } - } - } - } - } - } - newMatch = next - } - return matched, prev -} - -func findTranslations(f1, f2 Triangle) map[Beacon][]Translation { - //func findTranslation(f1, f2 Triangle) (Beacon, int) { - tx := make(map[Beacon][]Translation) - for rID := 0; rID < 24; rID++ { - for oIdx, orig := range []Beacon{f1.a, f1.b, f1.c} { - for fIdx, foreign := range []Beacon{f2.a, f2.b, f2.c} { - diff := foreign.Rotate(rID).Sub(orig) - t := Translation{oIdx, fIdx, rID} - if v, ok := tx[diff]; ok { - v = append(v, t) - tx[diff] = v - } else { - tx[diff] = []Translation{t} - } - } - } - for _, v := range tx { - // If we find 3, we have a match - if len(v) >= 3 { - return tx - } - } - } - return tx -} diff --git a/2021/day19/structs.go b/2021/day19/structs.go deleted file mode 100644 index cc8521e..0000000 --- a/2021/day19/structs.go +++ /dev/null @@ -1,126 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "strings" - - h "git.bullercodeworks.com/brian/adventofcode/helpers" -) - -type scanner struct { - number int - region map[h.Coordinate]bool - beacons []beacon -} - -func NewScanner(b []beacon) scanner { - return scanner{beacons: b} -} - -func NewScannerFromInput(inp []string) (scanner, error) { - s := scanner{} - r := strings.NewReader(inp[0]) - _, err := fmt.Fscanf(r, "--- scanner %d ---", &s.number) - if err != nil { - return s, errors.New("No Scanner ID") - } - for _, v := range inp[1:] { - r = strings.NewReader(v) - b := beacon{} - _, err := fmt.Fscanf(r, "%d,%d,%d", &b.x, &b.y, &b.z) - if err == nil { - s.beacons = append(s.beacons, b) - } - } - return s, nil -} - -func (s scanner) allRotations() []scanner { - var ret []scanner - var beacons [][]beacon - for i := range s.beacons { - beacons = append(beacons, s.beacons[i].allRotations()) - } - for i := range beacons { - ret = append(ret, NewScanner(beacons[i])) - } - return ret -} - -func (s scanner) getTransformedIfOverlap(w scanner) (transformedScanner, error) { - for _, otherReoriented := range w.allRotations() { - for _, first := range s.beacons { - for _, second := range otherReoriented.beacons { - otherPosition := first.sub(second) - var otherTransformed []beacon - for _, it := range otherReoriented.beacons { - otherTransformed = append(otherTransformed, otherPosition.add(it)) - } - // Check if otherTransformed has at least 12 beacons in common with s - var count int - for _, chkI := range otherTransformed { - for _, chkJ := range s.beacons { - if chkI.equals(chkJ) { - count++ - if count >= 12 { - return transformedScanner{ - beacons: otherTransformed, - position: otherPosition, - }, nil - } - } - } - } - } - } - } - return transformedScanner{}, errors.New("Scanners don't overlap") -} - -func (s scanner) String() string { - return fmt.Sprintf("-- Scanner %d (%d beacons) ---", s.number, len(s.beacons)) -} - -type beacon struct { - x, y, z int -} - -func (b beacon) sub(c beacon) beacon { - return beacon{ - x: b.x - c.x, - y: b.y - c.y, - z: b.z - c.z, - } -} - -func (b beacon) add(c beacon) beacon { - return beacon{ - x: b.x + c.x, - y: b.y + c.y, - z: b.z + c.z, - } -} - -func (b beacon) equals(c beacon) bool { - return b.x == c.x && b.y == c.y && b.z == c.z -} - -func (b beacon) allRotations() []beacon { - return []beacon{ - {b.x, b.y, b.z}, {b.x, -b.z, b.y}, {b.x, -b.y, -b.z}, {b.x, b.z, -b.y}, {-b.x, -b.y, b.z}, - {-b.x, -b.z, -b.y}, {-b.x, b.y, -b.z}, {-b.x, b.z, b.y}, {-b.z, b.x, -b.y}, {b.y, b.x, -b.z}, - {b.z, b.x, b.y}, {-b.y, b.x, b.z}, {b.z, -b.x, -b.y}, {b.y, -b.x, b.z}, {-b.z, -b.x, b.y}, - {-b.y, -b.x, -b.z}, {-b.y, -b.z, b.x}, {b.z, -b.y, b.x}, {b.y, b.z, b.x}, {-b.z, b.y, b.x}, - {b.z, b.y, -b.x}, {-b.y, b.z, -b.x}, {-b.z, -b.y, -b.x}, {b.y, -b.z, -b.x}, - } -} - -func (b beacon) String() string { - return fmt.Sprintf("{%d,%d,%d}", b.x, b.y, b.z) -} - -type transformedScanner struct { - beacons []beacon - position beacon -} diff --git a/2021/day19/structs.go.old.2 b/2021/day19/structs.go.old.2 deleted file mode 100644 index a77b1e5..0000000 --- a/2021/day19/structs.go.old.2 +++ /dev/null @@ -1,192 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "sort" - "strings" - - h "git.bullercodeworks.com/brian/adventofcode/helpers" -) - -type Pair struct { - a int - b int -} - -type Translation struct { - orig, foreign, rotID int -} - -type Triangle struct { - a, b, c Beacon - circumference, area int -} - -func NewTriangle(a, b, c Beacon) Triangle { - t := Triangle{a: a, b: b, c: c} - t.circumference = findCircumference(a, b, c) - t.area = findArea(a, b, c) - return t -} - -type Scanner struct { - number int - beacons []Beacon - prints []Triangle -} - -func NewScannerFromInput(inp []string) (Scanner, error) { - s := Scanner{} - r := strings.NewReader(inp[0]) - _, err := fmt.Fscanf(r, "--- scanner %d ---", &s.number) - if err != nil { - return s, errors.New("No Scanner ID") - } - for _, v := range inp[1:] { - r = strings.NewReader(v) - b := Beacon{} - _, err := fmt.Fscanf(r, "%d,%d,%d", &b.x, &b.y, &b.z) - if err == nil { - s.beacons = append(s.beacons, b) - } - } - prints := make([]Triangle, 0, len(s.beacons)) - dupes := make(map[Pair]struct{}) - for _, b := range s.beacons { - c1, c2 := b.findNearestTwo(s.beacons) - t := NewTriangle(b, c1, c2) - p := Pair{t.area, t.circumference} - if _, ok := dupes[p]; !ok { - prints = append(prints, t) - } - dupes[p] = struct{}{} - } - - return s, nil -} - -type Beacon struct { - x, y, z int -} - -func (b Beacon) Add(c Beacon) Beacon { - return Beacon{ - x: b.x + c.x, - y: b.y + c.y, - z: b.z + c.z, - } -} - -func (b Beacon) Sub(c Beacon) Beacon { - return Beacon{ - x: b.x - c.x, - y: b.y - c.y, - z: b.z - c.z, - } -} - -func (b Beacon) Negate() Beacon { - return Beacon{ - x: -b.x, - y: -b.y, - z: -b.z, - } -} - -func (b Beacon) Equals(c Beacon) bool { - return b.x == c.x && b.y == c.y && b.z == c.z -} - -func (b Beacon) RotateX90() Beacon { - return Beacon{ - x: b.x, - y: -b.z, - z: b.y, - } -} - -func (b Beacon) RotateY90() Beacon { - return Beacon{ - x: -b.z, - y: b.y, - z: b.x, - } -} - -func (b Beacon) RotateZ90() Beacon { - return Beacon{ - x: b.y, - y: -b.x, - z: b.z, - } -} - -func (b Beacon) AllRotations() []Beacon { - return []Beacon{ - {b.x, b.y, b.z}, - {b.x, -b.z, b.y}, - {b.x, -b.y, -b.z}, - {b.x, b.z, -b.y}, - {-b.x, -b.y, b.z}, - {-b.x, -b.z, -b.y}, - {-b.x, b.y, -b.z}, - {-b.x, b.z, b.y}, - {-b.z, b.x, -b.y}, - {b.y, b.x, -b.z}, - {b.z, b.x, b.y}, - {-b.y, b.x, b.z}, - {b.z, -b.x, -b.y}, - {b.y, -b.x, b.z}, - {-b.z, -b.x, b.y}, - {-b.y, -b.x, -b.z}, - {-b.y, -b.z, b.x}, - {b.z, -b.y, b.x}, - {b.y, b.z, b.x}, - {-b.z, b.y, b.x}, - {b.z, b.y, -b.x}, - {-b.y, b.z, -b.x}, - {-b.z, -b.y, -b.x}, - {b.y, -b.z, -b.x}, - } -} - -func (b Beacon) Rotate(rid int) Beacon { - l := b.AllRotations() - if rid < len(l) { - return l[rid] - } - return b -} - -func (b Beacon) Distance(c Beacon) int { - return h.Abs(b.x-c.x) + h.Abs(b.y-c.y) + h.Abs(b.z-c.z) -} - -func (b Beacon) findNearestTwo(list []Beacon) (Beacon, Beacon) { - var distances []Pair - for k, v := range list { - if !b.Equals(v) { - distances = append(distances, Pair{a: b.Distance(v), b: k}) - } - } - sort.Slice(distances, func(i, j int) bool { return distances[i].a < distances[j].a }) - return list[distances[0].b], list[distances[1].b] -} - -func findCircumference(a, b, c Beacon) int { - return a.Distance(b) + b.Distance(c) + c.Distance(a) -} - -func findArea(a, b, c Beacon) int { - wrk := crossProduct(b.Sub(a), c.Sub(a)) - return h.Abs(wrk.x) + h.Abs(wrk.y) + h.Abs(wrk.z) -} - -func crossProduct(a, b Beacon) Beacon { - return Beacon{ - x: a.y*b.z - a.z*b.y, - y: a.z*b.x - a.x*b.z, - z: a.x*b.y - a.y*b.x, - } -} diff --git a/helpers/helpers.go b/helpers/helpers.go index ec8e9b7..edbb1e9 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -182,12 +182,16 @@ func FileToBytes(fn string) []byte { // PrintProgress is for outputting a progress bar func PrintProgress(curr, total int) { - pct := int(float64(curr)/float64(total)) * 100 + pct := int(float64(curr) / float64(total) * 100) + fmt.Print("[") for i := 0; i < 100; i += 10 { if pct > i { fmt.Print(FILL_CHAR) + } else { + fmt.Print(" ") } } + fmt.Print("]") } // StringPermutations takes a string and returns all permutations of it