Advertisement
Guest User

Untitled

a guest
Jan 14th, 2024
84
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 7.08 KB | None | 0 0
  1. public class Day17
  2.   {
  3.     // Order here is important. Helps determine turns
  4.     public enum Direction
  5.     {
  6.       UP,
  7.       LEFT,
  8.       DOWN,
  9.       RIGHT
  10.     }
  11.  
  12.     class Node
  13.     {
  14.       public int x { get; }
  15.       public int y { get; }
  16.       public int heatloss { get; }
  17.       public Node(int x, int y, int h)
  18.       {
  19.         this.x = x;
  20.         this.y = y;
  21.         heatloss = h;
  22.       }
  23.  
  24.       public override string ToString()
  25.       {
  26.         return $"{x},{y}: {heatloss}";
  27.       }
  28.     }
  29.     class State
  30.     {
  31.       public (int x, int y) position { get; }
  32.       public Direction entry { get; }
  33.       public int steps { get; }
  34.  
  35.       public State((int x, int y) p, Direction d, int s)
  36.       {
  37.         position = p;
  38.         entry = d;
  39.         steps = s;
  40.       }
  41.  
  42.       // Needed the Equals and GetHashCode overrides.
  43.       // Dictionary didn't recognise States as identical keys without these. (used reference, not values)
  44.       public override bool Equals(object obj)
  45.       {
  46.           if (obj == null || GetType() != obj.GetType())
  47.           {
  48.               return false;
  49.           }
  50.  
  51.           State other = (State)obj;
  52.           return position.x == other.position.x && position.y == other.position.y && entry == other.entry && steps == other.steps;
  53.       }
  54.  
  55.        public override int GetHashCode()
  56.       {
  57.           unchecked
  58.           {
  59.             int hash = 17;
  60.             hash = hash * 23 + position.GetHashCode();
  61.             hash = hash * 23 + entry.GetHashCode();
  62.             hash = hash * 23 + steps.GetHashCode();
  63.             return hash;
  64.           }
  65.       }
  66.     }
  67.     List<string> strings = new List<string>();
  68.     List<List<Node>> grid = new();
  69.  
  70.     public Day17(string fileName)
  71.     {
  72.       strings = FileReader.AsStringArray(fileName).ToList();
  73.       foreach ((string line, int x) in strings.WithIndex())
  74.       {
  75.         List<Node> newLine = new();
  76.         foreach ((char number, int y) in line.ToArray().WithIndex())
  77.         {
  78.           newLine.Add(new Node(x, y, int.Parse(number.ToString())));
  79.         }
  80.         grid.Add(newLine);
  81.       }
  82.     }
  83.  
  84.     public int PartOne()
  85.     {
  86.       // Min is 0 here because there is no min, and 1 would cause the first step from start to go RIGHT.
  87.       // This happens because the start position thinks it was entered from the LEFT.
  88.       return ABetterAStar(0, 3);
  89.     }
  90.  
  91.     public int PartTwo()
  92.     {
  93.       return ABetterAStar(4, 10);
  94.     }
  95.  
  96.     private int ABetterAStar(int minMoves, int maxMoves)
  97.     {
  98.       // Start and end locations. Top-left to bottom-right.
  99.       (int x, int y) start = (0, 0);
  100.       (int x, int y) end = (grid.Count - 1, grid.First().Count - 1);
  101.  
  102.       // Every state knows its position, the direction it was entered from, and steps from that direction so far.
  103.       State firstState = new State(start, Direction.LEFT, 0);
  104.       State endState = new State(end, Direction.DOWN, 0);
  105.       // Dictionary tracks if we've entered a square from this direction before, and how much it cost previously
  106.       Dictionary<State, int> costMap = new Dictionary<State, int>();
  107.       costMap[firstState] = 0;
  108.       // Needs to be priority to focus on the ones that have the best heatloss + distance to end combo.
  109.       PriorityQueue<State, int> queue = new PriorityQueue<State, int>();
  110.       queue.Enqueue(firstState, 0);
  111.  
  112.       while (queue.Count > 0)
  113.       {
  114.         State current = queue.Dequeue();
  115.         if (current.position == end && current.steps >= minMoves)
  116.         {
  117.           // We know the first entry point will be the best, because its neighbours are resolved in priority.
  118.           return costMap[current];
  119.         }
  120.  
  121.         // Check all the neighbouring squares for better paths
  122.         foreach (State neighbour in GetNeighboursAsStates(current, minMoves, maxMoves))
  123.         {
  124.           if (IsValidPosition(neighbour.position.x, neighbour.position.y))
  125.           {
  126.             int newCost = costMap[current] + grid[neighbour.position.x][neighbour.position.y].heatloss;
  127.             // If this new entry to a square is better than our previous entry or if it's the first entry to this square
  128.             if (newCost < costMap.GetValueOrDefault(neighbour, int.MaxValue))
  129.             {
  130.               // Remember this new cost and add it to the queue
  131.               costMap[neighbour] = newCost;
  132.               // Manhattan Distance keeps us moving the right direction.
  133.               queue.Enqueue(neighbour, costMap[neighbour] + GetManhattanDistance(neighbour, endState));
  134.             }
  135.           }
  136.         }
  137.       }
  138.       return -1;
  139.     }
  140.  
  141.     private int GetManhattanDistance(State current, State end)
  142.     {
  143.       return Math.Abs(current.position.x - end.position.x) + Math.Abs(current.position.y - end.position.y);
  144.     }
  145.  
  146.     private bool IsValidPosition(int x, int y)
  147.     {
  148.       if (x < 0)
  149.       {
  150.         return false;
  151.       }
  152.       // below
  153.       if (x >= grid.Count)
  154.       {
  155.         return false;
  156.       }
  157.       // left
  158.       if (y < 0)
  159.       {
  160.         return false;
  161.       }
  162.       // right
  163.       if (y >= grid.First().Count)
  164.       {
  165.         return false;
  166.       }
  167.       // Passes all checks
  168.       return true;
  169.     }
  170.  
  171.     private List<State> GetNeighboursAsStates(State current, int minMoves, int maxMoves)
  172.     {
  173.       List<State> neighbours = new();
  174.       // If we're at the max, we need to take a turn. Return left and right from entry direction.
  175.       if (current.steps >= minMoves)
  176.       {
  177.         // Get left and right directions from entry point
  178.         List<Direction> turnDirections = GetTurnDirections(current.entry);
  179.         foreach (Direction d in turnDirections)
  180.         {
  181.           (int x, int y) neighbour = GetNeighbourCoordsFromDirection(current, d);
  182.           neighbours.Add(new State(neighbour, d, 1)); // Count reset to 1
  183.         }
  184.       }
  185.  
  186.       // We can keep going straight as well, so include that direction
  187.       if (current.steps < maxMoves)
  188.       {
  189.         (int x, int y) nextPosition = GetNeighbourCoordsFromDirection(current, current.entry);
  190.         neighbours.Add(new State(nextPosition, current.entry, current.steps + 1));
  191.       }
  192.       return neighbours;
  193.     }
  194.  
  195.     private (int x, int y) GetNeighbourCoordsFromDirection(State current, Direction entryDirection)
  196.     {
  197.       switch (entryDirection)
  198.       {
  199.         case Direction.UP:
  200.           return (current.position.x + 1, current.position.y);
  201.         case Direction.DOWN:
  202.           return (current.position.x - 1, current.position.y);
  203.         case Direction.LEFT:
  204.           return (current.position.x, current.position.y + 1);
  205.         case Direction.RIGHT:
  206.           return (current.position.x, current.position.y - 1);
  207.         default:
  208.           return (current.position.x, current.position.y);
  209.       }
  210.     }
  211.  
  212.     private List<Direction> GetTurnDirections(Direction entry)
  213.     {
  214.       // We entered in direction x, need to add x + 1 and x + 3, all modulus by 4 (number of possible directions).
  215.       List<Direction> list = new();
  216.       list.Add((Direction)(((int)entry + 1) % 4));
  217.       list.Add((Direction)(((int)entry + 3) % 4));
  218.       return list;
  219.     }
  220. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement