Skip to content

Commit

Permalink
Day 22
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkAD88 committed Dec 24, 2023
1 parent 85f1879 commit 75dacea
Show file tree
Hide file tree
Showing 3 changed files with 314 additions and 47 deletions.
177 changes: 130 additions & 47 deletions 2023/22/Program.cs
Original file line number Diff line number Diff line change
@@ -1,81 +1,164 @@
// Day 22

const string SLAB_NAMES = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

List<Slab> slabs = [ ];
Dictionary<(int, int), int> floor = [ ];

using var input = Console.IsInputRedirected ? Console.OpenStandardInput() : File.OpenRead("sample.txt");
using var input = Console.IsInputRedirected ? Console.OpenStandardInput() : File.OpenRead("input.txt");
using var reader = new StreamReader(input);
Console.WriteLine("PARSING:");
while (!reader.EndOfStream)
{
var line = reader.ReadLine()!;
Console.WriteLine($"\t{line}");
var coords = line.Split([ '~', ',' ]).Select(int.Parse).ToArray();
var cubes = (
from x in Enumerable.Range(coords[0], coords[3] - coords[0] + 1)
from y in Enumerable.Range(coords[1], coords[4] - coords[1] + 1)
from z in Enumerable.Range(coords[2], coords[5] - coords[2] + 1)
select new Cube(x, y, z))
.ToList();
slabs.Add(new Slab(cubes));

// Establish map Z floors
foreach (var cube in cubes)
{
floor[cube.XY] = Math.Min(cube.Z, floor.GetValueOrDefault(cube.XY, cube.Z));
}

Console.WriteLine(slabs.Last());
slabs.Add(new Slab(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], GetSlabName()));
}

/*
Console.WriteLine("BEFORE:");
slabs.ForEach(Console.WriteLine);
Console.WriteLine();
*/

// Drop slabs
foreach (var slab in slabs.OrderBy(slab => slab.Cubes.Min(cube => cube.Z)))
Dictionary<(int, int), int> floor = [ ];
foreach (var slab in slabs.OrderBy(slab => slab.Zs.Min()))
{
if (slab.MinZ == 1)
{
// already at minimum level
continue;
}
while (slab.TryDrop(slabs)) { }
}

/*
Console.WriteLine("AFTER:");
slabs.ForEach(Console.WriteLine);
Console.WriteLine();
*/

var z = slab.MinZ;
while (z > 1)
// Can be safely disintegrated
int disintegrated = 0;
foreach (var slab in slabs)
{
var canBeDisintegrated = slabs
.Except([ slab ])
.Where(eval => eval.SupportedBy.Count == 1)
.All(eval => !eval.SupportedBy.Contains(slab));

if (canBeDisintegrated)
disintegrated += 1;
}

Console.WriteLine($"Part 1 : {disintegrated}");

// Count falling bricks after disintegration
int disintegratedCount = 0;
foreach (var slab in slabs)
{
var queue = new Queue<Slab>([ slab ]);

// Keep track of which bricks have been disintegrated
var disintegratedSlabs = new HashSet<Slab>();
while (queue.TryDequeue(out var nextSlab))
{
z -= 1;
if (slab.Cubes.Any(cube => floor[cube.XY] == z))
// Disintegrate the next slab
disintegratedSlabs.Add(nextSlab);

// If the next slab supports any other slabs
// and the supported slabs are ONLY supported
// by slabs that have already been disintegrated
// then index our counter and add the supported
// slabs in to the queue for processing.
foreach (var supported in nextSlab.SupporterOf.Where(supported => supported.SupportedBy.All(disintegratedSlabs.Contains)))
{
break;
disintegratedCount += 1;
queue.Enqueue(supported);
}
}
}

foreach (var cube in slab.Cubes)
Console.WriteLine($"Part 2 : {disintegratedCount}");

// 3054 too low
// 70345 too high
// 68254 too high

string GetSlabName()
{
var result = "";
var index = slabs.Count;
while (index >= 0)
{
result = SLAB_NAMES[index % SLAB_NAMES.Length] + result;
if (index < SLAB_NAMES.Length)
{
cube.Z -= 1;
floor[cube.XY] = Math.Min(cube.Z, floor.GetValueOrDefault(cube.XY, cube.Z));
break;
}

index /= SLAB_NAMES.Length;
index -= 1;
}
}

Console.WriteLine("AFTER:");
slabs.ForEach(Console.WriteLine);
Console.WriteLine();
return result;
}

