This commit is contained in:
2024-11-30 23:17:46 -06:00
commit 8120a52ba0
29 changed files with 1317 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/target
/.vs/
*.user
/bin/
/obj/
*.exe
/inputs/

10
.idea/.idea.advent-of-code-2024/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/.idea.advent-of-code-2024.iml
/modules.xml
/projectSettingsUpdater.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -0,0 +1 @@
advent-of-code-2024

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

8
GlobalSuppressions.cs Normal file
View File

@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "<Pending>", Scope = "member", Target = "~F:aoc2024.DayTemplate.lines")]

View File

@ -0,0 +1,32 @@
{
"profiles": {
"all days": {
"commandName": "Project",
"commandLineArgs": "all"
},
"current day": {
"commandName": "Project",
"commandLineArgs": ""
},
"current day part 1": {
"commandName": "Project",
"commandLineArgs": "-part1"
},
"current day part 2": {
"commandName": "Project",
"commandLineArgs": "-part2"
},
"all days part 1": {
"commandName": "Project",
"commandLineArgs": "all -part1"
},
"all days part 2": {
"commandName": "Project",
"commandLineArgs": "all -part2"
},
"specific day": {
"commandName": "Project",
"commandLineArgs": "1"
}
}
}

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# Advent of Code 2024
My solutions to [Advent of Code 2024](https://adventofcode.com/2024).

View File

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>aoc2024</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<WarningLevel>9999</WarningLevel>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<NoWarn>1701;1702;8981</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<WarningLevel>9999</WarningLevel>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<NoWarn>1701;1702;8981</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Remove="inputs\01.txt" />
<None Remove="inputs\01a.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="inputs\01.txt" />
<EmbeddedResource Include="inputs\01a.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Z3" Version="4.12.2" />
</ItemGroup>
</Project>

25
advent-of-code-2024.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "advent-of-code-2024", "advent-of-code-2024.csproj", "{1B54D933-507B-4F44-9BE3-F1794B593AF7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1B54D933-507B-4F44-9BE3-F1794B593AF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B54D933-507B-4F44-9BE3-F1794B593AF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B54D933-507B-4F44-9BE3-F1794B593AF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B54D933-507B-4F44-9BE3-F1794B593AF7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {17D280F0-AD9F-481E-8687-28AAB4A54437}
EndGlobalSection
EndGlobal

7
global.json Normal file
View File

@ -0,0 +1,7 @@
{
"sdk": {
"version": "9.0.0",
"rollForward": "latestMajor",
"allowPrerelease": true
}
}

31
src/01.cs Normal file
View File

@ -0,0 +1,31 @@
namespace aoc2024;
internal class Day01 : Day
{
private readonly List<long> list1 = [];
private readonly List<long> list2 = [];
internal override void Parse()
{
var lines = Util.Parsing.ReadAllLines($"{GetDay()}");
foreach (var line in lines)
{
var vals = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
list1.Add(long.Parse(vals[0]));
list2.Add(long.Parse(vals[1]));
}
}
internal override string Part1()
{
list1.Sort();
list2.Sort();
long totalDist = list1.Select((num1, idx) => Math.Abs(num1 - list2[idx])).Sum();
return $"Total distance between lists: <+white>{totalDist}";
}
internal override string Part2()
{
long score = list1.Aggregate(0L, (accum, num1) => accum + num1 * list2.Count(num2 => num2 == num1));
return $"Lists similarity score: <+white>{score}";
}
}

65
src/Day.cs Normal file
View File

@ -0,0 +1,65 @@
namespace aoc2024;
internal abstract class Day : IDisposable
{
public void Dispose()
{
Logger.LogLine("");
}
internal void Go(bool runPart1, bool runPart2)
{
Logger.LogLine($"<reverse>{GetType().Name}<r>");
using (new Timer("Parsing"))
{
Parse();
}
if (runPart1)
{
using var stopwatch = new Timer();
var response = Part1();
stopwatch.Stop();
if (!string.IsNullOrEmpty(response))
{
Logger.LogLine($"<+black>> part1: {response}<r>");
}
else
{
stopwatch.Disabled = true;
}
}
if (runPart2)
{
using var stopwatch = new Timer();
var response = Part2();
stopwatch.Stop();
if (!string.IsNullOrEmpty(response))
{
Logger.LogLine($"<+black>> part2: {response}<r>");
}
else
{
stopwatch.Disabled = true;
}
}
}
internal int GetDayNum()
{
if (int.TryParse(GetType().Name["Day".Length..], out int dayNum))
{
return dayNum;
}
return -1;
}
internal string GetDay() => $"{GetDayNum():00}";
internal virtual void Parse() { }
internal virtual string Part1() { return string.Empty; }
internal virtual string Part2() { return string.Empty; }
}

41
src/Extensions.cs Normal file
View File

@ -0,0 +1,41 @@
using System.Diagnostics;
namespace aoc2024;
internal static class Extensions
{
public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
foreach (T item in enumeration)
{
action(item);
}
}
public static (double elapsed, string unit) ConvertToHumanReadable(this Stopwatch stopwatch)
{
var elapsed = 1.0d * stopwatch.ElapsedTicks / Stopwatch.Frequency;
var unit = "s";
if (elapsed < 0.001)
{
elapsed *= 1e+6;
unit = "us";
}
else if (elapsed < 1)
{
elapsed *= 1000;
unit = "ms";
}
else if (elapsed < 60)
{
unit = "s";
}
else if (elapsed < 60 * 60)
{
elapsed /= 60;
unit = "m";
}
return (elapsed, unit);
}
}

