From ea82ee5cd0707bd5f45af2308eea2a09b995e25e Mon Sep 17 00:00:00 2001
From: Patrick Auernig <patrick.auernig@mykolab.com>
Date: Sat, 11 Dec 2021 12:37:46 +0100
Subject: [PATCH] Add solution for 2021 day 08 part 2

---
 2021/README.md          |   2 +-
 2021/day-08/common.cs   |  46 ++++++++++++++
 2021/day-08/part_two.cs | 137 ++++++++++++++++++++++++++++++++++++++++
 README.md               |   2 +-
 4 files changed, 185 insertions(+), 2 deletions(-)
 create mode 100644 2021/day-08/part_two.cs

diff --git a/2021/README.md b/2021/README.md
index 5df7fb0..f92b694 100644
--- a/2021/README.md
+++ b/2021/README.md
@@ -11,7 +11,7 @@
 | 05  |   ✓    |   ✓    | [Nim]    |
 | 06  |   ✓    |   ✓    | [Inko]   |
 | 07  |   ✓    |   ✓    | [Swift]  |
-| 08  |   ✓    |        | [C#]     |
+| 08  |   ✓    |   ✓    | [C#]     |
 | 09  |   ✓    |   ✓    | [C]      |
 | 10  |   ✓    |   ✓    | [OCaml]  |
 | 11  |        |        |          |
diff --git a/2021/day-08/common.cs b/2021/day-08/common.cs
index 11865a0..6619082 100644
--- a/2021/day-08/common.cs
+++ b/2021/day-08/common.cs
@@ -43,4 +43,50 @@ namespace Common {
             }
         }
     }
+
+    public static class Permutation {
+        public static IEnumerable<T[]> Permutations<T>(this IEnumerable<T> input) {
+            return Permute(input, Enumerable.Empty<T>());
+        }
+
+        private static IEnumerable<T[]> Permute<T>(IEnumerable<T> memo, IEnumerable<T> pfx) {
+            if (memo.Any()) {
+                return
+                    from t in memo.Select((a, b) => (a, b))
+                    let next_memo = memo.Take(t.Item2).Concat(memo.Skip(t.Item2 + 1)).ToArray()
+                    let next_pfx = pfx.Append(t.Item1)
+                    from perms in Permute(next_memo, next_pfx)
+                    select perms;
+
+            } else {
+                return new[] { pfx.ToArray() };
+            }
+        }
+    }
+
+    public abstract class Option<T> {
+        private Option() {}
+
+        public abstract bool IsSome();
+
+        public bool GetValue(out T val) {
+            if (this is Some s) {
+                val = s.Value;
+                return true;
+            }
+
+            val = default(T);
+            return false;
+        }
+
+        public sealed class Some : Option<T> {
+            public Some(T val) => Value = val;
+            public T Value { get; }
+            public override bool IsSome() => true;
+        }
+
+        public sealed class None : Option<T> {
+            public override bool IsSome() => false;
+        }
+    }
 }
