Skip to content

Commit

Permalink
day(22): Sand Slabs 🕯️
Browse files Browse the repository at this point in the history
  • Loading branch information
adamrodger committed Dec 22, 2023
1 parent a83c1ad commit 9c76932
Show file tree
Hide file tree
Showing 3 changed files with 286 additions and 0 deletions.
205 changes: 205 additions & 0 deletions src/AdventOfCode/Day22.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AdventOfCode.Utilities;

namespace AdventOfCode
{
/// <summary>
/// Solver for Day 22
/// </summary>
public class Day22
{
public int Part1(string[] input)
{
var cubes = input.Select((line, i) => SandCube.Parse(i, line)).ToArray();
var tower = SandCubeTower.Build(cubes);

int total = 0;

foreach (SandCube cube in cubes)
{
if (!tower.Supporting.TryGetValue(cube.Id, out ISet<int> holdingUp))
{
// cube doesn't support anything, safe to remove
total++;
continue;
}

if (holdingUp.All(h => tower.SupportedBy[h].Count > 1))
{
// we're supporting things, but everything we're supporting is itself supported by at least one other thing
total++;
}
}

return total;
}

public int Part2(string[] input)
{
var cubes = input.Select((line, i) => SandCube.Parse(i, line)).ToArray();
var tower = SandCubeTower.Build(cubes);

int total = 0;

foreach (SandCube cube in cubes)
{
Queue<int> queue = new();
HashSet<int> fell = new() { cube.Id };

queue.Enqueue(cube.Id);

while (queue.Count > 0)
{
int id = queue.Dequeue();

if (!tower.Supporting.TryGetValue(id, out ISet<int> holdingUp))
{
// not holding anything up, so nothing to fall
continue;
}

foreach (int heldUp in holdingUp)
{
if (tower.SupportedBy[heldUp].All(fell.Contains))
{
// everything supporting this one fell down
queue.Enqueue(heldUp);
fell.Add(heldUp);
}
}
}

total += fell.Count - 1; // don't count the block that we removed
}

return total;
}

/// <summary>
/// Tower of sand cubes
/// </summary>
/// <param name="Supporting">Lookup of each sand cube to which other sand cubes it is supporting</param>
/// <param name="SupportedBy">Lookup of each sand cube to which other sand cubes it is supported by</param>
private record SandCubeTower(IDictionary<int, ISet<int>> Supporting, IDictionary<int, ISet<int>> SupportedBy)
{
/// <summary>
/// Building the tower from the given starting cube positions representing falling cubes
/// </summary>
/// <param name="falling">Falling cubes</param>
/// <returns>Sand cube tower after all cubes have fallen and settled</returns>
public static SandCubeTower Build(IEnumerable<SandCube> falling)
{
Dictionary<int, ISet<int>> supporting = new();
Dictionary<int, ISet<int>> supportedBy = new();
Dictionary<Point3D, int> occupiedSpace = new();

foreach (SandCube cube in falling.OrderBy(c => c.BottomLeftFront.Z))
{
SandCube current = cube;
SandCube dropped = cube.Drop();

// drop until we hit either the ground or a point occupied by another cube
while (dropped.BottomLayer().All(p => p.Z > 0 && !occupiedSpace.ContainsKey(p)))
{
current = dropped;
dropped = current.Drop();
}

// mark the cubes below as supporting this one
foreach (Point3D point in dropped.BottomLayer().Where(occupiedSpace.ContainsKey))
{
int supportingId = occupiedSpace[point];
supporting.GetOrCreate(supportingId, () => new HashSet<int>()).Add(current.Id);
supportedBy.GetOrCreate(current.Id, () => new HashSet<int>()).Add(supportingId);
}

// settle this cube in place
foreach (Point3D point in current.Points())
{
occupiedSpace[point] = current.Id;
}
}

return new SandCubeTower(supporting, supportedBy);
}
}

/// <summary>
/// A cube of sand
/// </summary>
/// <param name="Id">Cube ID</param>
/// <param name="BottomLeftFront">The bottom front left corner</param>
/// <param name="TopRightBack">The top right back corner</param>
private record SandCube(int Id, Point3D BottomLeftFront, Point3D TopRightBack)
{
/// <summary>
/// Parse a sand cube from input
/// </summary>
/// <param name="id">Cube ID</param>
/// <param name="input">Input line</param>
/// <returns>Sand cube</returns>
public static SandCube Parse(int id, string input)
{
int[] numbers = input.Numbers<int>();

Point3D bottomLeftFront = (Math.Min(numbers[0], numbers[3]),
Math.Min(numbers[1], numbers[4]),
Math.Min(numbers[2], numbers[5]));

Point3D topRightBack = (Math.Max(numbers[0], numbers[3]),
Math.Max(numbers[1], numbers[4]),
Math.Max(numbers[2], numbers[5]));

return new SandCube(id, bottomLeftFront, topRightBack);
}

/// <summary>
/// Drop the current cube by one place
/// </summary>
/// <returns>Same cube but space one lower down</returns>
public SandCube Drop()
{
return this with
{
BottomLeftFront = this.BottomLeftFront - (0, 0, 1),
TopRightBack = this.TopRightBack - (0, 0, 1)
};
}

/// <summary>
/// Enumerate all the points of space taken up by this cube
/// </summary>
/// <returns>Cube points</returns>
public IEnumerable<Point3D> Points()
{
for (int x = this.BottomLeftFront.X; x <= this.TopRightBack.X; x++)
{
for (int y = this.BottomLeftFront.Y; y <= this.TopRightBack.Y; y++)
{
for (int z = this.BottomLeftFront.Z; z <= this.TopRightBack.Z; z++)
{
yield return (x, y, z);
}
}
}
}

/// <summary>
/// Enumerate all the points of space taken up by the bottom layer of this cube
/// </summary>
/// <returns>Bottom layer</returns>
public IEnumerable<Point3D> BottomLayer()
{
for (int x = this.BottomLeftFront.X; x <= this.TopRightBack.X; x++)
{
for (int y = this.BottomLeftFront.Y; y <= this.TopRightBack.Y; y++)
{
yield return (x, y, this.BottomLeftFront.Z);
}
}
}
}
}
}
Binary file added src/AdventOfCode/inputs/day22.txt
Binary file not shown.
81 changes: 81 additions & 0 deletions tests/AdventOfCode.Tests/Day22Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.IO;
using Xunit;
using Xunit.Abstractions;


