• 2 Posts
  • 151 Comments
Joined 1 year ago
cake
Cake day: September 1st, 2023

help-circle


  • C#

    public partial class Day13 : Solver
    {
      private record struct Button(int X, int Y);
      private record struct Machine(int X, int Y, Button A, Button B);
      private List<Machine> machines = [];
    
      [GeneratedRegex(@"^Button (A|B): X\+(\d+), Y\+(\d+)$")]
      private static partial Regex ButtonSpec();
    
      [GeneratedRegex(@"^Prize: X=(\d+), Y=(\d+)$")]
      private static partial Regex PrizeSpec();
    
      public void Presolve(string input) {
        var machine_specs = input.Trim().Split("\n\n").ToList();
        foreach (var spec in machine_specs) {
          var lines = spec.Split("\n").ToList();
          if (ButtonSpec().Match(lines[0]) is not { Success: true } button_a_match
            || ButtonSpec().Match(lines[1]) is not { Success: true } button_b_match
            || PrizeSpec().Match(lines[2]) is not { Success:true} prize_match) {
            throw new InvalidDataException($"parse error: ${lines}");
          }
          machines.Add(new Machine(
            int.Parse(prize_match.Groups[1].Value),
            int.Parse(prize_match.Groups[2].Value),
            new Button(int.Parse(button_a_match.Groups[2].Value), int.Parse(button_a_match.Groups[3].Value)),
            new Button(int.Parse(button_b_match.Groups[2].Value), int.Parse(button_b_match.Groups[3].Value))
            ));
        }
      }
    
      private string Solve(bool unit_conversion) {
        BigInteger total_cost = 0;
        foreach (var machine in machines) {
          long prize_x = machine.X + (unit_conversion ? 10000000000000 : 0);
          long prize_y = machine.Y + (unit_conversion ? 10000000000000 : 0);
          BigInteger det = machine.A.X * machine.B.Y - machine.B.X * machine.A.Y;
          if (det == 0) continue;
          BigInteger det_a = prize_x * machine.B.Y - machine.B.X * prize_y;
          BigInteger det_b = prize_y * machine.A.X - machine.A.Y * prize_x;
          var (a, a_rem) = BigInteger.DivRem(det_a, det);
          var (b, b_rem) = BigInteger.DivRem(det_b, det);
          if (a_rem != 0 || b_rem != 0) continue;
          total_cost += a * 3 + b;
        }
        return total_cost.ToString();
      }
    
      public string SolveFirst() => Solve(false);
      public string SolveSecond() => Solve(true);
    }
    




  • C#

    public class Day12 : Solver
    {
      private string[] data;
      private int width, height;
      private Dictionary<int, long> perimeters = [];
      private Dictionary<int, long> areas = [];
      private Dictionary<int, long> sides = [];
      private int region_count;
    
      public void Presolve(string input) {
        data = input.Trim().Split("\n").ToArray();
        height = data.Length;
        width = data[0].Length;
        var graph_cc = MakeGraph(false);
        var cc = new ConnectedComponentsAlgorithm<Point, PointEdge>(graph_cc);
        cc.Compute();
        var graph_all = MakeGraph(true);
        Dictionary<(int Component, int Y), List<int>> x_sides = [];
        Dictionary<(int Component, int X), List<int>> y_sides = [];
        var search = new UndirectedBreadthFirstSearchAlgorithm<Point, PointEdge>(graph_all);
        search.SetRootVertex((0, 0));
        search.FinishVertex += vertex => {
          if (IsWithinBounds(vertex.Item1, vertex.Item2)) {
            int component = cc.Components[vertex];
            areas.TryAdd(component, 0L);
            areas[component] += 1;
          }
        };
        search.ExamineEdge += edge => {
          var (si, ti) = (IsWithinBounds(edge.Source), IsWithinBounds(edge.Target));
          bool border = si != ti || cc.Components[edge.Source] != cc.Components[edge.Target];
          if (si && border) {
            int component = cc.Components[edge.Source];
            perimeters.TryAdd(component, 0L);
            perimeters[component] += 1;
            if (edge.Source.Item1 == edge.Target.Item1) {
              int y = Math.Min(edge.Source.Item2, edge.Target.Item2);
              x_sides.TryAdd((component, y), []);
              x_sides[(component, y)].Add(edge.Source.Item2 > edge.Target.Item2 ? edge.Source.Item1 : -edge.Source.Item1 - 5);
            } else {
              int x = Math.Min(edge.Source.Item1, edge.Target.Item1);
              y_sides.TryAdd((component, x), []);
              y_sides[(component, x)].Add(edge.Source.Item1 > edge.Target.Item1 ? edge.Source.Item2 : -edge.Source.Item2 - 5);
            }
          }
        };
        search.Compute();
        region_count = cc.ComponentCount;
        foreach (var side_projection in x_sides) {
          side_projection.Value.Sort();
          sides.TryAdd(side_projection.Key.Component, 0);
          int last_x = int.MinValue;
          foreach (var x in side_projection.Value) {
            if (x != (last_x + 1)) sides[side_projection.Key.Component] += 1;
            last_x = x;
          }
        }
        foreach (var side_projection in y_sides) {
          side_projection.Value.Sort();
          sides.TryAdd(side_projection.Key.Component, 0);
          int last_y = int.MinValue;
          foreach (var y in side_projection.Value) {
            if (y != (last_y + 1)) sides[side_projection.Key.Component] += 1;
            last_y = y;
          }
        }
        foreach (var component in Enumerable.Range(0, region_count)) {
          if (!areas.ContainsKey(component)) continue;
        }
      }
    
      public string SolveFirst() =>
        Enumerable.Range(0, region_count)
          .Where(component => areas.ContainsKey(component))
          .Select(component => areas[component] * perimeters[component]).Sum().ToString();
    
      public string SolveSecond() =>
        Enumerable.Range(0, region_count)
          .Where(component => areas.ContainsKey(component))
          .Select(component => areas[component] * sides[component]).Sum().ToString();
    
      private record struct PointEdge(Point Source, Point Target): IEdge<Point>;
    
      private IUndirectedGraph<Point, PointEdge> MakeGraph(bool with_edges_between_plots)=>
        new DelegateUndirectedGraph<Point, PointEdge>(GetVertices(), with_edges_between_plots? GetAllEdges : GetEdgesWithoutBorders, false);
    
      private bool IsWithinBounds(int x, int y) => x >= 0 && x < width && y >= 0 && y < height;
      private bool IsWithinBounds(Point p) => IsWithinBounds(p.Item1, p.Item2);
    
      private readonly (int, int)[] directions = [(-1, 0), (0, -1), (1, 0), (0, 1)];
    
      private bool GetEdgesWithoutBorders(Point arg, out IEnumerable<PointEdge> result) {
        List<PointEdge> result_list = [];
        var (x, y) = arg;
        bool inside = IsWithinBounds(x, y);
        foreach (var (dx, dy) in directions) {
          var (ox, oy) = (x + dx, y + dy);
          if (!inside || !IsWithinBounds(ox, oy)) continue;
          if (data[y][x] == data[oy][ox]) result_list.Add(new(arg, (ox, oy)));
        }
        result = result_list;
        return true;
      }
    
      private bool GetAllEdges(Point arg, out IEnumerable<PointEdge> result) {
        List<PointEdge> result_list = [];
        var (x, y) = arg;
        foreach (var (dx, dy) in directions) {
          var (ox, oy) = (x + dx, y + dy);
          if (ox >= -1 && ox <= width && oy >= -1 && oy <= height) result_list.Add(new(arg, (ox, oy)));
        }
        result = result_list;
        return true;
      }
    
      private IEnumerable<(int, int)> GetVertices() => Enumerable.Range(-1, width + 2).SelectMany(x => Enumerable.Range(-1, height + 2).Select(y => (x, y)));
    }
    

  • C#

    public class Day11 : Solver
    {
      private long[] data;
    
      private class TreeNode(TreeNode? left, TreeNode? right, long value) {
        public TreeNode? Left = left;
        public TreeNode? Right = right;
        public long Value = value;
      }
    
      private Dictionary<(long, int), long> generation_length_cache = [];
      private Dictionary<long, TreeNode> subtree_pointers = [];
    
      public void Presolve(string input) {
        data = input.Trim().Split(" ").Select(long.Parse).ToArray();
        List<TreeNode> roots = data.Select(value => new TreeNode(null, null, value)).ToList();
        List<TreeNode> last_level = roots;
        subtree_pointers = roots.GroupBy(root => root.Value)
          .ToDictionary(grouping => grouping.Key, grouping => grouping.First());
        for (int i = 0; i < 75; i++) {
          List<TreeNode> next_level = [];
          foreach (var node in last_level) {
            long[] children = Transform(node.Value).ToArray();
            node.Left = new TreeNode(null, null, children[0]);
            if (subtree_pointers.TryAdd(node.Left.Value, node.Left)) {
              next_level.Add(node.Left);
            }
            if (children.Length <= 1) continue;
            node.Right = new TreeNode(null, null, children[1]);
            if (subtree_pointers.TryAdd(node.Right.Value, node.Right)) {
              next_level.Add(node.Right);
            }
          }
          last_level = next_level;
        }
      }
    
      public string SolveFirst() => data.Select(value => GetGenerationLength(value, 25)).Sum().ToString();
      public string SolveSecond() => data.Select(value => GetGenerationLength(value, 75)).Sum().ToString();
    
      private long GetGenerationLength(long value, int generation) {
        if (generation == 0) { return 1; }
        if (generation_length_cache.TryGetValue((value, generation), out var result)) return result;
        TreeNode cur = subtree_pointers[value];
        long sum = GetGenerationLength(cur.Left.Value, generation - 1);
        if (cur.Right is not null) {
          sum += GetGenerationLength(cur.Right.Value, generation - 1);
        }
        generation_length_cache[(value, generation)] = sum;
        return sum;
      }
    
      private IEnumerable<long> Transform(long arg) {
        if (arg == 0) return [1];
        if (arg.ToString() is { Length: var l } str && (l % 2) == 0) {
          return [int.Parse(str[..(l / 2)]), int.Parse(str[(l / 2)..])];
        }
        return [arg * 2024];
      }
    }
    

  • C#

    using QuickGraph;
    using QuickGraph.Algorithms.Search;
    using Point = (int, int);
    
    public class Day10 : Solver
    {
      private int[][] data;
      private int width, height;
      private List<int> destinations_counts = [], paths_counts = [];
      private record PointEdge(Point Source, Point Target): IEdge<Point>;
    
      private DelegateVertexAndEdgeListGraph<Point, PointEdge> MakeGraph() => new(AllPoints(), GetNeighbours);
    
      private static readonly List<Point> directions = [(1, 0), (-1, 0), (0, 1), (0, -1)];
    
      private bool GetNeighbours(Point from, out IEnumerable<PointEdge> result) {
        List<PointEdge> neighbours = [];
        int next_value = data[from.Item2][from.Item1] + 1;
        foreach (var (dx, dy) in directions) {
          int x = from.Item1 + dx, y = from.Item2 + dy;
          if (x < 0 || y < 0 || x >= width || y >= height) continue;
          if (data[y][x] != next_value) continue;
          neighbours.Add(new(from, (x, y)));
        }
        result = neighbours;
        return true;
      }
    
      private IEnumerable<Point> AllPoints() => Enumerable.Range(0, width).SelectMany(x => Enumerable.Range(0, height).Select(y => (x, y)));
    
      public void Presolve(string input) {
        data = input.Trim().Split("\n").Select(s => s.Select(ch => ch - '0').ToArray()).ToArray();
        width = data[0].Length;
        height = data.Length;
        var graph = MakeGraph();
        for (int i = 0; i < width; i++) {
          for (int j = 0; j < height; j++) {
            if (data[j][i] != 0) continue;
            var search = new BreadthFirstSearchAlgorithm<Point, PointEdge>(graph);
            Point start = (i, j);
            Dictionary<Point, int> paths_into = [];
            paths_into[start] = 1;
            var destinations = 0;
            var paths = 0;
            search.ExamineEdge += edge => {
              paths_into.TryAdd(edge.Target, 0);
              paths_into[edge.Target] += paths_into[edge.Source];
            };
            search.FinishVertex += vertex => {
              if (data[vertex.Item2][vertex.Item1] == 9) {
                paths += paths_into[vertex];
                destinations += 1;
              }
            };
            search.SetRootVertex(start);
            search.Compute();
            destinations_counts.Add(destinations);
            paths_counts.Add(paths);
          }
        }
      }
    
      public string SolveFirst() => destinations_counts.Sum().ToString();
      public string SolveSecond() => paths_counts.Sum().ToString();
    }
    



  • C#

    public class Day09 : Solver
    {
      private string data;
    
      public void Presolve(string input) {
        data = input.Trim();
      }
    
      public string SolveFirst() {
        var arr = new List<int>();
        bool file = true;
        int file_id = 0;
        foreach (var ch in data) {
          if (file) {
            Enumerable.Range(0, ch - '0').ToList().ForEach(_ => arr.Add(file_id));
            file_id++;
          } else {
            Enumerable.Range(0, ch - '0').ToList().ForEach(_ => arr.Add(-1));
          }
          file = !file;
        }
        int from_ptr = arr.Count - 1;
        int to_ptr = 0;
        while (from_ptr > to_ptr) {
          if (arr[to_ptr] != -1) {
            to_ptr++;
            continue;
          }
          if (arr[from_ptr] == -1) {
            from_ptr--;
            continue;
          }
          arr[to_ptr] = arr[from_ptr];
          arr[from_ptr] = -1;
          to_ptr++;
          from_ptr--;
        }
        return Enumerable.Range(0, arr.Count)
          .Select(block_id => arr[block_id] > 0 ? ((long)arr[block_id]) * block_id : 0)
          .Sum().ToString();
      }
    
      public string SolveSecond() {
        var files = new List<(int Start, int Size, int Id)>();
        bool is_file = true;
        int file_id = 0;
        int block_id = 0;
        foreach (var ch in data) {
          if (is_file) {
            files.Add((block_id, ch - '0', file_id));
            file_id++;
          }
          is_file = !is_file;
          block_id += (ch - '0');
        }
        while (true) {
          bool moved = false;
          for (int from_ptr = files.Count - 1; from_ptr >= 1; from_ptr--) {
            var file = files[from_ptr];
            if (file.Id >= file_id) continue;
            file_id = file.Id;
            for (int to_ptr = 0; to_ptr < from_ptr; to_ptr++) {
              if (files[to_ptr + 1].Start - files[to_ptr].Start - files[to_ptr].Size >= file.Size) {
                files.RemoveAt(from_ptr);
                files.Insert(to_ptr + 1, file with { Start = files[to_ptr].Start + files[to_ptr].Size });
                moved = true;
                break;
              }
            }
            if (moved) break;
          }
          if (!moved) break;
        }
        return files.Select(file => ((long)file.Id) * file.Size * (2 * ((long)file.Start) + file.Size - 1) / 2)
          .Sum().ToString();
      }
    }
    



  • C#

    public class Day08 : Solver
    {
      private ImmutableArray<string> data;
      private int width, height;
    
      public void Presolve(string input) {
        data = input.Trim().Split("\n").ToImmutableArray();
        width = data[0].Length;
        height = data.Length;
      }
    
      public string SolveFirst() {
        Dictionary<char, List<(int, int)>> antennae = [];
        HashSet<(int, int)> antinodes = [];
        for (int i = 0; i < width; i++) {
          for (int j = 0; j < height; j++) {
            if ('.' == data[j][i]) continue;
            antennae.TryAdd(data[j][i], []);
            foreach (var (oi, oj) in antennae[data[j][i]]) {
              int di = i - oi;
              int dj = j - oj;
              int ai = i + di;
              int aj = j + dj;
              if (ai >= 0 && aj >= 0 && ai < width && aj < height) {
                antinodes.Add((ai, aj));
              }
              ai = oi - di;
              aj = oj - dj;
              if (ai >= 0 && aj >= 0 && ai < width && aj < height) {
                antinodes.Add((ai, aj));
              }
            }
            antennae[data[j][i]].Add((i, j));
          }
        }
        return antinodes.Count.ToString();
      }
    
      public string SolveSecond() {
        Dictionary<char, List<(int, int)>> antennae = [];
        HashSet<(int, int)> antinodes = [];
        for (int i = 0; i < width; i++) {
          for (int j = 0; j < height; j++) {
            if ('.' == data[j][i]) continue;
            antennae.TryAdd(data[j][i], []);
            foreach (var (oi, oj) in antennae[data[j][i]]) {
              int di = i - oi;
              int dj = j - oj;
              for (int ai = i, aj = j;
                   ai >= 0 && aj >= 0 && ai < width && aj < height; 
                   ai += di, aj +=dj) {
                antinodes.Add((ai, aj));
              }
              for (int ai = oi, aj = oj;
                   ai >= 0 && aj >= 0 && ai < width && aj < height; 
                   ai -= di, aj -=dj) {
                antinodes.Add((ai, aj));
              }
            }
            antennae[data[j][i]].Add((i, j));
          }
        }
        return antinodes.Count.ToString();
      }
    }
    

  • Check if turning right would lead back onto the recorded path in the same direction we walked it before

    You’re checking this by casting one “ray” in the direction you’re turning. This will not always detect a cycle, it will only detect it, if your traversed path has already crossed one of the points of that “ray”. For example in this case:

    .%..
    ...#
    #...
    .^#.
    

    where % is the place where we put an obstacle.

    Unrelated to the algorithm, I’ve noticed that you’re using type hints (which is great), but do you actually run a type checker? Because I don’t think tuple[int] is the correct type for values like (0, 1).





  • C#

    public class Day07 : Solver
    {
      private ImmutableList<(long, ImmutableList<long>)> equations;
    
      public void Presolve(string input) {
        equations = input.Trim().Split("\n")
          .Select(line => line.Split(": "))
          .Select(split => (long.Parse(split[0]), split[1].Split(" ").Select(long.Parse).ToImmutableList()))
          .ToImmutableList();
      }
    
      private bool TrySolveWithConcat(long lhs, long head, ImmutableList<long> tail) {
        var lhs_string = lhs.ToString();
        var head_string = head.ToString();
        return lhs_string.Length > head_string.Length &&
          lhs_string.EndsWith(head_string) &&
          SolveEquation(long.Parse(lhs_string.Substring(0, lhs_string.Length - head_string.Length)), tail, true);
      }
    
      private bool SolveEquation(long lhs, ImmutableList<long> rhs, bool with_concat = false) {
        if (rhs.Count == 1) return lhs == rhs[0];
        long head = rhs[rhs.Count - 1];
        var tail = rhs.GetRange(0, rhs.Count - 1);
        return (SolveEquation(lhs - head, tail, with_concat))
          || (lhs % head == 0) && SolveEquation(lhs / head, tail, with_concat)
          || with_concat && TrySolveWithConcat(lhs, head, tail);
      }
    
      public string SolveFirst() => equations
        .Where(eq => SolveEquation(eq.Item1, eq.Item2))
        .Select(eq => eq.Item1)
        .Sum().ToString();
      public string SolveSecond() => equations
        .Where(eq => SolveEquation(eq.Item1, eq.Item2, true))
        .Select(eq => eq.Item1)
        .Sum().ToString();
    }