internal class Slab(IEnumerable<Cube> cubes)
internal class Slab
{
public IEnumerable<Cube> Cubes => cubes;
public Slab(int x1, int y1, int z1, int x2, int y2, int z2, string name = null)
{
Xs = Enumerable.Range(x1, x2 - x1 + 1).ToArray();
Ys = Enumerable.Range(y1, y2 - y1 + 1).ToArray();
Zs = Enumerable.Range(z1, z2 - z1 + 1).ToArray();
Name = name;
}

public int MinZ => Cubes.Min(cube => cube.Z);
public string? Name { get; }

public HashSet<Slab> SupportedBy = [ ];
public HashSet<Slab> SupporterOf = [ ];

public override string ToString() => string.Join(" ~ ", Cubes);
}
public int[] Xs { get; }

public int[] Ys { get; }

public int[] Zs { get; internal set; }

internal class Cube(int x, int y, int z)
{
public (int, int) XY => (X, Y);
public int X => x;
public int Y => y;
public int Z { get; set; } = z;
public override string ToString() => $"{X}, {Y}, {Z}";
public bool Occupies(IEnumerable<int> x, IEnumerable<int> y, IEnumerable<int> z) =>
Xs.Intersect(x).Any()
&& Ys.Intersect(y).Any()
&& Zs.Intersect(z).Any();

public bool TryDrop(IEnumerable<Slab> slabs)
{
var testZ = Zs.Min() - 1;
if (testZ < 1)
{
return false;
}

// Find slabs that I will be resting on
var blockingSlabs = slabs
.Except([ this ])
.Where(slab => slab.Occupies(Xs, Ys, [ testZ ]))
.ToArray();
if (blockingSlabs.Length != 0)
{
foreach (var slab in blockingSlabs)
{
SupportedBy.Add(slab);
slab.SupporterOf.Add(this);
}

return false;
}

Zs = Zs.Select(z => z - 1).ToArray();
return true;
}

public override string ToString()
{
return (Name != null ? $"({Name}) " : "") +
$"{Xs.Min()}, {Ys.Min()}, {Zs.Min()} ~ {Xs.Max()}, {Ys.Max()}, {Zs.Max()}" +
$" : BY ({string.Join(", ", SupportedBy.Select(slab => slab.Name))})" +
$" : SUPPORTING {SupporterOf.Count}";
}
}
181 changes: 181 additions & 0 deletions 2023/22/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
```text
--------Part 1--------- --------Part 2---------
Day Time Rank Score Time Rank Score
22 >24h 12454 0 >24h 11664 0
```

## Part One
This was relatively straight forward but I tripped myself up by overcomplicating the structure that I parsed the inputs into. My original approach was to create a `Slab` object and then have it contain an array of `Cube` objects each of which represented a coordinate in space.

The object structure made collision detection a royal PITA. Afer spending a few hours fighting it I realized that I was trying to force my model to work when it was simpler just to throw away the `Cube` model entirely and simply store the range of `X, Y, Z` coordinates in an array. Once I did that I was able to delete about 100 lins of code and actually get the right answer.

## Part Two
I'm my own worste enemy here again. I spent forever trying to use recursion to get the job done. Once I decided to not use recursion - I do really love recursion so it made me sad - I started playing with queues.

Queues got me closer but still did not get the right answer. All of my answers were still too high.

The problem I had was that I was over counting the disintegrations. That took a ton of log messages to figure out but once I did it was clear that I could use a HashSet to track what had or had not been processed and finally the code generated the proper answer.

-----


--- Day 22: Sand Slabs ---

Enough sand has fallen; it can finally filter water for Snow Island.

Well, **almost**.

The sand has been falling as large compacted **bricks** of sand, piling up to form an impressive stack here near the edge of Island Island. In order to make use of the sand to filter water, some of the bricks will need to be broken apart - nay, **disintegrated** - back into freely flowing sand.

The stack is tall enough that you'll have to be careful about choosing which bricks to disintegrate; if you disintegrate the wrong brick, large portions of the stack could topple, which sounds pretty dangerous.

The Elves responsible for water filtering operations took a **snapshot of the bricks while they were still falling** (your puzzle input) which should let you work out which bricks are safe to disintegrate. For example:

```text
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
```

Each line of text in the snapshot represents the position of a single brick at the time the snapshot was taken. The position is given as two `x,y,z` coordinates - one for each end of the brick - separated by a tilde (`~`). Each brick is made up of a single straight line of cubes, and the Elves were even careful to choose a time for the snapshot that had all of the free-falling bricks at **integer positions above the ground**, so the whole snapshot is aligned to a three-dimensional cube grid.

A line like `2,2,2~2,2,2` means that both ends of the brick are at the same coordinate - in other words, that the brick is a single cube.

Lines like `0,0,10~1,0,10` or `0,0,10~0,1,10` both represent bricks that are **two cubes** in volume, both oriented horizontally. The first brick extends in the `x` direction, while the second brick extends in the `y` direction.

A line like `0,0,1~0,0,10` represents a **ten-cube brick** which is oriented **vertically**. One end of the brick is the cube located at `0,0,1`, while the other end of the brick is located directly above it at `0,0,10`.

