Day 18: Ram Run
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
Dart
I knew keeping my search code from day 16 would come in handy, I just didn’t expect it to be so soon.
For Part 2 it finds that same path (laziness on my part), then does a simple binary chop to home in on the last valid path. (was then searches for the first block that will erm block that path, and re-runs the search after that block has dropped, repeating until blocked. Simple but okay. )
90 lines, half of which is my copied search method. Runs in a couple of seconds which isn’t great, but isn’t bad. Binary chop dropped it to 200ms.
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:more/more.dart';
var d4 = <Point<num>>[Point(0, 1), Point(0, -1), Point(1, 0), Point(-1, 0)];
solve(List<String> lines, int count, Point end, bool inPart1) {
var blocks = (lines
.map((e) => e.split(',').map(int.parse).toList())
.map((p) => Point<num>(p[0], p[1]))).toList();
var blocksSofar = blocks.take(count).toSet();
var start = Point(0, 0);
Map<Point, num> fNext(Point here) => {
for (var d in d4
.map((d) => d + here)
.where((e) =>
e.x.between(start.x, end.x) &&
e.y.between(start.y, end.y) &&
!blocksSofar.contains(e))
.toList())
d: 1
};
int fHeur(Point here) => 1;
bool fAtEnd(Point here) => here == end;
var cost = aStarSearch<Point>(start, fNext, fHeur, fAtEnd);
if (inPart1) return cost.first;
var lo = count, hi = blocks.length;
while (lo <= hi) {
var mid = (lo + hi) ~/ 2;
blocksSofar = blocks.take(mid).toSet();
cost = aStarSearch<Point>(start, fNext, fHeur, fAtEnd);
(cost.first > 0) ? lo = mid + 1 : hi = mid - 1;
}
var p = blocks[lo - 1];
return '${p.x},${p.y}';
}
part1(lines, count, end) => solve(lines, count, end, true);
part2(lines, count, end) => solve(lines, count, end, false);
That search method
/// Returns cost to destination, plus list of routes to destination.
/// Does Dijkstra/A* search depending on whether heuristic returns 1 or
/// something better.
(num, List<List<T>>) aStarSearch<T>(T start, Map<T, num> Function(T) fNext,
int Function(T) fHeur, bool Function(T) fAtEnd,
{multiplePaths = false}) {
var cameFrom = SetMultimap<T, T>.fromEntries([MapEntry(start, start)]);
var ends = <T>{};
var front = PriorityQueue<T>((a, b) => fHeur(a).compareTo(fHeur(b)))
..add(start);
var cost = <T, num>{start: 0};
while (front.isNotEmpty) {
var here = front.removeFirst();
if (fAtEnd(here)) {
ends.add(here);
continue;
}
var ns = fNext(here);
for (var n in ns.keys) {
var nCost = cost[here]! + ns[n]!;
if (!cost.containsKey(n) || nCost < cost[n]!) {
cost[n] = nCost;
front.add(n);
cameFrom.removeAll(n);
cameFrom[n].add(here);
}
if (multiplePaths && cost[n] == nCost) cameFrom[n].add(here);
}
}
Iterable<List<T>> routes(T h) sync* {
if (h == start) {
yield [h];
return;
}
for (var p in cameFrom[h]) {
yield* routes(p).map((e) => e + [h]);
}
}
if (ends.isEmpty) return (-1, []);
var minCost = ends.map((e) => cost[e]!).min;
ends = ends.where((e) => cost[e]! == minCost).toSet();
return (minCost, ends.fold([], (s, t) => s..addAll(routes(t).toList())));
}