Cut day 14 runtime in half

This is mostly from reusing the same object instead of creating a new one each time we test a new point, but there's also a minor amount of speedup from using HashSet instead of Dictionary. I originally thought I would need to care about why each spot was blocked (wall vs sand) but it turns out that's not important for this problem.
This commit is contained in:
2022-12-14 21:49:18 -06:00
parent b1d57f4fd0
commit c710a86f45

132
src/14.cs
View File

@ -2,38 +2,72 @@
internal class Day14 : Day internal class Day14 : Day
{ {
private record point(int x, int y); class point : IEquatable<point>
// Parses a string in the form "x,y" into a point
private static point ParsePoint(string str)
{ {
var parts = str.Trim().Split(','); public bool Equals(point? other)
if (parts.Length != 2)
{ {
throw new Exception($"found {parts.Length} pieces of input string, expected 2"); return x == other?.x && y == other.y;
} }
var x = int.Parse(parts[0].Trim()); public override bool Equals(object? obj)
var y = int.Parse(parts[1].Trim()); {
return new point(x, y); return Equals((point?) obj);
}
public override int GetHashCode()
{
return HashCode.Combine(x, y);
}
public static bool operator ==(point? left, point? right)
{
return Equals(left, right);
}
public static bool operator !=(point? left, point? right)
{
return !Equals(left, right);
}
public point(int _x, int _y)
{
x = _x;
y = _y;
}
public override string ToString()
{
return $"{x},{y}";
}
public int x;
public int y;
// Parses a string in the form "x,y" into a point
public static point Parse(string str)
{
var parts = str.Trim().Split(',');
if (parts.Length != 2)
{
throw new Exception($"found {parts.Length} pieces of input string, expected 2");
}
var x = int.Parse(parts[0].Trim());
var y = int.Parse(parts[1].Trim());
return new point(x, y);
}
} }
enum cellType private readonly HashSet<point> grid = new();
{
sand,
wall,
}
private readonly Dictionary<point, cellType> grid = new();
internal override void Parse() internal override void Parse()
{ {
foreach (var line in Util.Parsing.ReadAllLines("14")) foreach (var line in Util.Parsing.ReadAllLines("14"))
{ {
point? lastPoint = null; point? lastPoint = null;
var parts = line.Split(" -> "); var parts = line.Split(" -> ").Select(point.Parse);
foreach (var part in parts) foreach (var p in parts)
{ {
var p = ParsePoint(part);
if (lastPoint == null) if (lastPoint == null)
{ {
lastPoint = p; lastPoint = p;
@ -44,17 +78,16 @@ internal class Day14 : Day
{ {
for (int i = lastPoint.x; i != p.x; i += Math.Sign(p.x - lastPoint.x)) for (int i = lastPoint.x; i != p.x; i += Math.Sign(p.x - lastPoint.x))
{ {
grid[p with {x = i}] = cellType.wall; grid.Add(new point(i, p.y));
} }
} }
else if (p.y != lastPoint.y) else if (p.y != lastPoint.y)
{ {
for (int i = lastPoint.y; i != p.y; i += Math.Sign(p.y - lastPoint.y)) for (int i = lastPoint.y; i != p.y; i += Math.Sign(p.y - lastPoint.y))
{ {
grid[p with {y = i}] = cellType.wall; grid.Add(new point(p.x, i));
} }
} }
grid[p] = cellType.wall;
lastPoint = p; lastPoint = p;
} }
} }
@ -62,26 +95,30 @@ internal class Day14 : Day
internal override string Part1() internal override string Part1()
{ {
var g = new Dictionary<point, cellType>(grid); var g = new HashSet<point>(grid);
int lowestY = g.MaxBy(pair => pair.Key.y).Key.y; int lowestY = g.MaxBy(p => p.y)!.y;
point dropPoint = new point(500, 0); point dropPoint = new point(500, 0);
bool hitVoid = false; bool hitVoid = false;
int numDroppedSand = 0; int numDroppedSand = 0;
point sandLoc = new point(dropPoint.x, dropPoint.y);
point nextPoint = new point(sandLoc.x, sandLoc.y + 1);
while (!hitVoid) while (!hitVoid)
{ {
bool atRest = false; bool atRest = false;
point sandLoc = dropPoint with {y = dropPoint.y + 1}; sandLoc.x = dropPoint.x;
sandLoc.y = dropPoint.y;
while (!atRest) while (!atRest)
{ {
var nextPoint = sandLoc with {y = sandLoc.y + 1}; nextPoint.x = sandLoc.x;
if (g.ContainsKey(nextPoint)) nextPoint.y = sandLoc.y + 1;
if (g.Contains(nextPoint))
{ {
nextPoint = nextPoint with {x = sandLoc.x - 1}; nextPoint.x = sandLoc.x - 1;
if (g.ContainsKey(nextPoint)) if (g.Contains(nextPoint))
{ {
nextPoint = nextPoint with {x = sandLoc.x + 1}; nextPoint.x = sandLoc.x + 1;
if (g.ContainsKey(nextPoint)) if (g.Contains(nextPoint))
{ {
atRest = true; atRest = true;
} }
@ -90,7 +127,8 @@ internal class Day14 : Day
if (!atRest) if (!atRest)
{ {
sandLoc = nextPoint; sandLoc.x = nextPoint.x;
sandLoc.y = nextPoint.y;
} }
if (nextPoint.y > lowestY) if (nextPoint.y > lowestY)
@ -103,7 +141,7 @@ internal class Day14 : Day
if (!hitVoid) if (!hitVoid)
{ {
numDroppedSand++; numDroppedSand++;
g[sandLoc] = cellType.sand; g.Add(new point(sandLoc.x, sandLoc.y));
} }
} }
@ -112,31 +150,36 @@ internal class Day14 : Day
internal override string Part2() internal override string Part2()
{ {
var g = new Dictionary<point, cellType>(grid); var g = new HashSet<point>(grid);
int lowestY = g.MaxBy(pair => pair.Key.y).Key.y; int lowestY = g.MaxBy(p => p.y)!.y;
int floor = 2 + lowestY; int floor = 2 + lowestY;
point dropPoint = new point(500, 0); point dropPoint = new point(500, 0);
int numDroppedSand = 0; int numDroppedSand = 0;
point sandLoc = new point(dropPoint.x, dropPoint.y);
point nextPoint = new point(sandLoc.x, sandLoc.y + 1);
while (true) while (true)
{ {
bool atRest = false; bool atRest = false;
point sandLoc = dropPoint; sandLoc.x = dropPoint.x;
sandLoc.y = dropPoint.y;
while (!atRest) while (!atRest)
{ {
var nextPoint = sandLoc with {y = sandLoc.y + 1}; nextPoint.x = sandLoc.x;
nextPoint.y = sandLoc.y + 1;
if (nextPoint.y == floor) if (nextPoint.y == floor)
{ {
break; break;
} }
if (g.ContainsKey(nextPoint)) if (g.Contains(nextPoint))
{ {
nextPoint = nextPoint with {x = sandLoc.x - 1}; nextPoint.x = sandLoc.x - 1;
if (g.ContainsKey(nextPoint)) if (g.Contains(nextPoint))
{ {
nextPoint = nextPoint with {x = sandLoc.x + 1}; nextPoint.x = sandLoc.x + 1;
if (g.ContainsKey(nextPoint)) if (g.Contains(nextPoint))
{ {
atRest = true; atRest = true;
} }
@ -145,11 +188,12 @@ internal class Day14 : Day
if (!atRest) if (!atRest)
{ {
sandLoc = nextPoint; sandLoc.x = nextPoint.x;
sandLoc.y = nextPoint.y;
} }
} }
g[sandLoc] = cellType.sand; g.Add(new point(sandLoc.x, sandLoc.y));
numDroppedSand++; numDroppedSand++;
if (sandLoc == dropPoint) if (sandLoc == dropPoint)