78
src/Logger.cs Normal file
View File

@ -0,0 +1,78 @@
using System.Diagnostics;
namespace aoc2024;
internal class Logger
{
private static readonly Dictionary<string, string> colorCodes = new()
{
{ "r", "\u001b[0m" },
{ "black", "\u001b[30m" },
{ "red", "\u001b[31m" },
{ "green", "\u001b[32m" },
{ "yellow", "\u001b[33m" },
{ "blue", "\u001b[34m" },
{ "magenta", "\u001b[35m" },
{ "cyan", "\u001b[36m" },
{ "white", "\u001b[37m" },
{ "+black", "\u001b[30;1m" },
{ "+red", "\u001b[31;1m" },
{ "+green", "\u001b[32;1m" },
{ "+yellow", "\u001b[33;1m" },
{ "+blue", "\u001b[34;1m" },
{ "+magenta", "\u001b[35;1m" },
{ "+cyan", "\u001b[36;1m" },
{ "+white", "\u001b[37;1m" },
{ "bgBlack", "\u001b[40m" },
{ "bgRed", "\u001b[41m" },
{ "bgGreen", "\u001b[42m" },
{ "bgYellow", "\u001b[43m" },
{ "bgBlue", "\u001b[44m" },
{ "bgMagenta", "\u001b[45m" },
{ "bgCyan", "\u001b[46m" },
{ "bgWhite", "\u001b[47m" },
{ "+bgBlack", "\u001b[40;1m" },
{ "+bgRed", "\u001b[41;1m" },
{ "+bgGreen", "\u001b[42;1m" },
{ "+bgYellow", "\u001b[43;1m" },
{ "+bgBlue", "\u001b[44;1m" },
{ "+bgMagenta", "\u001b[45;1m" },
{ "+bgCyan", "\u001b[46;1m" },
{ "+bgWhite", "\u001b[47;1m" },
{ "bold", "\u001b[1m" },
{ "underline", "\u001b[4m" },
{ "reverse", "\u001b[7m" },
};
public static void LogLine(string msg)
{
Console.WriteLine(InsertColorCodes(msg));
Debug.WriteLine(StripColorCodes(msg));
}
public static void Log(string msg)
{
Console.Write(InsertColorCodes(msg));
Debug.Write(StripColorCodes(msg));
}
private static string InsertColorCodes(string msg)
{
foreach (var code in colorCodes)
{
msg = msg.Replace($"<{code.Key}>", code.Value, StringComparison.CurrentCultureIgnoreCase);
}
return msg;
}
private static string StripColorCodes(string msg)
{
foreach (var code in colorCodes)
{
msg = msg.Replace($"<{code.Key}>", string.Empty, StringComparison.CurrentCultureIgnoreCase);
}
return msg;
}
}

23
src/Template.cs Normal file
View File

@ -0,0 +1,23 @@
namespace aoc2024;
internal class DayTemplate : Day
{
internal override void Parse()
{
var lines = Util.Parsing.ReadAllLines($"{GetDay()}");
}
internal override string Part1()
{
return $"<+white>";
}
internal override string Part2()
{
return $"<+white>";
}
}

48
src/Timer.cs Normal file
View File

