Compare commits

1 Commits

Author SHA1 Message Date
68a494c929 WIP AdventOfCode 2023 Day24 Part2 2023-12-25 05:13:36 +01:00
2 changed files with 356 additions and 21 deletions

View File

@@ -10,7 +10,7 @@ namespace AdventOfCode2023.Tests;
public class Day24_Tests
{
private string[] _example1 = {
private readonly string[] _example1 = {
"19, 13, 30 @ -2, 1, -2",
"18, 19, 22 @ -1, -1, -2",
"20, 25, 34 @ -2, -2, -4",
@@ -28,6 +28,35 @@ public class Day24_Tests
Assert.Equal("2", result);
}
[Fact]
public void ResolvePart2__Example1()
{
Day24 day = new();
string result = day.ResolvePart2(_example1);
Assert.Equal("47", result);
}
private readonly string[] _example2 = {
"19, 13, 30 @ -2, 1, -2",
"18, 19, 22 @ -1, -1, -2",
"20, 25, 34 @ -2, -2, -4",
"12, 31, 28 @ -1, -2, -1",
"20, 19, 15 @ 1, -5, -3",
"24, 13, 10 @ -3, 1, 2",
};
[Fact]
public void ResolvePart2__Example2()
{
Day24 day = new();
string result = day.ResolvePart2(_example2);
Assert.Equal("47", result);
}
[Fact]
public void Hail_Intersect2D__Examples1()
{
@@ -37,35 +66,66 @@ public class Day24_Tests
Day24.Hail hailD = new("12, 31, 28 @ -1, -2, -1");
Day24.Hail hailE = new("20, 19, 15 @ 1, -5, -3");
(bool intersect_A_B, _, _, _) = hailA.Intersect2D(hailB);
(bool intersect_A_B, _, _) = hailA.Intersect2D(hailB);
Assert.True(intersect_A_B);
(bool intersect_A_C, _, _, _) = hailA.Intersect2D(hailC);
(bool intersect_A_C, _, _) = hailA.Intersect2D(hailC);
Assert.True(intersect_A_C);
(bool intersect_A_D, _, _, _) = hailA.Intersect2D(hailD);
(bool intersect_A_D, _, _) = hailA.Intersect2D(hailD);
Assert.True(intersect_A_D);
(bool intersect_A_E, _, _, _) = hailA.Intersect2D(hailE);
(bool intersect_A_E, _, _) = hailA.Intersect2D(hailE);
Assert.True(intersect_A_E);
(bool intersect_B_C, _, _, _) = hailB.Intersect2D(hailC);
(bool intersect_B_C, _, _) = hailB.Intersect2D(hailC);
Assert.False(intersect_B_C);
(bool intersect_B_D, _, _, _) = hailB.Intersect2D(hailD);
(bool intersect_B_D, _, _) = hailB.Intersect2D(hailD);
Assert.True(intersect_B_D);
(bool intersect_B_E, _, _, _) = hailB.Intersect2D(hailE);
(bool intersect_B_E, _, _) = hailB.Intersect2D(hailE);
Assert.True(intersect_B_E);
(bool intersect_C_D, _, _, _) = hailC.Intersect2D(hailD);
(bool intersect_C_D, _, _) = hailC.Intersect2D(hailD);
Assert.True(intersect_C_D);
(bool intersect_C_E, _, _, _) = hailC.Intersect2D(hailE);
(bool intersect_C_E, _, _) = hailC.Intersect2D(hailE);
Assert.True(intersect_C_E);
(bool intersect_D_E, _, _, _) = hailD.Intersect2D(hailE);
(bool intersect_D_E, _, _) = hailD.Intersect2D(hailE);
Assert.True(intersect_D_E);
}
[Fact]
public void Hail_Intersect3D__Examples1()
{
Day24.Hail rock = new("24, 13, 10 @ -3, 1, 2");
Day24.Hail hailA = new("19, 13, 30 @ -2, 1, -2");
Day24.Hail hailB = new("18, 19, 22 @ -1, -1, -2");
Day24.Hail hailC = new("20, 25, 34 @ -2, -2, -4");
Day24.Hail hailD = new("12, 31, 28 @ -1, -2, -1");
Day24.Hail hailE = new("20, 19, 15 @ 1, -5, -3");
(bool intersect_A, double t_A, _) = rock.Intersect3D(hailA);
Assert.True(intersect_A);
Assert.Equal(5, t_A);
(bool intersect_B, double t_B, _) = rock.Intersect3D(hailB);
Assert.True(intersect_B);
Assert.Equal(3, t_B);
(bool intersect_C, double t_C, _) = rock.Intersect3D(hailC);
Assert.True(intersect_C);
Assert.Equal(4, t_C);
(bool intersect_D, double t_D, _) = rock.Intersect3D(hailD);
Assert.True(intersect_D);
Assert.Equal(6, t_D);
(bool intersect_E, double t_E, _) = rock.Intersect3D(hailE);
Assert.True(intersect_E);
Assert.Equal(1, t_E);
}
}

View File

@@ -72,6 +72,44 @@ However, you'll need to search a much larger test area if you want to see if any
Considering only the X and Y axes, check all pairs of hailstones' future paths for intersections. How many of these intersections occur within the test area?
--- Part Two ---
Upon further analysis, it doesn't seem like any hailstones will naturally collide. It's up to you to fix that!
You find a rock on the ground nearby. While it seems extremely unlikely, if you throw it just right, you should be able to hit every hailstone in a single throw!
You can use the probably-magical winds to reach any integer position you like and to propel the rock at any integer velocity. Now including the Z axis in your calculations, if you throw the rock at time 0, where do you need to be so that the rock perfectly collides with every hailstone? Due to probably-magical inertia, the rock won't slow down or change direction when it collides with a hailstone.
In the example above, you can achieve this by moving to position 24, 13, 10 and throwing the rock at velocity -3, 1, 2. If you do this, you will hit every hailstone as follows:
Hailstone: 19, 13, 30 @ -2, 1, -2
Collision time: 5
Collision position: 9, 18, 20
Hailstone: 18, 19, 22 @ -1, -1, -2
Collision time: 3
Collision position: 15, 16, 16
Hailstone: 20, 25, 34 @ -2, -2, -4
Collision time: 4
Collision position: 12, 17, 18
Hailstone: 12, 31, 28 @ -1, -2, -1
Collision time: 6
Collision position: 6, 19, 22
Hailstone: 20, 19, 15 @ 1, -5, -3
Collision time: 1
Collision position: 21, 14, 12
Above, each hailstone is identified by its initial position and its velocity. Then, the time and position of that hailstone's collision with your rock are given.
After 1 nanosecond, the rock has exactly the same position as one of the hailstones, obliterating it into ice dust! Another hailstone is smashed to bits two nanoseconds after that. After a total of 6 nanoseconds, all of the hailstones have been destroyed.
So, at time 0, the rock needs to be at X position 24, Y position 13, and Z position 10. Adding these three coordinates together produces 47. (Don't add any coordinates from the rock's velocity.)
Determine the exact position and velocity the rock needs to have at time 0 so that it perfectly collides with every hailstone. What do you get if you add up the X, Y, and Z coordinates of that initial position?
*/
@@ -88,22 +126,224 @@ public class Day24 : IDay
{
for (int j = i + 1; j < hails.Length; j++)
{
(bool intersects, double s, double t, Vector3D point) = hails[i].Intersect2D(hails[j]);
if (intersects && s > 0 && t > 0 && point.X >= min && point.X <= max && point.Y >= min && point.Y <= max)
(bool intersects, double s, double t) = hails[i].Intersect2D(hails[j]);
if (intersects && s > 0 && t > 0)
{
long x = (long)(hails[i].Position.X + t * hails[i].Velocity.X);
long y = (long)(hails[i].Position.Y + t * hails[i].Velocity.Y);
if (x >= min && x <= max && y >= min && y <= max)
{
count++;
}
}
}
}
return count.ToString();
}
public string ResolvePart2(string[] inputs)
{
throw new NotImplementedException();
string result = ResolvePart2_SolutionInDataSet(inputs);
if (string.IsNullOrEmpty(result) == false)
{
return result;
}
result = ResolvePart2_BruteForceXY(inputs);
if (string.IsNullOrEmpty(result) == false)
{
return result;
}
return string.Empty;
}
public struct Vector3D
public string ResolvePart2_BruteForceXY(string[] inputs)
{
Hail[] hails = inputs.Select(input => new Hail(input)).ToArray();
// Brute force solution
long absMax = hails
.Select(h => new[] { h.Velocity.X, h.Velocity.Y, h.Velocity.Z, })
.SelectMany(x => x).Select(Math.Abs)
.Max();
Console.WriteLine($"Velocity AbsMax: {absMax}");
long minV = -absMax;
long maxV = absMax;
Vector3D? collisionPoint = null;
for (long vy = minV; vy < maxV && collisionPoint == null; vy++)
{
for (long vx = minV; vx < maxV && collisionPoint == null; vx++)
{
Vector3D newVelocity0 = new(
x: hails[0].Velocity.X - vx,
y: hails[0].Velocity.Y - vy,
z: hails[0].Velocity.Z);
Hail shiftedHail0 = new(hails[0].Position, newVelocity0);
Vector3D newVelocity1 = new(
x: hails[1].Velocity.X - vx,
y: hails[1].Velocity.Y - vy,
z: hails[1].Velocity.Z);
Hail shiftedHail1 = new(hails[1].Position, newVelocity1);
(bool intersects, double s, double t) = shiftedHail0.Intersect2D(shiftedHail1);
if (intersects == false || s < 0.0f || t < 0.0) { continue; }
long x = (long)(shiftedHail0.Position.X + t * shiftedHail0.Velocity.X);
long y = (long)(shiftedHail0.Position.Y + t * shiftedHail0.Velocity.Y);
Vector3D firstCollisionPoint = new(x, y, 0);
//Console.WriteLine($"Possible Rock: {vx} {vy} | {firstCollisionPoint.X}, {firstCollisionPoint.Y}, {firstCollisionPoint.Z} @ {vx}, {vy}, ?? | {s} {t}");
Hail[] shiftedHails = hails.Select(h =>
{
Vector3D newVelocity = new(
x: h.Velocity.X - vx,
y: h.Velocity.Y - vy,
z: h.Velocity.Z);
Hail newHail = new(h.Position, newVelocity);
return newHail;
}).ToArray();
Vector3D currentCollisionPoint = firstCollisionPoint;
bool allCollided = true;
for (int i = 2; i < shiftedHails.Length; i++)
{
(bool intersectsIter, double sIter, double tIter) = shiftedHails[0].Intersect2D(shiftedHails[i]);
long xIter = (long)(shiftedHail0.Position.X + tIter * shiftedHail0.Velocity.X);
long yIter = (long)(shiftedHail0.Position.Y + tIter * shiftedHail0.Velocity.Y);
Vector3D collisionPointIter = new(xIter, yIter, 0);
if (
intersectsIter &&
currentCollisionPoint.Cmp2D(collisionPointIter) &&
true)
{
continue;
}
allCollided = false;
//Console.WriteLine($"... Invalid rock: {i}");
break;
}
if (allCollided)
{
long xCollision = (long)(hails[0].Position.X + t * hails[0].Velocity.X);
long yCollision = (long)(hails[0].Position.Y + t * hails[0].Velocity.Y);
long zCollision = (long)(hails[0].Position.Z + t * hails[0].Velocity.Z);
Console.WriteLine($"Possible Rock: {xCollision}, {yCollision}, {zCollision} @ {vx}, {vy}, ?? | {t}");
collisionPoint = new Vector3D(xCollision, yCollision, zCollision);
}
}
}
if (collisionPoint == null)
{
return string.Empty;
}
return (collisionPoint.Value.X + collisionPoint.Value.Y + collisionPoint.Value.Z).ToString();
}
private string ResolvePart2_BruteForceXYZ(string[] inputs)
{
Hail[] hails = inputs.Select(input => new Hail(input)).ToArray();
// Brute force solution
long absMax = hails
.Select(h => new[] { h.Velocity.X, h.Velocity.Y, h.Velocity.Z, })
.SelectMany(x => x).Select(Math.Abs)
.Max();
Console.WriteLine($"Velocity AbsMax: {absMax}");
long minV = -absMax;
long maxV = absMax;
Vector3D? collisionPoint = null;
for (long vz = minV; vz < maxV && collisionPoint == null; vz++)
{
Console.WriteLine($"Brute forcing Z layer {vz}");
for (long vy = minV; vy < maxV && collisionPoint == null; vy++)
{
for (long vx = minV; vx < maxV && collisionPoint == null; vx++)
{
Vector3D newVelocity0 = new(
x: hails[0].Velocity.X - vx,
y: hails[0].Velocity.Y - vy,
z: hails[0].Velocity.Z - vz);
Hail shiftedHail0 = new(hails[0].Position, newVelocity0);
Vector3D newVelocity1 = new(
x: hails[1].Velocity.X - vx,
y: hails[1].Velocity.Y - vy,
z: hails[1].Velocity.Z - vz);
Hail shiftedHail1 = new(hails[1].Position, newVelocity1);
(bool intersects, double t, Vector3D? firstCollisionPoint) = shiftedHail0.Intersect3D(shiftedHail1, sameT: false);
if (intersects == false || firstCollisionPoint == null || t < 0.0) { continue; }
//Console.WriteLine($"Possible Rock: {firstCollisionPoint.Value.X}, {firstCollisionPoint.Value.Y}, {firstCollisionPoint.Value.Z} @ {vx}, {vy}, {vz} | {t}");
Hail[] shiftedHails = hails.Select(h =>
{
Vector3D newVelocity = new(
x: h.Velocity.X - vx,
y: h.Velocity.Y - vy,
z: h.Velocity.Z - vz);
Hail newHail = new(h.Position, newVelocity);
return newHail;
}).ToArray();
Vector3D currentCollisionPoint = firstCollisionPoint.Value;
bool allCollided = true;
for (int i = 2; i < shiftedHails.Length; i++)
{
(bool intersectsIter, double tIter, Vector3D? collisionPointIter) = shiftedHails[0].Intersect3D(shiftedHails[i], sameT: false);
if (
intersectsIter &&
tIter >= -0.001f &&
collisionPointIter != null &&
currentCollisionPoint.Cmp3D(collisionPointIter.Value) &&
true)
{
continue;
}
allCollided = false;
//Console.WriteLine($"... Invalid rock: {i}");
break;
}
if (allCollided)
{
collisionPoint = currentCollisionPoint;
}
}
}
}
return collisionPoint != null
? (collisionPoint.Value.X + collisionPoint.Value.Y + collisionPoint.Value.Z).ToString()
: string.Empty;
}
private string ResolvePart2_SolutionInDataSet(string[] inputs)
{
Hail[] hails = inputs.Select(input => new Hail(input)).ToArray();
// Check if any hail is doing the same as the rock
Hail? bestMatch = null;
for (int i = 0; i < hails.Length; i++)
{
bool allCollided = true;
for (int j = 0; j < hails.Length; j++)
{
if (i == j) { continue; }
(bool intersects, _, _) = hails[i].Intersect3D(hails[j]);
if (intersects) { continue; }
allCollided = false;
break;
}
if (allCollided)
{
bestMatch = hails[i];
}
}
if (bestMatch != null)
{
return (bestMatch.Value.Position.X + bestMatch.Value.Position.Y + bestMatch.Value.Position.Z).ToString();
}
return string.Empty;
}
public readonly struct Vector3D
{
public readonly long X;
public readonly long Y;
@@ -115,6 +355,16 @@ public class Day24 : IDay
Y = y;
Z = z;
}
public bool Cmp2D(Vector3D other)
{
return X == other.X && Y == other.Y;
}
public bool Cmp3D(Vector3D other)
{
return X == other.X && Y == other.Y && Z == other.Z;
}
}
public readonly struct Hail
@@ -122,6 +372,12 @@ public class Day24 : IDay
public readonly Vector3D Position;
public readonly Vector3D Velocity;
public Hail(Vector3D position, Vector3D velocity)
{
Position = position;
Velocity = velocity;
}
public Hail(string input)
{
string[] parts = input.Split(" @ ");
@@ -137,24 +393,43 @@ public class Day24 : IDay
z: Convert.ToInt64(strVelocity[2]));
}
public (bool intersects, double s, double t, Vector3D point) Intersect2D(Hail other)
public (bool intersects, double s, double t) Intersect2D(Hail other)
{
long s_Div = (-other.Velocity.X * Velocity.Y + Velocity.X * other.Velocity.Y);
if (s_Div == 0)
{
return (false, 0, 0, new Vector3D());
return (false, 0, 0);
}
double s = (-Velocity.Y * (Position.X - other.Position.X) + Velocity.X * (Position.Y - other.Position.Y)) / (double)s_Div;
long t_Div = (-other.Velocity.X * Velocity.Y + Velocity.X * other.Velocity.Y);
if (t_Div == 0)
{
return (false, 0, 0, new Vector3D());
return (false, 0, 0);
}
double t = (other.Velocity.X * (Position.Y - other.Position.Y) - other.Velocity.Y * (Position.X - other.Position.X)) / (double)t_Div;
Vector3D intersection = new((long)(Position.X + t * Velocity.X), (long)(Position.Y + t * Velocity.Y), 0);
return (true, s, t, intersection);
return (true, s, t);
}
public (bool intersects, double t, Vector3D? point) Intersect3D(Hail other, bool sameT = true)
{
(bool intersects, double s, double t) = Intersect2D(other);
if (intersects == false || (sameT && Math.Abs(s - t) > 0.001))
{
return (false, 0, null);
}
long zThis = (long)(Position.Z + t * Velocity.Z);
long zOther = (long)(other.Position.Z + s * other.Velocity.Z);
if (Math.Abs(zThis - zOther) > 0.001)
{
return (false, 0, null);
}
long yThis = (long)(Position.Y + t * Velocity.Y);
long xThis = (long)(Position.X + t * Velocity.X);
return (true, t, new Vector3D(xThis, yThis, zThis));
}
}
}