diff --git a/AdvendOfCode.Runner/Program.cs b/AdvendOfCode.Runner/Program.cs index d11d0fe..bcfbb1a 100644 --- a/AdvendOfCode.Runner/Program.cs +++ b/AdvendOfCode.Runner/Program.cs @@ -4,7 +4,7 @@ using AdventOfCode.Core; InputReader inputReader = new() { - //IsDebug = true + IsDebug = true }; //inputReader.SetInputByChallange(3); diff --git a/AdventOfCode.Core/InputReader.cs b/AdventOfCode.Core/InputReader.cs index 36d372b..8302a71 100644 --- a/AdventOfCode.Core/InputReader.cs +++ b/AdventOfCode.Core/InputReader.cs @@ -1,4 +1,5 @@ using AdventOfCode.Core.Shared; +using AdventOfCode.Core.Shared.Grid; namespace AdventOfCode.Core { @@ -84,14 +85,14 @@ namespace AdventOfCode.Core } } - public async Task> ReadToGrid() where T : Node, new() + public async Task> ReadToGrid() where T : Point, new() { Grid result = new(); int row = 0; await foreach(string line in ReadAsStringLine()) { // create the nodes from the lines - result.DataGrid.AddRange(line.Select((c, i) => new T { X = i, Y = row, Char = c })); + result.DataGrid.AddRange(line.Select((c, i) => new T { X = i, Y = row, Value = c })); row++; } @@ -113,7 +114,7 @@ namespace AdventOfCode.Core continue; } // create the nodes from the lines - result.DataGrid.AddRange(line.Select((c, i) => new T { X = i, Y = row, Char = c })); + result.DataGrid.AddRange(line.Select((c, i) => new T { X = i, Y = row, Value = c })); row++; } yield return result; diff --git a/AdventOfCode.Core/Shared/A-Star/AStarGrid.cs b/AdventOfCode.Core/Shared/A-Star/AStarGrid.cs index 285766c..b152972 100644 --- a/AdventOfCode.Core/Shared/A-Star/AStarGrid.cs +++ b/AdventOfCode.Core/Shared/A-Star/AStarGrid.cs @@ -27,7 +27,7 @@ } var neighbors = GetNeighbors(current, false); - neighbors = neighbors.Where(n => current.CanMoveTo(n)); + neighbors = neighbors.Where(n => current.CanMoveTo(current)); // calc costs foreach(AStarNode neighbor in neighbors) diff --git a/AdventOfCode.Core/Shared/A-Star/AStarNode.cs b/AdventOfCode.Core/Shared/A-Star/AStarNode.cs index 5cfb291..e7c48a3 100644 --- a/AdventOfCode.Core/Shared/A-Star/AStarNode.cs +++ b/AdventOfCode.Core/Shared/A-Star/AStarNode.cs @@ -1,27 +1,29 @@ -namespace AdventOfCode.Core.Shared.A_Star +using AdventOfCode.Core.Shared.Grid; + +namespace AdventOfCode.Core.Shared.A_Star { - public class AStarNode : Node + public class AStarNode : Point { /// /// Distance from start node /// - public int GCost { get; set; } = 0; + public long GCost { get; set; } = 0; /// /// Distance from end node /// - public int HCost { get; set; } + public long HCost { get; set; } /// /// Total cost (G + F) /// - public int FCost => GCost + HCost; + public long FCost => GCost + HCost; public bool? IsClosed { get; private set; } = null; public AStarNode? ParentNode { get; set; } - public AStarNode(Node position) : base(position) + public AStarNode(Point position) : base(position) { } public AStarNode(int x, int y, char value) : base(x, y, value) @@ -33,8 +35,10 @@ public bool CanMoveTo(AStarNode target) { - int diff = target.Integer - Integer; ; - return diff == 0 || diff == 1; + return false; + // TODO FIX + //int diff = target.Integer - Integer; + //return diff == 0 || diff == 1; } } } diff --git a/AdventOfCode.Core/Shared/Grid.cs b/AdventOfCode.Core/Shared/Grid.cs index 7f6c739..6f3eec4 100644 --- a/AdventOfCode.Core/Shared/Grid.cs +++ b/AdventOfCode.Core/Shared/Grid.cs @@ -1,11 +1,14 @@ -namespace AdventOfCode.Core.Shared +using AdventOfCode.Core.Shared.Grid; +using System.Runtime.CompilerServices; + +namespace AdventOfCode.Core.Shared { - public class Grid where T : Node + public class Grid where T : Point { public List DataGrid { get; set; } = []; - public int Columns => DataGrid.Max(n => n.X) + 1; - public int Rows => DataGrid.Max(n => n.Y) + 1; + public long Columns => DataGrid.Max(n => n.X) + 1; + public long Rows => DataGrid.Max(n => n.Y) + 1; public Grid() { } @@ -13,12 +16,12 @@ public Grid(IEnumerable data) : this(data.ToArray()) { } - public IEnumerable GetRow(int rowNumber) => DataGrid.Where(n => n.X == rowNumber); - public string GetRowAsString(int columnNumber) => string.Concat(DataGrid.Where(n => n.Y == columnNumber).OrderBy(n => n.X).Select(n => n.Char)); - public IEnumerable GetColumn(int columnNumber) => DataGrid.Where(n => n.Y == columnNumber); - public string GetColumnAsString(int rowNumber) => string.Concat(DataGrid.Where(n => n.X == rowNumber).OrderBy(n => n.Y).Select(n => n.Char)); - public T GetNode(int x, int y) => DataGrid.First(n => n.X == x && n.Y == y); - public IEnumerable FindWithValue(char toFind) => DataGrid.Where(n => n.Char == toFind); + public IEnumerable GetRow(long rowNumber) => DataGrid.Where(n => n.X == rowNumber); + public string GetRowAsString(long columnNumber) => string.Concat(DataGrid.Where(n => n.Y == columnNumber).OrderBy(n => n.X).Select(n => n.Value)); + public IEnumerable GetColumn(long columnNumber) => DataGrid.Where(n => n.Y == columnNumber); + public string GetColumnAsString(long rowNumber) => string.Concat(DataGrid.Where(n => n.X == rowNumber).OrderBy(n => n.Y).Select(n => n.Value)); + public T GetNode(long x, long y) => DataGrid.First(n => n.X == x && n.Y == y); + public IEnumerable FindWithValue(char toFind) => DataGrid.Where(n => n.Value == toFind); public void UpdateNode(T node) { @@ -40,7 +43,7 @@ || (source.X == target.X && Math.Abs(source.Y - target.Y) <= 1)); } - public IEnumerable GetSection(int fromX, int fromY, int toX, int toY) + public IEnumerable GetSection(long fromX, long fromY, long toX, long toY) { return DataGrid.Where(node => node.X >= fromX && node.X <= toX && node.Y >= fromY && node.Y <= toY); } diff --git a/AdventOfCode.Core/Shared/Grid/Point.cs b/AdventOfCode.Core/Shared/Grid/Point.cs index 778e118..f535dea 100644 --- a/AdventOfCode.Core/Shared/Grid/Point.cs +++ b/AdventOfCode.Core/Shared/Grid/Point.cs @@ -1,15 +1,22 @@ -using static System.Collections.Specialized.BitVector32; - -namespace AdventOfCode.Core.Shared.Grid +namespace AdventOfCode.Core.Shared.Grid { - public class Point(long X, long Y) + public class Point(long X, long Y) : IEquatable { public long X { get; set; } = X; public long Y { get; set; } = Y; - public string Value { get; set; } = string.Empty; + public char Value { get; set; } = ' '; - public Point(long X, long Y, string value) : this(X, Y) => Value = value; + public Point() : this(-1, -1) { } + + public Point(Point point) : this(point.X, point.Y) { } + + public Point(long X, long Y, char value) : this(X, Y) => Value = value; + + public long DistanceTo(Point other) + { + return Math.Abs(X - other.X) + Math.Abs(Y - other.Y); + } public bool Intersect(Point other) { @@ -22,5 +29,23 @@ namespace AdventOfCode.Core.Shared.Grid { return $"[{Y},{X}] {Value}"; } + + public bool Equals(Point? other) + { + if (other is null) + return false; + + if (this.X != other.X + || this.Y != other.Y + || this.Value != other.Value) + return false; + + return true; + } + + public override int GetHashCode() + { + return this.X.GetHashCode() ^ this.Y.GetHashCode() ^ this.Value.GetHashCode(); + } } } diff --git a/AdventOfCode.Core/Shared/Node.cs b/AdventOfCode.Core/Shared/Node.cs index ca07908..742ccf7 100644 --- a/AdventOfCode.Core/Shared/Node.cs +++ b/AdventOfCode.Core/Shared/Node.cs @@ -1,32 +1,15 @@ -namespace AdventOfCode.Core.Shared +using AdventOfCode.Core.Shared.Grid; + +namespace AdventOfCode.Core.Shared { - public class Node + public class Node(long X, long Y, char value) : Point(X, Y, value) { - public int X { get; set; } - public int Y { get; set; } - public char Char { get; set; } - public short Integer => (short)Char; - - public Node() { } - - public Node(int x, int y, char character) - { - X = x; - Y = y; - Char = character; - } - - public Node(Node position) : this(position.X, position.Y, position.Char) - { } - - public int DistanceTo(Node other) - { - return Math.Abs(X - other.X) + Math.Abs(Y - other.Y); - } + public Node() : this(-1, -1, '0') { } + public Node(long X, long Y) : this(X, Y, ' ') { } public override string ToString() { - return $"[{Y},{X}] {Char}"; + return $"[{Y},{X}] {Value}"; } } } diff --git a/AdventOfCode.Solutions/2023/Day 03/Day03.cs b/AdventOfCode.Solutions/2023/Day 03/Day03.cs index 3a9e43f..42b7939 100644 --- a/AdventOfCode.Solutions/2023/Day 03/Day03.cs +++ b/AdventOfCode.Solutions/2023/Day 03/Day03.cs @@ -37,7 +37,7 @@ namespace AdventOfCode.Solutions._2023 { var (Numbers, Symbols) = await GetNumbersAndSymbols(); return Symbols - .Where(s => s.Value == "*") + .Where(s => s.Value == '*') .Select(g => Numbers.Where(n => n.Intersect(g))) .Where(n => n.Count() == 2) .Select(num => int.Parse(num.First().Value) * int.Parse(num.Last().Value)) @@ -57,7 +57,7 @@ namespace AdventOfCode.Solutions._2023 } List numbers = parts.Where(p => p.IsDigit()).Select(number => new Rectangle(new Point(number.X - 1, number.Y - 1), new Point(number.X + number.Length, number.Y + 1), number.Section)).ToList(); - List symbols = parts.Where(p => p.IsPartSymbol()).Select(symbol => new Point(symbol.X, symbol.Y, symbol.Section)).ToList(); + List symbols = parts.Where(p => p.IsPartSymbol()).Select(symbol => new Point(symbol.X, symbol.Y, symbol.Section[0])).ToList(); return (numbers, symbols); } diff --git a/AdventOfCode.Solutions/2023/Day 05/Day05.cs b/AdventOfCode.Solutions/2023/Day 05/Day05.cs index 457f040..61fe869 100644 --- a/AdventOfCode.Solutions/2023/Day 05/Day05.cs +++ b/AdventOfCode.Solutions/2023/Day 05/Day05.cs @@ -155,7 +155,7 @@ namespace AdventOfCode.Solutions._2023 if (lastId < currentSeedRange.End.X) { - Line map = new(new Point(lastId, 0, ""), currentSeedRange.End); + Line map = new(new Point(lastId, 0, ' '), currentSeedRange.End); resultRanges.Add(map); } } diff --git a/AdventOfCode.Solutions/2023/Day 10/Day10.cs b/AdventOfCode.Solutions/2023/Day 10/Day10.cs index 1881898..1810c6d 100644 --- a/AdventOfCode.Solutions/2023/Day 10/Day10.cs +++ b/AdventOfCode.Solutions/2023/Day 10/Day10.cs @@ -1,4 +1,5 @@ using AdventOfCode.Core; +using AdventOfCode.Core.Shared.Grid; using System.Linq.Expressions; namespace AdventOfCode.Solutions._2023 @@ -31,17 +32,17 @@ namespace AdventOfCode.Solutions._2023 if (value.Steps >= 0) // this is in the main loop { - if (value.Char == '-') // never crosses, also to not update prev + if (value.Value == '-') // never crosses, also to not update prev continue; - if ((prevSegment == 'L' && value.Char == '7') // this is L7 meaning that there is no crossing - || prevSegment == 'F' && value.Char == 'J') // this is FJ meaning that there is no crossing + if ((prevSegment == 'L' && value.Value == '7') // this is L7 meaning that there is no crossing + || prevSegment == 'F' && value.Value == 'J') // this is FJ meaning that there is no crossing { - prevSegment = value.Char; + prevSegment = value.Value; continue; } - prevSegment = value.Char; + prevSegment = value.Value; pipesCrossed++; continue; } @@ -60,7 +61,7 @@ namespace AdventOfCode.Solutions._2023 startPipe.Steps = 0; Pipe[] lineToWalk = grid.GetNeighbors(startPipe, false) - .Where(n => n.Char != '.') + .Where(n => n.Value != '.') .Where(n => n.GetNeighbors().Any(np => np.X == startPipe.X && np.Y == startPipe.Y)) .ToArray(); @@ -99,9 +100,9 @@ namespace AdventOfCode.Solutions._2023 } } - public class Pipe : Node + public class Pipe : Point { - public Pipe From { get; set; } + public Pipe From { get; set; } = null; public int Steps { get; set; } = -1; @@ -115,7 +116,7 @@ namespace AdventOfCode.Solutions._2023 public IEnumerable GetNeighbors() { - switch (Char) + switch (Value) { case '|': yield return GetNorth(); diff --git a/AdventOfCode.Solutions/2023/Day 14/Day14.cs b/AdventOfCode.Solutions/2023/Day 14/Day14.cs index 3d4b6a3..78f220e 100644 --- a/AdventOfCode.Solutions/2023/Day 14/Day14.cs +++ b/AdventOfCode.Solutions/2023/Day 14/Day14.cs @@ -1,4 +1,9 @@ using AdventOfCode.Core; +using AdventOfCode.Core.Shared.Grid; +using System.Collections.Generic; +using System.Runtime.ExceptionServices; +using System.Text; +using System.Threading.Tasks.Dataflow; namespace AdventOfCode.Solutions._2023 { @@ -8,38 +13,154 @@ namespace AdventOfCode.Solutions._2023 public async Task GetSolutionPart1() { - string[] lines = await _inputReader.ReadAsVerticalArrayString(); - int totalWeight = 0; - for(int lineIndex = 0; lineIndex < lines.Length; lineIndex++) - { - int weight = lines[lineIndex].Length, lineWeight = 0; - for (int charIndex = 0; charIndex < lines[lineIndex].Length; charIndex++) - { - if (lines[lineIndex][charIndex] is '.') - continue; + Grid grid = await _inputReader.ReadToGrid(); + HashSet bolders = grid.FindWithValue('O').Select(p => new Point(p.X, p.Y)).ToHashSet(); + HashSet cubes = grid.FindWithValue('#').Select(p => new Point(p.X, p.Y)).ToHashSet(); - if (lines[lineIndex][charIndex] is '#') - { - weight = lines[lineIndex].Length - charIndex - 1; - continue; - } + bolders = MoveUp(bolders, cubes); - if (lines[lineIndex][charIndex] is 'O') - { - lineWeight += weight; - weight--; - } - } - - totalWeight += lineWeight; - } - - return totalWeight.ToString(); + return bolders.Sum(bolder => grid.Rows - bolder.Y).ToString(); } public async Task GetSolutionPart2() { - return string.Empty; + long maxCycles = 1_000_000_000; + Dictionary mapStates = []; + bool foundLoop = false; + + Grid grid = await _inputReader.ReadToGrid(); + HashSet bolders = grid.FindWithValue('O').Select(p => new Point(p.X, p.Y)).ToHashSet(); + HashSet cubes = grid.FindWithValue('#').Select(p => new Point(p.X, p.Y)).ToHashSet(); + + for (int currentCycle = 0; currentCycle < maxCycles; currentCycle++) + { + mapStates.TryAdd(CreateStringMap(bolders, cubes, grid.Rows, grid.Columns), currentCycle); + + bolders = MoveUp(bolders, cubes); + bolders = MoveLeft(bolders, cubes); + bolders = MoveDown(bolders, cubes, grid.Rows); + bolders = MoveRight(bolders, cubes, grid.Columns); + + if (foundLoop) continue; + + int hash = bolders.GetHashCode(); + if (mapStates.TryGetValue(CreateStringMap(bolders, cubes, grid.Rows, grid.Columns), out int prevCycle)) + { + // calculate remaining cycles to the first exit point + long remainingCycles = (maxCycles - prevCycle) % (currentCycle + 1 - prevCycle) + 1; + // set the total the first exit point + maxCycles = remainingCycles + currentCycle; + foundLoop = true; + } + } + + + return bolders.Sum(bolder => grid.Rows - bolder.Y).ToString(); + } + + private HashSet MoveUp(HashSet bolders, HashSet cubes) + { + HashSet boldersMoved = []; + foreach (Point bolder in bolders.OrderBy(bolder => bolder.Y) ) + { + // current location + long ySearch = bolder.Y; + while (ySearch - 1 >= 0 // ensure in map + && !boldersMoved.Contains(new(bolder.X, ySearch - 1)) // check that the next tile does not have a bolder + && !cubes.Contains(new(bolder.X, ySearch - 1))) // check that the next tile has not cube + { + ySearch--; // move to next + } + + // something found on next location so place bolder here + boldersMoved.Add(new(bolder.X, ySearch)); + } + + return boldersMoved; + } + + private HashSet MoveDown(HashSet bolders, HashSet cubes, long ySize) + { + HashSet boldersMoved = []; + foreach (Point bolder in bolders.OrderBy(bolder => bolder.Y)) + { + // current location + long ySearch = bolder.Y; + while (ySearch + 1 < ySize // ensure in map + && !boldersMoved.Contains(new(bolder.X, ySearch + 1)) // check that the next tile does not have a bolder + && !cubes.Contains(new(bolder.X, ySearch + 1))) // check that the next tile has not cube + { + ySearch++; // move to next + } + + // something found on next location so place bolder here + boldersMoved.Add(new(bolder.X, ySearch)); + } + + return boldersMoved; + } + + private HashSet MoveLeft(HashSet bolders, HashSet cubes) + { + HashSet boldersMoved = []; + foreach (Point bolder in bolders.OrderBy(bolder => bolder.X)) + { + // current location + long xSearch = bolder.X; + while (xSearch - 1 >= 0 // ensure in map + && !boldersMoved.Contains(new(xSearch, bolder.Y)) // check that the next tile does not have a bolder + && !cubes.Contains(new(xSearch, bolder.Y))) // check that the next tile has not cube + { + xSearch--; // move to next + } + + // something found on next location so place bolder here + boldersMoved.Add(new(xSearch, bolder.Y)); + } + + return boldersMoved; + } + + private HashSet MoveRight(HashSet bolders, HashSet cubes, long xSize) + { + HashSet boldersMoved = []; + foreach (Point bolder in bolders.OrderBy(bolder => bolder.Y)) + { + // current location + long xSearch = bolder.Y; + while (xSearch + 1 < xSize // ensure in map + && !boldersMoved.Contains(new(xSearch, bolder.Y)) // check that the next tile does not have a bolder + && !cubes.Contains(new(xSearch, bolder.Y))) // check that the next tile has not cube + { + xSearch++; // move to next + } + + // something found on next location so place bolder here + boldersMoved.Add(new(xSearch, bolder.Y)); + } + + return boldersMoved; + } + + private static string CreateStringMap(HashSet bolders, HashSet cubess, long maxX, long maxY) + { + var stringBuilder = new StringBuilder(); + for (var y = 0; y < maxY; y++) + { + for (var x = 0; x < maxX; x++) + { + if (bolders.Contains(new Point(x, y))) + stringBuilder.Append('O'); + else if (cubess.Contains(new Point(x, y))) + stringBuilder.Append('#'); + else + stringBuilder.Append('.'); + } + + stringBuilder.AppendLine(); + } + + return stringBuilder.ToString(); } } } \ No newline at end of file diff --git a/AdventOfCode.Solutions/2023/Day 15/Day15.cs b/AdventOfCode.Solutions/2023/Day 15/Day15.cs new file mode 100644 index 0000000..226faa8 --- /dev/null +++ b/AdventOfCode.Solutions/2023/Day 15/Day15.cs @@ -0,0 +1,19 @@ +using AdventOfCode.Core; + +namespace AdventOfCode.Solutions._2023 +{ + public class Day15(InputReader reader) : IChallange + { + private InputReader _inputReader = reader; + + public async Task GetSolutionPart1() + { + return string.Empty; + } + + public async Task GetSolutionPart2() + { + return string.Empty; + } + } +} \ No newline at end of file diff --git a/AdventOfCode.Solutions/2023/Day 15/day-15-input.txt b/AdventOfCode.Solutions/2023/Day 15/day-15-input.txt new file mode 100644 index 0000000..e69de29