@ -0,0 +1,48 @@
using System.Diagnostics;
namespace aoc2024;
internal class Timer : IDisposable
{
private readonly Stopwatch stopwatch = Stopwatch.StartNew();
private readonly string? name;
private bool stopped;
public bool Disabled { get; set; }
public Timer(string? inName = null)
{
name = inName;
}
public void Stop()
{
if (stopped)
{
return;
}
stopwatch.Stop();
stopped = true;
}
public void Dispose()
{
Stop();
if (Disabled)
{
return;
}
var (elapsed, unit) = stopwatch.ConvertToHumanReadable();
var color = "<red>";
if (unit == "us" || (unit == "ms" && elapsed < 10))
{
color = "<green>";
}
else if (unit == "ms" && elapsed < 250)
{
color = "<yellow>";
}
Logger.LogLine($"<cyan>{name}{(!string.IsNullOrEmpty(name) ? " t" : "T")}ook {color}{elapsed:N1}{unit}<r>");
}
}

49
src/Util/Bisect.cs Normal file
View File

@ -0,0 +1,49 @@
namespace aoc2024.Util;
public static class Bisect
{
// Bisect takes a known-good low and known-bad high value as the bounds
// to bisect, and a function to test each value for success or failure.
// If the function succeeds, the value is adjusted toward the maximum,
// and if the function fails, the value is adjusted toward the minimum.
// The final value is returned when the difference between the success
// and the failure is less than or equal to the acceptance threshold
// (usually 1, for integers).
public static double Find(double low, double high, double threshold, Func<double, bool> tryFunc)
{
while (System.Math.Abs(high - low) > threshold)
{
var currVal = low + ((high - low) / 2);
var success = tryFunc(currVal);
if (success)
{
low = currVal;
}
else
{
high = currVal;
}
}
return low;
}
public static double Find(long low, long high, long threshold, Func<long, bool> tryFunc)
{
while (System.Math.Abs(high - low) > threshold)
{
var currVal = low + ((high - low) / 2);
var success = tryFunc(currVal);
if (success)
{
low = currVal;
}
else
{
high = currVal;
}
}
return low;
}
}

39
src/Util/Combinatorics.cs Normal file
View File

@ -0,0 +1,39 @@
namespace aoc2024.Util;
public static class Combinatorics
{
public static IEnumerable<IEnumerable<T>> GetPermutations<T>(IList<T> list)
{
Action<IList<T>, int>? helper = null;
List<IEnumerable<T>> res = new();
helper = (arr, n) =>
{
if (n == 1)
{
var tmp = new T[arr.Count];
arr.CopyTo(tmp, 0);
res.Add(tmp);
}
else
{
for (var i = 0; i < n; i++)
{
// ReSharper disable once AccessToModifiedClosure
helper!(arr, n - 1);
if (n % 2 == 1)
{
(arr[i], arr[n - 1]) = (arr[n - 1], arr[i]);
}
else
{
(arr[0], arr[n - 1]) = (arr[n - 1], arr[0]);
}
}
}
};
helper(list, list.Count);
return res;
}
}

22
src/Util/Constants.cs Normal file
View File

@ -0,0 +1,22 @@
namespace aoc2024.Util;
public static class Constants
{
public const char SolidBlock = '█';
public const char SolidSquare = '■';
public const char SolidSmallSquare = '▪';
public const char BoxVert = '│';
public const char BoxHorz = '─';
public const char BoxCurveNE = '╰';
public const char BoxCurveNW = '╯';
public const char BoxCurveSW = '╮';
public const char BoxCurveSE = '╭';
public const char BoxDoubleVert = '║';
public const char BoxDoubleHorz = '═';
public const char BoxDoubleNE = '╚';
public const char BoxDoubleNW = '╝';
public const char BoxDoubleSW = '╗';
public const char BoxDoubleSE = '╔';
}

49
src/Util/Extensions.cs Normal file
View File

@ -0,0 +1,49 @@
namespace aoc2024.Util;
public static class Extensions
{
public static bool IsDigit(this char c) => c >= '0' && c <= '9';
public static void AddUnique<T>(this ICollection<T> list, T elem)
{
if (!list.Contains(elem))
{
list.Add(elem);
}
}
public static int IndexOf<T>(this ICollection<T> list, T elem) where T : IEquatable<T>
{
for (int idx = 0; idx < list.Count; idx++)
{
if (list.ElementAt(idx).Equals(elem))
{
return idx;
}
}
return -1;
}
public static string ReplaceFirst(this string str, char ch, char replace)
{
for (int i = 0; i < str.Length; i++)
{
if (str[i] == ch)
{
return str.ReplaceAt(i, replace);
}
}
return str;
}
public static string ReplaceAt(this string str, int index, char replace)
{
return str[..index] + replace + str[(index + 1)..];
}
public static bool SequenceEquals<T>(this T[,] a, T[,] b) => a.Rank == b.Rank
&& Enumerable.Range(0, a.Rank).All(d=> a.GetLength(d) == b.GetLength(d))
&& a.Cast<T>().SequenceEqual(b.Cast<T>());
}

