From 68a494c929ed6c3456b8ad1a5bbab6cadfdfddf2 Mon Sep 17 00:00:00 2001 From: "Valeriano A.R." Date: Mon, 25 Dec 2023 05:13:36 +0100 Subject: [PATCH] WIP AdventOfCode 2023 Day24 Part2 --- AdventOfCode2023.Tests/Day24_Tests.cs | 82 ++++++- AdventOfCode2023/Day24.cs | 295 +++++++++++++++++++++++++- 2 files changed, 356 insertions(+), 21 deletions(-) diff --git a/AdventOfCode2023.Tests/Day24_Tests.cs b/AdventOfCode2023.Tests/Day24_Tests.cs index 0197eb7..ea89086 100644 --- a/AdventOfCode2023.Tests/Day24_Tests.cs +++ b/AdventOfCode2023.Tests/Day24_Tests.cs @@ -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); + } } \ No newline at end of file diff --git a/AdventOfCode2023/Day24.cs b/AdventOfCode2023/Day24.cs index fe0841f..7e8b7ca 100644 --- a/AdventOfCode2023/Day24.cs +++ b/AdventOfCode2023/Day24.cs @@ -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,10 +126,15 @@ 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) { - count++; + 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++; + } } } } @@ -100,10 +143,207 @@ public class Day24 : IDay 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)); } } } \ No newline at end of file