javascript - zipper

This commit is contained in:
James Walker 2018-09-25 09:29:01 -04:00
parent b800f5729c
commit 9589d3409f
Signed by: walkah
GPG Key ID: 3C127179D6086E93
5 changed files with 7375 additions and 0 deletions

View File

@ -0,0 +1,60 @@
# Zipper
Creating a zipper for a binary tree.
[Zippers](https://en.wikipedia.org/wiki/Zipper_%28data_structure%29) 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)
## Setup
Go through the setup instructions for Javascript to
install the necessary dependencies:
[https://exercism.io/tracks/javascript/installation](https://exercism.io/tracks/javascript/installation)
## Requirements
Install assignment dependencies:
```bash
$ npm install
```
## Making the test suite pass
Execute the tests with:
```bash
$ npm test
```
In the test suites all tests but the first have been skipped.
Once you get a test passing, you can enable the next one by
changing `xtest` to `test`.
## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

7080
javascript/zipper/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
{
"name": "exercism-javascript",
"version": "0.0.0",
"description": "Exercism exercises in Javascript.",
"author": "Katrina Owen",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/exercism/javascript"
},
"devDependencies": {
"babel-jest": "^21.2.0",
"babel-plugin-transform-builtin-extend": "^1.1.2",
"babel-preset-env": "^1.4.0",
"eslint": "^3.19.0",
"eslint-config-airbnb": "^15.0.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^5.0.1",
"eslint-plugin-react": "^7.0.1",
"jest": "^21.2.1"
},
"jest": {
"modulePathIgnorePatterns": [
"package.json"
]
},
"babel": {
"presets": [
[
"env",
{
"targets": [
{
"node": "current"
}
]
}
]
],
"plugins": [
[
"babel-plugin-transform-builtin-extend",
{
"globals": [
"Error"
]
}
],
[
"transform-regenerator"
]
]
},
"scripts": {
"test": "jest --no-cache ./*",
"watch": "jest --no-cache --watch ./*",
"lint": "eslint .",
"lint-test": "eslint . && jest --no-cache ./* "
},
"eslintConfig": {
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"env": {
"es6": true,
"node": true,
"jest": true
},
"extends": "airbnb",
"rules": {
"import/no-unresolved": "off",
"import/extensions": "off"
}
},
"licenses": [
"MIT"
],
"dependencies": {}
}

View File

@ -0,0 +1,78 @@
class Zipper {
constructor(tree) {
// quick hack for deep object clone.
this.tree = JSON.parse(JSON.stringify(tree));
this.focus = [];
}
static fromTree(tree) {
return new Zipper(tree);
}
toTree() {
return this.tree;
}
left() {
const subtree = this.getSubtree();
if (subtree.left) {
this.focus.push('left');
return this;
}
return null;
}
right() {
const subtree = this.getSubtree();
if (subtree.right) {
this.focus.push('right');
return this;
}
return null;
}
value() {
const subtree = this.getSubtree();
return subtree.value;
}
up() {
if (this.focus.length > 0) {
this.focus.pop();
return this;
}
return null;
}
setValue(value) {
const subtree = this.getSubtree();
subtree.value = value;
return this;
}
setLeft(left) {
const subtree = this.getSubtree();
subtree.left = left;
return this;
}
setRight(right) {
const subtree = this.getSubtree();
subtree.right = right;
return this;
}
getSubtree(subtree = null, step = 0) {
if (step == 0) {
subtree = this.tree;
}
if (step == this.focus.length) {
return subtree;
}
return this.getSubtree(subtree[this.focus[step]], step + 1);
}
}
export default Zipper;

View File

@ -0,0 +1,77 @@
import Zipper from './zipper';
// Tests adapted from `problem-specifications/zipper/canonical-data.json` @ v1.0.0
function bt(value, left, right) {
return {
value,
left,
right,
};
}
function leaf(value) {
return bt(value, null, null);
}
describe('Zipper', () => {
const t1 = bt(1, bt(2, null, leaf(3)), leaf(4));
const t2 = bt(1, bt(5, null, leaf(3)), leaf(4));
const t3 = bt(1, bt(2, leaf(5), leaf(3)), leaf(4));
const t4 = bt(1, leaf(2), leaf(4));
const t5 = bt(1, bt(2, null, leaf(3)), bt(6, leaf(7), leaf(8)));
const t6 = bt(1, bt(2, null, leaf(5)), leaf(4));
let zipper;
beforeEach(() => {
zipper = Zipper.fromTree(t1);
});
test('data is retained', () => {
expect(zipper.toTree()).toEqual(t1);
});
test('left, right and value', () => {
expect(zipper.left().right().value()).toEqual(3);
});
test('dead end', () => {
expect(zipper.left().left()).toBe(null);
});
test('tree from deep focus', () => {
expect(zipper.left().right().toTree()).toEqual(t1);
});
test('traversing up from top', () => {
expect(zipper.up()).toEqual(null);
});
test('left, right and up', () => {
expect(zipper.left().up().right().up().left().right().value()).toEqual(3);
});
test('setValue', () => {
expect(zipper.left().setValue(5).toTree()).toEqual(t2);
});
test('setValue after traversing up', () => {
expect(zipper.left().right().up().setValue(5).toTree()).toEqual(t2);
});
test('setLeft with leaf', () => {
expect(zipper.left().setLeft(leaf(5)).toTree()).toEqual(t3);
});
test('setRight with null', () => {
expect(zipper.left().setRight(null).toTree()).toEqual(t4);
});
test('setRight with subtree', () => {
expect(zipper.setRight(bt(6, leaf(7), leaf(8))).toTree()).toEqual(t5);
});
test('setValue on deep focus', () => {
expect(zipper.left().right().setValue(5).toTree()).toEqual(t6);
});
});