Day 16: Reindeer Maze

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

  • @mykl
    link
    2
    edit-2
    7 minutes ago

    Dart

    I liked the flexibility of the path operator in the Uiua solution so much that I built a similar search function in Dart. Not quite as compact, but still an interesting piece of code that I will keep on hand for other path-finding puzzles.

    About 80 lines of code, about half of which is the super-flexible search function.

    import 'dart:math';
    import 'package:collection/collection.dart';
    import 'package:more/more.dart';
    
    List<Point<num>> d4 = [Point(1, 0), Point(-1, 0), Point(0, 1), Point(0, -1)];
    
    /// 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) {
      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);
          }
          if (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]);
        }
      }
    
      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())));
    }
    
    typedef PP = (Point, Point);
    
    (num, List<List<PP>>) solve(List<String> lines) {
      var grid = {
        for (var r in lines.indexed())
          for (var c in r.value.split('').indexed().where((e) => e.value != '#'))
            Point<num>(c.index, r.index): c.value
      };
      var start = grid.entries.firstWhere((e) => e.value == 'S').key;
      var end = grid.entries.firstWhere((e) => e.value == 'E').key;
      var dir = Point<num>(1, 0);
    
      fHeur(PP pd) => 1; // faster than euclidean distance.
      fNextAndCost(PP pd) => <PP, int>{
            for (var n in d4
                .where((n) => n != pd.last * -1 && grid.containsKey(pd.first + n)))
              (pd.first + n, n): ((n == pd.last) ? 1 : 1001) // (Point, Dir) : Cost
          };
      fAtEnd(PP pd) => pd.first == end;
    
      return aStarSearch<PP>((start, dir), fNextAndCost, fHeur, fAtEnd);
    }
    
    part1(List<String> lines) => solve(lines).first;
    
    part2(List<String> lines) => solve(lines)
        .last
        .map((l) => l.map((e) => e.first).toSet())
        .flattenedToSet
        .length;