Skip Navigation

πŸƒβ€β™€οΈ - 2024 DAY 20 SOLUTIONS -πŸƒβ€β™€οΈ

Day 20: Race Condition

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

16 comments
  • Haskell

    I should probably do something about the n2 loop in findCheats, but it's fast enough for now. Besides, my brain has melted. Somewhat better (0.575s). Can't shake the feeling that I'm missing an obvious closed-form solution, though.

     undefined
        
    import Control.Monad
    import Data.List
    import Data.Map (Map)
    import Data.Map qualified as Map
    import Data.Maybe
    import Data.Set qualified as Set
    
    type Pos = (Int, Int)
    
    readInput :: String -> Map Pos Char
    readInput s = Map.fromList [((i, j), c) | (i, l) <- zip [0 ..] (lines s), (j, c) <- zip [0 ..] l]
    
    solveMaze :: Map Pos Char -> Maybe [Pos]
    solveMaze maze = listToMaybe $ go [] start
      where
        walls = Map.keysSet $ Map.filter (== '#') maze
        Just [start, end] = traverse (\c -> fst <$> find ((== c) . snd) (Map.assocs maze)) ['S', 'E']
        go h p@(i, j)
          | p == end = return [end]
          | otherwise = do
              p' <- [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)]
              guard $ p' `notElem` h
              guard $ p' `Set.notMember` walls
              (p :) <$> go [p] p'
    
    dist (i1, j1) (i2, j2) = abs (i2 - i1) + abs (j2 - j1)
    
    findCheats :: [Pos] -> Int -> Int -> [((Pos, Pos), Int)]
    findCheats path minScore maxLen = do
      (t2, end) <- zip [0 ..] path
      (t1, start) <- zip [0 .. t2 - minScore] path
      let len = dist start end
          score = t2 - t1 - len
      guard $ len <= maxLen
      guard $ score >= minScore
      return ((start, end), score)
    
    main = do
      Just path <- solveMaze . readInput <$> readFile "input20"
      mapM_ (print . length . findCheats path 100) [2, 20]
    
      
    • Ah, the number of potential start points is much smaller than the length of the path. I guess a map from position to offset would do it, but I'm not sure it's really worth it.

  • Rust

    The important part here is to build two maps first that hold for every point on the path the distance to the start and the distance to the end respectively. Then calculating the path length for a cheat vector is a simple lookup. For part 2 I first generated all vectors with manhattan distance <= 20, or more specifically, exactly half of those vectors to avoid checking the same vector in both directions.

    Part 2 takes 15ms. The code looks a bit unwieldy at parts and uses the pyramid of doom paradigm but works pretty well.

    Also on github

  • C++ / Boost

    Ah, cunning - my favourite one so far this year, I think. Nothing too special compared to the other solutions - floods the map using Dijkstra, then checks "every pair" for how much of a time saver it is. 0.3s on my laptop; it iterates through every pair twice since it does part 1 and part 2 separately, which could easily be improved upon.

  • Dart

    Simple path-finding + brute force. O(n*sqrt-n) I think but only takes ~200 milliseconds so that'll do for today. Time to walk the dog!

    After being so pleased to have written my own path-finding algorithm the other day, I discovered that my regular more library already includes one. D'oh.

    (edit: shaved some milliseconds off)

     undefined
        
    import 'dart:math';
    import 'package:more/more.dart';
    
    List<Point<num>> d4 = [Point(1, 0), Point(-1, 0), Point(0, 1), Point(0, -1)];
    
    List<Point<num>> findPath(List<String> lines) {
      var maze = {
        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
      }.withDefault('#');
      var path = AStarSearchIterable<Point>(
          startVertices: [maze.entries.firstWhere((e) => e.value == 'S').key],
          successorsOf: (p) => d4.map((e) => (e + p)).where((n) => maze[n] != '#'),
          costEstimate: (p) => 1,
          targetPredicate: (p) => maze[p] == 'E').first.vertices;
      return path;
    }
    
    num dist(Point p1, Point p2) => (p1.x - p2.x).abs() + (p1.y - p2.y).abs();
    
    solve(List<String> lines, int cheat, int target) {
      List<Point<num>> path = findPath(lines);
      var cheats = 0;
      for (int s = 0; s < path.length - cheat; s++) {
        for (int e = s + cheat; e < path.length; e++) {
          var d = dist(path[e], path[s]);
          var diff = e - s - d;
          if (d <= cheat && diff >= target) cheats += 1;
        }
      }
      return cheats;
    }
    
      
16 comments