diff --git a/Common/FragmentLevelSolverBase.cs b/Common/FragmentLevelSolverBase.cs
index 9aef698..ad60b14 100644
--- a/Common/FragmentLevelSolverBase.cs
+++ b/Common/FragmentLevelSolverBase.cs
@@ -3,6 +3,7 @@
using AoCLevelInputProvider;
using Parsing;
using Parsing.Schema;
+using Parsing.Data;
using Parsing.Tokenization;
public abstract class FragmentLevelSolverBase
diff --git a/Common/FulltextLevelSolverBase.cs b/Common/FulltextLevelSolverBase.cs
index bb675a2..7115465 100644
--- a/Common/FulltextLevelSolverBase.cs
+++ b/Common/FulltextLevelSolverBase.cs
@@ -3,6 +3,7 @@
using AoCLevelInputProvider;
using Parsing;
using Parsing.Schema;
+using Parsing.Data;
using Parsing.Tokenization;
public abstract class FullTextLevelSolverBase
diff --git a/Level4/Level4.csproj b/Level4/Level4.csproj
new file mode 100644
index 0000000..34709f5
--- /dev/null
+++ b/Level4/Level4.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Level4/Level4Solver.cs b/Level4/Level4Solver.cs
new file mode 100644
index 0000000..7cdde2c
--- /dev/null
+++ b/Level4/Level4Solver.cs
@@ -0,0 +1,67 @@
+namespace AoC24;
+
+using AoC24.Common;
+using AoCLevelInputProvider;
+using Parsing;
+using Parsing.Data;
+using Parsing.Schema;
+
+public class Level4Solver : FullTextLevelSolverBase
+{
+ public override int LevelNumber
+ {
+ get { return 4; }
+ }
+
+ protected override InputSchemaBuilder DefineInputSchema(InputSchemaBuilder schemaBuilder)
+ {
+ return schemaBuilder
+ .Repeat()
+ .Expect(InputType.Char)
+ .EndRepetition();
+ }
+
+ public override string SolveFirstStar()
+ {
+ var data = this.GetData()
+ .AsListRows();
+
+ var searchSequence = new List { "X", "M", "A", "S" };
+ var manipulator = DefaultTwoDimensionalManipulator.Create(data);
+ var searchResults = manipulator.FindInSet(searchSequence);
+
+ return searchResults.Count.ToString();
+ }
+
+ public override string SolveSecondStar()
+ {
+ var data = this.GetData()
+ .AsListRows();
+
+ var searchSequence = new List { "M", "A", "S" };
+ var manipulator = DefaultTwoDimensionalManipulator.Create(data);
+ var searchResults = manipulator.FindInSet(searchSequence, Direction.NE | Direction.SE | Direction.NW | Direction.SW);
+
+ var indicesComparator = (IDataIndex index1, IDataIndex index2) =>
+ {
+ return index1.GetIndices()[0] == index2.GetIndices()[0] && index1.GetIndices()[1] == index2.GetIndices()[1];
+ };
+
+ var foundMatches = 0;
+ for(int i = 0; i < searchResults.Count; i++)
+ {
+ var secondPositionA = manipulator.Move(searchResults[i].DataIndex, searchResults[i].Direction);
+ for(int j = i+1; j < searchResults.Count; j++)
+ {
+ var secondPositionB = manipulator.Move(searchResults[j].DataIndex, searchResults[j].Direction);
+ if(indicesComparator(secondPositionA, secondPositionB))
+ {
+ foundMatches++;
+ break;
+ }
+ }
+ }
+
+ return foundMatches.ToString();
+ }
+}
diff --git a/Level4/Program.cs b/Level4/Program.cs
new file mode 100644
index 0000000..f05b52f
--- /dev/null
+++ b/Level4/Program.cs
@@ -0,0 +1,24 @@
+// See https://aka.ms/new-console-template for more information
+
+using AoC24;
+
+var levelSolver = new Level4Solver();
+var solution1 = levelSolver.SolveFirstStar();
+var solution2 = levelSolver.SolveSecondStar();
+
+if (!string.IsNullOrEmpty(solution1))
+{
+ Console.WriteLine("Solution for example 1 is: " + solution1);
+}
+else
+{
+ Console.WriteLine("Example 1 has not been solved yet!");
+}
+if (!string.IsNullOrEmpty(solution2))
+{
+ Console.WriteLine("Solution for example 2 is: " + solution2);
+}
+else
+{
+ Console.WriteLine("Example 2 has not been solved yet!");
+}
diff --git a/Level5/Level5.csproj b/Level5/Level5.csproj
new file mode 100644
index 0000000..34709f5
--- /dev/null
+++ b/Level5/Level5.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Level5/Level5Solver.cs b/Level5/Level5Solver.cs
new file mode 100644
index 0000000..67bd2a4
--- /dev/null
+++ b/Level5/Level5Solver.cs
@@ -0,0 +1,252 @@
+namespace AoC24;
+
+using AoC24.Common;
+using AoCLevelInputProvider;
+using Parsing;
+using Parsing.Schema;
+
+public class Rule
+{
+ public string NumLeft { get; set; }
+ public string NumRight { get; set; }
+
+ public Rule(string numLeft, string numRight)
+ {
+ NumLeft = numLeft;
+ NumRight = numRight;
+ }
+
+ public bool IsApplicable(List inputs)
+ {
+ return inputs.Contains(NumLeft) && inputs.Contains(NumRight);
+ }
+
+ public bool IsValid(List inputs)
+ {
+ if(!IsApplicable(inputs))
+ {
+ return true;
+ }
+ for(int i = 0; i < inputs.Count; i++)
+ {
+ if(inputs[i].Equals(NumRight))
+ {
+ //Console.WriteLine("Matched NumRight:" + inputs[i]);
+ return false;
+ }
+ if(inputs[i].Equals(NumLeft))
+ {
+ //Console.WriteLine("Matched NumLeft:" + inputs[i]);
+ return true;
+ }
+ }
+ return true;
+ }
+}
+
+public class RuleSet
+{
+ public List Rules { get; set; } = new List();
+
+ public RuleSet()
+ {}
+
+ public bool IsValid(List inputs)
+ {
+ foreach(var rule in Rules)
+ {
+ if(!rule.IsValid(inputs))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+public class RuleOrderer
+{
+ private List rules;
+
+ public RuleOrderer(List rules)
+ {
+ this.rules = rules;
+ }
+
+ public bool RuleExistsThatPutsThisPageFirst(string page, string pageToCompareTo)
+ {
+ foreach (var rule in rules)
+ {
+ if(rule.NumLeft == page && rule.NumRight == pageToCompareTo)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public List OrderViaRules(List input)
+ {
+ var sortedList = new List();
+
+ foreach(string num in input)
+ {
+ if(sortedList.Count == 0)
+ {
+ sortedList.Add(num);
+ }
+ else
+ {
+ for(int i = 0; i < sortedList.Count; i++)
+ {
+ var compareNum = sortedList[i];
+ if(this.RuleExistsThatPutsThisPageFirst(num, compareNum))
+ {
+ sortedList.Insert(i, num);
+ //Console.WriteLine(string.Join(",", sortedList));
+ break;
+ }
+ if( i == sortedList.Count - 1)
+ {
+ sortedList.Add(num);
+ //Console.WriteLine(string.Join(",", sortedList));
+ break;
+ }
+ }
+ }
+ }
+
+ // Console.WriteLine("=================");
+ // Console.WriteLine("Input: " + string.Join(",", input));
+ // Console.WriteLine("Applicable rules: ");
+ // foreach(var rule in rules.ToList().OrderBy(x => x.NumLeft))
+ // {
+ // Console.WriteLine(rule.NumLeft + "|" + rule.NumRight);
+ // }
+ // Console.WriteLine("Output: " + string.Join(",", sortedList));
+ // Console.ReadLine();
+
+ return sortedList;
+ }
+}
+
+public class Level5Solver: FragmentLevelSolverBase
+{
+ public override int LevelNumber
+ {
+ get { return 5; }
+ }
+
+ protected override FragmentSchemaBuilder DefineInputSchema(FragmentSchemaBuilder schemaBuilder)
+ {
+ return schemaBuilder
+ .StartOptions()
+ .Option()
+ .Expect(InputType.Integer, "rule_left")
+ .Expect("|")
+ .Expect(InputType.Integer, "rule_right")
+ .Option()
+ .Expect(InputType.Integer, "pages")
+ .Repeat()
+ .Expect(",")
+ .Expect(InputType.Integer, "pages")
+ .EndRepetition()
+ .EndOptions();
+ }
+
+ public override string SolveFirstStar()
+ {
+ var data = this.GetData()
+ .AsFragments();
+
+ var validSequences = new List>();
+ var ruleset = new RuleSet();
+
+ foreach(var fragment in data)
+ {
+ if(fragment["rule_left"].Count > 0)
+ {
+ //Console.WriteLine("Adding rule: " + fragment["rule_left"][0] + "|" + fragment["rule_right"][0]);
+ ruleset.Rules.Add(new Rule(fragment["rule_left"][0], fragment["rule_right"][0]));
+ }
+ else
+ {
+ //Console.WriteLine(string.Join(",", fragment["pages"]));
+ if(ruleset.IsValid(fragment["pages"]))
+ {
+ //Console.WriteLine("Adding fragment: " + string.Join(",", fragment["pages"]));
+ validSequences.Add(fragment["pages"]);
+ }
+ }
+ }
+
+ var count = 0;
+
+ foreach(var sequence in validSequences)
+ {
+ var middleItem = sequence[sequence.Count/2];
+ count += int.Parse(middleItem);
+ }
+
+ return count.ToString();
+ }
+
+ public override string SolveSecondStar()
+ {
+ var data = this.GetData()
+ .AsFragments();
+
+ var invalidSequences = new List>();
+ var ruleset = new RuleSet();
+
+ foreach(var fragment in data)
+ {
+ if(fragment["rule_left"].Count > 0)
+ {
+ //Console.WriteLine("Adding rule: " + fragment["rule_left"][0] + "|" + fragment["rule_right"][0]);
+ ruleset.Rules.Add(new Rule(fragment["rule_left"][0], fragment["rule_right"][0]));
+ }
+ else
+ {
+ //Console.WriteLine(string.Join(",", fragment["pages"]));
+ if(!ruleset.IsValid(fragment["pages"]))
+ {
+ //Console.WriteLine("Adding fragment: " + string.Join(",", fragment["pages"]));
+ invalidSequences.Add(fragment["pages"]);
+ }
+ }
+ }
+
+ var orderedSequences = new List>();
+
+ foreach(var sequence in invalidSequences)
+ {
+ var applicableRules = new List();
+ foreach(var rule in ruleset.Rules)
+ {
+ if(rule.IsApplicable(sequence))
+ {
+ applicableRules.Add(rule);
+ }
+ }
+
+ var orderer = new RuleOrderer(applicableRules);
+ var orderedSequence = orderer.OrderViaRules(sequence);
+ if(!ruleset.IsValid(orderedSequence))
+ {
+ throw new Exception("badbadbad");
+ }
+ orderedSequences.Add(orderedSequence);
+ }
+
+ var count = 0;
+
+ foreach(var sequence in orderedSequences)
+ {
+ var middleItem = sequence[sequence.Count/2];
+ count += int.Parse(middleItem);
+ }
+
+ return count.ToString();
+ }
+}
diff --git a/Level5/Program.cs b/Level5/Program.cs
new file mode 100644
index 0000000..910cc6a
--- /dev/null
+++ b/Level5/Program.cs
@@ -0,0 +1,24 @@
+// See https://aka.ms/new-console-template for more information
+
+using AoC24;
+
+var levelSolver = new Level5Solver();
+var solution1 = levelSolver.SolveFirstStar();
+var solution2 = levelSolver.SolveSecondStar();
+
+if (!string.IsNullOrEmpty(solution1))
+{
+ Console.WriteLine("Solution for example 1 is: " + solution1);
+}
+else
+{
+ Console.WriteLine("Example 1 has not been solved yet!");
+}
+if (!string.IsNullOrEmpty(solution2))
+{
+ Console.WriteLine("Solution for example 2 is: " + solution2);
+}
+else
+{
+ Console.WriteLine("Example 2 has not been solved yet!");
+}
diff --git a/Level6/Level6.csproj b/Level6/Level6.csproj
new file mode 100644
index 0000000..34709f5
--- /dev/null
+++ b/Level6/Level6.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Level6/Level6Solver.cs b/Level6/Level6Solver.cs
new file mode 100644
index 0000000..b8fb47d
--- /dev/null
+++ b/Level6/Level6Solver.cs
@@ -0,0 +1,248 @@
+namespace AoC24;
+
+using AoC24.Common;
+using AoCLevelInputProvider;
+using Parsing;
+using Parsing.Data;
+using Parsing.Schema;
+
+public class LoopDetectionException : Exception
+{
+ public LoopDetectionException(string message) : base(message)
+ {}
+}
+
+public class GuardRunner
+{
+ public IDataIndex currentPosition;
+ public IDataIndex initialCurrentPosition;
+ public int TraversedTiles;
+ public IDictionary>> traversalMap = new Dictionary>>();
+ public Direction currentDirection;
+ public Direction initialDirection;
+ public List> obstacles;
+ private DefaultTwoDimensionalManipulator manipulator;
+ private DefaultDataSetIndexer indexer;
+ private List> dataSet;
+
+ public GuardRunner(IDataIndex startingPosition, string guardChar, List> obstacles, List> dataSet)
+ {
+ switch(guardChar)
+ {
+ case "^":
+ this.currentDirection = Direction.Up;
+ break;
+ case ">":
+ this.currentDirection = Direction.Right;
+ break;
+ case "v":
+ this.currentDirection = Direction.Down;
+ break;
+ case "<":
+ this.currentDirection = Direction.Left;
+ break;
+ default:
+ throw new ArgumentException("Invalid directional parameter given");
+ }
+
+ this.initialDirection = currentDirection;
+ this.currentPosition = startingPosition;
+ this.initialCurrentPosition = startingPosition;
+ this.obstacles = obstacles;
+ this.manipulator = new DefaultTwoDimensionalManipulator(dataSet);
+ this.indexer = new DefaultDataSetIndexer();
+ this.dataSet = dataSet;
+ }
+
+ private void VisitTile(int x, int y, Direction d)
+ {
+ if(!this.traversalMap.ContainsKey(x))
+ {
+ this.traversalMap[x] = new Dictionary>();
+ }
+ if(!this.traversalMap[x].ContainsKey(y))
+ {
+ this.traversalMap[x][y] = new List();
+ }
+ this.traversalMap[x][y].Add(d);
+ var dirStringList = new List();
+ foreach(var direction in this.traversalMap[x][y])
+ {
+ dirStringList.Add(direction.ToString());
+ }
+ //Console.WriteLine($"Check tile ({x},{y}): " + string.Join(",", dirStringList));
+ }
+
+ private Direction TurnRight(Direction d)
+ {
+ switch(d)
+ {
+ case Direction.Up:
+ return Direction.Right;
+ case Direction.Right:
+ return Direction.Down;
+ case Direction.Down:
+ return Direction.Left;
+ case Direction.Left:
+ return Direction.Up;
+ default:
+ throw new InvalidOperationException("This default case should never be reached!");
+ }
+ }
+
+ private bool AreEqual(DirectionalSearchResult di1, DirectionalSearchResult di2)
+ {
+ return di1.Direction == di2.Direction
+ && di1.DataIndex.GetIndices()[0] == di2.DataIndex.GetIndices()[0]
+ && di1.DataIndex.GetIndices()[1] == di2.DataIndex.GetIndices()[1];
+ }
+
+ public void Run()
+ {
+ TraversedTiles = 1;
+ currentPosition = initialCurrentPosition;
+ traversalMap = new Dictionary>>();
+ currentDirection = initialDirection;
+
+ while(true)
+ {
+ var nextPosition = currentPosition;
+
+ while(nextPosition == currentPosition)
+ {
+ var nextPositionCandidate = manipulator.Move(currentPosition, currentDirection);
+
+ if(!manipulator.IsValidIndex(nextPositionCandidate))
+ {
+ // we just went off the map
+ return;
+ }
+ // if we added an extra obstacle it will be at the beginning of the list
+ if(this.obstacles[0].DataIndex.GetIndices()[0] == nextPositionCandidate.GetIndices()[0]
+ && this.obstacles[0].DataIndex.GetIndices()[1] == nextPositionCandidate.GetIndices()[1])
+ {
+ currentDirection = TurnRight(currentDirection);
+ }
+ else if(indexer.Get(dataSet, nextPositionCandidate) == "#")
+ {
+ currentDirection = TurnRight(currentDirection);
+ }
+ else
+ {
+ nextPosition = nextPositionCandidate;
+ }
+ }
+
+ // we just moved, take into account the tile we just moved on
+ currentPosition = nextPosition;
+
+ // if we have been here before we have entered a loop
+ if(this.traversalMap.ContainsKey(currentPosition.GetIndices()[0])
+ && this.traversalMap[currentPosition.GetIndices()[0]].ContainsKey(currentPosition.GetIndices()[1])
+ && this.traversalMap[currentPosition.GetIndices()[0]][currentPosition.GetIndices()[1]].Contains(currentDirection))
+ {
+ throw new LoopDetectionException("");
+ }
+
+ VisitTile(currentPosition.GetIndices()[0], currentPosition.GetIndices()[1], currentDirection);
+ }
+ }
+}
+
+public class Level6Solver : FullTextLevelSolverBase
+{
+ public override int LevelNumber
+ {
+ get { return 6; }
+ }
+
+ protected override InputSchemaBuilder DefineInputSchema(InputSchemaBuilder schemaBuilder)
+ {
+ return schemaBuilder
+ .Repeat()
+ .Expect(InputType.Char)
+ .EndRepetition();
+ }
+
+ public override string SolveFirstStar()
+ {
+ var data = this.GetData()
+ .AsListRows();
+
+ var manipulator = DefaultTwoDimensionalManipulator.Create(data);
+ var obstacles = manipulator.FindInSet("#");
+ var possibleGuardCharacters = new List() { ">", "<", "v", "^" };
+ var obstacleList = manipulator.FindInSet("#");
+ SearchResult guard = null;
+ foreach(var gchar in possibleGuardCharacters)
+ {
+ guard = manipulator.FindInSet(gchar).SingleOrDefault();
+ if (guard != null)
+ {
+ break;
+ }
+ }
+ var dataAccessor = new DefaultDataSetIndexer();
+ var guardChar = dataAccessor.Get(data, guard.DataIndex);
+
+ var guardRunner = new GuardRunner(guard.DataIndex, guardChar, obstacleList, data);
+ guardRunner.Run();
+
+ var count = 1;
+ foreach(var xCoord in guardRunner.traversalMap.Keys)
+ {
+ count += guardRunner.traversalMap[xCoord].Count;
+ }
+
+ return count.ToString();
+ }
+
+ public override string SolveSecondStar()
+ {
+ var data = this.GetData()
+ .AsListRows();
+
+ var manipulator = DefaultTwoDimensionalManipulator.Create(data);
+ var possibleGuardCharacters = new List() { ">", "<", "v", "^" };
+ var obstacleList = manipulator.FindInSet("#");
+ SearchResult guard = null;
+ foreach(var gchar in possibleGuardCharacters)
+ {
+ guard = manipulator.FindInSet(gchar).SingleOrDefault();
+ if (guard != null)
+ {
+ break;
+ }
+ }
+ var dataAccessor = new DefaultDataSetIndexer();
+ var guardChar = dataAccessor.Get(data, guard.DataIndex);
+
+ // first determine regular taken path
+ var guardRunner = new GuardRunner(guard.DataIndex, guardChar, obstacleList, data);
+ guardRunner.Run();
+
+ // now for each visited tile, place an obstacle there and see if we run into a loop
+ int loopCount = 0;
+ foreach(var xCoord in guardRunner.traversalMap.Keys)
+ {
+ foreach(var yCoord in guardRunner.traversalMap[xCoord].Keys)
+ {
+ //Console.WriteLine($"Trying obstacle at ({xCoord},{yCoord})");
+ var newObstacleList = new List>();
+ newObstacleList.Add(new SearchResult(new DefaultPositionalDataIndex(xCoord, yCoord)));
+ newObstacleList.AddRange(obstacleList);
+ var testRunner = new GuardRunner(guard.DataIndex, guardChar, newObstacleList, data);
+ try
+ {
+ testRunner.Run();
+ }
+ catch(LoopDetectionException)
+ {
+ loopCount += 1;
+ }
+ }
+ }
+
+ return loopCount.ToString();
+ }
+}
diff --git a/Level6/Program.cs b/Level6/Program.cs
new file mode 100644
index 0000000..add5bb9
--- /dev/null
+++ b/Level6/Program.cs
@@ -0,0 +1,24 @@
+// See https://aka.ms/new-console-template for more information
+
+using AoC24;
+
+var levelSolver = new Level6Solver();
+var solution1 = levelSolver.SolveFirstStar();
+var solution2 = levelSolver.SolveSecondStar();
+
+if (!string.IsNullOrEmpty(solution1))
+{
+ Console.WriteLine("Solution for example 1 is: " + solution1);
+}
+else
+{
+ Console.WriteLine("Example 1 has not been solved yet!");
+}
+if (!string.IsNullOrEmpty(solution2))
+{
+ Console.WriteLine("Solution for example 2 is: " + solution2);
+}
+else
+{
+ Console.WriteLine("Example 2 has not been solved yet!");
+}
diff --git a/Level7/Level7.csproj b/Level7/Level7.csproj
new file mode 100644
index 0000000..34709f5
--- /dev/null
+++ b/Level7/Level7.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Level7/Level7.sln b/Level7/Level7.sln
new file mode 100644
index 0000000..e483f54
--- /dev/null
+++ b/Level7/Level7.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Level7", "Level7.csproj", "{B69CB5AF-BF4D-4425-A20B-3E95D2E0EABB}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B69CB5AF-BF4D-4425-A20B-3E95D2E0EABB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B69CB5AF-BF4D-4425-A20B-3E95D2E0EABB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B69CB5AF-BF4D-4425-A20B-3E95D2E0EABB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B69CB5AF-BF4D-4425-A20B-3E95D2E0EABB}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3F192730-CD0E-42EA-89C1-94B84247D113}
+ EndGlobalSection
+EndGlobal
diff --git a/Level7/Level7Solver.cs b/Level7/Level7Solver.cs
new file mode 100644
index 0000000..826dd43
--- /dev/null
+++ b/Level7/Level7Solver.cs
@@ -0,0 +1,239 @@
+namespace AoC24;
+
+using AoC24.Common;
+using AoCLevelInputProvider;
+using Parsing;
+using Parsing.Data;
+using Parsing.Schema;
+
+public enum Operator
+{
+ Multiply,
+ Add,
+ Concatenate,
+}
+
+public class Equation
+{
+ private List operands;
+
+ private List operators;
+
+ public Equation(List operands)
+ {
+ this.operands = new List();
+ this.operands.AddRange(operands);
+ this.operators = new List();
+ }
+
+ private Equation(List operands, List operators)
+ {
+ this.operands = new List();
+ this.operands.AddRange(operands);
+ this.operators = new List();
+ this.operators.AddRange(operators);
+ }
+
+ public void AddOperator(Operator op)
+ {
+ this.operators.Add(op);
+ }
+
+ public bool CanHoldMoreOperators()
+ {
+ return this.operators.Count < (this.operands.Count - 1);
+ }
+
+ public long Solve()
+ {
+ long result = this.operands[0];
+ for(int i = 0; i < this.operators.Count; i++)
+ {
+ switch(this.operators[i])
+ {
+ case Operator.Add:
+ result = result + (this.operands[i+1]);
+ break;
+ case Operator.Multiply:
+ result = result * (this.operands[i+1]);
+ break;
+ default:
+ throw new Exception("This should never be reached");
+ }
+ }
+ return result;
+ }
+
+ public long Solve2()
+ {
+ long result = this.operands[0];
+ for(int i = 0; i < this.operators.Count; i++)
+ {
+ switch(this.operators[i])
+ {
+ case Operator.Add:
+ result = result + (this.operands[i+1]);
+ break;
+ case Operator.Multiply:
+ result = result * (this.operands[i+1]);
+ break;
+ case Operator.Concatenate:
+ result = result * (long)Math.Pow(10, (this.operands[i+1].ToString().Length)) + this.operands[i+1];
+ break;
+ default:
+ throw new Exception("This should never be reached");
+ }
+ }
+ return result;
+ }
+
+ public Equation Clone()
+ {
+ return new Equation(this.operands, this.operators);
+ }
+}
+
+public class EquationBuilder
+{
+ public long TestValue;
+
+ private List operands;
+
+ private List operatorOptions;
+
+ private List SolvableEquations = new List();
+
+ public EquationBuilder(long testValue, params Operator[] operatorOptions)
+ {
+ this.TestValue = testValue;
+ this.operands = new List();
+ this.operatorOptions = new List();
+ this.operatorOptions.AddRange(operatorOptions);
+ }
+
+ public void AddOperand(int operand)
+ {
+ this.operands.Add(operand);
+ }
+
+ private List BuildEquationCandidates(Equation e)
+ {
+ List resultEquations = new List();
+ if(!e.CanHoldMoreOperators())
+ {
+ resultEquations.Add(e);
+ }
+ else
+ {
+ foreach(var op in this.operatorOptions)
+ {
+ var newEquation = e.Clone();
+ newEquation.AddOperator(op);
+ resultEquations.AddRange(BuildEquationCandidates(newEquation));
+ }
+ }
+ return resultEquations;
+ }
+
+ public bool FindSolutions()
+ {
+ var allPossibleEquations = BuildEquationCandidates(new Equation(this.operands));
+ foreach(var equation in allPossibleEquations)
+ {
+ if(equation.Solve() == this.TestValue)
+ {
+ this.SolvableEquations.Add(equation);
+ }
+ }
+ return this.SolvableEquations.Any();
+ }
+
+ public bool FindSolutions2()
+ {
+ var allPossibleEquations = BuildEquationCandidates(new Equation(this.operands));
+ foreach(var equation in allPossibleEquations)
+ {
+ if(equation.Solve2() == this.TestValue)
+ {
+ this.SolvableEquations.Add(equation);
+ }
+ }
+ return this.SolvableEquations.Any();
+ }
+}
+
+public class Level7Solver : FullTextLevelSolverBase
+{
+ public override int LevelNumber
+ {
+ get { return 7; }
+ }
+
+ private long ParseLong(string word)
+ {
+ return long.Parse(word.Replace(":", ""));
+ }
+
+ protected override InputSchemaBuilder DefineInputSchema(InputSchemaBuilder schemaBuilder)
+ {
+ return schemaBuilder
+ .Expect(InputType.Custom, InputType.Long, this.ParseLong)
+ .Repeat()
+ .Expect(InputType.Long)
+ .EndRepetition();
+ }
+
+ public override string SolveFirstStar()
+ {
+ var equationBuilders = this.GetData()
+ .Filter(InputType.Long)
+ .AsListRows()
+ .TransformData((List inputs) => {
+ var eb = new EquationBuilder(inputs[0], Operator.Multiply, Operator.Add);
+ for(int i = 1; i < inputs.Count; i++)
+ {
+ eb.AddOperand((int)inputs[i]);
+ }
+ return eb;
+ });
+
+ long totalSum = 0;
+
+ foreach(var equationBuilder in equationBuilders)
+ {
+ if(equationBuilder.FindSolutions())
+ {
+ totalSum += equationBuilder.TestValue;
+ }
+ }
+
+ return totalSum.ToString();
+ }
+
+ public override string SolveSecondStar()
+ {
+ var equationBuilders = this.GetData()
+ .Filter(InputType.Long)
+ .AsListRows()
+ .TransformData((List inputs) => {
+ var eb = new EquationBuilder(inputs[0], Operator.Multiply, Operator.Add, Operator.Concatenate);
+ for(int i = 1; i < inputs.Count; i++)
+ {
+ eb.AddOperand((int)inputs[i]);
+ }
+ return eb;
+ });
+
+ long totalSum = 0;
+
+ foreach(var equationBuilder in equationBuilders)
+ {
+ if(equationBuilder.FindSolutions2())
+ {
+ totalSum += equationBuilder.TestValue;
+ }
+ }
+
+ return totalSum.ToString();
+ }
+}
diff --git a/Level7/Program.cs b/Level7/Program.cs
new file mode 100644
index 0000000..8dc97d1
--- /dev/null
+++ b/Level7/Program.cs
@@ -0,0 +1,24 @@
+// See https://aka.ms/new-console-template for more information
+
+using AoC24;
+
+var levelSolver = new Level7Solver();
+var solution1 = levelSolver.SolveFirstStar();
+var solution2 = levelSolver.SolveSecondStar();
+
+if (!string.IsNullOrEmpty(solution1))
+{
+ Console.WriteLine("Solution for example 1 is: " + solution1);
+}
+else
+{
+ Console.WriteLine("Example 1 has not been solved yet!");
+}
+if (!string.IsNullOrEmpty(solution2))
+{
+ Console.WriteLine("Solution for example 2 is: " + solution2);
+}
+else
+{
+ Console.WriteLine("Example 2 has not been solved yet!");
+}