Fucking day 13
This commit is contained in:
1279
day13/data.txt
Normal file
1279
day13/data.txt
Normal file
File diff suppressed because it is too large
Load Diff
15
day13/example.txt
Normal file
15
day13/example.txt
Normal file
@ -0,0 +1,15 @@
|
||||
Button A: X+94, Y+34
|
||||
Button B: X+22, Y+67
|
||||
Prize: X=8400, Y=5400
|
||||
|
||||
Button A: X+26, Y+66
|
||||
Button B: X+67, Y+21
|
||||
Prize: X=12748, Y=12176
|
||||
|
||||
Button A: X+17, Y+86
|
||||
Button B: X+84, Y+37
|
||||
Prize: X=7870, Y=6450
|
||||
|
||||
Button A: X+69, Y+23
|
||||
Button B: X+27, Y+71
|
||||
Prize: X=18641, Y=10279
|
3
day13/go.mod
Normal file
3
day13/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module stevenlr.com/aoc2024/day13
|
||||
|
||||
go 1.23.3
|
241
day13/main.go
Normal file
241
day13/main.go
Normal file
@ -0,0 +1,241 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// fmt.Println(part1(readData("example.txt", 0)), 480)
|
||||
// fmt.Println(part1(readData("data.txt", 0)))
|
||||
fmt.Println(part2(readData("example.txt", 0)), 480)
|
||||
fmt.Println(part2(readData("data.txt", 0)), 31623)
|
||||
fmt.Println(part2(readData("example.txt", 10000000000000)))
|
||||
fmt.Println(part2(readData("data.txt", 10000000000000)))
|
||||
}
|
||||
|
||||
type Machine struct {
|
||||
PrizeX, PrizeY int64
|
||||
DxA, DyA int64
|
||||
DxB, DyB int64
|
||||
}
|
||||
|
||||
type Position struct {
|
||||
X, Y int64
|
||||
}
|
||||
|
||||
type Candidate struct {
|
||||
Pos Position
|
||||
EstimateCost int64
|
||||
}
|
||||
|
||||
type CandidateItem struct {
|
||||
Cnd *Candidate
|
||||
Index int
|
||||
}
|
||||
|
||||
type CandidateQueue struct {
|
||||
elements []*CandidateItem
|
||||
index map[Position]*CandidateItem
|
||||
}
|
||||
|
||||
func MakeCandidateQueue() *CandidateQueue {
|
||||
queue := new(CandidateQueue)
|
||||
queue.index = make(map[Position]*CandidateItem)
|
||||
return queue
|
||||
}
|
||||
|
||||
func (queue *CandidateQueue) Len() int {
|
||||
return len(queue.elements)
|
||||
}
|
||||
|
||||
func (queue *CandidateQueue) Less(i, j int) bool {
|
||||
return queue.elements[i].Cnd.EstimateCost < queue.elements[i].Cnd.EstimateCost
|
||||
}
|
||||
|
||||
func (queue *CandidateQueue) Swap(i, j int) {
|
||||
queue.elements[i], queue.elements[j] = queue.elements[j], queue.elements[i]
|
||||
queue.elements[i].Index = i
|
||||
queue.elements[j].Index = j
|
||||
}
|
||||
|
||||
func (queue *CandidateQueue) Push(x any) {
|
||||
candidate := x.(Candidate)
|
||||
item := &CandidateItem{&candidate, len(queue.elements)}
|
||||
queue.elements = append(queue.elements, item)
|
||||
queue.index[candidate.Pos] = item
|
||||
}
|
||||
|
||||
func (queue *CandidateQueue) AddElement(candidate Candidate) {
|
||||
item, alreadyIn := queue.index[candidate.Pos]
|
||||
if alreadyIn {
|
||||
item.Cnd = &candidate
|
||||
heap.Fix(queue, item.Index)
|
||||
} else {
|
||||
heap.Push(queue, candidate)
|
||||
}
|
||||
}
|
||||
|
||||
func (queue *CandidateQueue) Pop() any {
|
||||
if len(queue.elements) == 0 {
|
||||
return nil
|
||||
} else {
|
||||
elmt := queue.elements[len(queue.elements)-1]
|
||||
queue.elements = queue.elements[:len(queue.elements)-1]
|
||||
delete(queue.index, elmt.Cnd.Pos)
|
||||
return elmt.Cnd
|
||||
}
|
||||
}
|
||||
|
||||
func part1(machines []Machine) (totalPrice int64) {
|
||||
for _, m := range machines {
|
||||
sol := part1Machine(m)
|
||||
if sol >= 0 {
|
||||
totalPrice += sol
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func part1Machine(machine Machine) (price int64) {
|
||||
fmt.Println(machine)
|
||||
|
||||
maxStepX := max(machine.DxA, machine.DxB)
|
||||
maxStepY := max(machine.DyA, machine.DyB)
|
||||
|
||||
estimateCost := func(x, y int64) int64 {
|
||||
distX := machine.PrizeX - x
|
||||
distY := machine.PrizeY - y
|
||||
return int64(math.Floor(min(float64(distX)/float64(maxStepX), float64(distY)/float64(maxStepY))))
|
||||
}
|
||||
|
||||
realCost := make([][]int64, machine.PrizeY+1)
|
||||
for y := range machine.PrizeY + 1 {
|
||||
realCost[y] = make([]int64, machine.PrizeX+1)
|
||||
for x := range machine.PrizeX + 1 {
|
||||
realCost[y][x] = math.MaxInt
|
||||
}
|
||||
}
|
||||
|
||||
realCost[0][0] = 0
|
||||
|
||||
queue := MakeCandidateQueue()
|
||||
queue.AddElement(Candidate{Position{0, 0}, estimateCost(0, 0)})
|
||||
|
||||
for queue.Len() > 0 {
|
||||
candidate := heap.Pop(queue).(*Candidate)
|
||||
|
||||
xa := candidate.Pos.X + machine.DxA
|
||||
ya := candidate.Pos.Y + machine.DyA
|
||||
realCostA := realCost[candidate.Pos.Y][candidate.Pos.X] + 3
|
||||
if xa == machine.PrizeX && ya == machine.PrizeY {
|
||||
return realCostA
|
||||
} else if xa <= machine.PrizeX && ya <= machine.PrizeY && realCostA < realCost[ya][xa] {
|
||||
realCost[ya][xa] = realCostA
|
||||
queue.AddElement(Candidate{Position{xa, ya}, realCostA + estimateCost(xa, ya)})
|
||||
}
|
||||
|
||||
xb := candidate.Pos.X + machine.DxB
|
||||
yb := candidate.Pos.Y + machine.DyB
|
||||
realCostB := realCost[candidate.Pos.Y][candidate.Pos.X] + 1
|
||||
if xb == machine.PrizeX && yb == machine.PrizeY {
|
||||
return realCostB
|
||||
} else if xb <= machine.PrizeX && yb <= machine.PrizeY && realCostB < realCost[yb][xb] {
|
||||
realCost[yb][xb] = realCostB
|
||||
queue.AddElement(Candidate{Position{xb, yb}, realCostB + estimateCost(xb, yb)})
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func part2(machines []Machine) (totalPrice int64) {
|
||||
for _, m := range machines {
|
||||
sol := part2Machine(m)
|
||||
if sol >= 0 {
|
||||
totalPrice += sol
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func part2Machine(machine Machine) (price int64) {
|
||||
// This is fucking dumb.
|
||||
// The general problem uses annoying math with diophantine stuff and shit.
|
||||
// But they constructed the dataset so that we can do the dumb thing of
|
||||
// solving the equations like dumb fucks and it so happens that the real solutions
|
||||
// minimize the cost metric. So fucking useless and stupid.
|
||||
|
||||
// ax a + bx b = tx
|
||||
// ay a + by b = ty
|
||||
//
|
||||
// (ax bx) * (a) = (tx)
|
||||
// (ay by) (b) (ty)
|
||||
//
|
||||
// (a) = ( by -bx) * (tx)
|
||||
// (b) (-ay ax) (ty) / (ax by - ay bx)
|
||||
|
||||
det := machine.DxA*machine.DyB - machine.DyA*machine.DxB
|
||||
if det == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
a := int64(float64(machine.DyB*machine.PrizeX-machine.DxB*machine.PrizeY) / float64(det))
|
||||
b := int64(float64(machine.DxA*machine.PrizeY-machine.DyA*machine.PrizeX) / float64(det))
|
||||
|
||||
if machine.DxA*a+machine.DxB*b == machine.PrizeX && machine.DyA*a+machine.DyB*b == machine.PrizeY {
|
||||
return a*3 + b
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
func trimAll(strs []string) {
|
||||
for i, s := range strs {
|
||||
strs[i] = strings.TrimSpace(s)
|
||||
}
|
||||
}
|
||||
|
||||
func parseCoordinateValue(s string) (value int) {
|
||||
value, _ = strconv.Atoi(s[2:])
|
||||
return
|
||||
}
|
||||
|
||||
func parsePair(s string) (x, y int64) {
|
||||
parts := strings.Split(strings.Split(s, ":")[1], ",")
|
||||
trimAll(parts)
|
||||
|
||||
x = int64(parseCoordinateValue(parts[0]))
|
||||
y = int64(parseCoordinateValue(parts[1]))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func readData(fileName string, offset int64) (machines []Machine) {
|
||||
fp, _ := os.Open(fileName)
|
||||
scanner := bufio.NewScanner(fp)
|
||||
|
||||
var machine Machine
|
||||
|
||||
for line := 0; scanner.Scan(); line++ {
|
||||
switch line % 4 {
|
||||
case 0:
|
||||
machine = Machine{}
|
||||
machine.DxA, machine.DyA = parsePair(scanner.Text())
|
||||
case 1:
|
||||
machine.DxB, machine.DyB = parsePair(scanner.Text())
|
||||
case 2:
|
||||
machine.PrizeX, machine.PrizeY = parsePair(scanner.Text())
|
||||
machine.PrizeX += offset
|
||||
machine.PrizeY += offset
|
||||
machines = append(machines, machine)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
Reference in New Issue
Block a user