290 lines
7.9 KiB
Go
290 lines
7.9 KiB
Go
package day21
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
func Run() (result int) {
|
|
fmt.Print("hello day21")
|
|
filename := "day21/input"
|
|
field := ReadField(filename)
|
|
log.Print(field)
|
|
|
|
initialSaturatedFields := make(map[Coord]any)
|
|
log.Print(initialSaturatedFields)
|
|
|
|
// for i := 6; i <= 10; i++ {
|
|
// reachableBySteps := field.ReachableBySteps(i, map[Coord]any{
|
|
// Coord{Row: field.RowStart, Col: field.ColStart}: struct{}{},
|
|
// })
|
|
|
|
// log.Print("reachable after steps : ", i, len(reachableBySteps))
|
|
// field.PrintCoord(reachableBySteps, 1)
|
|
// }
|
|
|
|
steps := 26501365
|
|
reachableBySteps := field.ReachableBySteps(
|
|
steps,
|
|
map[FieldPoint]any{
|
|
FieldPoint{
|
|
InField: Coord{Row: field.RowStart, Col: field.ColStart},
|
|
}: struct{}{}},
|
|
make(map[Coord]int),
|
|
steps)
|
|
result = reachableBySteps
|
|
log.Print("reachable after steps : ", steps, result)
|
|
|
|
return result
|
|
}
|
|
|
|
// let's do dijkstra?
|
|
// i would need lots of space for edges?
|
|
// let's use a map with minimal distances?
|
|
// OR. just breath first traversal
|
|
|
|
type Field struct {
|
|
RowStart, ColStart int
|
|
symbols [][]rune
|
|
SaturatedEvenCount, SaturatedOddCount int
|
|
}
|
|
|
|
type Coord struct {
|
|
Row, Col int
|
|
}
|
|
|
|
type FieldPoint struct {
|
|
InField Coord
|
|
MetaField Coord
|
|
}
|
|
|
|
func (f Field) ReachableBySteps(n int, startingAt map[FieldPoint]any, saturatedFields map[Coord]int, initialSteps int) (countReachable int) {
|
|
if n%100 == 0 {
|
|
log.Println("going step: ", n)
|
|
}
|
|
if n == 0 {
|
|
sizeOfUnsaturated := len(startingAt)
|
|
sizeOfSaturated := 0
|
|
// log.Printf("> before adding saturated fields. central is in even %t\n", CentralFieldIsInEven(initialSteps, n))
|
|
for saturatedField := range saturatedFields {
|
|
isEven := FieldIsInEven(initialSteps, n, saturatedField)
|
|
// log.Printf("> adding saturated field %+v. it is in even %t\n", saturatedField, isEven)
|
|
if isEven {
|
|
sizeOfSaturated += f.SaturatedEvenCount
|
|
} else {
|
|
sizeOfSaturated += f.SaturatedOddCount
|
|
}
|
|
}
|
|
|
|
return sizeOfUnsaturated + sizeOfSaturated
|
|
}
|
|
// else collect directly available
|
|
|
|
oneStepExpanded := make(map[FieldPoint]any)
|
|
for cur := range startingAt {
|
|
for _, neighbor := range f.Neighbors(cur, saturatedFields) {
|
|
oneStepExpanded[neighbor] = struct{}{}
|
|
}
|
|
}
|
|
|
|
metaFields := make(map[Coord]int)
|
|
for next := range oneStepExpanded {
|
|
metaFields[next.MetaField] += 1
|
|
}
|
|
|
|
for workedUponFieldCoord, amount := range metaFields {
|
|
isEven := FieldIsInEven(initialSteps, n, workedUponFieldCoord)
|
|
if workedUponFieldCoord.Col == 0 && workedUponFieldCoord.Row == 0 {
|
|
// log.Printf("checking %+v : %d as worked fields for saturation. isEven %t", workedUponFieldCoord, amount, isEven)
|
|
}
|
|
if isEven && amount == f.SaturatedEvenCount {
|
|
log.Printf(">>> adding %+v to saturated, with amount %d\n", workedUponFieldCoord, amount)
|
|
saturatedFields[workedUponFieldCoord] = n
|
|
}
|
|
if !isEven && amount == f.SaturatedOddCount {
|
|
log.Printf(">>> adding %+v to saturated, with amount %d\n", workedUponFieldCoord, amount)
|
|
saturatedFields[workedUponFieldCoord] = n
|
|
}
|
|
}
|
|
|
|
for point := range oneStepExpanded {
|
|
saturatedAtStep, fromSaturated := saturatedFields[point.MetaField]
|
|
// hack. to not remove points from saturated fields too early
|
|
if fromSaturated && (saturatedAtStep - n > 200) {
|
|
delete(oneStepExpanded, point)
|
|
}
|
|
}
|
|
|
|
// if n < 4 {
|
|
// log.Print("reachable after steps : ", n, len(oneStepExpanded))
|
|
// f.PrintCoord(oneStepExpanded, 5)
|
|
// }
|
|
|
|
return f.ReachableBySteps(n-1, oneStepExpanded, saturatedFields, initialSteps)
|
|
}
|
|
|
|
func CentralFieldIsInEven(initialSteps, currentSteps int) bool {
|
|
// off by one here because on initial step we first do 'neighbors' then comparicons
|
|
return (initialSteps-currentSteps)%2 != 0
|
|
}
|
|
|
|
func FieldIsInEven(initialSteps, currentSteps int, metaCoord Coord) bool {
|
|
centralIsInEven := CentralFieldIsInEven(initialSteps, currentSteps)
|
|
fieldIsInSyncWithCentral := (metaCoord.Col+metaCoord.Row)%2 == 0
|
|
if fieldIsInSyncWithCentral {
|
|
return centralIsInEven
|
|
} else {
|
|
return !centralIsInEven
|
|
}
|
|
}
|
|
|
|
func (f Field) Neighbors(c FieldPoint, saturatedFields map[Coord]int) (resut []FieldPoint) {
|
|
closeCoords := []FieldPoint{
|
|
{InField: Coord{Row: c.InField.Row + 1, Col: c.InField.Col}, MetaField: c.MetaField},
|
|
{InField: Coord{Row: c.InField.Row - 1, Col: c.InField.Col}, MetaField: c.MetaField},
|
|
{InField: Coord{Row: c.InField.Row, Col: c.InField.Col + 1}, MetaField: c.MetaField},
|
|
{InField: Coord{Row: c.InField.Row, Col: c.InField.Col - 1}, MetaField: c.MetaField},
|
|
}
|
|
|
|
for i, close := range closeCoords {
|
|
height := len(f.symbols)
|
|
width := len(f.symbols[0])
|
|
if close.InField.Row == height {
|
|
close.InField.Row = 0
|
|
close.MetaField.Row += 1
|
|
}
|
|
if close.InField.Row == -1 {
|
|
close.InField.Row = height - 1
|
|
close.MetaField.Row -= 1
|
|
}
|
|
if close.InField.Col == width {
|
|
close.InField.Col = 0
|
|
close.MetaField.Col += 1
|
|
}
|
|
if close.InField.Col == -1 {
|
|
// log.Printf("moving COL to lefter field from %d to %d", close.Col, width-1)
|
|
close.InField.Col = width - 1
|
|
close.MetaField.Col -= 1
|
|
}
|
|
closeCoords[i] = close
|
|
// but this is not it. i need to store the XX and YY
|
|
// so that points in other 'fields' would count separately. yuk
|
|
}
|
|
|
|
for _, close := range closeCoords {
|
|
if f.ValidCoord(close.InField.Row, close.InField.Col) {
|
|
symb := f.symbols[close.InField.Row][close.InField.Col]
|
|
_, fieldIsAlreadySaturated := saturatedFields[close.MetaField]
|
|
if (symb == '.' || symb == 'S') && !fieldIsAlreadySaturated {
|
|
resut = append(resut, close)
|
|
}
|
|
}
|
|
}
|
|
|
|
// log.Print("getting neighbors for ", c, resut)
|
|
|
|
return
|
|
}
|
|
|
|
func (f Field) ValidCoord(row, col int) bool {
|
|
// log.Print("check valid ", row, col, row >= 0 && row < len(f.symbols) && col >= 0 && col < len(f.symbols[0]))
|
|
|
|
valid := row >= 0 && row < len(f.symbols) && col >= 0 && col < len(f.symbols[0])
|
|
if !valid {
|
|
panic(fmt.Sprint("getting invalid coord: ", row, col))
|
|
}
|
|
return valid
|
|
}
|
|
|
|
func (f Field) String() (result string) {
|
|
result += "\n"
|
|
for _, line := range f.symbols {
|
|
result += string(line)
|
|
result += "\n"
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func ReadField(filename string) (result Field) {
|
|
bytes, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
text := strings.TrimSpace(string(bytes))
|
|
|
|
lines := strings.Split(text, "\n")
|
|
rows := make([][]rune, len(lines))
|
|
for rowNum, line := range lines {
|
|
rows[rowNum] = []rune(line)
|
|
for colNum, symb := range line {
|
|
if symb == 'S' {
|
|
result.RowStart = rowNum
|
|
result.ColStart = colNum
|
|
}
|
|
}
|
|
}
|
|
result.symbols = rows
|
|
odd, even := result.PointsInEachPhase()
|
|
result.SaturatedEvenCount = even
|
|
result.SaturatedOddCount = odd
|
|
return
|
|
}
|
|
|
|
func (f Field) PrintCoord(coords map[FieldPoint]any, expandByField int) {
|
|
|
|
for fieldRow := -expandByField; fieldRow <= expandByField; fieldRow++ {
|
|
lines := make([]string, len(f.symbols))
|
|
for fieldCol := -expandByField; fieldCol <= expandByField; fieldCol++ {
|
|
|
|
for rowNum, row := range f.symbols {
|
|
for colNum, col := range row {
|
|
_, marked := coords[FieldPoint{InField: Coord{Row: rowNum, Col: colNum},
|
|
MetaField: Coord{Row: fieldRow, Col: fieldCol}}]
|
|
if marked {
|
|
lines[rowNum] += "O"
|
|
} else {
|
|
lines[rowNum] += string(col)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
for _, line := range lines {
|
|
fmt.Println(line)
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// if the field is fully saturated, what is amount of 'visited' points?
|
|
// odd - meaning one step around 'S', even - meaning with standing on 'S'
|
|
func (f Field) PointsInEachPhase() (pointsIfOddPhase, pointsIfEvenPhase int) {
|
|
remainderOfEvenPhase := (f.RowStart + f.ColStart) % 2
|
|
text := "\n"
|
|
for i, row := range f.symbols {
|
|
for j, cell := range row {
|
|
if cell != '#' {
|
|
if (i+j)%2 == remainderOfEvenPhase {
|
|
pointsIfEvenPhase += 1
|
|
text += "E"
|
|
} else {
|
|
pointsIfOddPhase += 1
|
|
text += "O"
|
|
}
|
|
} else {
|
|
text += "#"
|
|
}
|
|
}
|
|
text += "\n"
|
|
}
|
|
fmt.Println(text)
|
|
log.Printf("calculating points in even and odd phases", pointsIfEvenPhase, pointsIfOddPhase)
|
|
|
|
return
|
|
}
|