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"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1694529238,
|
"lastModified": 1701680307,
|
||||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -20,11 +20,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1701253981,
|
"lastModified": 1703255338,
|
||||||
"narHash": "sha256-ztaDIyZ7HrTAfEEUt9AtTDNoCYxUdSd6NrRHaYOIxtk=",
|
"narHash": "sha256-Z6wfYJQKmDN9xciTwU3cOiOk+NElxdZwy/FiHctCzjU=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e92039b55bcd58469325ded85d4f58dd5a4eaf58",
|
"rev": "6df37dc6a77654682fe9f071c62b4242b5342e04",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
pkgs.wgo
|
pkgs.wgo
|
||||||
pkgs.semgrep
|
pkgs.semgrep
|
||||||
pkgs.gopls
|
pkgs.gopls
|
||||||
|
|
||||||
|
pkgs.python3
|
||||||
|
pkgs.python311Packages.z3-solver
|
||||||
];
|
];
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
export GOPATH=$PWD/.go
|
export GOPATH=$PWD/.go
|
||||||
|
|
6
main.go
6
main.go
|
@ -4,15 +4,15 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sunshine.industries/aoc2023/day22"
|
"sunshine.industries/aoc2023/day24"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
log.Print("> starting run:")
|
log.Print("> starting run:")
|
||||||
|
|
||||||
result := day22.Run()
|
result := day24.Run()
|
||||||
log.Printf("\n\nday22 result: %d\n****\n", result)
|
log.Printf("\n\nday24 result: %d\n****\n", result)
|
||||||
endTime := time.Now()
|
endTime := time.Now()
|
||||||
diff := endTime.Sub(startTime)
|
diff := endTime.Sub(startTime)
|
||||||
log.Printf("execution took %s", diff.String())
|
log.Printf("execution took %s", diff.String())
|
||||||
|
|
Loading…
Reference in New Issue