namespace AdventOfCode.Tests
{
public class Day22Tests
{
private readonly ITestOutputHelper output;
private readonly Day22 solver;

public Day22Tests(ITestOutputHelper output)
{
this.output = output;
this.solver = new Day22();
}

private static string[] GetRealInput()
{
string[] input = File.ReadAllLines("inputs/day22.txt");
return input;
}

private static string[] GetSampleInput()
{
return new string[]
{
"1,0,1~1,2,1",
"0,0,2~2,0,2",
"0,2,3~2,2,3",
"0,0,4~0,2,4",
"2,0,5~2,2,5",
"0,1,6~2,1,6",
"1,1,8~1,1,9",
};
}

[Fact]
public void Part1_SampleInput_ProducesCorrectResponse()
{
var expected = 5;

var result = solver.Part1(GetSampleInput());

Assert.Equal(expected, result);
}

[Fact]
public void Part1_RealInput_ProducesCorrectResponse()
{
var expected = 395;

var result = solver.Part1(GetRealInput());
output.WriteLine($"Day 22 - Part 1 - {result}");

Assert.Equal(expected, result);
}

[Fact]
public void Part2_SampleInput_ProducesCorrectResponse()
{
var expected = 7;

var result = solver.Part2(GetSampleInput());

Assert.Equal(expected, result);
}

[Fact]
public void Part2_RealInput_ProducesCorrectResponse()
{
var expected = 64714;

var result = solver.Part2(GetRealInput());
output.WriteLine($"Day 22 - Part 2 - {result}");

Assert.Equal(expected, result);
}
}
}

0 comments on commit 9c76932

Please sign in to comment.