Day 12: Garden Groups

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

  • SteveDinn@lemmy.ca
    link
    fedilink
    arrow-up
    2
    ·
    16 days ago

    C#

    There is probably a more efficient way of finding the sides, but this way was what came to me.

    using System.Diagnostics;
    using Common;
    
    namespace Day12;
    
    static class Program
    {
        static void Main()
        {
            var start = Stopwatch.GetTimestamp();
    
            var sampleInput = Input.ParseInput("sample.txt");
            var programInput = Input.ParseInput("input.txt");
    
            var (samplePart1, samplePart2) = Solve(sampleInput);
            Console.WriteLine($"Part 1 sample: {samplePart1}");
            Console.WriteLine($"Part 1 input: {samplePart2}");
    
            var (inputPart1, inputPart2) = Solve(programInput);
            Console.WriteLine($"Part 2 sample: {inputPart1}");
            Console.WriteLine($"Part 2 input: {inputPart2}");
    
            Console.WriteLine($"That took about {Stopwatch.GetElapsedTime(start)}");
        }
    
        static (int part1, int part2) Solve(Input i)
        {
            var retail = 0;
            var bulk = 0;
            var allPlotPoints = new Dictionary<char, HashSet<Point>>();
            foreach (var p in Grid.EnumerateAllPoints(i.Bounds))
            {
                var plant = i.ElementAt(p);
    
                if (!allPlotPoints.TryGetValue(plant, out var previousPlotPoints))
                {
                    previousPlotPoints = new();
                    allPlotPoints[plant] = previousPlotPoints;
                }
                else if (previousPlotPoints.Contains(p)) continue;
    
                var plotPoints = new HashSet<Point>();
                var perimeter = SearchPlot(i, plotPoints, plant, p);
                var area = plotPoints.Count;
                var sides = CountSides(plotPoints);
                retail += area * perimeter;
                bulk += area * sides;
    
                previousPlotPoints.AddRange(plotPoints);
            }
    
            return (retail, bulk);
        }
    
        static int CountSides(HashSet<Point> plot)
        {
            var sides = 0;
    
            // Track the points we've visited searching for sides
            HashSet<Point> visitedDownRight = new(),
                visitedDownLeft = new(),
                visitedRightDown = new(),
                visitedRightUp = new();
    
            // Sort the points in the plot from upper-left to lower-right, so we can
            // go through them in reading order
            foreach (var p in plot.OrderBy(p => (p.Row * 10000) + p.Col))
            {
                // Move right while looking up
                sides += GetSideLength(p, plot, visitedRightUp, Direction.Right, Direction.Up) > 0 ? 1 : 0;
                
                // Move right while looking down
                sides += GetSideLength(p, plot, visitedRightDown, Direction.Right, Direction.Down) > 0 ? 1 : 0;
                
                // Move down while looking right
                sides += GetSideLength(p, plot, visitedDownRight, Direction.Down, Direction.Right) > 0 ? 1 : 0;
                
                // Move down while looking left
                sides += GetSideLength(p, plot, visitedDownLeft, Direction.Down, Direction.Left) > 0 ? 1 : 0;
            }
    
            return sides;
        }
    
        static int GetSideLength(Point p, HashSet<Point> plotPoints, HashSet<Point> visited, Direction move, Direction look)
        {
            if (!plotPoints.Contains(p)) return 0;
            if (!visited.Add(p)) return 0;
            if (plotPoints.Contains(p.Move(look))) return 0;
            return 1 + GetSideLength(p.Move(move), plotPoints, visited, move, look);
        }
    
        static int SearchPlot(Input i, HashSet<Point> plotPoints, char plant, Point p)
        {
            if (!plotPoints.Add(p)) return 0;
            return p
                .GetCardinalMoves()
                .Select(move =>
                {
                    if (!i.IsInBounds(move) || (i.ElementAt(move) != plant)) return 1;
                    return SearchPlot(i, plotPoints, plant, move);
                })
                .Sum();
        }
    }
    
    public class Input
    {
        public required string[] Map { get; init; }
        
        public Point Bounds => new Point(this.Map.Length, this.Map[0].Length);
        public char ElementAt(Point p) => this.Map[p.Row][p.Col];
        public bool IsInBounds(Point p) => p.IsInBounds(this.Map.Length, this.Map[0].Length);
        
        public static Input ParseInput(string file) => new Input()
        {
            Map = File.ReadAllLines(file),
        };
    }
    
    • hades@lemm.ee
      link
      fedilink
      arrow-up
      1
      ·
      16 days ago

      What is the Point type? I’m surprised that you can’t just lexicographically sort instead of plot.OrderBy(p => (p.Row * 10000) + p.Col).

      • SteveDinn@lemmy.ca
        link
        fedilink
        arrow-up
        1
        ·
        15 days ago

        It’s a simple record type I use for (x,y) coordinate problems:

        record struct Point(int X, int Y);

        It’s defined in a separate project containing things I use in multiple problems.

        Maybe I could have done it that way, but this was the first thing I thought of, and it worked :)