Skip to content

Commit

Permalink
Add Zipper (#303)
Browse files Browse the repository at this point in the history
* Make 1st 6 tests pass

* implement set_value + fix to_tree

* refactor up

* set_value_after_traversing_up

* Implement set_left + set_right

* implement all tests

* Refactor

* Fill metadata

* increase difficulty

* Assume Node exists in Zipper

* Set up scaffold + update difficulty

* Ret options from tests (replace unwrap() with ?)
  • Loading branch information
0xNeshi authored Nov 4, 2024
1 parent de5e5c0 commit be13943
Show file tree
Hide file tree
Showing 8 changed files with 576 additions and 0 deletions.
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,14 @@
"practices": [],
"prerequisites": [],
"difficulty": 4
},
{
"slug": "zipper",
"name": "Zipper",
"uuid": "61120275-6457-43ce-b40b-7e86a18b7f5b",
"practices": [],
"prerequisites": [],
"difficulty": 8
}
],
"foregone": [
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
20 changes: 20 additions & 0 deletions exercises/practice/zipper/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"authors": [
"0xNeshi"
],
"files": {
"solution": [
"src/lib.cairo"
],
"test": [
"tests/zipper.cairo"
],
"example": [
".meta/example.cairo"
],
"invalidator": [
"Scarb.toml"
]
},
"blurb": "Creating a zipper for a binary tree."
}
168 changes: 168 additions & 0 deletions exercises/practice/zipper/.meta/example.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
type BinaryTree = Option<Box<BinaryTreeNode>>;

#[derive(Drop, Debug, PartialEq, Copy)]
struct BinaryTreeNode {
value: u32,
left: BinaryTree,
right: BinaryTree,
}

pub impl OptionalBinaryTreeNodePartialEq of PartialEq<Option<Box<BinaryTreeNode>>> {
fn eq(lhs: @Option<Box<BinaryTreeNode>>, rhs: @Option<Box<BinaryTreeNode>>) -> bool {
match (lhs, rhs) {
(Option::Some(lhs), Option::Some(rhs)) => (*lhs).unbox() == (*rhs).unbox(),
(Option::None, Option::None) => true,
_ => false
}
}
}

#[generate_trait]
impl NodeImpl of NodeTrait {
fn set_value(self: BinaryTreeNode, value: u32) -> BinaryTreeNode {
BinaryTreeNode { value, ..self }
}

fn set_left(self: BinaryTreeNode, left: BinaryTree) -> BinaryTreeNode {
BinaryTreeNode { left, ..self }
}

fn set_right(self: BinaryTreeNode, right: BinaryTree) -> BinaryTreeNode {
BinaryTreeNode { right, ..self }
}
}

impl NodeIntoBinaryTree of Into<BinaryTreeNode, BinaryTree> {
fn into(self: BinaryTreeNode) -> BinaryTree {
Option::Some(BoxTrait::new(self))
}
}

#[generate_trait]
pub impl BinaryTreeImpl of BinaryTreeTrait {
fn empty() -> BinaryTree {
Option::None
}

fn new(value: u32, left: BinaryTree, right: BinaryTree) -> BinaryTree {
Option::Some(BoxTrait::new(BinaryTreeNode { value, left, right }))
}

fn leaf(value: u32) -> BinaryTree {
Self::new(value, Self::empty(), Self::empty())
}

fn set_value(self: BinaryTree, value: u32) -> BinaryTree {
Self::new(value, *self.left(), *self.right())
}

fn value(self: @BinaryTree) -> Option<u32> {
Option::Some((*self)?.value)
}

fn left(self: @BinaryTree) -> @BinaryTree {
@match self {
Option::None => Option::None,
Option::Some(bst) => bst.left
}
}

fn right(self: @BinaryTree) -> @BinaryTree {
@match self {
Option::None => Option::None,
Option::Some(bst) => bst.right
}
}
}

#[derive(Drop, Copy, Debug, PartialEq)]
enum Path {
Left,
Right
}

#[derive(Drop, Copy, Debug, PartialEq)]
struct Ancestor {
path: Path,
node: BinaryTreeNode
}

#[derive(Drop, Debug, PartialEq)]
struct Zipper {
node: BinaryTreeNode,
ancestors: Span<Ancestor>
}