96
src/Util/Math.cs Normal file
View File

@ -0,0 +1,96 @@
using System.Numerics;
namespace aoc2024.Util;
public static class Math
{
public static T GCD<T>(T a, T b) where T : IBinaryInteger<T>
{
while (true)
{
if (b == T.Zero)
{
return a;
}
var a1 = a;
a = b;
b = a1 % b;
}
}
public static T LCM<T>(params T[] nums) where T : IBinaryInteger<T>
{
var num = nums.Length;
switch (num)
{
case 0:
return T.Zero;
case 1:
return nums[0];
}
var ret = lcm(nums[0], nums[1]);
for (var i = 2; i < num; i++)
{
ret = lcm(nums[i], ret);
}
return ret;
}
private static T lcm<T>(T a, T b) where T : IBinaryInteger<T>
{
return (a * b) / GCD(a, b);
}
public static long Modulo(long numer, long denom)
{
// long q = numer / denom;
long r = numer % denom;
if (r < 0)
{
if (denom > 0)
{
// q = q - 1;
r = r + denom;
}
else
{
// q = q + 1;
r = r - denom;
}
}
return r;
}
// taken from https://rosettacode.org/wiki/Chinese_remainder_theorem#C.23
public static T CRT<T>(T[] n, T[] a) where T : IBinaryInteger<T>
{
T prod = n.Aggregate(T.One, (i, j) => i * j);
T p;
T sm = T.Zero;
for (int i = 0; i < n.Length; i++)
{
p = prod / n[i];
sm += a[i] * ModularMultiplicativeInverse(p, n[i]) * p;
}
return sm % prod;
}
public static T ModularMultiplicativeInverse<T>(T a, T mod) where T : IBinaryInteger<T>
{
T b = a % mod;
for (T x = T.One; x < mod; x++)
{
if ((b * x) % mod == T.One)
{
return x;
}
}
return T.One;
}
}

91
src/Util/Parsing.cs Normal file
View File

@ -0,0 +1,91 @@
using System.Reflection;
using System.Text;
namespace aoc2024.Util;
public static class Parsing
{
private static readonly char[] StripPreamble = { (char)8745, (char)9559, (char)9488, };
private static readonly Encoding[] StripBOMsFromEncodings = { Encoding.UTF8, Encoding.Unicode, Encoding.BigEndianUnicode, };
private static void ReadData(string inputName, Action<string> processor)
{
if (Console.IsInputRedirected)
{
bool processedSomething = false;
for (int i = 0; Console.In.ReadLine() is { } line; i++)
{
if (i == 0)
{
if (line[0..StripPreamble.Length].SequenceEqual(StripPreamble))
{
line = line[StripPreamble.Length..];
}
else
{
foreach (var encoding in StripBOMsFromEncodings)
{
if (line.StartsWith(encoding.GetString(encoding.GetPreamble()), StringComparison.Ordinal))
{
line = line.Replace(encoding.GetString(encoding.GetPreamble()), "", StringComparison.Ordinal);
}
}
}
}
processor(line);
if (!string.IsNullOrEmpty(line))
{
processedSomething = true;
}
}
if (processedSomething)
{
return;
}
}
var filename = $"inputs/{inputName}.txt";
if (File.Exists(filename))
{
if (Directory.Exists(Path.GetDirectoryName(filename)!) && File.Exists(filename))
{
foreach (var line in File.ReadLines(filename))
{
processor(line);
}
return;
}
}
// typeof(Logger) is not technically correct since what we need is the "default namespace,"
// but "default namespace" is a Project File concept, not a C#/.NET concept, so it's not
// accessible at runtime. instead, we assume Logger is also part of the "default namespace"
var resourceName = $"{typeof(Logger).Namespace}.inputs.{inputName}.txt";
using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
using StreamReader reader = new(stream!);
while (reader.ReadLine() is { } readLine)
{
processor(readLine);
}
}
internal static string ReadAllText(string filename)
{
StringBuilder sb = new();
ReadData(filename, (line) => sb.AppendLine(line));
return sb.ToString().Trim();
}
internal static IEnumerable<string> ReadAllLines(string filename)
{
List<string> lines = new();
ReadData(filename, (line) => lines.Add(line));
return lines;
}
internal static IEnumerable<long> ReadAllLinesAsInts(string filename)
{
return ReadAllLines(filename).Select(long.Parse);
}
}

