Skip to content

Commit

Permalink
day(18): Lavaduct Lagoon 🦌
Browse files Browse the repository at this point in the history
  • Loading branch information
adamrodger committed Dec 18, 2023
1 parent 81dd549 commit 5668bde
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 0 deletions.
158 changes: 158 additions & 0 deletions src/AdventOfCode/Day18.cs
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 added src/AdventOfCode/inputs/day18.txt
Binary file not shown.
112 changes: 112 additions & 0 deletions tests/AdventOfCode.Tests/Day18Tests.cs
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);
}
}
}

0 comments on commit 5668bde

Please sign in to comment.