diff --git a/advent-of-code-2021.csproj b/advent-of-code-2021.csproj
index 8cf4a20..8c40388 100644
--- a/advent-of-code-2021.csproj
+++ b/advent-of-code-2021.csproj
@@ -69,6 +69,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/inputs/18.txt b/inputs/18.txt
new file mode 100644
index 0000000..889631d
--- /dev/null
+++ b/inputs/18.txt
@@ -0,0 +1,100 @@
+[3,[5,[7,[3,9]]]]
+[[[[7,0],0],[2,[2,8]]],[[[7,8],1],3]]
+[[[[2,7],0],7],4]
+[[2,1],[9,0]]
+[[[[7,1],[3,2]],[[9,8],5]],[2,7]]
+[[[8,9],[[8,7],0]],[[[8,7],[6,3]],[[1,7],[8,9]]]]
+[[8,6],[[9,[1,7]],[6,[3,9]]]]
+[[2,[[5,6],6]],[[4,[5,9]],[3,[4,5]]]]
+[[[[2,0],[1,1]],[6,6]],[[1,9],[[2,7],[6,8]]]]
+[[[4,6],[[6,3],[3,9]]],[[[2,6],[6,1]],[[9,9],[1,5]]]]
+[[[4,[3,1]],3],6]
+[[0,[[5,2],8]],[1,[9,[4,3]]]]
+[[[[8,6],[2,1]],[2,[8,6]]],[[[7,1],[3,9]],0]]
+[[[[4,7],[2,7]],[[8,9],2]],[[[2,4],[7,2]],[3,7]]]
+[[5,[2,2]],[[1,6],[[9,1],[5,0]]]]
+[[5,[[1,2],[6,4]]],[6,8]]
+[[[5,[1,7]],7],[7,[8,1]]]
+[[1,9],[[0,3],[[6,7],[2,4]]]]
+[1,[7,[[0,6],0]]]
+[[[[5,7],9],[[3,2],7]],[[5,1],[9,9]]]
+[[[[0,4],[9,6]],[[8,3],[7,4]]],[7,[6,2]]]
+[[[[1,6],0],[[8,0],[3,4]]],[[3,[0,3]],4]]
+[4,[[7,8],[4,[9,7]]]]
+[[[2,[3,7]],5],[0,[9,9]]]
+[[[2,0],[[5,8],[7,6]]],[[9,[6,2]],[3,2]]]
+[[[3,1],3],[[[3,7],6],[9,8]]]
+[[7,[[2,5],5]],[5,[3,[4,5]]]]
+[[[6,7],6],[2,[[9,3],9]]]
+[[[[5,6],7],[[3,2],5]],[[9,[4,3]],[3,8]]]
+[0,7]
+[[[4,6],[2,9]],[[[7,6],[5,1]],7]]
+[[0,5],[[1,[4,1]],[[7,3],9]]]
+[[[2,[3,8]],5],[[[5,9],8],[7,0]]]
+[[[6,[8,6]],[[3,6],7]],[[2,1],[6,[7,5]]]]
+[[2,[[6,3],[8,9]]],[[[5,6],4],[[7,0],1]]]
+[[[[7,1],[5,6]],8],[[[8,9],4],[8,3]]]
+[[[9,2],[1,0]],0]
+[[5,[5,[8,5]]],4]
+[[3,[5,[4,9]]],3]
+[[8,[[7,7],6]],5]
+[[4,[[5,1],1]],[1,[1,[9,8]]]]
+[[[7,[3,6]],[[2,8],[4,7]]],[[[8,8],[4,0]],[2,4]]]
+[[[[3,6],3],[0,9]],2]
+[[2,8],[[8,[8,6]],[[1,1],[4,5]]]]
+[[2,[1,[1,0]]],[[[6,2],[7,4]],[[7,1],6]]]
+[3,[8,[7,[8,6]]]]
+[[1,0],[[[0,4],[0,5]],[1,5]]]
+[[[[5,0],4],[[7,8],[8,8]]],[[1,7],0]]
+[1,[[[4,1],7],[6,[9,0]]]]
+[[[1,8],2],[[5,5],[8,5]]]
+[[4,[9,[0,6]]],[[[8,9],[4,5]],4]]
+[[[[5,4],[1,7]],[[3,1],[7,9]]],[[[0,8],[4,7]],[[5,9],6]]]
+[[[[8,0],9],4],[[7,[1,3]],5]]
+[[[[5,0],6],[[6,1],8]],[[9,1],7]]
+[[9,[6,[8,8]]],[7,[[7,1],6]]]
+[[[5,[1,5]],[3,[4,2]]],[[[5,2],7],[[6,9],[2,8]]]]
+[[[5,[5,5]],[5,7]],[4,[[2,9],7]]]
+[[[[0,4],0],[[0,6],[3,0]]],[0,[[8,1],2]]]
+[[[7,[4,6]],[[7,2],[4,6]]],[[[9,3],[4,9]],6]]
+[[6,7],7]
+[[[4,1],[8,[1,5]]],[[4,6],0]]
+[[[4,[5,5]],5],[[0,[2,7]],[1,1]]]
+[[[[0,1],3],[6,7]],[4,7]]
+[[4,[6,4]],[[[9,8],1],[9,3]]]
+[[[4,9],0],[[[7,0],[0,9]],[1,[1,0]]]]
+[[[7,9],[[9,5],[6,9]]],[[0,[3,0]],[0,[5,9]]]]
+[9,[[0,0],[[1,9],9]]]
+[[[5,[0,5]],[[9,8],[9,5]]],[[0,[2,5]],7]]
+[[[[5,8],6],9],[[[2,7],7],[[7,8],5]]]
+[[8,[[4,7],6]],2]
+[[[[7,1],[9,0]],[9,[1,7]]],[[8,[6,7]],[2,5]]]
+[[4,[2,9]],8]
+[[[[7,6],[5,3]],[5,[9,7]]],[[6,[8,1]],[[6,4],9]]]
+[[7,[[7,8],4]],[[1,3],[4,[9,7]]]]
+[[[6,[6,7]],[[2,8],3]],[7,[6,[0,3]]]]
+[[9,8],[[0,[4,8]],[[9,1],1]]]
+[[[[4,0],[5,9]],7],[6,[[5,9],[9,6]]]]
+[[8,1],[1,[9,[8,3]]]]
+[[[1,[5,1]],[6,7]],[[5,9],[2,[6,7]]]]
+[[[3,7],[[7,8],1]],[[0,[6,3]],[8,0]]]
+[[5,[[9,3],[1,2]]],7]
+[[[1,[9,9]],3],[[6,4],[4,1]]]
+[[6,[1,[3,6]]],[2,9]]
+[[2,[0,2]],[5,[[9,4],[5,0]]]]
+[[4,[[3,1],[7,0]]],[[9,1],[[5,5],[6,7]]]]
+[[3,[[7,1],[3,4]]],[7,[9,[9,4]]]]
+[[9,9],[[5,4],[[9,7],4]]]
+[[[5,1],8],[[6,7],9]]
+[[[0,[9,5]],[4,3]],[3,2]]
+[[[6,[4,1]],[[8,7],[5,3]]],[[[1,2],5],[[9,2],5]]]
+[[[[7,4],[9,0]],[[1,8],[2,9]]],[[5,[1,9]],[4,0]]]
+[[[4,[3,8]],[[3,3],[2,8]]],[[[1,3],9],[[8,5],6]]]
+[[[[6,4],[7,9]],[[7,6],8]],[7,[9,8]]]
+[[7,[3,5]],7]
+[[[[5,0],[2,3]],[3,7]],[[4,[6,3]],[7,[4,4]]]]
+[[6,[3,[7,6]]],[[[5,8],[8,1]],[3,[1,5]]]]
+[[8,[9,[5,2]]],2]
+[[1,[5,4]],[[7,[8,0]],8]]
+[[[[2,7],4],3],[[1,4],[8,4]]]
+[3,[9,2]]
\ No newline at end of file
diff --git a/src/18.cs b/src/18.cs
new file mode 100644
index 0000000..b377a13
--- /dev/null
+++ b/src/18.cs
@@ -0,0 +1,799 @@
+using System.Diagnostics;
+
+namespace aoc2021;
+
+internal class Day18 : Day
+{
+ class SFNum
+ {
+ public int? left;
+ public int? right;
+ public SFNum? leftNum;
+ public SFNum? rightNum;
+ public SFNum? owner;
+
+ public bool exploding;
+ public bool splittingLeft;
+ public bool splittingRight;
+
+ public override string ToString() => $"{(exploding ? "<+white>" : "")}" +
+ $"[" +
+ $"{leftNum?.ToString() ?? ""}{(splittingLeft ? "<+white>" : "")}{left?.ToString() ?? ""}{(splittingLeft ? "" : "")}" +
+ $"," +
+ $"{rightNum?.ToString() ?? ""}{(splittingRight ? "<+white>" : "")}{right?.ToString() ?? ""}{(splittingRight ? "" : "")}" +
+ $"]" +
+ $"{(exploding ? "" : "")}";
+
+ public SFNum()
+ {
+
+ }
+
+ public SFNum(SFNum other)
+ {
+ left = other.left;
+ right = other.right;
+ if (other.leftNum != null)
+ {
+ leftNum = new SFNum(other.leftNum)
+ {
+ owner = this
+ };
+ }
+ if (other.rightNum != null)
+ {
+ rightNum = new SFNum(other.rightNum)
+ {
+ owner = this
+ };
+ }
+
+ exploding = other.exploding;
+ splittingLeft = other.splittingLeft;
+ splittingRight = other.splittingRight;
+ }
+
+ public SFNum Root
+ {
+ get
+ {
+ SFNum curr = this;
+ while (curr.owner != null)
+ {
+ curr = curr.owner;
+ }
+
+ return curr;
+ }
+ }
+
+ public long Magnitude
+ {
+ get
+ {
+ long result = 0;
+ if (leftNum != null)
+ {
+ result += 3 * leftNum.Magnitude;
+ }
+ else
+ {
+ result += 3 * (int)left!;
+ }
+
+ if (rightNum != null)
+ {
+ result += 2 * rightNum.Magnitude;
+ }
+ else
+ {
+ result += 2 * (int)right!;
+ }
+
+ return result;
+ }
+ }
+ }
+
+ [Conditional("DEBUG")]
+ private static void DoTests()
+ {
+ DoExplodeTests();
+ DoSplitTests();
+ DoMagnitudeTests();
+ DoAddTests();
+ DoReduceTests();
+ DoPart1Tests();
+ }
+
+ private static void DoExplodeTests()
+ {
+ Logger.Log("test: explode");
+ var tests = new List()
+ {
+ "[[[[[9,8],1],2],3],4]",
+ "[7,[6,[5,[4,[3,2]]]]]",
+ "[[6,[5,[4,[3,2]]]],1]",
+ "[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]",
+ "[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]",
+ "[[[[[4,3],4],4],[7,[[8,4],9]]],[1,1]]",
+ "[[[[0,7],4],[7,[[8,4],9]]],[1,1]]",
+ "[[[[0,7],4],[[7,8],[0,[6,7]]]],[1,1]]",
+ };
+ var exploded = new List()
+ {
+ "[[[[0,9],2],3],4]",
+ "[7,[6,[5,[7,0]]]]",
+ "[[6,[5,[7,0]]],3]",
+ "[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]",
+ "[[3,[2,[8,0]]],[9,[5,[7,0]]]]",
+ "[[[[0,7],4],[7,[[8,4],9]]],[1,1]]",
+ "[[[[0,7],4],[15,[0,13]]],[1,1]]",
+ "[[[[0,7],4],[[7,8],[6,0]]],[8,1]]",
+ };
+
+ for (int i = 0; i < tests.Count; i++)
+ {
+ Logger.Log($"1.{(i + 1)}");
+ SFNum test = ParseSFNum(tests[i]);
+ SFNum result = ParseSFNum(exploded[i]);
+ var didExplode = CheckExplodes(test);
+ if (!didExplode || test.ToString() != result.ToString())
+ {
+ throw new Exception();
+ }
+ Logger.Log("✓");
+ }
+
+ var twoStepTests = new List()
+ {
+ "[[[[[4,3],4],4],[7,[[8,4],9]]],[1,1]]",
+ };
+ var twoStepExploded = new List()
+ {
+ "[[[[0,7],4],[15,[0,13]]],[1,1]]",
+ };
+
+ for (int i = 0; i < twoStepTests.Count; i++)
+ {
+ Logger.Log($"2.{(i + 1)}");
+ SFNum test = ParseSFNum(twoStepTests[i]);
+ SFNum result = ParseSFNum(twoStepExploded[i]);
+ var didExplode = CheckExplodes(test);
+ if (!didExplode)
+ {
+ throw new Exception();
+ }
+ didExplode = CheckExplodes(test);
+ if (!didExplode || test.ToString() != result.ToString())
+ {
+ throw new Exception();
+ }
+ Logger.Log("✓");
+ }
+ }
+
+ private static void DoSplitTests()
+ {
+ Logger.Log("test: split");
+ var tests = new List()
+ {
+ "[[[[0,7],4],[15,[0,13]]],[1,1]]",
+ "[[[[0,7],4],[[7,8],[0,13]]],[1,1]]",
+ "[[[[7,7],[7,8]],[[9,5],[8,0]]],[[[9,10],20],[8,[9,0]]]]",
+ };
+ var split = new List()
+ {
+ "[[[[0,7],4],[[7,8],[0,13]]],[1,1]]",
+ "[[[[0,7],4],[[7,8],[0,[6,7]]]],[1,1]]",
+ "[[[[7,7],[7,8]],[[9,5],[8,0]]],[[[9,[5,5]],20],[8,[9,0]]]]",
+ };
+
+ for (int i = 0; i < tests.Count; i++)
+ {
+ Logger.Log($"{(i + 1)}");
+ SFNum test = ParseSFNum(tests[i]);
+ SFNum result = ParseSFNum(split[i]);
+ var didSplit = CheckSplits(test);
+ if (!didSplit || test.ToString() != result.ToString())
+ {
+ throw new Exception();
+ }
+ // todo: add tests verifying owners are correct
+ Logger.Log("✓");
+ }
+ }
+
+ private static void DoMagnitudeTests()
+ {
+ Logger.Log("test: magnitude");
+ var tests = new List()
+ {
+ "[9,1]",
+ "[1,9]",
+ "[[9,1],[1,9]]",
+ "[[1,2],[[3,4],5]]",
+ "[[[[0,7],4],[[7,8],[6,0]]],[8,1]]",
+ "[[[[1,1],[2,2]],[3,3]],[4,4]]",
+ "[[[[3,0],[5,3]],[4,4]],[5,5]]",
+ "[[[[5,0],[7,4]],[5,5]],[6,6]]",
+ "[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]",
+ };
+ var magnitudes = new List()
+ {
+ 29,
+ 21,
+ 129,
+ 143,
+ 1384,
+ 445,
+ 791,
+ 1137,
+ 3488,
+ };
+
+ for (int i = 0; i < tests.Count; i++)
+ {
+ Logger.Log($"{(i + 1)}");
+ SFNum test = ParseSFNum(tests[i]);
+ var magnitude = test.Magnitude;
+ if (magnitude != magnitudes[i])
+ {
+ throw new Exception();
+ }
+ Logger.Log("✓");
+ }
+ }
+
+ private static void DoAddTests()
+ {
+ Logger.Log("test: add");
+ var tests = new List>()
+ {
+ new()
+ {
+ "[[[[4,3],4],4],[7,[[8,4],9]]]",
+ "[1,1]",
+ },
+ };
+ var results = new List()
+ {
+ "[[[[[4,3],4],4],[7,[[8,4],9]]],[1,1]]",
+ };
+
+ for (int i = 0; i < tests.Count; i++)
+ {
+ Logger.Log($"{(i + 1)}");
+ SFNum test = ParseSFNum(tests[i][0]);
+ for (int j = 1; j < tests[i].Count; j++)
+ {
+ test = Add(test, ParseSFNum(tests[i][j]));
+ }
+ SFNum result = ParseSFNum(results[i]);
+ if (test.ToString() != result.ToString())
+ {
+ throw new Exception();
+ }
+ if (result.leftNum!.owner != result || result.rightNum!.owner != result)
+ {
+ throw new Exception();
+ }
+ Logger.Log("✓");
+ }
+ }
+
+ private static void DoReduceTests()
+ {
+ Logger.Log("test: reduce");
+ var tests = new List()
+ {
+ "[[[[[4,3],4],4],[7,[[8,4],9]]],[1,1]]",
+ };
+ var results = new List()
+ {
+ "[[[[0,7],4],[[7,8],[6,0]]],[8,1]]",
+ };
+
+ for (int i = 0; i < tests.Count; i++)
+ {
+ Logger.Log($"{(i + 1)}");
+ SFNum test = ParseSFNum(tests[i]);
+ SFNum result = ParseSFNum(results[i]);
+ Reduce(test);
+ if (test.ToString() != result.ToString())
+ {
+ throw new Exception();
+ }
+ Logger.Log("✓");
+ }
+ }
+
+ private static void DoPart1Tests()
+ {
+ Logger.Log("test: add+reduce");
+ var vals = new List>()
+ {
+ new()
+ {
+ ParseSFNum("[1,1]"),
+ ParseSFNum("[2,2]"),
+ ParseSFNum("[3,3]"),
+ ParseSFNum("[4,4]"),
+ },
+ new()
+ {
+ ParseSFNum("[1,1]"),
+ ParseSFNum("[2,2]"),
+ ParseSFNum("[3,3]"),
+ ParseSFNum("[4,4]"),
+ ParseSFNum("[5,5]"),
+ },
+ new()
+ {
+ ParseSFNum("[1,1]"),
+ ParseSFNum("[2,2]"),
+ ParseSFNum("[3,3]"),
+ ParseSFNum("[4,4]"),
+ ParseSFNum("[5,5]"),
+ ParseSFNum("[6,6]"),
+ },
+ new()
+ {
+ ParseSFNum("[[[[4,3],4],4],[7,[[8,4],9]]]"),
+ ParseSFNum("[1,1]"),
+ },
+ new()
+ {
+ ParseSFNum("[[[0,[4,5]],[0,0]],[[[4,5],[2,6]],[9,5]]]"),
+ ParseSFNum("[7,[[[3,7],[4,3]],[[6,3],[8,8]]]]"),
+ },
+ new()
+ {
+ ParseSFNum("[[[[4,0],[5,4]],[[7,7],[6,0]]],[[8,[7,7]],[[7,9],[5,0]]]]"),
+ ParseSFNum("[[2,[[0,8],[3,4]]],[[[6,7],1],[7,[1,6]]]]"),
+ },
+ new()
+ {
+ ParseSFNum("[[[[6,7],[6,7]],[[7,7],[0,7]]],[[[8,7],[7,7]],[[8,8],[8,0]]]]"),
+ ParseSFNum("[[[[2,4],7],[6,[0,5]]],[[[6,8],[2,8]],[[2,1],[4,5]]]]"),
+ },
+ new()
+ {
+ ParseSFNum("[[[[7,0],[7,7]],[[7,7],[7,8]]],[[[7,7],[8,8]],[[7,7],[8,7]]]]"),
+ ParseSFNum("[7,[5,[[3,8],[1,4]]]]"),
+ },
+ new()
+ {
+ ParseSFNum("[[[[7,7],[7,8]],[[9,5],[8,7]]],[[[6,8],[0,8]],[[9,9],[9,0]]]]"),
+ ParseSFNum("[[2,[2,2]],[8,[8,1]]]"),
+ },
+ new()
+ {
+ ParseSFNum("[[[[6,6],[6,6]],[[6,0],[6,7]]],[[[7,7],[8,9]],[8,[8,1]]]]"),
+ ParseSFNum("[2,9]"),
+ },
+ new()
+ {
+ ParseSFNum("[[[[6,6],[7,7]],[[0,7],[7,7]]],[[[5,5],[5,6]],9]]"),
+ ParseSFNum("[1,[[[9,3],9],[[9,0],[0,7]]]]"),
+ },
+ new()
+ {
+ ParseSFNum("[[[[7,8],[6,7]],[[6,8],[0,8]]],[[[7,7],[5,0]],[[5,5],[5,6]]]]"),
+ ParseSFNum("[[[5,[7,4]],7],1]"),
+ },
+ new()
+ {
+ ParseSFNum("[[[[7,7],[7,7]],[[8,7],[8,7]]],[[[7,0],[7,7]],9]]"),
+ ParseSFNum("[[[[4,2],2],6],[8,7]]"),
+ },
+ };
+ var results = new List()
+ {
+ ParseSFNum("[[[[1,1],[2,2]],[3,3]],[4,4]]"),
+ ParseSFNum("[[[[3,0],[5,3]],[4,4]],[5,5]]"),
+ ParseSFNum("[[[[5,0],[7,4]],[5,5]],[6,6]]"),
+ ParseSFNum("[[[[0,7],4],[[7,8],[6,0]]],[8,1]]"),
+ ParseSFNum("[[[[4,0],[5,4]],[[7,7],[6,0]]],[[8,[7,7]],[[7,9],[5,0]]]]"),
+ ParseSFNum("[[[[6,7],[6,7]],[[7,7],[0,7]]],[[[8,7],[7,7]],[[8,8],[8,0]]]]"),
+ ParseSFNum("[[[[7,0],[7,7]],[[7,7],[7,8]]],[[[7,7],[8,8]],[[7,7],[8,7]]]]"),
+ ParseSFNum("[[[[7,7],[7,8]],[[9,5],[8,7]]],[[[6,8],[0,8]],[[9,9],[9,0]]]]"),
+ ParseSFNum("[[[[6,6],[6,6]],[[6,0],[6,7]]],[[[7,7],[8,9]],[8,[8,1]]]]"),
+ ParseSFNum("[[[[6,6],[7,7]],[[0,7],[7,7]]],[[[5,5],[5,6]],9]]"),
+ ParseSFNum("[[[[7,8],[6,7]],[[6,8],[0,8]]],[[[7,7],[5,0]],[[5,5],[5,6]]]]"),
+ ParseSFNum("[[[[7,7],[7,7]],[[8,7],[8,7]]],[[[7,0],[7,7]],9]]"),
+ ParseSFNum("[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]"),
+ };
+
+ for (int i = 0; i < vals.Count; i++)
+ {
+ Logger.Log($"{(i + 1)}");
+ SFNum curr = vals[i][0];
+ for (int j = 1; j < vals[i].Count; j++)
+ {
+ curr = Add(curr, vals[i][j]);
+ Reduce(curr);
+ }
+
+ if (curr.ToString() != results[i].ToString())
+ {
+ Logger.Log($" expected: {results[i]}");
+ throw new Exception();
+ }
+ Logger.Log("✓");
+ }
+ }
+
+ internal override void Go()
+ {
+ DoTests();
+
+ var lines = Util.ReadAllLines("inputs/18.txt");
+ List nums = new();
+ foreach (var line in lines)
+ {
+ SFNum parsed = ParseSFNum(line);
+ nums.Add(parsed);
+ }
+ Part1(nums);
+ Part2(nums);
+ }
+
+ private static SFNum ParseSFNum(string line)
+ {
+ int phase = 0;
+ SFNum root = new();
+ SFNum? curr = null;
+ string numStr = string.Empty;
+
+ var assignNum = () =>
+ {
+ if (numStr.Length == 0)
+ {
+ return;
+ }
+
+ var val = Convert.ToInt32(numStr);
+ if (phase == 0)
+ {
+ curr!.left = val;
+ }
+ else
+ {
+ curr!.right = val;
+ }
+
+ numStr = string.Empty;
+ };
+
+ foreach (var ch in line)
+ {
+ switch (ch)
+ {
+ case '[':
+ if (curr == null)
+ {
+ curr = root;
+ break;
+ }
+
+ if (phase == 0)
+ {
+ curr!.leftNum = new()
+ {
+ owner = curr,
+ };
+ curr = curr.leftNum;
+ }
+ else
+ {
+ curr!.rightNum = new()
+ {
+ owner = curr,
+ };
+ curr = curr.rightNum;
+ }
+ phase = 0;
+ break;
+
+ case ',':
+ assignNum();
+ phase = 1;
+ break;
+
+ case ']':
+ assignNum();
+ curr = curr!.owner;
+ break;
+
+ default:
+ numStr += ch;
+ break;
+ }
+ }
+
+ return root;
+ }
+
+ private static void Part1(IEnumerable nums)
+ {
+ using var t = new Timer();
+
+ SFNum? curr = null;
+ foreach (var num in nums)
+ {
+ if (curr == null)
+ {
+ curr = num;
+ continue;
+ }
+ curr = Add(curr, num);
+ Reduce(curr);
+ }
+
+ t.Stop();
+ Logger.Log($"<+black>> part1: {curr} -> <+white>{curr!.Magnitude}");
+ }
+
+ private static void Part2(IEnumerable nums)
+ {
+ using var t = new Timer();
+
+ List<(SFNum first, SFNum second, SFNum reduced, long magnitude)> totals = new();
+ for (int i = 0; i < nums.Count() - 1; i++)
+ {
+ for (int j = i + 1; j < nums.Count(); j++)
+ {
+ var first = nums.ElementAt(i);
+ var second = nums.ElementAt(j);
+ var curr = Add(first, second);
+ Reduce(curr);
+ totals.Add((first, second, curr, curr.Magnitude));
+
+ curr = Add(second, first);
+ Reduce(curr);
+ totals.Add((second, first, curr, curr.Magnitude));
+ }
+ }
+
+ var highest = totals.MaxBy(x => x.magnitude);
+
+ t.Stop();
+ Logger.Log($"<+black>> part2: {highest.first} + {highest.second} = {highest.reduced} -> <+white>{highest.magnitude}");
+ }
+
+ private static SFNum Add(SFNum first, SFNum second)
+ {
+ var result = new SFNum()
+ {
+ leftNum = new SFNum(first),
+ rightNum = new SFNum(second),
+ };
+ result.leftNum.owner = result;
+ result.rightNum.owner = result;
+
+ return result;
+ }
+
+ private static void Reduce(SFNum num)
+ {
+ for (int i = 0; i < 10000; i++)
+ {
+ if (CheckExplodes(num))
+ {
+ continue;
+ }
+
+ if (CheckSplits(num))
+ {
+ continue;
+ }
+
+ return;
+ }
+
+ throw new Exception();
+ }
+
+ private static bool CheckExplodes(SFNum num, int depth = 0)
+ {
+#if DEBUG
+ var initial = new SFNum()
+ {
+ left = num.left,
+ right = num.right,
+ leftNum = num.leftNum,
+ rightNum = num.rightNum,
+ owner = num.owner,
+ };
+#endif
+ if (num.leftNum == null && num.rightNum == null && depth >= 4)
+ {
+ Explode(num);
+#if DEBUG
+ if (initial.Root.ToString() == num.Root.ToString())
+ {
+ throw new Exception();
+ }
+#endif
+ return true;
+ }
+
+ if (num.leftNum != null)
+ {
+ if (CheckExplodes(num.leftNum, depth + 1))
+ {
+ return true;
+ }
+ }
+ if (num.rightNum != null)
+ {
+ if (CheckExplodes(num.rightNum, depth + 1))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static void Explode(SFNum num)
+ {
+#if DEBUG
+ num.exploding = true;
+ Logger.Log($"exploding: {num.Root}");
+#endif
+
+ var curr = num;
+ var last = num;
+ while (true)
+ {
+ curr = curr.owner;
+ if (curr == null)
+ {
+ break;
+ }
+
+ if (last == curr.leftNum)
+ {
+ last = curr;
+ continue;
+ }
+
+ if (curr.leftNum != null)
+ {
+ curr = curr.leftNum;
+ while (curr.rightNum != null)
+ {
+ curr = curr.rightNum;
+ }
+ curr.right += num.left;
+ }
+ else
+ {
+ curr.left += num.left;
+ }
+ break;
+ }
+
+ curr = num;
+ last = num;
+ while (true)
+ {
+ curr = curr.owner;
+ if (curr == null)
+ {
+ break;
+ }
+
+ if (last == curr.rightNum)
+ {
+ last = curr;
+ continue;
+ }
+
+ if (curr.rightNum != null)
+ {
+ curr = curr.rightNum;
+ while (curr.leftNum != null)
+ {
+ curr = curr.leftNum;
+ }
+ curr.left += num.right;
+ }
+ else
+ {
+ curr.right += num.right;
+ }
+ break;
+ }
+
+ if (num == num.owner?.leftNum)
+ {
+ num.owner.leftNum = null;
+ num.owner.left = 0;
+ }
+ else if (num == num.owner?.rightNum)
+ {
+ num.owner.rightNum = null;
+ num.owner.right = 0;
+ }
+
+#if DEBUG
+ Logger.Log($" -> {num.Root}");
+#endif
+
+ num.owner = null;
+ }
+
+ private static bool CheckSplits(SFNum num)
+ {
+ if (num.leftNum != null)
+ {
+ if (CheckSplits(num.leftNum))
+ {
+ return true;
+ }
+ }
+
+ if (Split(num))
+ {
+ return true;
+ }
+
+ if (num.rightNum != null)
+ {
+ if (CheckSplits(num.rightNum))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool Split(SFNum num)
+ {
+ if (num.left != null && num.left >= 10)
+ {
+ num.splittingLeft = true;
+#if DEBUG
+ Logger.Log($"splitting: {num.Root}");
+#endif
+ num.leftNum = new()
+ {
+ left = (int)Math.Floor((int)num.left / 2.0),
+ right = (int)Math.Ceiling((int)num.left / 2.0),
+ owner = num,
+ };
+ num.left = null;
+ num.splittingLeft = false;
+#if DEBUG
+ Logger.Log($" -> {num.Root}");
+#endif
+ return true;
+ }
+
+ if (num.right != null && num.right >= 10)
+ {
+ num.splittingRight = true;
+#if DEBUG
+ Logger.Log($"splitting: {num.Root}");
+#endif
+ num.rightNum = new()
+ {
+ left = (int)Math.Floor((int)num.right / 2.0),
+ right = (int)Math.Ceiling((int)num.right / 2.0),
+ owner = num,
+ };
+ num.right = null;
+ num.splittingRight = false;
+#if DEBUG
+ Logger.Log($" -> {num.Root}");
+#endif
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/main.cs b/src/main.cs
index 666915a..3c43b1a 100644
--- a/src/main.cs
+++ b/src/main.cs
@@ -35,7 +35,8 @@ else
"14" => new Day14(),
"15" => new Day15(),
"16" => new Day16(),
- _ => new Day17(),
+ "17"=> new Day17(),
+ _ => new Day18(),
};
day.Go();
}