View File

@ -0,0 +1,76 @@
using System.Numerics;
namespace aoc2024.Util.Pathfinding;
public class Dijkstra
{
/*
* 1 function Dijkstra(Graph, source):
2 dist[source] ← 0 // Initialization
3
4 create vertex priority queue Q
5
6 for each vertex v in Graph.Vertices:
7 if v ≠ source
8 dist[v] ← INFINITY // Unknown distance from source to v
9 prev[v] ← UNDEFINED // Predecessor of v
10
11 Q.add_with_priority(v, dist[v])
12
13
14 while Q is not empty: // The main loop
15 u ← Q.extract_min() // Remove and return best vertex
16 for each neighbor v of u: // Go through all v neighbors of u
17 alt ← dist[u] + Graph.Edges(u, v)
18 if alt < dist[v]:
19 dist[v] ← alt
20 prev[v] ← u
21 Q.decrease_priority(v, alt)
22
23 return dist, prev
*/
public static (long[,], Dictionary<ivec2, ivec2>) Solve(long[,] graph, ivec2 start)
{
long width = graph.GetLength(0);
long height = graph.GetLength(1);
long[,] dist = new long[width, height];
Dictionary<ivec2, ivec2> prev = new();
PriorityQueue<ivec2, long> pq = new();
for (long x = 0; x < width; x++)
{
for (long y = 0; y < height; y++)
{
ivec2 pt = new(x, y);
if (start.x == x && start.y == y)
{
dist[x, y] = 0;
}
else
{
dist[x, y] = long.MaxValue;
}
}
}
while (pq.Count > 0)
{
var u = pq.Dequeue();
foreach (var neighbor in u.GetBoundedOrthogonalNeighbors(0, 0, (int)width - 1, (int)height - 1))
{
if (!prev.ContainsKey(neighbor))
{
pq.Enqueue(neighbor, dist[neighbor.x, neighbor.y]);
}
long alt = dist[u.x, u.y] + graph[neighbor.x, neighbor.y];
if (alt < dist[neighbor.x, neighbor.y])
{
dist[neighbor.x, neighbor.y] = alt;
prev.TryAdd(neighbor, u);
}
}
}
return (dist, prev);
}
}

35
src/Util/Testing.cs Normal file
View File

@ -0,0 +1,35 @@
using System.Diagnostics;
namespace aoc2024.Util;
public static class Testing
{
internal static void StartTestSet(string name)
{
Logger.LogLine($"<underline>test: {name}<r>");
}
internal static void StartTest(string label)
{
Logger.LogLine($"<magenta>{label}<r>");
}
internal static void TestCondition(Func<bool> a, bool printResult = true)
{
if (a.Invoke() == false)
{
Debug.Assert(false);
if (printResult)
{
Logger.LogLine("<red>x<r>");
}
}
else
{
if (printResult)
{
Logger.LogLine("<green>✓<r>");
}
}
}
}

192
src/Util/Vec2.cs Normal file
View File

