Compare commits
10 Commits
c3acf211c3
...
1f83bca4e6
Author | SHA1 | Date |
---|---|---|
|
1f83bca4e6 | |
|
5b03b8f156 | |
|
bea82cb548 | |
|
d749979aae | |
|
b6a56554af | |
|
2f6120fbd8 | |
|
0c31596018 | |
|
28cf35e0e4 | |
|
7ebb6dee2c | |
|
44de1377ca |
|
@ -0,0 +1,26 @@
|
|||
package day23
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// length of longest scenic route
|
||||
func Run() int {
|
||||
fmt.Println("day 23")
|
||||
max := 0
|
||||
filename := "day23/input"
|
||||
field := ReadField(filename)
|
||||
fmt.Println(field.SparseString())
|
||||
// finalPaths := RunDFSTingy(field)
|
||||
// // log.Println(finalPaths)
|
||||
|
||||
// for _, path := range finalPaths {
|
||||
// if path.Visited.Cardinality() > max {
|
||||
// log.Println("one path len is ", path.Visited.Cardinality())
|
||||
// max = path.Visited.Cardinality()
|
||||
// }
|
||||
// }
|
||||
|
||||
return max
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#.#####################
|
||||
#.......#########...###
|
||||
#######.#########.#.###
|
||||
###.....#.>.>.###.#.###
|
||||
###v#####.#v#.###.#.###
|
||||
###.>...#.#.#.....#...#
|
||||
###v###.#.#.#########.#
|
||||
###...#.#.#.......#...#
|
||||
#####.#.#.#######.#.###
|
||||
#.....#.#.#.......#...#
|
||||
#.#####.#.#.#########v#
|
||||
#.#...#...#...###...>.#
|
||||
#.#.#v#######v###.###v#
|
||||
#...#.>.#...>.>.#.###.#
|
||||
#####v#.#.###v#.#.###.#
|
||||
#.....#...#...#.#.#...#
|
||||
#.#########.###.#.#.###
|
||||
#...###...#...#...#.###
|
||||
###.###.#.###v#####v###
|
||||
#...#...#.#.>.>.#.>.###
|
||||
#.###.###.#.###.#.#v###
|
||||
#.....###...###...#...#
|
||||
#####################.#
|
|
@ -0,0 +1,10 @@
|
|||
#.#####################
|
||||
#.#####################
|
||||
#.##............#######
|
||||
#.##.##########.#######
|
||||
#....##########.#######
|
||||
####..#########.#######
|
||||
#####...........#######
|
||||
###############.#######
|
||||
###############.#######
|
||||
###############.#######
|
|
@ -0,0 +1,175 @@
|
|||
package day23
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Coord struct {
|
||||
Row, Col int
|
||||
}
|
||||
|
||||
type CellType rune
|
||||
|
||||
const (
|
||||
Path CellType = '.'
|
||||
Tree CellType = '#'
|
||||
SlideDown CellType = 'v'
|
||||
SlideUp CellType = '^'
|
||||
SlideLeft CellType = '<'
|
||||
SlideRight CellType = '>'
|
||||
)
|
||||
|
||||
type Field struct {
|
||||
MaxRow, MaxCol int
|
||||
Cells map[Coord]CellType
|
||||
StartCol, EndCol int
|
||||
}
|
||||
|
||||
func (f *Field) EndCoord() Coord {
|
||||
return Coord{Row: f.MaxRow, Col: f.EndCol}
|
||||
}
|
||||
|
||||
func (f *Field) NeighborsPart2(c Coord) (neighbors []Coord) {
|
||||
symb, exists := f.Cells[c]
|
||||
if !exists {
|
||||
panic(fmt.Sprintf("coord %+v not found in field", c))
|
||||
}
|
||||
|
||||
var coords []Coord
|
||||
switch symb {
|
||||
case Tree:
|
||||
panic(fmt.Sprintf("attempting to get neighbors of a tree at %+v", c))
|
||||
default:
|
||||
coords = []Coord{
|
||||
{Row: c.Row + 1, Col: c.Col},
|
||||
{Row: c.Row - 1, Col: c.Col},
|
||||
{Row: c.Row, Col: c.Col + 1},
|
||||
{Row: c.Row, Col: c.Col - 1},
|
||||
}
|
||||
}
|
||||
|
||||
for _, coord := range coords {
|
||||
neighborSymb, found := f.Cells[coord]
|
||||
if !found || neighborSymb == Tree {
|
||||
continue
|
||||
}
|
||||
neighbors = append(neighbors, coord)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Field) Neighbors(c Coord) (neighbors []Coord) {
|
||||
symb, exists := f.Cells[c]
|
||||
if !exists {
|
||||
panic(fmt.Sprintf("coord %+v not found in field", c))
|
||||
}
|
||||
|
||||
var coords []Coord
|
||||
switch symb {
|
||||
case Path:
|
||||
coords = []Coord{
|
||||
{Row: c.Row + 1, Col: c.Col},
|
||||
{Row: c.Row - 1, Col: c.Col},
|
||||
{Row: c.Row, Col: c.Col + 1},
|
||||
{Row: c.Row, Col: c.Col - 1},
|
||||
}
|
||||
case Tree:
|
||||
panic(fmt.Sprintf("attempting to get neighbors of a tree at %+v", c))
|
||||
case SlideDown:
|
||||
coords = []Coord{{Row: c.Row + 1, Col: c.Col}}
|
||||
case SlideUp:
|
||||
coords = []Coord{{Row: c.Row - 1, Col: c.Col}}
|
||||
case SlideLeft:
|
||||
coords = []Coord{{Row: c.Row, Col: c.Col - 1}}
|
||||
case SlideRight:
|
||||
coords = []Coord{{Row: c.Row, Col: c.Col + 1}}
|
||||
}
|
||||
|
||||
for _, coord := range coords {
|
||||
neighborSymb, found := f.Cells[coord]
|
||||
if !found || neighborSymb == Tree {
|
||||
continue
|
||||
}
|
||||
neighbors = append(neighbors, coord)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Field) String() (result string) {
|
||||
result += "\n"
|
||||
for row := 0; row <= f.MaxRow; row++ {
|
||||
for col := 0; col <= f.MaxCol; col++ {
|
||||
if row == 0 && col == f.StartCol {
|
||||
result += "S"
|
||||
continue
|
||||
}
|
||||
if row == f.MaxRow && col == f.EndCol {
|
||||
result += "E"
|
||||
continue
|
||||
}
|
||||
|
||||
symb := f.Cells[Coord{Row: row, Col: col}]
|
||||
result += string(symb)
|
||||
}
|
||||
result += "\n"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Field) SparseString() (result string) {
|
||||
result += "\n"
|
||||
for row := 0; row <= f.MaxRow; row++ {
|
||||
for col := 0; col <= f.MaxCol; col++ {
|
||||
if row == 0 && col == f.StartCol {
|
||||
result += "S"
|
||||
continue
|
||||
}
|
||||
if row == f.MaxRow && col == f.EndCol {
|
||||
result += "E"
|
||||
continue
|
||||
}
|
||||
|
||||
symb := f.Cells[Coord{Row: row, Col: col}]
|
||||
if symb != Tree {
|
||||
neighbors := f.NeighborsPart2(Coord{Row: row, Col: col})
|
||||
if len(neighbors) > 2 {
|
||||
result += "o"
|
||||
} else {
|
||||
result += "."
|
||||
}
|
||||
} else {
|
||||
result += " "
|
||||
}
|
||||
}
|
||||
result += "\n"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadField(filename string) (result Field) {
|
||||
bytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(string(bytes)), "\n")
|
||||
result.MaxRow = len(lines) - 1
|
||||
result.MaxCol = len(lines[0]) - 1
|
||||
rows := make(map[Coord]CellType)
|
||||
|
||||
for rowNum, row := range lines {
|
||||
for colNum, symb := range row {
|
||||
rows[Coord{Row: rowNum, Col: colNum}] = CellType(symb)
|
||||
if rowNum == 0 && symb == rune(Path) {
|
||||
result.StartCol = colNum
|
||||
}
|
||||
if rowNum == result.MaxRow && symb == rune(Path) {
|
||||
result.EndCol = colNum
|
||||
}
|
||||
}
|
||||
}
|
||||
result.Cells = rows
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package day23
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestReadField(t *testing.T) {
|
||||
filename := "example"
|
||||
field := ReadField(filename)
|
||||
t.Log(field.String())
|
||||
}
|
||||
|
||||
func TestStartNeighbors(t *testing.T) {
|
||||
filename := "example"
|
||||
field := ReadField(filename)
|
||||
startNeighbors := field.Neighbors(Coord{Row: 0, Col: field.StartCol})
|
||||
t.Log(startNeighbors)
|
||||
}
|
||||
|
||||
// 5,3
|
||||
func TestForkNeighbors(t *testing.T) {
|
||||
filename := "example"
|
||||
field := ReadField(filename)
|
||||
startNeighbors := field.Neighbors(Coord{Row: 5, Col: 3})
|
||||
t.Log(startNeighbors)
|
||||
}
|
||||
|
||||
func TestSlideNeighbors(t *testing.T) {
|
||||
filename := "example"
|
||||
field := ReadField(filename)
|
||||
startNeighbors := field.Neighbors(Coord{Row: 6, Col: 3})
|
||||
t.Log(startNeighbors)
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
package day23
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"slices"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
index int
|
||||
c Coord
|
||||
name string
|
||||
}
|
||||
func (n Node)Name() string {
|
||||
var r string
|
||||
if n.index < 25 {
|
||||
num := 'A' + n.index
|
||||
r = string(rune(num))
|
||||
} else {
|
||||
num := 'a' + n.index - 25
|
||||
r = string(rune(num))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type Graph struct {
|
||||
nodes map[Coord]Node
|
||||
nodesByIndex []Node
|
||||
edges [][]int // from, to, length. excluding from, including to
|
||||
}
|
||||
|
||||
func MaxDist(from, to Node) (result int) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func PrintFieldWithGraph(g Graph, f Field) (result string) {
|
||||
result += "\n"
|
||||
for row := 0; row <= f.MaxRow; row++ {
|
||||
for col := 0; col <= f.MaxCol; col++ {
|
||||
symb := f.Cells[Coord{Row: row, Col: col}]
|
||||
if symb != Tree {
|
||||
coord := Coord{Row: row, Col: col}
|
||||
node, exists := g.nodes[coord]
|
||||
if exists {
|
||||
result += fmt.Sprint(node.Name())
|
||||
} else {
|
||||
result += "."
|
||||
}
|
||||
} else {
|
||||
result += " "
|
||||
}
|
||||
}
|
||||
result += "\n"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CreateGraph(f Field) (g Graph) {
|
||||
startCoord := Coord{Row: 0, Col: f.StartCol}
|
||||
// directly below start
|
||||
initialPath := PathEnd{
|
||||
end: Coord{Row: 1, Col: f.StartCol}, visited: mapset.NewSet[Coord](),
|
||||
}
|
||||
|
||||
g = Graph{
|
||||
nodes: map[Coord]Node{
|
||||
startCoord: Node{
|
||||
index: 0,
|
||||
c: startCoord,
|
||||
name: "A",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const presumedNodeCount = 36
|
||||
g.edges = make([][]int, presumedNodeCount)
|
||||
for i := 0; i < presumedNodeCount; i++ {
|
||||
g.edges[i] = make([]int, presumedNodeCount)
|
||||
}
|
||||
|
||||
recursiveGraphStep(f, initialPath, &g, startCoord, 1, mapset.NewSet[Coord]())
|
||||
g.edges[0][0] = 0
|
||||
|
||||
g.nodesByIndex = make([]Node, len(g.nodes))
|
||||
for _, node := range g.nodes {
|
||||
g.nodesByIndex[node.index] = node
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (g *Graph)Neighbors(node Node) (nodes []Node) {
|
||||
index := node.index
|
||||
for toIndex, len := range g.edges[index] {
|
||||
if len > 0 {
|
||||
nodes = append(nodes, g.nodesByIndex[toIndex])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var maxSoFar int = -1
|
||||
func CheckMaxSoFar(maybe int) {
|
||||
if maybe > maxSoFar {
|
||||
maxSoFar = maybe
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Graph) DFSLenOnGraph(atNode Node, visited mapset.Set[int],
|
||||
toNode Node, lenSoFar int) int {
|
||||
|
||||
if atNode == toNode {
|
||||
CheckMaxSoFar(lenSoFar)
|
||||
return lenSoFar
|
||||
}
|
||||
log.Printf("at %+v to %+v cur dist is %d.\t\t|max so far %d| \n", atNode, toNode, lenSoFar, maxSoFar)
|
||||
|
||||
neighbors := g.Neighbors(atNode)
|
||||
toVisit := slices.DeleteFunc(neighbors, func(n Node) bool {
|
||||
return visited.Contains(n.index)
|
||||
})
|
||||
|
||||
if len(toVisit) == 0 {
|
||||
return -1
|
||||
}
|
||||
max := -1
|
||||
|
||||
for _, nextNode := range toVisit {
|
||||
newVisited := visited.Clone()
|
||||
newVisited.Add(atNode.index)
|
||||
dist := g.edges[atNode.index][nextNode.index]
|
||||
maxFromNext := g.DFSLenOnGraph(nextNode, newVisited, toNode, lenSoFar + dist)
|
||||
if maxFromNext > max {
|
||||
max = maxFromNext
|
||||
}
|
||||
}
|
||||
|
||||
return max
|
||||
}
|
||||
|
||||
// run dfs, remembering from which remembers from which node we go, which path already traversed
|
||||
func recursiveGraphStep(f Field, p PathEnd, g *Graph, goingFrom Coord, goingLen int, visitedPathPoints mapset.Set[Coord]) {
|
||||
// log.Printf("entering coord %+v. from %+v with len %d\n", p.end, goingFrom, goingLen)
|
||||
|
||||
// if visitedPathPoints.Contains(p.end) {
|
||||
// return
|
||||
// }
|
||||
|
||||
neighbors := f.NeighborsPart2(p.end)
|
||||
|
||||
isCrossRoad := len(neighbors) > 2
|
||||
if isCrossRoad {
|
||||
log.Println("this should be crossroad ", p.end)
|
||||
}
|
||||
isStart := p.end == Coord{Row: 0, Col: f.StartCol}
|
||||
isEnd := p.end == f.EndCoord()
|
||||
if isEnd {
|
||||
log.Println("this should be end ", p.end)
|
||||
}
|
||||
|
||||
isNode := isCrossRoad || isStart || isEnd
|
||||
|
||||
continuedPaths := ExtendPath(p, f)
|
||||
|
||||
if !isNode {
|
||||
// just recurse into next paths, from same node, with increased len
|
||||
visitedPathPoints.Add(p.end)
|
||||
for _, nextStep := range continuedPaths {
|
||||
recursiveGraphStep(f, nextStep, g, goingFrom, goingLen+1, visitedPathPoints)
|
||||
}
|
||||
} else {
|
||||
node, known := g.nodes[p.end]
|
||||
// check if known, if not known - create
|
||||
if !known {
|
||||
node = Node{
|
||||
c: p.end,
|
||||
index: len(g.nodes),
|
||||
}
|
||||
node.name = node.Name()
|
||||
g.nodes[p.end] = node
|
||||
log.Printf("creating node %s %+v\n", node.Name(), node)
|
||||
}
|
||||
from := g.nodes[goingFrom]
|
||||
log.Printf("from %s to %s\n", from.Name(), node.Name())
|
||||
// and add vertices to currently traversed
|
||||
if g.edges[node.index][from.index] == 0 {
|
||||
g.edges[node.index][from.index] = goingLen
|
||||
g.edges[from.index][node.index] = goingLen
|
||||
} else {
|
||||
knownEdge := g.edges[node.index][from.index]
|
||||
if goingLen > knownEdge {
|
||||
g.edges[node.index][from.index] = goingLen
|
||||
g.edges[from.index][node.index] = goingLen
|
||||
}
|
||||
}
|
||||
// NOTE ah, it's possible to have two edges between i and j
|
||||
// but, i only need the longest one
|
||||
// log.Printf("adding edges between %d & %d of len %d\n", node.index, from.index, goingLen)
|
||||
|
||||
// continue with new 'from' and len of 1
|
||||
if !known {
|
||||
for _, nextStep := range continuedPaths {
|
||||
log.Printf("from %s should recurse to %+v", node.Name(), nextStep)
|
||||
recursiveGraphStep(f, nextStep, g, p.end, 1, visitedPathPoints)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GraphToMermaid(g Graph) (result string) {
|
||||
result += "\nflowchart LR\n"
|
||||
lines := mapset.NewSet[string]()
|
||||
for _, node := range g.nodes {
|
||||
for to, len := range g.edges[node.index] {
|
||||
var toNode Node
|
||||
for _, other := range g.nodes {
|
||||
if other.index == to {
|
||||
toNode = other
|
||||
}
|
||||
}
|
||||
if len > 0 {
|
||||
var fromName, toName string
|
||||
if node.index < toNode.index {
|
||||
fromName = node.Name()
|
||||
toName = toNode.Name()
|
||||
} else {
|
||||
fromName = toNode.Name()
|
||||
toName = node.Name()
|
||||
}
|
||||
line := fmt.Sprintf("\t%s---|length %d|%s\n", fromName, len, toName)
|
||||
lines.Add(line)
|
||||
}
|
||||
}
|
||||
// result += fmt.Sprintf("%s--|%d|%s\n", a ...any)
|
||||
}
|
||||
|
||||
for line := range lines.Iter() {
|
||||
result += line
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package day23
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
)
|
||||
|
||||
func TestGraphCreate(t *testing.T) {
|
||||
filename := "example2"
|
||||
field := ReadField(filename)
|
||||
|
||||
fmt.Println(field.SparseString())
|
||||
|
||||
graph := CreateGraph(field)
|
||||
t.Log(graph)
|
||||
}
|
||||
|
||||
func TestPrintGraph(t *testing.T) {
|
||||
filename := "example2"
|
||||
field := ReadField(filename)
|
||||
|
||||
fmt.Println(field.SparseString())
|
||||
|
||||
graph := CreateGraph(field)
|
||||
t.Log(PrintFieldWithGraph(graph, field))
|
||||
t.Logf(">>>\n %+v\n", graph)
|
||||
|
||||
}
|
||||
|
||||
func TestPrintGraphInput(t *testing.T) {
|
||||
filename := "input"
|
||||
field := ReadField(filename)
|
||||
|
||||
fmt.Println(field.SparseString())
|
||||
|
||||
graph := CreateGraph(field)
|
||||
t.Log(PrintFieldWithGraph(graph, field))
|
||||
t.Logf(">>>\n %+v\n", graph)
|
||||
|
||||
}
|
||||
|
||||
func TestPrintMermaidGraphInput(t *testing.T) {
|
||||
filename := "input"
|
||||
field := ReadField(filename)
|
||||
|
||||
fmt.Println(field.SparseString())
|
||||
|
||||
graph := CreateGraph(field)
|
||||
mmdContent := GraphToMermaid(graph)
|
||||
t.Log(mmdContent)
|
||||
|
||||
fileBorder, err := os.Create(filename + ".mmd")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := fileBorder.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
fileBorder.WriteString(mmdContent)
|
||||
}
|
||||
|
||||
func TestGraphMaxBetweenExample(t *testing.T) {
|
||||
filename := "example"
|
||||
field := ReadField(filename)
|
||||
graph := CreateGraph(field)
|
||||
|
||||
t.Log(PrintFieldWithGraph(graph, field))
|
||||
|
||||
|
||||
from := graph.nodes[Coord{Row: 0, Col: field.StartCol}]
|
||||
to := graph.nodes[field.EndCoord()]
|
||||
|
||||
dist := graph.DFSLenOnGraph(from, mapset.NewSet[int](), to, 0)
|
||||
|
||||
t.Log(graph)
|
||||
t.Logf("please dist %d", dist)
|
||||
|
||||
}
|
||||
|
||||
func TestGraphMaxBetweenInput(t *testing.T) {
|
||||
filename := "input"
|
||||
field := ReadField(filename)
|
||||
|
||||
graph := CreateGraph(field)
|
||||
|
||||
t.Log(PrintFieldWithGraph(graph, field))
|
||||
from := graph.nodes[Coord{Row: 0, Col: field.StartCol}]
|
||||
to := graph.nodes[field.EndCoord()]
|
||||
|
||||
dist := graph.DFSLenOnGraph(from, mapset.NewSet[int](), to, 0)
|
||||
|
||||
t.Log(graph)
|
||||
t.Logf("please dist %d", dist)
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
|
||||
flowchart LR
|
||||
M---|length 166|N
|
||||
d---|length 62|h
|
||||
H---|length 190|I
|
||||
f---|length 136|h
|
||||
j---|length 94|k
|
||||
B---|length 152|L
|
||||
I---|length 40|J
|
||||
W---|length 56|X
|
||||
E---|length 214|F
|
||||
C---|length 60|K
|
||||
V---|length 142|b
|
||||
a---|length 110|b
|
||||
I---|length 138|P
|
||||
J---|length 184|K
|
||||
Y---|length 146|a
|
||||
c---|length 190|d
|
||||
Q---|length 114|T
|
||||
J---|length 240|O
|
||||
C---|length 184|D
|
||||
L---|length 172|M
|
||||
Q---|length 140|R
|
||||
Y---|length 464|k
|
||||
O---|length 76|V
|
||||
N---|length 102|O
|
||||
K---|length 152|L
|
||||
U---|length 80|c
|
||||
V---|length 72|W
|
||||
b---|length 202|j
|
||||
A---|length 39|B
|
||||
W---|length 236|a
|
||||
P---|length 166|Q
|
||||
e---|length 174|f
|
||||
G---|length 186|R
|
||||
T---|length 258|d
|
||||
X---|length 142|Y
|
||||
b---|length 128|c
|
||||
F---|length 378|G
|
||||
S---|length 108|T
|
||||
N---|length 62|W
|
||||
U---|length 110|V
|
||||
a---|length 138|k
|
||||
S---|length 234|e
|
||||
d---|length 108|e
|
||||
H---|length 166|Q
|
||||
O---|length 158|P
|
||||
M---|length 360|X
|
||||
h---|length 184|i
|
||||
B---|length 244|C
|
||||
D---|length 96|J
|
||||
D---|length 154|E
|
||||
R---|length 118|S
|
||||
E---|length 146|I
|
||||
P---|length 128|U
|
||||
T---|length 268|U
|
||||
i---|length 198|j
|
||||
G---|length 144|H
|
||||
F---|length 102|H
|
||||
f---|length 77|g
|
||||
K---|length 266|N
|
||||
c---|length 64|i
|
|
@ -0,0 +1,125 @@
|
|||
#+title: Notes
|
||||
* ok, second part is long.
|
||||
and here optimization of storing direction of enter into path, and it's length,
|
||||
would that be helpful?
|
||||
it might not, because based on visited some future longer path might not be available.
|
||||
|
||||
i don't know how to optimize.
|
||||
|
||||
i could maybe do same calculation in parallel, somehow
|
||||
put not into queue, but into channel
|
||||
* wait a second. previous answer was 2018
|
||||
and now long checks result in me waiting for intermediate 1882.
|
||||
let's add early cutoffs, if not end by 2018, then abandon
|
||||
doubt that's implementable
|
||||
|
||||
* well. do i want to try parallel?
|
||||
seems like false path, really
|
||||
like there should be a better optimizaiton first
|
||||
* maybe we could join detours into 'potential longest paths'
|
||||
like if we traverse, and get to a point which was previously visited,
|
||||
for evey path that went through that split path,
|
||||
i could check whether i can take paths that went through this point, and switch their part with the detoured part.
|
||||
* and maybe we could continue longest paths first?
|
||||
like making pathsToFurther a heap by visited.Cordinality ?
|
||||
|
||||
oh, and then we'll find 'some path to end'
|
||||
and additional paths will try to add their detour.
|
||||
|
||||
so, i guess when finding a path to end, i could save path to end for each point.
|
||||
then if i reach the point, i could check if i can use some of the
|
||||
|
||||
and i guess if i do depth first then i'll always have all paths to end from a point if i return to it?
|
||||
* this sounds like an idea.
|
||||
with heap do depth first.
|
||||
|
||||
if it's first visit to a point, just go further
|
||||
if i find the end, i'd want to mark all points on the path with path info
|
||||
|
||||
hm. recursive calls might make this easier.
|
||||
because i'd want both 'prefixVisited' set and totalPathSet
|
||||
|
||||
due to depth first, we'll discover shortest path first.
|
||||
and points will get mapped with this first (of potential multiple) path info to end.
|
||||
|
||||
now if on followup steps i get into the point with info on paths to end,
|
||||
that should mean that i've already found all paths to end from that point, right?
|
||||
|
||||
now i need to check for the 'detour' which 'paths to end' are still possible with that detour added
|
||||
by taking set of elements from this point, to end. and checking that intersection with detour elements is 0.
|
||||
|
||||
if there are like this - report finding a new path, and save to all elements of that path somehow.
|
||||
|
||||
and now on finding detours i wouldn't need to re-check path to end, that should save a lot of time
|
||||
** so how to go about in coding this?
|
||||
have shared map[Coord][]EndPathInfo
|
||||
|
||||
the DFS means i'm recursing into each child.
|
||||
and taking the result of the call.
|
||||
it should be info on path to end? or multiple paths to end.
|
||||
which should be added to current node.
|
||||
|
||||
and then calling with start point will return paths to end from start, and i'll be able to take the by length
|
||||
|
||||
ok. but. if i'm entering the coord, and there are already paths to end.
|
||||
then i need to presume that those are only possible paths to end from this point,
|
||||
because all other paths should have been explored by now,
|
||||
i for my 'detour' determine whether it is consistent with any of already found paths to end.
|
||||
** NO. dfs doesn't mean i'll find shortest path first.
|
||||
so if i'm in visited, it doesn't mean that stored is shorter and current is a detour.
|
||||
|
||||
but dfs should mean that all paths from this prefix have finished.
|
||||
so, sure. there have to be all done?
|
||||
** my example2 has fork on row 3 col 10
|
||||
so 4,10 and 3,11 should be visited separately.
|
||||
|
||||
6,17 is where they join and the point which should have second entry
|
||||
** allright, ugh. my new solution is memory hogging.
|
||||
maybe i can draw the stuff and it will be several neat thingies
|
||||
* maybe new approach?
|
||||
make a graph. with vertices of Start, End and Crossroads.
|
||||
yes.
|
||||
let's create a graph representation.
|
||||
** so, from A to AG
|
||||
i think i can do this manually now
|
||||
** distances are
|
||||
39
|
||||
244
|
||||
184
|
||||
154
|
||||
214
|
||||
378
|
||||
144
|
||||
190
|
||||
40
|
||||
184
|
||||
152
|
||||
172
|
||||
166
|
||||
102
|
||||
158
|
||||
166
|
||||
140
|
||||
118
|
||||
108
|
||||
268
|
||||
110
|
||||
72
|
||||
56
|
||||
142
|
||||
146
|
||||
110
|
||||
128
|
||||
190
|
||||
108
|
||||
174
|
||||
77
|
||||
1
|
||||
** again?
|
||||
no, let's write code.
|
||||
** didn't count all the way
|
||||
2023/12/23 15:55:55 at {index:30 c:{Row:125 Col:137} name:f} to {index:31 c:{Row:140 Col:139} name:g} cur dist is 3997. |max so far 6406|
|
||||
signal: interrupt
|
||||
FAIL sunshine.industries/aoc2023/day23 380.499s
|
||||
|
||||
tried more or less stable value, and interrupted
|
|
@ -0,0 +1,161 @@
|
|||
package day23
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
)
|
||||
|
||||
type PathEnd struct {
|
||||
end Coord
|
||||
visited mapset.Set[Coord]
|
||||
}
|
||||
func (p PathEnd)Sring() string {
|
||||
return fmt.Sprintf("PathEnd[at %+v, visited: %+v]", p.end, p.visited)
|
||||
}
|
||||
|
||||
func ExtendPath(p PathEnd, f Field) (nextPaths []PathEnd) {
|
||||
endPointNeighbors := f.NeighborsPart2(p.end)
|
||||
for _, potentialNewEnd := range endPointNeighbors {
|
||||
if !p.visited.Contains(potentialNewEnd) {
|
||||
nextVisited := p.visited.Clone()
|
||||
nextVisited.Add(p.end)
|
||||
nextPaths = append(nextPaths, PathEnd{
|
||||
end: potentialNewEnd,
|
||||
visited: nextVisited,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// info on path from start to end
|
||||
type PathInfo struct {
|
||||
Visited mapset.Set[Coord]
|
||||
}
|
||||
|
||||
func RunDFSTingy(f Field) []PathInfo {
|
||||
initialPath := PathEnd{
|
||||
end: Coord{Row: 0, Col: f.StartCol}, visited: mapset.NewSet[Coord](),
|
||||
}
|
||||
initialShared := make(map[Coord][]PathInfo)
|
||||
|
||||
return DFSScenicPaths(f, initialPath, initialShared)
|
||||
}
|
||||
|
||||
var knownMax int = 0
|
||||
func CheckAndPrintMax(maybeNewMax int) {
|
||||
if maybeNewMax > knownMax {
|
||||
log.Printf("\n\n>>>>found new max: %d\n", maybeNewMax)
|
||||
knownMax = maybeNewMax
|
||||
}
|
||||
}
|
||||
|
||||
func DFSScenicPaths(f Field, curPath PathEnd,
|
||||
sharedMem map[Coord][]PathInfo) (pathsFromTheStartToEnd []PathInfo) {
|
||||
curCoord := curPath.end
|
||||
|
||||
if curCoord == (Coord{ Row: 6, Col: 15 }) {
|
||||
log.Println(">>>>>>>>")
|
||||
}
|
||||
// log.Printf("entering %+v with mem %+v\n", curPath, sharedMem[curCoord])
|
||||
|
||||
if curCoord == f.EndCoord() {
|
||||
pathsFromTheStartToEnd = append(pathsFromTheStartToEnd, PathInfo{curPath.visited.Clone()})
|
||||
log.Printf("got to end. cur len is %d\n", curPath.visited.Cardinality())
|
||||
CheckAndPrintMax(curPath.visited.Cardinality())
|
||||
// i guess return only from current to end?
|
||||
// and on non terminal first time, return copy with self added?
|
||||
return
|
||||
}
|
||||
|
||||
// now for non final point
|
||||
knownPaths, visitedBefore := sharedMem[curCoord]
|
||||
|
||||
// NOTE but this only if we haven't visited this coord before!
|
||||
|
||||
if !visitedBefore {
|
||||
nextSteps := ExtendPath(curPath, f)
|
||||
suffixesFromCurToEnd := make([]PathInfo, 0)
|
||||
for _, nextPath := range nextSteps {
|
||||
pathsToEndThrough := DFSScenicPaths(f, nextPath, sharedMem)
|
||||
// i guess here deduct the prefix.
|
||||
|
||||
for _, path := range pathsToEndThrough {
|
||||
// will contain this and further
|
||||
suffix := PathInfo{
|
||||
Visited: path.Visited.Difference(curPath.visited).Clone(),
|
||||
}
|
||||
// log.Printf(">> from path \n%+v make suffix \n%+v\n\n", path, suffix)
|
||||
suffixesFromCurToEnd = append(suffixesFromCurToEnd, suffix)
|
||||
}
|
||||
|
||||
pathsFromTheStartToEnd = append(pathsFromTheStartToEnd, pathsToEndThrough...)
|
||||
|
||||
if len(pathsToEndThrough) != 0 {
|
||||
// log.Printf("setting mem for %+v to %+v", curCoord, suffixesFromCurToEnd)
|
||||
sharedMem[curCoord] = suffixesFromCurToEnd
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
} else {
|
||||
// have visited this point before, due to dfs all possible paths to end should already be known
|
||||
// so curPath.visited should contian a detour.
|
||||
// need to figure out if this detour is compatible with any of the known paths to end
|
||||
// from those create 'new' paths to end with that detour
|
||||
// return those and add those to the shared mem
|
||||
for _, knownPathToEnd := range knownPaths {
|
||||
// those are all points through which this known path goes from current to end
|
||||
// if our curPath
|
||||
fromCurToEnd := knownPathToEnd.Visited
|
||||
thisPrefix := curPath.visited
|
||||
|
||||
if thisPrefix.Intersect(fromCurToEnd).Cardinality() == 0 {
|
||||
// then current prefix is compatible with this path.
|
||||
fromCurPrefixToEnd := thisPrefix.Clone()
|
||||
fromCurPrefixToEnd.Union(fromCurToEnd)
|
||||
pathsFromTheStartToEnd = append(pathsFromTheStartToEnd, PathInfo{fromCurPrefixToEnd})
|
||||
log.Printf("additional path to end of len %d\n", fromCurPrefixToEnd.Cardinality())
|
||||
CheckAndPrintMax(fromCurPrefixToEnd.Cardinality())
|
||||
}
|
||||
}
|
||||
log.Printf("having second visit into %+v.\n", curPath)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
panic("should not be reachable")
|
||||
}
|
||||
|
||||
// return paths that end on End
|
||||
func RunAllScenicPaths(f Field) (result []PathEnd) {
|
||||
pathsToFurther := []PathEnd{
|
||||
{end: Coord{Row: 0, Col: f.StartCol}, visited: mapset.NewSet[Coord]()},
|
||||
}
|
||||
|
||||
for len(pathsToFurther) > 0 {
|
||||
curCheckedPath := pathsToFurther[0]
|
||||
pathsToFurther = pathsToFurther[1:]
|
||||
|
||||
if curCheckedPath.end == f.EndCoord() {
|
||||
result = append(result, curCheckedPath)
|
||||
// log.Printf("found end path of len %d . %+v", curCheckedPath.visited.Cardinality(), curCheckedPath)
|
||||
continue
|
||||
}
|
||||
|
||||
nextSteps := ExtendPath(curCheckedPath, f)
|
||||
|
||||
// log.Printf("for %+v next steps %+v\n", curCheckedPath, pathsToFurther)
|
||||
// log.Printf("remaining paths to check len is %d", len(pathsToFurther))
|
||||
// log.Println(pathsToFurther)
|
||||
|
||||
if len(nextSteps) > 0 {
|
||||
pathsToFurther = append(pathsToFurther, nextSteps...)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package day23
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRunAllPaths(t *testing.T) {
|
||||
filename := "example"
|
||||
field := ReadField(filename)
|
||||
finalPaths := RunAllScenicPaths(field)
|
||||
t.Log(finalPaths)
|
||||
|
||||
max := 0
|
||||
for _, path := range finalPaths {
|
||||
if path.visited.Cardinality() > max {
|
||||
max = path.visited.Cardinality()
|
||||
}
|
||||
}
|
||||
t.Logf("max path len is %d", max)
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
flowchart LR
|
||||
L---|length 152|K
|
||||
L---|length 172|M
|
||||
U---|length 268|T
|
||||
U---|length 110|V
|
||||
W---|length 72|V
|
||||
W---|length 56|X
|
||||
a---|length 146|Y
|
||||
a---|length 110|b
|
||||
f---|length 174|e
|
||||
f---|length 77|g
|
||||
f---|length 136|h
|
||||
H---|length 144|G
|
||||
H---|length 190|I
|
||||
T---|length 108|S
|
||||
T---|length 268|U
|
||||
M---|length 172|L
|
||||
M---|length 166|N
|
||||
F---|length 214|E
|
||||
F---|length 378|G
|
||||
I---|length 190|H
|
||||
I---|length 40|J
|
||||
A---|length 2|A
|
||||
A---|length 39|B
|
||||
Q---|length 166|P
|
||||
Q---|length 140|R
|
||||
Y---|length 142|X
|
||||
Y---|length 146|a
|
||||
d---|length 190|c
|
||||
d---|length 108|e
|
||||
e---|length 108|d
|
||||
e---|length 174|f
|
||||
h---|length 136|f
|
||||
h---|length 184|i
|
||||
J---|length 40|I
|
||||
J---|length 184|K
|
||||
N---|length 166|M
|
||||
N---|length 102|O
|
||||
X---|length 56|W
|
||||
X---|length 142|Y
|
||||
j---|length 198|i
|
||||
j---|length 94|k
|
||||
B---|length 39|A
|
||||
B---|length 244|C
|
||||
G---|length 378|F
|
||||
G---|length 144|H
|
||||
P---|length 158|O
|
||||
P---|length 166|Q
|
||||
D---|length 184|C
|
||||
D---|length 154|E
|
||||
E---|length 154|D
|
||||
E---|length 214|F
|
||||
K---|length 184|J
|
||||
K---|length 152|L
|
||||
O---|length 102|N
|
||||
O---|length 158|P
|
||||
R---|length 140|Q
|
||||
R---|length 118|S
|
||||
S---|length 118|R
|
||||
S---|length 108|T
|
||||
V---|length 110|U
|
||||
V---|length 72|W
|
||||
c---|length 128|b
|
||||
c---|length 190|d
|
||||
C---|length 244|B
|
||||
C---|length 184|D
|
||||
k---|length 94|j
|
||||
i---|length 184|h
|
||||
i---|length 198|j
|
||||
g---|length 77|f
|
||||
b---|length 110|a
|
||||
b---|length 128|c
|
Binary file not shown.
After Width: | Height: | Size: 203 KiB |
|
@ -0,0 +1,5 @@
|
|||
19, 13, 30 @ -2, 1, -2
|
||||
18, 19, 22 @ -1, -1, -2
|
||||
20, 25, 34 @ -2, -2, -4
|
||||
12, 31, 28 @ -1, -2, -1
|
||||
20, 19, 15 @ 1, -5, -3
|
|
@ -0,0 +1,73 @@
|
|||
package day24
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// most inner loop
|
||||
// assumint stone hits h1 at t1, and h2 at t2
|
||||
// return the line. so 'HailParam' for my stone trajectory
|
||||
func AssumeHails(h1, h2 HailParam, t1, t2 int) (stoneTrajectory HailParam, isInt bool) {
|
||||
Dx, isXInt := AssumedDelta(h1.p0.x, h2.p0.x, h1.Dx, h2.Dx, t1, t2)
|
||||
Dy, isYInt := AssumedDelta(h1.p0.y, h2.p0.y, h1.Dy, h2.Dy, t1, t2)
|
||||
Dz, isZInt := AssumedDelta(h1.p0.z, h2.p0.z, h1.Dz, h2.Dz, t1, t2)
|
||||
|
||||
isInt = isXInt && isYInt && isZInt
|
||||
|
||||
x := AssumedStartFromDelta(h1.p0.x, h1.Dx, t1, Dx)
|
||||
y := AssumedStartFromDelta(h1.p0.y, h1.Dy, t1, Dy)
|
||||
z := AssumedStartFromDelta(h1.p0.z, h1.Dz, t1, Dz)
|
||||
|
||||
stoneTrajectoryLine := fmt.Sprintf("%d, %d, %d @ %d, %d, %d", x, y, z, Dx, Dy, Dz)
|
||||
stoneTrajectory = ReadHailLine(stoneTrajectoryLine)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func HailMaryLoop(hails []HailParam) {
|
||||
// for t1, t2 from [1, 100]
|
||||
// try to fit stoneTrajectory on every pair of hails.
|
||||
// and hope for integer fit
|
||||
for t1 := 1; t1 <= 100; t1++ {
|
||||
for t2 := t1+1 ; t2 <= 100; t2++ {
|
||||
for i, hail := range hails {
|
||||
innerHail:
|
||||
for j, otherHail := range hails {
|
||||
if i == j {
|
||||
continue innerHail
|
||||
}
|
||||
_, isInt := AssumeHails(hail, otherHail, t1, t2)
|
||||
if !isInt {
|
||||
continue innerHail // TODO first hope to loose
|
||||
}
|
||||
// if isInt {
|
||||
// log.Printf("hail mary int fit between %s (%d) and %s (%d)",
|
||||
// hail.SomeString(), t1, otherHail.SomeString(), t2)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check for inner loop : when get assumed stoneTrajectory
|
||||
// for all hail params, check that they intercept
|
||||
// func CheckAssumedTrajectory(assumedStone HailParam, hails []HailParam) bool {
|
||||
// for _, hail := range hails {
|
||||
// // i guess i could try to do what?
|
||||
// // assume oh, no. there can be t whatever
|
||||
// }
|
||||
// }
|
||||
|
||||
func AssumedDelta(c1, c2 int, Dc1, Dc2 int, t1, t2 int) (delta int, isInt bool) {
|
||||
divisor := t1 - t2
|
||||
divisible := c1 - c2 + (t1 * Dc1) - (t2 * Dc2)
|
||||
|
||||
isInt = divisible % divisor == 0
|
||||
delta = divisible / divisor
|
||||
return
|
||||
}
|
||||
|
||||
func AssumedStartFromDelta(c1 int, Dc1 int, t1, Dc int) (c int) {
|
||||
return c1 + t1 * Dc1 - t1 * Dc
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package day24
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestHailMaryOnExamle(t *testing.T) {
|
||||
filename := "input"
|
||||
hails := ReadHailFile(filename)
|
||||
HailMaryLoop(hails)
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
package day24
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// CoordMin int = 7
|
||||
// CoordMax int = 27
|
||||
CoordMin int = 200000000000000
|
||||
CoordMax int = 400000000000000
|
||||
)
|
||||
|
||||
type Point struct {
|
||||
x, y, z int
|
||||
}
|
||||
|
||||
type HailParam struct {
|
||||
p0, p1 Point
|
||||
Dx, Dy, Dz int
|
||||
line string
|
||||
// for 2d : ay + bx = 0
|
||||
a, b, c int
|
||||
// for 2d : y = slope*x + shift
|
||||
slope, shift float64
|
||||
}
|
||||
|
||||
func (h *HailParam) SomeString() string {
|
||||
return h.line
|
||||
}
|
||||
|
||||
func (h *HailParam) GetCoord(name string) (result int) {
|
||||
switch name {
|
||||
case "x":
|
||||
result = h.p0.x
|
||||
case "y":
|
||||
result = h.p0.y
|
||||
case "z":
|
||||
result = h.p0.z
|
||||
default:
|
||||
panic("unknown param")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h *HailParam) GetSpeedOf(name string) (result int) {
|
||||
switch name {
|
||||
case "x":
|
||||
result = h.Dx
|
||||
case "y":
|
||||
result = h.Dy
|
||||
case "z":
|
||||
result = h.Dz
|
||||
default:
|
||||
panic("unknown param")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func CheckPairwiseIntersections(hails []HailParam) (totalIntersections int) {
|
||||
for i, hail := range hails {
|
||||
for j := i + 1; j < len(hails); j++ {
|
||||
otherHail := hails[j]
|
||||
intersect := CheckTaskIntersection(hail, otherHail)
|
||||
if intersect {
|
||||
totalIntersections += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CheckTaskIntersection(h1, h2 HailParam) (doIntersect bool) {
|
||||
log.Printf("intersecting %+v and %+v\n", h1, h2)
|
||||
// x, y, intersectAtAll := IntersectByTwoPoints(h1, h2)
|
||||
x, y, intersectAtAll := IntersectBySlopeAndShift(h1, h2)
|
||||
if !intersectAtAll {
|
||||
log.Println("no intersection at all\n", x, y)
|
||||
return false
|
||||
}
|
||||
isH1Future := h1.FloatPointInFuture(x, y)
|
||||
isH2Future := h2.FloatPointInFuture(x, y)
|
||||
|
||||
if !isH1Future {
|
||||
log.Printf("point %f, %f in the past for h1\n", x, y)
|
||||
}
|
||||
if !isH2Future {
|
||||
log.Printf("point %f, %f in the past for h2\n", x, y)
|
||||
}
|
||||
if !isH1Future || !isH2Future {
|
||||
return false
|
||||
}
|
||||
|
||||
if x < float64(CoordMin) || x > float64(CoordMax) ||
|
||||
y < float64(CoordMin) || y > float64(CoordMax) {
|
||||
log.Printf("intersect at %f %f but outside of area\n", x, y)
|
||||
return false // outside of area
|
||||
}
|
||||
|
||||
log.Println("> intersect inside of the area! ", x, y)
|
||||
return true
|
||||
}
|
||||
|
||||
func IntersectInTheeDimentions(h1, h2 HailParam) (interX, interY, interZ float64,
|
||||
interT float64, isIntersecting bool) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func IntersectBySlopeAndShift(h1, h2 HailParam) (intersectionX, intersectionY float64, isIntersecting bool) {
|
||||
if h1.slope == h2.slope {
|
||||
return
|
||||
}
|
||||
// y = slope * x + shift
|
||||
// slope1 * x + shift1 = slope2 * x + shift2
|
||||
// x = ( shift2 - shift1 ) / (slope1 - slope2)
|
||||
|
||||
x := (h2.shift - h1.shift) / (h1.slope - h2.slope)
|
||||
y := h1.slope*x + h1.shift
|
||||
|
||||
return x, y, true
|
||||
}
|
||||
|
||||
func (h HailParam) PointInFuture(p Point) bool {
|
||||
xPositiveSteps := (p.x-h.p0.x)*h.Dx >= 0
|
||||
yPositiveSteps := (p.y-h.p0.y)*h.Dy >= 0
|
||||
zPositiveSteps := (p.z-h.p0.z)*h.Dz >= 0
|
||||
return xPositiveSteps && yPositiveSteps && zPositiveSteps
|
||||
}
|
||||
func (h HailParam) FloatPointInFuture(x, y float64) bool {
|
||||
xPositiveSteps := (x-float64(h.p0.x))*float64(h.Dx) >= 0
|
||||
// yPositiveSteps := (y - float64(h.p0.y)) * float64(h.Dy) >= 0
|
||||
// return xPositiveSteps && yPositiveSteps
|
||||
return xPositiveSteps
|
||||
}
|
||||
|
||||
// 19, 13, 30 @ -2, 1, -2
|
||||
func ReadHailLine(line string) (h HailParam) {
|
||||
h.line = line
|
||||
line = strings.ReplaceAll(line, "@", "")
|
||||
line = strings.ReplaceAll(line, ",", "")
|
||||
fields := strings.Fields(line)
|
||||
|
||||
h.p0.x = AtoIOrPanic(fields[0])
|
||||
h.p0.y = AtoIOrPanic(fields[1])
|
||||
h.p0.z = AtoIOrPanic(fields[2])
|
||||
h.Dx = AtoIOrPanic(fields[3])
|
||||
h.Dy = AtoIOrPanic(fields[4])
|
||||
h.Dz = AtoIOrPanic(fields[5])
|
||||
|
||||
countP1AfterMillis := 1
|
||||
|
||||
h.p1.x = h.p0.x + countP1AfterMillis*h.Dx
|
||||
h.p1.y = h.p0.y + countP1AfterMillis*h.Dy
|
||||
h.p1.z = h.p0.z + countP1AfterMillis*h.Dz
|
||||
|
||||
h.a = h.p0.y - h.p1.y
|
||||
h.b = h.p1.x - h.p0.x
|
||||
h.c = -(h.p0.x*h.p1.y - h.p1.x*h.p0.y)
|
||||
|
||||
h.slope = float64(h.Dy) / float64(h.Dx)
|
||||
// y = slope * x + shift
|
||||
// shift = y - slope * x // for some point
|
||||
h.shift = float64(h.p0.y) - h.slope*float64(h.p0.x)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ReadHailFile(filename string) []HailParam {
|
||||
bytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
text := strings.TrimSpace(string(bytes))
|
||||
lines := strings.Split(text, "\n")
|
||||
result := make([]HailParam, len(lines))
|
||||
|
||||
for i, line := range lines {
|
||||
result[i] = ReadHailLine(line)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func AtoIOrPanic(str string) (num int) {
|
||||
num, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package day24
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadLine(t *testing.T) {
|
||||
lines := `19, 13, 30 @ -2, 1, -2
|
||||
18, 19, 22 @ -1, -1, -2
|
||||
20, 25, 34 @ -2, -2, -4
|
||||
12, 31, 28 @ -1, -2, -1
|
||||
20, 19, 15 @ 1, -5, -3`
|
||||
|
||||
for _, line := range strings.Split(lines, "\n") {
|
||||
hail := ReadHailLine(line)
|
||||
t.Log(hail)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestReadLineInput(t *testing.T) {
|
||||
lines := `147847636573416, 190826994408605, 140130741291716 @ 185, 49, 219
|
||||
287509258905812, 207449079739538, 280539021150559 @ -26, 31, 8
|
||||
390970075767404, 535711685410735, 404166182422876 @ -147, -453, -149
|
||||
306391780523937, 382508967958270, 264612201472049 @ -24, -274, 28
|
||||
278063616684570, 510959526404728, 288141792965603 @ -18, -441, -6`
|
||||
for _, line := range strings.Split(lines, "\n") {
|
||||
hail := ReadHailLine(line)
|
||||
t.Logf("%+v\n", hail)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSecondPointIsInFuture(t *testing.T) {
|
||||
lines := `19, 13, 30 @ -2, 1, -2
|
||||
18, 19, 22 @ -1, -1, -2
|
||||
20, 25, 34 @ -2, -2, -4
|
||||
12, 31, 28 @ -1, -2, -1
|
||||
20, 19, 15 @ 1, -5, -3`
|
||||
|
||||
for _, line := range strings.Split(lines, "\n") {
|
||||
hail := ReadHailLine(line)
|
||||
t.Log(hail)
|
||||
t.Logf("calced seconds point %+v is in future %t\n", hail.p1, hail.PointInFuture(hail.p1))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIntersectExampleOne(t *testing.T) {
|
||||
// Hailstone A: 19, 13, 30 @ -2, 1, -2
|
||||
// Hailstone B: 18, 19, 22 @ -1, -1, -2
|
||||
// Hailstones' paths will cross inside the test area (at x=14.333, y=15.333).
|
||||
|
||||
hA := ReadHailLine("19, 13, 30 @ -2, 1, -2")
|
||||
hB := ReadHailLine("18, 19, 22 @ -1, -1, -2")
|
||||
|
||||
x, y, check := IntersectBySlopeAndShift(hA, hB)
|
||||
if !check {
|
||||
panic("should intersect")
|
||||
}
|
||||
t.Logf("got intersection at %f %f", x, y)
|
||||
}
|
||||
|
||||
func TestIntersectExampleTwo(t *testing.T) {
|
||||
// Hailstone A: 18, 19, 22 @ -1, -1, -2
|
||||
// Hailstone B: 20, 25, 34 @ -2, -2, -4
|
||||
hA := ReadHailLine("18, 19, 22 @ -1, -1, -2")
|
||||
hB := ReadHailLine("20, 25, 34 @ -2, -2, -4")
|
||||
|
||||
x, y, check := IntersectBySlopeAndShift(hA, hB)
|
||||
if check {
|
||||
panic("should not intersect")
|
||||
}
|
||||
t.Logf("got intersection at %f %f", x, y)
|
||||
}
|
||||
|
||||
func TestExamplePairwiseChecks(t *testing.T) {
|
||||
filename := "example"
|
||||
hails := ReadHailFile(filename)
|
||||
for _, hail := range hails {
|
||||
t.Log(hail)
|
||||
}
|
||||
|
||||
intersections := CheckPairwiseIntersections(hails)
|
||||
t.Log("counted intersections ", intersections)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package day24
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func Run() int {
|
||||
fmt.Println("hello day 24, i'm getting tired")
|
||||
filenae := "day24/input"
|
||||
hails := ReadHailFile(filenae)
|
||||
return CheckPairwiseIntersections(hails)
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
#+title: Notes
|
||||
* i want help from math
|
||||
https://math.stackexchange.com/questions/28503/how-to-find-intersection-of-two-lines-in-3d
|
||||
|
||||
'vector parametric form' is exactly what we're getting in the input?
|
||||
* huh and only 'looking forward in time' so solutions with negative t are not important.
|
||||
cooool
|
||||
** i see that speeds are integers, so updated points are integers.
|
||||
maybe i do calculation of every line on every time point?
|
||||
|
||||
and if i do that is there a way to get intersections efficietly?
|
||||
** i'll also need the ends for lines? ends to the line segments.
|
||||
with limits on the x & y by the task
|
||||
|
||||
for example both 7 <= <= 27
|
||||
for input 200000000000000 <= <= 400000000000000
|
||||
|
||||
also. can't we move the coords? maybe not? maybe only for one
|
||||
so, what do i do? to get the ends of the lines?
|
||||
i try to calcluate with both x & y in 2 min\max. then if the other is ok, than that's the ends?
|
||||
wait, what happens when i do x = 7, and x = 27 and y is outside? it means no intesections, i guess
|
||||
or it could be outside from different sides, so not all x are ok, but there's still line there
|
||||
** Using homogeneous coordinates
|
||||
https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
|
||||
no, i don't understant that
|
||||
** https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
|
||||
with 2 points. i guess
|
||||
but also - check if the point in future of the hail, by comparing with speeds?
|
||||
should be easy
|
||||
** and i got wrong result
|
||||
day24 result: 8406
|
||||
** another formula gives
|
||||
day24 result: 8406
|
||||
** another formula
|
||||
12938
|
||||
*
|
||||
* ok, part 2.
|
||||
what if.
|
||||
i start checking t = 0, 1, etc.
|
||||
for each t, i need two points of the two hail lines.
|
||||
|
||||
it would constitute the trajectory.
|
||||
then condition for the solution that all other hail lines will intersect it at some t.
|
||||
so check for intersection (maybe not necessarily in the field?)
|
||||
|
||||
go though lines, if any fail to intersect - continue with t
|
||||
|
||||
if all intersect, find where the rock has to be in time 0
|
||||
|
||||
oh. no.
|
||||
it's not just intersect. it's that the movement of the rock with t would be there at correct time? yuck?
|
||||
|
||||
would there really be more than i line that intersects all of the hail lines?
|
||||
|
||||
i'll just need to also figure out t=0 from other coords.
|
||||
|
||||
i don't like this at all.
|
||||
|
||||
And intersections have to be over (X, Y, Z)
|
||||
** so 'hail mary' approach would be
|
||||
scan first 1k nanoseconds. so already 1M calculations
|
||||
( this is first part of desperation, that at least 2 hails will intercept in first 1k ticks )
|
||||
|
||||
for collision 1, assume HailA is on path.
|
||||
then iterate for all other assumint they are intercepted on t 2 etc ?
|
||||
|
||||
no. the intersections could be on non-integer times?
|
||||
( this would be second part of the 'hail mary' )
|
||||
|
||||
from that i should be able to construct the 'trajectory' line.
|
||||
and then check with all other points - do the intersect?
|
||||
( and check of intersection in future would be nice )
|
||||
|
||||
then if line confirmed, will need to calc for t = 0, t = 1, and get speeds
|
||||
*** not hoping for all integer intersections
|
||||
or what if i will hope for that?
|
||||
let's try?
|
||||
* ok, what if i could do system of equasions?
|
||||
#+begin_src
|
||||
yuck_test.go:12:
|
||||
x + Dx * t0 == 19 + -2 * t0
|
||||
y + Dy * t0 == 13 + 1 * t0
|
||||
z + Dz * t0 == 19 + -2 * t0
|
||||
x + Dx * t1 == 18 + -1 * t1
|
||||
y + Dy * t1 == 19 + -1 * t1
|
||||
z + Dz * t1 == 18 + -2 * t1
|
||||
x + Dx * t2 == 20 + -2 * t2
|
||||
y + Dy * t2 == 25 + -2 * t2
|
||||
z + Dz * t2 == 20 + -4 * t2
|
||||
solve for x, y, z, Dx, Dy, Dz, t1, t2, t3. ti > 0
|
||||
#+end_src
|
||||
|
||||
#+begin_src
|
||||
yuck_test.go:18:
|
||||
x + Dx * t0 == 147847636573416 + 185 * t0
|
||||
y + Dy * t0 == 190826994408605 + 49 * t0
|
||||
z + Dz * t0 == 147847636573416 + 219 * t0
|
||||
x + Dx * t1 == 287509258905812 + -26 * t1
|
||||
y + Dy * t1 == 207449079739538 + 31 * t1
|
||||
z + Dz * t1 == 287509258905812 + 8 * t1
|
||||
x + Dx * t2 == 390970075767404 + -147 * t2
|
||||
y + Dy * t2 == 535711685410735 + -453 * t2
|
||||
z + Dz * t2 == 390970075767404 + -149 * t2
|
||||
solve for x, y, z, Dx, Dy, Dz, t1, t2, t3. ti > 0
|
||||
#+end_src
|
||||
* got some solution
|
||||
enefedov@LLF33A87M:~/Documents/personal/advent-of-code-2023$ python day24/pythonZ3/forInput.py
|
||||
Solution: [t0 = 666003776903,
|
||||
t2 = 779453185471,
|
||||
t1 = 654152070134,
|
||||
Dz = 18,
|
||||
Dx = 47,
|
||||
Dy = -360,
|
||||
z = 273997500449219,
|
||||
y = 463222539161932,
|
||||
x = 239756157786030]
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from z3 import *
|
||||
|
||||
s = Solver()
|
||||
|
||||
x = Real('x')
|
||||
Dx = Real('Dx')
|
||||
y = Real('y')
|
||||
Dy = Real('Dy')
|
||||
z = Real('z')
|
||||
Dz = Real('Dz')
|
||||
t0 = Real('t0')
|
||||
eqT0 = t0 >= 0
|
||||
eq0x = x + Dx * t0 == (-2 * t0) + 19
|
||||
eq0y = y + Dy * t0 == (1 * t0) + 13
|
||||
eq0z = z + Dz * t0 == (-2 * t0) + 30
|
||||
t1 = Real('t1')
|
||||
eqT1 = t1 >= 0
|
||||
eq1x = x + Dx * t1 == (-1 * t1) + 18
|
||||
eq1y = y + Dy * t1 == (-1 * t1) + 19
|
||||
eq1z = z + Dz * t1 == (-2 * t1) + 22
|
||||
t2 = Real('t2')
|
||||
eqT2 = t2 >= 0
|
||||
eq2x = x + Dx * t2 == (-2 * t2) + 20
|
||||
eq2y = y + Dy * t2 == (-2 * t2) + 25
|
||||
eq2z = z + Dz * t2 == (-4 * t2) + 34
|
||||
#solve for x, y, z, Dx, Dy, Dz, t1, t2, t3.
|
||||
|
||||
|
||||
s.add(eqT0,
|
||||
eq0x,
|
||||
eq0y,
|
||||
eq0z,
|
||||
eqT1,
|
||||
eq1x,
|
||||
eq1y,
|
||||
eq1z,
|
||||
eqT2,
|
||||
eq2x,
|
||||
eq2y,
|
||||
eq2z)
|
||||
|
||||
if s.check() == sat:
|
||||
print("Solution:", s.model())
|
||||
else:
|
||||
print("No solution found")
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from z3 import *
|
||||
|
||||
s = Solver()
|
||||
|
||||
x = Real('x')
|
||||
Dx = Real('Dx')
|
||||
y = Real('y')
|
||||
Dy = Real('Dy')
|
||||
z = Real('z')
|
||||
Dz = Real('Dz')
|
||||
t0 = Real('t0')
|
||||
eqT0 = t0 >= 0
|
||||
eq0x = x + Dx * t0 == (185 * t0) + 147847636573416
|
||||
eq0y = y + Dy * t0 == (49 * t0) + 190826994408605
|
||||
eq0z = z + Dz * t0 == (219 * t0) + 140130741291716
|
||||
t1 = Real('t1')
|
||||
eqT1 = t1 >= 0
|
||||
eq1x = x + Dx * t1 == (-26 * t1) + 287509258905812
|
||||
eq1y = y + Dy * t1 == (31 * t1) + 207449079739538
|
||||
eq1z = z + Dz * t1 == (8 * t1) + 280539021150559
|
||||
t2 = Real('t2')
|
||||
eqT2 = t2 >= 0
|
||||
eq2x = x + Dx * t2 == (-147 * t2) + 390970075767404
|
||||
eq2y = y + Dy * t2 == (-453 * t2) + 535711685410735
|
||||
eq2z = z + Dz * t2 == (-149 * t2) + 404166182422876
|
||||
#solve for x, y, z, Dx, Dy, Dz, t1, t2, t3.
|
||||
|
||||
|
||||
s.add(eqT0,
|
||||
eq0x,
|
||||
eq0y,
|
||||
eq0z,
|
||||
eqT1,
|
||||
eq1x,
|
||||
eq1y,
|
||||
eq1z,
|
||||
eqT2,
|
||||
eq2x,
|
||||
eq2y,
|
||||
eq2z)
|
||||
|
||||
if s.check() == sat:
|
||||
print("Solution:", s.model())
|
||||
else:
|
||||
print("No solution found")
|
||||
|
||||
print(273997500449219 + 463222539161932 + 239756157786030)
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from z3 import *
|
||||
|
||||
x = Real('x')
|
||||
y = Real('y')
|
||||
|
||||
eq1 = x + y == 5
|
||||
eq2 = x - y == 3
|
||||
|
||||
s = Solver()
|
||||
s.add(eq1, eq2)
|
||||
|
||||
if s.check() == sat:
|
||||
print("Solution:", s.model())
|
||||
else:
|
||||
print("No solution found")
|
|
@ -0,0 +1,78 @@
|
|||
package day24
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func SystemsWithSymbols() (result string) {
|
||||
result += "\n"
|
||||
coords := []string{"x", "y", "z"}
|
||||
for i := 0; i < 3; i++ {
|
||||
for _, coord := range coords {
|
||||
result += fmt.Sprintf("%s + D%s * t%d == %s%d + D%s%d * t%d\n",
|
||||
coord, coord, i, coord, i, coord, i, i)
|
||||
}
|
||||
}
|
||||
|
||||
result += "solve for x, y, z, Dx, Dy, Dz, t1, t2, t3. ti > 0"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func SystemFromThreeHailstones(hails []HailParam) (result string) {
|
||||
result += "\n"
|
||||
coords := []string{"x", "y", "z"}
|
||||
for i := 0; i < 3; i++ {
|
||||
result += fmt.Sprintf("t%d >= 0\n", i)
|
||||
hailIter := hails[i]
|
||||
for _, coord := range coords {
|
||||
result += fmt.Sprintf("%s + D%s * t%d == %d + %d * t%d\n",
|
||||
coord, coord, i,
|
||||
hailIter.GetCoord(coord), hailIter.GetSpeedOf(coord), i)
|
||||
}
|
||||
}
|
||||
|
||||
result += "solve for x, y, z, Dx, Dy, Dz, t1, t2, t3."
|
||||
return
|
||||
}
|
||||
|
||||
func SystemFromThreeHailstonesToTheLeft(hails []HailParam) (result string) {
|
||||
result += "\n"
|
||||
coords := []string{"x", "y", "z"}
|
||||
for i := 0; i < 3; i++ {
|
||||
result += fmt.Sprintf("t%d >= 0\n", i)
|
||||
hailIter := hails[i]
|
||||
for _, coord := range coords {
|
||||
result += fmt.Sprintf("%s + D%s * t%d - (%d * t%d) == %d \n",
|
||||
coord, coord, i,
|
||||
hailIter.GetSpeedOf(coord), i, hailIter.GetCoord(coord))
|
||||
}
|
||||
}
|
||||
|
||||
result += "solve for x, y, z, Dx, Dy, Dz, t1, t2, t3."
|
||||
return
|
||||
}
|
||||
|
||||
func SystemAsPythonInit(hails []HailParam) (result string) {
|
||||
result += "\n"
|
||||
coords := []string{"x", "y", "z"}
|
||||
for _, coord := range coords {
|
||||
result += fmt.Sprintf("%s = Real('%s')\n", coord, coord)
|
||||
result += fmt.Sprintf("D%s = Real('D%s')\n", coord, coord)
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
result += fmt.Sprintf("t%d = Real('t%d')\n", i, i)
|
||||
result += fmt.Sprintf("eqT%d = t%d >= 0\n", i, i)
|
||||
hailIter := hails[i]
|
||||
for _, coord := range coords {
|
||||
result += fmt.Sprintf("eq%d%s = %s + D%s * t%d == (%d * t%d) + %d \n",
|
||||
i, coord,
|
||||
coord, coord, i,
|
||||
hailIter.GetSpeedOf(coord), i, hailIter.GetCoord(coord))
|
||||
}
|
||||
}
|
||||
|
||||
result += "//solve for x, y, z, Dx, Dy, Dz, t1, t2, t3."
|
||||
return
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package day24
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPrintJustSymbol(t *testing.T) {
|
||||
t.Log(SystemsWithSymbols())
|
||||
}
|
||||
|
||||
func TestPrintSystemExample(t *testing.T) {
|
||||
filename := "example"
|
||||
hails := ReadHailFile(filename)
|
||||
t.Log(SystemAsPythonInit(hails))
|
||||
}
|
||||
|
||||
func TestPrintSystemInput(t *testing.T) {
|
||||
filename := "input"
|
||||
hails := ReadHailFile(filename)
|
||||
t.Log(SystemAsPythonInit(hails))
|
||||
}
|
12
flake.lock
12
flake.lock
|
@ -5,11 +5,11 @@
|
|||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"lastModified": 1701680307,
|
||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -20,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1701253981,
|
||||
"narHash": "sha256-ztaDIyZ7HrTAfEEUt9AtTDNoCYxUdSd6NrRHaYOIxtk=",
|
||||
"lastModified": 1703255338,
|
||||
"narHash": "sha256-Z6wfYJQKmDN9xciTwU3cOiOk+NElxdZwy/FiHctCzjU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e92039b55bcd58469325ded85d4f58dd5a4eaf58",
|
||||
"rev": "6df37dc6a77654682fe9f071c62b4242b5342e04",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
pkgs.wgo
|
||||
pkgs.semgrep
|
||||
pkgs.gopls
|
||||
|
||||
pkgs.python3
|
||||
pkgs.python311Packages.z3-solver
|
||||
];
|
||||
shellHook = ''
|
||||
export GOPATH=$PWD/.go
|
||||
|
|
6
main.go
6
main.go
|
@ -4,15 +4,15 @@ import (
|
|||
"log"
|
||||
"time"
|
||||
|
||||
"sunshine.industries/aoc2023/day22"
|
||||
"sunshine.industries/aoc2023/day24"
|
||||
)
|
||||
|
||||
func main() {
|
||||
startTime := time.Now()
|
||||
log.Print("> starting run:")
|
||||
|
||||
result := day22.Run()
|
||||
log.Printf("\n\nday22 result: %d\n****\n", result)
|
||||
result := day24.Run()
|
||||
log.Printf("\n\nday24 result: %d\n****\n", result)
|
||||
endTime := time.Now()
|
||||
diff := endTime.Sub(startTime)
|
||||
log.Printf("execution took %s", diff.String())
|
||||
|
|
Loading…
Reference in New Issue