javascript - zipper
This commit is contained in:
		
							
								
								
									
										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);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
		Reference in New Issue
	
	Block a user