@ -0,0 +1,192 @@
namespace aoc2024.Util;
public readonly struct ivec2 : IEquatable<ivec2>, IComparable<ivec2>, IComparable
{
public readonly long x = 0;
public readonly long y = 0;
public static readonly ivec2 ZERO = new ivec2(0, 0);
public static readonly ivec2 ONE = new ivec2(1, 1);
public static readonly ivec2 LEFT = new ivec2(-1, 0);
public static readonly ivec2 RIGHT = new ivec2(1, 0);
public static readonly ivec2 UP = new ivec2(0, -1);
public static readonly ivec2 DOWN = new ivec2(0, 1);
public static readonly ivec2[] FOURWAY = {RIGHT, LEFT, UP, DOWN};
public static readonly ivec2[] EIGHTWAY = {UP, UP + RIGHT, RIGHT, RIGHT + DOWN, DOWN, DOWN + LEFT, LEFT, LEFT + UP};
public ivec2(long xv, long yv)
{
x = xv;
y = yv;
}
public bool IsZero() => x == 0 && y == 0;
public long Sum => x + y;
public long Product => x * y;
public long MaxElement => System.Math.Max(x, y);
public ivec2 GetRotatedLeft() => new ivec2(y, -x);
public ivec2 GetRotatedRight() => new ivec2(-y, x);
public long Dot(ivec2 v) => (x * v.x) + (y * v.y);
public long LengthSquared => (x * x) + (y * y);
public float Length => MathF.Sqrt(LengthSquared);
public long ManhattanDistance => Abs(this).Sum;
public long ManhattanDistanceTo(ivec2 other) => System.Math.Abs(x - other.x) + System.Math.Abs(y - other.y);
public ivec2 GetBestDirectionTo(ivec2 p)
{
ivec2 diff = p - this;
if (diff.IsZero())
{
return ZERO;
}
ivec2 dir = diff / Abs(diff).MaxElement;
return Sign(dir);
}
// get a point in the 8 cells around me closest to p
public ivec2 GetNearestNeighbor(ivec2 p)
{
ivec2 dir = GetBestDirectionTo(p);
return this + dir;
}
public IEnumerable<ivec2> GetOrthogonalNeighbors()
{
foreach (var dir in FOURWAY)
{
yield return this + dir;
}
}
public IEnumerable<ivec2> GetBoundedOrthogonalNeighbors(int minX, int minY, int maxX, int maxY)
{
foreach (var dir in FOURWAY)
{
var pt = this + dir;
if (!pt.IsWithinRange(minX, minY, maxX, maxY))
{
continue;
}
yield return pt;
}
}
public IEnumerable<ivec2> GetNeighbors()
{
foreach (var dir in EIGHTWAY)
{
yield return this + dir;
}
}
public IEnumerable<ivec2> GetBoundedNeighbors(int minX, int minY, int maxX, int maxY)
{
foreach (var dir in EIGHTWAY)
{
var pt = this + dir;
if (!pt.IsWithinRange(minX, minY, maxX, maxY))
{
continue;
}
yield return pt;
}
}
public long this[long i] => (i == 0) ? x : y;
public static ivec2 operator +(ivec2 v) => v;
public static ivec2 operator -(ivec2 v) => new ivec2(-v.x, -v.y);
public static ivec2 operator +(ivec2 a, ivec2 b) => new ivec2(a.x + b.x, a.y + b.y);
public static ivec2 operator -(ivec2 a, ivec2 b) => new ivec2(a.x - b.x, a.y - b.y);
public static ivec2 operator *(ivec2 a, ivec2 b) => new ivec2(a.x * b.x, a.y * b.y);
public static ivec2 operator *(long a, ivec2 v) => new ivec2(a * v.x, a * v.y);
public static ivec2 operator *(ivec2 v, long a) => new ivec2(a * v.x, a * v.y);
public static ivec2 operator /(ivec2 v, long a) => new ivec2(v.x / a, v.y / a);
public static bool operator ==(ivec2 a, ivec2 b) => (a.x == b.x) && (a.y == b.y);
public static bool operator !=(ivec2 a, ivec2 b) => (a.x != b.x) || (a.y != b.y);
public static bool operator <(ivec2 a, ivec2 b) => (a.x < b.x) && (a.y < b.y);
public static bool operator <=(ivec2 a, ivec2 b) => (a.x <= b.x) && (a.y <= b.y);
public static bool operator >(ivec2 a, ivec2 b) => (a.x > b.x) && (a.y > b.y);
public static bool operator >=(ivec2 a, ivec2 b) => (a.x >= b.x) && (a.y >= b.y);
public bool IsWithinRange(int minX, int minY, int maxX, int maxY) => x >= minX && y >= minY && x <= maxX && y <= maxY;
public bool Equals(ivec2 other)
{
return x == other.x && y == other.y;
}
public override bool Equals(object? obj)
{
return obj is ivec2 other && Equals(other);
}
public int CompareTo(ivec2 other)
{
if (this < other)
{
return -1;
}
if (this > other)
{
return 1;
}
return 0;
}
public int CompareTo(object? obj)
{
if (ReferenceEquals(null, obj)) return 1;
return obj is ivec2 other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(ivec2)}");
}
public static ivec2 Sign(ivec2 v) => new ivec2(System.Math.Sign(v.x), System.Math.Sign(v.y));
public static ivec2 Min(ivec2 a, ivec2 b) => new ivec2(System.Math.Min(a.x, b.x), System.Math.Min(a.y, b.y));
public static ivec2 Max(ivec2 a, ivec2 b) => new ivec2(System.Math.Max(a.x, b.x), System.Math.Max(a.y, b.y));
public static ivec2 Clamp(ivec2 v, ivec2 lh, ivec2 rh) => Min(rh, Max(lh, v));
public static ivec2 Abs(ivec2 v) => new ivec2(System.Math.Abs(v.x), System.Math.Abs(v.y));
public static ivec2 Mod(ivec2 val, long den)
{
long x = val.x % den;
long y = val.y % den;
if (x < 0)
{
x += den;
}
if (y < 0)
{
y += den;
}
return new ivec2(x, y);
}
public static long Dot(ivec2 a, ivec2 b) => (a.x * b.x) + (a.y * b.y);
public static ivec2 Parse(string s)
{
string[] parts = s.Split(',', 2);
long x = long.Parse(parts[0]);
long y = long.Parse(parts[1]);
return new ivec2(x, y);
}
public override int GetHashCode() => HashCode.Combine(x, y);
public override string ToString() => $"{x},{y}";
}

