Add day 13 part 2 solution
parent
5d8f753a96
commit
7ff46673da
149
day13/main.go
149
day13/main.go
|
@ -10,11 +10,45 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Coordinate represents a position in our pattern
|
||||||
type Coordinate struct {
|
type Coordinate struct {
|
||||||
row int
|
row int
|
||||||
col int
|
col int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SmudgeComparer is a stateful comparator between two slices. The idea is that for a given chain of comparisons,
|
||||||
|
// the two arrays will be allowed to differ by exactly one item, exactly once. All other times, they must be
|
||||||
|
// exactly identical.
|
||||||
|
type SmudgeComparer struct {
|
||||||
|
haveDoneModification bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComparePossiblySmudgedLine performs the smudged comparison. See struct doc for more details
|
||||||
|
func (comparer *SmudgeComparer) ComparePossiblySmudgedLine(a []int, b []int) bool {
|
||||||
|
if comparer.haveDoneModification {
|
||||||
|
return slices.Equal(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := findExtraItem(a, b)
|
||||||
|
if errors.Is(err, errSlicesEqual) {
|
||||||
|
return true
|
||||||
|
} else if errors.Is(err, errSlicesDiffer) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
comparer.haveDoneModification = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HaveDoneModification indicates whether or not a modification has been performed during previous runs of
|
||||||
|
// / ComparePossiblySmudgedLine
|
||||||
|
func (comparer *SmudgeComparer) HaveDoneModification() bool {
|
||||||
|
return comparer.haveDoneModification
|
||||||
|
}
|
||||||
|
|
||||||
|
var errSlicesEqual = errors.New("slices are equal")
|
||||||
|
var errSlicesDiffer = errors.New("slices differ by more than one element")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) != 2 && len(os.Args) != 3 {
|
if len(os.Args) != 2 && len(os.Args) != 3 {
|
||||||
fmt.Fprintf(os.Stderr, "Usage: %s inputfile\n", os.Args[0])
|
fmt.Fprintf(os.Stderr, "Usage: %s inputfile\n", os.Args[0])
|
||||||
|
@ -42,6 +76,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Part 1: %d\n", part1(patternSections))
|
fmt.Printf("Part 1: %d\n", part1(patternSections))
|
||||||
|
fmt.Printf("Part 2: %d\n", part2(patternSections))
|
||||||
}
|
}
|
||||||
|
|
||||||
func part1(sections []map[Coordinate]struct{}) int {
|
func part1(sections []map[Coordinate]struct{}) int {
|
||||||
|
@ -58,11 +93,26 @@ func part1(sections []map[Coordinate]struct{}) int {
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func part2(sections []map[Coordinate]struct{}) int {
|
||||||
|
total := 0
|
||||||
|
for i, section := range sections {
|
||||||
|
sectionResults, err := evaluateSmudgedSection(section)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("invalid section %d: %s", i, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
total += sectionResults
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluateSection will summarize the given section according to the mirror rules for part 1
|
||||||
func evaluateSection(section map[Coordinate]struct{}) (int, error) {
|
func evaluateSection(section map[Coordinate]struct{}) (int, error) {
|
||||||
maxRow := maxAlongAxis(Coordinate{row: 1, col: 0}, section)
|
maxRow := maxAlongAxis(Coordinate{row: 1, col: 0}, section)
|
||||||
byRow := getAllAlongAxis(Coordinate{row: 1, col: 0}, section)
|
byRow := getAllAlongAxis(Coordinate{row: 1, col: 0}, section)
|
||||||
for row := 0; row < maxRow; row++ {
|
for row := 0; row < maxRow; row++ {
|
||||||
if isMirroredAcrossAxis(row, byRow) {
|
if isMirroredAcrossAxis(row, byRow, slices.Equal) {
|
||||||
return (row + 1) * 100, nil
|
return (row + 1) * 100, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +120,7 @@ func evaluateSection(section map[Coordinate]struct{}) (int, error) {
|
||||||
maxCol := maxAlongAxis(Coordinate{row: 0, col: 1}, section)
|
maxCol := maxAlongAxis(Coordinate{row: 0, col: 1}, section)
|
||||||
byCol := getAllAlongAxis(Coordinate{row: 0, col: 1}, section)
|
byCol := getAllAlongAxis(Coordinate{row: 0, col: 1}, section)
|
||||||
for col := 0; col < maxCol; col++ {
|
for col := 0; col < maxCol; col++ {
|
||||||
if isMirroredAcrossAxis(col, byCol) {
|
if isMirroredAcrossAxis(col, byCol, slices.Equal) {
|
||||||
return col + 1, nil
|
return col + 1, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,13 +128,38 @@ func evaluateSection(section map[Coordinate]struct{}) (int, error) {
|
||||||
return 0, errors.New("section is not mirrored")
|
return 0, errors.New("section is not mirrored")
|
||||||
}
|
}
|
||||||
|
|
||||||
func isMirroredAcrossAxis(axisIdx int, byAxis map[int][]int) bool {
|
// evaluateSection will summarize the given section according to the mirror rules for part 2
|
||||||
|
func evaluateSmudgedSection(section map[Coordinate]struct{}) (int, error) {
|
||||||
|
maxRow := maxAlongAxis(Coordinate{row: 1, col: 0}, section)
|
||||||
|
byRow := getAllAlongAxis(Coordinate{row: 1, col: 0}, section)
|
||||||
|
for row := 0; row < maxRow; row++ {
|
||||||
|
comparer := SmudgeComparer{}
|
||||||
|
if isMirroredAcrossAxis(row, byRow, comparer.ComparePossiblySmudgedLine) && comparer.haveDoneModification {
|
||||||
|
return (row + 1) * 100, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maxCol := maxAlongAxis(Coordinate{row: 0, col: 1}, section)
|
||||||
|
byCol := getAllAlongAxis(Coordinate{row: 0, col: 1}, section)
|
||||||
|
for col := 0; col < maxCol; col++ {
|
||||||
|
comparer := SmudgeComparer{}
|
||||||
|
if isMirroredAcrossAxis(col, byCol, comparer.ComparePossiblySmudgedLine) && comparer.haveDoneModification {
|
||||||
|
return col + 1, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, errors.New("section is not mirrored")
|
||||||
|
}
|
||||||
|
|
||||||
|
// isMirroredAcrossAxis checks, For the given item along an axis, and a mapping between current axis items and
|
||||||
|
// perpendicular axis items, whether or not the surrounding rows could be considered mirrored
|
||||||
|
func isMirroredAcrossAxis(axisIdx int, byAxis map[int][]int, eqFunc func(a []int, b []int) bool) bool {
|
||||||
maxAxisIdx := maxKey(byAxis)
|
maxAxisIdx := maxKey(byAxis)
|
||||||
// Radiate "outwards" from the axis item, and check the corresponding elements
|
// Radiate "outwards" from the axis item, and check the corresponding elements
|
||||||
for offset := 1; axisIdx+offset <= maxAxisIdx && axisIdx-(offset-1) >= 0; offset++ {
|
for offset := 1; axisIdx+offset <= maxAxisIdx && axisIdx-(offset-1) >= 0; offset++ {
|
||||||
rowItems := byAxis[axisIdx-(offset-1)]
|
axisItems := byAxis[axisIdx-(offset-1)]
|
||||||
oppositeItems := byAxis[axisIdx+offset]
|
oppositeItems := byAxis[axisIdx+offset]
|
||||||
if !slices.Equal(rowItems, oppositeItems) {
|
if !eqFunc(axisItems, oppositeItems) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,6 +167,58 @@ func isMirroredAcrossAxis(axisIdx int, byAxis map[int][]int) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// findExtraItem finds the index of a standalone differing item in each list. If one of the two return values
|
||||||
|
// is not negative one, that is the index of the standout item (note that only one will be > -1 at a time).
|
||||||
|
// If the two slices are equal, errSlicesEqual is returned. If the two slices differ by more than one element,
|
||||||
|
// errSlicesDiffer is returned.
|
||||||
|
//
|
||||||
|
// NOTE: this function assumes that these lists are sorted
|
||||||
|
func findExtraItem(a, b []int) (int, int, error) {
|
||||||
|
if abs(len(a)-len(b)) > 1 {
|
||||||
|
return -1, -1, errSlicesDiffer
|
||||||
|
}
|
||||||
|
|
||||||
|
aCursor := 0
|
||||||
|
bCursor := 0
|
||||||
|
aCandidate := -1
|
||||||
|
bCandidate := -1
|
||||||
|
for aCursor < len(a) && bCursor < len(b) {
|
||||||
|
aItem := a[aCursor]
|
||||||
|
bItem := b[bCursor]
|
||||||
|
|
||||||
|
if aItem == bItem {
|
||||||
|
aCursor++
|
||||||
|
bCursor++
|
||||||
|
} else if aItem < bItem {
|
||||||
|
if aCandidate != -1 || bCandidate != -1 {
|
||||||
|
return -1, -1, errSlicesDiffer
|
||||||
|
}
|
||||||
|
|
||||||
|
aCandidate = aCursor
|
||||||
|
aCursor++
|
||||||
|
} else {
|
||||||
|
if aCandidate != -1 || bCandidate != -1 {
|
||||||
|
return -1, -1, errSlicesDiffer
|
||||||
|
}
|
||||||
|
|
||||||
|
bCandidate = bCursor
|
||||||
|
bCursor++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if aCursor < len(a) && bCursor >= len(b) {
|
||||||
|
return aCursor, -1, nil
|
||||||
|
} else if bCursor >= len(a) && bCursor < len(b) {
|
||||||
|
return -1, bCursor, nil
|
||||||
|
} else if aCandidate == -1 && bCandidate == -1 {
|
||||||
|
return -1, -1, errSlicesEqual
|
||||||
|
}
|
||||||
|
|
||||||
|
return aCandidate, bCandidate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// maxAlongAxis finds the max value along the given axis, using a coordinate set of {1, 0} or {0, 1}
|
||||||
|
// Panics if an invalid axis is given
|
||||||
func maxAlongAxis(axis Coordinate, section map[Coordinate]struct{}) int {
|
func maxAlongAxis(axis Coordinate, section map[Coordinate]struct{}) int {
|
||||||
if !(axis.row == 0 && axis.col == 1 || axis.row == 1 && axis.col == 0) {
|
if !(axis.row == 0 && axis.col == 1 || axis.row == 1 && axis.col == 0) {
|
||||||
panic("invalid axis")
|
panic("invalid axis")
|
||||||
|
@ -108,6 +235,10 @@ func maxAlongAxis(axis Coordinate, section map[Coordinate]struct{}) int {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getAllAlongAxis gets all items along the given axis, with the resulting map holding each index along that axis
|
||||||
|
// and the values holding all elements that are perpendicular to that axis
|
||||||
|
//
|
||||||
|
// Panics if an invalid axis is given
|
||||||
func getAllAlongAxis(axis Coordinate, section map[Coordinate]struct{}) map[int][]int {
|
func getAllAlongAxis(axis Coordinate, section map[Coordinate]struct{}) map[int][]int {
|
||||||
if !(axis.row == 0 && axis.col == 1 || axis.row == 1 && axis.col == 0) {
|
if !(axis.row == 0 && axis.col == 1 || axis.row == 1 && axis.col == 0) {
|
||||||
panic("invalid axis")
|
panic("invalid axis")
|
||||||
|
@ -171,3 +302,11 @@ func tryParse[T any](items []string, parse func(string) (T, error)) ([]T, error)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func abs(n int) int {
|
||||||
|
if n < 0 {
|
||||||
|
return -n
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue