-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
81dd549
commit 5668bde
Showing
3 changed files
with
270 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Globalization; | ||
using System.Linq; | ||
using AdventOfCode.Utilities; | ||
|
||
namespace AdventOfCode | ||
{ | ||
/// <summary> | ||
/// Solver for Day 18 | ||
/// </summary> | ||
public class Day18 | ||
{ | ||
// this originally generated the perimeter points then flood-filled the centre | ||
public long Part1(string[] input) => DigPlan.ForPart1(input).CalculateArea(); | ||
|
||
// Had to look up hints for part 2. I didn't know the maths for finding the area of an irregular polygon | ||
public long Part2(string[] input) => DigPlan.ForPart2(input).CalculateArea(); | ||
|
||
/// <summary> | ||
/// Plan for digging the lava lake | ||
/// </summary> | ||
/// <param name="Instructions">Plan instructions for digging the perimeter</param> | ||
private record DigPlan(IList<DigInstruction> Instructions) | ||
{ | ||
/// <summary> | ||
/// Instructions for digging the lava lake in part 1 | ||
/// </summary> | ||
/// <param name="input">Input</param> | ||
/// <returns>Dig plan</returns> | ||
public static DigPlan ForPart1(IReadOnlyList<string> input) | ||
{ | ||
var instructions = from line in input | ||
let direction = line[0] switch | ||
{ | ||
'U' => Bearing.North, | ||
'D' => Bearing.South, | ||
'R' => Bearing.East, | ||
'L' => Bearing.West, | ||
_ => throw new ArgumentOutOfRangeException() | ||
} | ||
let steps = line.Numbers<int>().First() | ||
select new DigInstruction(direction, steps); | ||
|
||
return new DigPlan(instructions.ToArray()); | ||
} | ||
|
||
/// <summary> | ||
/// Instructions for digging the lava lake in part 2 | ||
/// </summary> | ||
/// <param name="input">Input</param> | ||
/// <returns>Dig plan</returns> | ||
public static DigPlan ForPart2(IReadOnlyList<string> input) | ||
{ | ||
var instructions = from line in input | ||
let direction = line[^2] switch | ||
{ | ||
'3' => Bearing.North, | ||
'1' => Bearing.South, | ||
'0' => Bearing.East, | ||
'2' => Bearing.West, | ||
_ => throw new ArgumentOutOfRangeException() | ||
} | ||
let steps = int.Parse(line[^7..^2], NumberStyles.HexNumber) | ||
select new DigInstruction(direction, steps); | ||
|
||
return new DigPlan(instructions.ToArray()); | ||
} | ||
|
||
/// <summary> | ||
/// Calculate the entire area of the lava lake | ||
/// </summary> | ||
/// <returns>Lava lake area</returns> | ||
public long CalculateArea() | ||
{ | ||
(IList<(long X, long Y)> vertices, long perimeter) = this.PlanPerimeter(); | ||
|
||
long area = CalculateInternalArea(vertices); | ||
|
||
// See https://en.wikipedia.org/wiki/Pick%27s_theorem | ||
return area + perimeter / 2 + 1; | ||
} | ||
|
||
/// <summary> | ||
/// Use the instructions to map out the perimeter | ||
/// </summary> | ||
/// <returns>All the vertices and the total perimeter length</returns> | ||
private (IList<(long X, long Y)> Vertices, long Perimeter) PlanPerimeter() | ||
{ | ||
long x = 0; | ||
long y = 0; | ||
long perimeter = 0; | ||
List<(long X, long Y)> vertices = new() { (0, 0) }; | ||
|
||
foreach ((Bearing direction, var steps) in this.Instructions) | ||
{ | ||
long deltaX = 0; | ||
long deltaY = 0; | ||
|
||
switch (direction) | ||
{ | ||
case Bearing.North: | ||
deltaY -= steps; | ||
break; | ||
case Bearing.South: | ||
deltaY += steps; | ||
break; | ||
case Bearing.East: | ||
deltaX += steps; | ||
break; | ||
case Bearing.West: | ||
deltaX -= steps; | ||
break; | ||
} | ||
|
||
x += deltaX; | ||
y += deltaY; | ||
perimeter += steps; | ||
vertices.Add((x, y)); | ||
} | ||
|
||
return (vertices, perimeter); | ||
} | ||
|
||
/// <summary> | ||
/// Calculate the internal area of the lava lake defined by the given vertices | ||
/// </summary> | ||
/// <param name="vertices">Vertices</param> | ||
/// <returns>Internal area</returns> | ||
/// <remarks> | ||
/// See: | ||
/// https://en.m.wikipedia.org/wiki/Shoelace_formula#Triangle_formula | ||
/// https://www.theoremoftheday.org/GeometryAndTrigonometry/Shoelace/TotDShoelace.pdf | ||
/// </remarks> | ||
private static long CalculateInternalArea(IList<(long X, long Y)> vertices) | ||
{ | ||
long area = 0; | ||
|
||
// create a closed loop and iterate the vertices anti-clockwise | ||
IEnumerable<((long X, long Y) First, (long X, long Y) Second)> loop = vertices.Zip(vertices.Skip(1).Append(vertices[0])); | ||
|
||
foreach (((long X, long Y) first, (long X, long Y) second) in loop) | ||
{ | ||
area += (first.X * second.Y) - (second.X * first.Y); | ||
} | ||
|
||
return area / 2; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Instruction for digging an edge of the lava lake | ||
/// </summary> | ||
/// <param name="Direction">Dig direction</param> | ||
/// <param name="Steps">Number of steps to dig</param> | ||
private record DigInstruction(Bearing Direction, int Steps); | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
using System.IO; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
|
||
namespace AdventOfCode.Tests | ||
{ | ||
public class Day18Tests | ||
{ | ||
private readonly ITestOutputHelper output; | ||
private readonly Day18 solver; | ||
|
||
public Day18Tests(ITestOutputHelper output) | ||
{ | ||
this.output = output; | ||
this.solver = new Day18(); | ||
} | ||
|
||
private static string[] GetRealInput() | ||
{ | ||
string[] input = File.ReadAllLines("inputs/day18.txt"); | ||
return input; | ||
} | ||
|
||
private static string[] GetSampleInput() | ||
{ | ||
return new string[] | ||
{ | ||
"R 6 (#70c710)", | ||
"D 5 (#0dc571)", | ||
"L 2 (#5713f0)", | ||
"D 2 (#d2c081)", | ||
"R 2 (#59c680)", | ||
"D 2 (#411b91)", | ||
"L 5 (#8ceee2)", | ||
"U 2 (#caa173)", | ||
"L 1 (#1b58a2)", | ||
"U 2 (#caa171)", | ||
"R 2 (#7807d2)", | ||
"U 3 (#a77fa3)", | ||
"L 2 (#015232)", | ||
"U 2 (#7a21e3)", | ||
}; | ||
} | ||
|
||
[Fact] | ||
public void Part1_RealInput_ProducesCorrectResponse() | ||
{ | ||
var expected = 46394; | ||
|
||
var result = solver.Part1(GetRealInput()); | ||
output.WriteLine($"Day 18 - Part 1 - {result}"); | ||
|
||
Assert.Equal(expected, result); | ||
} | ||
|
||
[Fact] | ||
public void Part2_SampleInput_ProducesCorrectResponse() | ||
{ | ||
var expected = 952408144115; | ||
|
||
var result = solver.Part2(GetSampleInput()); | ||
|
||
Assert.Equal(expected, result); | ||
} | ||
|
||
[Fact] | ||
public void Part2_MyInput_ProducesCorrectResponse() | ||
{ | ||
/* | ||
01234567891111 | ||
0123 | ||
0 ##### | ||
1 # # | ||
2 ##### # | ||
3 # # | ||
4 ##### ###### | ||
5 # # | ||
6 ########## | ||
*/ | ||
long expected = 62; | ||
|
||
var result = this.solver.Part2(new [] | ||
{ | ||
"R 4 (#000040)", | ||
"D 4 (#000041)", | ||
"R 5 (#000050)", | ||
"D 2 (#000021)", | ||
"L 9 (#000092)", | ||
"U 2 (#000023)", | ||
"L 4 (#000042)", | ||
"U 2 (#000023)", | ||
"R 4 (#000040)", | ||
"U 2 (#000023)", | ||
}); | ||
|
||
Assert.Equal(expected, result); | ||
} | ||
|
||
[Fact] | ||
public void Part2_RealInput_ProducesCorrectResponse() | ||
{ | ||
var expected = 201398068194715; | ||
|
||
var result = solver.Part2(GetRealInput()); | ||
output.WriteLine($"Day 18 - Part 2 - {result}"); | ||
|
||
Assert.Equal(expected, result); | ||
} | ||
} | ||
} |