153
src/Util/Vec3.cs Normal file
View File

@ -0,0 +1,153 @@
namespace aoc2024.Util;
public record struct ivec3(long x, long y, long z) : IComparable<ivec3>, IComparable
{
public static readonly ivec3 ZERO = new ivec3(0, 0, 0);
public static readonly ivec3 ONE = new ivec3(1, 1, 1);
public static readonly ivec3 LEFT = new ivec3(-1, 0, 0);
public static readonly ivec3 RIGHT = new ivec3(1, 0, 0);
public static readonly ivec3 UP = new ivec3(0, -1, 0);
public static readonly ivec3 DOWN = new ivec3(0, 1, 0);
public static readonly ivec3 FORWARD = new ivec3(0, 0, 1);
public static readonly ivec3 BACKWARD = new ivec3(0, 0, -1);
public static readonly ivec3[] DIRECTIONS = {LEFT, RIGHT, UP, DOWN, FORWARD, BACKWARD};
public bool IsZero() => x == 0 && y == 0 && z == 0;
public long Sum => x + y + z;
public long Product => x * y * z;
public long MaxElement => System.Math.Max(x, System.Math.Max(y, z));
public long MinElement => System.Math.Min(x, System.Math.Min(y, z));
public ivec3 GetRotatedLeft() => new ivec3(y, -x, z);
public ivec3 GetRotatedRight() => new ivec3(-y, x, z);
public long Dot(ivec3 v) => (x * v.x) + (y * v.y) + (z * v.z);
public long LengthSquared => (x * x) + (y * y) + (z * z);
public float Length => MathF.Sqrt(LengthSquared);
public long ManhattanDistance => Abs(this).Sum;
public long ManhattanDistanceTo(ivec3 other) => System.Math.Abs(x - other.x) + System.Math.Abs(y - other.y) + System.Math.Abs(z - other.z);
public bool IsTouching(ivec3 other) => ManhattanDistanceTo(other) == 1;
public IEnumerable<ivec3> GetNeighbors(ivec3 min, ivec3 max)
{
foreach (var d in DIRECTIONS)
{
var n = this + d;
if (n >= min && n <= max)
{
yield return n;
}
}
}
public ivec3 GetBestDirectionTo(ivec3 p)
{
ivec3 diff = p - this;
if (diff.IsZero())
{
return ZERO;
}
ivec3 dir = diff / Abs(diff).MaxElement;
return Sign(dir);
}
// get a point in the 8 cells around me closest to p
public ivec3 GetNearestNeighbor(ivec3 p)
{
ivec3 dir = GetBestDirectionTo(p);
return this + dir;
}
public long this[long i] => (i == 0) ? x : (i == 1) ? y : z;
public static ivec3 operator +(ivec3 v) => v;
public static ivec3 operator -(ivec3 v) => new ivec3(-v.x, -v.y, -v.z);
public static ivec3 operator +(ivec3 a, ivec3 b) => new ivec3(a.x + b.x, a.y + b.y, a.z + b.z);
public static ivec3 operator -(ivec3 a, ivec3 b) => new ivec3(a.x - b.x, a.y - b.y, a.z - b.z);
public static ivec3 operator *(ivec3 a, ivec3 b) => new ivec3(a.x * b.x, a.y * b.y, a.z * b.z);
public static ivec3 operator *(long a, ivec3 v) => new ivec3(a * v.x, a * v.y, a * v.z);
public static ivec3 operator *(ivec3 v, long a) => new ivec3(a * v.x, a * v.y, a * v.z);
public static ivec3 operator /(ivec3 v, long a) => new ivec3(v.x / a, v.y / a, v.z / a);
public static bool operator <(ivec3 a, ivec3 b) => (a.x < b.x) && (a.y < b.y) && (a.z < b.z);
public static bool operator <=(ivec3 a, ivec3 b) => (a.x <= b.x) && (a.y <= b.y) && (a.z <= b.z);
public static bool operator >(ivec3 a, ivec3 b) => (a.x > b.x) && (a.y > b.y) && (a.z > b.z);
public static bool operator >=(ivec3 a, ivec3 b) => (a.x >= b.x) && (a.y >= b.y) && (a.z >= b.z);
public bool Equals(ivec3 other)
{
return x == other.x && y == other.y && z == other.z;
}
public int CompareTo(ivec3 other)
{
if (this < other)
{
return -1;
}
if (this > other)
{
return 1;
}
return 0;
}
public int CompareTo(object? obj)
{
if (ReferenceEquals(null, obj)) return 1;
return obj is ivec3 other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(ivec3)}");
}
public static ivec3 Sign(ivec3 v) => new ivec3(System.Math.Sign(v.x), System.Math.Sign(v.y), System.Math.Sign(v.z));
public static ivec3 Min(ivec3 a, ivec3 b) => new ivec3(System.Math.Min(a.x, b.x), System.Math.Min(a.y, b.y), System.Math.Min(a.z, b.z));
public static ivec3 Max(ivec3 a, ivec3 b) => new ivec3(System.Math.Max(a.x, b.x), System.Math.Max(a.y, b.y), System.Math.Max(a.z, b.z));
public static ivec3 Clamp(ivec3 v, ivec3 lh, ivec3 rh) => Min(rh, Max(lh, v));
public static ivec3 Abs(ivec3 v) => new ivec3(System.Math.Abs(v.x), System.Math.Abs(v.y), System.Math.Abs(v.z));
public static ivec3 Mod(ivec3 val, long den)
{
long x = val.x % den;
long y = val.y % den;
long z = val.z % den;
if (x < 0)
{
x += den;
}
if (y < 0)
{
y += den;
}
if (z < 0)
{
z += den;
}
return new ivec3(x, y, z);
}
public static long Dot(ivec3 a, ivec3 b) => (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
public static ivec3 Parse(string s)
{
string[] parts = s.Split(',', 3);
long x = long.Parse(parts[0]);
long y = long.Parse(parts[1]);
long z = long.Parse(parts[2]);
return new ivec3(x, y, z);
}
public override int GetHashCode() => HashCode.Combine(x, y, z);
public override string ToString() => $"{x},{y},{z}";
}

