diff --git a/src/AdventOfCode/Day23.cs b/src/AdventOfCode/Day23.cs new file mode 100644 index 0000000..540eaa0 --- /dev/null +++ b/src/AdventOfCode/Day23.cs @@ -0,0 +1,110 @@ +using System.Collections.Generic; +using System.Linq; +using AdventOfCode.Utilities; + +namespace AdventOfCode +{ + /// + /// Solver for Day 23 + /// + public class Day23 + { + public int Part1(string[] input) + { + SortedDictionary> graph = BuildGraph(input); + + int total = 0; + + /* Look for triangles of interconnected nodes, i.e. + + node + / \ + / \ + left -- right + */ + foreach (string node in graph.Keys) + { + foreach (string left in graph[node].Where(l => string.CompareOrdinal(node, l) < 0)) + { + foreach (string right in graph[left].Where(r => string.CompareOrdinal(left, r) < 0)) + { + if (!graph[node].Contains(right)) + { + continue; + } + + // found a triangle + if (node.StartsWith('t') || left.StartsWith('t') || right.StartsWith('t')) + { + total++; + } + } + } + } + + return total; + } + + public string Part2(string[] input) + { + SortedDictionary> graph = BuildGraph(input); + + List biggest = []; + HashSet visited = []; + + foreach (string node in graph.Keys) + { + if (!visited.Add(node)) + { + // already part of another clique + continue; + } + + List clique = [node]; + + foreach (string candidate in graph.Keys) + { + if (visited.Contains(candidate)) + { + // already part of another clique + continue; + } + + // if it's connected to every current member of the clique then it's allowed in + if (clique.All(member => graph[member].Contains(candidate))) + { + clique.Add(candidate); + visited.Add(candidate); + } + } + + if (clique.Count > biggest.Count) + { + biggest = clique; + } + } + + return string.Join(',', biggest.Order()); + } + + /// + /// Build the graph of every node to all the reachable nodes from that point + /// + /// Input edge descriptions + /// Graph + private static SortedDictionary> BuildGraph(string[] input) + { + SortedDictionary> graph = new(); + + foreach (string line in input) + { + string[] elements = line.Split('-'); + + graph.GetOrCreate(elements[0], () => new List()).Add(elements[1]); + graph.GetOrCreate(elements[1], () => new List()).Add(elements[0]); + } + + return graph; + } + } +} diff --git a/src/AdventOfCode/inputs/day23.txt b/src/AdventOfCode/inputs/day23.txt new file mode 100644 index 0000000..192657c Binary files /dev/null and b/src/AdventOfCode/inputs/day23.txt differ diff --git a/tests/AdventOfCode.Tests/Day23Tests.cs b/tests/AdventOfCode.Tests/Day23Tests.cs new file mode 100644 index 0000000..41c7eb3 --- /dev/null +++ b/tests/AdventOfCode.Tests/Day23Tests.cs @@ -0,0 +1,105 @@ +using System.IO; +using Xunit; +using Xunit.Abstractions; + +namespace AdventOfCode.Tests +{ + public class Day23Tests + { + private readonly ITestOutputHelper output; + private readonly Day23 solver; + + public Day23Tests(ITestOutputHelper output) + { + this.output = output; + this.solver = new Day23(); + } + + private static string[] GetRealInput() + { + string[] input = File.ReadAllLines("inputs/day23.txt"); + return input; + } + + private static string[] GetSampleInput() + { + return new string[] + { + "kh-tc", + "qp-kh", + "de-cg", + "ka-co", + "yn-aq", + "qp-ub", + "cg-tb", + "vc-aq", + "tb-ka", + "wh-tc", + "yn-cg", + "kh-ub", + "ta-co", + "de-co", + "tc-td", + "tb-wq", + "wh-td", + "ta-ka", + "td-qp", + "aq-cg", + "wq-ub", + "ub-vc", + "de-ta", + "wq-aq", + "wq-vc", + "wh-yn", + "ka-de", + "kh-ta", + "co-tc", + "wh-qp", + "tb-vc", + "td-yn", + }; + } + + [Fact] + public void Part1_SampleInput_ProducesCorrectResponse() + { + var expected = 7; + + var result = solver.Part1(GetSampleInput()); + + Assert.Equal(expected, result); + } + + [Fact] + public void Part1_RealInput_ProducesCorrectResponse() + { + var expected = 1599; + + var result = solver.Part1(GetRealInput()); + output.WriteLine($"Day 23 - Part 1 - {result}"); + + Assert.Equal(expected, result); + } + + [Fact] + public void Part2_SampleInput_ProducesCorrectResponse() + { + var expected = "co,de,ka,ta"; + + var result = solver.Part2(GetSampleInput()); + + Assert.Equal(expected, result); + } + + [Fact] + public void Part2_RealInput_ProducesCorrectResponse() + { + var expected = "av,ax,dg,di,dw,fa,ge,kh,ki,ot,qw,vz,yw"; + + var result = solver.Part2(GetRealInput()); + output.WriteLine($"Day 23 - Part 2 - {result}"); + + Assert.Equal(expected, result); + } + } +}