diff --git a/07/BridgeRepair.Tests/EquationTests.cs b/07/BridgeRepair.Tests/EquationTests.cs new file mode 100644 index 0000000..43d8d7a --- /dev/null +++ b/07/BridgeRepair.Tests/EquationTests.cs @@ -0,0 +1,87 @@ +namespace BridgeRepair.Tests; + +public class EquationTests +{ + [Test] + [MethodDataSource(nameof(GeneratePossibleOperatorConfigurationsTestCases))] + public async Task GeneratePossibleOperatorConfigurations_WhenCalled_ItShouldGenerateExpectedCombinations(GeneratePossibleOperatorConfigurationsTestCase testCase) + { + var result = new Equation(testCase.TargetValue, testCase.Numbers).GeneratePossibleOperatorConfigurations().ToList(); + + await Assert.That(result).IsEquivalentTo(testCase.ExpectedCombinations); + } + + [Test] + [MethodDataSource(nameof(EvaluateTestCases))] + public async Task Evaluate_WhenCalled_ItShouldReturnExpectedValue(EvaluateTestCase testCase) + { + var result = new Equation(testCase.TargetValue, testCase.Numbers).Evaluate(testCase.Combinations); + + await Assert.That(result).IsEquivalentTo(testCase.ExpectedValue); + } + + [Test] + [MethodDataSource(nameof(IsPossibleTestCases))] + public async Task IsPossible_WhenCalled_ItShouldReturnExpectedValue(IsPossibleTestCase testCase) + { + var result = new Equation(testCase.TargetValue, testCase.Numbers).IsPossible(); + + await Assert.That(result).IsEquivalentTo(testCase.ExpectedValue); + } + + [Test] + public async Task IsPossible_WhenCalledWithPart2_ItShouldReturnExpectedValue() + { + // TODO: Make data driven test + var resultOne = new Equation(156, [15, 6]).IsPossible(isPart2: true); + var resultTwo = new Equation(7290, [6, 8, 6, 15]).IsPossible(isPart2: true); + var resultThree = new Equation(192, [17, 8, 14]).IsPossible(isPart2: true); + + await Assert.That(resultOne).IsTrue(); + await Assert.That(resultTwo).IsTrue(); + await Assert.That(resultThree).IsTrue(); + } + + public static IEnumerable> IsPossibleTestCases() + { + yield return () => new(190, [10, 19], true); + yield return () => new(3267, [81, 40, 27], true); + yield return () => new(83, [17, 5], false); + yield return () => new(156, [15, 6], false); + yield return () => new(7290, [6, 8, 6, 15], false); + yield return () => new(161011, [16, 10, 13], false); + yield return () => new(192, [17, 8, 14], false); + yield return () => new(21037, [9, 7, 18, 13], false); + yield return () => new(292, [11, 6, 16, 20], true); + } + + public record IsPossibleTestCase(long TargetValue, List Numbers, bool ExpectedValue); + + public static IEnumerable> EvaluateTestCases() + { + yield return () => new(190, [10, 19], ['+'], 29); + yield return () => new(190, [10, 19], ['*'], 190); + yield return () => new(3267, [81, 40, 27], ['*', '+'], 3267); + yield return () => new(292, [11, 6, 16, 20], ['+', '*', '+'], 292); + } + + public record EvaluateTestCase(long TargetValue, List Numbers, char[] Combinations, long ExpectedValue); + + public static IEnumerable> GeneratePossibleOperatorConfigurationsTestCases() + { + yield return () => new(190, [10, 19], [['+'], ['*']]); + yield return () => new(3267, [81, 40, 27], [['+', '+'], ['*', '+'], ['+', '*'], ['*', '*']]); + yield return () => new(292, [11, 6, 16, 20], [ + ['+', '+', '+'], + ['*', '+', '+'], + ['+', '*', '+'], + ['*', '*', '+'], + ['+', '+', '*'], + ['*', '+', '*'], + ['+', '*', '*'], + ['*', '*', '*'], + ]); + } + + public record GeneratePossibleOperatorConfigurationsTestCase(long TargetValue, List Numbers, List ExpectedCombinations); +} \ No newline at end of file diff --git a/07/BridgeRepair.Tests/PuzzleParserTests.cs b/07/BridgeRepair.Tests/PuzzleParserTests.cs new file mode 100644 index 0000000..f5a9a78 --- /dev/null +++ b/07/BridgeRepair.Tests/PuzzleParserTests.cs @@ -0,0 +1,75 @@ +namespace BridgeRepair.Tests; + +public class PuzzleParserTests +{ + private readonly PuzzleParser _parser = new(); + private readonly string[] _exampleInput = [ + "190: 10 19", + "3267: 81 40 27", + "83: 17 5", + "156: 15 6", + "7290: 6 8 6 15", + "161011: 16 10 13", + "192: 17 8 14", + "21037: 9 7 18 13", + "292: 11 6 16 20", + ]; + + private async Task GetPuzzleInput() + { + return await File.ReadAllLinesAsync(Path.Combine(AppContext.BaseDirectory, "INPUT.txt")); + } + + [Test] + public async Task SolutionPartOne_WhenGivenExampleInput_ItShouldReturnExpectedValue() + { + var result = _parser.Parse(_exampleInput).Where(e => e.IsPossible()).Sum(e => e.TestValue); + + await Assert.That(result).IsEqualTo(3749); + } + + [Test] + public async Task SolutionPartOne_WhenGivenPuzzleInput_ItShouldReturnExpectedValue() + { + var input = await GetPuzzleInput(); + var result = _parser.Parse(input).Where(e => e.IsPossible()).Sum(e => e.TestValue); + + await Assert.That(result).IsEqualTo(14711933466277); + } + + [Test] + public async Task SolutionPartTwo_WhenGivenExampleInput_ItShouldReturnExpectedValue() + { + var result = _parser.Parse(_exampleInput).Where(e => e.IsPossible(isPart2: true)).Sum(e => e.TestValue); + + await Assert.That(result).IsEqualTo(11387); + } + + [Test] + public async Task SolutionPartTwo_WhenGivenPuzzleInput_ItShouldReturnExpectedValue() + { + var input = await GetPuzzleInput(); + var result = _parser.Parse(input).Where(e => e.IsPossible(isPart2: true)).Sum(e => e.TestValue); + + await Assert.That(result).IsEqualTo(286580387663654); + } + + [Test] + public async Task Parse_WhenGivenExampleInput_ItShouldReturnExpectedList() + { + var result = _parser.Parse(_exampleInput); + + await Assert.That(result).IsEquivalentTo(new List() + { + new(190, [10, 19]), + new(3267, [81, 40, 27]), + new(83, [17, 5]), + new(156, [15, 6]), + new(7290, [6, 8, 6, 15]), + new(161011, [16, 10, 13]), + new(192, [17, 8, 14]), + new(21037, [9, 7, 18, 13]), + new(292, [11, 6, 16, 20]), + }); + } +} \ No newline at end of file diff --git a/07/BridgeRepair/Equation.cs b/07/BridgeRepair/Equation.cs new file mode 100644 index 0000000..5618405 --- /dev/null +++ b/07/BridgeRepair/Equation.cs @@ -0,0 +1,76 @@ +namespace BridgeRepair; + +// TODO: Look at what the public API should be here. +class Equation(long testValue, List numbers) +{ + public readonly long TestValue = testValue; + + public bool IsPossible(bool isPart2 = false) + { + var configs = GeneratePossibleOperatorConfigurations(isPart2).ToList(); + return configs.Select(Evaluate).Any(result => result == TestValue); + } + + public long Evaluate(char[] operators) + { + var result = numbers[0]; + + for (var i = 0; i < operators.Length; i++) + { + var currentOperator = operators[i]; + var number = numbers[i + 1]; + + if (currentOperator is '+') + { + result += number; + continue; + } + + if (currentOperator is '*') + { + result *= number; + continue; + } + + result = long.Parse(result + number.ToString()); + } + + return result; + } + + public IEnumerable GeneratePossibleOperatorConfigurations(bool isPart2 = false) + { + // TODO: make it possible to pass is operators we can use. + var numberOfPositions = numbers.Count - 1; + var config = new char[numberOfPositions]; + var numOfOperators = isPart2 ? 3 : 2; + var numberOfPossibleConfigurations = Math.Pow(numOfOperators, numberOfPositions); + + for (var i = 0; i < numberOfPossibleConfigurations; i++) + { + var j = 0; + + while (j < numberOfPositions) + { + var currentOperator = config[j]; + + if (currentOperator is '+') + { + config[j] = '*'; + break; + } + + if (isPart2 && currentOperator is '*') + { + config[j] = '|'; + break; + } + + config[j] = '+'; + j++; + } + + yield return config.ToArray(); + } + } +} \ No newline at end of file diff --git a/07/BridgeRepair/Program.cs b/07/BridgeRepair/Program.cs index 5f94bf8..eecefb9 100644 --- a/07/BridgeRepair/Program.cs +++ b/07/BridgeRepair/Program.cs @@ -1,5 +1,7 @@ using System.Diagnostics; +using BridgeRepair; + if (args.Length is 0) { Console.WriteLine("Please provide a path to the input file."); @@ -18,7 +20,9 @@ var input = await File.ReadAllLinesAsync(args[0]); var stopwatch = new Stopwatch(); stopwatch.Start(); -// TODO: Implement solution +var result = new PuzzleParser().Parse(input) + .Where(e => e.IsPossible(isPart2)) + .Sum(e => e.TestValue); stopwatch.Stop(); -Console.WriteLine($". ({stopwatch.ElapsedMilliseconds}ms)"); \ No newline at end of file +Console.WriteLine($"The sum of the test value for possible equations is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); \ No newline at end of file diff --git a/07/BridgeRepair/PuzzleParser.cs b/07/BridgeRepair/PuzzleParser.cs new file mode 100644 index 0000000..40eed06 --- /dev/null +++ b/07/BridgeRepair/PuzzleParser.cs @@ -0,0 +1,9 @@ +namespace BridgeRepair; + +class PuzzleParser +{ + public List Parse(string[] input) + { + return input.Select(s => s.ToEquation()).ToList(); + } +} \ No newline at end of file diff --git a/07/BridgeRepair/StringExtensions.cs b/07/BridgeRepair/StringExtensions.cs new file mode 100644 index 0000000..266cf5c --- /dev/null +++ b/07/BridgeRepair/StringExtensions.cs @@ -0,0 +1,14 @@ +namespace BridgeRepair; + +static class StringExtensions +{ + public static Equation ToEquation(this string line) + { + var parts = line.Split(':'); + var testValue = long.Parse(parts[0]); + var numbers = parts[1].Split(" ", StringSplitOptions.RemoveEmptyEntries) + .Select(long.Parse) + .ToList(); + return new(testValue, numbers); + } +} \ No newline at end of file diff --git a/README.md b/README.md index bf58008..51ad911 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,4 @@ dotnet build | 04 | [Problem](./04/PROBLEM.md) | [Solution](./04/CeresSearch/) | This was fun! Definitely was able to see the growth in my abilities here. | | 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/PrintQueue/) | A graph to the rescue! | | 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/GuardGallivant/) | I kind of brute forced part two... | +| 07 | [Problem](./07/PROBLEM.md) | [Solution](./07/BridgeRepair/) | Not perfect or pretty, but good enough |