Files
2021/src/05.cs
Parnic 7d93691276 Day 5 solution
There is a lot to be disliked here, starting with the Line Length calculation, which I'm sure I'm just totally overthinking or otherwise being dumb about, to the way I'm picking the next point while moving along the line.

Also, TIL how much of a difference a good GetHashCode() override can make when comparing/equating custom struct instances. Not implementing GetHashCode() on this solution resulted in ~250ms runtime on my PC. Implementing a "bad" GetHashCode() (which was `return x.GetHashCode() | y.GetHashCode();` which seemed fine to me ¯\_(ツ)_/¯) resulted in >1s runtime on my PC. Using this implementation (HashCode.Combine()) results in ~25ms runtime on my PC. Those numbers are with nothing changing other than the implementation of GetHashCode() on struct Point.

I also disabled nullability in my quest to improve performance here. The compiler was complaining that my Equals() override wasn't equivalent because of nullability, so it had to be `object? obj` which I didn't care for since this was a value type and not a reference type.
2021-12-05 11:19:13 -06:00

131 lines
4.0 KiB
C#

using System.Diagnostics;
using System.Text.RegularExpressions;
namespace aoc2021
{
internal class Day05
{
private static readonly Regex lineRegex = new(@"(?<x1>\d+),(?<y1>\d+) -> (?<x2>\d+),(?<y2>\d+)", RegexOptions.Compiled);
[DebuggerDisplay("{x},{y}")]
struct Point : IEquatable<Point>
{
public int X { get; init; }
public int Y { get; init; }
public bool Equals(Point other) => X == other.X && Y == other.Y;
public override bool Equals(object obj) => obj is Point point && Equals(point);
public override int GetHashCode() => HashCode.Combine(X, Y);
}
[DebuggerDisplay("{start} -> {end}")]
struct Line
{
public Point Start { get; init; }
public Point End { get; init; }
public int Length
{
get
{
// this is bad and i feel bad
if (Start.X == End.X || Start.Y == End.Y)
{
return Math.Abs((End.X - Start.X) + (End.Y - Start.Y));
}
return Math.Abs(Start.X - End.X);
}
}
}
internal static void Go()
{
Logger.Log("Day 5");
Logger.Log("-----");
var lines = File.ReadAllLines("inputs/05.txt");
List<Line> segments = new();
foreach (var line in lines)
{
var match = lineRegex.Match(line);
Line segment = new()
{
Start = new Point()
{
X = Convert.ToInt32(match.Groups["x1"].Value),
Y = Convert.ToInt32(match.Groups["y1"].Value),
},
End = new Point()
{
X = Convert.ToInt32(match.Groups["x2"].Value),
Y = Convert.ToInt32(match.Groups["y2"].Value)
},
};
segments.Add(segment);
}
Part1(segments);
Part2(segments);
Logger.Log("");
}
private static void Part1(IEnumerable<Line> lines)
{
using var t = new Timer();
int numPointsGreater = Solve(lines, (line) => !(line.Start.X == line.End.X || line.Start.Y == line.End.Y));
Logger.Log($"part1: {numPointsGreater}");
}
private static void Part2(IEnumerable<Line> lines)
{
using var t = new Timer();
int numPointsGreater = Solve(lines, (line) => false);
Logger.Log($"part2: {numPointsGreater}");
}
private static int Solve(IEnumerable<Line> lines, Func<Line, bool> filter)
{
Dictionary<Point, int> coveredPoints = new();
int numPointsGreater = 0;
foreach (var line in lines)
{
if (filter(line))
{
continue;
}
for (int i = 0; i <= line.Length; i++)
{
int x = line.Start.X;
int y = line.Start.Y;
if (line.Start.X != line.End.X)
{
x += (line.Start.X > line.End.X ? -1 : 1) * i;
}
if (line.Start.Y != line.End.Y)
{
y += (line.Start.Y > line.End.Y ? -1 : 1) * i;
}
Point point = new() { X = x, Y = y };
if (!coveredPoints.TryGetValue(point, out int curr))
{
coveredPoints.Add(point, curr);
}
if (curr == 1)
{
numPointsGreater++;
}
coveredPoints[point] = curr + 1;
}
}
return numPointsGreater;
}
}
}