diff --git a/config.json b/config.json index 587a98a1c0..b03f092424 100644 --- a/config.json +++ b/config.json @@ -814,6 +814,15 @@ "Transforming" ] }, + { + "slug": "zipper", + "difficulty": 10, + "topics": [ + "Trees", + "Recursion", + "Searching" + ] + }, { "slug": "alphametics", "difficulty": 10, diff --git a/exercises/pov/Example.cs b/exercises/pov/Example.cs index a8128a9918..c1db1086e9 100644 --- a/exercises/pov/Example.cs +++ b/exercises/pov/Example.cs @@ -17,9 +17,9 @@ public bool Equals(Graph other) => Value.Equals(other.Value) && Children.SequenceEqual(other.Children); } -public class Crumb +public class GraphCrumb { - public Crumb(T value, IEnumerable> left, IEnumerable> right) + public GraphCrumb(T value, IEnumerable> left, IEnumerable> right) { Value = value; Left = left; @@ -31,16 +31,16 @@ public Crumb(T value, IEnumerable> left, IEnumerable> right) public IEnumerable> Right { get; } } -public class Zipper +public class GraphZipper { - public Zipper(Graph focus, IEnumerable> crumbs) + public GraphZipper(Graph focus, IEnumerable> crumbs) { Focus = focus; Crumbs = crumbs; } public Graph Focus { get; } - public IEnumerable> Crumbs { get; } + public IEnumerable> Crumbs { get; } } public static class Pov @@ -54,15 +54,15 @@ public static Graph FromPOV(T value, Graph graph) where T : IComparable public static IEnumerable TracePathBetween(T value1, T value2, Graph graph) where T : IComparable => ZipperToPath(FindNode(value2, GraphToZipper(FromPOV(value1, graph)))); - private static Zipper GraphToZipper(Graph graph) + private static GraphZipper GraphToZipper(Graph graph) { if (graph == null) return null; - return new Zipper(graph, Enumerable.Empty>()); + return new GraphZipper(graph, Enumerable.Empty>()); } - private static IEnumerable ZipperToPath(Zipper zipper) + private static IEnumerable ZipperToPath(GraphZipper zipper) { if (zipper == null) return null; @@ -70,7 +70,7 @@ private static IEnumerable ZipperToPath(Zipper zipper) return zipper.Crumbs.Select(c => c.Value).Reverse().Concat(new[] { zipper.Focus.Value }); } - private static Zipper GoDown(Zipper zipper) + private static GraphZipper GoDown(GraphZipper zipper) { if (zipper == null || !zipper.Focus.Children.Any()) return null; @@ -78,12 +78,12 @@ private static Zipper GoDown(Zipper zipper) var focus = zipper.Focus; var children = focus.Children; - var newCrumb = new Crumb(focus.Value, Enumerable.Empty>(), children.Skip(1)); + var newCrumb = new GraphCrumb(focus.Value, Enumerable.Empty>(), children.Skip(1)); - return new Zipper(children.First(), new[] { newCrumb }.Concat(zipper.Crumbs)); + return new GraphZipper(children.First(), new[] { newCrumb }.Concat(zipper.Crumbs)); } - private static Zipper GoRight(Zipper zipper) + private static GraphZipper GoRight(GraphZipper zipper) { if (zipper == null || !zipper.Crumbs.Any() || !zipper.Crumbs.First().Right.Any()) return null; @@ -91,12 +91,12 @@ private static Zipper GoRight(Zipper zipper) var crumbs = zipper.Crumbs; var firstCrumb = crumbs.First(); - var newCrumb = new Crumb(firstCrumb.Value, firstCrumb.Left.Concat(new[] { zipper.Focus }), firstCrumb.Right.Skip(1)); + var newCrumb = new GraphCrumb(firstCrumb.Value, firstCrumb.Left.Concat(new[] { zipper.Focus }), firstCrumb.Right.Skip(1)); - return new Zipper(firstCrumb.Right.First(), new[] { newCrumb }.Concat(crumbs.Skip(1))); + return new GraphZipper(firstCrumb.Right.First(), new[] { newCrumb }.Concat(crumbs.Skip(1))); } - private static Zipper FindNode(T value, Zipper zipper) + private static GraphZipper FindNode(T value, GraphZipper zipper) where T : IComparable { if (zipper == null || zipper.Focus.Value.CompareTo(value) == 0) @@ -105,7 +105,7 @@ private static Zipper FindNode(T value, Zipper zipper) return FindNode(value, GoDown(zipper)) ?? FindNode(value, GoRight(zipper)); } - private static Graph ChangeParent(Zipper zipper) + private static Graph ChangeParent(GraphZipper zipper) { if (zipper == null) return null; @@ -116,7 +116,7 @@ private static Graph ChangeParent(Zipper zipper) var firstCrumb = zipper.Crumbs.First(); var focus = zipper.Focus; - var newZipper = new Zipper(CreateGraph(firstCrumb.Value, firstCrumb.Left.Concat(firstCrumb.Right)), zipper.Crumbs.Skip(1)); + var newZipper = new GraphZipper(CreateGraph(firstCrumb.Value, firstCrumb.Left.Concat(firstCrumb.Right)), zipper.Crumbs.Skip(1)); var parentGraph = ChangeParent(newZipper); var ys = focus.Children.Concat(new[] { parentGraph }); diff --git a/exercises/zipper/Example.cs b/exercises/zipper/Example.cs new file mode 100644 index 0000000000..23995d9589 --- /dev/null +++ b/exercises/zipper/Example.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +public class BinTree : IEquatable> +{ + public BinTree(T value, BinTree left, BinTree right) + { + Value = value; + Left = left; + Right = right; + } + + public BinTree(BinTree tree) : this(tree.Value, tree.Left, tree.Right) + { + } + + public T Value { get; } + public BinTree Left { get; } + public BinTree Right { get; } + + public bool Equals(BinTree other) + { + if (other == null || !Equals(Value, other.Value)) + return false; + + if (!ReferenceEquals(Left, other.Left) && (!Left?.Equals(other.Left) ?? false)) + return false; + + if (!ReferenceEquals(Right, other.Right) && (!Right?.Equals(other.Right) ?? false)) + return false; + + return true; + } +} + +public abstract class BinTreeCrumb +{ + public BinTreeCrumb(T value, BinTree tree) + { + Value = value; + Tree = tree; + } + + public T Value { get; } + public BinTree Tree { get; } +} + +public class BinTreeLeftCrumb : BinTreeCrumb +{ + public BinTreeLeftCrumb(T value, BinTree tree) : base(value, tree) + { + } +} + +public class BinTreeRightCrumb : BinTreeCrumb +{ + public BinTreeRightCrumb(T value, BinTree tree) : base(value, tree) + { + } +} + +public class Zipper +{ + private readonly T value; + private readonly BinTree left; + private readonly BinTree right; + private readonly List> crumbs; + + public Zipper(T value, BinTree left, BinTree right, List> crumbs) + { + this.value = value; + this.left = left; + this.right = right; + this.crumbs = crumbs; + } + + public T Value => value; + + public Zipper SetValue(T newValue) => new Zipper(newValue, left, right, crumbs); + + public Zipper SetLeft(BinTree binTree) => new Zipper(value, binTree, right, crumbs); + + public Zipper SetRight(BinTree binTree) => new Zipper(value, left, binTree, crumbs); + + public Zipper Left() + { + if (left == null) + return null; + + var newCrumbs = new[] { new BinTreeLeftCrumb(value, right) }.Concat(crumbs).ToList(); + return new Zipper(left.Value, left.Left, left.Right, newCrumbs); + } + + public Zipper Right() + { + if (right == null) + return null; + + var newCrumbs = new[] { new BinTreeRightCrumb(value, left) }.Concat(crumbs).ToList(); + return new Zipper(right.Value, right.Left, right.Right, newCrumbs); + } + + public Zipper Up() + { + if (crumbs.Count == 0) + return null; + + var firstCrumb = crumbs[0]; + var remainingCrumbs = crumbs.Skip(1).ToList(); + + if (firstCrumb is BinTreeLeftCrumb) + return new Zipper(firstCrumb.Value, new BinTree(value, left, right), firstCrumb.Tree, remainingCrumbs); + + if (firstCrumb is BinTreeRightCrumb) + return new Zipper(firstCrumb.Value, firstCrumb.Tree, new BinTree(value, left, right), remainingCrumbs); + + return null; + } + + public BinTree ToTree() + { + var tree = new BinTree(value, left, right); + + foreach (var crumb in crumbs) + { + if (crumb is BinTreeLeftCrumb) + tree = new BinTree(crumb.Value, new BinTree(tree), crumb.Tree); + if (crumb is BinTreeRightCrumb) + tree = new BinTree(crumb.Value, crumb.Tree, new BinTree(tree)); + } + + return tree; + } + + public static Zipper FromTree(BinTree tree) => new Zipper(tree.Value, tree.Left, tree.Right, new List>()); +} \ No newline at end of file diff --git a/exercises/zipper/ZipperTest.cs b/exercises/zipper/ZipperTest.cs new file mode 100644 index 0000000000..feeed02c84 --- /dev/null +++ b/exercises/zipper/ZipperTest.cs @@ -0,0 +1,83 @@ +using NUnit.Framework; + +public class ZipperTest +{ + private static BinTree bt(int v, BinTree l, BinTree r) => new BinTree(v, l, r); + private static BinTree leaf(int v) => bt(v, null, null); + + private static readonly BinTree empty = null; + private static readonly BinTree t1 = new BinTree(1, bt(2, empty, leaf(3)), leaf(4)); + private static readonly BinTree t2 = new BinTree(1, bt(5, empty, leaf(3)), leaf(4)); + private static readonly BinTree t3 = new BinTree(1, bt(2, leaf(5), leaf(3)), leaf(4)); + private static readonly BinTree t4 = new BinTree(1, leaf(2), leaf(4)); + + [Test] + public void Data_is_retained() + { + var zipper = Zipper.FromTree(t1); + var tree = zipper.ToTree(); + Assert.That(tree, Is.EqualTo(t1)); + } + + [Ignore("Remove to run test")] + [Test] + public void Left_right_and_value() + { + var zipper = Zipper.FromTree(t1); + Assert.That(zipper.Left().Right().Value, Is.EqualTo(3)); + } + + [Ignore("Remove to run test")] + [Test] + public void Dead_end() + { + var zipper = Zipper.FromTree(t1); + Assert.That(zipper.Left().Left(), Is.Null); + } + + [Ignore("Remove to run test")] + [Test] + public void Tree_from_deep_focus() + { + var zipper = Zipper.FromTree(t1); + Assert.That(zipper.Left().Right().ToTree(), Is.EqualTo(t1)); + } + + [Ignore("Remove to run test")] + [Test] + public void Set_value() + { + var zipper = Zipper.FromTree(t1); + var updatedZipper = zipper.Left().SetValue(5); + var tree = updatedZipper.ToTree(); + Assert.That(tree, Is.EqualTo(t2)); + } + + [Ignore("Remove to run test")] + [Test] + public void Set_left_with_value() + { + var zipper = Zipper.FromTree(t1); + var updatedZipper = zipper.Left().SetLeft(new BinTree(5, null, null)); + var tree = updatedZipper.ToTree(); + Assert.That(tree, Is.EqualTo(t3)); + } + + [Ignore("Remove to run test")] + [Test] + public void Set_right_to_null() + { + var zipper = Zipper.FromTree(t1); + var updatedZipper = zipper.Left().SetRight(null); + var tree = updatedZipper.ToTree(); + Assert.That(tree, Is.EqualTo(t4)); + } + + [Ignore("Remove to run test")] + [Test] + public void Different_paths_to_same_zipper() + { + var zipper = Zipper.FromTree(t1); + Assert.That(zipper.Left().Up().Right().ToTree(), Is.EqualTo(zipper.Right().ToTree())); + } +}