From 30d27c396f130caf61dcb12a555674f200c18d52 Mon Sep 17 00:00:00 2001 From: mazharenko Date: Mon, 23 Dec 2024 00:33:06 +0500 Subject: [PATCH] bfs to sequence generator --- Directory.Packages.props | 2 +- src/aoc/Common/Bfs/Bfs.cs | 37 ---------- src/aoc/Common/Bfs/Builders.cs | 45 ------------- src/aoc/Common/Bfs/Folder.cs | 60 ----------------- src/aoc/Common/Bfs/Search.cs | 22 ------ src/aoc/Common/Search/Bfs.cs | 45 +++++++++++++ src/aoc/Common/Search/Builders.cs | 23 +++++++ src/aoc/Common/Search/Common.cs | 56 ++++++++++++++++ .../Common/Search/PathEnumerableExtensions.cs | 32 +++++++++ src/aoc/Year2024/Day10.cs | 67 +++++++++---------- src/aoc/Year2024/Day12.cs | 38 +++++------ src/aoc/Year2024/Day18.cs | 22 ++---- src/aoc/Year2024/Day20.cs | 22 ++---- 13 files changed, 214 insertions(+), 257 deletions(-) delete mode 100644 src/aoc/Common/Bfs/Bfs.cs delete mode 100644 src/aoc/Common/Bfs/Builders.cs delete mode 100644 src/aoc/Common/Bfs/Folder.cs delete mode 100644 src/aoc/Common/Bfs/Search.cs create mode 100644 src/aoc/Common/Search/Bfs.cs create mode 100644 src/aoc/Common/Search/Builders.cs create mode 100644 src/aoc/Common/Search/Common.cs create mode 100644 src/aoc/Common/Search/PathEnumerableExtensions.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 09b7a01..0823377 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,7 @@ - + diff --git a/src/aoc/Common/Bfs/Bfs.cs b/src/aoc/Common/Bfs/Bfs.cs deleted file mode 100644 index 7d4460a..0000000 --- a/src/aoc/Common/Bfs/Bfs.cs +++ /dev/null @@ -1,37 +0,0 @@ -using aoc.Common.BfsBuilders; - -// ReSharper disable once CheckNamespace -namespace aoc.Common; - -public readonly record struct PathItem(T Item, int Len); - -public readonly record struct Path(L> PathList) -{ - public T HeadItem { get; } = PathList.Head.Item; -} - -public abstract record Result; // todo: explicit match? -public record Found(Path Path) : Result; -public record NotFound : Result; - -public static class Bfs -{ - public static class Common - { - public interface IAdjacency - { - IEnumerable<(T newState, int weight)> GetAdjacent(T pos); - } - - public class AdhocAdjacency(Func> function) : IAdjacency - { - public IEnumerable<(T newState, int weight)> GetAdjacent(T pos) => function(pos); - } - - - public static BfsBuilder StartWith(T start) - { - return new BfsBuilder(start); - } - } -} \ No newline at end of file diff --git a/src/aoc/Common/Bfs/Builders.cs b/src/aoc/Common/Bfs/Builders.cs deleted file mode 100644 index bbecd15..0000000 --- a/src/aoc/Common/Bfs/Builders.cs +++ /dev/null @@ -1,45 +0,0 @@ -using aoc.Common.BfsImpl; -using static aoc.Common.Bfs.Common; - -// ReSharper disable once CheckNamespace -namespace aoc.Common.BfsBuilders; - -public class BfsBuilder(T start) -{ - public BfsAdjBuilder WithAdjacency(Func adjacency) - => WithAdjacency(new AdhocAdjacency(adjacency)); - - public BfsAdjBuilder WithAdjacency(Func> adjacency) - => WithAdjacency(new AdhocAdjacency(state => adjacency(state).Select(newState => (newState, 1)))); - - public BfsAdjBuilder WithAdjacency(IAdjacency adjacency) - => new(start, adjacency, x => x); -} - -public class BfsAdjBuilder(T start, IAdjacency adjacency, Func visitedKey) -{ - public BfsAdjBuilder WithVisitedKey(Func visitedKeyFunction) - { - return new BfsAdjBuilder(start, adjacency, visitedKeyFunction); - } - - public BfsAdjBuilder WithoutTrackingVisited() - { - return new BfsAdjBuilder(start, adjacency, x => null); - } - - public Folder WithFolder(TRes seed, Func, (TRes, TraversalResult)> function) - { - return WithFolder(seed, new AdhocFold(function)); - } - - public Folder WithFolder(TRes seed, IFold fold) - { - return new Folder(start, adjacency, visitedKey, seed, fold); - } - - public Folder> WithTarget(Target target) - { - return new Folder>(start, adjacency, visitedKey, new NotFound(), new SearchFold(target)); - } -} \ No newline at end of file diff --git a/src/aoc/Common/Bfs/Folder.cs b/src/aoc/Common/Bfs/Folder.cs deleted file mode 100644 index b607ad5..0000000 --- a/src/aoc/Common/Bfs/Folder.cs +++ /dev/null @@ -1,60 +0,0 @@ -// ReSharper disable once CheckNamespace - -namespace aoc.Common.BfsImpl; - -public class Folder(T start, Bfs.Common.IAdjacency adjacency, Func visitedKey, TRes seed, IFold folder) -{ - public TRes Run() - { - var visited = new HashSet { visitedKey(start) }; - var queue = new PriorityQueue, int>(); - queue.Enqueue(new Path(L.Singleton(new PathItem(start, 0))), int.MaxValue); - return Fold(seed, visited, queue); - } - - private TRes Fold(TRes state, HashSet visited, PriorityQueue, int> queue) - { - while (true) - { - if (queue.Count == 0) return state; - var currentPath = queue.Dequeue(); - var current = currentPath.PathList.Head; - switch (folder.Fold(state, currentPath)) - { - case (var newState, TraversalResult.Interrupt): - return newState; - case (var newState, TraversalResult.Continue): - var adjacent = adjacency.GetAdjacent(current.Item); - foreach (var (adjacentValue, weight) in adjacent) - { - var key = visitedKey(adjacentValue); - if (key is null || visited.Add(key)) queue.Enqueue(new Path(currentPath.PathList.Prepend(new PathItem(adjacentValue, current.Len + weight))), current.Len + weight); - } - - state = newState; - continue; - default: - throw new InvalidOperationException(); - } - - break; - } - } -} - -public interface IFold -{ - (TRes, TraversalResult) Fold(TRes acc, Path path); -} - -public class AdhocFold(Func, (TRes, TraversalResult)> function) : IFold -{ - public (TRes, TraversalResult) Fold(TRes acc, Path path) - => function(acc, path); -} - -public enum TraversalResult -{ - Continue, - Interrupt -} \ No newline at end of file diff --git a/src/aoc/Common/Bfs/Search.cs b/src/aoc/Common/Bfs/Search.cs deleted file mode 100644 index fccf3b4..0000000 --- a/src/aoc/Common/Bfs/Search.cs +++ /dev/null @@ -1,22 +0,0 @@ -// ReSharper disable once CheckNamespace - -namespace aoc.Common.BfsImpl; - -public delegate bool Target(T state); // ITarget? - -public static class Targets -{ - public static Target Value(T value) - => state => Equals(value, state); -} - -public class SearchFold(Target target) : IFold> -{ - public (Result, TraversalResult) Fold(Result acc, Path path) - { - var (current, _) = path.PathList; - if (target(current.Item)) - return (new Found(path), TraversalResult.Interrupt); - return (new NotFound(), TraversalResult.Continue); - } -} \ No newline at end of file diff --git a/src/aoc/Common/Search/Bfs.cs b/src/aoc/Common/Search/Bfs.cs new file mode 100644 index 0000000..fa95459 --- /dev/null +++ b/src/aoc/Common/Search/Bfs.cs @@ -0,0 +1,45 @@ +using System.Collections; + +namespace aoc.Common.Search; + +public static class Bfs +{ + public static BfsBuilder StartWith(T start) + { + return new BfsBuilder(start, x => x); + } +} + +public class Bfs(T start, IAdjacency adjacency, Func? visitedKey) : IEnumerable> +{ + public IEnumerator> GetEnumerator() + { + var visited = new HashSet(); + if (visitedKey is not null) + visited.Add(visitedKey(start)); + var queue = new PriorityQueue, int>(); + queue.Enqueue(new Path(L.Singleton(new PathItem(start, 0))), int.MaxValue); + while (true) + { + if (queue.Count == 0) yield break; + var currentPath = queue.Dequeue(); + + yield return currentPath; + + var current = currentPath.PathList.Head; + var adjacent = adjacency.GetAdjacent(current.Item); + foreach (var (adjacentValue, weight) in adjacent) + { + if (visitedKey is null || visited.Add(visitedKey(adjacentValue))) + queue.Enqueue(new Path(currentPath.PathList.Prepend(new PathItem(adjacentValue, current.Len + weight))), current.Len + weight); + } + } + } + + // todo: Enumerable? Enumerable>? + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} \ No newline at end of file diff --git a/src/aoc/Common/Search/Builders.cs b/src/aoc/Common/Search/Builders.cs new file mode 100644 index 0000000..c075872 --- /dev/null +++ b/src/aoc/Common/Search/Builders.cs @@ -0,0 +1,23 @@ +namespace aoc.Common.Search; + +public class BfsBuilder(T start, Func? visitedKeyFunction) +{ + public BfsBuilder WithVisitedKey(Func visited) + { + return new BfsBuilder(start, visited); + } + + public BfsBuilder WithoutTrackingVisited() + { + return new BfsBuilder(start, null); + } + + public Bfs WithAdjacency(Func adjacency) + => WithAdjacency(new AdhocAdjacency(adjacency)); + + public Bfs WithAdjacency(Func> adjacency) + => WithAdjacency(new AdhocAdjacency(state => adjacency(state).Select(newState => (newState, 1)))); + + public Bfs WithAdjacency(IAdjacency adjacency) + => new(start, adjacency, visitedKeyFunction); +} \ No newline at end of file diff --git a/src/aoc/Common/Search/Common.cs b/src/aoc/Common/Search/Common.cs new file mode 100644 index 0000000..cb385af --- /dev/null +++ b/src/aoc/Common/Search/Common.cs @@ -0,0 +1,56 @@ +namespace aoc.Common.Search; + +public readonly record struct PathItem(T Item, int Len); + +public readonly record struct Path(L> PathList) +{ + public T HeadItem { get; } = PathList.Head.Item; + public int Len => PathList.Head.Len; +} + +public abstract record Result; + +public static class ResultExtensions +{ + public static TRes Match(this Result result, Func, TRes> onFound, Func onNotFound) + { + return result switch + { + Found found => onFound(found.Path), + NotFound => onNotFound(), + _ => throw new ArgumentOutOfRangeException(nameof(result)) + }; + } + + public static void Match(this Result result, Action> onFound, Action onNotFound) + { + switch (result) + { + case Found found: + onFound(found.Path); + break; + case NotFound: + onNotFound(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(result)); + } + } + + public static Path AsFound(this Result result) => + Match(result, p => p, () => throw new InvalidOperationException("The result was not 'Found'")); +} + +public record Found(Path Path) : Result; + +public record NotFound : Result; + +public interface IAdjacency +{ + IEnumerable<(T newState, int weight)> GetAdjacent(T state); +} + +public class AdhocAdjacency(Func> function) : IAdjacency +{ + public IEnumerable<(T newState, int weight)> GetAdjacent(T pos) => function(pos); +} \ No newline at end of file diff --git a/src/aoc/Common/Search/PathEnumerableExtensions.cs b/src/aoc/Common/Search/PathEnumerableExtensions.cs new file mode 100644 index 0000000..02a2a38 --- /dev/null +++ b/src/aoc/Common/Search/PathEnumerableExtensions.cs @@ -0,0 +1,32 @@ +namespace aoc.Common.Search; + + +public delegate bool Target(T state); // ITarget? + +public static class Targets +{ + public static Target Value(T value) + => state => Equals(value, state); +} + +public static class PathEnumerableExtensions +{ + public static Path FindTarget(this IEnumerable> paths, Target target) + { + return paths.First(path => target(path.HeadItem)); + } + + public static Result TryFindTarget(this IEnumerable> paths, Target target) + { + foreach (var path in paths) + { + if (target(path.HeadItem)) + return new Found(path); + } + + return new NotFound(); + } + + public static IEnumerable Items(this IEnumerable> paths) + => paths.Select(path => path.HeadItem); +} \ No newline at end of file diff --git a/src/aoc/Year2024/Day10.cs b/src/aoc/Year2024/Day10.cs index f71d4e2..361c18f 100644 --- a/src/aoc/Year2024/Day10.cs +++ b/src/aoc/Year2024/Day10.cs @@ -1,20 +1,22 @@ using aoc.Common; -using aoc.Common.BfsImpl; +using aoc.Common.Search; +using MoreLinq; namespace aoc.Year2024; internal partial class Day10 -{ private readonly Example example = new( - """ - 89010123 - 78121874 - 87430965 - 96549874 - 45678903 - 32019012 - 01329801 - 10456732 - """); +{ + private readonly Example example = new( + """ + 89010123 + 78121874 + 87430965 + 96549874 + 45678903 + 32019012 + 01329801 + 10456732 + """); internal partial class Part1 { @@ -29,17 +31,13 @@ public int Solve(int[,] input) return trailHeads.Sum(head => { - var bfs = - Bfs.Common.StartWith(head.point) + return + Bfs.StartWith(head.point) .WithAdjacency(new MapAdjacency(input)) - .WithFolder(L.Empty>(), (acc, path) => - { - if (input.At(path.PathList.Head.Item) == 9) - return (acc.Prepend(path.PathList.Head.Item), TraversalResult.Continue); - return (acc, TraversalResult.Continue); - }); - - return bfs.Run().Distinct().Count(); + .Items() + .Where(p => input.At(p) == 9) + .Distinct() + .Count(); }); } @@ -63,19 +61,13 @@ public int Solve(int[,] input) return trailHeads.Sum(head => { - var bfs = - Bfs.Common.StartWith(head.point) - .WithAdjacency(new MapAdjacency(input)) - // the Adjacency guarantees that the graph search won't blow up, but we need all the paths - .WithoutTrackingVisited() - .WithFolder(0, (acc, path) => - { - if (input.At(path.PathList.Head.Item) == 9) - return (acc + 1, TraversalResult.Continue); - return (acc, TraversalResult.Continue); - }); - - return bfs.Run(); + return Bfs + .StartWith(head.point) + // the Adjacency guarantees that the graph search won't blow up, but we need all the paths + .WithoutTrackingVisited() + .WithAdjacency(new MapAdjacency(input)) + .Items() + .Count(p => input.At(p) == 9); }); } @@ -86,10 +78,11 @@ public int Solve(int[,] input) } } - public class MapAdjacency(int[,] map) : Bfs.Common.IAdjacency> + private class MapAdjacency(int[,] map) : IAdjacency> { public IEnumerable<(V newState, int weight)> GetAdjacent(V pos) - {// todo: special bfs case for maps + { + // todo: special bfs case for maps return Directions.All4().Select(d => d + pos) .Where(map.Inside) .Where(adj => map.At(adj) == map.At(pos) + 1) // slope diff --git a/src/aoc/Year2024/Day12.cs b/src/aoc/Year2024/Day12.cs index 6674b2b..633f585 100644 --- a/src/aoc/Year2024/Day12.cs +++ b/src/aoc/Year2024/Day12.cs @@ -1,5 +1,5 @@ using aoc.Common; -using aoc.Common.BfsImpl; +using aoc.Common.Search; using MoreLinq; namespace aoc.Year2024; @@ -37,31 +37,27 @@ public int Solve(char[,] input) var start = remainingPlots.First(); // While traversing the graph, for each visited node, we not only determine its adjacent nodes // but also count how many of them lie outside this region. - var g = Bfs.Common.StartWith((plot: start, plant: input.At(start), sides: (int?)null)) + var region = Bfs.StartWith((plot: start, plant: input.At(start), sides: (int?)null)) .WithAdjacency(p => { var dirs = Directions.All4(); - var allAdj = dirs.Select(d => d + p.plot).ToList(); - var (inside, outside) = allAdj.Partition(adj => input.TryAt(adj, out var adjValue) && adjValue == p.plant); return inside.Select(x => (x, input.At(x), (int?)null)) .Append((p.plot, p.plant, outside.Count())); - }).WithFolder(L.Empty<(V plot, int sides)>(), (acc, path) => - { - if (path.HeadItem.sides is null) - return (acc, TraversalResult.Continue); - return (acc.Prepend((path.HeadItem.plot, path.HeadItem.sides.Value)), TraversalResult.Continue); - }).Run().ToList(); + }).Items() + .Where(x => x.sides is not null) + .Select(x => (x.plot, sides: x.sides!.Value)) + .ToList(); - var area = g.Select(x => x.plot).Count(); - var perimeter = g.Sum(x => x.sides); + var area = region.Select(x => x.plot).Count(); + var perimeter = region.Sum(x => x.sides); price += area * perimeter; - remainingPlots.ExceptWith(g.Select(x => x.plot)); + remainingPlots.ExceptWith(region.Select(x => x.plot)); } return price; @@ -109,21 +105,17 @@ public int Solve(char[,] input) var price = 0; while (remainingPoints.Count != 0) { - var start = (remainingPoints.First().point, el: remainingPoints.First().element); - var region = Bfs.Common.StartWith(start) + var start = remainingPoints.First().point; + var region = Bfs.StartWith(start) .WithAdjacency(p => { var dirs = Directions.All4(); - return dirs.Select(d => d + p.point) - .Where(input.Inside) - .Select(adj => (adj, input.At(adj))) - .Where(adj => adj.Item2 == p.el); + return dirs.Select(d => d + p) + .Where(adj => input.TryAt(adj, out var adjValue) + && adjValue == input.At(p)); }) - .WithVisitedKey(x => x.point) - .WithFolder(L.Empty>(), - (acc, path) => (acc.Prepend(path.HeadItem.point), TraversalResult.Continue) - ).Run().ToList(); + .Items().ToList(); var area = region.Count; var extendedRegion = diff --git a/src/aoc/Year2024/Day18.cs b/src/aoc/Year2024/Day18.cs index cb0ae95..08fabed 100644 --- a/src/aoc/Year2024/Day18.cs +++ b/src/aoc/Year2024/Day18.cs @@ -1,5 +1,5 @@ using aoc.Common; -using aoc.Common.BfsImpl; +using aoc.Common.Search; using mazharenko.AoCAgent.Generator; namespace aoc.Year2024; @@ -9,7 +9,7 @@ internal partial class Day18 { private static Result> RunSearch(HashSet> blockingBytes) { - return Bfs.Common.StartWith(V.Create(0, 0)) + return Bfs.StartWith(V.Create(0, 0)) .WithAdjacency(pos => { return Directions.All4() @@ -17,8 +17,7 @@ private static Result> RunSearch(HashSet> blockingBytes) .Where(x => !blockingBytes.Contains(x)) .Where(x => x is { X: >= 0 and <= 70, Y: >= 0 and <= 70 }) .ToArray(); - }).WithTarget(Targets.Value(V.Create(70, 70))) - .Run(); + }).TryFindTarget(Targets.Value(V.Create(70, 70))); } public V[] Parse(string input) @@ -34,14 +33,7 @@ internal partial class Part1 public int Solve(V[] input) { var blockingBytes = input.Take(1024).ToHashSet(); - - var bs = RunSearch(blockingBytes); - - return bs switch - { - // todo: expose Len - Found> found => found.Path.PathList.Count() - 1 - }; + return RunSearch(blockingBytes).AsFound().Len; } } @@ -49,7 +41,6 @@ internal partial class Part2 { public string Solve(V[] input) { - // todo: maybe collect all paths to target with Dijkstra and then remove the paths one by one containing the byte under consideration var blockingBytes = input.Take(1024).ToHashSet(); var wonPositions = new HashSet>(); @@ -58,9 +49,8 @@ public string Solve(V[] input) blockingBytes.Add(anotherByte); if (wonPositions.Count != 0 && !wonPositions.Contains(anotherByte)) continue; - - var bs = RunSearch(blockingBytes); - switch (bs) + + switch (RunSearch(blockingBytes)) { case Found> (var path): wonPositions.UnionWith(path.PathList.Select(pi => pi.Item)); diff --git a/src/aoc/Year2024/Day20.cs b/src/aoc/Year2024/Day20.cs index 42ad7e3..a1b9ee7 100644 --- a/src/aoc/Year2024/Day20.cs +++ b/src/aoc/Year2024/Day20.cs @@ -1,5 +1,5 @@ using aoc.Common; -using aoc.Common.BfsImpl; +using aoc.Common.Search; using mazharenko.AoCAgent.Generator; namespace aoc.Year2024; @@ -25,28 +25,18 @@ private static int Solve(char[,] map, int shortcutLength) var start = map.AsEnumerable().Single(x => x.element == 'S').point; var end = map.AsEnumerable().Single(x => x.element == 'E').point; - - var forward = Bfs.Common.StartWith(start) + + var forward = Bfs.StartWith(start) .WithAdjacency(x => Directions.All4().Select(d => d + x) .Where(y => map.At(y) != '#')) - .WithFolder(new Dictionary, int>(), (acc, path) => - { - // todo: I need bfs as a sequence generator!!! - acc[path.HeadItem] = path.PathList.Head.Len; - return (acc, TraversalResult.Continue); - }).Run(); + .ToDictionary(path => path.HeadItem, path => path.Len); - var backward = Bfs.Common.StartWith(end) + var backward = Bfs.StartWith(end) .WithAdjacency(x => Directions.All4().Select(d => d + x)//todo: extension .Where(y => map.At(y) != '#')) - .WithFolder(new Dictionary, int>(), (acc, path) => - { - // todo: I need bfs as an enumerator!!! - acc[path.HeadItem] = path.PathList.Head.Len; - return (acc, TraversalResult.Continue); - }).Run(); + .ToDictionary(path => path.HeadItem, path => path.Len); var noShortcutsPath = forward[end];