diff --git a/2018/day22/coord.go b/2018/day22/coord.go new file mode 100644 index 0000000..4793b3d --- /dev/null +++ b/2018/day22/coord.go @@ -0,0 +1,19 @@ +package main + +type coord struct { + x, y int +} + +func (c coord) neighbors() []coord { + n := []coord{ + {c.x + 1, c.y}, + {c.x, c.y + 1}, + } + if c.x > 0 { + n = append(n, coord{c.x - 1, c.y}) + } + if c.y > 0 { + n = append(n, coord{c.x, c.y - 1}) + } + return n +} diff --git a/2018/day22/day22.go b/2018/day22/day22.go index e60d206..4ae56db 100644 --- a/2018/day22/day22.go +++ b/2018/day22/day22.go @@ -2,141 +2,74 @@ package main import ( "bufio" + "container/heap" "fmt" - "log" "os" - "regexp" - "strconv" - "strings" ) -var depth int -var target *Pos +const bailFactor = 8 func main() { - input := stdinToStringSlice() - reg, err := regexp.Compile("[^0-9]+") - if err != nil { - log.Fatal(err) - } - depth = Atoi(reg.ReplaceAllString(input[0], "")) - tgt := strings.Trim(input[1], "target: ") - tgtPts := strings.Split(tgt, ",") - target = NewPos(Atoi(tgtPts[0]), Atoi(tgtPts[1]), depth) - + inp := StdinToStringSlice() + fmt.Printf("# Part 1\nRisk level: %d\n", RiskLevel(inp)) + fmt.Printf("# Part 2\nRescue minutes: %d\n", Rescue(inp)) } -func part1() { - c := BuildCave(target) - var risk int - for y := 0; y <= target.y; y++ { - for x := 0; x <= target.x; x++ { - risk += c.risk(x, y, depth) +func RiskLevel(input []string) int { + m := NewMap(input) + sum := 0 + for y := 0; y <= m.target.y; y++ { + for x := 0; x <= m.target.x; x++ { + sum += m.Type(x, y) } } - fmt.Println("= Part 1 =") - fmt.Println(risk) + return sum } -func part2() { - // Find shortest path form 0,0 to target - // taking into account cost of changing gear - // when necessary - -} - -type Cave struct { - positions map[string]*Pos -} - -func BuildCave(target *Pos) *Cave { - c := &Cave{ - positions: make(map[string]*Pos), +func Rescue(input []string) int { + m := NewMap(input) + queue := PriorityQueue{ + &Item{pos: coord{0, 0}, time: 0, equip: ToolTorch}, } - c.addPos(target) - return c -} + heap.Init(&queue) -func (c *Cave) addPos(p *Pos) { - c.positions[p.string()] = p -} - -func (c *Cave) getPos(x, y, z int) *Pos { - var p *Pos - var ok bool - if p, ok = c.positions[c.buildKey(x, y, z)]; !ok { - p = NewPos(x, y, z) - c.addPos(p) + type step struct { + coord coord + equip int } - return p -} -func (c *Cave) buildKey(x, y, z int) string { - return fmt.Sprintf( - "(%d,%d,%d)", - x, y, z, - ) -} + distances := map[step]int{ + step{coord: coord{0, 0}, equip: ToolTorch}: 0, + } -func (c *Cave) getGeoIndex(x, y, z int) int { - p := c.getPos(x, y, z) - if p.geo < 0 { - if x == 0 && y == 0 { - p.geo = 0 - } else if x == target.x && y == target.y { - p.geo = 0 - } else if y == 0 { - p.geo = x * 16807 - } else if x == 0 { - p.geo = y * 48271 - } else { - p.geo = c.erosion(x-1, y, z) * c.erosion(x, y-1, z) + for len(queue) > 0 { + item := (heap.Pop(&queue)).(*Item) + if item.pos.x == m.target.x && item.pos.y == m.target.y && item.equip == ToolTorch { + return item.time + } + + // Check if we're wandering too far away + if item.pos.x > bailFactor*m.target.x || item.pos.y > bailFactor*m.target.y { + continue + } + + if t, ok := distances[step{coord: item.pos, equip: item.equip}]; ok && t < item.time { + continue + } + + for _, n := range m.Neighbors(item.pos, item.equip) { + d := step{coord: n.pos, equip: n.equip} + if t, ok := distances[step{coord: n.pos, equip: n.equip}]; !ok || item.time+n.time < t { + distances[d] = item.time + n.time + heap.Push(&queue, &Item{pos: n.pos, time: item.time + n.time, equip: n.equip}) + } } } - return p.geo + + return 0 } -func (c *Cave) erosion(x, y, z int) int { - p := c.getPos(x, y, z) - if p.erosion < 0 { - p.erosion = (c.getGeoIndex(x, y, z) + z) % 20183 - } - return p.erosion -} - -func (c *Cave) risk(x, y, z int) int { - return c.erosion(x, y, z) % 3 -} - -type Pos struct { - x, y, z int - geo, erosion int -} - -func NewPos(x, y, z int) *Pos { - r := &Pos{ - x: x, - y: y, - z: z, - geo: -1, - erosion: -1, - } - - return r -} - -func (p *Pos) equals(n *Pos) bool { - return p.x == n.x && p.y == n.y && p.z == n.z -} - -func (p *Pos) string() string { - return fmt.Sprintf( - "(%d,%d,%d)", - p.x, p.y, p.z, - ) -} - -func stdinToStringSlice() []string { +func StdinToStringSlice() []string { var input []string scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { @@ -144,12 +77,3 @@ func stdinToStringSlice() []string { } return input } - -func Atoi(i string) int { - var ret int - var err error - if ret, err = strconv.Atoi(i); err != nil { - log.Fatal("Invalid Atoi: " + i) - } - return ret -} diff --git a/2018/day22/map.go b/2018/day22/map.go new file mode 100644 index 0000000..e3a3a1c --- /dev/null +++ b/2018/day22/map.go @@ -0,0 +1,111 @@ +package main + +import "fmt" + +const ( + geologicY = 16807 + geologicX = 48271 + caveModulo = 20183 +) + +const ( + TypeRocky = 0 + TypeWet = 1 + TypeNarrow = 2 +) + +const ( + ToolNone = 1 << iota + ToolTorch + ToolGear +) + +type Map struct { + target coord + depth int + geologicIndicesCache map[int]map[int]int + erosionLevelsCache map[int]map[int]int +} + +func NewMap(input []string) *Map { + m := Map{} + m.geologicIndicesCache = make(map[int]map[int]int) + m.erosionLevelsCache = make(map[int]map[int]int) + + if len(input) != 2 { + panic("Invalid Input") + } + _, err := fmt.Sscanf(input[0], "depth: %d", &m.depth) + if err != nil { + panic(err) + } + _, err = fmt.Sscanf(input[1], "target: %d,%d", &m.target.x, &m.target.y) + if err != nil { + panic(err) + } + + return &m +} + +func (m *Map) GeologicIndex(x, y int) int { + if m.geologicIndicesCache[y] != nil { + if i, ok := m.geologicIndicesCache[y][x]; ok { + return i + } + } else { + m.geologicIndicesCache[y] = make(map[int]int) + } + + switch { + case x == 0 && y == 0, x == m.target.x && y == m.target.y: + m.geologicIndicesCache[y][x] = 0 + case y == 0: + m.geologicIndicesCache[y][x] = x * geologicY + case x == 0: + m.geologicIndicesCache[y][x] = y * geologicX + default: + m.geologicIndicesCache[y][x] = m.ErosionLevel(x-1, y) * m.ErosionLevel(x, y-1) + } + return m.geologicIndicesCache[y][x] +} + +func (m *Map) ErosionLevel(x, y int) int { + if m.erosionLevelsCache[y] != nil { + if level, ok := m.erosionLevelsCache[y][x]; ok { + return level + } + } else { + m.erosionLevelsCache[y] = make(map[int]int) + } + m.erosionLevelsCache[y][x] = (m.GeologicIndex(x, y) + m.depth) % caveModulo + return m.erosionLevelsCache[y][x] +} + +func (m Map) Type(x, y int) int { + return m.ErosionLevel(x, y) % 3 +} + +func (m Map) Neighbors(pos coord, equip int) []Item { + var n []Item + for _, c := range pos.neighbors() { + t := m.Type(c.x, c.y) + if equip&allowed(t) != 0 { + n = append(n, Item{pos: c, equip: equip, time: 1}) + n = append(n, Item{pos: c, equip: equip ^ allowed(t), time: 8}) + } + } + return n +} + +func allowed(regionType int) int { + switch regionType { + case TypeRocky: + return ToolGear | ToolTorch + case TypeWet: + return ToolGear | ToolNone + case TypeNarrow: + return ToolTorch | ToolNone + default: + panic(fmt.Errorf("unknown region type: %d", regionType)) + } +} diff --git a/2018/day22/priorityqueue.go b/2018/day22/priorityqueue.go new file mode 100644 index 0000000..16ad918 --- /dev/null +++ b/2018/day22/priorityqueue.go @@ -0,0 +1,48 @@ +package main + +type Item struct { + pos coord + equip int + time int + index int +} + +type PriorityQueue []*Item + +func (pq PriorityQueue) Len() int { + return len(pq) +} + +func (pq PriorityQueue) Less(i, j int) bool { + return pq[i].time < pq[j].time +} + +func (pq PriorityQueue) Swap(i, j int) { + pq[i], pq[j] = pq[j], pq[i] + pq[i].index = i + pq[j].index = j +} + +func (pq *PriorityQueue) Push(x interface{}) { + item := x.(*Item) + item.index = len(*pq) + *pq = append(*pq, item) +} + +func (pq *PriorityQueue) Pop() interface{} { + old := *pq + n := len(old) + item := old[n-1] + *pq = old[0 : n-1] + return item +} + +func (i Item) Compare(o Item) int { + switch { + case i.time > o.time: + return 1 + case i.time < o.time: + return -1 + } + return 0 +}