diff --git a/2021/day-08/part_two.cs b/2021/day-08/part_two.cs
new file mode 100644
index 0000000..06fce16
--- /dev/null
+++ b/2021/day-08/part_two.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Common;
+using static Common.SegmentDisplay;
+using static Common.Permutation;
+
+class PartTwo {
+    private static readonly HashSet<int>[] VALID_POSITIONS = {
+        new HashSet<int>() {0, 1, 2, 4, 5, 6},
+        new HashSet<int>() {2, 5},
+        new HashSet<int>() {0, 2, 3, 4, 6},
+        new HashSet<int>() {0, 2, 3, 5, 6},
+        new HashSet<int>() {1, 2, 3, 5},
+        new HashSet<int>() {0, 1, 3, 5, 6},
+        new HashSet<int>() {0, 1, 3, 4, 5, 6},
+        new HashSet<int>() {0, 2, 5},
+        new HashSet<int>() {0, 1, 2, 3, 4, 5, 6},
+        new HashSet<int>() {0, 1, 2, 3, 5, 6},
+    };
+
+    public static void Main(string[] args) {
+        var seg_display = new SegmentDisplay(args[0]);
+
+        var output_sum = seg_display.Wirings.Sum(w => OutputNumber(w));
+
+        Console.WriteLine(output_sum);
+    }
+
+    private static int OutputNumber(Wiring wiring) {
+        var inputs = wiring.Inputs.OrderBy(x => x.Count).ToList();
+        var digit = 0;
+
+        if (Analyze(inputs).GetValue(out var segments)) {
+            var digit_idx = wiring.Outputs.Count - 1;
+
+            foreach (var output in wiring.Outputs) {
+                var segment_digit = GetOutputDigit(output, segments);
+
+                digit += segment_digit * (int)Math.Pow(10, digit_idx);
+
+                digit_idx--;
+            }
+        }
+
+        return digit;
+    }
+
+    private static int GetOutputDigit(HashSet<char> output, Dictionary<char, int> segments) {
+        var mapped_output = output.Select(v => segments[v]);
+
+        switch (output.Count) {
+            case 2: return 1;
+            case 3: return 7;
+            case 4: return 4;
+            case 7: return 8;
+            case 5:
+                foreach (var n in new[]{2, 3, 5}) {
+                    if (!VALID_POSITIONS[n].Except(mapped_output).Any()) return n;
+                }
+                break;
+            case 6:
+                foreach (var n in new[]{0, 6, 9}) {
+                    if (!VALID_POSITIONS[n].Except(mapped_output).Any()) return n;
+                }
+                break;
+            default:
+                throw new Exception("Invalid output length");
+        }
+
+        throw new Exception("No valid mapping for output");
+    }
+
+    private static Option<Dictionary<char, int>> Analyze(IEnumerable<HashSet<char>> inputs) {
+        return Analyze(inputs, new Dictionary<char, int>());
+    }
+
+    private static Option<Dictionary<char, int>> Analyze(
+        IEnumerable<HashSet<char>> inputs,
+        Dictionary<char, int> segments
+    ) {
+        if (!inputs.Any()) return new Option<Dictionary<char, int>>.Some(segments);
+
+        var input = inputs.First();
+        var segment_count = input.Count;
+        var unmapped_input = input.Except(segments.Keys);
+        var mapped_input = input.Intersect(segments.Keys).Select(v => segments[v]);
+
+        switch (segment_count) {
+            case 2: return Analyze(1, unmapped_input, mapped_input, inputs, segments);
+            case 3: return Analyze(7, unmapped_input, mapped_input, inputs, segments);
+            case 4: return Analyze(4, unmapped_input, mapped_input, inputs, segments);
+            case 7: return new Option<Dictionary<char, int>>.Some(segments);
+            case 5:
+                foreach (var n in new[]{2, 3, 5}) {
+                    var retval = Analyze(n, unmapped_input.ToList(), mapped_input.ToList(), inputs, segments);
+                    if (retval.IsSome()) return retval;
+                }
+                break;
+            case 6:
+                foreach (var n in new[]{0, 6, 9}) {
+                    var retval = Analyze(n, unmapped_input.ToList(), mapped_input.ToList(), inputs, segments);
+                    if (retval.IsSome()) return retval;
+                }
+                break;
+            default:
+                throw new Exception("Invalid input length");
+        }
+
+        return new Option<Dictionary<char, int>>.None();
+    }
+
+    private static Option<Dictionary<char, int>> Analyze(
+        int num,
+        IEnumerable<char> unmapped_input,
+        IEnumerable<int> mapped_input,
+        IEnumerable<HashSet<char>> inputs,
+        Dictionary<char, int> segments
+    ) {
+        if (!mapped_input.Except(VALID_POSITIONS[num]).Any()) {
+            foreach (var perm in Permutations(unmapped_input)) {
+                var new_segments = new Dictionary<char, int>(segments);
+
+                foreach (var pair in perm.Zip(VALID_POSITIONS[num].Except(mapped_input), (a, b) => (a, b))) {
+                    new_segments.Add(pair.Item1, pair.Item2);
+                }
+
+                var retval = Analyze(inputs.Skip(1).ToList(), new_segments);
+
+                if (retval.IsSome()) return retval;
+            }
+        }
+
+        return new Option<Dictionary<char, int>>.None();
+    }
+}
diff --git a/README.md b/README.md
index 6201c1a..e188ad2 100644
--- a/README.md
+++ b/README.md
@@ -8,4 +8,4 @@
 -   [2018](2018/README.md) (0% completed)
 -   [2019](2019/README.md) (0% completed)
 -   [2020](2020/README.md) (16% completed)
--   [2021](2021/README.md) (38% completed)
+-   [2021](2021/README.md) (40% completed)