From 37386e98f28508065006bfadf5e8248aa3b43e13 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 9 Oct 2017 12:57:38 -0500 Subject: [PATCH 1/7] zipper: add README --- exercises/zipper/README.md | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 exercises/zipper/README.md diff --git a/exercises/zipper/README.md b/exercises/zipper/README.md new file mode 100644 index 0000000000..f7f4a858c4 --- /dev/null +++ b/exercises/zipper/README.md @@ -0,0 +1,43 @@ +# Zipper + +Creating a zipper for a binary tree. + +[Zippers](https://en.wikipedia.org/wiki/Zipper_%28data_structure%29) are +a way purely functional of navigating within a data structure and +manipulating it. They essentially contain a data structure and a +pointer into that data structure (called the focus). + +For example given a rose tree (where each node contains a value and a +list of child nodes) a zipper might support these operations: + +- `from_tree` (get a zipper out of a rose tree, the focus is on the root node) +- `to_tree` (get the rose tree out of the zipper) +- `value` (get the value of the focus node) +- `prev` (move the focus to the previous child of the same parent, + returns a new zipper) +- `next` (move the focus to the next child of the same parent, returns a + new zipper) +- `up` (move the focus to the parent, returns a new zipper) +- `set_value` (set the value of the focus node, returns a new zipper) +- `insert_before` (insert a new subtree before the focus node, it + becomes the `prev` of the focus node, returns a new zipper) +- `insert_after` (insert a new subtree after the focus node, it becomes + the `next` of the focus node, returns a new zipper) +- `delete` (removes the focus node and all subtrees, focus moves to the + `next` node if possible otherwise to the `prev` node if possible, + otherwise to the parent node, returns a new zipper) + +### Submitting Exercises + +Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/` directory. + +For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit /python/bob/bob.py`. + + +For more detailed information about running tests, code style and linting, +please see the [help page](http://exercism.io/languages/python). + + +## Submitting Incomplete Solutions + +It's possible to submit an incomplete solution so you can see how others have completed the exercise. From deb6fea29ff59319d0c0e382d24affed26fbfcb8 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 9 Oct 2017 13:06:17 -0500 Subject: [PATCH 2/7] zipper: add template solution --- exercises/zipper/zipper.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 exercises/zipper/zipper.py diff --git a/exercises/zipper/zipper.py b/exercises/zipper/zipper.py new file mode 100644 index 0000000000..05d4cffaf5 --- /dev/null +++ b/exercises/zipper/zipper.py @@ -0,0 +1,22 @@ +class Zipper(object): + @staticmethod + def from_tree(tree): + pass + + def left(self): + pass + + def set_left(self): + pass + + def right(self): + pass + + def set_right(self): + pass + + def up(self): + pass + + def to_tree(self): + pass From ade6c1f71a5c74483c020d18128b953ebc9fef15 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 9 Oct 2017 13:50:43 -0500 Subject: [PATCH 3/7] zipper: update config.json --- config.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/config.json b/config.json index d0d26be0e3..7eb34fa1c5 100644 --- a/config.json +++ b/config.json @@ -844,6 +844,18 @@ "conditionals" ] }, + { + "uuid": "4f5f890d-0db4-5480-f79a-21057c37871b15133dc", + "slug": "zipper", + "core": false, + "unlocked_by": null, + "difficulty": 8, + "topics": [ + "recursion", + "searching", + "trees" + ] + }, { "uuid": "e7351e8e-d3ff-4621-b818-cd55cf05bffd", "slug": "accumulate", From b10b51892cc607e7d8d1b44bfe89f2dfb924bcc0 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 9 Oct 2017 16:01:59 -0500 Subject: [PATCH 4/7] zipper: add test cases --- exercises/zipper/README.md | 2 +- exercises/zipper/zipper.py | 10 +++++ exercises/zipper/zipper_test.py | 80 +++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 exercises/zipper/zipper_test.py diff --git a/exercises/zipper/README.md b/exercises/zipper/README.md index f7f4a858c4..d1241c229c 100644 --- a/exercises/zipper/README.md +++ b/exercises/zipper/README.md @@ -3,7 +3,7 @@ Creating a zipper for a binary tree. [Zippers](https://en.wikipedia.org/wiki/Zipper_%28data_structure%29) are -a way purely functional of navigating within a data structure and +a purely functional way of navigating within a data structure and manipulating it. They essentially contain a data structure and a pointer into that data structure (called the focus). diff --git a/exercises/zipper/zipper.py b/exercises/zipper/zipper.py index 05d4cffaf5..caa8339dbe 100644 --- a/exercises/zipper/zipper.py +++ b/exercises/zipper/zipper.py @@ -1,6 +1,16 @@ class Zipper(object): @staticmethod def from_tree(tree): + return Zipper(tree) + + def __init__(self, root, parent=None): + self.root = dict(root) + self.parent = None if parent is None else dict(parent) + + def value(self): + return self.root['value'] + + def set_value(self): pass def left(self): diff --git a/exercises/zipper/zipper_test.py b/exercises/zipper/zipper_test.py new file mode 100644 index 0000000000..05e7471bf1 --- /dev/null +++ b/exercises/zipper/zipper_test.py @@ -0,0 +1,80 @@ +import unittest + +from zipper import Zipper + + +def bt(value, left, right): + return { + 'value': value, + 'left': left, + 'right': right + } + + +def leaf(value): + return bt(value, None, None) + + +EMPTY_TREE = None + + +def create_trees(): + t1 = bt(1, bt(2, EMPTY_TREE, leaf(3)), leaf(4)) + t2 = bt(1, bt(5, EMPTY_TREE, leaf(3)), leaf(4)) + t3 = bt(1, bt(2, leaf(5), leaf(3)), leaf(4)) + t4 = bt(1, leaf(2), leaf(4)) + return (t1, t2, t3, t4) + + +class ZipperTest(unittest.TestCase): + def test_data_is_retained(self): + t1, _, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + tree = zipper.to_tree() + self.assertEqual(tree, t1) + + def test_left_and_right_value(self): + t1, _, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + self.assertEqual(zipper.left().right().value(), 3) + + def test_dead_end(self): + t1, _, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + self.assertIsNone(zipper.left().left()) + + def test_tree_from_deep_focus(self): + t1, _, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + self.assertEqual(zipper.left().right().to_tree(), t1) + + def test_set_value(self): + t1, t2, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + updatedZipper = zipper.left().set_value(5) + tree = updatedZipper.to_tree() + self.assertEqual(tree, t2) + + def test_set_left_with_value(self): + t1, _, t3, _ = create_trees() + zipper = Zipper.from_tree(t1) + updatedZipper = zipper.left().set_left(leaf(5)) + tree = updatedZipper.to_tree() + self.assertEqual(tree, t3) + + def test_set_right_to_none(self): + t1, _, _, t4 = create_trees() + zipper = Zipper.from_tree(t1) + updatedZipper = zipper.left().set_right(None) + tree = updatedZipper.to_tree() + self.assertEqual(tree, t4) + + def test_different_paths_to_same_zipper(self): + t1, _, _, _ = create_trees() + zipper = Zipper.from_tree(t1) + self.assertEqual(zipper.left().up().right().to_tree(), + zipper.right().to_tree()) + + +if __name__ == '__main__': + unittest.main() From 7aad5f12e33214e5d29f700c38f238d1c1515b14 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 9 Oct 2017 16:02:08 -0500 Subject: [PATCH 5/7] zipper: add example solution --- exercises/zipper/example.py | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 exercises/zipper/example.py diff --git a/exercises/zipper/example.py b/exercises/zipper/example.py new file mode 100644 index 0000000000..a9003e9e21 --- /dev/null +++ b/exercises/zipper/example.py @@ -0,0 +1,41 @@ +class Zipper(object): + @staticmethod + def from_tree(tree): + return Zipper(dict(tree), []) + + def __init__(self, tree, ancestors): + self.tree = tree + self.ancestors = ancestors + + def value(self): + return self.tree['value'] + + def set_value(self, value): + self.tree['value'] = value + return self + + def left(self): + if self.tree['left'] is None: + return None + return Zipper(self.tree['left'], self.ancestors + [self.tree]) + + def set_left(self, tree): + self.tree['left'] = tree + return self + + def right(self): + if self.tree['right'] is None: + return None + return Zipper(self.tree['right'], self.ancestors + [self.tree]) + + def set_right(self, tree): + self.tree['right'] = tree + return self + + def up(self): + return Zipper(self.ancestors[-1], self.ancestors[:-1]) + + def to_tree(self): + if any(self.ancestors): + return self.ancestors[0] + return self.tree From 98d40664e8ade89ba93723aead85982986e44cb2 Mon Sep 17 00:00:00 2001 From: cmccandless Date: Sat, 21 Oct 2017 09:54:15 -0500 Subject: [PATCH 6/7] zipper: remove example code from solution template --- exercises/zipper/zipper.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/exercises/zipper/zipper.py b/exercises/zipper/zipper.py index caa8339dbe..14903a8a09 100644 --- a/exercises/zipper/zipper.py +++ b/exercises/zipper/zipper.py @@ -1,14 +1,10 @@ class Zipper(object): @staticmethod def from_tree(tree): - return Zipper(tree) - - def __init__(self, root, parent=None): - self.root = dict(root) - self.parent = None if parent is None else dict(parent) + pass def value(self): - return self.root['value'] + pass def set_value(self): pass From f23aee0ef4cdc1e6de1a5c4f862ee946469c3c0d Mon Sep 17 00:00:00 2001 From: cmccandless Date: Sat, 21 Oct 2017 10:22:58 -0500 Subject: [PATCH 7/7] zipper: add canonical data version --- exercises/zipper/zipper_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exercises/zipper/zipper_test.py b/exercises/zipper/zipper_test.py index 05e7471bf1..0e2a0a7ef7 100644 --- a/exercises/zipper/zipper_test.py +++ b/exercises/zipper/zipper_test.py @@ -2,6 +2,8 @@ from zipper import Zipper +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0 + def bt(value, left, right): return {