Skip to content
This repository has been archived by the owner on May 30, 2023. It is now read-only.

Commit

Permalink
Add zipper exercise (#159)
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikSchierboom authored May 6, 2023
1 parent 63dca45 commit abba7c6
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 0 deletions.
11 changes: 11 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,17 @@
],
"difficulty": 2
},
{
"slug": "zipper",
"name": "Zipper",
"uuid": "26c4c4c6-154f-42fd-b624-158132c028c3",
"practices": [],
"prerequisites": [
"strings",
"vectors"
],
"difficulty": 9
},
{
"slug": "yacht",
"name": "Yacht",
Expand Down
27 changes: 27 additions & 0 deletions exercises/practice/zipper/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Instructions

Creating a zipper for a binary tree.

[Zippers][zipper] are 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).

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)

[zipper]: https://en.wikipedia.org/wiki/Zipper_%28data_structure%29
17 changes: 17 additions & 0 deletions exercises/practice/zipper/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"authors": [
"ErikSchierboom"
],
"files": {
"solution": [
"src/zipper.cljs"
],
"test": [
"test/zipper_test.cljs"
],
"example": [
".meta/src/example.cljs"
]
},
"blurb": "Creating a zipper for a binary tree."
}
57 changes: 57 additions & 0 deletions exercises/practice/zipper/.meta/src/example.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
(ns zipper)

(defn from-trail [tree last]
(if (= (nth last 0) "left")
{:value (nth last 1), :left tree, :right (nth last 2)}
{:value (nth last 1), :left (nth last 2), :right tree}))

(defn from-tree [tree]
{:tree tree :trail []})

(defn value [z]
(:value (:tree z)))

(defn zipper [tree trail]
{:tree tree :trail trail})

(defn left [z]
(when (:left (:tree z))
(zipper (:left (:tree z))
(conj [["left" (:value (:tree z)) (:right (:tree z))]]
(:trail z)))))
(defn right [z]
(when (:right (:tree z))
(zipper (:right (:tree z))
(conj [["right" (:value (:tree z)) (:left (:tree z))]]
(:trail z)))))

(defn rebuild-tree [tree trail]
(if (= 0 (count trail))
tree
(recur (from-trail tree (first trail)) (fnext trail))))

(defn to-tree [z]
(rebuild-tree (:tree z) (:trail z)))

(defn up [z]
(when-not (zero? (count (:trail z)))
(zipper (from-trail (:tree z) (first (:trail z)))
(fnext (:trail z)))))

(defn set-value [z value]
(zipper {:value value,
:left (:left (:tree z)),
:right (:right (:tree z))}
(:trail z)))

(defn set-left [z left]
(zipper {:value (:value (:tree z)),
:left left,
:right (:right (:tree z))}
(:trail z)))

(defn set-right [z right]
(zipper {:value (:value (:tree z)),
:left (:left (:tree z)),
:right right}
(:trail z)))
10 changes: 10 additions & 0 deletions exercises/practice/zipper/deps.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{:deps
{org.clojure/clojure {:mvn/version "1.10.1"}
org.clojure/clojurescript {:mvn/version "1.10.773"}}

:aliases
{:test
{:extra-paths ["test"]
:extra-deps
{olical/cljs-test-runner {:mvn/version "3.8.0"}}
:main-opts ["-m" "cljs-test-runner.main"]}}}
28 changes: 28 additions & 0 deletions exercises/practice/zipper/src/zipper.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
(ns zipper)

(defn from-tree []
)

(defn value []
)

(defn left []
)

(defn right []
)

(defn to-tree []
)

(defn up []
)

(defn set-value []
)

(defn set-left []
)

(defn set-right []
)
192 changes: 192 additions & 0 deletions exercises/practice/zipper/test/zipper_test.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
(ns zipper-test
(:require [clojure.test :refer [deftest testing is run-tests]]
zipper))