The ground is at `z=0` and is perfectly flat; the lowest `z` value a brick can have is therefore `1`. So, `5,5,1~5,6,1` and `0,2,1~0,2,5` are both resting on the ground, but `3,3,2~3,3,3` was above the ground at the time of the snapshot.

Because the snapshot was taken while the bricks were still falling, some bricks will **still be in the air**; you'll need to start by figuring out where they will end up. Bricks are magically stabilized, so they **never rotate**, even in weird situations like where a long horizontal brick is only supported on one end. Two bricks cannot occupy the same position, so a falling brick will come to rest upon the first other brick it encounters.

Here is the same example again, this time with each brick given a letter so it can be marked in diagrams:

```text
1,0,1~1,2,1 <- A
0,0,2~2,0,2 <- B
0,2,3~2,2,3 <- C
0,0,4~0,2,4 <- D
2,0,5~2,2,5 <- E
0,1,6~2,1,6 <- F
1,1,8~1,1,9 <- G
```

At the time of the snapshot, from the side so the `x` axis goes left to right, these bricks are arranged like this:

```text
x
012
.G. 9
.G. 8
... 7
FFF 6
..E 5 z
D.. 4
CCC 3
BBB 2
.A. 1
--- 0
```

Rotating the perspective 90 degrees so the `y` axis now goes left to right, the same bricks are arranged like this:

```text
y
012
.G. 9
.G. 8
... 7
.F. 6
EEE 5 z
DDD 4
..C 3
B.. 2
AAA 1
--- 0
```

Once all of the bricks fall downward as far as they can go, the stack looks like this, where `?` means bricks are hidden behind other bricks at that location:

```text
x
012
.G. 6
.G. 5
FFF 4
D.E 3 z
??? 2
.A. 1
--- 0
```

Again from the side:

```text
y
012
.G. 6
.G. 5
.F. 4
??? 3 z
B.C 2
AAA 1
--- 0
```

Now that all of the bricks have settled, it becomes easier to tell which bricks are supporting which other bricks:

* Brick `A` is the only brick supporting bricks `B` and `C`.
* Brick `B` is one of two bricks supporting brick `D` and brick `E`.
* Brick `C` is the other brick supporting brick `D` and brick `E`.
* Brick `D` supports brick `F`.
* Brick `E` also supports brick `F`.
* Brick `F` supports brick `G`.
* Brick `G` isn't supporting any bricks.

Your first task is to figure out **which bricks are safe to disintegrate**. A brick can be safely disintegrated if, after removing it, no other bricks would fall further directly downward. Don't actually disintegrate any bricks - just determine what would happen if, for each brick, only that brick were disintegrated. Bricks can be disintegrated even if they're completely surrounded by other bricks; you can squeeze between bricks if you need to.

In this example, the bricks can be disintegrated as follows:

* Brick `A` cannot be disintegrated safely; if it were disintegrated, bricks `B` and `C` would both fall.
* Brick `B` **can** be disintegrated; the bricks above it (`D` and `E`) would still be supported by brick `C`.
* Brick `C` **can** be disintegrated; the bricks above it (`D` and `E`) would still be supported by brick `B`.
* Brick `D` **can** be disintegrated; the brick above it (`F`) would still be supported by brick `E`.
* Brick `E` **can** be disintegrated; the brick above it (`F`) would still be supported by brick `D`.
* Brick `F` cannot be disintegrated; the brick above it (`G`) would fall.
* Brick `G` **can** be disintegrated; it does not support any other bricks.
*
So, in this example, `5` bricks can be safely disintegrated.

Figure how the blocks will settle based on the snapshot. Once they've settled, consider disintegrating a single brick; **how many bricks could be safely chosen as the one to get disintegrated?**

Your puzzle answer was `430`.

--- Part Two ---

Disintegrating bricks one at a time isn't going to be fast enough. While it might sound dangerous, what you really need is a **chain reaction**.

You'll need to figure out the best brick to disintegrate. For each brick, determine how many **other bricks would fall** if that brick were disintegrated.

Using the same example as above:

* Disintegrating brick `A` would cause all `6` other bricks to fall.
* Disintegrating brick `F` would cause only `1` other brick, `G`, to fall.

Disintegrating any other brick would cause **no other bricks** to fall. So, in this example, the sum of **the number of other bricks that would fall** as a result of disintegrating each brick is `7`.

For each brick, determine how many **other bricks** would fall if that brick were disintegrated. **What is the sum of the number of other bricks that would fall?**

Your puzzle answer was `60558`.

Both parts of this puzzle are complete! They provide two gold stars: **

At this point, you should return to your Advent calendar and try another puzzle.

If you still want to see it, you can get your puzzle input.

You can also [Share] this puzzle.
Loading

0 comments on commit 75dacea

Please sign in to comment.