diff --git a/AdventOfCode/AdventOfCode.csproj b/AdventOfCode/AdventOfCode.csproj index ab3abdc..0ff1ded 100644 --- a/AdventOfCode/AdventOfCode.csproj +++ b/AdventOfCode/AdventOfCode.csproj @@ -13,7 +13,6 @@ - diff --git a/AdventOfCode/Common/AOCExtensions.cs b/AdventOfCode/Common/AOCExtensions.cs index ca3760d..9fab128 100644 --- a/AdventOfCode/Common/AOCExtensions.cs +++ b/AdventOfCode/Common/AOCExtensions.cs @@ -39,5 +39,17 @@ namespace AdventOfCode.Common Console.WriteLine(); } } + + public static void PrintSquareArray(this char[,] arr) + { + for (int x = 0; x < arr.GetLength(0); x++) + { + for (int y = 0; y < arr.GetLength(1); y++) + { + Console.Write(arr[x, y] + " "); + } + Console.WriteLine(); + } + } } } diff --git a/AdventOfCode/Models/AOCDay.cs b/AdventOfCode/Models/AOCDay.cs index 43d70f8..71265db 100644 --- a/AdventOfCode/Models/AOCDay.cs +++ b/AdventOfCode/Models/AOCDay.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; namespace AdventOfCode.Models { @@ -47,8 +48,8 @@ namespace AdventOfCode.Models this._response.Status = false; this._response.StackTrace = e.StackTrace; } - if (!request.IgnoreLogMessages) - this._response.Debug = this._debugMessages; + + this._response.Debug = this._debugMessages; return this._response; } @@ -66,6 +67,26 @@ namespace AdventOfCode.Models { return this._request.Input.Trim().Replace("\r", "").Split("\n"); } + + protected List SplitInputOnEmptyLine() + { + var rows = GetSplitInput(); + var input = new List(); + int set = 0; + for (int i = 0; i < rows.Length; i++) + { + if (string.IsNullOrWhiteSpace(rows[i])) + { + input.Add(rows.Skip(set).Take(i - set).ToArray()); + set = i + 1; + } + if (i == rows.Length - 1) + { + input.Add(rows.Skip(set).ToArray()); + } + } + return input; + } public void SetLogger(ILogger logger) { this._logger = logger; @@ -73,8 +94,16 @@ namespace AdventOfCode.Models protected void Log(object message, params object[] args) { - if (_logger != null && !this._request.IgnoreLogMessages) - _logger.LogInformation(message.ToString(), args); + if (!this._request.IgnoreLogMessages) + { + if (_logger != null) + _logger.LogInformation(message.ToString(), args); + _debugMessages.Add(string.Format(message.ToString(), args)); + } + } + + protected void Info(object message, params object[] args) + { _debugMessages.Add(string.Format(message.ToString(), args)); } } diff --git a/AdventOfCode/_2022/Day11/Day11.cs b/AdventOfCode/_2022/Day11/Day11.cs new file mode 100644 index 0000000..fec9b59 --- /dev/null +++ b/AdventOfCode/_2022/Day11/Day11.cs @@ -0,0 +1,68 @@ +using AdventOfCode._2022.Models; +using AdventOfCode.Common; +using AdventOfCode.Models; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AdventOfCode._2022.Day11 +{ + [AOC(year: 2022, day: 11)] + public class Day11 : AOCDay + { + protected override AOCResponse ExecutePartA() + { + ExecuteMonkeyBusiness(20); + return _response; + } + + protected override AOCResponse ExecutePartB() + { + ExecuteMonkeyBusiness(10000); + return _response; + } + + private void ExecuteMonkeyBusiness(int rounds) + { + var input = SplitInputOnEmptyLine(); + var monkeys = new List(); + foreach (var monkey in input) + { + monkeys.Add(new Monkey(_request.Version, monkey.ToArray())); + } + + long lcm = 1; //Only needed for part b + //Target Monkey Mapping + foreach (var monkey in monkeys) + { + lcm *= monkey.DivisibleNumber; + monkey.MapTargetMonkeys(monkeys.First(x => x.MonkeyId == monkey.TargetMonkeyIds.Item1), monkeys.First(x => x.MonkeyId == monkey.TargetMonkeyIds.Item2)); + } + + //Start round + for (int i = 0; i < rounds; i++) + { + foreach (var monkey in monkeys) + { + monkey.Business(lcm); + } + + if (_request.Version == AOCVersion.A) + monkeys.ForEach(x => Log("Round" + (i + 1) + "_M" + x.MonkeyId + ": " + string.Join(", ", x.Items))); + else + { + if ((i + 1) == 1 || (i + 1) == 20 || (i + 1) == 1000 || (i + 1) % 1000 == 0) + { + monkeys.ForEach(x => Log($"R{i + 1}Monkey{x.MonkeyId} inspected {x.ItemsInspected} items")); + monkeys.ForEach(x => Log("Round" + (i + 1) + "_M" + x.MonkeyId + ": " + string.Join(", ", x.Items))); + } + } + + } + monkeys.ForEach(x => Info($"Monkey{x.MonkeyId} inspected {x.ItemsInspected} items")); + + var crazyMonkeys = monkeys.OrderByDescending(x => x.ItemsInspected).ToArray(); + _response.Answer = crazyMonkeys[0].ItemsInspected * crazyMonkeys[1].ItemsInspected; + } + } +} diff --git a/AdventOfCode/_2022/Day11/Monkey.cs b/AdventOfCode/_2022/Day11/Monkey.cs new file mode 100644 index 0000000..b4fd514 --- /dev/null +++ b/AdventOfCode/_2022/Day11/Monkey.cs @@ -0,0 +1,105 @@ +using AdventOfCode.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace AdventOfCode._2022.Day11 +{ + public class Monkey + { + private AOCVersion _version; + public int MonkeyId { get; set; } + public Queue Items { get; set; } + public Tuple Operation { get; set; } + public long DivisibleNumber { get; set; } + public Tuple TargetMonkeyIds { get; set; } + public Tuple TargetMonkeys { get; set; } + public long ItemsInspected { get; set; } + public Monkey(AOCVersion version, string[] monkey) + { + _version = version; + //Pull monkey id + MonkeyId = Convert.ToInt32(Regex.Replace(monkey.First(), "[^0-9.]", "")); + + //Item creation + Items = new Queue(); + Regex.Replace(monkey[1], "[^0-9. ]", "").Trim().Split(' ').ToList().ForEach(x => Items.Enqueue(Convert.ToInt32(x))); + + //Generate operation + var splitOperation = monkey[2].Split(' '); + Operation = Tuple.Create(splitOperation[splitOperation.Length - 2], splitOperation[splitOperation.Length - 1]); + + //Divisible number + DivisibleNumber = Convert.ToInt32(Regex.Replace(monkey[3], "[^0-9.]", "")); + + //TargetMonkey + TargetMonkeyIds = Tuple.Create(Convert.ToInt32(Regex.Replace(monkey[4], "[^0-9.]", "")), Convert.ToInt32(Regex.Replace(monkey[5], "[^0-9.]", ""))); + + //Set items insepcts to 0 + ItemsInspected = 0; + } + + public void MapTargetMonkeys(Monkey trueMonkey, Monkey falseMonkey) + { + TargetMonkeys = Tuple.Create(trueMonkey, falseMonkey); + } + + public void Business(long lcm = 0) + { + while (Items.Any()) + { + var item = Items.Dequeue(); + ItemsInspected++; + if (_version == AOCVersion.A) + { + item = ExecuteOperation(item); //Worry level + item = item / 3; //Monkey boredom + + //Throw item to next monkey + if (item % DivisibleNumber == 0) + { + TargetMonkeys.Item1.Items.Enqueue(item); + } + else + { + TargetMonkeys.Item2.Items.Enqueue(item); + } + } + else + { + long number; + if (!long.TryParse(Operation.Item2, out number)) + number = item; + item %= lcm; + number %= lcm; + var result = Operation.Item1 == "+" ? item + number : item * number; + if (result% DivisibleNumber == 0) + { + TargetMonkeys.Item1.Items.Enqueue(result); + } + else + { + TargetMonkeys.Item2.Items.Enqueue(result); + } + } + + + } + } + + private long ExecuteOperation(long item) + { + long number; + if (!long.TryParse(Operation.Item2, out number)) + number = item; + if (Operation.Item1 == "+") + return number + item; + else if (Operation.Item1 == "*") + return number * item; + throw new ArgumentException("Unknown operation " + Operation.Item1); + } + + } +} diff --git a/AdventOfCode/_2022/Day12/Day12.cs b/AdventOfCode/_2022/Day12/Day12.cs new file mode 100644 index 0000000..5361d1f --- /dev/null +++ b/AdventOfCode/_2022/Day12/Day12.cs @@ -0,0 +1,29 @@ +using AdventOfCode.Common; +using AdventOfCode.Models; +using System.Linq; + +namespace AdventOfCode._2022.Day12 +{ + [AOC(year: 2022, day: 12)] + [IgnoreTestAnswer(AOCVersion.B)] //Run time is 1min 38sec 648ms for PartB + public class Day12 : AOCDay + { + protected override AOCResponse ExecutePartA() + { + var map = new Heightmap(GetSplitInput()); + map.Traverse(_request.Version); + map.Steps.ToList().ForEach(x => Info(x)); + _response.Answer = map.TotalSteps; + + return _response; + } + + protected override AOCResponse ExecutePartB() + { + //Only difference is done VIA AOCVersion variable + //Run time is 1min 38sec 648ms for PartB, might want to optimize + return ExecutePartA(); + } + + } +} diff --git a/AdventOfCode/_2022/Day12/Heightmap.cs b/AdventOfCode/_2022/Day12/Heightmap.cs new file mode 100644 index 0000000..4f488e5 --- /dev/null +++ b/AdventOfCode/_2022/Day12/Heightmap.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AdventOfCode.Common; +using AdventOfCode.Models; + +namespace AdventOfCode._2022.Day12 +{ + public class Heightmap + { + public int TotalSteps { get; set; } + public string[] Steps { get; set; } + private Coord _start, _end; + private char[,] _map; + private readonly int MaxX, MaxY; + public Heightmap(string[] input) + { + //Create our 2d board + _map = new char[input.Count(), input.First().Length]; + for (int i = 0; i < input.Count(); i++) + { + var line = input[i]; + for (int j = 0; j < line.Length; j++) + { + _map[i, j] = line[j]; + } + } + + //Find the start and end + MaxX = _map.GetLength(0); + MaxY = _map.GetLength(1); + for (int x = 0; x < _map.GetLength(0); x++) + { + for (int y = 0; y < _map.GetLength(1); y++) + { + if (_map[x, y] == 'S') _start = new Coord(x, y); + if (_map[x, y] == 'E') _end = new Coord(x, y); + } + } + + //Ensure a start and end was found + if (_start == null) throw new ArgumentException("No start coord found!"); + if (_end == null) throw new ArgumentException("No end coord found!"); + _start.SetDistance(_end);//Set the distance between start and end + } + + public void Traverse(AOCVersion version) + { + if (version == AOCVersion.A) + { + //Traverse our 2d matrix + AStarTraversal(); + } + else + { + _map[_start.X, _start.Y] = 'a'; //Change our S to an 'a' elevation + var possibleStarts = new List(); + for (int x = 0; x < _map.GetLength(0); x++) + { + for (int y = 0; y < _map.GetLength(1); y++) + { + if (_map[x, y] == 'a') possibleStarts.Add(new Coord(x, y)); + } + } + + var routes = new List(); + foreach (var possibleStart in possibleStarts) + { + _start = possibleStart; + AStarTraversal(); + routes.Add(Steps.ToArray()); + } + Steps = routes.OrderByDescending(x => x.Length).Last(); + TotalSteps = Steps.Length - 1; + } + } + + private List GetAdjacentCoords(Coord currentCoord) + { + var adjCoords = new List() + { + new Coord { X = currentCoord.X, Y = currentCoord.Y - 1, Parent = currentCoord, Cost = currentCoord.Cost + 1 }, //Left + new Coord { X = currentCoord.X, Y = currentCoord.Y + 1, Parent = currentCoord, Cost = currentCoord.Cost + 1},// Right + new Coord { X = currentCoord.X - 1, Y = currentCoord.Y, Parent = currentCoord, Cost = currentCoord.Cost + 1 }, //Up + new Coord { X = currentCoord.X + 1, Y = currentCoord.Y, Parent = currentCoord, Cost = currentCoord.Cost + 1 }, //Down + }; + + adjCoords.ForEach(coord => coord.SetDistance(_end)); + var currentChar = _map[currentCoord.X, currentCoord.Y]; + if (currentChar == 'S') currentChar = 'a'; //Set the starting char to 'a' so it can do the diff in char instead of from 'S' + + return adjCoords + .Where(coord => coord.X >= 0 && coord.X < MaxX) + .Where(coord => coord.Y >= 0 && coord.Y < MaxY) + .Where(coord => (_map[coord.X, coord.Y] != 'E' && (_map[coord.X, coord.Y] - currentChar) <= 1) //At most upper height is 1, can drop any amount + || (_map[coord.X, coord.Y] == 'E' && (currentChar == 'z' || currentChar == 'y'))) //E's height IS 'z', so it can go z->z or y->z for the final move. + .ToList(); + } + + private void AStarTraversal() + { + var activeCoords = new List(); + activeCoords.Add(_start); + var visitedCoords = new List(); + + while (activeCoords.Any()) + { + var checkCoord = activeCoords.OrderByDescending(x => x.CostDistance).Last(); + + if (checkCoord.X == _end.X && checkCoord.Y == _end.Y) + { + //We found the destination and we can be sure (Because the the OrderBy above) + //That it's the most low cost option. + var coord = checkCoord; + var steps = new List(); + while (true) + { + steps.Add($"({coord.X},{coord.Y}) -> {_map[coord.X, coord.Y]}"); + coord = coord.Parent; + if (coord == null) + { + Steps = steps.Reverse().ToArray(); + TotalSteps = steps.Count-1; //N coords touched, so N-1 steps. + return; + } + } + } + + visitedCoords.Add(checkCoord); + activeCoords.Remove(checkCoord); + + var adjacentCoords = GetAdjacentCoords(checkCoord); + + foreach (var adjCoor in adjacentCoords) + { + //Skip to next coord if we've already visited + if (visitedCoords.Any(x => x.X == adjCoor.X && x.Y == adjCoor.Y)) + continue; + + //Its already pending as an active coord, but we need to re-check and update it if the cost is smaller + if (activeCoords.Any(x => x.X == adjCoor.X && x.Y == adjCoor.Y)) + { + var existingCoord = activeCoords.First(x => x.X == adjCoor.X && x.Y == adjCoor.Y); + if (existingCoord.CostDistance > checkCoord.CostDistance) + { + activeCoords.Remove(existingCoord); + activeCoords.Add(adjCoor); + } + } + else + { + //New coord so we are adding it to the list + activeCoords.Add(adjCoor); + } + } + } + } + + /// + /// This is a brute force method, it works for the sample but not my input, it takes way to long as it tries to explore every single path and square. + /// + /// + /// + /// + [Obsolete] + public List Traverse(List visited, Coord pos) + { + visited.Add(pos); //Signify that we visited this coord in our search + char val = _map[pos.X, pos.Y]; + if (val == 'E') + { + //We only want to return if the last visited char is 'z' since it needs to complete alphabet before continuing + var lastVisitedLocation = visited[visited.Count - 2]; + return _map[lastVisitedLocation.X, lastVisitedLocation.Y] == 'z' ? visited : null; + } + + if (val == 'S') val = 'a'; //So its on an even playing field. + + List shortestPath = null; + //left + var newCoord = new Coord(pos.X, pos.Y - 1); + if ((pos.Y - 1) >= 0 + && !visited.Any(c => c.X == newCoord.X && c.Y == newCoord.Y) + && _map[newCoord.X, newCoord.Y] - val <= 1) + { + var newVisited = visited.Select(x => x).ToList(); + var path = Traverse(newVisited, newCoord); + if (path != null && (shortestPath == null || path.Count() < shortestPath.Count())) + shortestPath = path; + } + //right + newCoord = new Coord(pos.X, pos.Y + 1); + if ((pos.Y + 1) < MaxY + && !visited.Any(c => c.X == newCoord.X && c.Y == newCoord.Y) + && _map[newCoord.X, newCoord.Y] - val <= 1) + { + var newVisited = visited.Select(x => x).ToList(); + var path = Traverse(newVisited, newCoord); + if (path != null && (shortestPath == null || path.Count() < shortestPath.Count())) + shortestPath = path; + } + //up + newCoord = new Coord(pos.X - 1, pos.Y); + if ((pos.X - 1) >= 0 + && !visited.Any(c => c.X == newCoord.X && c.Y == newCoord.Y) + && _map[newCoord.X, newCoord.Y] - val <= 1) + { + var newVisited = visited.Select(x => x).ToList(); + var path = Traverse(newVisited, newCoord); + if (path != null && (shortestPath == null || path.Count() < shortestPath.Count())) + shortestPath = path; + } + //down + newCoord = new Coord(pos.X + 1, pos.Y); + if ((pos.X + 1) < MaxX + && !visited.Any(c => c.X == newCoord.X && c.Y == newCoord.Y) + && _map[newCoord.X, newCoord.Y] - val <= 1) + { + var newVisited = visited.Select(x => x).ToList(); + var path = Traverse(newVisited, newCoord); + if (path != null && (shortestPath == null || path.Count() < shortestPath.Count())) + shortestPath = path; + } + return shortestPath; + } + } + + public class Coord + { + public Coord() { } + public Coord(int x, int y) + { + this.X = x; + this.Y = y; + } + public int X { get; set; } + public int Y { get; set; } + public int Cost { get; set; } + public int Distance { get; set; } + public int CostDistance { get { return Cost + Distance; } } + public Coord Parent { get; set; } + + //An estimated distance to a specific target + public void SetDistance(Coord target) + { + this.Distance = Math.Abs(target.X - X) + Math.Abs(target.Y - Y); + } + } +}