(def t1 {:value 1, :left {:value 2, :left nil, :right {:value 3, :left nil, :right nil}}, :right {:value 4, :left nil, :right nil}})

(deftest expected-value-test
(let [tree {:value 1
:left {:value 2
:left nil
:right {:value 3
:left nil
:right nil}}
:right {:value 4
:left nil
:right nil}}]
(testing "data is retained"
(is (= tree (-> tree zipper/from-tree zipper/to-tree))))
(testing "left, right and value"
(is (= 3 (-> t1
zipper/from-tree
zipper/left
zipper/right
zipper/value
))))
(testing "dead end"
(is (nil? (-> tree
zipper/from-tree
zipper/left
zipper/left))))
(testing "tree from deep focus"
(is (= tree (-> tree
zipper/from-tree
zipper/left
zipper/right
zipper/to-tree))))
(testing "traversing up from top"
(is (= nil
(-> tree
zipper/from-tree
zipper/up))))
(testing "left, right, and up"
(is (= 3
(-> tree
zipper/from-tree
zipper/left
zipper/up
zipper/right
zipper/up
zipper/left
zipper/right
zipper/value))))
(testing "test ability to descend multiple levels and return"
(is (= 1
(-> tree
zipper/from-tree
zipper/left
zipper/right
zipper/up
zipper/up
zipper/value))))
(testing "set_value"
(is (= {:value 1
:left {:value 5
:left nil
:right {:value 3
:left nil
:right nil}}
:right {:value 4
:left nil
:right nil}}
(-> tree
zipper/from-tree
zipper/left
(zipper/set-value 5)
zipper/to-tree))))
(testing "set_value after traversing up"
(is (= {:value 1
:left {:value 5
:left nil
:right {:value 3
:left nil
:right nil}}
:right {:value 4
:left nil
:right nil}}
(-> tree
zipper/from-tree
zipper/left
zipper/right
zipper/up
(zipper/set-value 5)
zipper/to-tree))))
(testing "set_left with leaf"
(is (= {:value 1
:left {:value 2
:left {:value 5
:left nil
:right nil}
:right {:value 3
:left nil
:right nil}}
:right {:value 4
:left nil
:right nil}}
(-> tree
zipper/from-tree
zipper/left
(zipper/set-left {:value 5
:left nil
:right nil})
zipper/to-tree))))
(testing "set_right with null"
(is (= {:value 1
:left {:value 2
:left nil
:right nil}
:right {:value 4
:left nil
:right nil}}
(-> tree
zipper/from-tree
zipper/left
(zipper/set-right nil)
zipper/to-tree))))
(testing "set_right with subtree"
(is (= {:value 1
:left {:value 2
:left nil
:right {:value 3
:left nil
:right nil}}
:right {:value 6
:left {:value 7
:left nil
:right nil}
:right {:value 8
:left nil
:right nil}}}
(-> tree
zipper/from-tree
(zipper/set-right {:value 6
:left {:value 7
:left nil
:right nil}
:right {:value 8
:left nil
:right nil}})
zipper/to-tree))))
(testing "set_value on deep focus"
(is (= {:value 1
:left {:value 2
:left nil
:right {:value 5
:left nil
:right nil}}
:right {:value 4
:left nil
:right nil}}
(-> tree
zipper/from-tree
zipper/left
zipper/right
(zipper/set-value 5)
zipper/to-tree))))))

(deftest sameResultFromOperations-test
(testing "different paths to same zipper"
(is (= (-> {:value 1
:left {:value 2
:left nil
:right {:value 3
:left nil
:right nil}}
:right {:value 4
:left nil
:right nil}}
zipper/from-tree
zipper/right)
(-> {:value 1
:left {:value 2
:left nil
:right {:value 3
:left nil
:right nil}}
:right {:value 4
:left nil
:right nil}}
zipper/from-tree
zipper/left
zipper/up
zipper/right)))))

0 comments on commit abba7c6

Please sign in to comment.