#[generate_trait]
pub impl ZipperImpl of ZipperTrait {
fn new(tree: BinaryTree, ancestors: Span<Ancestor>) -> Option<Zipper> {
Option::Some(Zipper { node: tree?.unbox(), ancestors })
}

fn from_tree(tree: BinaryTree) -> Option<Zipper> {
Self::new(tree, array![].span())
}

fn to_tree(self: Zipper) -> BinaryTree {
rebuild_tree(self.node.into(), self.ancestors)
}

fn value(self: @Zipper) -> @u32 {
self.node.value
}

fn left(self: Zipper) -> Option<Zipper> {
Self::new(
self.node.left, self.ancestors.append(Ancestor { path: Path::Left, node: self.node })
)
}

fn right(self: Zipper) -> Option<Zipper> {
Self::new(
self.node.right, self.ancestors.append(Ancestor { path: Path::Right, node: self.node })
)
}

fn up(self: Zipper) -> Option<Zipper> {
let mut ancestors = self.ancestors;
let ancestor = *ancestors.pop_back()?;
Self::new(from_ancestor(self.node.into(), ancestor), ancestors)
}

fn set_value(self: Zipper, value: u32) -> Zipper {
Self::new(self.node.set_value(value).into(), self.ancestors).unwrap()
}

fn set_left(self: Zipper, left: BinaryTree) -> Zipper {
Self::new(self.node.set_left(left).into(), self.ancestors).unwrap()
}

fn set_right(self: Zipper, right: BinaryTree) -> Zipper {
Self::new(self.node.set_right(right).into(), self.ancestors).unwrap()
}
}

fn from_ancestor(tree: BinaryTree, ancestor: Ancestor) -> BinaryTree {
match ancestor.path {
Path::Left => ancestor.node.set_left(tree).into(),
Path::Right => ancestor.node.set_right(tree).into(),
}
}

fn rebuild_tree(tree: BinaryTree, ancestors: Span<Ancestor>) -> BinaryTree {
let mut ancestors = ancestors;
if let Option::Some(last) = ancestors.pop_back() {
rebuild_tree(from_ancestor(tree, *last), ancestors)
} else {
tree
}
}

#[generate_trait]
impl SpanAppendImpl of SpanAppendTrait {
fn append<T, +Drop<T>, +Clone<T>>(self: Span<T>, value: T) -> Span<T> {
let mut arr: Array<T> = self.into();
arr.append(value);
arr.span()
}
}
52 changes: 52 additions & 0 deletions exercises/practice/zipper/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[771c652e-0754-4ef0-945c-0675d12ef1f5]
description = "data is retained"

[d7dcbb92-47fc-4d01-b81a-df3353bc09ff]
description = "left, right and value"

[613d8286-b05c-4453-b205-e6f9c5966339]
description = "dead end"

[dda31af7-1c68-4e29-933a-c9d198d94284]
description = "tree from deep focus"

[1e3072a6-f85b-430b-b014-cdb4087e3577]
description = "traversing up from top"

[b8505f6a-aed4-4c2e-824f-a0ed8570d74b]
description = "left, right, and up"

[b9aa8d54-07b7-4bfd-ab6b-7ff7f35930b6]
description = "test ability to descend multiple levels and return"

[47df1a27-b709-496e-b381-63a03b82ea5f]
description = "set_value"

[16a1f1a8-dbed-456d-95ac-1cbb6093e0ab]
description = "set_value after traversing up"

[535a91af-a02e-49cd-8d2c-ecb6e4647174]
description = "set_left with leaf"

[b3f60c4b-a788-4ffd-be5d-1e69aee61de3]
description = "set_right with null"

[e91c221d-7b90-4604-b4ec-46638a673a12]
description = "set_right with subtree"

[c246be85-6648-4e9c-866f-b08cd495149a]
description = "set_value on deep focus"

[47aa85a0-5240-48a4-9f42-e2ac636710ea]
description = "different paths to same zipper"
7 changes: 7 additions & 0 deletions exercises/practice/zipper/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "zipper"
version = "0.1.0"
edition = "2024_07"

[dev-dependencies]
cairo_test = "2.8.2"
Loading

0 comments on commit be13943

Please sign in to comment.