From 8120a52ba0917e244e4106e10d84b93232cb1a7a Mon Sep 17 00:00:00 2001 From: Parnic Date: Sat, 30 Nov 2024 23:17:46 -0600 Subject: [PATCH] Day 1 --- .gitignore | 7 + .../.idea/.gitignore | 10 + .idea/.idea.advent-of-code-2024/.idea/.name | 1 + .../.idea/encodings.xml | 4 + .../.idea/indexLayout.xml | 8 + .idea/.idea.advent-of-code-2024/.idea/vcs.xml | 6 + GlobalSuppressions.cs | 8 + Properties/launchSettings.json | 32 +++ README.md | 3 + advent-of-code-2024.csproj | 40 ++++ advent-of-code-2024.sln | 25 +++ global.json | 7 + src/01.cs | 31 +++ src/Day.cs | 65 ++++++ src/Extensions.cs | 41 ++++ src/Logger.cs | 78 +++++++ src/Template.cs | 23 +++ src/Timer.cs | 48 +++++ src/Util/Bisect.cs | 49 +++++ src/Util/Combinatorics.cs | 39 ++++ src/Util/Constants.cs | 22 ++ src/Util/Extensions.cs | 49 +++++ src/Util/Math.cs | 96 +++++++++ src/Util/Parsing.cs | 91 +++++++++ src/Util/Pathfinding/Dijkstra.cs | 76 +++++++ src/Util/Testing.cs | 35 ++++ src/Util/Vec2.cs | 192 ++++++++++++++++++ src/Util/Vec3.cs | 153 ++++++++++++++ src/main.cs | 78 +++++++ 29 files changed, 1317 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.idea.advent-of-code-2024/.idea/.gitignore create mode 100644 .idea/.idea.advent-of-code-2024/.idea/.name create mode 100644 .idea/.idea.advent-of-code-2024/.idea/encodings.xml create mode 100644 .idea/.idea.advent-of-code-2024/.idea/indexLayout.xml create mode 100644 .idea/.idea.advent-of-code-2024/.idea/vcs.xml create mode 100644 GlobalSuppressions.cs create mode 100644 Properties/launchSettings.json create mode 100644 README.md create mode 100644 advent-of-code-2024.csproj create mode 100644 advent-of-code-2024.sln create mode 100644 global.json create mode 100644 src/01.cs create mode 100644 src/Day.cs create mode 100644 src/Extensions.cs create mode 100644 src/Logger.cs create mode 100644 src/Template.cs create mode 100644 src/Timer.cs create mode 100644 src/Util/Bisect.cs create mode 100644 src/Util/Combinatorics.cs create mode 100644 src/Util/Constants.cs create mode 100644 src/Util/Extensions.cs create mode 100644 src/Util/Math.cs create mode 100644 src/Util/Parsing.cs create mode 100644 src/Util/Pathfinding/Dijkstra.cs create mode 100644 src/Util/Testing.cs create mode 100644 src/Util/Vec2.cs create mode 100644 src/Util/Vec3.cs create mode 100644 src/main.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd301fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/target +/.vs/ +*.user +/bin/ +/obj/ +*.exe +/inputs/ diff --git a/.idea/.idea.advent-of-code-2024/.idea/.gitignore b/.idea/.idea.advent-of-code-2024/.idea/.gitignore new file mode 100644 index 0000000..6ce23b6 --- /dev/null +++ b/.idea/.idea.advent-of-code-2024/.idea/.gitignore @@ -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/ diff --git a/.idea/.idea.advent-of-code-2024/.idea/.name b/.idea/.idea.advent-of-code-2024/.idea/.name new file mode 100644 index 0000000..3d15d3c --- /dev/null +++ b/.idea/.idea.advent-of-code-2024/.idea/.name @@ -0,0 +1 @@ +advent-of-code-2024 \ No newline at end of file diff --git a/.idea/.idea.advent-of-code-2024/.idea/encodings.xml b/.idea/.idea.advent-of-code-2024/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.advent-of-code-2024/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.advent-of-code-2024/.idea/indexLayout.xml b/.idea/.idea.advent-of-code-2024/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.advent-of-code-2024/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.advent-of-code-2024/.idea/vcs.xml b/.idea/.idea.advent-of-code-2024/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.advent-of-code-2024/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/GlobalSuppressions.cs b/GlobalSuppressions.cs new file mode 100644 index 0000000..13beaeb --- /dev/null +++ b/GlobalSuppressions.cs @@ -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 = "", Scope = "member", Target = "~F:aoc2024.DayTemplate.lines")] diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..68ad4d8 --- /dev/null +++ b/Properties/launchSettings.json @@ -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" + } + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a4d89c --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Advent of Code 2024 + +My solutions to [Advent of Code 2024](https://adventofcode.com/2024). diff --git a/advent-of-code-2024.csproj b/advent-of-code-2024.csproj new file mode 100644 index 0000000..32483ca --- /dev/null +++ b/advent-of-code-2024.csproj @@ -0,0 +1,40 @@ + + + + Exe + net9.0 + aoc2024 + enable + enable + False + True + latest + + + + 9999 + True + 1701;1702;8981 + + + + 9999 + True + 1701;1702;8981 + + + + + + + + + + + + + + + + + diff --git a/advent-of-code-2024.sln b/advent-of-code-2024.sln new file mode 100644 index 0000000..402ef8f --- /dev/null +++ b/advent-of-code-2024.sln @@ -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 diff --git a/global.json b/global.json new file mode 100644 index 0000000..f4fd385 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "9.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file diff --git a/src/01.cs b/src/01.cs new file mode 100644 index 0000000..7fd2aa1 --- /dev/null +++ b/src/01.cs @@ -0,0 +1,31 @@ +namespace aoc2024; + +internal class Day01 : Day +{ + private readonly List list1 = []; + private readonly List 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}"; + } +} diff --git a/src/Day.cs b/src/Day.cs new file mode 100644 index 0000000..4dbaa41 --- /dev/null +++ b/src/Day.cs @@ -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($"{GetType().Name}"); + + 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}"); + } + 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}"); + } + 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; } +} diff --git a/src/Extensions.cs b/src/Extensions.cs new file mode 100644 index 0000000..f9355fd --- /dev/null +++ b/src/Extensions.cs @@ -0,0 +1,41 @@ +using System.Diagnostics; + +namespace aoc2024; + +internal static class Extensions +{ + public static void ForEach(this IEnumerable enumeration, Action 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); + } +} diff --git a/src/Logger.cs b/src/Logger.cs new file mode 100644 index 0000000..1181d7a --- /dev/null +++ b/src/Logger.cs @@ -0,0 +1,78 @@ +using System.Diagnostics; + +namespace aoc2024; + +internal class Logger +{ + private static readonly Dictionary 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; + } +} diff --git a/src/Template.cs b/src/Template.cs new file mode 100644 index 0000000..a147195 --- /dev/null +++ b/src/Template.cs @@ -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>"; + } +} diff --git a/src/Timer.cs b/src/Timer.cs new file mode 100644 index 0000000..4b32b32 --- /dev/null +++ b/src/Timer.cs @@ -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 = ""; + if (unit == "us" || (unit == "ms" && elapsed < 10)) + { + color = ""; + } + else if (unit == "ms" && elapsed < 250) + { + color = ""; + } + Logger.LogLine($"{name}{(!string.IsNullOrEmpty(name) ? " t" : "T")}ook {color}{elapsed:N1}{unit}"); + } +} diff --git a/src/Util/Bisect.cs b/src/Util/Bisect.cs new file mode 100644 index 0000000..6a80a5e --- /dev/null +++ b/src/Util/Bisect.cs @@ -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 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 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; + } +} \ No newline at end of file diff --git a/src/Util/Combinatorics.cs b/src/Util/Combinatorics.cs new file mode 100644 index 0000000..9259003 --- /dev/null +++ b/src/Util/Combinatorics.cs @@ -0,0 +1,39 @@ +namespace aoc2024.Util; + +public static class Combinatorics +{ + public static IEnumerable> GetPermutations(IList list) + { + Action, int>? helper = null; + List> 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; + } +} diff --git a/src/Util/Constants.cs b/src/Util/Constants.cs new file mode 100644 index 0000000..bc8049b --- /dev/null +++ b/src/Util/Constants.cs @@ -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 = '╔'; +} diff --git a/src/Util/Extensions.cs b/src/Util/Extensions.cs new file mode 100644 index 0000000..bf274ec --- /dev/null +++ b/src/Util/Extensions.cs @@ -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(this ICollection list, T elem) + { + if (!list.Contains(elem)) + { + list.Add(elem); + } + } + + public static int IndexOf(this ICollection list, T elem) where T : IEquatable + { + 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(this T[,] a, T[,] b) => a.Rank == b.Rank + && Enumerable.Range(0, a.Rank).All(d=> a.GetLength(d) == b.GetLength(d)) + && a.Cast().SequenceEqual(b.Cast()); +} diff --git a/src/Util/Math.cs b/src/Util/Math.cs new file mode 100644 index 0000000..cacfcd2 --- /dev/null +++ b/src/Util/Math.cs @@ -0,0 +1,96 @@ +using System.Numerics; + +namespace aoc2024.Util; + +public static class Math +{ + public static T GCD(T a, T b) where T : IBinaryInteger + { + while (true) + { + if (b == T.Zero) + { + return a; + } + + var a1 = a; + a = b; + b = a1 % b; + } + } + + public static T LCM(params T[] nums) where T : IBinaryInteger + { + 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 a, T b) where T : IBinaryInteger + { + 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[] n, T[] a) where T : IBinaryInteger + { + 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 a, T mod) where T : IBinaryInteger + { + T b = a % mod; + for (T x = T.One; x < mod; x++) + { + if ((b * x) % mod == T.One) + { + return x; + } + } + + return T.One; + } +} \ No newline at end of file diff --git a/src/Util/Parsing.cs b/src/Util/Parsing.cs new file mode 100644 index 0000000..d47c0f8 --- /dev/null +++ b/src/Util/Parsing.cs @@ -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 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 ReadAllLines(string filename) + { + List lines = new(); + ReadData(filename, (line) => lines.Add(line)); + return lines; + } + + internal static IEnumerable ReadAllLinesAsInts(string filename) + { + return ReadAllLines(filename).Select(long.Parse); + } +} \ No newline at end of file diff --git a/src/Util/Pathfinding/Dijkstra.cs b/src/Util/Pathfinding/Dijkstra.cs new file mode 100644 index 0000000..27faa22 --- /dev/null +++ b/src/Util/Pathfinding/Dijkstra.cs @@ -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) Solve(long[,] graph, ivec2 start) + { + long width = graph.GetLength(0); + long height = graph.GetLength(1); + long[,] dist = new long[width, height]; + Dictionary prev = new(); + PriorityQueue 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); + } +} \ No newline at end of file diff --git a/src/Util/Testing.cs b/src/Util/Testing.cs new file mode 100644 index 0000000..f2706c6 --- /dev/null +++ b/src/Util/Testing.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; + +namespace aoc2024.Util; + +public static class Testing +{ + internal static void StartTestSet(string name) + { + Logger.LogLine($"test: {name}"); + } + + internal static void StartTest(string label) + { + Logger.LogLine($"{label}"); + } + + internal static void TestCondition(Func a, bool printResult = true) + { + if (a.Invoke() == false) + { + Debug.Assert(false); + if (printResult) + { + Logger.LogLine("x"); + } + } + else + { + if (printResult) + { + Logger.LogLine(""); + } + } + } +} \ No newline at end of file diff --git a/src/Util/Vec2.cs b/src/Util/Vec2.cs new file mode 100644 index 0000000..19421c9 --- /dev/null +++ b/src/Util/Vec2.cs @@ -0,0 +1,192 @@ +namespace aoc2024.Util; + +public readonly struct ivec2 : IEquatable, IComparable, 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 GetOrthogonalNeighbors() + { + foreach (var dir in FOURWAY) + { + yield return this + dir; + } + } + + public IEnumerable 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 GetNeighbors() + { + foreach (var dir in EIGHTWAY) + { + yield return this + dir; + } + } + + public IEnumerable 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}"; +} diff --git a/src/Util/Vec3.cs b/src/Util/Vec3.cs new file mode 100644 index 0000000..6e2a4a8 --- /dev/null +++ b/src/Util/Vec3.cs @@ -0,0 +1,153 @@ +namespace aoc2024.Util; + +public record struct ivec3(long x, long y, long z) : IComparable, 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 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}"; +} diff --git a/src/main.cs b/src/main.cs new file mode 100644 index 0000000..38bddcb --- /dev/null +++ b/src/main.cs @@ -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 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 {desiredDay}"); + } + else + { + day = (Day?) Activator.CreateInstance(type); + } + } + + day?.Go(runPart1 ?? true, runPart2 ?? true); + day?.Dispose(); + } +}