javascript - zipper
This commit is contained in:
parent
b800f5729c
commit
9589d3409f
60
javascript/zipper/README.md
Normal file
60
javascript/zipper/README.md
Normal 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
7080
javascript/zipper/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
80
javascript/zipper/package.json
Normal file
80
javascript/zipper/package.json
Normal 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": {}
|
||||
}
|
78
javascript/zipper/zipper.js
Normal file
78
javascript/zipper/zipper.js
Normal 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;
|
77
javascript/zipper/zipper.spec.js
Normal file
77
javascript/zipper/zipper.spec.js
Normal 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);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user