├── .gitignore ├── Aoc ├── Aoc.csproj ├── Program.cs ├── ScratchPad.cs └── appsettings.json ├── Automation ├── Automation.csproj ├── Client │ └── AocHttpClient.cs ├── Input │ └── InputProvider.cs ├── Readme │ ├── FavouriteTable.cs │ ├── FavouriteTableBuilder.cs │ ├── FavouriteTableFormatter.cs │ └── ReadmeUtils.cs └── Runner │ └── SolutionRunner.cs ├── LICENSE ├── README.md ├── Solutions ├── Attributes │ ├── Difficulty.cs │ ├── InputSpecificSolutionAttribute.cs │ ├── PuzzleInfoAttribute.cs │ └── Topics.cs ├── Common │ ├── NoSolutionException.cs │ └── SolutionBase.cs ├── Solutions.csproj ├── Y2015 │ ├── D01 │ │ └── Solution.cs │ ├── D02 │ │ ├── Box.cs │ │ └── Solution.cs │ ├── D03 │ │ └── Solution.cs │ ├── D04 │ │ └── Solution.cs │ ├── D05 │ │ └── Solution.cs │ ├── D06 │ │ └── Solution.cs │ ├── D07 │ │ └── Solution.cs │ ├── D08 │ │ └── Solution.cs │ ├── D09 │ │ └── Solution.cs │ ├── D10 │ │ └── Solution.cs │ ├── D11 │ │ └── Solution.cs │ ├── D12 │ │ └── Solution.cs │ ├── D13 │ │ └── Solution.cs │ ├── D14 │ │ └── Solution.cs │ ├── D15 │ │ └── Solution.cs │ ├── D16 │ │ └── Solution.cs │ ├── D17 │ │ ├── Cup.cs │ │ ├── Solution.cs │ │ └── State.cs │ ├── D18 │ │ └── Solution.cs │ ├── D19 │ │ └── Solution.cs │ ├── D20 │ │ └── Solution.cs │ ├── D21 │ │ ├── CombatResult.cs │ │ ├── Gear.cs │ │ ├── Resolution.cs │ │ ├── Shop.cs │ │ ├── Sim.cs │ │ ├── Solution.cs │ │ └── Unit.cs │ ├── D22 │ │ ├── GameData.cs │ │ ├── Solution.cs │ │ ├── Spell.cs │ │ ├── State.cs │ │ └── Units.cs │ ├── D23 │ │ ├── Solution.cs │ │ └── Vm.cs │ ├── D24 │ │ ├── GroupComparer.cs │ │ └── Solution.cs │ └── D25 │ │ └── Solution.cs ├── Y2016 │ ├── Common │ │ └── Vm.cs │ ├── D01 │ │ └── Solution.cs │ ├── D02 │ │ └── Solution.cs │ ├── D03 │ │ └── Solution.cs │ ├── D04 │ │ └── Solution.cs │ ├── D05 │ │ └── Solution.cs │ ├── D06 │ │ ├── Encoding.cs │ │ └── Solution.cs │ ├── D07 │ │ └── Solution.cs │ ├── D08 │ │ └── Solution.cs │ ├── D09 │ │ └── Solution.cs │ ├── D10 │ │ ├── Node.cs │ │ └── Solution.cs │ ├── D11 │ │ ├── Device.cs │ │ ├── DeviceType.cs │ │ ├── Solution.cs │ │ └── State.cs │ ├── D12 │ │ └── Solution.cs │ ├── D13 │ │ └── Solution.cs │ ├── D14 │ │ ├── HashSequence.cs │ │ └── Solution.cs │ ├── D15 │ │ ├── Disk.cs │ │ └── Solution.cs │ ├── D16 │ │ └── Solution.cs │ ├── D17 │ │ ├── PathCriteria.cs │ │ ├── Solution.cs │ │ └── State.cs │ ├── D18 │ │ └── Solution.cs │ ├── D19 │ │ └── Solution.cs │ ├── D20 │ │ ├── Enums.cs │ │ └── Solution.cs │ ├── D21 │ │ └── Solution.cs │ ├── D22 │ │ ├── PosComparer.cs │ │ ├── Solution.cs │ │ └── State.cs │ ├── D23 │ │ └── Solution.cs │ ├── D24 │ │ └── Solution.cs │ └── D25 │ │ └── Solution.cs ├── Y2017 │ ├── Common │ │ ├── KnotHash.cs │ │ └── Vm.cs │ ├── D01 │ │ └── Solution.cs │ ├── D02 │ │ └── Solution.cs │ ├── D03 │ │ ├── Solution.cs │ │ └── Spiral.cs │ ├── D04 │ │ └── Solution.cs │ ├── D05 │ │ └── Solution.cs │ ├── D06 │ │ ├── MemoryBankState.cs │ │ └── Solution.cs │ ├── D07 │ │ ├── Solution.cs │ │ └── Tower.cs │ ├── D08 │ │ ├── Instruction.cs │ │ ├── Scope.cs │ │ └── Solution.cs │ ├── D09 │ │ ├── Scope.cs │ │ └── Solution.cs │ ├── D10 │ │ └── Solution.cs │ ├── D11 │ │ └── Solution.cs │ ├── D12 │ │ └── Solution.cs │ ├── D13 │ │ ├── Scanner.cs │ │ └── Solution.cs │ ├── D14 │ │ └── Solution.cs │ ├── D15 │ │ └── Solution.cs │ ├── D16 │ │ └── Solution.cs │ ├── D17 │ │ └── Solution.cs │ ├── D18 │ │ └── Solution.cs │ ├── D19 │ │ └── Solution.cs │ ├── D20 │ │ ├── Records.cs │ │ └── Solution.cs │ ├── D21 │ │ ├── Pattern.cs │ │ └── Solution.cs │ ├── D22 │ │ ├── Solution.cs │ │ └── State.cs │ ├── D23 │ │ ├── Solution.cs │ │ └── asm.txt │ ├── D24 │ │ ├── AdapterHelper.cs │ │ ├── Comparers.cs │ │ ├── Records.cs │ │ └── Solution.cs │ └── D25 │ │ ├── Solution.cs │ │ └── TuringMachine.cs ├── Y2018 │ ├── D01 │ │ └── Solution.cs │ ├── D02 │ │ └── Solution.cs │ ├── D03 │ │ └── Solution.cs │ ├── D04 │ │ └── Solution.cs │ ├── D05 │ │ └── Solution.cs │ ├── D06 │ │ └── Solution.cs │ ├── D07 │ │ └── Solution.cs │ ├── D08 │ │ └── Solution.cs │ ├── D09 │ │ └── Solution.cs │ ├── D10 │ │ └── Solution.cs │ ├── D11 │ │ └── Solution.cs │ ├── D12 │ │ ├── Input.cs │ │ └── Solution.cs │ ├── D13 │ │ ├── CrashResponse.cs │ │ ├── Solution.cs │ │ ├── State.cs │ │ ├── TickOrderComparer.cs │ │ └── Track.cs │ ├── D14 │ │ └── Solution.cs │ ├── D15 │ │ ├── CombatResult.cs │ │ ├── GameData.cs │ │ ├── GameState.cs │ │ ├── Pathfinding.cs │ │ ├── Sim.cs │ │ ├── Solution.cs │ │ ├── SquareComparer.cs │ │ ├── Unit.cs │ │ └── UnitFactory.cs │ ├── D16 │ │ ├── Cpu.cs │ │ ├── Observation.cs │ │ └── Solution.cs │ ├── D17 │ │ ├── Materials.cs │ │ ├── Reservoir.cs │ │ └── Solution.cs │ ├── D18 │ │ └── Solution.cs │ ├── D19 │ │ ├── Cpu.cs │ │ ├── Solution.cs │ │ └── asm.txt │ ├── D20 │ │ ├── MapChars.cs │ │ └── Solution.cs │ ├── D21 │ │ ├── Cpu.cs │ │ ├── Solution.cs │ │ └── asm.txt │ ├── D22 │ │ ├── Cave.cs │ │ ├── Enums.cs │ │ ├── Records.cs │ │ └── Solution.cs │ ├── D23 │ │ ├── SearchRanking.cs │ │ └── Solution.cs │ ├── D24 │ │ ├── Comparers.cs │ │ ├── Enums.cs │ │ ├── Group.cs │ │ ├── Input.cs │ │ ├── Records.cs │ │ ├── Sim.cs │ │ ├── Solution.cs │ │ └── State.cs │ └── D25 │ │ └── Solution.cs ├── Y2019 │ ├── D01 │ │ └── Solution.cs │ ├── D02 │ │ └── Solution.cs │ ├── D03 │ │ └── Solution.cs │ ├── D04 │ │ └── Solution.cs │ ├── D05 │ │ └── Solution.cs │ ├── D06 │ │ └── Solution.cs │ ├── D07 │ │ └── Solution.cs │ ├── D08 │ │ └── Solution.cs │ ├── D09 │ │ └── Solution.cs │ ├── D10 │ │ └── Solution.cs │ ├── D11 │ │ └── Solution.cs │ ├── D12 │ │ ├── Moon.cs │ │ ├── Solution.cs │ │ ├── State.cs │ │ └── StateComp.cs │ ├── D13 │ │ ├── GameObject.cs │ │ ├── Joystick.cs │ │ ├── Screen.cs │ │ └── Solution.cs │ ├── D14 │ │ ├── Reaction.cs │ │ ├── Solution.cs │ │ └── Term.cs │ ├── D15 │ │ ├── Solution.cs │ │ └── Tile.cs │ ├── D16 │ │ └── Solution.cs │ ├── D17 │ │ ├── Records.cs │ │ ├── RoutineBuilder.cs │ │ └── Solution.cs │ ├── D18 │ │ ├── Field.cs │ │ ├── PathFinder.cs │ │ ├── Solution.cs │ │ └── State.cs │ ├── D19 │ │ └── Solution.cs │ ├── D20 │ │ ├── Enums.cs │ │ ├── PortalKey.cs │ │ ├── PortalMap.cs │ │ ├── Records.cs │ │ └── Solution.cs │ ├── D21 │ │ ├── Solution.cs │ │ └── Springdroid.cs │ ├── D22 │ │ └── Solution.cs │ ├── D23 │ │ ├── Computer.cs │ │ ├── Nat.cs │ │ ├── Network.cs │ │ ├── NetworkAwaiter.cs │ │ ├── Packet.cs │ │ └── Solution.cs │ ├── D24 │ │ ├── GridType.cs │ │ └── Solution.cs │ ├── D25 │ │ ├── Cheats.cs │ │ └── Solution.cs │ └── IntCode │ │ ├── Instruction.cs │ │ ├── IntCodeSolution.cs │ │ ├── IntCodeVm.cs │ │ ├── IntCodeVmFactory.cs │ │ ├── IntCodeVmOperations.cs │ │ ├── OpCode.cs │ │ └── ParameterMode.cs ├── Y2020 │ ├── D01 │ │ └── Solution.cs │ ├── D02 │ │ └── Solution.cs │ ├── D03 │ │ └── Solution.cs │ ├── D04 │ │ └── Solution.cs │ ├── D05 │ │ └── Solution.cs │ ├── D06 │ │ └── Solution.cs │ ├── D07 │ │ ├── BagContent.cs │ │ └── Solution.cs │ ├── D08 │ │ ├── Machine.cs │ │ └── Solution.cs │ ├── D09 │ │ ├── Solution.cs │ │ └── Sum.cs │ ├── D10 │ │ └── Solution.cs │ ├── D11 │ │ ├── Concern.cs │ │ ├── SeatMap.cs │ │ └── Solution.cs │ ├── D12 │ │ └── Solution.cs │ ├── D13 │ │ └── Solution.cs │ ├── D14 │ │ ├── Machine.cs │ │ ├── MaskFloating.cs │ │ ├── MaskSimple.cs │ │ └── Solution.cs │ ├── D15 │ │ └── Solution.cs │ ├── D16 │ │ └── Solution.cs │ ├── D17 │ │ └── Solution.cs │ ├── D18 │ │ ├── Operators.cs │ │ └── Solution.cs │ ├── D19 │ │ └── Solution.cs │ ├── D20 │ │ ├── EdgeFingerprint.cs │ │ ├── SeaMonster.cs │ │ ├── Solution.cs │ │ └── Tile.cs │ ├── D21 │ │ ├── Food.cs │ │ └── Solution.cs │ ├── D22 │ │ ├── Deck.cs │ │ └── Solution.cs │ ├── D23 │ │ └── Solution.cs │ ├── D24 │ │ └── Solution.cs │ └── D25 │ │ └── Solution.cs ├── Y2021 │ ├── D01 │ │ └── Solution.cs │ ├── D02 │ │ └── Solution.cs │ ├── D03 │ │ ├── BitCriteria.cs │ │ └── Solution.cs │ ├── D04 │ │ ├── BingoCard.cs │ │ ├── BingoData.cs │ │ └── Solution.cs │ ├── D05 │ │ └── Solution.cs │ ├── D06 │ │ └── Solution.cs │ ├── D07 │ │ └── Solution.cs │ ├── D08 │ │ ├── DisplayObservation.cs │ │ └── Solution.cs │ ├── D09 │ │ └── Solution.cs │ ├── D10 │ │ ├── Solution.cs │ │ └── SyntaxChecker.cs │ ├── D11 │ │ ├── OctopusGrid.cs │ │ └── Solution.cs │ ├── D12 │ │ ├── PathFinder.cs │ │ └── Solution.cs │ ├── D13 │ │ ├── FoldType.cs │ │ ├── Origami.cs │ │ └── Solution.cs │ ├── D14 │ │ ├── Rule.cs │ │ └── Solution.cs │ ├── D15 │ │ └── Solution.cs │ ├── D16 │ │ ├── Operator.cs │ │ ├── Packet.cs │ │ ├── Section.cs │ │ └── Solution.cs │ ├── D17 │ │ └── Solution.cs │ ├── D18 │ │ ├── Element.cs │ │ ├── SnailfishParser.cs │ │ └── Solution.cs │ ├── D19 │ │ ├── Map.cs │ │ ├── Records.cs │ │ └── Solution.cs │ ├── D20 │ │ └── Solution.cs │ ├── D21 │ │ ├── DeterministicDie.cs │ │ ├── Records.cs │ │ └── Solution.cs │ ├── D22 │ │ └── Solution.cs │ ├── D23 │ │ ├── Field.cs │ │ ├── Input.cs │ │ ├── Move.cs │ │ ├── SideRoom.cs │ │ ├── Solution.cs │ │ └── State.cs │ ├── D24 │ │ ├── Alu.cs │ │ └── Solution.cs │ └── D25 │ │ └── Solution.cs ├── Y2022 │ ├── D01 │ │ └── Solution.cs │ ├── D02 │ │ ├── RockPaperScissorsChoice.cs │ │ ├── RockPaperScissorsHelper.cs │ │ ├── RockPaperScissorsResult.cs │ │ └── Solution.cs │ ├── D03 │ │ └── Solution.cs │ ├── D04 │ │ └── Solution.cs │ ├── D05 │ │ ├── CraneInstruction.cs │ │ ├── CraneOperator.cs │ │ ├── CranePlan.cs │ │ ├── PickupMode.cs │ │ └── Solution.cs │ ├── D06 │ │ └── Solution.cs │ ├── D07 │ │ ├── Command.cs │ │ ├── ConsoleParser.cs │ │ └── Solution.cs │ ├── D08 │ │ └── Solution.cs │ ├── D09 │ │ └── Solution.cs │ ├── D10 │ │ ├── Cpu.cs │ │ └── Solution.cs │ ├── D11 │ │ ├── Monkey.cs │ │ ├── MonkeyData.cs │ │ ├── Operator.cs │ │ └── Solution.cs │ ├── D12 │ │ └── Solution.cs │ ├── D13 │ │ ├── ComparisonResult.cs │ │ ├── IntegerPacketElement.cs │ │ ├── ListPacketElement.cs │ │ ├── PacketComparator.cs │ │ ├── PacketElement.cs │ │ ├── PacketPair.cs │ │ ├── PacketParser.cs │ │ └── Solution.cs │ ├── D14 │ │ └── Solution.cs │ ├── D15 │ │ ├── Reporting.cs │ │ └── Solution.cs │ ├── D16 │ │ ├── Solution.cs │ │ ├── Strategy.cs │ │ ├── StrategyFinder.cs │ │ └── ValveData.cs │ ├── D17 │ │ ├── JetPattern.cs │ │ ├── RockSource.cs │ │ ├── Rocks.cs │ │ └── Solution.cs │ ├── D18 │ │ └── Solution.cs │ ├── D19 │ │ ├── Blueprint.cs │ │ ├── Inventory.cs │ │ ├── Materials.cs │ │ └── Solution.cs │ ├── D20 │ │ └── Solution.cs │ ├── D21 │ │ ├── AlgebraicOperation.cs │ │ ├── Expression.cs │ │ ├── ExpressionFactory.cs │ │ ├── Operator.cs │ │ └── Solution.cs │ ├── D22 │ │ ├── Instruction.cs │ │ ├── MapData.cs │ │ ├── MoveMode.cs │ │ ├── Solution.cs │ │ └── Square.cs │ ├── D23 │ │ ├── MovePreferences.cs │ │ └── Solution.cs │ ├── D24 │ │ ├── Blizzards.cs │ │ ├── Solution.cs │ │ ├── Storm.cs │ │ └── Terrain.cs │ └── D25 │ │ └── Solution.cs ├── Y2023 │ ├── D01 │ │ └── Solution.cs │ ├── D02 │ │ └── Solution.cs │ ├── D03 │ │ └── Solution.cs │ ├── D04 │ │ └── Solution.cs │ ├── D05 │ │ ├── Almanac.cs │ │ ├── MapEntry.cs │ │ ├── MapTable.cs │ │ └── Solution.cs │ ├── D06 │ │ └── Solution.cs │ ├── D07 │ │ ├── Deck.cs │ │ ├── Hand.cs │ │ └── Solution.cs │ ├── D08 │ │ └── Solution.cs │ ├── D09 │ │ └── Solution.cs │ ├── D10 │ │ └── Solution.cs │ ├── D11 │ │ └── Solution.cs │ ├── D12 │ │ ├── Solution.cs │ │ └── State.cs │ ├── D13 │ │ └── Solution.cs │ ├── D14 │ │ └── Solution.cs │ ├── D15 │ │ ├── Box.cs │ │ └── Solution.cs │ ├── D16 │ │ └── Solution.cs │ ├── D17 │ │ └── Solution.cs │ ├── D18 │ │ └── Solution.cs │ ├── D19 │ │ ├── Rule.cs │ │ └── Solution.cs │ ├── D20 │ │ ├── ModuleType.cs │ │ ├── Network.cs │ │ ├── Solution.cs │ │ └── Trace.cs │ ├── D21 │ │ └── Solution.cs │ ├── D22 │ │ ├── Brick.cs │ │ └── Solution.cs │ ├── D23 │ │ └── Solution.cs │ ├── D24 │ │ ├── Aabb2.cs │ │ ├── Ray3.cs │ │ ├── Solution.cs │ │ └── Vec3.cs │ └── D25 │ │ └── Solution.cs └── Y2024 │ ├── D01 │ └── Solution.cs │ ├── D02 │ └── Solution.cs │ ├── D03 │ └── Solution.cs │ ├── D04 │ └── Solution.cs │ ├── D05 │ └── Solution.cs │ ├── D06 │ └── Solution.cs │ ├── D07 │ └── Solution.cs │ ├── D08 │ └── Solution.cs │ ├── D09 │ ├── Disk.cs │ └── Solution.cs │ ├── D10 │ └── Solution.cs │ ├── D11 │ └── Solution.cs │ ├── D12 │ └── Solution.cs │ ├── D13 │ └── Solution.cs │ ├── D14 │ └── Solution.cs │ ├── D15 │ └── Solution.cs │ ├── D16 │ └── Solution.cs │ ├── D17 │ ├── Solution.cs │ ├── Vm.cs │ └── asm.text │ ├── D18 │ └── Solution.cs │ ├── D19 │ └── Solution.cs │ ├── D20 │ └── Solution.cs │ ├── D21 │ ├── Pad.cs │ └── Solution.cs │ ├── D22 │ └── Solution.cs │ ├── D23 │ └── Solution.cs │ ├── D24 │ └── Solution.cs │ └── D25 │ └── Solution.cs ├── Utilities.Tests ├── Collections │ ├── CircularBuffer.Tests.cs │ ├── CircularLinkedList.Tests.cs │ ├── DefaultDict.Tests.cs │ └── DisjointSet.Tests.cs ├── Extensions │ ├── NumberExtensions.Tests.cs │ ├── RegexExtensions.Tests.cs │ └── StringExtensions.Tests.cs ├── Geometry │ └── Euclidean │ │ ├── Grid2D.Factory.Tests.cs │ │ ├── Grid2D.Tests.cs │ │ ├── Grid2D.Transforms.Tests.cs │ │ └── Rot3D.Tests.cs ├── Language │ └── ContextFree │ │ └── CykParser.Tests.cs ├── Numerics │ ├── LinearSolver.Tests.cs │ ├── Numerics.Tests.cs │ └── Range.Tests.cs └── Utilities.Tests.csproj ├── Utilities ├── Collections │ ├── CircularBuffer.cs │ ├── CircularLinkedList.cs │ ├── CircularLinkedListNode.cs │ ├── DefaultDict.cs │ ├── DisjointSet.cs │ └── DisjointSetNode.cs ├── Extensions │ ├── CollectionExtensions.cs │ ├── NumberExtensions.cs │ ├── RegexExtensions.cs │ └── StringExtensions.cs ├── Geometry │ ├── Euclidean │ │ ├── Aabb2D.cs │ │ ├── Aabb3D.cs │ │ ├── Aabb4D.cs │ │ ├── Axis.cs │ │ ├── Degrees.cs │ │ ├── Grid2D.Factory.cs │ │ ├── Grid2D.Transforms.cs │ │ ├── Grid2D.cs │ │ ├── Metric.cs │ │ ├── Octree.cs │ │ ├── Origin.cs │ │ ├── Pose2D.cs │ │ ├── Quaternion.cs │ │ ├── Rot3D.cs │ │ ├── Vec2D.cs │ │ ├── Vec3D.cs │ │ ├── Vec4D.cs │ │ └── VecThrowHelper.cs │ └── Hexagonal │ │ └── Hex.cs ├── Graph │ ├── BinaryTree.cs │ ├── BinaryTreeNode.cs │ ├── BinaryTreePrinter.cs │ ├── DirectedGraph.cs │ └── GraphHelper.cs ├── Hashing │ └── Md5Provider.cs ├── Language │ └── ContextFree │ │ ├── CnfConverter.cs │ │ ├── CykParser.cs │ │ ├── Grammar.Utilities.cs │ │ ├── Grammar.cs │ │ └── Production.cs ├── Numerics │ ├── LinearSolver.cs │ ├── Numerics.cs │ └── Range.cs └── Utilities.csproj ├── advent-of-code.sln ├── advent-of-code.sln.DotSettings └── global.json /Aoc/Aoc.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | Always 14 | 15 | 16 | Always 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Aoc/ScratchPad.cs: -------------------------------------------------------------------------------- 1 | namespace Aoc; 2 | 3 | public static class ScratchPad 4 | { 5 | public static void Execute() 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /Aoc/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "UserSession": "", 3 | "InputCachePath": "Inputs" 4 | } -------------------------------------------------------------------------------- /Automation/Automation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Automation/Readme/FavouriteTable.cs: -------------------------------------------------------------------------------- 1 | using Solutions.Attributes; 2 | 3 | namespace Automation.Readme; 4 | 5 | public sealed class FavouriteTable(int year, IEnumerable entries) 6 | { 7 | public readonly record struct Entry(string Title, int Year, int Day, Topics Topics, Difficulty Difficulty); 8 | 9 | public int Year { get; } = year; 10 | public IReadOnlyList Entries { get; } = new List(entries); 11 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Trevor Barker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Solutions/Attributes/Difficulty.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Attributes; 2 | 3 | public enum Difficulty 4 | { 5 | Easy, 6 | Medium, 7 | Hard 8 | } -------------------------------------------------------------------------------- /Solutions/Attributes/InputSpecificSolutionAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Attributes; 2 | 3 | [AttributeUsage(validOn: AttributeTargets.Class)] 4 | public sealed class InputSpecificSolutionAttribute(string message) : Attribute 5 | { 6 | private const string DefaultMessage = 7 | "This solution implementation is input specific, and may not work on all inputs"; 8 | 9 | public string Message { get; } = message; 10 | 11 | public InputSpecificSolutionAttribute() : this(DefaultMessage) 12 | { 13 | } 14 | } -------------------------------------------------------------------------------- /Solutions/Attributes/PuzzleInfoAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Attributes; 2 | 3 | [AttributeUsage(validOn: AttributeTargets.Class)] 4 | public sealed class PuzzleInfoAttribute(string title, Topics topics, Difficulty difficulty, bool favourite = false) 5 | : Attribute 6 | { 7 | public string Title { get; } = title; 8 | public Topics Topics { get; } = topics; 9 | public Difficulty Difficulty { get; } = difficulty; 10 | public bool Favourite { get; } = favourite; 11 | } -------------------------------------------------------------------------------- /Solutions/Attributes/Topics.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Attributes; 2 | 3 | [Flags] 4 | public enum Topics 5 | { 6 | None = 0, 7 | Vectors = 1 << 0, 8 | Graphs = 1 << 1, 9 | Recursion = 1 << 2, 10 | StringParsing = 1 << 3, 11 | RegularExpressions = 1 << 4, 12 | Assembly = 1 << 5, 13 | Math = 1 << 6, 14 | IntCode = 1 << 7, 15 | BitwiseOperations = 1 << 8, 16 | FormalLanguage = 1 << 9, 17 | Hashing = 1 << 10, 18 | Simulation = 1 << 11 19 | } -------------------------------------------------------------------------------- /Solutions/Common/NoSolutionException.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Common; 2 | 3 | public sealed class NoSolutionException : Exception 4 | { 5 | private const string NoSolutionErrorText = "No solution exists"; 6 | 7 | public NoSolutionException() : base(NoSolutionErrorText) 8 | { 9 | } 10 | 11 | public NoSolutionException(string message) : base(message) 12 | { 13 | } 14 | } -------------------------------------------------------------------------------- /Solutions/Solutions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Solutions/Y2015/D01/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D01; 2 | 3 | [PuzzleInfo("Not Quite Lisp", Topics.StringParsing, Difficulty.Easy)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var instructions = GetInputText(); 9 | return part switch 10 | { 11 | 1 => FollowInstructions(instructions, basement: false), 12 | 2 => FollowInstructions(instructions, basement: true), 13 | _ => PuzzleNotSolvedString 14 | }; 15 | } 16 | 17 | private static int FollowInstructions(string instructions, bool basement) 18 | { 19 | var floor = 0; 20 | var index = 0; 21 | 22 | while (index < instructions.Length) 23 | { 24 | floor += instructions[index++] == '(' ? 1 : -1; 25 | 26 | if (basement && floor == -1) 27 | { 28 | return index; 29 | } 30 | } 31 | 32 | return floor; 33 | } 34 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D02/Box.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D02; 2 | 3 | public readonly struct Box(long l, long w, long h) 4 | { 5 | private long Lw => l * w; 6 | private long Wh => w * h; 7 | private long Lh => l * h; 8 | private long MinFace => new[] { Lw, Wh, Lh }.Min(); 9 | private long MinPerimeter => new[] { 2 * l + 2 * w, 2 * w + 2 * h, 2 * l + 2 * h }.Min(); 10 | private long Volume => l * w * h; 11 | 12 | public long PaperReq => 2L * Lw + 2L * Wh + 2L * Lh + MinFace; 13 | public long RibbonReq => MinPerimeter + Volume; 14 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D02/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2015.D02; 4 | 5 | [PuzzleInfo("I Was Told There Would Be No Math", Topics.Math, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var input = GetInputLines(); 11 | var boxes = input.Select(ParseBox); 12 | 13 | return part switch 14 | { 15 | 1 => boxes.Sum(box => box.PaperReq), 16 | 2 => boxes.Sum(box => box.RibbonReq), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static Box ParseBox(string line) 22 | { 23 | var dims = line.ParseLongs(); 24 | return new Box( 25 | l: dims[0], 26 | w: dims[1], 27 | h: dims[2]); 28 | } 29 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D03/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2015.D03; 4 | 5 | [PuzzleInfo("Perfectly Spherical Houses in a Vacuum", Topics.Vectors, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | private static readonly Dictionary Map = new() 9 | { 10 | { '^', Vec2D.Up }, 11 | { 'v', Vec2D.Down }, 12 | { '<', Vec2D.Left }, 13 | { '>', Vec2D.Right } 14 | }; 15 | 16 | public override object Run(int part) 17 | { 18 | var steps = GetInputText(); 19 | return part switch 20 | { 21 | 1 => CountDistinctAlone(steps), 22 | 2 => CountDistinctPair(steps), 23 | _ => PuzzleNotSolvedString 24 | }; 25 | } 26 | 27 | private static int CountDistinctAlone(string steps) 28 | { 29 | var pos = Vec2D.Zero; 30 | var set = new HashSet(collection: [Vec2D.Zero]); 31 | 32 | foreach (var step in steps) 33 | { 34 | pos += Map[step]; 35 | set.Add(pos); 36 | } 37 | 38 | return set.Count; 39 | } 40 | 41 | private static int CountDistinctPair(string steps) 42 | { 43 | var set = new HashSet(collection: [Vec2D.Zero]); 44 | var agents = new Dictionary 45 | { 46 | { 0, Vec2D.Zero }, 47 | { 1, Vec2D.Zero } 48 | }; 49 | 50 | for (var i = 0; i < steps.Length; i++) 51 | { 52 | agents[i % 2] += Map[steps[i]]; 53 | set.Add(agents[i % 2]); 54 | } 55 | 56 | return set.Count; 57 | } 58 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D04/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Hashing; 2 | 3 | namespace Solutions.Y2015.D04; 4 | 5 | [PuzzleInfo("The Ideal Stocking Stuffer", Topics.Hashing, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var key = GetInputText(); 11 | return part switch 12 | { 13 | 1 => FindHashSeed(key, zeroes: 5), 14 | 2 => FindHashSeed(key, zeroes: 6), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static int FindHashSeed(string key, int zeroes) 20 | { 21 | using var provider = new Md5Provider(); 22 | var hash = string.Empty; 23 | var target = new string(c: '0', zeroes); 24 | var value = 0; 25 | 26 | while (!hash.StartsWith(target)) 27 | { 28 | hash = provider.GetHashHex($"{key}{++value}"); 29 | } 30 | 31 | return value; 32 | } 33 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D05/Solution.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Solutions.Y2015.D05; 4 | 5 | [PuzzleInfo("Doesn't He Have Intern-Elves For This?", Topics.RegularExpressions, Difficulty.Medium, favourite: true)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | private static readonly Regex NiceA = new(pattern:@"^(?=.*(.)\1)(?!.*(ab|cd|pq|xy))(.*[aeiou]){3,}"); 9 | private static readonly Regex NiceB = new(pattern:@"^(?=.*(.)(.).*\1\2).*(.).\3"); 10 | 11 | public override object Run(int part) 12 | { 13 | var strings = GetInputLines(); 14 | return part switch 15 | { 16 | 1 => strings.Count(NiceA.IsMatch), 17 | 2 => strings.Count(NiceB.IsMatch), 18 | _ => PuzzleNotSolvedString 19 | }; 20 | } 21 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D08/Solution.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Solutions.Y2015.D08; 4 | 5 | [PuzzleInfo("Matchsticks", Topics.RegularExpressions, Difficulty.Medium, favourite: true)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var strings = GetInputLines(); 11 | return part switch 12 | { 13 | 1 => strings.Sum(raw => raw.Length - ToInMemory(raw).Length), 14 | 2 => strings.Sum(raw => ToEscaped(raw).Length - raw.Length), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static string ToInMemory(string str) 20 | { 21 | str = Regex.Replace(str, pattern: @"^""", replacement: ""); 22 | str = Regex.Replace(str, pattern: @"""$", replacement: ""); 23 | str = Regex.Replace(str, pattern: @"\\\\", replacement: "\\"); 24 | str = Regex.Replace(str, pattern: @"\\""", replacement: "\""); 25 | str = Regex.Replace(str, pattern: @"\\x[a-f0-9][a-f0-9]", replacement: "U"); 26 | 27 | return str; 28 | } 29 | 30 | private static string ToEscaped(string str) 31 | { 32 | str = Regex.Replace(str, pattern: @"\\\\(?!\"")", replacement: "\\\\\\\\"); 33 | str = Regex.Replace(str, pattern: @"\\""", replacement: "\\\\\\\""); 34 | str = Regex.Replace(str, pattern: @"\\x[a-f0-9][a-f0-9]", replacement: "\\\\xUU"); 35 | str = Regex.Replace(str, pattern: @"^""", replacement: "\"\\\""); 36 | str = Regex.Replace(str, pattern: @"""$", replacement: "\"\\\""); 37 | 38 | return str; 39 | } 40 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D10/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2015.D10; 4 | 5 | [PuzzleInfo("Elves Look, Elves Say", Topics.None, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var input = GetInputText(); 11 | var sequence = input 12 | .Select(StringExtensions.AsDigit) 13 | .ToList(); 14 | 15 | return part switch 16 | { 17 | 1 => LookAndSay(sequence, rounds: 40), 18 | 2 => LookAndSay(sequence, rounds: 50), 19 | _ => PuzzleNotSolvedString 20 | }; 21 | } 22 | 23 | private static int LookAndSay(List sequence, int rounds) 24 | { 25 | for (var i = 0; i < rounds; i++) 26 | { 27 | sequence = LookAndSay(sequence); 28 | } 29 | 30 | return sequence.Count; 31 | } 32 | 33 | private static List LookAndSay(List sequence) 34 | { 35 | var next = new List(); 36 | var prev = sequence[0]; 37 | var count = 1; 38 | 39 | for (var i = 1; i < sequence.Count; i++) 40 | { 41 | if (sequence[i] == prev) 42 | { 43 | count++; 44 | continue; 45 | } 46 | 47 | next.Add(count); 48 | next.Add(prev); 49 | prev = sequence[i]; 50 | count = 1; 51 | } 52 | 53 | next.Add(count); 54 | next.Add(prev); 55 | 56 | return next; 57 | } 58 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D17/Cup.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D17; 2 | 3 | public readonly record struct Cup(int Id, int Size); -------------------------------------------------------------------------------- /Solutions/Y2015/D17/State.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2015.D17; 4 | 5 | public readonly struct State : IEquatable 6 | { 7 | private readonly string _key; 8 | 9 | public HashSet Unused { get; } 10 | public int NumUsed { get; } 11 | public int TotalVolume { get; } 12 | 13 | public State(HashSet unused, int numUsed, int totalVolume) 14 | { 15 | _key = string.Join(',', unused.OrderBy(c => c.Id)); 16 | Unused = unused; 17 | NumUsed = numUsed; 18 | TotalVolume = totalVolume; 19 | } 20 | 21 | public State AfterUsing(Cup cup) 22 | { 23 | return new State( 24 | unused: Unused.Except(cup).ToHashSet(), 25 | numUsed: NumUsed + 1, 26 | totalVolume: TotalVolume + cup.Size); 27 | } 28 | 29 | public bool Equals(State other) 30 | { 31 | return _key == other._key; 32 | } 33 | 34 | public override bool Equals(object? obj) 35 | { 36 | return obj is State other && Equals(other); 37 | } 38 | 39 | public override int GetHashCode() 40 | { 41 | return _key.GetHashCode(); 42 | } 43 | 44 | public static bool operator ==(State left, State right) 45 | { 46 | return left.Equals(right); 47 | } 48 | 49 | public static bool operator !=(State left, State right) 50 | { 51 | return !left.Equals(right); 52 | } 53 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D20/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D20; 2 | 3 | [PuzzleInfo("Infinite Elves and Infinite Houses", Topics.Math, Difficulty.Medium, favourite: true)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var input = GetInputText(); 9 | var threshold = int.Parse(input); 10 | 11 | return part switch 12 | { 13 | 1 => FindFirstHouse(threshold, factor: 10, limit: int.MaxValue), 14 | 2 => FindFirstHouse(threshold, factor: 11, limit: 50), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static int FindFirstHouse(int threshold, int factor, int limit) 20 | { 21 | // Compute an upperbound of houses to compute, then cache the number of presents for all 22 | // houses from 1 to upperbound. Use sieve-like method to compute sum of divisors: 23 | // 24 | var upperBound = threshold / factor + 1; 25 | var presentCounts = new int[upperBound + 1]; 26 | 27 | for (var i = 1; i <= upperBound; i++) 28 | { 29 | var delivered = 0; 30 | for (var j = i; j <= upperBound && delivered < limit; j += i) 31 | { 32 | presentCounts[j] += factor * i; 33 | delivered++; 34 | } 35 | } 36 | 37 | for (var i = 1; i < presentCounts.Length; i++) 38 | { 39 | if (presentCounts[i] >= threshold) 40 | { 41 | return i; 42 | } 43 | } 44 | 45 | throw new NoSolutionException(); 46 | } 47 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D21/CombatResult.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D21; 2 | 3 | public readonly record struct CombatResult(Resolution Resolution, Unit Player); -------------------------------------------------------------------------------- /Solutions/Y2015/D21/Gear.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D21; 2 | 3 | public readonly record struct Gear(int Cost, int Damage, int Armor); -------------------------------------------------------------------------------- /Solutions/Y2015/D21/Resolution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D21; 2 | 3 | public enum Resolution 4 | { 5 | Win, 6 | Lose 7 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D21/Shop.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D21; 2 | 3 | public static class Shop 4 | { 5 | public static readonly HashSet Weapons = 6 | [ 7 | new Gear(Cost: 8, Damage: 4, Armor: 0), 8 | new Gear(Cost: 10, Damage: 5, Armor: 0), 9 | new Gear(Cost: 25, Damage: 6, Armor: 0), 10 | new Gear(Cost: 40, Damage: 7, Armor: 0), 11 | new Gear(Cost: 74, Damage: 8, Armor: 0) 12 | ]; 13 | 14 | public static readonly HashSet Armor = 15 | [ 16 | new Gear(Cost: 13, Damage: 0, Armor: 1), 17 | new Gear(Cost: 31, Damage: 0, Armor: 2), 18 | new Gear(Cost: 53, Damage: 0, Armor: 3), 19 | new Gear(Cost: 75, Damage: 0, Armor: 4), 20 | new Gear(Cost: 102, Damage: 0, Armor: 5) 21 | ]; 22 | 23 | public static readonly HashSet Rings = 24 | [ 25 | new Gear(Cost: 25, Damage: 1, Armor: 0), 26 | new Gear(Cost: 50, Damage: 2, Armor: 0), 27 | new Gear(Cost: 100, Damage: 3, Armor: 0), 28 | new Gear(Cost: 20, Damage: 0, Armor: 1), 29 | new Gear(Cost: 40, Damage: 0, Armor: 2), 30 | new Gear(Cost: 80, Damage: 0, Armor: 3) 31 | ]; 32 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D21/Sim.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D21; 2 | 3 | public static class Sim 4 | { 5 | public static CombatResult Run(Unit player, Unit enemy) 6 | { 7 | while (!player.Dead && !enemy.Dead) 8 | { 9 | enemy = enemy.InflictDamage(Math.Max(1, player.Damage - enemy.Armor)); 10 | 11 | if (!enemy.Dead) 12 | { 13 | player = player.InflictDamage(Math.Max(1, enemy.Damage - player.Armor)); 14 | } 15 | } 16 | 17 | return new CombatResult( 18 | Resolution: player.Dead ? Resolution.Lose : Resolution.Win, 19 | Player: player); 20 | } 21 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D21/Unit.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D21; 2 | 3 | public readonly struct Unit 4 | { 5 | public int Damage { get; } 6 | public int Armor { get; } 7 | public int GearCost { get; } 8 | public int Hp { get; } 9 | 10 | public bool Dead => Hp <= 0; 11 | 12 | private Unit(int damage, int armor, int gearCost, int hp) 13 | { 14 | Damage = damage; 15 | Armor = armor; 16 | GearCost = gearCost; 17 | Hp = hp; 18 | } 19 | 20 | public Unit InflictDamage(int damage) 21 | { 22 | return new Unit( 23 | damage: Damage, 24 | armor: Armor, 25 | gearCost: GearCost, 26 | hp: Math.Max(0, Hp - damage)); 27 | } 28 | 29 | public static Unit Spawn(ICollection gear, int hp) 30 | { 31 | return new Unit( 32 | damage: gear.Sum(g => g.Damage), 33 | armor: gear.Sum(g => g.Armor), 34 | gearCost: gear.Sum(g => g.Cost), 35 | hp: hp); 36 | } 37 | 38 | public static Unit Spawn(int damage, int armor, int hp) 39 | { 40 | return new Unit(damage, armor, gearCost: 0, hp); 41 | } 42 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D22/GameData.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D22; 2 | 3 | public static class GameData 4 | { 5 | public static readonly HashSet Spells = 6 | [ 7 | Spell.MagicMissile, 8 | Spell.Drain, 9 | Spell.Shield, 10 | Spell.Poison, 11 | Spell.Recharge 12 | ]; 13 | 14 | public static readonly Dictionary SpellCosts = new() 15 | { 16 | { Spell.MagicMissile, 53 }, 17 | { Spell.Drain, 73 }, 18 | { Spell.Shield, 113 }, 19 | { Spell.Poison, 173 }, 20 | { Spell.Recharge, 229 } 21 | }; 22 | 23 | public static readonly Dictionary SpellDurations = new() 24 | { 25 | { Spell.MagicMissile, 0 }, 26 | { Spell.Drain, 0 }, 27 | { Spell.Shield, 6 }, 28 | { Spell.Poison, 6 }, 29 | { Spell.Recharge, 5 } 30 | }; 31 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D22/Spell.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D22; 2 | 3 | public enum Spell 4 | { 5 | MagicMissile, 6 | Drain, 7 | Shield, 8 | Poison, 9 | Recharge 10 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D22/Units.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D22; 2 | 3 | public readonly record struct Boss(int Hp, int Dmg); 4 | public readonly record struct Wizard(int Hp, int Mana, int Armor); -------------------------------------------------------------------------------- /Solutions/Y2015/D23/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D23; 2 | 3 | [PuzzleInfo("Opening the Turing Lock", Topics.Assembly|Topics.Simulation, Difficulty.Easy)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | return part switch 9 | { 10 | 1 => Emulate(a: 0L), 11 | 2 => Emulate(a: 1L), 12 | _ => PuzzleNotSolvedString 13 | }; 14 | } 15 | 16 | private long Emulate(long a) 17 | { 18 | var program = GetInputLines(); 19 | var vm = new Vm { ['a'] = a }; 20 | 21 | vm.Run(program); 22 | return vm['b']; 23 | } 24 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D24/GroupComparer.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2015.D24; 2 | 3 | public sealed class GroupComparer : IComparer?> 4 | { 5 | public static GroupComparer Instance { get; } = new(); 6 | 7 | private GroupComparer() 8 | { 9 | } 10 | 11 | public int Compare(HashSet? x, HashSet? y) 12 | { 13 | if (x!.Count != y!.Count) 14 | { 15 | return x.Count.CompareTo(y.Count); 16 | } 17 | 18 | var xS = x.Aggregate(seed: 1L, (i, j) => i * j); 19 | var yS = y.Aggregate(seed: 1L, (i, j) => i * j); 20 | 21 | return xS.CompareTo(yS); 22 | } 23 | } -------------------------------------------------------------------------------- /Solutions/Y2015/D25/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2015.D25; 4 | 5 | [PuzzleInfo("Let It Snow", Topics.Math, Difficulty.Medium)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override int Parts => 1; 9 | 10 | public override object Run(int part) 11 | { 12 | return part switch 13 | { 14 | 1 => GetCode(), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private ulong GetCode() 20 | { 21 | var input = GetInputText(); 22 | var numbers = input.ParseInts(); 23 | var tx = (ulong)numbers[1]; 24 | var ty = (ulong)numbers[0]; 25 | 26 | var x = 1UL; 27 | var y = 1UL; 28 | var code = 20151125UL; 29 | 30 | while (true) 31 | { 32 | x += 1; 33 | y -= 1; 34 | 35 | if (y == 0) 36 | { 37 | y = x; 38 | x = 1; 39 | } 40 | 41 | code = GetNextCode(code); 42 | if (x == tx && y == ty) 43 | { 44 | break; 45 | } 46 | } 47 | 48 | return code; 49 | } 50 | 51 | private static ulong GetNextCode(ulong prev) 52 | { 53 | return 252533UL * prev % 33554393UL; 54 | } 55 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D01/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | using Utilities.Geometry.Euclidean; 3 | 4 | namespace Solutions.Y2016.D01; 5 | 6 | [PuzzleInfo("No Time for a Taxicab", Topics.Vectors, Difficulty.Easy)] 7 | public sealed class Solution : SolutionBase 8 | { 9 | public override object Run(int part) 10 | { 11 | var input = GetInputText(); 12 | var steps = input.Split(separator: ", ", StringSplitOptions.RemoveEmptyEntries); 13 | 14 | return part switch 15 | { 16 | 1 => GetMinDistance(steps, haltOnRepeat: false), 17 | 2 => GetMinDistance(steps, haltOnRepeat: true), 18 | _ => PuzzleNotSolvedString 19 | }; 20 | } 21 | 22 | private static int GetMinDistance(IEnumerable steps, bool haltOnRepeat) 23 | { 24 | var pose = new Pose2D(Pos: Vec2D.Zero, Face: Vec2D.Up); 25 | var visited = new HashSet(); 26 | 27 | foreach (var step in steps) 28 | { 29 | pose = step[0] switch 30 | { 31 | 'L' => pose.Turn(Rot3D.P90Z), 32 | 'R' => pose.Turn(Rot3D.N90Z), 33 | _ => pose 34 | }; 35 | 36 | for (var i = 0; i < step.ParseInt(); i++) 37 | { 38 | pose = pose.Step(); 39 | if (haltOnRepeat && !visited.Add(pose.Pos)) 40 | { 41 | return pose.Pos.Magnitude(metric: Metric.Taxicab); 42 | } 43 | } 44 | } 45 | 46 | return pose.Pos.Magnitude(metric: Metric.Taxicab); 47 | } 48 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D03/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2016.D03; 4 | 5 | [PuzzleInfo("Square With Three Sides", Topics.Math, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | return part switch 11 | { 12 | 1 => CountValidHorizontal(), 13 | 2 => CountValidVertical(), 14 | _ => PuzzleNotSolvedString 15 | }; 16 | } 17 | 18 | private int CountValidHorizontal() 19 | { 20 | var input = GetInputLines(); 21 | return input 22 | .Select(StringExtensions.ParseInts) 23 | .Count(IsValidTriangle); 24 | } 25 | 26 | private int CountValidVertical() 27 | { 28 | var count = 0; 29 | var numbers = GetInputLines() 30 | .Select(line => line.ParseInts()) 31 | .ToList(); 32 | 33 | var lengths = new int[3]; 34 | for (var i = 0; i < numbers.Count; i += 3) 35 | for (var c = 0; c < 3; c++) 36 | { 37 | lengths[0] = numbers[i + 0][c]; 38 | lengths[1] = numbers[i + 1][c]; 39 | lengths[2] = numbers[i + 2][c]; 40 | 41 | if (IsValidTriangle(lengths)) 42 | { 43 | count++; 44 | } 45 | } 46 | 47 | return count; 48 | } 49 | 50 | private static bool IsValidTriangle(IList lengths) 51 | { 52 | return 53 | lengths[0] + lengths[1] > lengths[2] && 54 | lengths[0] + lengths[2] > lengths[1] && 55 | lengths[1] + lengths[2] > lengths[0]; 56 | } 57 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D06/Encoding.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2016.D06; 2 | 3 | public enum Encoding 4 | { 5 | MostCommon, 6 | LeastCommon 7 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D06/Solution.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Utilities.Collections; 3 | 4 | namespace Solutions.Y2016.D06; 5 | 6 | [PuzzleInfo("Signals and Noise", Topics.StringParsing, Difficulty.Medium)] 7 | public sealed class Solution : SolutionBase 8 | { 9 | public override object Run(int part) 10 | { 11 | return part switch 12 | { 13 | 1 => ParseMessage(encoding: Encoding.MostCommon), 14 | 2 => ParseMessage(encoding: Encoding.LeastCommon), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private string ParseMessage(Encoding encoding) 20 | { 21 | var message = new StringBuilder(); 22 | var messages = GetInputLines(); 23 | var columnCounts = new DefaultDict>( 24 | defaultSelector: _ => new DefaultDict(defaultValue: 0)); 25 | 26 | for (var j = 0; j < messages[0].Length; j++) 27 | for (var i = 0; i < messages.Length; i++) 28 | { 29 | columnCounts[j][messages[i][j]]++; 30 | } 31 | 32 | for (var i = 0; i < messages[0].Length; i++) 33 | { 34 | var counts = columnCounts[i]; 35 | var letter = encoding switch 36 | { 37 | Encoding.MostCommon => counts.Keys.MaxBy(c => counts[c]), 38 | Encoding.LeastCommon => counts.Keys.MinBy(c => counts[c]), 39 | _ => throw new NoSolutionException() 40 | }; 41 | 42 | message.Append(letter); 43 | } 44 | 45 | return message.ToString(); 46 | } 47 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D10/Node.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2016.D10; 2 | 3 | public sealed class Node 4 | { 5 | public string Id { get; } 6 | public string? Low { get; init; } 7 | public string? High { get; init; } 8 | public HashSet Values { get; } = new(capacity: 2); 9 | 10 | public bool Ready => Values.Count == 2; 11 | 12 | public Node(string id) 13 | { 14 | Id = id; 15 | } 16 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D11/Device.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2016.D11; 2 | 3 | public readonly record struct Device(DeviceType Type, string Element, int Floor); -------------------------------------------------------------------------------- /Solutions/Y2016/D11/DeviceType.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2016.D11; 2 | 3 | public enum DeviceType 4 | { 5 | Microchip, 6 | Generator 7 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D12/Solution.cs: -------------------------------------------------------------------------------- 1 | using Solutions.Y2016.Common; 2 | 3 | namespace Solutions.Y2016.D12; 4 | 5 | [PuzzleInfo("Leonardo's Monorail", Topics.Assembly|Topics.Simulation, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var input = GetInputLines(); 11 | var tokens = input.Select(line => line.Split(' ')).ToList(); 12 | 13 | return part switch 14 | { 15 | 1 => RunProgram(tokens, c: 0), 16 | 2 => RunProgram(tokens, c: 1), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static long RunProgram(IList program, long c) 22 | { 23 | var vm = new Vm { ["c"] = c }; 24 | vm.Run(program); 25 | return vm["a"]; 26 | } 27 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D14/HashSequence.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Hashing; 2 | 3 | namespace Solutions.Y2016.D14; 4 | 5 | public sealed class HashSequence : IDisposable 6 | { 7 | private readonly string _salt; 8 | private readonly int _stretches; 9 | private readonly List _hashes = []; 10 | private readonly Md5Provider _md5Provider = new(); 11 | 12 | public string this[int i] => GetHashInternal(i); 13 | 14 | public HashSequence(string salt, int stretches, int count) 15 | { 16 | _salt = salt; 17 | _stretches = stretches; 18 | EnsureContains(count); 19 | } 20 | 21 | private string GetHashInternal(int i) 22 | { 23 | EnsureContains(count: i + 1); 24 | return _hashes[i]; 25 | } 26 | 27 | private void EnsureContains(int count) 28 | { 29 | while(_hashes.Count < count) 30 | { 31 | var input = $"{_salt}{_hashes.Count}"; 32 | var hash = _md5Provider.GetHashHex(input); 33 | 34 | for (var i = 0; i < _stretches; i++) 35 | { 36 | hash = _md5Provider.GetHashHex(hash); 37 | } 38 | 39 | _hashes.Add(hash); 40 | } 41 | } 42 | 43 | public void Dispose() 44 | { 45 | _md5Provider.Dispose(); 46 | } 47 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D14/Solution.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Solutions.Y2016.D14; 4 | 5 | [PuzzleInfo("One-Time Pad", Topics.Hashing, Difficulty.Medium)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | private static readonly Regex CandidateRegex = new(@"(?[a-z0-9])\1\1"); 9 | 10 | public override object Run(int part) 11 | { 12 | var salt = GetInputText(); 13 | return part switch 14 | { 15 | 1 => FindKeys(salt, stretches: 0000, count: 64), 16 | 2 => FindKeys(salt, stretches: 2016, count: 64), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static int FindKeys(string salt, int stretches, int count) 22 | { 23 | using var hashes = new HashSequence(salt, stretches, count: 1000); 24 | var indices = new List(); 25 | 26 | for (var i = 0; indices.Count < count; i++) 27 | { 28 | var candidate = hashes[i]; 29 | var match = CandidateRegex.Match(candidate); 30 | 31 | if (!match.Success) 32 | { 33 | continue; 34 | } 35 | 36 | var repeat = match.Groups["C"].Value.Single(); 37 | var need = new string(repeat, count: 5); 38 | 39 | for (var j = 1; j <= 1000; j++) 40 | { 41 | if (hashes[i + j].Contains(need)) 42 | { 43 | indices.Add(i); 44 | break; 45 | } 46 | } 47 | } 48 | 49 | return indices.Last(); 50 | } 51 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D15/Disk.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2016.D15; 2 | 3 | public readonly record struct Disk(int Depth, int Positions, int Initial); -------------------------------------------------------------------------------- /Solutions/Y2016/D16/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2016.D16; 2 | 3 | [PuzzleInfo("Dragon Checksum", Topics.BitwiseOperations, Difficulty.Easy)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var input = GetInputText(); 9 | var data = input.Select(c => c == '1').ToList(); 10 | 11 | return part switch 12 | { 13 | 1 => Validate(data, length: 272), 14 | 2 => Validate(data, length: 35651584), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static string Validate(List data, int length) 20 | { 21 | while (data.Count < length) 22 | { 23 | data = Generate(data); 24 | } 25 | 26 | var trimmed = data.Take(length).ToList(); 27 | var checksum = Checksum(trimmed); 28 | 29 | while (checksum.Count % 2 == 0) 30 | { 31 | checksum = Checksum(checksum); 32 | } 33 | 34 | return string.Concat(checksum.Select(b => b ? '1' : '0')); 35 | } 36 | 37 | private static List Generate(IList data) 38 | { 39 | var b = data 40 | .Reverse() 41 | .Select(bit => !bit); 42 | 43 | return data 44 | .Append(false) 45 | .Concat(b) 46 | .ToList(); 47 | } 48 | 49 | private static List Checksum(List data) 50 | { 51 | var checksum = new List(); 52 | for (var i = 0; i < data.Count; i += 2) 53 | { 54 | checksum.Add(!(data[i] ^ data[i + 1])); 55 | } 56 | 57 | return checksum; 58 | } 59 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D17/PathCriteria.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2016.D17; 2 | 3 | public enum PathCriteria 4 | { 5 | Shortest, 6 | Longest 7 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D17/State.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2016.D17; 4 | 5 | public readonly record struct State(Vec2D Pos, string Path); -------------------------------------------------------------------------------- /Solutions/Y2016/D19/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Collections; 2 | 3 | namespace Solutions.Y2016.D19; 4 | 5 | [PuzzleInfo("An Elephant Named Joseph", Topics.Math, Difficulty.Hard, favourite: true)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var input = GetInputText(); 11 | var count = int.Parse(input); 12 | 13 | return part switch 14 | { 15 | 1 => FindWinnerAdjacent(count), 16 | 2 => FindWinnerAcross(count), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static int FindWinnerAdjacent(int count) 22 | { 23 | var members = new CircularLinkedList(Enumerable.Range(1, count)); 24 | var current = members.Head; 25 | 26 | while (members.Count > 1) 27 | { 28 | members.Remove(current!.Next!); 29 | current = current.Next; 30 | } 31 | 32 | return current!.Value; 33 | } 34 | 35 | private static int FindWinnerAcross(int count) 36 | { 37 | var members = new CircularLinkedList(Enumerable.Range(1, count)); 38 | var across = members.Head; 39 | 40 | for (var i = 0; i < count / 2; i++) 41 | { 42 | across = across!.Next; 43 | } 44 | 45 | while (members.Count > 1) 46 | { 47 | var tmp = members.Count % 2 == 1 48 | ? across!.Next!.Next 49 | : across!.Next; 50 | 51 | members.Remove(across); 52 | across = tmp; 53 | } 54 | 55 | return across!.Value; 56 | } 57 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D20/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2016.D20; 2 | 3 | public enum EndpointType 4 | { 5 | Start, 6 | End 7 | } 8 | 9 | public enum Target 10 | { 11 | LowestValid, 12 | TotalAllowed 13 | } -------------------------------------------------------------------------------- /Solutions/Y2016/D22/PosComparer.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2016.D22; 4 | 5 | public sealed class PosComparer : IComparer 6 | { 7 | public int Compare(Vec2D x, Vec2D y) 8 | { 9 | var xComparison = x.X.CompareTo(y.X); 10 | return xComparison != 0 11 | ? xComparison : x.Y.CompareTo(y.Y); 12 | } 13 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D01/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2017.D01; 4 | 5 | [PuzzleInfo("Inverse Captcha", Topics.StringParsing, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var stream = GetInputText(); 11 | return part switch 12 | { 13 | 1 => SumDigits(stream, steps: 1), 14 | 2 => SumDigits(stream, steps: stream.Length / 2), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static int SumDigits(string stream, int steps) 20 | { 21 | return stream 22 | .Where((chr, i) => chr == stream[(i + steps) % stream.Length]) 23 | .Sum(StringExtensions.AsDigit); 24 | } 25 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D02/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2017.D02; 4 | 5 | [PuzzleInfo("Corruption Checksum", Topics.Math, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var table = ParseInputLines(parseFunc: StringExtensions.ParseInts); 11 | return part switch 12 | { 13 | 1 => GetChecksum(table), 14 | 2 => GetDivisorSum(table), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static int GetChecksum(IEnumerable> table) 20 | { 21 | return table.Sum(row => row.Max() - row.Min()); 22 | } 23 | 24 | private static int GetDivisorSum(IEnumerable> table) 25 | { 26 | return table.Sum(row => 27 | { 28 | for (var i = 0; i < row.Count; i++) 29 | for (var j = 0; j < row.Count; j++) 30 | { 31 | if (i == j) 32 | { 33 | continue; 34 | } 35 | 36 | if (row[i] % row[j] == 0) 37 | { 38 | return row[i] / row[j]; 39 | } 40 | } 41 | 42 | throw new NoSolutionException(); 43 | }); 44 | } 45 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D03/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2017.D03; 4 | 5 | [PuzzleInfo("Spiral Memory", Topics.Vectors, Difficulty.Medium)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var input = GetInputText(); 11 | var value = int.Parse(input); 12 | 13 | return part switch 14 | { 15 | 1 => GetDistanceToOrigin(value), 16 | 2 => GetFirstLargerValue(value), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static int GetDistanceToOrigin(int square) 22 | { 23 | var memory = new Spiral(); 24 | memory.Build( 25 | valueFunc: spiral => spiral.LastVal + 1, 26 | stopFunc: spiral => spiral.LastVal == square); 27 | 28 | return memory.LastPos.Magnitude(Metric.Taxicab); 29 | } 30 | 31 | private static int GetFirstLargerValue(int threshold) 32 | { 33 | var memory = new Spiral(); 34 | memory.Build( 35 | valueFunc: spiral => spiral.NextPos.GetAdjacentSet(Metric.Chebyshev).Sum(pos => spiral[pos]), 36 | stopFunc: spiral => spiral.LastVal > threshold); 37 | 38 | return memory.LastVal; 39 | } 40 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D03/Spiral.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Collections; 2 | using Utilities.Geometry.Euclidean; 3 | 4 | namespace Solutions.Y2017.D03; 5 | 6 | public sealed class Spiral 7 | { 8 | private readonly DefaultDict _memory; 9 | private Vec2D _dir = Vec2D.Right; 10 | 11 | public int LastVal { get; private set; } 12 | public Vec2D LastPos { get; private set; } 13 | public Vec2D NextPos => LastPos + _dir; 14 | 15 | public int this[Vec2D pos] 16 | { 17 | get => _memory[pos]; 18 | private set => StoreValue(pos, value); 19 | } 20 | 21 | public Spiral() 22 | { 23 | _memory = new DefaultDict(defaultValue: 0); 24 | StoreValue(pos: Vec2D.Zero, value: 1); 25 | } 26 | 27 | public void Build(Func valueFunc, Predicate stopFunc) 28 | { 29 | var stepsBeforeTurn = 1; 30 | var stepsSinceLastTurn = 0; 31 | var turnsSinceStepIncrement = 0; 32 | 33 | while (!stopFunc(this)) 34 | { 35 | if (stepsSinceLastTurn == stepsBeforeTurn) 36 | { 37 | _dir = Rot3D.P90Z.Transform(_dir); 38 | stepsSinceLastTurn = 0; 39 | turnsSinceStepIncrement++; 40 | } 41 | 42 | if (turnsSinceStepIncrement == 2) 43 | { 44 | stepsBeforeTurn++; 45 | turnsSinceStepIncrement = 0; 46 | } 47 | 48 | stepsSinceLastTurn++; 49 | this[LastPos + _dir] = valueFunc(this); 50 | } 51 | } 52 | 53 | private void StoreValue(Vec2D pos, int value) 54 | { 55 | _memory[pos] = value; 56 | LastVal = value; 57 | LastPos = pos; 58 | } 59 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D04/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2017.D04; 2 | 3 | using Passphrase = IList; 4 | 5 | [PuzzleInfo("High-Entropy Passphrases", Topics.StringParsing, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var passphrases = ParseInputLines(parseFunc: ParsePassphrase); 11 | return part switch 12 | { 13 | 1 => CountValidDistinctWords(passphrases), 14 | 2 => CountValidNoAnagrams(passphrases), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static int CountValidDistinctWords(IEnumerable passphrases) 20 | { 21 | return passphrases.Count(passphrase => new HashSet(passphrase).Count == passphrase.Count); 22 | } 23 | 24 | private static int CountValidNoAnagrams(IEnumerable passphrases) 25 | { 26 | return passphrases.Count(passphrase => 27 | { 28 | var sortedWords = passphrase.Select(word => string.Concat(word.Order())); 29 | var sortedSet = new HashSet(sortedWords); 30 | 31 | return sortedSet.Count == passphrase.Count; 32 | }); 33 | } 34 | 35 | private static Passphrase ParsePassphrase(string line) 36 | { 37 | return line.Split(' ', StringSplitOptions.RemoveEmptyEntries); 38 | } 39 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D05/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2017.D05; 2 | 3 | [PuzzleInfo("A Maze of Twisty Trampolines, All Alike", Topics.Simulation, Difficulty.Easy)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var offsets = ParseInputLines(parseFunc: int.Parse); 9 | return part switch 10 | { 11 | 1 => CountSteps(offsets, offsetModifier: offset => offset + 1), 12 | 2 => CountSteps(offsets, offsetModifier: offset => offset >= 3 ? offset - 1 : offset + 1), 13 | _ => PuzzleNotSolvedString 14 | }; 15 | } 16 | 17 | private static int CountSteps(int[] offsets, Func offsetModifier) 18 | { 19 | var ip = 0; 20 | var steps = 0; 21 | 22 | while (ip >= 0 && ip < offsets.Length) 23 | { 24 | var jumpTo = ip + offsets[ip]; 25 | offsets[ip] = offsetModifier(offsets[ip]); 26 | steps++; 27 | ip = jumpTo; 28 | } 29 | 30 | return steps; 31 | } 32 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D06/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2017.D06; 4 | 5 | [PuzzleInfo("Memory Reallocation", Topics.BitwiseOperations, Difficulty.Easy, favourite: true)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var input = GetInputText(); 11 | var state = ParseState(input); 12 | 13 | return part switch 14 | { 15 | 1 => GetCycle(state), 16 | 2 => GetLoopLength(state), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static int GetCycle(MemoryBankState state) 22 | { 23 | var seen = new HashSet(); 24 | while (seen.Add(state)) 25 | { 26 | state = state.Reallocate(); 27 | } 28 | 29 | return seen.Count; 30 | } 31 | 32 | private static int GetLoopLength(MemoryBankState state) 33 | { 34 | var seen = new HashSet(); 35 | while (seen.Add(state)) 36 | { 37 | state = state.Reallocate(); 38 | } 39 | 40 | seen.Clear(); 41 | while (seen.Add(state)) 42 | { 43 | state = state.Reallocate(); 44 | } 45 | 46 | return seen.Count; 47 | } 48 | 49 | private static MemoryBankState ParseState(string input) 50 | { 51 | var ulongs = input 52 | .ParseLongs() 53 | .Select(l => (ulong)l) 54 | .ToList(); 55 | 56 | return new MemoryBankState(banks: ulongs); 57 | } 58 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D08/Instruction.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2017.D08; 2 | 3 | public readonly record struct Instruction(string DestReg, string DestOp, int DestArg, string SrcReg, string CheckOp, int CheckArg); -------------------------------------------------------------------------------- /Solutions/Y2017/D08/Scope.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2017.D08; 2 | 3 | public enum Scope 4 | { 5 | Halted = 0, 6 | Lifetime 7 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D09/Scope.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2017.D09; 2 | 3 | public enum Scope 4 | { 5 | Group = 0, 6 | Garbage 7 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D10/Solution.cs: -------------------------------------------------------------------------------- 1 | using Solutions.Y2017.Common; 2 | using Utilities.Extensions; 3 | 4 | namespace Solutions.Y2017.D10; 5 | 6 | [PuzzleInfo("Knot Hash", Topics.Hashing, Difficulty.Medium)] 7 | public sealed class Solution : SolutionBase 8 | { 9 | public override object Run(int part) 10 | { 11 | var input = GetInputText(); 12 | return part switch 13 | { 14 | 1 => HashSimple(input), 15 | 2 => HashFull(input), 16 | _ => PuzzleNotSolvedString 17 | }; 18 | } 19 | 20 | private static int HashSimple(string input) 21 | { 22 | var lengths = GetByteArray(input.ParseInts()); 23 | var ring = GetByteArray(Enumerable.Range(0, 256)); 24 | var result = KnotHash.TieKnots(ring, lengths, rounds: 1); 25 | 26 | return result[0] * result[1]; 27 | } 28 | 29 | private static string HashFull(string input) 30 | { 31 | return KnotHash.ComputeHash(input); 32 | } 33 | 34 | private static byte[] GetByteArray(IEnumerable ints) 35 | { 36 | return ints.Select(n => (byte)n).ToArray(); 37 | } 38 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D11/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Hexagonal; 2 | 3 | namespace Solutions.Y2017.D11; 4 | 5 | [PuzzleInfo("Hex Ed", Topics.Math, Difficulty.Easy, favourite: true)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | private static readonly Dictionary Directions = new() 9 | { 10 | { "n", Hex.Directions[Hex.Flat.N] }, 11 | { "ne", Hex.Directions[Hex.Flat.Ne] }, 12 | { "se", Hex.Directions[Hex.Flat.Se] }, 13 | { "s", Hex.Directions[Hex.Flat.S] }, 14 | { "sw", Hex.Directions[Hex.Flat.Sw] }, 15 | { "nw", Hex.Directions[Hex.Flat.Nw] } 16 | }; 17 | 18 | public override object Run(int part) 19 | { 20 | var input = GetInputText(); 21 | var steps = input.Split(separator: ','); 22 | 23 | return part switch 24 | { 25 | 1 => GetEndDistance(steps), 26 | 2 => GetMaxDistance(steps), 27 | _ => PuzzleNotSolvedString 28 | }; 29 | } 30 | 31 | private static int GetEndDistance(IEnumerable steps) 32 | { 33 | var end = steps.Aggregate( 34 | seed: Hex.Zero, 35 | func: (pos, step) => pos + Directions[step]); 36 | 37 | return end.Magnitude; 38 | } 39 | 40 | private static int GetMaxDistance(IEnumerable steps) 41 | { 42 | var pos = Hex.Zero; 43 | var set = new HashSet(); 44 | 45 | foreach (var step in steps) 46 | { 47 | pos += Directions[step]; 48 | set.Add(pos); 49 | } 50 | 51 | return set.Max(p => p.Magnitude); 52 | } 53 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D13/Scanner.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2017.D13; 2 | 3 | public readonly struct Scanner(int depth, int range) 4 | { 5 | public int Depth { get; } = depth; 6 | public int Range { get; } = range; 7 | 8 | public int Severity => Depth * Range; 9 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D13/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2017.D13; 4 | 5 | [PuzzleInfo("Packet Scanners", Topics.Math, Difficulty.Medium)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var input = GetInputLines(); 11 | var scanners = new List(input.Select(ParseScanner)); 12 | 13 | return part switch 14 | { 15 | 1 => GetSeverity(scanners, delay: 0), 16 | 2 => FindDelay(scanners), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static int FindDelay(IList scanners) 22 | { 23 | var delay = 0; 24 | while (scanners.Any(s => IsCaught(scanner: s, time: s.Depth + delay))) 25 | { 26 | delay++; 27 | } 28 | return delay; 29 | } 30 | 31 | private static int GetSeverity(IEnumerable scanners, int delay) 32 | { 33 | return scanners 34 | .Where(s => IsCaught(scanner: s, time: s.Depth + delay)) 35 | .Sum(s => s.Severity); 36 | } 37 | 38 | private static bool IsCaught(Scanner scanner, int time) 39 | { 40 | return time % (scanner.Range - 1) == 0 && time / (scanner.Range - 1) % 2 == 0; 41 | } 42 | 43 | private static Scanner ParseScanner(string line) 44 | { 45 | var numbers = line.ParseInts(); 46 | return new Scanner( 47 | depth: numbers[0], 48 | range: numbers[1]); 49 | } 50 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D20/Records.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2017.D20; 4 | 5 | public readonly record struct Particle(Vec3D Pos, Vec3D Vel, Vec3D Acc); 6 | public readonly record struct Collision(int Tick, Vec3D Pos); -------------------------------------------------------------------------------- /Solutions/Y2017/D22/State.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2017.D22; 2 | 3 | public enum State 4 | { 5 | Clean = 0, 6 | Weakened, 7 | Infected, 8 | Flagged 9 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D24/AdapterHelper.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Collections; 2 | 3 | namespace Solutions.Y2017.D24; 4 | 5 | public sealed class AdapterHelper 6 | { 7 | private readonly Dictionary _adapters = new(); 8 | private readonly DefaultDict> _compatibilities = new(defaultSelector: _ => []); 9 | 10 | public IReadOnlySet GetCompatibilities(int port, HashSet used) 11 | { 12 | return _compatibilities[port] 13 | .Where(c => !used.Contains(c.ViaAdapter)) 14 | .ToHashSet(); 15 | } 16 | 17 | public int GetStrength(IEnumerable used) 18 | { 19 | return used.Sum(key => _adapters[key].Port1 + _adapters[key].Port2); 20 | } 21 | 22 | public void RegisterAdapter(Adapter adapter) 23 | { 24 | var compatibility1 = new Compatibility(ResultingPort: adapter.Port1, ViaAdapter: adapter.Key); 25 | var compatibility2 = new Compatibility(ResultingPort: adapter.Port2, ViaAdapter: adapter.Key); 26 | 27 | _adapters[adapter.Key] = adapter; 28 | _compatibilities[adapter.Port1].Add(compatibility2); 29 | _compatibilities[adapter.Port2].Add(compatibility1); 30 | } 31 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D24/Comparers.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2017.D24; 2 | 3 | public sealed class StrengthComparer : IComparer 4 | { 5 | public int Compare(Bridge x, Bridge y) 6 | { 7 | return x.Strength.CompareTo(y.Strength); 8 | } 9 | } 10 | 11 | public sealed class LengthComparer : IComparer 12 | { 13 | public int Compare(Bridge x, Bridge y) 14 | { 15 | return x.Length != y.Length 16 | ? x.Length.CompareTo(y.Length) 17 | : x.Strength.CompareTo(y.Length); 18 | } 19 | } -------------------------------------------------------------------------------- /Solutions/Y2017/D24/Records.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2017.D24; 2 | 3 | public readonly record struct Adapter(string Key, int Port1, int Port2); 4 | public readonly record struct Bridge(int Strength, int Length); 5 | public readonly record struct Compatibility(int ResultingPort, string ViaAdapter); -------------------------------------------------------------------------------- /Solutions/Y2017/D25/TuringMachine.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2017.D25; 2 | 3 | public sealed class TuringMachine(IReadOnlyDictionary ruleTable) 4 | { 5 | public readonly record struct Transition(bool Write, int Move, char Next); 6 | public readonly record struct State(Transition False, Transition True); 7 | 8 | private readonly Dictionary _tape = new(); 9 | private int _cursor; 10 | 11 | public int Run(char state, int steps) 12 | { 13 | _tape.Clear(); 14 | _cursor = 0; 15 | 16 | for (var i = 0; i < steps; i++) 17 | { 18 | var rule = ruleTable[state]; 19 | var transition = _tape.TryGetValue(_cursor, out var value) && value 20 | ? rule.True 21 | : rule.False; 22 | 23 | _tape[_cursor] = transition.Write; 24 | _cursor += transition.Move; 25 | 26 | state = transition.Next; 27 | } 28 | 29 | return _tape.Values.Count(b => b); 30 | } 31 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D01/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D01; 2 | 3 | [PuzzleInfo("Chronal Calibration", Topics.Math, Difficulty.Easy)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var numbers = ParseInputLines(int.Parse); 9 | return part switch 10 | { 11 | 1 => numbers.Sum(), 12 | 2 => GetFirstRepeatedFrequency(numbers), 13 | _ => PuzzleNotSolvedString 14 | }; 15 | } 16 | 17 | private static int GetFirstRepeatedFrequency(int[] numbers) 18 | { 19 | var i = 0; 20 | var freq = 0; 21 | var seen = new HashSet(); 22 | 23 | while (seen.Add(freq)) 24 | { 25 | freq += numbers[i]; 26 | i = (i + 1) % numbers.Length; 27 | } 28 | 29 | return freq; 30 | } 31 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D02/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D02; 2 | 3 | [PuzzleInfo("Inventory Management System", Topics.StringParsing, Difficulty.Easy)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | return part switch 9 | { 10 | 1 => GetChecksum(ids: GetInputLines(), c1: 2, c2: 3), 11 | 2 => GetCommonCorrectLetters(ids: GetInputLines()), 12 | _ => PuzzleNotSolvedString 13 | }; 14 | } 15 | 16 | private static int GetChecksum(IList ids, int c1, int c2) 17 | { 18 | return 19 | ids.Count(id => HasExactCountOfAnyLetter(id, c1)) * 20 | ids.Count(id => HasExactCountOfAnyLetter(id, c2)); 21 | } 22 | 23 | private static string GetCommonCorrectLetters(string[] ids) 24 | { 25 | var numIds = ids.Length; 26 | var idLength = ids[0].Length; 27 | 28 | for (var i = 0; i < numIds; i++) 29 | for (var j = 0; j < numIds; j++) 30 | { 31 | var z = ZipCommon(ids[i], ids[j]); 32 | if (z.Length == idLength - 1) 33 | { 34 | return z; 35 | } 36 | } 37 | 38 | throw new NoSolutionException(); 39 | } 40 | 41 | private static string ZipCommon(string a, string b) 42 | { 43 | return string.Join(string.Empty, a.Zip(b, (c1, c2) => c1 == c2 ? c1.ToString() : string.Empty)); 44 | } 45 | 46 | private static bool HasExactCountOfAnyLetter(string id, int count) 47 | { 48 | return id.Any(c => id.Count(e => e == c) == count); 49 | } 50 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D03/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Collections; 2 | using Utilities.Extensions; 3 | using Utilities.Geometry.Euclidean; 4 | 5 | namespace Solutions.Y2018.D03; 6 | 7 | [PuzzleInfo("No Matter How You Slice It", Topics.Vectors, Difficulty.Easy)] 8 | public sealed class Solution : SolutionBase 9 | { 10 | public override object Run(int part) 11 | { 12 | var claims = ParseInputLines(parseFunc: ParseClaim); 13 | return part switch 14 | { 15 | 1 => CountClaimOverlaps(claims.Select(c => c.Aabb)), 16 | 2 => GetNonOverlappedClaim(claims.ToList()), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static int CountClaimOverlaps(IEnumerable claimAabbs) 22 | { 23 | var map = new DefaultDict(defaultValue: 0); 24 | 25 | foreach (var aabb in claimAabbs) 26 | foreach (var position in aabb) 27 | { 28 | map[position]++; 29 | } 30 | 31 | return map.Keys.Count(c => map[c] > 1); 32 | } 33 | 34 | private static int GetNonOverlappedClaim(IList<(int Id, Aabb2D aabb2D)> claims) 35 | { 36 | return claims 37 | .Single(claim => claims.Count(other => Aabb2D.Overlap(a: claim.aabb2D, b: other.aabb2D, out _)) == 1).Id; 38 | } 39 | 40 | private static (int Id, Aabb2D Aabb) ParseClaim(string line) 41 | { 42 | var numbers = line.ParseInts(); 43 | var id = numbers[0]; 44 | var aabb = new Aabb2D( 45 | xMin: numbers[1], 46 | xMax: numbers[1] + numbers[3] - 1, 47 | yMin: numbers[2], 48 | yMax: numbers[2] + numbers[4] - 1); 49 | 50 | return (id, aabb); 51 | } 52 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D12/Input.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D12; 2 | 3 | public static class Input 4 | { 5 | public static void Parse(IList input, out HashSet state, out Dictionary rules) 6 | { 7 | state = new HashSet(); 8 | rules = new Dictionary(); 9 | 10 | for (var i = 2; i < input.Count; i++) 11 | { 12 | var mask = 0U; 13 | for (var j = 0; j < 5; j++) 14 | { 15 | if (input[i][j] == '#') 16 | { 17 | mask |= 1U << j; 18 | } 19 | } 20 | 21 | rules.Add(mask, input[i].Last() == '#'); 22 | } 23 | 24 | var initialStr = input[0] 25 | .Where(c => c is '#' or '.') 26 | .ToList(); 27 | 28 | for (var i = 0; i < initialStr.Count; i++) 29 | { 30 | if (initialStr[i] == '#') 31 | { 32 | state.Add(i); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D13/CrashResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D13; 2 | 3 | public enum CrashResponse 4 | { 5 | Halt = 0, 6 | Remove 7 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D13/TickOrderComparer.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2018.D13; 4 | 5 | public sealed class TickOrderComparer : IComparer 6 | { 7 | public int Compare(Vec2D a, Vec2D b) 8 | { 9 | var yComparison = a.Y.CompareTo(b.Y); 10 | return yComparison != 0 11 | ? yComparison 12 | : a.X.CompareTo(b.X); 13 | } 14 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D13/Track.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2018.D13; 4 | 5 | public static class Track 6 | { 7 | public const char Junction = '+'; 8 | public const char Left = '\\'; 9 | public const char Right = '/'; 10 | 11 | public static readonly Dictionary CartFacings = new() 12 | { 13 | { '<', Vec2D.Left }, 14 | { '>', Vec2D.Right }, 15 | { '^', Vec2D.Down }, 16 | { 'v', Vec2D.Up } 17 | }; 18 | 19 | public static readonly Dictionary TurnChoices = new() 20 | { 21 | { 0, Rot3D.N90Z }, 22 | { 1, Rot3D.Zero }, 23 | { 2, Rot3D.P90Z } 24 | }; 25 | 26 | public static Vec2D TurnForCorner(char corner, Vec2D face) 27 | { 28 | var eastOrWest = face == Vec2D.Left || face == Vec2D.Right; 29 | var rot = corner switch 30 | { 31 | Left => eastOrWest ? Rot3D.P90Z : Rot3D.N90Z, 32 | Right => eastOrWest ? Rot3D.N90Z : Rot3D.P90Z, 33 | _ => throw new ArgumentOutOfRangeException(nameof(corner)) 34 | }; 35 | 36 | return rot.Transform(face); 37 | } 38 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D15/CombatResult.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D15; 2 | 3 | public readonly struct CombatResult 4 | { 5 | public char WinningTeam { get; } 6 | public int CompletedRounds { get; } 7 | public int HpSum { get; } 8 | public IReadOnlyDictionary Casualties { get; } 9 | 10 | public int Score => CompletedRounds * HpSum; 11 | 12 | public static CombatResult FromState(GameState state) 13 | { 14 | return new CombatResult( 15 | winningTeam: state.Units.Values.First(unit => !unit.Dead).Team, 16 | completedRounds: state.Tick, 17 | hpSum: state.Units.Values.Sum(unit => unit.Hp), 18 | casualties: state.Casualties); 19 | } 20 | 21 | public void Print() 22 | { 23 | var casualtiesSummary = string.Join(' ', Casualties.Select(kvp => $"[{kvp.Key}={kvp.Value}]")); 24 | 25 | Console.WriteLine("COMBAT RESULT: "); 26 | Console.WriteLine($"Winning team: {WinningTeam}"); 27 | Console.WriteLine($"Completed combat rounds: {CompletedRounds}"); 28 | Console.WriteLine($"Remaining HP sum: {HpSum}"); 29 | Console.WriteLine($"Casualties: {casualtiesSummary}"); 30 | } 31 | 32 | private CombatResult(char winningTeam, int completedRounds, int hpSum, IReadOnlyDictionary casualties) 33 | { 34 | WinningTeam = winningTeam; 35 | CompletedRounds = completedRounds; 36 | HpSum = hpSum; 37 | Casualties = casualties; 38 | } 39 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D15/GameData.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D15; 2 | 3 | public static class GameData 4 | { 5 | public const char Empty = '.'; 6 | public const char Goblin = 'G'; 7 | public const char Elf = 'E'; 8 | 9 | public const int Hp = 200; 10 | public const int Dmg = 3; 11 | 12 | public static readonly SquareComparer SquareComparer = new(); 13 | public static readonly Dictionary EnemyMap = new() 14 | { 15 | { Elf, Goblin }, 16 | { Goblin, Elf } 17 | }; 18 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D15/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D15; 2 | 3 | [PuzzleInfo("Beverage Bandits", Topics.Simulation, Difficulty.Hard)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var input = GetInputLines(); 9 | return part switch 10 | { 11 | 1 => PlayDefaultGame(input, print: LogsEnabled), 12 | 2 => PlayBuffedGame(input, print: LogsEnabled), 13 | _ => PuzzleNotSolvedString 14 | }; 15 | } 16 | 17 | private static int PlayDefaultGame(IList input, bool print) 18 | { 19 | var emptyBuffs = new Dictionary(); 20 | var state = GameState.Create(input, teamDmgBuffs: emptyBuffs); 21 | var result = Sim.Run(state, print); 22 | 23 | return result.Score; 24 | } 25 | 26 | private static int PlayBuffedGame(IList input, bool print) 27 | { 28 | var score = 0; 29 | var dmgBuff = 0; 30 | var desiredResult = false; 31 | 32 | while (!desiredResult) 33 | { 34 | var buffs = new Dictionary { { GameData.Elf, dmgBuff++ } }; 35 | var state = GameState.Create(input, buffs); 36 | var result = Sim.Run(state, print); 37 | 38 | score = result.Score; 39 | desiredResult = 40 | result.WinningTeam == GameData.Elf && 41 | result.Casualties[GameData.Elf] == 0; 42 | } 43 | 44 | return score; 45 | } 46 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D15/SquareComparer.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2018.D15; 4 | 5 | public sealed class SquareComparer : IComparer 6 | { 7 | public int Compare(Vec2D a, Vec2D b) 8 | { 9 | var yComparison = a.Y.CompareTo(b.Y); 10 | return yComparison != 0 11 | ? yComparison 12 | : a.X.CompareTo(b.X); 13 | } 14 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D15/Unit.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2018.D15; 4 | 5 | public sealed class Unit 6 | { 7 | public int Id { get; } 8 | public char Team { get; } 9 | public int Dmg { get; } 10 | public int Hp { get; private set; } 11 | public Vec2D Pos { get; set; } 12 | 13 | public bool Dead => Hp <= 0; 14 | 15 | public Unit(int id, char team, int dmg, int hp, Vec2D pos) 16 | { 17 | Id = id; 18 | Team = team; 19 | Dmg = dmg; 20 | Hp = hp; 21 | Pos = pos; 22 | } 23 | 24 | public void InflictDamage(int dmg) 25 | { 26 | Hp = Math.Max(0, Hp - dmg); 27 | } 28 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D15/UnitFactory.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2018.D15; 4 | 5 | public static class UnitFactory 6 | { 7 | public static Unit Build(int id, char team, int dmgBuff, Vec2D pos) 8 | { 9 | return new Unit( 10 | id: id, 11 | team: team, 12 | pos: pos, 13 | dmg: GameData.Dmg + dmgBuff, 14 | hp: GameData.Hp); 15 | } 16 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D16/Observation.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D16; 2 | 3 | public readonly record struct Observation(IList Before, IList Instr, IList After); -------------------------------------------------------------------------------- /Solutions/Y2018/D17/Materials.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D17; 2 | 3 | public static class Terrain 4 | { 5 | public const char Spring = '+'; 6 | public const char Empty = '.'; 7 | public const char Clay = '#'; 8 | } 9 | 10 | public static class Water 11 | { 12 | public const char Flowing = '|'; 13 | public const char Settled = '~'; 14 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D19/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2018.D19; 4 | 5 | [PuzzleInfo("Go With The Flow", Topics.Assembly|Topics.Simulation, Difficulty.Hard)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var input = GetInputLines(); 11 | var ipAdr = input[0].ParseInt(); 12 | var program = input[1..].Select(ParseInstruction).ToArray(); 13 | 14 | return part switch 15 | { 16 | 1 => Execute(ipAdr, program, r0: 0, enableOptimizations: true), 17 | 2 => Execute(ipAdr, program, r0: 1, enableOptimizations: true), 18 | _ => PuzzleNotSolvedString 19 | }; 20 | } 21 | 22 | private static int Execute(int ipAdr, IList program, int r0, bool enableOptimizations) 23 | { 24 | var cpu = new Cpu(ipAdr) { [0] = r0 }; 25 | var ec = cpu.Run(program, enableOptimizations); 26 | 27 | return ec; 28 | } 29 | 30 | private static Cpu.Instruction ParseInstruction(string line) 31 | { 32 | var elements = line.Split(' '); 33 | return new Cpu.Instruction( 34 | Opcode: elements[0], 35 | A: int.Parse(elements[1]), 36 | B: int.Parse(elements[2]), 37 | C: int.Parse(elements[3])); 38 | } 39 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D20/MapChars.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2018.D20; 4 | 5 | public static class MapChars 6 | { 7 | public const char Start = 'X'; 8 | public const char Empty = '.'; 9 | 10 | public static readonly Dictionary DoorChars = new() 11 | { 12 | { Vec2D.Up, '-' }, 13 | { Vec2D.Down, '-' }, 14 | { Vec2D.Left, '|' }, 15 | { Vec2D.Right, '|' } 16 | }; 17 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D22/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D22; 2 | 3 | public enum RegionType 4 | { 5 | Rocky = 0, 6 | Wet, 7 | Narrow 8 | } 9 | 10 | public enum EquippedTool 11 | { 12 | None = 0, 13 | ClimbingGear, 14 | Torch 15 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D22/Records.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2018.D22; 4 | 5 | public readonly record struct Scan(int Depth, Vec2D Mouth, Vec2D Target); 6 | public readonly record struct Region(long Erosion, RegionType Type); 7 | public readonly record struct State(Vec2D Pos, EquippedTool Tool); 8 | public readonly record struct Transition(State NextState, int Cost); -------------------------------------------------------------------------------- /Solutions/Y2018/D23/SearchRanking.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D23; 2 | 3 | public readonly struct SearchRanking(long inRange, long distance, long volume) : IComparable 4 | { 5 | private readonly long _inRange = inRange; 6 | private readonly long _distance = distance; 7 | private readonly long _volume = volume; 8 | 9 | public int CompareTo(SearchRanking other) 10 | { 11 | // If one region intersects the range of more bots than the other, rank it higher 12 | // 13 | var inRangeComparison = _inRange.CompareTo(other._inRange); 14 | if (inRangeComparison != 0) 15 | { 16 | return -1 * inRangeComparison; 17 | } 18 | 19 | // Otherwise, if one region is close to the origin, rank it higher 20 | // 21 | var distanceComparison = _distance.CompareTo(other._distance); 22 | if (distanceComparison != 0) 23 | { 24 | return distanceComparison; 25 | } 26 | 27 | // Finally, rank the smaller region (by volume) higher 28 | // 29 | return _volume.CompareTo(other._volume); 30 | } 31 | 32 | public override string ToString() 33 | { 34 | return $"[Bots in range: {_inRange}] [Distance: {_distance}] [Volume: {_volume}]"; 35 | } 36 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D24/Comparers.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D24; 2 | 3 | public sealed class TargetPriorityComparer : IComparer 4 | { 5 | public int Compare(Group x, Group y) 6 | { 7 | return x.EffectivePower != y.EffectivePower 8 | ? x.EffectivePower.CompareTo(y.EffectivePower) 9 | : x.Units.Initiative.CompareTo(y.Units.Initiative); 10 | } 11 | } 12 | 13 | public sealed class AttackPriorityComparer : IComparer 14 | { 15 | public int Compare(Group x, Group y) 16 | { 17 | return x.Units.Initiative.CompareTo(y.Units.Initiative); 18 | } 19 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D24/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D24; 2 | 3 | public enum Team 4 | { 5 | ImmuneSystem = 0, 6 | Infection 7 | } 8 | 9 | public enum Resolution 10 | { 11 | Draw = 0, 12 | Winner 13 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D24/Group.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D24; 2 | 3 | using GroupKey = ValueTuple; 4 | 5 | public readonly struct Group 6 | { 7 | public Team Team { get; } 8 | public int Id { get; } 9 | public int Size { get; } 10 | public Unit Units { get; } 11 | 12 | public GroupKey Ck => (Team, Id); 13 | public int EffectivePower => Size * Units.Attack.Damage; 14 | public bool Dead => Size <= 0; 15 | 16 | public Group(Team team, int id, Unit units, int size) 17 | { 18 | Team = team; 19 | Id = id; 20 | Size = size; 21 | Units = units; 22 | } 23 | 24 | public override string ToString() 25 | { 26 | return $"{Team} Group {Id}"; 27 | } 28 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D24/Records.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D24; 2 | 3 | public readonly record struct Attack(string Type, int Damage); 4 | public readonly record struct Unit(int Hp, IReadOnlySet Weaknesses, IReadOnlySet Immunities, Attack Attack, int Initiative); 5 | public readonly record struct Result(Resolution Resolution, Team Winner, int Score); -------------------------------------------------------------------------------- /Solutions/Y2018/D24/State.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2018.D24; 2 | 3 | using GroupKey = ValueTuple; 4 | 5 | public sealed class State 6 | { 7 | private bool Drawn { get; } 8 | private Dictionary> MembersByTeam { get; } = new() 9 | { 10 | { Team.ImmuneSystem, new HashSet() }, 11 | { Team.Infection, new HashSet() } 12 | }; 13 | 14 | public Dictionary GroupsByCk { get; } = new(); 15 | 16 | public IEnumerable GroupKeys => GroupsByCk.Keys; 17 | public bool Resolved => Drawn || MembersByTeam.Values.Any(members => members.Count == 0); 18 | 19 | public State(IEnumerable groups, bool drawn = false) 20 | { 21 | Drawn = drawn; 22 | RegisterGroups(groups); 23 | } 24 | 25 | public Result GetResult() 26 | { 27 | if (!Resolved) 28 | { 29 | throw new NoSolutionException(); 30 | } 31 | 32 | return new Result( 33 | Resolution: Drawn ? Resolution.Draw : Resolution.Winner, 34 | Winner: Drawn ? default : GetWinningTeamInternal(), 35 | Score: GroupsByCk.Values.Sum(group => group.Size)); 36 | } 37 | 38 | private Team GetWinningTeamInternal() 39 | { 40 | return MembersByTeam[Team.Infection].Count == 0 41 | ? Team.ImmuneSystem 42 | : Team.Infection; 43 | } 44 | 45 | private void RegisterGroups(IEnumerable groups) 46 | { 47 | foreach (var group in groups) 48 | { 49 | GroupsByCk[(group.Team, group.Id)] = group; 50 | MembersByTeam[group.Team].Add(group.Id); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Solutions/Y2018/D25/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Collections; 2 | using Utilities.Geometry.Euclidean; 3 | 4 | namespace Solutions.Y2018.D25; 5 | 6 | [PuzzleInfo("Four-Dimensional Adventure", Topics.Vectors, Difficulty.Medium)] 7 | public sealed class Solution : SolutionBase 8 | { 9 | public override int Parts => 1; 10 | 11 | public override object Run(int part) 12 | { 13 | return part switch 14 | { 15 | 1 => CountConstellations(), 16 | _ => PuzzleNotSolvedString 17 | }; 18 | } 19 | 20 | private int CountConstellations() 21 | { 22 | var points = ParseInputLines(parseFunc: Vec4D.Parse); 23 | var disjointSet = new DisjointSet(); 24 | var adjacency = new Dictionary>(); 25 | 26 | foreach (var point in points) 27 | { 28 | adjacency[point] = points.Where(p => Vec4D.Distance( 29 | a: p, 30 | b: point, 31 | metric: Metric.Taxicab) <= 3); 32 | } 33 | 34 | foreach (var (point, adjacencies) in adjacency) 35 | { 36 | disjointSet.MakeSet(point); 37 | foreach (var adjacent in adjacencies) 38 | { 39 | disjointSet.MakeSet(adjacent); 40 | disjointSet.Union(point, adjacent); 41 | } 42 | } 43 | 44 | return disjointSet.PartitionsCount; 45 | } 46 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D01/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D01; 2 | 3 | [PuzzleInfo("The Tyranny of the Rocket Equation", Topics.Math, Difficulty.Easy)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var masses = ParseInputLines(parseFunc: int.Parse); 9 | return part switch 10 | { 11 | 1 => masses.Sum(GetNaiveFuelRequirement), 12 | 2 => masses.Sum(GetIterativeFuelRequirement), 13 | _ => PuzzleNotSolvedString 14 | }; 15 | } 16 | 17 | private static int GetNaiveFuelRequirement(int mass) 18 | { 19 | return mass / 3 - 2; 20 | } 21 | 22 | private static int GetIterativeFuelRequirement(int mass) 23 | { 24 | var total = 0; 25 | var fuel = GetNaiveFuelRequirement(mass); 26 | 27 | while (fuel > 0) 28 | { 29 | total += fuel; 30 | fuel = GetNaiveFuelRequirement(fuel); 31 | } 32 | 33 | return total; 34 | } 35 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D04/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Numerics; 2 | 3 | namespace Solutions.Y2019.D04; 4 | 5 | [PuzzleInfo("Secure Container", Topics.StringParsing, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var input = GetInputText(); 11 | var range = Range.Parse(input); 12 | 13 | return part switch 14 | { 15 | 1 => CountValidPasswords(range, minRun: 2, maxRun: 6), 16 | 2 => CountValidPasswords(range, minRun: 2, maxRun: 2), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static int CountValidPasswords(Range range, int minRun, int maxRun) 22 | { 23 | return Enumerable.Range(range.Min, range.Length).Count(p => IsValid(p, minRun, maxRun)); 24 | } 25 | 26 | private static bool IsValid(int password, int minRun, int maxRun) 27 | { 28 | var chars = new List(password.ToString()); 29 | var observedRuns = new HashSet(); 30 | var curObservedRun = 1; 31 | 32 | for (var i = 1; i < chars.Count; i++) 33 | { 34 | if (chars[i] < chars[i - 1]) 35 | { 36 | return false; 37 | } 38 | 39 | if (chars[i] == chars[i - 1]) 40 | { 41 | curObservedRun++; 42 | } 43 | else 44 | { 45 | observedRuns.Add(curObservedRun); 46 | curObservedRun = 1; 47 | } 48 | } 49 | 50 | observedRuns.Add(curObservedRun); 51 | return observedRuns.Any(r => r >= minRun && r <= maxRun); 52 | } 53 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D05/Solution.cs: -------------------------------------------------------------------------------- 1 | using Solutions.Y2019.IntCode; 2 | 3 | namespace Solutions.Y2019.D05; 4 | 5 | [PuzzleInfo("Sunny with a Chance of Asteroids", Topics.IntCode, Difficulty.Easy)] 6 | public sealed class Solution : IntCodeSolution 7 | { 8 | public override object Run(int part) 9 | { 10 | var program = LoadIntCodeProgram(); 11 | return part switch 12 | { 13 | 1 => RunTestProgram(program, systemId: 1), 14 | 2 => RunTestProgram(program, systemId: 5), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static long RunTestProgram(IList program, int systemId) 20 | { 21 | var vm = IntCodeVm.Create(program, systemId); 22 | vm.Run(); 23 | return vm.OutputBuffer.Last(); 24 | } 25 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D09/Solution.cs: -------------------------------------------------------------------------------- 1 | using Solutions.Y2019.IntCode; 2 | 3 | namespace Solutions.Y2019.D09; 4 | 5 | [PuzzleInfo("Sensor Boost", Topics.IntCode, Difficulty.Easy)] 6 | public sealed class Solution : IntCodeSolution 7 | { 8 | public override object Run(int part) 9 | { 10 | return part switch 11 | { 12 | 1 => RunBoostProgram(input: 1L), 13 | 2 => RunBoostProgram(input: 2L), 14 | _ => PuzzleNotSolvedString 15 | }; 16 | } 17 | 18 | private long RunBoostProgram(long input) 19 | { 20 | var vm = IntCodeVm.Create(LoadIntCodeProgram(), input); 21 | var ec = vm.Run(); 22 | 23 | return ec == IntCodeVm.ExitCode.Halted 24 | ? vm.OutputBuffer.Dequeue() 25 | : throw new NoSolutionException(message: $"Invalid VM exit code [{ec}]"); 26 | } 27 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D12/Moon.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D12; 2 | 3 | public enum Moon 4 | { 5 | Io, 6 | Europa, 7 | Ganymede, 8 | Callisto 9 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D12/State.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2019.D12; 4 | 5 | public readonly record struct State(Vec3D Pos, Vec3D Vel); -------------------------------------------------------------------------------- /Solutions/Y2019/D12/StateComp.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2019.D12; 4 | 5 | public readonly struct StateComp(Axis component, State state) : IEquatable 6 | { 7 | private int Pos { get; } = state.Pos[component]; 8 | private int Vel { get; } = state.Vel[component]; 9 | 10 | public bool Equals(StateComp other) 11 | { 12 | return Pos == other.Pos && Vel == other.Vel; 13 | } 14 | 15 | public override bool Equals(object? obj) 16 | { 17 | return obj is StateComp other && Equals(other); 18 | } 19 | 20 | public override int GetHashCode() 21 | { 22 | return HashCode.Combine(Pos, Vel); 23 | } 24 | 25 | public static bool operator ==(StateComp left, StateComp right) 26 | { 27 | return left.Equals(right); 28 | } 29 | 30 | public static bool operator !=(StateComp left, StateComp right) 31 | { 32 | return !left.Equals(right); 33 | } 34 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D13/GameObject.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D13; 2 | 3 | public enum GameObject 4 | { 5 | Empty, 6 | Wall, 7 | Block, 8 | Paddle, 9 | Ball 10 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D13/Joystick.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D13; 2 | 3 | public static class Joystick 4 | { 5 | public const long Neutral = 0; 6 | public const long Left = -1; 7 | public const long Right = 1; 8 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D14/Reaction.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D14; 2 | 3 | public readonly struct Reaction(IEnumerable reactants, Term product) 4 | { 5 | public IReadOnlyList Reactants { get; } = new List(reactants); 6 | public Term Product { get; } = product; 7 | 8 | public override string ToString() 9 | { 10 | var reactantStrings = Reactants.Select(r => $"{r.Amount} {r.Substance}"); 11 | return $"{string.Join(',', reactantStrings)} => {Product.Amount} {Product.Substance}"; 12 | } 13 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D14/Term.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D14; 2 | 3 | public readonly record struct Term(long Amount, string Substance); -------------------------------------------------------------------------------- /Solutions/Y2019/D15/Tile.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D15; 2 | 3 | public enum Tile 4 | { 5 | Wall = 0, 6 | Empty, 7 | Target 8 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D17/Records.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2019.D17; 4 | 5 | public readonly record struct State(IReadOnlySet Scaffolding, Pose2D Robot); -------------------------------------------------------------------------------- /Solutions/Y2019/D18/PathFinder.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D18; 2 | 3 | public sealed class PathFinder(Field field) 4 | { 5 | public int Run(bool ignoreDoors) 6 | { 7 | var initialState = State.Initial(field.StartPos); 8 | var queue = new Queue(collection: [initialState]); 9 | var visited = new HashSet(collection: [initialState]); 10 | 11 | while (queue.Count > 0) 12 | { 13 | var state = queue.Dequeue(); 14 | if (field.AllKeysFound(state)) 15 | { 16 | return state.Steps; 17 | } 18 | 19 | foreach (var adj in field.GetAdj(state.Pos)) 20 | { 21 | if (!ignoreDoors && field.CheckForDoorAt(adj, out var door) && !state.HasKey(char.ToLower(door))) 22 | { 23 | continue; 24 | } 25 | 26 | var next = field.CheckForKeyAt(adj, out var key) && !state.HasKey(key) 27 | ? state.AfterPickup(adj, key) 28 | : state.AfterStep(adj); 29 | 30 | if (visited.Add(next)) 31 | { 32 | queue.Enqueue(next); 33 | } 34 | } 35 | } 36 | 37 | throw new NoSolutionException(); 38 | } 39 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D18/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D18; 2 | 3 | [PuzzleInfo("Many-Worlds Interpretation", Topics.Graphs, Difficulty.Medium)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | return part switch 9 | { 10 | 1 => FindShortestPath(applyInputOverrides: false, ignoreDoors: false), 11 | 2 => FindShortestPath(applyInputOverrides: true, ignoreDoors: true), 12 | _ => PuzzleNotSolvedString 13 | }; 14 | } 15 | 16 | private int FindShortestPath(bool applyInputOverrides, bool ignoreDoors) 17 | { 18 | var fields = Field.Parse(GetInputLines(), applyInputOverrides); 19 | return fields.Sum(f => new PathFinder(f).Run(ignoreDoors)); 20 | } 21 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D19/Solution.cs: -------------------------------------------------------------------------------- 1 | using Solutions.Y2019.IntCode; 2 | using Utilities.Geometry.Euclidean; 3 | 4 | namespace Solutions.Y2019.D19; 5 | 6 | [PuzzleInfo("Tractor Beam", Topics.IntCode, Difficulty.Medium)] 7 | public sealed class Solution : IntCodeSolution 8 | { 9 | public override object Run(int part) 10 | { 11 | return part switch 12 | { 13 | 1 => CountBeamPoints(searchDimension: 50), 14 | 2 => FindShip(shipSize: 100), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private long FindShip(int shipSize) 20 | { 21 | var program = LoadIntCodeProgram(); 22 | var x = 0; 23 | var y = 0; 24 | 25 | while (!CheckPointInBeam(x + shipSize - 1, y, program)) 26 | { 27 | y += 1; 28 | while (!CheckPointInBeam(x, y + shipSize - 1, program)) 29 | { 30 | x += 1; 31 | } 32 | } 33 | 34 | return 10000L * x + y; 35 | } 36 | 37 | private int CountBeamPoints(int searchDimension) 38 | { 39 | var program = LoadIntCodeProgram(); 40 | var searchArea = new Aabb2D( 41 | xMin: 0, 42 | xMax: searchDimension - 1, 43 | yMin: 0, 44 | yMax: searchDimension - 1); 45 | 46 | return searchArea.Sum(point => CheckPointInBeam(point.X, point.Y, program) ? 1 : 0); 47 | } 48 | 49 | private static bool CheckPointInBeam(int x, int y, IList program) 50 | { 51 | var inputs = new long[] { x, y }; 52 | var vm = IntCodeVm.Create(program, inputs); 53 | 54 | vm.Run(); 55 | return vm.OutputBuffer.Dequeue() > 0L; 56 | } 57 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D20/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D20; 2 | 3 | public enum MazeType 4 | { 5 | Static = 0, 6 | Recursive 7 | } 8 | 9 | public enum EntranceType 10 | { 11 | Inner = 0, 12 | Outer 13 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D20/PortalKey.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D20; 2 | 3 | public readonly struct PortalKey(char c1, char c2) : IEquatable 4 | { 5 | private readonly char _c1 = c1; 6 | private readonly char _c2 = c2; 7 | 8 | public bool Equals(PortalKey other) 9 | { 10 | return _c1 == other._c1 && _c2 == other._c2; 11 | } 12 | 13 | public override bool Equals(object? obj) 14 | { 15 | return obj is PortalKey other && Equals(other); 16 | } 17 | 18 | public override int GetHashCode() 19 | { 20 | return HashCode.Combine(_c1, _c2); 21 | } 22 | 23 | public static bool operator ==(PortalKey left, PortalKey right) 24 | { 25 | return left.Equals(right); 26 | } 27 | 28 | public static bool operator !=(PortalKey left, PortalKey right) 29 | { 30 | return !left.Equals(right); 31 | } 32 | 33 | public override string ToString() 34 | { 35 | return new string(new[] { _c1, _c2 }); 36 | } 37 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D20/Records.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2019.D20; 4 | 5 | public readonly record struct State(Vec2D Pos, int Depth); 6 | public readonly record struct PortalEntrance(Vec2D Pos, EntranceType Type); -------------------------------------------------------------------------------- /Solutions/Y2019/D21/Springdroid.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Solutions.Y2019.IntCode; 3 | 4 | namespace Solutions.Y2019.D21; 5 | 6 | public static class Springdroid 7 | { 8 | private const int AsciiRange = 255; 9 | 10 | public static bool Run(IList firmware, IEnumerable program, out string output) 11 | { 12 | var input = Compile(program); 13 | var vm = IntCodeVm.Create(firmware, input); 14 | 15 | vm.Run(); 16 | 17 | if (vm.OutputBuffer.Last() > AsciiRange) 18 | { 19 | output = vm.OutputBuffer.Last().ToString(); 20 | return true; 21 | } 22 | 23 | output = ReadAsciiOutput(vm); 24 | return false; 25 | } 26 | 27 | private static IEnumerable Compile(IEnumerable script) 28 | { 29 | foreach (var instr in script) 30 | { 31 | foreach (var c in instr) 32 | { 33 | yield return c; 34 | } 35 | 36 | yield return '\n'; 37 | } 38 | } 39 | 40 | private static string ReadAsciiOutput(IntCodeVm vm) 41 | { 42 | var sb = new StringBuilder(); 43 | while (vm.OutputBuffer.Any()) 44 | { 45 | sb.Append((char)vm.OutputBuffer.Dequeue()); 46 | } 47 | 48 | return sb.ToString(); 49 | } 50 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D23/Computer.cs: -------------------------------------------------------------------------------- 1 | using Solutions.Y2019.IntCode; 2 | 3 | namespace Solutions.Y2019.D23; 4 | 5 | public sealed class Computer 6 | { 7 | private readonly int _id; 8 | private readonly IntCodeVm _vm; 9 | private readonly Queue _inputBuffer = new(); 10 | 11 | public event EventHandler? PacketEmitted; 12 | 13 | public Computer(int id, IList firmware) 14 | { 15 | _id = id; 16 | _vm = IntCodeVm.Create(firmware, id); 17 | _vm.AwaitingInput += OnAwaitingInput; 18 | _vm.OutputEmitted += OnOutputEmitted; 19 | } 20 | 21 | public void Tick() 22 | { 23 | _vm.Run(); 24 | } 25 | 26 | public void EnqueuePacket(Packet packet) 27 | { 28 | _inputBuffer.Enqueue(packet); 29 | } 30 | 31 | private void OnAwaitingInput(object? sender, EventArgs e) 32 | { 33 | if (_inputBuffer.Count == 0) 34 | { 35 | _vm.InputBuffer.Enqueue(item: -1L); 36 | return; 37 | } 38 | 39 | var packet = _inputBuffer.Dequeue(); 40 | _vm.InputBuffer.Enqueue(packet.X); 41 | _vm.InputBuffer.Enqueue(packet.Y); 42 | } 43 | 44 | private void OnOutputEmitted(object? sender, EventArgs e) 45 | { 46 | if (_vm.OutputBuffer.Count < 3) 47 | { 48 | return; 49 | } 50 | 51 | RaisePacketEmitted(new Packet( 52 | RecipientId: (int)_vm.OutputBuffer.Dequeue(), 53 | X: _vm.OutputBuffer.Dequeue(), 54 | Y: _vm.OutputBuffer.Dequeue())); 55 | } 56 | 57 | private void RaisePacketEmitted(Packet payload) 58 | { 59 | PacketEmitted?.Invoke(this, new PacketEventArgs(_id, payload)); 60 | } 61 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D23/Nat.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D23; 2 | 3 | public sealed class Nat 4 | { 5 | private Packet Packet { get; set; } 6 | public bool BufferInitialized { get; private set; } 7 | 8 | public void WriteBuffer(long x, long y) 9 | { 10 | Packet = new Packet(RecipientId: 0, x, y); 11 | BufferInitialized = true; 12 | } 13 | 14 | public Packet ReadBuffer() 15 | { 16 | return BufferInitialized ? Packet : throw new NoSolutionException(); 17 | } 18 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D23/Packet.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D23; 2 | 3 | public readonly record struct Packet(int RecipientId, long X, long Y); 4 | 5 | public sealed class PacketEventArgs(int senderId, Packet payload) : EventArgs 6 | { 7 | public int SenderId { get; } = senderId; 8 | public Packet Payload { get; } = payload; 9 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D23/Solution.cs: -------------------------------------------------------------------------------- 1 | using Solutions.Y2019.IntCode; 2 | 3 | namespace Solutions.Y2019.D23; 4 | 5 | [PuzzleInfo("Category Six", Topics.IntCode, Difficulty.Hard)] 6 | public sealed class Solution : IntCodeSolution 7 | { 8 | public override object Run(int part) 9 | { 10 | var firmware = LoadIntCodeProgram(); 11 | var networkAwaiter = new NetworkAwaiter(firmware); 12 | 13 | return part switch 14 | { 15 | 1 => networkAwaiter.WaitForMessage(targetRecipient: 255).Result, 16 | 2 => networkAwaiter.WaitForRepeatedNatMessage().Result, 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D24/GridType.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.D24; 2 | 3 | public enum GridType 4 | { 5 | Static = 0, 6 | Recursive 7 | } -------------------------------------------------------------------------------- /Solutions/Y2019/D25/Cheats.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Solutions.Y2019.D25; 4 | 5 | public static class Cheats 6 | { 7 | public const string TestInventoryCommand = "north"; 8 | public static readonly Regex PasscodeRegex = new(@"(\d+)"); 9 | public static readonly Queue AutomationCommands = new([ 10 | "west", 11 | "take semiconductor", 12 | "west", 13 | "take planetoid", 14 | "west", 15 | "take food ration", 16 | "west", 17 | "take fixed point", 18 | "west", 19 | "take klein bottle", 20 | "east", 21 | "south", 22 | "west", 23 | "take weather machine", 24 | "east", 25 | "north", 26 | "east", 27 | "east", 28 | "south", 29 | "south", 30 | "south", 31 | "take pointer", 32 | "north", 33 | "north", 34 | "east", 35 | "take coin", 36 | "east", 37 | "north", 38 | "east", 39 | "inv" 40 | ]); 41 | public static readonly IReadOnlyList InventoryItems = new List 42 | { 43 | "semiconductor", 44 | "planetoid", 45 | "food ration", 46 | "fixed point", 47 | "klein bottle", 48 | "weather machine", 49 | "coin", 50 | "pointer" 51 | }; 52 | } -------------------------------------------------------------------------------- /Solutions/Y2019/IntCode/Instruction.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.IntCode; 2 | 3 | internal readonly struct Instruction(OpCode opCode, IEnumerable paramModes) 4 | { 5 | private List ParamModes { get; } = [..paramModes]; 6 | public OpCode OpCode { get; } = opCode; 7 | 8 | public ParameterMode GetParamMode(int paramIndex) 9 | { 10 | return paramIndex < ParamModes.Count 11 | ? ParamModes[paramIndex] 12 | : ParameterMode.Pos; 13 | } 14 | } -------------------------------------------------------------------------------- /Solutions/Y2019/IntCode/IntCodeSolution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.IntCode; 2 | 3 | public abstract class IntCodeSolution : SolutionBase 4 | { 5 | protected IList LoadIntCodeProgram() 6 | { 7 | return new List(GetInputText().Split(separator: ',').Select(long.Parse)); 8 | } 9 | } -------------------------------------------------------------------------------- /Solutions/Y2019/IntCode/IntCodeVmFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.IntCode; 2 | 3 | public partial class IntCodeVm 4 | { 5 | public static IntCodeVm Create(IList program) 6 | { 7 | return new IntCodeVm(program: program, inputs: []); 8 | } 9 | 10 | public static IntCodeVm Create(IList program, IEnumerable inputs) 11 | { 12 | return new IntCodeVm(program, inputs); 13 | } 14 | 15 | public static IntCodeVm Create(IList program, long input) 16 | { 17 | return new IntCodeVm(program, inputs: [input]); 18 | } 19 | } -------------------------------------------------------------------------------- /Solutions/Y2019/IntCode/OpCode.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.IntCode; 2 | 3 | internal enum OpCode 4 | { 5 | Add = 1, 6 | Mul = 2, 7 | Inp = 3, 8 | Out = 4, 9 | Jit = 5, 10 | Jif = 6, 11 | Lst = 7, 12 | Eql = 8, 13 | Rbo = 9, 14 | Hlt = 99 15 | } -------------------------------------------------------------------------------- /Solutions/Y2019/IntCode/ParameterMode.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2019.IntCode; 2 | 3 | internal enum ParameterMode 4 | { 5 | Pos = 0, 6 | Imm = 1, 7 | Rel = 2 8 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D01/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D01; 2 | 3 | [PuzzleInfo("Report Repair", Topics.Math, Difficulty.Easy)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var numbers = ParseInputLines(parseFunc: int.Parse).ToHashSet(); 9 | return part switch 10 | { 11 | 1 => GetSumPairProduct(targetSum: 2020, numbers), 12 | 2 => GetSumTripletProduct(targetSum: 2020, numbers), 13 | _ => PuzzleNotSolvedString 14 | }; 15 | } 16 | 17 | private static int GetSumPairProduct(int targetSum, HashSet numbers) 18 | { 19 | foreach (var n1 in numbers) 20 | { 21 | var n2 = targetSum - n1; 22 | if (numbers.Contains(n2)) 23 | { 24 | return n1 * n2; 25 | } 26 | } 27 | 28 | throw new NoSolutionException(); 29 | } 30 | 31 | private static int GetSumTripletProduct(int targetSum, HashSet numbers) 32 | { 33 | foreach (var n1 in numbers) 34 | foreach (var n2 in numbers) 35 | { 36 | var n3 = targetSum - n1 - n2; 37 | if (numbers.Contains(n3)) 38 | { 39 | return n1 * n2 * n3; 40 | } 41 | } 42 | 43 | throw new NoSolutionException(); 44 | } 45 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D05/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D05; 2 | 3 | [PuzzleInfo("Binary Boarding", Topics.BitwiseOperations, Difficulty.Medium)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var boardingPasses = GetInputLines(); 9 | return part switch 10 | { 11 | 1 => boardingPasses.Max(GetSeatId), 12 | 2 => FindMissingSeat(boardingPasses), 13 | _ => PuzzleNotSolvedString 14 | }; 15 | } 16 | 17 | private static int FindMissingSeat(IEnumerable boardingPasses) 18 | { 19 | var orderedSeatIds = boardingPasses 20 | .Select(GetSeatId) 21 | .Order() 22 | .ToList(); 23 | 24 | for (var i = 1; i < orderedSeatIds.Count; i++) 25 | { 26 | if (orderedSeatIds[i] != orderedSeatIds[i - 1] + 1) 27 | { 28 | return orderedSeatIds[i] - 1; 29 | } 30 | } 31 | 32 | throw new NoSolutionException(); 33 | } 34 | 35 | private static int GetSeatId(string boardingPass) 36 | { 37 | const int numRows = 128; 38 | const int numCols = 8; 39 | 40 | var rowBits = (int)Math.Round(Math.Log2(numRows)); 41 | var row = Convert.ToInt16(boardingPass[..rowBits] 42 | .Replace(oldChar: 'F', newChar: '0') 43 | .Replace(oldChar: 'B', newChar: '1'), fromBase: 2); 44 | var col = Convert.ToInt16(boardingPass[rowBits..] 45 | .Replace(oldChar: 'L', newChar: '0') 46 | .Replace(oldChar: 'R', newChar: '1'), fromBase: 2); 47 | 48 | return numCols * row + col; 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D06/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2020.D06; 4 | 5 | [PuzzleInfo("Custom Customs", Topics.StringParsing, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var lines = GetInputLines(); 11 | var groupAnswers = lines.ChunkByNonEmpty(); 12 | 13 | return part switch 14 | { 15 | 1 => groupAnswers.Sum(GetUniqueGroupAnswers), 16 | 2 => groupAnswers.Sum(GetUnanimousGroupAnswers), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static int GetUniqueGroupAnswers(IList groupAnswers) 22 | { 23 | return groupAnswers.SelectMany(g => g).Distinct().Count(); 24 | } 25 | 26 | private static int GetUnanimousGroupAnswers(IList groupAnswers) 27 | { 28 | return groupAnswers.IntersectAll().Count; 29 | } 30 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D07/BagContent.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D07; 2 | 3 | public readonly record struct BagContent(int Count, string Colour); -------------------------------------------------------------------------------- /Solutions/Y2020/D08/Machine.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D08; 2 | 3 | public sealed class Machine 4 | { 5 | public const string Nop = "nop"; 6 | public const string Jmp = "jmp"; 7 | private const string Acc = "acc"; 8 | 9 | public readonly record struct Result(int Acc, bool Looped); 10 | 11 | public static Result Run(List<(string Op, int Arg)> instructions) 12 | { 13 | var looped = false; 14 | var acc = 0; 15 | var pc = 0; 16 | var pcValueSet = new HashSet([0]); 17 | 18 | while (pc < instructions.Count) 19 | { 20 | var (op, arg) = instructions[pc]; 21 | switch (op) 22 | { 23 | case Nop: 24 | pc++; 25 | break; 26 | case Jmp: 27 | pc += arg; 28 | break; 29 | case Acc: 30 | acc += arg; 31 | pc++; 32 | break; 33 | } 34 | 35 | if (!pcValueSet.Add(pc)) 36 | { 37 | looped = true; 38 | break; 39 | } 40 | } 41 | 42 | return new Result(acc, looped); 43 | } 44 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D09/Sum.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D09; 2 | 3 | public sealed class Sum 4 | { 5 | public long Value { get; private set; } 6 | private List Elements { get; } = []; 7 | 8 | public Sum(long initial) 9 | { 10 | Value = initial; 11 | Elements.Add(initial); 12 | } 13 | 14 | public long Add(long number) 15 | { 16 | Value += number; 17 | Elements.Add(number); 18 | return Value; 19 | } 20 | 21 | public long GetExtremaSum() 22 | { 23 | return Elements.Min() + Elements.Max(); 24 | } 25 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D11/Concern.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D11; 2 | 3 | public enum Concern 4 | { 5 | Adjacent, 6 | Visible 7 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D14/MaskSimple.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D14; 2 | 3 | public readonly struct MaskSimple 4 | { 5 | private readonly ulong _setMask; 6 | private readonly ulong _clearMask; 7 | 8 | public MaskSimple(string maskStr) 9 | { 10 | var setMask = 0UL; 11 | var clearMask = ~0UL; 12 | 13 | for (var i = 0; i < maskStr.Length; i++) 14 | { 15 | switch (maskStr[maskStr.Length - i - 1]) 16 | { 17 | case '0': 18 | clearMask &= ~(1UL << i); 19 | continue; 20 | case '1': 21 | setMask |= 1UL << i; 22 | continue; 23 | } 24 | } 25 | 26 | _setMask = setMask; 27 | _clearMask = clearMask; 28 | } 29 | 30 | public ulong Apply(ulong value) 31 | { 32 | value |= _setMask; 33 | value &= _clearMask; 34 | 35 | return value; 36 | } 37 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D14/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D14; 2 | 3 | [PuzzleInfo("Docking Data", Topics.Assembly|Topics.BitwiseOperations|Topics.Simulation, Difficulty.Hard)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var program = GetInputLines(); 9 | return part switch 10 | { 11 | 1 => Machine.RunV1(program), 12 | 2 => Machine.RunV2(program), 13 | _ => PuzzleNotSolvedString 14 | }; 15 | } 16 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D15/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D15; 2 | 3 | [PuzzleInfo("Rambunctious Recitation", Topics.Simulation, Difficulty.Medium)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var initialNumbers = GetInitialNumbers(GetInputText()); 9 | return part switch 10 | { 11 | 1 => GetNthSpokenNumber(initialNumbers, n: 2020), 12 | 2 => GetNthSpokenNumber(initialNumbers, n: 30000000), 13 | _ => PuzzleNotSolvedString 14 | }; 15 | } 16 | 17 | private static int GetNthSpokenNumber(IEnumerable startingNumbers, int n) 18 | { 19 | var turnNumber = 1; 20 | var lastSpoken = 0; 21 | var spokenMap = new Dictionary(); 22 | 23 | foreach (var number in startingNumbers) 24 | { 25 | lastSpoken = number; 26 | spokenMap[number] = (Last: turnNumber, Previous: turnNumber); 27 | 28 | turnNumber++; 29 | } 30 | 31 | while (turnNumber <= n) 32 | { 33 | lastSpoken = spokenMap[lastSpoken].Last - spokenMap[lastSpoken].Previous; 34 | spokenMap[lastSpoken] = spokenMap.ContainsKey(lastSpoken) 35 | ? spokenMap[lastSpoken] = (Last: turnNumber, Previous: spokenMap[lastSpoken].Last) 36 | : spokenMap[lastSpoken] = (Last: turnNumber, Previous: turnNumber); 37 | 38 | turnNumber++; 39 | } 40 | 41 | return lastSpoken; 42 | } 43 | 44 | private static IEnumerable GetInitialNumbers(string input) 45 | { 46 | return input.Split(separator: ',').Select(int.Parse); 47 | } 48 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D18/Operators.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D18; 2 | 3 | public static class Operators 4 | { 5 | public const char Add = '+'; 6 | public const char Mul = '*'; 7 | public const char Open = '('; 8 | public const char Close = ')'; 9 | 10 | public delegate long Operator(long lhs, long rhs); 11 | 12 | public static readonly Dictionary Delegates = new() 13 | { 14 | { Add, (a, b) => a + b }, 15 | { Mul, (a, b) => a * b } 16 | }; 17 | public static readonly Dictionary EqualPrecedence = new() 18 | { 19 | { Add, 0 }, 20 | { Mul, 0 } 21 | }; 22 | public static readonly Dictionary AddPrecedence = new() 23 | { 24 | { Add, 0 }, 25 | { Mul, 1 } 26 | }; 27 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D20/EdgeFingerprint.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D20; 2 | 3 | public readonly struct EdgeFingerprint 4 | { 5 | private readonly string _forwards; 6 | private readonly string _backwards; 7 | 8 | public EdgeFingerprint(string edgeString) 9 | { 10 | _forwards = edgeString; 11 | _backwards = string.Concat(_forwards.Reverse()); 12 | } 13 | 14 | public bool IsCongruentTo(EdgeFingerprint other) 15 | { 16 | return _forwards == other._forwards || _forwards == other._backwards; 17 | } 18 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D20/SeaMonster.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2020.D20; 4 | 5 | public static class SeaMonster 6 | { 7 | public const char Chr = '#'; 8 | public static int Width => 20; 9 | public static int Height => 2; 10 | 11 | public static readonly HashSet Pattern = 12 | [ 13 | new(X: 0, Y: 1), 14 | new(X: 1, Y: 0), 15 | new(X: 4, Y: 0), 16 | new(X: 5, Y: 1), 17 | new(X: 6, Y: 1), 18 | new(X: 7, Y: 0), 19 | new(X: 10, Y: 0), 20 | new(X: 11, Y: 1), 21 | new(X: 12, Y: 1), 22 | new(X: 13, Y: 0), 23 | new(X: 16, Y: 0), 24 | new(X: 17, Y: 1), 25 | new(X: 18, Y: 1), 26 | new(X: 18, Y: 2), 27 | new(X: 19, Y: 1) 28 | ]; 29 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D21/Food.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D21; 2 | 3 | public sealed class Food(IEnumerable ingredients, IEnumerable listedAllergens) 4 | { 5 | public HashSet Ingredients { get; } = [..ingredients]; 6 | public HashSet ListedAllergens { get; } = [..listedAllergens]; 7 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D22/Deck.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D22; 2 | 3 | public sealed class Deck(IEnumerable initial) 4 | { 5 | private readonly Queue _cards = new(collection: initial); 6 | 7 | public bool HasCards => _cards.Count != 0; 8 | public int CardsRemaining => _cards.Count; 9 | public string State => string.Join(',', _cards); 10 | 11 | public Deck Copy(int numCards) 12 | { 13 | return new Deck(_cards.Take(numCards)); 14 | } 15 | 16 | public int DrawFromTop() 17 | { 18 | return _cards.Dequeue(); 19 | } 20 | 21 | public void AddToBottom(int card) 22 | { 23 | _cards.Enqueue(card); 24 | } 25 | 26 | public int Score() 27 | { 28 | var score = 0; 29 | while (_cards.Count != 0) 30 | { 31 | score += _cards.Count * _cards.Dequeue(); 32 | } 33 | return score; 34 | } 35 | } -------------------------------------------------------------------------------- /Solutions/Y2020/D25/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2020.D25; 2 | 3 | [PuzzleInfo("Combo Breaker", Topics.Math, Difficulty.Medium)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | private const long DeviceSubject = 7L; 7 | private const long Mod = 20201227L; 8 | 9 | public override int Parts => 1; 10 | 11 | public override object Run(int part) 12 | { 13 | return part switch 14 | { 15 | 1 => CrackEncryption(ParsePublicKeys(GetInputLines())), 16 | _ => PuzzleNotSolvedString 17 | }; 18 | } 19 | 20 | private static long CrackEncryption((long Key1, long Key2) publicKeys) 21 | { 22 | return Transform(publicKeys.Key2, FindLoopSize(DeviceSubject, publicKeys.Key1)); 23 | } 24 | 25 | private static int FindLoopSize(long subject, long pubKey) 26 | { 27 | var loops = 0; 28 | var value = 1L; 29 | 30 | while (value != pubKey) 31 | { 32 | value = Loop(subject, value); 33 | loops++; 34 | } 35 | 36 | return loops; 37 | } 38 | 39 | private static long Transform(long subject, int numLoops) 40 | { 41 | var value = 1L; 42 | for (var i = 0; i < numLoops; i++) 43 | { 44 | value = Loop(subject, value); 45 | } 46 | return value; 47 | } 48 | 49 | private static long Loop(long subject, long value) 50 | { 51 | return value * subject % Mod; 52 | } 53 | 54 | private static (long Key1, long Key2) ParsePublicKeys(string[] input) 55 | { 56 | return (long.Parse(input[0]), long.Parse(input[1])); 57 | } 58 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D01/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D01; 2 | 3 | [PuzzleInfo("Sonar Sweep", Topics.None, Difficulty.Easy)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | return part switch 9 | { 10 | 1 => CountDepthIncreases(windowSize: 1), 11 | 2 => CountDepthIncreases(windowSize: 3), 12 | _ => PuzzleNotSolvedString 13 | }; 14 | } 15 | 16 | private int CountDepthIncreases(int windowSize) 17 | { 18 | var numIncreases = 0; 19 | var window = new Queue(windowSize); 20 | 21 | var depths = GetInputLines() 22 | .Select(int.Parse) 23 | .ToList(); 24 | 25 | foreach (var depth in depths) 26 | { 27 | if (window.Count < windowSize) 28 | { 29 | window.Enqueue(depth); 30 | continue; 31 | } 32 | 33 | var prevSum = window.Sum(); 34 | window.Dequeue(); 35 | window.Enqueue(depth); 36 | var curSum = window.Sum(); 37 | 38 | if (curSum > prevSum) 39 | { 40 | numIncreases++; 41 | } 42 | } 43 | 44 | return numIncreases; 45 | } 46 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D03/BitCriteria.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D03; 2 | 3 | public enum BitCriteria 4 | { 5 | MostCommon = 0, 6 | LeastCommon 7 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D06/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D06; 2 | 3 | [PuzzleInfo("Lanternfish", Topics.Math | Topics.Simulation, Difficulty.Medium, favourite: true)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | private const int ResetTo = 6; 7 | private const int SpawnAt = 8; 8 | 9 | public override object Run(int part) 10 | { 11 | return part switch 12 | { 13 | 1 => ModelLanternFish(GetInitialState(), days: 80), 14 | 2 => ModelLanternFish(GetInitialState(), days: 256), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static long ModelLanternFish(IEnumerable initialState, int days) 20 | { 21 | var internalTimerCount = new long[SpawnAt + 1]; 22 | foreach (var timer in initialState) 23 | { 24 | internalTimerCount[timer]++; 25 | } 26 | 27 | for (var day = 0; day < days; day++) 28 | { 29 | var readyToSpawn = internalTimerCount[0]; 30 | for (var t = 0; t < SpawnAt; t++) 31 | { 32 | internalTimerCount[t] = internalTimerCount[t + 1]; 33 | } 34 | 35 | internalTimerCount[ResetTo] += readyToSpawn; 36 | internalTimerCount[SpawnAt] = readyToSpawn; 37 | } 38 | 39 | return internalTimerCount.Sum(); 40 | } 41 | 42 | private IEnumerable GetInitialState() 43 | { 44 | return GetInputText() 45 | .Split(separator: ',') 46 | .Select(int.Parse); 47 | } 48 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D07/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2021.D07; 4 | 5 | [PuzzleInfo("The Treachery of Whales", Topics.Math, Difficulty.Medium)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | return part switch 11 | { 12 | 1 => CalculateMinCumulativeCost(n => n), 13 | 2 => CalculateMinCumulativeCost(n => n * (n + 1)/2), 14 | _ => PuzzleNotSolvedString 15 | }; 16 | } 17 | 18 | private int CalculateMinCumulativeCost(Func distanceCostFunc) 19 | { 20 | var positions = GetPositions(); 21 | var distinctPositions = positions.Distinct(); 22 | var best = int.MaxValue; 23 | 24 | foreach (var distinct in distinctPositions) 25 | { 26 | best = Math.Min( 27 | val1: best, 28 | val2: positions.Sum(pos => distanceCostFunc(Math.Abs(distinct - pos)))); 29 | } 30 | 31 | return best; 32 | } 33 | 34 | private int[] GetPositions() 35 | { 36 | return GetInputText().ParseInts(); 37 | } 38 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D08/DisplayObservation.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D08; 2 | 3 | public readonly struct DisplayObservation(IEnumerable uniqueSignalPatterns, IEnumerable outputDigits) 4 | { 5 | public List UniqueSegmentPatterns { get; } = [..uniqueSignalPatterns]; 6 | public List OutputDigitSegments { get; } = [..outputDigits]; 7 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D11/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | using Utilities.Geometry.Euclidean; 3 | 4 | namespace Solutions.Y2021.D11; 5 | 6 | [PuzzleInfo("Dumbo Octopus", Topics.Simulation, Difficulty.Medium)] 7 | public sealed class Solution : SolutionBase 8 | { 9 | public override object Run(int part) 10 | { 11 | var input = GetInputLines(); 12 | var state = Grid2D.MapChars(strings: input, elementFunc:StringExtensions.AsDigit); 13 | var grid = new OctopusGrid(state); 14 | 15 | return part switch 16 | { 17 | 1 => grid.CountFlashes(steps: 100, type: OctopusGrid.FlashType.Single), 18 | 2 => grid.CountStepsUntilFlash(type: OctopusGrid.FlashType.All), 19 | _ => PuzzleNotSolvedString 20 | }; 21 | } 22 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D12/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Collections; 2 | 3 | namespace Solutions.Y2021.D12; 4 | 5 | [PuzzleInfo("Passage Pathing", Topics.Graphs|Topics.Recursion, Difficulty.Medium, favourite: true)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | return part switch 11 | { 12 | 1 => CountPaths(bonusSmallCaveVisit: false), 13 | 2 => CountPaths(bonusSmallCaveVisit: true), 14 | _ => PuzzleNotSolvedString 15 | }; 16 | } 17 | 18 | private int CountPaths(bool bonusSmallCaveVisit) 19 | { 20 | var adjacencyMap = ParseAdjacencyMap(GetInputLines()); 21 | var caveTraverser = new PathFinder(adjacencyMap, bonusSmallCaveVisit); 22 | var numPaths = 0; 23 | 24 | void OnPathFound() 25 | { 26 | numPaths++; 27 | } 28 | 29 | caveTraverser.PathFound += OnPathFound; 30 | caveTraverser.Run(); 31 | 32 | return numPaths; 33 | } 34 | 35 | private static DefaultDict> ParseAdjacencyMap(IEnumerable lines) 36 | { 37 | var adjacencyMap = new DefaultDict>(defaultSelector: _ => []); 38 | foreach (var line in lines) 39 | { 40 | var vertices = line.Split(separator: '-'); 41 | var v1 = vertices[0]; 42 | var v2 = vertices[1]; 43 | 44 | adjacencyMap[v1].Add(v2); 45 | adjacencyMap[v2].Add(v1); 46 | } 47 | return adjacencyMap; 48 | } 49 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D13/FoldType.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D13; 2 | 3 | public enum FoldType 4 | { 5 | Horizontal, 6 | Vertical 7 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D14/Rule.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D14; 2 | 3 | public readonly struct Rule 4 | { 5 | public char Lhs { get; init; } 6 | public char Rhs { get; init; } 7 | public char Insert { get; init; } 8 | 9 | public (char, char) MatchKey => (Lhs, Rhs); 10 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D16/Operator.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D16; 2 | 3 | public enum Operator 4 | { 5 | Sum, 6 | Product, 7 | Minimum, 8 | Maximum, 9 | Identity, 10 | GreaterThan, 11 | LessThan, 12 | EqualTo 13 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D16/Section.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D16; 2 | 3 | public enum Section 4 | { 5 | Version, 6 | TypeId, 7 | LengthTypeId, 8 | SubPacketCount, 9 | SubPacketBits 10 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D18/Element.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D18; 2 | 3 | public readonly struct Element 4 | { 5 | public static readonly Element Open = new (Type.Open); 6 | public static readonly Element Close = new (Type.Close); 7 | public static readonly Element Delim = new (Type.Delim); 8 | 9 | public Type ElementType { get; } 10 | public int Value { get; } 11 | 12 | private Element(Type elementType) 13 | { 14 | ElementType = elementType; 15 | Value = 0; 16 | } 17 | 18 | public Element(int value) : this(Type.Value) 19 | { 20 | Value = value; 21 | } 22 | 23 | public override string ToString() 24 | { 25 | return ElementType switch 26 | { 27 | Type.Open => "[", 28 | Type.Close => "]", 29 | Type.Delim => ",", 30 | Type.Value => Value.ToString(), 31 | _ => throw new ArgumentOutOfRangeException() 32 | }; 33 | } 34 | 35 | public enum Type 36 | { 37 | Open, 38 | Close, 39 | Delim, 40 | Value 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Solutions/Y2021/D18/SnailfishParser.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D18; 2 | 3 | using SfNumber = List; 4 | 5 | public static class SnailfishParser 6 | { 7 | private const char Open = '['; 8 | private const char Close = ']'; 9 | private const char Delim = ','; 10 | 11 | private static readonly HashSet SyntaxSet = 12 | [ 13 | Open, 14 | Close, 15 | Delim 16 | ]; 17 | 18 | public static SfNumber Parse(string number) 19 | { 20 | var elements = new SfNumber(); 21 | var valueBuffer = new Queue(); 22 | 23 | foreach (var c in number) 24 | { 25 | if (SyntaxSet.Contains(c) && valueBuffer.Count > 0) 26 | { 27 | var valueString = string.Concat(valueBuffer); 28 | var valueElement = new Element(int.Parse(valueString)); 29 | 30 | valueBuffer.Clear(); 31 | elements.Add(valueElement); 32 | } 33 | 34 | switch (c) 35 | { 36 | case Open: 37 | elements.Add(Element.Open); 38 | continue; 39 | case Close: 40 | elements.Add(Element.Close); 41 | continue; 42 | case Delim: 43 | elements.Add(Element.Delim); 44 | continue; 45 | } 46 | 47 | valueBuffer.Enqueue(c); 48 | } 49 | 50 | return elements; 51 | } 52 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D19/Map.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2021.D19; 4 | 5 | public sealed class Map 6 | { 7 | public Dictionary KnownScanners { get; } = new(); 8 | public Dictionary> KnownBeacons { get; } = new(); 9 | 10 | public Map(Reporting referenceReporting) 11 | { 12 | KnownScanners.Add(referenceReporting.ScannerId, Vec3D.Zero); 13 | KnownBeacons.Add(referenceReporting.ScannerId, [..referenceReporting.Beacons]); 14 | } 15 | 16 | public int GetDistinctBeaconCount() 17 | { 18 | return KnownBeacons 19 | .SelectMany(kvp => kvp.Value) 20 | .Distinct() 21 | .Count(); 22 | } 23 | 24 | public int GetMaxScannerDistance() 25 | { 26 | var max = 0; 27 | var scanners = KnownScanners.Values.ToList(); 28 | 29 | // ReSharper disable once LoopCanBeConvertedToQuery 30 | for (var i = 0; i < scanners.Count; i++) 31 | for (var j = 0; j < scanners.Count; j++) 32 | { 33 | if (i != j) 34 | { 35 | max = Math.Max(max, Vec3D.Distance(scanners[i], scanners[j], metric: Metric.Taxicab)); 36 | } 37 | } 38 | 39 | return max; 40 | } 41 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D19/Records.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2021.D19; 4 | 5 | public readonly record struct Transform(Quaternion FacingTransform, Axis RotationAxis); 6 | public readonly record struct Reporting(int ScannerId, Vec3D[] Beacons); -------------------------------------------------------------------------------- /Solutions/Y2021/D21/DeterministicDie.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D21; 2 | 3 | public sealed class DeterministicDie 4 | { 5 | private const int Sides = 100; 6 | private int _nextRollValue = 1; 7 | 8 | public int NumRolls { get; private set; } 9 | 10 | public int Roll() 11 | { 12 | NumRolls++; 13 | var value = _nextRollValue++; 14 | 15 | if (_nextRollValue > Sides) 16 | { 17 | _nextRollValue = 1; 18 | } 19 | 20 | return value; 21 | } 22 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D21/Records.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2021.D21; 2 | 3 | public record struct Player(int Position, int Score); 4 | public record struct GameState(Player P1, Player P2); 5 | public record struct WinCounts(long P1, long P2); -------------------------------------------------------------------------------- /Solutions/Y2021/D23/Input.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2021.D23; 4 | 5 | public static class Input 6 | { 7 | public static readonly Dictionary Part1 = new() 8 | { 9 | { new Vec2D(X: 2, Y: 1), 'B' }, 10 | { new Vec2D(X: 2, Y: 0), 'C' }, 11 | 12 | { new Vec2D(X: 4, Y: 1), 'C' }, 13 | { new Vec2D(X: 4, Y: 0), 'D' }, 14 | 15 | { new Vec2D(X: 6, Y: 1), 'A' }, 16 | { new Vec2D(X: 6, Y: 0), 'D' }, 17 | 18 | { new Vec2D(X: 8, Y: 1), 'B' }, 19 | { new Vec2D(X: 8, Y: 0), 'A' } 20 | }; 21 | 22 | public static readonly Dictionary Part2 = new() 23 | { 24 | { new Vec2D(X: 2, Y: 3), 'B' }, 25 | { new Vec2D(X: 2, Y: 2), 'D' }, 26 | { new Vec2D(X: 2, Y: 1), 'D' }, 27 | { new Vec2D(X: 2, Y: 0), 'C' }, 28 | 29 | { new Vec2D(X: 4, Y: 3), 'C' }, 30 | { new Vec2D(X: 4, Y: 2), 'C' }, 31 | { new Vec2D(X: 4, Y: 1), 'B' }, 32 | { new Vec2D(X: 4, Y: 0), 'D' }, 33 | 34 | { new Vec2D(X: 6, Y: 3), 'A' }, 35 | { new Vec2D(X: 6, Y: 2), 'B' }, 36 | { new Vec2D(X: 6, Y: 1), 'A' }, 37 | { new Vec2D(X: 6, Y: 0), 'D' }, 38 | 39 | { new Vec2D(X: 8, Y: 3), 'B' }, 40 | { new Vec2D(X: 8, Y: 2), 'A' }, 41 | { new Vec2D(X: 8, Y: 1), 'C' }, 42 | { new Vec2D(X: 8, Y: 0), 'A' } 43 | }; 44 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D23/Move.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2021.D23; 4 | 5 | public readonly record struct Move(Vec2D From, Vec2D To, int Cost, MoveType Type); 6 | 7 | public enum MoveType 8 | { 9 | ToHallway, 10 | ToSideRoom 11 | } -------------------------------------------------------------------------------- /Solutions/Y2021/D23/SideRoom.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2021.D23; 4 | 5 | public sealed class SideRoom 6 | { 7 | private readonly int _abscissa; 8 | private readonly int _depth; 9 | 10 | public SideRoom(int abscissa, int depth) 11 | { 12 | _abscissa = abscissa; 13 | _depth = depth; 14 | } 15 | 16 | public Vec2D Top => new(_abscissa, _depth - 1); 17 | public Vec2D Bottom => new(_abscissa, 0); 18 | 19 | public bool Contains(Vec2D pos) 20 | { 21 | return pos.X == _abscissa && pos.Y >= 0 && pos.Y < _depth; 22 | } 23 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D01/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2022.D01; 4 | 5 | [PuzzleInfo("Calorie Counting", Topics.None, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | return part switch 11 | { 12 | 1 => GetMaxCalories(num: 1), 13 | 2 => GetMaxCalories(num: 3), 14 | _ => PuzzleNotSolvedString 15 | }; 16 | } 17 | 18 | private int GetMaxCalories(int num) 19 | { 20 | return GetInputLines() 21 | .ChunkByNonEmpty() 22 | .Select(chunk => chunk.Sum(int.Parse)) 23 | .OrderDescending() 24 | .Take(num) 25 | .Sum(); 26 | } 27 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D02/RockPaperScissorsChoice.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D02; 2 | 3 | public enum RockPaperScissorsChoice 4 | { 5 | Rock = 0, 6 | Paper, 7 | Scissors 8 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D02/RockPaperScissorsResult.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D02; 2 | 3 | public enum RockPaperScissorsResult 4 | { 5 | Loss = 0, 6 | Draw, 7 | Win 8 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D04/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Numerics; 2 | 3 | namespace Solutions.Y2022.D04; 4 | 5 | [PuzzleInfo("Camp Cleanup", Topics.Math, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var assignments = ParseInputLines(parseFunc: ParseAssignment); 11 | return part switch 12 | { 13 | 1 => CountEncapsulating(assignments), 14 | 2 => CountIntersecting(assignments), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static int CountEncapsulating(IEnumerable<(Range R1, Range R2)> assignments) 20 | { 21 | return assignments.Count(assignment => CheckContains(a: assignment.R1, b: assignment.R2)); 22 | } 23 | 24 | private static int CountIntersecting(IEnumerable<(Range R1, Range R2)> assignments) 25 | { 26 | return assignments.Count(assignment => Range.Overlap(a: assignment.R1, b: assignment.R2, out _)); 27 | } 28 | 29 | private static bool CheckContains(Range a, Range b) 30 | { 31 | return a.Contains(b) || b.Contains(a); 32 | } 33 | 34 | private static (Range R1, Range R2) ParseAssignment(string line) 35 | { 36 | var assignments = line.Split(separator: ','); 37 | var r1 = Range.Parse(assignments[0]); 38 | var r2 = Range.Parse(assignments[1]); 39 | 40 | return (r1, r2); 41 | } 42 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D05/CraneInstruction.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D05; 2 | 3 | public readonly record struct CraneInstruction(int NumMoves, int SourceStack, int DestinationStack); -------------------------------------------------------------------------------- /Solutions/Y2022/D05/CraneOperator.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D05; 2 | 3 | public static class CraneOperator 4 | { 5 | public static Dictionary> ExecutePlan(CranePlan plan, PickupMode pickupMode) 6 | { 7 | return pickupMode switch 8 | { 9 | PickupMode.OneAtATime => ExecutePlanOneAtATime(plan), 10 | PickupMode.ManyAtATime => ExecutePlanManyAtATime(plan), 11 | _ => throw new NoSolutionException() 12 | }; 13 | } 14 | 15 | private static Dictionary> ExecutePlanOneAtATime(CranePlan plan) 16 | { 17 | var stacksMap = plan.InitialStacksState; 18 | foreach (var instruction in plan.Instructions) 19 | { 20 | for (var i = 0; i < instruction.NumMoves; i++) 21 | { 22 | stacksMap[instruction.DestinationStack].Push(stacksMap[instruction.SourceStack].Pop()); 23 | } 24 | } 25 | return stacksMap; 26 | } 27 | 28 | private static Dictionary> ExecutePlanManyAtATime(CranePlan plan) 29 | { 30 | var stacksMap = plan.InitialStacksState; 31 | var buffer = new Stack(); 32 | 33 | foreach (var instruction in plan.Instructions) 34 | { 35 | for (var i = 0; i < instruction.NumMoves; i++) 36 | { 37 | buffer.Push(stacksMap[instruction.SourceStack].Pop()); 38 | } 39 | 40 | while (buffer.Count > 0) 41 | { 42 | stacksMap[instruction.DestinationStack].Push(buffer.Pop()); 43 | } 44 | } 45 | 46 | return stacksMap; 47 | } 48 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D05/PickupMode.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D05; 2 | 3 | public enum PickupMode 4 | { 5 | OneAtATime, 6 | ManyAtATime 7 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D05/Solution.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Solutions.Y2022.D05; 4 | 5 | [PuzzleInfo("Supply Stacks", Topics.Simulation, Difficulty.Medium)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | if (!CranePlan.TryParse(GetInputLines(), out var plan)) 11 | { 12 | throw new NoSolutionException(message: "Failed to parse input"); 13 | } 14 | 15 | return part switch 16 | { 17 | 1 => GetTopCratesAfterPlan(plan!, PickupMode.OneAtATime), 18 | 2 => GetTopCratesAfterPlan(plan!, PickupMode.ManyAtATime), 19 | _ => PuzzleNotSolvedString 20 | }; 21 | } 22 | 23 | private static string GetTopCratesAfterPlan(CranePlan plan, PickupMode mode) 24 | { 25 | return GetTopCrates(CraneOperator.ExecutePlan(plan, mode)); 26 | } 27 | 28 | private static string GetTopCrates(Dictionary> state) 29 | { 30 | var sb = new StringBuilder(); 31 | foreach (var stack in state.Values) 32 | { 33 | sb.Append(stack.Peek()); 34 | } 35 | 36 | return sb.ToString(); 37 | } 38 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D06/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Collections; 2 | 3 | namespace Solutions.Y2022.D06; 4 | 5 | [PuzzleInfo("Tuning Trouble", Topics.StringParsing, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var datastream = GetInputText(); 11 | return part switch 12 | { 13 | 1 => ListenForStartMarker(datastream, markerLength: 4), 14 | 2 => ListenForStartMarker(datastream, markerLength: 14), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static int ListenForStartMarker(string datastream, int markerLength) 20 | { 21 | var buffer = new Queue(capacity: markerLength); 22 | var bufferContentMap = new DefaultDict(defaultValue: 0); 23 | 24 | for (var i = 0; i < datastream.Length; i++) 25 | { 26 | var received = datastream[i]; 27 | if (buffer.Count >= markerLength) 28 | { 29 | var token = buffer.Dequeue(); 30 | if (--bufferContentMap[token] <= 0) 31 | { 32 | bufferContentMap.Remove(token); 33 | } 34 | } 35 | 36 | bufferContentMap[received]++; 37 | buffer.Enqueue(received); 38 | 39 | if (bufferContentMap.Count == markerLength) 40 | { 41 | return i + 1; 42 | } 43 | } 44 | 45 | throw new NoSolutionException(); 46 | } 47 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D07/Command.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D07; 2 | 3 | public enum Command 4 | { 5 | Cd, 6 | Ls 7 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D07/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D07; 2 | 3 | [PuzzleInfo("No Space Left On Device", Topics.StringParsing, Difficulty.Medium)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var consoleOutput = GetInputLines(); 9 | var consoleParser = new ConsoleParser(); 10 | var sizeIndex = consoleParser.BuildDirectoryMap(consoleOutput); 11 | 12 | return part switch 13 | { 14 | 1 => SumDirectoriesUnderSize(sizeIndex, thresholdSize: 100000), 15 | 2 => FreeUpSpace(sizeIndex, systemVolume: 70000000, requiredSpace: 30000000), 16 | _ => PuzzleNotSolvedString 17 | }; 18 | } 19 | 20 | private static int SumDirectoriesUnderSize(IDictionary sizeIndex, int thresholdSize) 21 | { 22 | return sizeIndex.Values 23 | .Where(v => v <= thresholdSize) 24 | .Sum(); 25 | } 26 | 27 | private static int FreeUpSpace(IDictionary directorySizeIndex, int systemVolume, int requiredSpace) 28 | { 29 | var freeSpace = systemVolume - directorySizeIndex[ConsoleParser.RootDirectoryPath]; 30 | var needed = requiredSpace - freeSpace; 31 | 32 | return directorySizeIndex.Values 33 | .Where(v => v >= needed) 34 | .Min(); 35 | } 36 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D10/Cpu.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D10; 2 | 3 | public sealed class Cpu 4 | { 5 | public readonly struct State 6 | { 7 | public int Cycle { get; init; } 8 | public int X { get; init; } 9 | } 10 | 11 | public enum Opcode 12 | { 13 | // ReSharper disable IdentifierTypo 14 | Addx = 0, 15 | Noop = 1 16 | // ReSharper restore IdentifierTypo 17 | } 18 | 19 | public event Action? Ticked; 20 | 21 | private int Cycle { get; set; } = 1; 22 | private int X { get; set; } = 1; 23 | 24 | public void Run(IEnumerable<(Opcode opcode, int arg)> instructions) 25 | { 26 | foreach (var instruction in instructions) 27 | { 28 | switch (instruction.opcode) 29 | { 30 | case Opcode.Noop: 31 | Tick(); 32 | break; 33 | case Opcode.Addx: 34 | Tick(); 35 | Tick(); 36 | X += instruction.arg; 37 | break; 38 | default: 39 | throw new NoSolutionException(); 40 | } 41 | } 42 | } 43 | 44 | private void Tick() 45 | { 46 | Ticked?.Invoke(GetState()); 47 | Cycle++; 48 | } 49 | 50 | private State GetState() 51 | { 52 | return new State { Cycle = Cycle, X = X }; 53 | } 54 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D11/Operator.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D11; 2 | 3 | public enum Operator 4 | { 5 | Add, 6 | Multiply, 7 | Square 8 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D11/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D11; 2 | 3 | [PuzzleInfo("Monkey in the Middle", Topics.Simulation|Topics.Math, Difficulty.Medium)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | return part switch 9 | { 10 | 1 => QuantifyMonkeyBusiness(rounds: 20, applyBoredDivisor: true), 11 | 2 => QuantifyMonkeyBusiness(rounds: 10000, applyBoredDivisor: false), 12 | _ => PuzzleNotSolvedString 13 | }; 14 | } 15 | 16 | private long QuantifyMonkeyBusiness(int rounds, bool applyBoredDivisor) 17 | { 18 | var monkeyMap = MonkeyData.Parse(GetInputLines(), applyBoredDivisor); 19 | var activityCounts = new long[monkeyMap.Count]; 20 | var round = 0; 21 | 22 | while (round < rounds) 23 | { 24 | for (var i = 0; i < monkeyMap.Count; i++) 25 | { 26 | while (monkeyMap[i].IsHoldingItem) 27 | { 28 | var (throwTo, thrownItem) = monkeyMap[i].InspectNextItem(); 29 | activityCounts[i]++; 30 | monkeyMap[throwTo].CatchItem(thrownItem); 31 | } 32 | } 33 | 34 | round++; 35 | } 36 | 37 | return activityCounts 38 | .OrderDescending() 39 | .Take(2) 40 | .Aggregate((first, second) => first * second); 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D13/ComparisonResult.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D13; 2 | 3 | public enum ComparisonResult 4 | { 5 | Ordered = -1, 6 | Indeterminate, 7 | Scrambled 8 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D13/IntegerPacketElement.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D13; 2 | 3 | public sealed class IntegerPacketElement(int value) : PacketElement 4 | { 5 | public int Value { get; } = value; 6 | 7 | public ListPacketElement AsList() 8 | { 9 | return new ListPacketElement(elements: [this]); 10 | } 11 | 12 | public override string ToString() 13 | { 14 | return Value.ToString(); 15 | } 16 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D13/ListPacketElement.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Solutions.Y2022.D13; 4 | 5 | public sealed class ListPacketElement : PacketElement 6 | { 7 | private readonly List _elements = []; 8 | 9 | public ListPacketElement(IEnumerable? elements) 10 | { 11 | if (elements == null) 12 | { 13 | return; 14 | } 15 | 16 | foreach (var element in elements) 17 | { 18 | _elements.Add(element); 19 | } 20 | } 21 | 22 | public PacketElement this[int i] => _elements[i]; 23 | public int Count => _elements.Count; 24 | 25 | public bool HasElementAtIndex(int i) 26 | { 27 | return i >= 0 && i < _elements.Count; 28 | } 29 | 30 | public override string ToString() 31 | { 32 | var sb = new StringBuilder(); 33 | sb.Append(ListStart); 34 | 35 | for (var i = 0; i < _elements.Count; i++) 36 | { 37 | sb.Append(_elements[i]); 38 | if (i != _elements.Count - 1) 39 | { 40 | sb.Append(ElementDelimiter); 41 | } 42 | } 43 | 44 | sb.Append(ListEnd); 45 | return sb.ToString(); 46 | } 47 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D13/PacketElement.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D13; 2 | 3 | public abstract class PacketElement : IComparable 4 | { 5 | public const char ElementDelimiter = ','; 6 | public const char ListStart = '['; 7 | public const char ListEnd = ']'; 8 | 9 | public int CompareTo(PacketElement? other) 10 | { 11 | ArgumentNullException.ThrowIfNull(other); 12 | return (int)PacketComparator.CompareElements(this, other); 13 | } 14 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D13/PacketPair.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D13; 2 | 3 | public readonly record struct PacketPair(int Index, PacketElement First, PacketElement Second); -------------------------------------------------------------------------------- /Solutions/Y2022/D13/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D13; 2 | 3 | [PuzzleInfo("Distress Signal", Topics.StringParsing, Difficulty.Hard)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | private const string DivisorPacket1 = "[[2]]"; 7 | private const string DivisorPacket2 = "[[6]]"; 8 | 9 | public override object Run(int part) 10 | { 11 | return part switch 12 | { 13 | 1 => SumOrderedPacketIndices(PacketParser.ParsePairs(GetInputLines())), 14 | 2 => CalculateDecoderKey(PacketParser.ParsePackets(GetInputLines())), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static int SumOrderedPacketIndices(IEnumerable pairs) 20 | { 21 | return pairs 22 | .Where(pair => pair.First.CompareTo(pair.Second) < 0) 23 | .Sum(pair => pair.Index); 24 | } 25 | 26 | private static int CalculateDecoderKey(IEnumerable packets) 27 | { 28 | var list = packets.ToList(); 29 | var divisor1 = PacketParser.ParseElement(DivisorPacket1); 30 | var divisor2 = PacketParser.ParseElement(DivisorPacket2); 31 | 32 | list.Add(divisor1); 33 | list.Add(divisor2); 34 | list.Sort(); 35 | 36 | var firstIndex = list.IndexOf(divisor1); 37 | var secondIndex = list.IndexOf(divisor2); 38 | 39 | return (firstIndex + 1) * (secondIndex + 1); 40 | } 41 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D15/Reporting.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2022.D15; 4 | 5 | public readonly struct Reporting(Vec2D sensorPos, Vec2D beaconPos) 6 | { 7 | public Vec2D SensorPos { get; } = sensorPos; 8 | public Vec2D BeaconPos { get; } = beaconPos; 9 | public int Range { get; } = Vec2D.Distance(a: sensorPos, b: beaconPos, Metric.Taxicab); 10 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D16/Strategy.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D16; 2 | 3 | public readonly struct Strategy(int flow, IEnumerable opened) 4 | { 5 | public int Flow { get; } = flow; 6 | public IReadOnlySet Opened { get; } = new HashSet(opened); 7 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D17/JetPattern.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2022.D17; 4 | 5 | public sealed class JetPattern 6 | { 7 | private static readonly Dictionary JetVectorMap = new() 8 | { 9 | { '>', Vec2D.Right }, 10 | { '<', Vec2D.Left } 11 | }; 12 | 13 | 14 | private readonly List _list; 15 | 16 | public int Index { get; private set; } 17 | 18 | private JetPattern(IEnumerable vectors) 19 | { 20 | Index = 0; 21 | _list = [..vectors]; 22 | } 23 | 24 | public Vec2D Next() 25 | { 26 | var next = _list[Index % _list.Count]; 27 | Index = (Index + 1) % _list.Count; 28 | return next; 29 | } 30 | 31 | public static JetPattern Parse(string sequence) 32 | { 33 | return new JetPattern(sequence.Select(c => JetVectorMap[c])); 34 | } 35 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D17/RockSource.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D17; 2 | 3 | public static class RockSource 4 | { 5 | private static readonly List List = new(); 6 | 7 | static RockSource() 8 | { 9 | List.Add(new HorizontalLine()); 10 | List.Add(new Plus()); 11 | List.Add(new L()); 12 | List.Add(new VerticalLine()); 13 | List.Add(new Square()); 14 | } 15 | 16 | public static Rock Get(int index) 17 | { 18 | return List[index % List.Count]; 19 | } 20 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D17/Rocks.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2022.D17; 4 | 5 | public abstract class Rock 6 | { 7 | public abstract HashSet Shape { get; } 8 | } 9 | 10 | public sealed class HorizontalLine : Rock 11 | { 12 | public override HashSet Shape { get; } = 13 | [ 14 | new(X: 0, Y: 0), 15 | new(X: 1, Y: 0), 16 | new(X: 2, Y: 0), 17 | new(X: 3, Y: 0) 18 | ]; 19 | } 20 | 21 | public sealed class Plus : Rock 22 | { 23 | public override HashSet Shape { get; } = 24 | [ 25 | new(X: 1, Y: 0), 26 | new(X: 1, Y: 1), 27 | new(X: 1, Y: 2), 28 | new(X: 0, Y: 1), 29 | new(X: 2, Y: 1) 30 | ]; 31 | } 32 | 33 | public sealed class L : Rock 34 | { 35 | public override HashSet Shape { get; } = 36 | [ 37 | new(X: 0, Y: 0), 38 | new(X: 1, Y: 0), 39 | new(X: 2, Y: 0), 40 | new(X: 2, Y: 1), 41 | new(X: 2, Y: 2) 42 | ]; 43 | } 44 | 45 | public sealed class VerticalLine : Rock 46 | { 47 | public override HashSet Shape { get; } = 48 | [ 49 | new(X: 0, Y: 0), 50 | new(X: 0, Y: 1), 51 | new(X: 0, Y: 2), 52 | new(X: 0, Y: 3) 53 | ]; 54 | } 55 | 56 | public sealed class Square : Rock 57 | { 58 | public override HashSet Shape { get; } = 59 | [ 60 | new(X: 0, Y: 0), 61 | new(X: 0, Y: 1), 62 | new(X: 1, Y: 0), 63 | new(X: 1, Y: 1) 64 | ]; 65 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D19/Materials.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D19; 2 | 3 | [Flags] 4 | public enum Materials 5 | { 6 | Ore = 1 << 0, 7 | Clay = 1 << 1, 8 | Obsidian = 1 << 2, 9 | Geode = 1 << 3, 10 | All = ~0 11 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D21/AlgebraicOperation.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D21; 2 | 3 | public readonly struct AlgebraicOperation 4 | { 5 | public Operator Operator { get; init; } 6 | public long KnownOperand { get; init; } 7 | public bool KnownOperandOnLhs { get; init; } 8 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D21/Expression.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D21; 2 | 3 | public readonly struct Expression 4 | { 5 | public string Id { get; init; } 6 | public Operator Operator { get; init; } 7 | public long Value { get; init; } 8 | public string Lhs { get; init; } 9 | public string Rhs { get; init; } 10 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D21/ExpressionFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D21; 2 | 3 | public static class ExpressionFactory 4 | { 5 | private const char IdDelimiter = ':'; 6 | private const char ElementDelimiter = ' '; 7 | private static readonly Dictionary OperatorMap = new() 8 | { 9 | { '+', Operator.Add }, 10 | { '-', Operator.Subtract }, 11 | { '*', Operator.Multiply }, 12 | { '/', Operator.Divide } 13 | }; 14 | 15 | public static Expression Parse(string expressionStr) 16 | { 17 | var elements = expressionStr.Split(IdDelimiter, StringSplitOptions.TrimEntries); 18 | var arguments = elements[1].Split(ElementDelimiter); 19 | 20 | if (arguments.Length == 1) 21 | { 22 | return new Expression 23 | { 24 | Id = elements[0], 25 | Operator = Operator.Identity, 26 | Value = long.Parse(elements[1]) 27 | }; 28 | } 29 | 30 | return new Expression 31 | { 32 | Id = elements[0], 33 | Operator = OperatorMap[arguments[1][0]], 34 | Lhs = arguments[0], 35 | Rhs = arguments[2] 36 | }; 37 | } 38 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D21/Operator.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D21; 2 | 3 | public enum Operator 4 | { 5 | Identity, 6 | Add, 7 | Subtract, 8 | Multiply, 9 | Divide 10 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D22/Instruction.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2022.D22; 4 | 5 | public readonly struct Instruction(int steps, Quaternion rot) 6 | { 7 | public int Steps { get; } = steps; 8 | public Quaternion Rot { get; } = rot; 9 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D22/MoveMode.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D22; 2 | 3 | public enum MoveMode 4 | { 5 | Planar, 6 | Cubic 7 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D22/Square.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D22; 2 | 3 | public enum Square 4 | { 5 | Free, 6 | Blocked, 7 | OutOfBounds 8 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D23/MovePreferences.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2022.D23; 4 | 5 | public static class MovePreferences 6 | { 7 | private static readonly List<(Vec2D target, HashSet checkSet)> Choices = 8 | [ 9 | (target: Vec2D.Up, checkSet:[new Vec2D(X: -1, Y: 1), new Vec2D(X: 0, Y: 1), new Vec2D(X: 1, Y: 1)]), 10 | (target: Vec2D.Down, checkSet:[new Vec2D(X: -1, Y: -1), new Vec2D(X: 0, Y: -1), new Vec2D(X: 1, Y: -1)]), 11 | (target: Vec2D.Left, checkSet:[new Vec2D(X: -1, Y: 1), new Vec2D(X: -1, Y: 0), new Vec2D(X: -1, Y: -1)]), 12 | (target: Vec2D.Right, checkSet:[new Vec2D(X: 1, Y: 1), new Vec2D(X: 1, Y: 0), new Vec2D(X: 1, Y: -1)]) 13 | ]; 14 | 15 | public static int Count => Choices.Count; 16 | 17 | public static (Vec2D target, HashSet checkSet) Get(int i) 18 | { 19 | return Choices[i % Count]; 20 | } 21 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D24/Blizzards.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2022.D24; 4 | 5 | public readonly struct Blizzard(Vec2D pos, Vec2D vel, Vec2D respawnAt) 6 | { 7 | private Vec2D Vel { get; } = vel; 8 | 9 | public Vec2D Pos { get; } = pos; 10 | public Vec2D RespawnAt { get; } = respawnAt; 11 | public Vec2D Ahead => Pos + Vel; 12 | 13 | public Blizzard Step() 14 | { 15 | return new Blizzard(pos: Ahead, vel: Vel, RespawnAt); 16 | } 17 | 18 | public Blizzard Respawn() 19 | { 20 | return new Blizzard(pos: RespawnAt, vel: Vel, RespawnAt); 21 | } 22 | } -------------------------------------------------------------------------------- /Solutions/Y2022/D24/Terrain.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2022.D24; 2 | 3 | public static class Terrain 4 | { 5 | public const char Void = '.'; 6 | public const char Wall = '#'; 7 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D04/Solution.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using Utilities.Extensions; 3 | 4 | namespace Solutions.Y2023.D04; 5 | 6 | [PuzzleInfo("Scratchcards", Topics.None, Difficulty.Easy)] 7 | public sealed class Solution : SolutionBase 8 | { 9 | private readonly record struct Card(int Wins); 10 | 11 | public override object Run(int part) 12 | { 13 | return part switch 14 | { 15 | 1 => CountScore(), 16 | 2 => CountCards(), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private int CountScore() 22 | { 23 | return ParseInputLines(parseFunc: ParseCard).Sum(card => (int)Math.Pow(2, card.Wins - 1)); 24 | } 25 | 26 | private int CountCards() 27 | { 28 | var cards = ParseInputLines(parseFunc: ParseCard); 29 | var counts = Enumerable.Repeat(element: 1, count: cards.Length).ToArray(); 30 | 31 | for (var n = 0; n < cards.Length; n++) 32 | for (var w = 1; w <= cards[n].Wins; w++) 33 | { 34 | counts[n + w] += counts[n]; 35 | } 36 | 37 | return counts.Sum(); 38 | } 39 | 40 | private static Card ParseCard(string line) 41 | { 42 | var match = Regex.Match(input: line, pattern: @"Card\s+(?:\d+):(?:\s+(?\d+))+\s\|(?:\s+(?\d+))+"); 43 | var wins = match.Groups["Wins"].ParseInts(); 44 | var have = match.Groups["Have"].ParseInts(); 45 | 46 | return new Card(Wins: have.Count(wins.Contains)); 47 | } 48 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D05/Almanac.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2023.D05; 4 | 5 | public sealed class Almanac 6 | { 7 | public IReadOnlyList Seeds { get; } 8 | public IReadOnlyList Maps { get; } 9 | 10 | private Almanac(IEnumerable seeds, IEnumerable> mappings) 11 | { 12 | Seeds = seeds.ToList(); 13 | Maps = mappings.Select(MapTable.Build).ToList(); 14 | } 15 | 16 | public static Almanac Parse(string[] input) 17 | { 18 | var seeds = input[0].ParseLongs(); 19 | var maps = input 20 | .Skip(2) 21 | .ChunkBy(l => !string.IsNullOrWhiteSpace(l) && !l.Contains(':')) 22 | .Select(c => c.Select(ParseMapEntry).ToList()); 23 | 24 | return new Almanac(seeds, maps); 25 | } 26 | 27 | private static MapEntry ParseMapEntry(string line) 28 | { 29 | var longs = line.ParseLongs(); 30 | return new MapEntry( 31 | DestStart: longs[0], 32 | SourceStart: longs[1], 33 | RangeLength: longs[2]); 34 | } 35 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D05/MapEntry.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Numerics; 2 | 3 | namespace Solutions.Y2023.D05; 4 | 5 | public readonly record struct MapEntry(long DestStart, long SourceStart, long RangeLength) 6 | { 7 | public long SourceMin => SourceStart; 8 | public long SourceMax => SourceStart + RangeLength - 1; 9 | public Range SourceRange => new (SourceStart, SourceMax); 10 | 11 | public Range Apply(Range range) 12 | { 13 | return new Range( 14 | min: DestStart + range.Min - SourceStart, 15 | max: DestStart + range.Max - SourceStart); 16 | } 17 | 18 | public static MapEntry Default(long min, long max) 19 | { 20 | return new MapEntry( 21 | DestStart: min, 22 | SourceStart: min, 23 | RangeLength: max - min + 1); 24 | } 25 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D05/MapTable.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2023.D05; 2 | 3 | public sealed class MapTable 4 | { 5 | public IReadOnlyList OrderedEntries { get; } 6 | 7 | private MapTable(IEnumerable orderedEntries) 8 | { 9 | OrderedEntries = new List(collection: orderedEntries); 10 | } 11 | 12 | public static MapTable Build(IEnumerable entries) 13 | { 14 | var queue = new Queue(collection: entries.OrderBy(mapping => mapping.SourceMin)); 15 | var order = new List(); 16 | var upper = -1L; 17 | 18 | while (queue.Count != 0) 19 | { 20 | var next = queue.Dequeue(); 21 | if (upper + 1 != next.SourceMin) 22 | { 23 | order.Add(item: MapEntry.Default( 24 | min: upper + 1, 25 | max: next.SourceMin - 1)); 26 | } 27 | 28 | order.Add(next); 29 | upper = order[^1].SourceMax; 30 | } 31 | 32 | // Append with [Max(ranges.Max) + 1, long.MaxValue] 33 | // 34 | if (order[^1].SourceMax != long.MaxValue) 35 | { 36 | order.Add(item: MapEntry.Default( 37 | min: order[^1].SourceMax + 1, 38 | max: long.MaxValue)); 39 | } 40 | 41 | return new MapTable(orderedEntries: order); 42 | } 43 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D07/Deck.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2023.D07; 2 | 3 | public static class Deck 4 | { 5 | private const char Joker = 'J'; 6 | private static readonly Dictionary RankMap = new() 7 | { 8 | { '2', 2 }, 9 | { '3', 3 }, 10 | { '4', 4 }, 11 | { '5', 5 }, 12 | { '6', 6 }, 13 | { '7', 7 }, 14 | { '8', 8 }, 15 | { '9', 9 }, 16 | { 'T', 10 }, 17 | { 'J', 11 }, 18 | { 'Q', 12 }, 19 | { 'K', 13 }, 20 | { 'A', 14 } 21 | }; 22 | 23 | public static bool HasJokers { get; set; } 24 | 25 | public static bool IsJoker(char card) => HasJokers && card == Joker; 26 | public static int GetRank(char card) => IsJoker(card) ? 1 : RankMap[card]; 27 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D07/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2023.D07; 4 | 5 | [PuzzleInfo("Camel Cards", Topics.Math, Difficulty.Medium)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | return part switch 11 | { 12 | 1 => ScoreHands(jokers: false), 13 | 2 => ScoreHands(jokers: true), 14 | _ => PuzzleNotSolvedString 15 | }; 16 | } 17 | 18 | private int ScoreHands(bool jokers) 19 | { 20 | Deck.HasJokers = jokers; 21 | return ParseInputLines(parseFunc: ParseHand) 22 | .Order() 23 | .Select((hand, index) => hand.Bid * (index + 1)) 24 | .Sum(); 25 | } 26 | 27 | private static Hand ParseHand(string line) 28 | { 29 | var elements = line.Split(separator: ' '); 30 | return new Hand(cards: elements[0], bid: elements[1].ParseInt()); 31 | } 32 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D09/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2023.D09; 4 | 5 | [PuzzleInfo("Mirage Maintenance", Topics.Math, Difficulty.Easy)] 6 | public class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var input = GetInputLines(); 11 | return part switch 12 | { 13 | 1 => input.Sum(line => Extrapolate(report: line, forwards: true)), 14 | 2 => input.Sum(line => Extrapolate(report: line, forwards: false)), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static int Extrapolate(string report, bool forwards) 20 | { 21 | var values = report.ParseInts(); 22 | var initial = forwards 23 | ? values 24 | : values.Reverse(); 25 | 26 | IList sequences = [initial.ToArray()]; 27 | while (sequences[^1].Any(val => val != 0)) 28 | { 29 | sequences.Add(item: sequences[^1] 30 | .Skip(1) 31 | .Select((val, i) => val - sequences[^1][i]) 32 | .ToArray()); 33 | } 34 | 35 | return sequences 36 | .Reverse() 37 | .Skip(1) 38 | .Aggregate(seed: 0, func: (n, seq) => n + seq[^1]); 39 | } 40 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D12/State.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2023.D12; 2 | 3 | public readonly record struct State(string Pattern, int Pos, char Current, bool RunActive, int RunIndex, int RunLength) 4 | { 5 | public static State Initial(string pattern) 6 | { 7 | return new State(Pattern: pattern, Pos: 0, Current: pattern[0], RunActive: false, RunIndex: -1, RunLength: 0); 8 | } 9 | 10 | public State Branch(char c) 11 | { 12 | return this with { Current = c }; 13 | } 14 | 15 | public State Consume() 16 | { 17 | return Current == '.' 18 | ? AdvanceWorking() 19 | : AdvanceDamaged(); 20 | } 21 | 22 | private State AdvanceWorking() 23 | { 24 | var c = GetTokenOrDefault(Pos + 1); 25 | return new State(Pattern: Pattern, Pos: Pos + 1, Current: c, RunActive: false, RunIndex: RunIndex, RunLength: 0); 26 | } 27 | 28 | private State AdvanceDamaged() 29 | { 30 | var c = GetTokenOrDefault(Pos + 1); 31 | return RunActive 32 | ? new State(Pattern: Pattern, Pos: Pos + 1, Current: c, RunActive: true, RunIndex: RunIndex, RunLength: RunLength + 1) 33 | : new State(Pattern: Pattern, Pos: Pos + 1, Current: c, RunActive: true, RunIndex: RunIndex + 1, RunLength: 1); 34 | } 35 | 36 | private char GetTokenOrDefault(int pos) 37 | { 38 | return pos < Pattern.Length 39 | ? Pattern[pos] 40 | : '\0'; 41 | } 42 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D15/Box.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2023.D15; 2 | 3 | public readonly struct Box(int n) 4 | { 5 | private Dictionary> Index { get; } = new(); 6 | private LinkedList Lenses { get; } = []; 7 | 8 | public void Remove(string label) 9 | { 10 | if (Index.Remove(label, out var lens)) 11 | { 12 | Lenses.Remove(lens); 13 | } 14 | } 15 | 16 | public void Add(string label, int focalLength) 17 | { 18 | if (Index.TryGetValue(label, out var oldLens)) 19 | { 20 | oldLens.Value = focalLength; 21 | } 22 | else 23 | { 24 | Lenses.AddLast(value: focalLength); 25 | Index[label] = Lenses.Last!; 26 | } 27 | } 28 | 29 | public int GetPower() 30 | { 31 | var sum = 0; 32 | var i = 0; 33 | var lens = Lenses.First; 34 | 35 | while (lens != null) 36 | { 37 | sum += (n + 1) * (i++ + 1) * lens.Value; 38 | lens = lens.Next; 39 | } 40 | 41 | return sum; 42 | } 43 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D15/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Collections; 2 | using Utilities.Extensions; 3 | 4 | namespace Solutions.Y2023.D15; 5 | 6 | [PuzzleInfo("Lens Library", Topics.None, Difficulty.Easy)] 7 | public sealed class Solution : SolutionBase 8 | { 9 | public override object Run(int part) 10 | { 11 | var input = GetInputText(); 12 | var instr = input.Split(separator: ','); 13 | var boxes = new DefaultDict(defaultSelector: n => new Box(n)); 14 | 15 | return part switch 16 | { 17 | 1 => instr.Sum(Hash), 18 | 2 => Install(instr, boxes), 19 | _ => PuzzleNotSolvedString 20 | }; 21 | } 22 | 23 | private static int Hash(string s) 24 | { 25 | return s.Aggregate(seed: 0, func: (val, c) => (val + c) * 17 % 256); 26 | } 27 | 28 | private static int Install(IEnumerable instructions, DefaultDict boxes) 29 | { 30 | foreach (var instr in instructions) 31 | { 32 | var label = string.Concat(instr.TakeWhile(char.IsLetter)); 33 | var box = boxes[Hash(label)]; 34 | 35 | switch (instr[label.Length]) 36 | { 37 | case '-': 38 | box.Remove(label); 39 | break; 40 | case '=': 41 | box.Add(label, focalLength: instr.ParseInt()); 42 | break; 43 | } 44 | } 45 | 46 | return boxes.Values.Sum(box => box.GetPower()); 47 | } 48 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D19/Rule.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2023.D19; 2 | 3 | public readonly record struct Rule(string Lhs, string Op, long Rhs, string Next); -------------------------------------------------------------------------------- /Solutions/Y2023/D20/ModuleType.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2023.D20; 2 | 3 | public enum ModuleType 4 | { 5 | Broadcaster = 0, 6 | FlipFlop, 7 | Conjunction, 8 | Untyped 9 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D20/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Numerics; 2 | 3 | namespace Solutions.Y2023.D20; 4 | 5 | [PuzzleInfo("Pulse Propagation", Topics.Graphs|Topics.Simulation, Difficulty.Medium)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var input = GetInputLines(); 11 | var network = Network.Parse(input); 12 | 13 | return part switch 14 | { 15 | 1 => AggregatePulses(network, b: 1000), 16 | 2 => GetFirstRxSignal(network), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static long AggregatePulses(Network network, int b) 22 | { 23 | return network 24 | .Simulate(b).Pulses.Values 25 | .Aggregate(seed: 1L, func: (product, count) => product * count); 26 | } 27 | 28 | private static long GetFirstRxSignal(Network network) 29 | { 30 | var trace = network.Simulate(b: 25000); 31 | var cycles = network.ReceiverInputs 32 | .Select(id => trace.Watches[id][^1] - trace.Watches[id][^2]) 33 | .ToList(); 34 | 35 | return Numerics.Lcm(cycles); 36 | } 37 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D20/Trace.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2023.D20; 2 | 3 | public record struct Trace(IDictionary Pulses, IDictionary> Watches); -------------------------------------------------------------------------------- /Solutions/Y2023/D22/Brick.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2023.D22; 4 | 5 | public sealed class Brick(int id, Aabb3D extents) 6 | { 7 | public int Id => id; 8 | public Aabb3D Extents { get; set; } = extents; 9 | 10 | public static Brick Parse(int id, string line) 11 | { 12 | var ps = line.Split(separator: '~'); 13 | var p1 = Vec3D.Parse(ps[0]); 14 | var p2 = Vec3D.Parse(ps[1]); 15 | 16 | return new Brick(id, extents: new Aabb3D(extents: [p1, p2], inclusive: true)); 17 | } 18 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D24/Aabb2.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2023.D24; 2 | 3 | public readonly record struct Aabb2(decimal Min, decimal Max) 4 | { 5 | public bool Contains(Vec3 p) 6 | { 7 | return p.X >= Min && p.X <= Max && 8 | p.Y >= Min && p.Y <= Max; 9 | } 10 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D24/Ray3.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2023.D24; 4 | 5 | public readonly record struct Ray3(Vec3 S, Vec3 V) 6 | { 7 | private const decimal EpsilonParallel = 1e-3m; 8 | 9 | public static bool Intersect2D(Ray3 a, Ray3 b, out Vec3 p) 10 | { 11 | var dx = b.S.X - a.S.X; 12 | var dy = b.S.Y - a.S.Y; 13 | var cp = b.V.X * a.V.Y - b.V.Y * a.V.X; 14 | 15 | if (Math.Abs(cp) < EpsilonParallel) 16 | { 17 | p = Vec3.Zero; 18 | return false; 19 | } 20 | 21 | var u = (dy * b.V.X - dx * b.V.Y) / cp; 22 | var v = (dy * a.V.X - dx * a.V.Y) / cp; 23 | 24 | p = a.GetPoint(u); 25 | return u >= 0 && v >= 0; 26 | } 27 | 28 | public static Ray3 Parse(string line) 29 | { 30 | var n = line.ParseLongs(); 31 | var s = new Vec3(X: n[0], Y: n[1], Z: n[2]); 32 | var v = new Vec3(X: n[3], Y: n[4], Z: n[5]); 33 | 34 | return new Ray3(s, v); 35 | } 36 | 37 | private Vec3 GetPoint(decimal t) 38 | { 39 | return new Vec3( 40 | X: S.X + t * V.X, 41 | Y: S.Y + t * V.Y, 42 | Z: S.Z + t * V.Z); 43 | } 44 | } -------------------------------------------------------------------------------- /Solutions/Y2023/D24/Vec3.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2023.D24; 2 | 3 | public readonly record struct Vec3(decimal X, decimal Y, decimal Z) 4 | { 5 | public static readonly Vec3 Zero = new(X: 0, Y: 0, Z: 0); 6 | } -------------------------------------------------------------------------------- /Solutions/Y2024/D01/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2024.D01; 4 | 5 | [PuzzleInfo("Historian Hysteria", Topics.StringParsing, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var l1 = new List(); 11 | var l2 = new List(); 12 | 13 | foreach (var numbers in ParseInputLines(l => l.ParseInts())) 14 | { 15 | l1.Add(numbers[0]); 16 | l2.Add(numbers[1]); 17 | } 18 | 19 | l1.Sort(); 20 | l2.Sort(); 21 | 22 | return part switch 23 | { 24 | 1 => l1.Zip(l2, (n1, n2) => Math.Abs(n1 - n2)).Sum(), 25 | 2 => l1.Sum(n1 => n1 * l2.Count(n2 => n1 == n2)), 26 | _ => PuzzleNotSolvedString 27 | }; 28 | } 29 | } -------------------------------------------------------------------------------- /Solutions/Y2024/D02/Solution.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Solutions.Y2024.D02; 4 | 5 | [PuzzleInfo("Red-Nosed Reports", Topics.None, Difficulty.Easy)] 6 | public sealed class Solution : SolutionBase 7 | { 8 | public override object Run(int part) 9 | { 10 | var reports = ParseInputLines(l => l.ParseInts()); 11 | return part switch 12 | { 13 | 1 => reports.Count(IsSafe), 14 | 2 => reports.Select(Permute).Count(set => set.Any(IsSafe)), 15 | _ => PuzzleNotSolvedString 16 | }; 17 | } 18 | 19 | private static bool IsSafe(IList report) 20 | { 21 | var sign = Math.Sign(report[1] - report[0]); 22 | return report 23 | .Zip(report.Skip(1), (n1, n2) => n2 - n1) 24 | .All(d => Math.Sign(d) == sign && Math.Abs(d) is >= 1 and <= 3); 25 | } 26 | 27 | private static IEnumerable> Permute(IList report) 28 | { 29 | for (var i = 0; i < report.Count; i++) 30 | { 31 | var variant = new List(report); 32 | variant.RemoveAt(i); 33 | yield return variant; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Solutions/Y2024/D03/Solution.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using Utilities.Extensions; 3 | 4 | namespace Solutions.Y2024.D03; 5 | 6 | [PuzzleInfo("Mull It Over", Topics.RegularExpressions, Difficulty.Easy, favourite: true)] 7 | public sealed class Solution : SolutionBase 8 | { 9 | public override object Run(int part) 10 | { 11 | var text = GetInputText(); 12 | return part switch 13 | { 14 | 1 => SumNaive(text), 15 | 2 => SumContextAware(text), 16 | _ => PuzzleNotSolvedString 17 | }; 18 | } 19 | 20 | private static int SumNaive(string text) 21 | { 22 | var matches = Regex.Matches(text, @"mul\((?:\d){1,3},(?:\d){1,3}\)"); 23 | return matches 24 | .Select(match => match.Value.ParseInts()) 25 | .Select(nums => nums[0] * nums[1]) 26 | .Sum(); 27 | } 28 | 29 | private static int SumContextAware(string text) 30 | { 31 | var runs = Regex.Split(text, @"(do\(\))|(don't\(\))"); 32 | return runs 33 | .Where((_, i) => i == 0 || runs[i - 1] == "do()") 34 | .Sum(SumNaive); 35 | } 36 | } -------------------------------------------------------------------------------- /Solutions/Y2024/D11/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2024.D11; 2 | 3 | [PuzzleInfo("Plutonian Pebbles", Topics.Recursion|Topics.Simulation, Difficulty.Medium)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | return part switch 9 | { 10 | 1 => CountStones(blinks: 25), 11 | 2 => CountStones(blinks: 75), 12 | _ => PuzzleNotSolvedString 13 | }; 14 | } 15 | 16 | private long CountStones(int blinks) 17 | { 18 | var memo= new Dictionary<(string, int), long>(); 19 | return GetInputText() 20 | .Split(' ') 21 | .Sum(stone => GetTotal(s: stone, b: blinks, m: memo)); 22 | } 23 | 24 | private static long GetTotal(string s, int b, Dictionary<(string, int), long> m) 25 | { 26 | if (b == 0) 27 | { 28 | return 1; 29 | } 30 | 31 | var k = (s, b--); 32 | var n = s.Length / 2; 33 | 34 | if (m.TryGetValue(k, out var cached)) 35 | { 36 | return cached; 37 | } 38 | 39 | if (s is "" or "0") 40 | { 41 | m[k] = GetTotal(s: "1", b, m); 42 | return m[k]; 43 | } 44 | 45 | if (s.Length % 2 == 0) 46 | { 47 | m[k] = GetTotal(s: s[..n], b, m) + 48 | GetTotal(s: s[n..].TrimStart('0'), b, m); 49 | return m[k]; 50 | } 51 | 52 | m[k] = GetTotal(s: $"{2024L * long.Parse(s)}", b, m); 53 | return m[k]; 54 | } 55 | } -------------------------------------------------------------------------------- /Solutions/Y2024/D17/Solution.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Utilities.Extensions; 3 | 4 | namespace Solutions.Y2024.D17; 5 | 6 | [InputSpecificSolution] 7 | [PuzzleInfo("Chronospatial Computer", Topics.Assembly, Difficulty.Hard, favourite: true)] 8 | public sealed class Solution : SolutionBase 9 | { 10 | public override object Run(int part) 11 | { 12 | var input = GetInputLines(); 13 | var program = input[^1].ParseInts(); 14 | 15 | return part switch 16 | { 17 | 1 => Execute(program, a: input[0].ParseNumber()), 18 | 2 => Reverse(program), 19 | _ => PuzzleNotSolvedString 20 | }; 21 | } 22 | 23 | private static string Execute(int[] program, BigInteger a) 24 | { 25 | return string.Join(',', Vm.Run(program, a)); 26 | } 27 | 28 | private static BigInteger Reverse(int[] program) 29 | { 30 | var queue = new Queue<(BigInteger val, int idx)>([(val: BigInteger.Zero, idx: 1)]); 31 | while (queue.Count != 0) 32 | { 33 | var (value, idx) = queue.Dequeue(); 34 | for (var candidate = value; candidate <= value + 0b111; candidate++) 35 | { 36 | var need = program[^idx..]; 37 | var have = Vm.Simulate(candidate); 38 | 39 | if (!need.SequenceEqual(have)) continue; 40 | if (idx == program.Length) return candidate; 41 | 42 | queue.Enqueue((candidate << 3, idx + 1)); 43 | } 44 | } 45 | 46 | throw new NoSolutionException("Could not construct a solution"); 47 | } 48 | } -------------------------------------------------------------------------------- /Solutions/Y2024/D17/asm.text: -------------------------------------------------------------------------------- 1 | ============================================================ 2 | Annotated assembly 3 | ============================================================ 4 | 0 opcode: 2 (bst) operand: 4 // B = A AND 0b111 5 | 2 opcode: 1 (bxl) operand: 1 // B = B XOR 0b001 6 | 4 opcode: 7 (cdv) operand: 5 // C = A >> B 7 | 6 opcode: 0 (adv) operand: 3 // A = A >> 3 8 | 8 opcode: 1 (bxl) operand: 4 // B = B XOR 0b100 9 | 10 opcode: 4 (bxc) operand: 5 // B = B XOR C 10 | 12 opcode: 5 (out) operand: 5 // out B AND 0b111 11 | 14 opcode: 3 (jnz) operand: 0 // if (A) LOOP -------------------------------------------------------------------------------- /Solutions/Y2024/D19/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2024.D19; 2 | 3 | [PuzzleInfo("Linen Layout", Topics.Recursion, Difficulty.Easy)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override object Run(int part) 7 | { 8 | var lines = GetInputLines(); 9 | var patterns = lines[0].Split(", "); 10 | var designs = lines[2..]; 11 | var memo = new Dictionary { [string.Empty] = 1L }; 12 | 13 | return part switch 14 | { 15 | 1 => designs.Count(design => Permute(design, patterns, memo) > 0), 16 | 2 => designs.Sum(design => Permute(design, patterns, memo)), 17 | _ => PuzzleNotSolvedString 18 | }; 19 | } 20 | 21 | private static long Permute(string design, string[] patterns, Dictionary memo) 22 | { 23 | if (memo.TryGetValue(design, out var value)) 24 | { 25 | return value; 26 | } 27 | 28 | memo[design] = patterns 29 | .Where(design.StartsWith) 30 | .Sum(pattern => Permute(design[pattern.Length..], patterns, memo)); 31 | return memo[design]; 32 | } 33 | } -------------------------------------------------------------------------------- /Solutions/Y2024/D21/Pad.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Geometry.Euclidean; 2 | 3 | namespace Solutions.Y2024.D21; 4 | 5 | public class Pad 6 | { 7 | public Dictionary PosMap { get; } 8 | public Dictionary KeyMap { get; } 9 | 10 | private Pad(Dictionary posMap) 11 | { 12 | PosMap = posMap; 13 | KeyMap = posMap.ToDictionary( 14 | keySelector: kvp => kvp.Value, 15 | elementSelector: kvp => kvp.Key); 16 | } 17 | 18 | public static Pad Parse(string flat, int cols, char omit) 19 | { 20 | var keys = new Dictionary(); 21 | var rows = flat.Length / cols; 22 | 23 | for (var y = 0; y < rows; y++) 24 | for (var x = 0; x < cols; x++) 25 | { 26 | var pos = new Vec2D(x, y); 27 | var key = flat[y * cols + x]; 28 | 29 | if (key != omit) 30 | { 31 | keys[pos] = key; 32 | } 33 | } 34 | 35 | return new Pad(keys); 36 | } 37 | } -------------------------------------------------------------------------------- /Solutions/Y2024/D25/Solution.cs: -------------------------------------------------------------------------------- 1 | namespace Solutions.Y2024.D25; 2 | 3 | [PuzzleInfo("Code Chronicle", Topics.StringParsing, Difficulty.Easy)] 4 | public sealed class Solution : SolutionBase 5 | { 6 | public override int Parts => 1; 7 | 8 | public override object Run(int part) 9 | { 10 | var locks = new List>(); 11 | var keys = new List>(); 12 | var congruent = new HashSet<(int Lock, int Key)>(); 13 | 14 | foreach (var schematic in ChunkInputByNonEmpty()) 15 | { 16 | var isLock = schematic[0].All(c => c == '#'); 17 | var heights = Enumerable.Range(0, schematic[0].Length) 18 | .Select(x => schematic.Count(line => line[x] == '#') - 1) 19 | .ToList(); 20 | 21 | if (isLock) 22 | locks.Add(heights); 23 | else 24 | keys.Add(heights); 25 | } 26 | 27 | for (var l = 0; l < locks.Count; l++) 28 | for (var k = 0; k < keys.Count; k++) 29 | { 30 | if (locks[l].Zip(keys[k]).All(tuple => tuple.First + tuple.Second <= 5)) 31 | { 32 | congruent.Add((l, k)); 33 | } 34 | } 35 | 36 | return congruent.Count; 37 | } 38 | } -------------------------------------------------------------------------------- /Utilities.Tests/Extensions/NumberExtensions.Tests.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Extensions; 2 | 3 | namespace Utilities.Tests.Extensions; 4 | 5 | /// 6 | /// Tests associated with . 7 | /// 8 | public class NumberExtensionsTests 9 | { 10 | [Theory] 11 | [InlineData(10, 3, 1)] 12 | [InlineData(-10, 3, 2)] 13 | [InlineData(10, -3, -2)] 14 | [InlineData(-10, -3, -1)] 15 | [InlineData(10, 5, 0)] 16 | public void Modulo_ReturnsCorrectResult(int a, int modulus, int expected) 17 | { 18 | // Act 19 | var result = a.Modulo(modulus); 20 | 21 | // Assert 22 | Assert.Equal(expected, result); 23 | } 24 | 25 | [Fact] 26 | public void Modulo_ZeroModulus_ThrowsDivideByZeroException() 27 | { 28 | // Act & Assert 29 | Assert.Throws(() => 10.Modulo(0)); 30 | } 31 | } -------------------------------------------------------------------------------- /Utilities.Tests/Numerics/LinearSolver.Tests.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Numerics; 2 | 3 | namespace Utilities.Tests.Numerics; 4 | 5 | /// 6 | /// Tests associated with . 7 | /// 8 | public sealed class LinearSolverTests 9 | { 10 | [Fact] 11 | public void Solve_ConsistentSystem_ReturnsExpectedSolution() 12 | { 13 | // Arrange 14 | var coefficients = new double[,] 15 | { 16 | { 1, -2, 1, 0 }, // 1x - 2y + 1z = 0 17 | { 2, 1, -3, 5 }, // 2x + 1y - 3z = 5 18 | { 4, -7, 1, -1 } // 4x - 7y + 1x = -1 19 | }; 20 | var expected = new double[] { 3, 2, 1 }; // Solution: x=3, y=2, z=1 21 | 22 | // Act 23 | var actual = LinearSolver.Solve(coefficients, epsilon: double.Epsilon); 24 | 25 | // Assert 26 | Assert.Equal(expected[0], actual[0], precision: 3); 27 | Assert.Equal(expected[1], actual[1], precision: 3); 28 | Assert.Equal(expected[2], actual[2], precision: 3); 29 | } 30 | 31 | [Fact] 32 | public void Solve_InconsistentSystem_ThrowsException() 33 | { 34 | // Arrange 35 | var coefficients = new double[,] 36 | { 37 | { 1, 1, -3, 4 }, // 1x + 1y - 3z = 4 38 | { 1, 0, -2, 1 }, // 1x + 0y - 2z = 1 39 | { 0, -1, 1, 2 } // 0x - 1y + 1z = 2 40 | }; 41 | 42 | // Act & Assert 43 | Assert.Throws(() => LinearSolver.Solve(coefficients, epsilon: double.Epsilon)); 44 | } 45 | } -------------------------------------------------------------------------------- /Utilities.Tests/Numerics/Numerics.Tests.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities.Tests.Numerics; 2 | 3 | /// 4 | /// Tests associated with . 5 | /// 6 | public sealed class NumericsTests 7 | { 8 | public static TheoryData GcdTestData => new() 9 | { 10 | { 12, 18, 6 }, 11 | { 14, 35, 7 }, 12 | { 9, 28, 1 }, 13 | { 25, 10, 5 }, 14 | { 13, 17, 1 } 15 | }; 16 | 17 | [Theory] 18 | [MemberData(nameof(GcdTestData))] 19 | public void Gcd_ShouldReturnExpectedValue(int a, int b, int expected) 20 | { 21 | // Act 22 | var actual = Utilities.Numerics.Numerics.Gcd(a, b); 23 | 24 | // Assert 25 | Assert.Equal(expected, actual); 26 | } 27 | 28 | public static TheoryData LcmTestData => new() 29 | { 30 | { 4, 5, 20 }, 31 | { 6, 8, 24 }, 32 | { 12, 15, 60 }, 33 | { 9, 14, 126 }, 34 | { 21, 14, 42 } 35 | }; 36 | 37 | [Theory] 38 | [MemberData(nameof(LcmTestData))] 39 | public void Lcm_ShouldReturnExpectedValue(int a, int b, int expected) 40 | { 41 | // Act 42 | var actual = Utilities.Numerics.Numerics.Lcm(a, b); 43 | 44 | // Assert 45 | Assert.Equal(expected, actual); 46 | } 47 | } -------------------------------------------------------------------------------- /Utilities.Tests/Utilities.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | false 8 | 13 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Utilities/Collections/CircularLinkedListNode.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities.Collections; 2 | 3 | /// 4 | /// A node in a . 5 | /// 6 | /// The type of the encapsulated 7 | public sealed class CircularLinkedListNode 8 | { 9 | public CircularLinkedList? List { get; internal set; } 10 | public CircularLinkedListNode? Next { get; internal set; } 11 | public CircularLinkedListNode? Prev { get; internal set; } 12 | public T Value { get; set; } 13 | 14 | internal CircularLinkedListNode(CircularLinkedList list, T value) 15 | { 16 | List = list; 17 | Value = value; 18 | } 19 | 20 | internal void Invalidate() 21 | { 22 | List = null; 23 | Next = null; 24 | Prev = null; 25 | } 26 | } -------------------------------------------------------------------------------- /Utilities/Collections/DisjointSetNode.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities.Collections; 2 | 3 | internal sealed class DisjointSetNode where T : IEquatable 4 | { 5 | internal T Element { get; } 6 | internal DisjointSetNode Parent { get; set; } 7 | internal int Rank { get; set; } 8 | 9 | internal DisjointSetNode(T element) 10 | { 11 | Element = element; 12 | Parent = this; 13 | Rank = 0; 14 | } 15 | } -------------------------------------------------------------------------------- /Utilities/Extensions/NumberExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace Utilities.Extensions; 4 | 5 | public static class NumberExtensions 6 | { 7 | /// 8 | /// Returns the signed remainder of the division: 9 | /// 10 | /// / 11 | /// 12 | /// 13 | /// The dividend 14 | /// The divisor 15 | /// The type associated with and the 16 | /// The signed remainder of the division 17 | /// The provided is zero 18 | public static T Modulo(this T a, T modulus) where T : INumber 19 | { 20 | if (T.IsZero(modulus)) 21 | { 22 | throw new DivideByZeroException(); 23 | } 24 | 25 | return (a % modulus + modulus) % modulus; 26 | } 27 | } -------------------------------------------------------------------------------- /Utilities/Geometry/Euclidean/Axis.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities.Geometry.Euclidean; 2 | 3 | /// 4 | /// Represents an axis/component in space. 5 | /// 6 | public enum Axis 7 | { 8 | X, 9 | Y, 10 | Z, 11 | W 12 | } -------------------------------------------------------------------------------- /Utilities/Geometry/Euclidean/Degrees.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities.Geometry.Euclidean; 2 | 3 | public static class Degrees 4 | { 5 | public const int Zero = 0; 6 | public const int N90 = -90; 7 | public const int P90 = 90; 8 | public const int P180 = 180; 9 | public const int P270 = 270; 10 | public const int P360 = 360; 11 | } -------------------------------------------------------------------------------- /Utilities/Geometry/Euclidean/Metric.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities.Geometry.Euclidean; 2 | 3 | /// 4 | /// Represents a metric in space 5 | /// 6 | public enum Metric 7 | { 8 | /// 9 | /// Also known as the chessboard metric, equivalent to the max of the individual component magnitudes 10 | /// 11 | Chebyshev, 12 | /// 13 | /// Also known as the Manhattan metric, equivalent to the sum of the individual component magnitudes 14 | /// 15 | Taxicab 16 | } -------------------------------------------------------------------------------- /Utilities/Geometry/Euclidean/Origin.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities.Geometry.Euclidean; 2 | 3 | /// 4 | /// Represents the convention of a coordinate system origin 5 | /// 6 | public enum Origin 7 | { 8 | /// 9 | /// Y increases as you move upwards 10 | /// 11 | Xy, 12 | /// 13 | /// Y increases as you move downwards 14 | /// 15 | Uv 16 | } -------------------------------------------------------------------------------- /Utilities/Geometry/Euclidean/Pose2D.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities.Geometry.Euclidean; 2 | 3 | /// 4 | /// A readonly value type representing a 2D pose (Position and Facing vectors). 5 | /// 6 | public readonly record struct Pose2D(Vec2D Pos, Vec2D Face) 7 | { 8 | public Vec2D Ahead => Pos + Face; 9 | public Vec2D Behind => Pos - Face; 10 | public Vec2D Right => Pos + Rot3D.N90Z.Transform(Face); 11 | public Vec2D Left => Pos + Rot3D.P90Z.Transform(Face); 12 | 13 | public Pose2D Step() 14 | { 15 | return this with { Pos = Ahead }; 16 | } 17 | 18 | public Pose2D Step(int amount) 19 | { 20 | return this with { Pos = Pos + amount * Face }; 21 | } 22 | 23 | public Pose2D Turn(Quaternion rot) 24 | { 25 | return this with { Face = rot.Transform(Face) }; 26 | } 27 | 28 | public override string ToString() 29 | { 30 | return $"{nameof(Pos)}={Pos} {nameof(Face)}={Face}"; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /Utilities/Geometry/Euclidean/VecThrowHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities.Geometry.Euclidean; 2 | 3 | /// 4 | /// Throw helper for common Vector exceptions. 5 | /// 6 | /// The type of the Vector consumer, used to add detail to exception messages 7 | public abstract class VecThrowHelper 8 | { 9 | public static Exception InvalidComponent(Axis component) 10 | { 11 | return new ArgumentException( 12 | message: $"The {component} component does not exist in {nameof(TVec)} space"); 13 | } 14 | 15 | public static Exception InvalidMetric(Metric metric) 16 | { 17 | return new ArgumentException( 18 | message: $"The {metric} distance metric is not well defined over {nameof(TVec)} space"); 19 | } 20 | } -------------------------------------------------------------------------------- /Utilities/Graph/BinaryTree.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities.Graph; 2 | 3 | /// 4 | /// A generic binary tree. 5 | /// 6 | /// The node to set as the 7 | /// The type associated with each node value 8 | public sealed class BinaryTree(BinaryTreeNode root) 9 | { 10 | public BinaryTreeNode Root { get; } = root; 11 | 12 | public BinaryTree(T rootValue) : this(root: new BinaryTreeNode(value: rootValue)) 13 | { 14 | } 15 | 16 | /// 17 | /// Print the to the console, starting at the . 18 | /// 19 | /// When not specified nodes are printed using the default ToString implementation 20 | /// The minimum spacing between each run of formatted node text 21 | /// How many empty lines should precede the root node 22 | /// The margin to the furthest left node 23 | public void Print(Func? formatter = null, int spacing = 2, int topMargin = 1, int leftMargin = 1) 24 | { 25 | Root.Print(formatter, spacing, topMargin, leftMargin); 26 | } 27 | } -------------------------------------------------------------------------------- /Utilities/Graph/BinaryTreeNode.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities.Graph; 2 | 3 | /// 4 | /// A generic node in a instance. 5 | /// 6 | /// The node value 7 | /// The type associated with node value 8 | public sealed class BinaryTreeNode(T value) 9 | { 10 | public T Value { get; set; } = value; 11 | public BinaryTreeNode? Left { get; set; } 12 | public BinaryTreeNode? Right { get; set; } 13 | 14 | /// 15 | /// Print the and it's children to the console. 16 | /// 17 | /// When not specified nodes are printed using the default ToString implementation 18 | /// The minimum spacing between each run of formatted node text 19 | /// How many empty lines should precede the root node 20 | /// The margin to the furthest left node 21 | public void Print(Func? formatter = null, int spacing = 2, int topMargin = 1, int leftMargin = 1) 22 | { 23 | BinaryTreePrinter.Print(root: this, formatter, spacing, topMargin, leftMargin); 24 | } 25 | } -------------------------------------------------------------------------------- /Utilities/Graph/DirectedGraph.cs: -------------------------------------------------------------------------------- 1 | using Utilities.Collections; 2 | 3 | namespace Utilities.Graph; 4 | 5 | /// 6 | /// A primitive directed graph template 7 | /// 8 | /// The type of value associated with each vertex 9 | public sealed class DirectedGraph where T : IEquatable 10 | { 11 | public readonly record struct Edge(T From, T To); 12 | 13 | public DefaultDict> Incoming { get; } = new(defaultSelector: _ => []); 14 | public DefaultDict> Outgoing { get; } = new(defaultSelector: _ => []); 15 | 16 | public IEnumerable Sources => Outgoing.Keys.Where(v => Incoming[v].Count == 0); 17 | public IEnumerable Sinks => Incoming.Keys.Where(v => Outgoing[v].Count == 0); 18 | 19 | public DirectedGraph() 20 | { 21 | } 22 | 23 | public DirectedGraph(IEnumerable edges) 24 | { 25 | foreach (var edge in edges) 26 | { 27 | AddEdge(edge); 28 | } 29 | } 30 | 31 | public void AddEdge(Edge edge) 32 | { 33 | AddEdge(edge.From, edge.To); 34 | } 35 | 36 | public void AddEdge(T from, T to) 37 | { 38 | Incoming[to].Add(from); 39 | Outgoing[from].Add(to); 40 | } 41 | 42 | public void RemoveEdge(Edge edge) 43 | { 44 | RemoveEdge(edge.From, edge.To); 45 | } 46 | 47 | public void RemoveEdge(T from, T to) 48 | { 49 | Incoming[to].Remove(from); 50 | Outgoing[from].Remove(to); 51 | } 52 | } -------------------------------------------------------------------------------- /Utilities/Hashing/Md5Provider.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | 4 | namespace Utilities.Hashing; 5 | 6 | public sealed class Md5Provider : IDisposable 7 | { 8 | private readonly MD5 _hashProvider = MD5.Create(); 9 | 10 | public string GetHashHex(string input) 11 | { 12 | var bytes = Encoding.UTF8.GetBytes(input); 13 | var hash = _hashProvider.ComputeHash(bytes); 14 | 15 | return Convert.ToHexString(hash).ToLower(); 16 | } 17 | 18 | public void Dispose() 19 | { 20 | _hashProvider.Dispose(); 21 | } 22 | } -------------------------------------------------------------------------------- /Utilities/Numerics/Numerics.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace Utilities.Numerics; 4 | 5 | public static class Numerics 6 | { 7 | /// 8 | /// Compute the Greatest Common Divisor of and . 9 | /// 10 | /// The GCD of and 11 | public static T Gcd(T a, T b) where T : INumber 12 | { 13 | while (a != T.Zero && b != T.Zero) 14 | { 15 | if (a > b) 16 | { 17 | a %= b; 18 | } 19 | else 20 | { 21 | b %= a; 22 | } 23 | } 24 | 25 | return a + b; 26 | } 27 | 28 | /// 29 | /// Compute the Least Common Multiple of and . 30 | /// 31 | /// The LCM of and 32 | public static T Lcm(T a, T b) where T : INumber 33 | { 34 | return a * b / Gcd(a, b); 35 | } 36 | 37 | /// 38 | /// Compute the Least Common Multiple of the provided . 39 | /// 40 | /// The LCM of 41 | public static T Lcm(ICollection numbers) where T : INumber 42 | { 43 | return numbers 44 | .Skip(1) 45 | .Aggregate( 46 | seed: numbers.First(), 47 | func: Lcm); 48 | } 49 | } -------------------------------------------------------------------------------- /Utilities/Utilities.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": true 6 | } 7 | } --------------------------------------------------------------------------------