78
src/main.cs Normal file
View File

@ -0,0 +1,78 @@
using aoc2024;
using aoc2024.Timer t = new("Full program");
var types = System.Reflection.Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(ty => ty.IsSubclassOf(typeof(Day)) && !ty.IsAbstract && ty.Name != "DayTemplate")
.OrderBy(ty => ty.Name).ToList();
bool runAll = false;
bool? runPart1 = null;
bool? runPart2 = null;
List<string> desiredDays = new();
foreach (var arg in args)
{
if (arg.Equals("-part1", StringComparison.CurrentCultureIgnoreCase))
{
runPart1 = true;
}
else if (arg.Equals("-part2", StringComparison.CurrentCultureIgnoreCase))
{
runPart2 = true;
}
else if (arg.Equals("all", StringComparison.CurrentCultureIgnoreCase))
{
runAll = true;
}
else
{
desiredDays.Add(arg);
}
}
if (runPart1 != null || runPart2 != null)
{
runPart1 ??= false;
runPart2 ??= false;
}
if (runAll)
{
foreach (var type in types)
{
using var day = (Day)Activator.CreateInstance(type)!;
day.Go(runPart1 ?? true, runPart2 ?? true);
}
}
else
{
if (desiredDays.Count == 0)
{
desiredDays.Add("");
}
foreach (var desiredDay in desiredDays)
{
Day? day = null;
if (string.IsNullOrEmpty(desiredDay))
{
day = (Day) Activator.CreateInstance(types.Last())!;
}
else
{
var type = types.FirstOrDefault(x => x.Name == $"Day{desiredDay.PadLeft(2, '0')}");
if (type == null)
{
Logger.LogLine($"Unknown day <cyan>{desiredDay}<r>");
}
else
{
day = (Day?) Activator.CreateInstance(type);
}
}
day?.Go(runPart1 ?? true, runPart2 ?? true);
day?.Dispose();
}
}