diff --git a/advent-of-code-2022.csproj b/advent-of-code-2022.csproj index 729f342..619169b 100644 --- a/advent-of-code-2022.csproj +++ b/advent-of-code-2022.csproj @@ -2,12 +2,13 @@ Exe - net6.0 + net7.0 aoc2022 enable enable False True + default diff --git a/src/01.cs b/src/01.cs index 71efa47..0d9f470 100644 --- a/src/01.cs +++ b/src/01.cs @@ -7,7 +7,7 @@ internal class Day01 : Day internal override void Parse() { List calories = new(); - foreach (var line in Util.ReadAllLines("01")) + foreach (var line in Util.Parsing.ReadAllLines("01")) { if (string.IsNullOrEmpty(line)) { diff --git a/src/02.cs b/src/02.cs index 3d0e193..8e5c161 100644 --- a/src/02.cs +++ b/src/02.cs @@ -6,7 +6,7 @@ internal class Day02 : Day internal override void Parse() { - lines = Util.ReadAllLines("02"); + lines = Util.Parsing.ReadAllLines("02"); } internal override string Part1() diff --git a/src/03.cs b/src/03.cs index d10fc79..a5a754c 100644 --- a/src/03.cs +++ b/src/03.cs @@ -6,7 +6,7 @@ internal class Day03 : Day internal override void Parse() { - sacks = Util.ReadAllLines("03"); + sacks = Util.Parsing.ReadAllLines("03"); } static int GetPriority(char x) => x <= 'Z' ? x - 'A' + 27 : x - 'a' + 1; diff --git a/src/04.cs b/src/04.cs index 8c1f8ef..e0058a1 100644 --- a/src/04.cs +++ b/src/04.cs @@ -6,7 +6,7 @@ internal class Day04 : Day internal override void Parse() { - foreach (var line in Util.ReadAllLines("04")) + foreach (var line in Util.Parsing.ReadAllLines("04")) { var assignments = line.Split(','); var firstAssignment = assignments[0].Split('-'); diff --git a/src/05.cs b/src/05.cs index d5b4dd1..7440bda 100644 --- a/src/05.cs +++ b/src/05.cs @@ -17,7 +17,7 @@ internal class Day05 : Day internal override void Parse() { int state = 0; - foreach (var line in Util.ReadAllLines("05")) + foreach (var line in Util.Parsing.ReadAllLines("05")) { if (state == 0) { diff --git a/src/06.cs b/src/06.cs index 98d82ac..8bc5364 100644 --- a/src/06.cs +++ b/src/06.cs @@ -6,7 +6,7 @@ internal class Day06 : Day internal override void Parse() { - buffer = Util.ReadAllText("06"); + buffer = Util.Parsing.ReadAllText("06"); } private static int FindDistinct(string buf, int distinctLen) diff --git a/src/07.cs b/src/07.cs index ca8de15..91b317f 100644 --- a/src/07.cs +++ b/src/07.cs @@ -2,7 +2,7 @@ internal class Day07 : Day { - private class file + private class fileInfo { public long size; // ReSharper disable once NotAccessedField.Local @@ -11,11 +11,11 @@ internal class Day07 : Day public override string ToString() => $"{name}, {size:N0}b"; } - private class dir + private class dirInfo { - public dir? outer; - public readonly List dirs = new(); - public readonly List files = new(); + public dirInfo? outer; + public readonly List dirs = new(); + public readonly List files = new(); public string name = string.Empty; public long size => files.Sum(x => x.size) + dirs.Sum(x => x.size); @@ -23,13 +23,13 @@ internal class Day07 : Day public override string ToString() => $"{name}, {size:N0}b, {dirs.Count} dir{(dirs.Count == 1 ? "" : "s")}, {files.Count} file{(files.Count == 1 ? "" : "s")}{(outer != null ? $", parent '{outer.name}'" : "")}"; } - private readonly dir rootDir = new() {name = "/"}; + private readonly dirInfo rootDirInfo = new() {name = "/"}; internal override void Parse() { - dir? curr = null; + dirInfo? curr = null; - foreach (var line in Util.ReadAllLines("07")) + foreach (var line in Util.Parsing.ReadAllLines("07")) { if (line.StartsWith("$")) { @@ -45,7 +45,7 @@ internal class Day07 : Day { if (arg == "/") { - curr = rootDir; + curr = rootDirInfo; } else if (arg == "..") { @@ -62,17 +62,17 @@ internal class Day07 : Day var parts = line.Split(' '); if (parts[0] == "dir") { - curr!.dirs.Add(new dir() { name = parts[1], outer = curr }); + curr!.dirs.Add(new dirInfo() { name = parts[1], outer = curr }); } else { - curr!.files.Add(new file { size = long.Parse(parts[0]), name = parts[1] }); + curr!.files.Add(new fileInfo { size = long.Parse(parts[0]), name = parts[1] }); } } } } - private static IEnumerable GetCandidates(dir root, long? threshold = null) + private static IEnumerable GetCandidates(dirInfo root, long? threshold = null) { if (threshold == null || root.size <= threshold) { @@ -95,15 +95,15 @@ internal class Day07 : Day internal override string Part1() { - List candidates = new(GetCandidates(rootDir, 100000)); + List candidates = new(GetCandidates(rootDirInfo, 100000)); return $"Sum of directories below 100,000 bytes: <+white>{candidates.Sum(x => x.size)}"; } internal override string Part2() { - List flatDirList = new(GetCandidates(rootDir)); - var rootSize = rootDir.size; + List flatDirList = new(GetCandidates(rootDirInfo)); + var rootSize = rootDirInfo.size; const int totalSize = 70000000; var currentFreeSpace = totalSize - rootSize; const int totalNeededFreeSpace = 30000000; diff --git a/src/08.cs b/src/08.cs index 0b7b6e1..8c8cfdf 100644 --- a/src/08.cs +++ b/src/08.cs @@ -6,7 +6,7 @@ internal class Day08 : Day internal override void Parse() { - var lines = new List(Util.ReadAllLines("08")); + var lines = new List(Util.Parsing.ReadAllLines("08")); trees = new int[lines.Count][]; for (int i = 0; i < lines.Count; i++) { diff --git a/src/Template.cs b/src/Template.cs index ec22ecf..08c42ab 100644 --- a/src/Template.cs +++ b/src/Template.cs @@ -3,11 +3,9 @@ namespace aoc2022; internal class DayTemplate : Day { - IEnumerable? lines; - internal override void Parse() { - lines = Util.ReadAllLines("##"); + var lines = Util.Parsing.ReadAllLines("##"); } internal override string Part1() diff --git a/src/Util.cs b/src/Util.cs index e78618e..5f28270 100644 --- a/src/Util.cs +++ b/src/Util.cs @@ -1,118 +1 @@ -using System.Diagnostics; -using System.Reflection; -using System.Text; - -namespace aoc2022; - -internal static class Util -{ - 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)) - { - foreach (var line in File.ReadLines(filename)) - { - processor(line); - } - - return; - } - - // typeof(Util) 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 Util is also part of the "default namespace" - var resourceName = $"{typeof(Util).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) - { - string contents = string.Empty; - ReadData(filename, (line) => contents = line); - return contents; - } - - 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); - } - - internal static void StartTestSet(string name) - { - Logger.Log($"test: {name}"); - } - - internal static void StartTest(string label) - { - Logger.Log($"{label}"); - } - - internal static void TestCondition(Func a, bool printResult = true) - { - if (a.Invoke() == false) - { - Debug.Assert(false); - if (printResult) - { - Logger.Log("x"); - } - } - else - { - if (printResult) - { - Logger.Log(""); - } - } - } -} + \ No newline at end of file diff --git a/src/Util/Bisect.cs b/src/Util/Bisect.cs new file mode 100644 index 0000000..0041ace --- /dev/null +++ b/src/Util/Bisect.cs @@ -0,0 +1,51 @@ +using System.Numerics; + +namespace aoc2022.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..d668cd7 --- /dev/null +++ b/src/Util/Combinatorics.cs @@ -0,0 +1,38 @@ +namespace aoc2022.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++) + { + 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/Math.cs b/src/Util/Math.cs new file mode 100644 index 0000000..17d5804 --- /dev/null +++ b/src/Util/Math.cs @@ -0,0 +1,46 @@ +using System.Numerics; + +namespace aoc2022.Util; + +public static class Math +{ + public static ulong GCD(ulong a, ulong b) + { + while (true) + { + if (b == 0) + { + return a; + } + + var a1 = a; + a = b; + b = a1 % b; + } + } + + public static ulong LCM(params ulong[] nums) + { + var num = nums.Length; + switch (num) + { + case 0: + return 0; + 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 ulong lcm(ulong a, ulong b) + { + return (a * b) / GCD(a, b); + } +} \ No newline at end of file diff --git a/src/Util/Parsing.cs b/src/Util/Parsing.cs new file mode 100644 index 0000000..e213a6e --- /dev/null +++ b/src/Util/Parsing.cs @@ -0,0 +1,88 @@ +using System.Reflection; +using System.Text; + +namespace aoc2022.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)) + { + 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) + { + string contents = string.Empty; + ReadData(filename, (line) => contents = line); + return contents; + } + + 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/Testing.cs b/src/Util/Testing.cs new file mode 100644 index 0000000..8bd2c83 --- /dev/null +++ b/src/Util/Testing.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; + +namespace aoc2022.Util; + +public static class Testing +{ + internal static void StartTestSet(string name) + { + Logger.Log($"test: {name}"); + } + + internal static void StartTest(string label) + { + Logger.Log($"{label}"); + } + + internal static void TestCondition(Func a, bool printResult = true) + { + if (a.Invoke() == false) + { + Debug.Assert(false); + if (printResult) + { + Logger.Log("x"); + } + } + else + { + if (printResult) + { + Logger.Log(""); + } + } + } +} \ No newline at end of file