├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── API.md ├── LICENSE ├── README.md ├── benchmark ├── container.js ├── hash.js └── keys.js ├── gulpfile.js ├── package.json ├── superhash.js └── test ├── config.js └── superhash-test.js /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: HgC0LO2Xgb51c2DmjB2BUNY64kNn3UfCD -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | iteration.md -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.11" 5 | script: 6 | - npm run test-travis 7 | after_script: 8 | - npm install -g coveralls@2 9 | - cat ./coverage/lcov.info | coveralls 10 | - npm install -g codeclimate-test-reporter 11 | - cat ./coverage/lcov.info | codeclimate -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | 2 | * [`SuperHash(entries)`](#superhashentries) 3 | * [`set(keys, value)`](#setkeys-value) 4 | * [`get(keys)`](#getkeys) 5 | * [`has(keys)`](#haskeys) 6 | * [`keys`](#keys) 7 | * [`entries`](#entries) 8 | * [`values`](#values) 9 | * [`forEach(cb, context)`](#foreachcb-context) 10 | * [`delete(keys)`](#deletekeys) 11 | * [`clear`](#clear) 12 | 13 | ### `SuperHash(entries)` 14 | * **entries** (`Array`) two dimensional array with entries to prefill the map. Keys must be an array (even if just one) 15 | 16 | Creates a new SuperHash 17 | 18 | Example `entries` Array 19 | 20 | ``` 21 | [[1,2,3],'foo'], [[{foo: 'bar', {blip:'blop'}],'bar']] 22 | ``` 23 | ### `set(keys, value)` 24 | * **keys** (`...*`) Used to generate hash 25 | * **value** (`*`) to be associated with the key 26 | 27 | Creates a hash from the keys if it doesn't exist and sets the last argument passed in as the value 28 | ### `get(keys)` 29 | * **keys** (`...*`) Used to generate a hash for lookup 30 | 31 | Returns the value associated with the hash generated from the keys 32 | ### `has(keys)` 33 | * **keys** (`...*`) - Used to generate a hash for lookup 34 | 35 | Tells whether or not value associated with the hash generated from the keys is in the map 36 | ### `keys` 37 | 38 | Returns all keys from the hash map 39 | ### `entries` 40 | 41 | Returns all entries (key/value pairs) from the hash map 42 | ### `values` 43 | 44 | Returns all values from the hash map 45 | ### `forEach(cb, context)` 46 | * **cb** (`Function`) callback function called with `(key, value)` for each entry in the map 47 | * **context** (`*`) `this` context for the callback 48 | 49 | Loops through each value in the hashmap passing it as the first argument in callack 50 | 51 | ```js 52 | hashMap.forEach(function(value){ 53 | 54 | }); 55 | ``` 56 | ### `delete(keys)` 57 | * **keys** (`...*`) - Used to generate a hash for lookup 58 | 59 | Removes the hash generated by the keys and the associated value 60 | ### `clear` 61 | 62 | Deletes all keys and values from hash map -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014- Esco Obong (http://hbyte.com/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SuperHash 2 | ======== 3 | 4 | [![Build Status](https://travis-ci.org/esco/superhash-node.svg?branch=master)](https://travis-ci.org/esco/superhash) [![Coverage Status](https://coveralls.io/repos/esco/superhash/badge.png)](https://coveralls.io/r/esco/superhash) [![Code Climate](https://codeclimate.com/github/esco/superhash/badges/gpa.svg)](https://codeclimate.com/github/esco/superhash) 5 | 6 | ![superhash](http://i.imgur.com/JcPuyeW.png) 7 | 8 | HashMap that supports using one or more keys of any type* 9 | 10 | >Hash keys are generated using [esco/multikey-hash](https://github.com/esco/multikey-hash) 11 | 12 | ## Installation 13 | 14 | ``` 15 | $ npm install superhash 16 | ``` 17 | 18 | ## Examples 19 | 20 | ```js 21 | var SuperHash = require('superhash'); 22 | var hashMap = new SuperHash(); 23 | var data = 'value'; 24 | ``` 25 | 26 | Single key: 27 | ```js 28 | hashMap.set(5, data); 29 | hashMap.get("5"); // returns undefined 30 | hashMap.get(5); // returns 'value' 31 | hashMap.delete(5); // returns true 32 | ``` 33 | 34 | Multiple keys: 35 | ```js 36 | var obj = { name: 'foo' }; 37 | hashMap.set(1, obj, true, data); 38 | hashMap.get(1, obj, "true"); // returns undefined 39 | hashMap.get(1, obj, true); // returns 'value' 40 | hashMap.delete(1, obj, true); // returns true 41 | ``` 42 | 43 | ## API 44 | [See API.md][api-url] 45 | 46 | ## FAQ 47 | 48 | ### What types of keys can be used? 49 | 50 | Any primitive or mutable object can be used as a key. 51 | 52 | ### Who named this module? 53 | [@nik](http://github.com/nik) 54 | 55 | ## LICENSE 56 | [MIT][license-url] 57 | 58 | [license-url]: LICENSE 59 | [api-url]: API.md 60 | -------------------------------------------------------------------------------- /benchmark/container.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tests: { 3 | 'arr': function(){ 4 | var container = [0,1]; 5 | }, 6 | 7 | 'obj': function() { 8 | var container = {keys: 0, values: 1}; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /benchmark/hash.js: -------------------------------------------------------------------------------- 1 | var SuperHash = require('../superhash'); 2 | var HashMap = require('hashmap').HashMap; 3 | var es6Collections = require('es6-collections'); 4 | 5 | var key = [[1,2,{ name: 'foo' },'ref'],1,2]; 6 | var superhash = new SuperHash; 7 | var hashmap = new HashMap; 8 | var map = new Map; 9 | 10 | for (var i = 0; i < 1000; i++) { 11 | superhash.set(i,i); 12 | hashmap.set(i,i); 13 | map.set(i,i); 14 | } 15 | 16 | module.exports = { 17 | tests: { 18 | 'superhash': function(){ 19 | superhash.set(key, 'value'); 20 | }, 21 | 22 | 'hashmap': function() { 23 | hashmap.set(key, 'value'); 24 | }, 25 | 26 | 'map': function() { 27 | map.set(key, 'value'); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /benchmark/keys.js: -------------------------------------------------------------------------------- 1 | var object = { a: '1', b: '2', c: '3', d: '4', e: '5' }; 2 | 3 | 4 | module.exports = { 5 | tests: { 6 | 'keys': function(){ 7 | Object.keys(object).forEach(function(){ 8 | 9 | }); 10 | }, 11 | 12 | 'for..in': function() { 13 | for (var key in object) { 14 | if (object.hasOwnProperty(key)) { 15 | } 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var jshint = require('gulp-jshint'); 3 | var istanbul = require('gulp-istanbul'); 4 | var mocha = require('gulp-mocha'); 5 | var mdox = require('gulp-mdox'); 6 | var benchmark = require('gulp-bench'); 7 | 8 | var paths = { 9 | src: ['*.js'], 10 | tests: ['test/*.js'], 11 | benchmarks: ['benchmark/hash.js'] 12 | }; 13 | 14 | gulp.task('lint', function(){ 15 | return gulp.src(paths.src) 16 | .pipe(jshint()) 17 | .pipe(jshint.reporter('jshint-stylish')); 18 | }); 19 | 20 | gulp.task('docs', function() { 21 | return gulp.src('superhash.js') 22 | .pipe(mdox({ 23 | name: "API.md", 24 | github: true 25 | })) 26 | .pipe(gulp.dest("./")); 27 | }); 28 | 29 | gulp.task('bench', function() { 30 | return gulp.src(paths.benchmarks) 31 | .pipe(benchmark()); 32 | }); 33 | 34 | gulp.task('test', function(){ 35 | return gulp.src(paths.src) 36 | .pipe(istanbul()) 37 | .on('finish', function(){ 38 | gulp.src(paths.tests) 39 | .pipe(mocha()) 40 | .pipe(istanbul.writeReports()); 41 | }); 42 | }); 43 | 44 | gulp.task('watch', function(){ 45 | gulp.watch([paths.src, paths.tests], ['lint', 'test', 'docs']); 46 | }); 47 | 48 | gulp.task('default', ['lint', 'test', 'docs']); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "superhash", 3 | "version": "1.1.1", 4 | "description": "HashMap that supports using one or more keys of any type.", 5 | "main": "superhash.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/esco/superhash.git" 9 | }, 10 | "scripts": { 11 | "test": "mocha test/*-test.js --require test/config.js", 12 | "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --ignore-leaks --require test/config test/", 13 | "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --ignore-leaks --require test/config test/" 14 | }, 15 | "author": "Esco Obong", 16 | "license": "MIT", 17 | "keywords": "hash, hashmap, map, dictionary, set, hash table, multi-key, multikey, datastructure", 18 | "devDependencies": { 19 | "chai": "^1.9.2", 20 | "es6-collections": "^0.3.4", 21 | "grunt": "^0.4.5", 22 | "gulp": "^3.8.8", 23 | "gulp-bench": "^1.1.0", 24 | "gulp-coveralls": "^0.1.3", 25 | "gulp-istanbul": "^0.3.1", 26 | "gulp-jshint": "^1.9.0", 27 | "gulp-mdox": "0.0.2", 28 | "gulp-mocha": "^1.1.1", 29 | "hashmap": "^1.1.0", 30 | "istanbul": "^0.3.2", 31 | "jshint-stylish": "^1.0.0", 32 | "mocha": "^1.21.4", 33 | "sinon": "*", 34 | "sinon-chai": "*" 35 | }, 36 | "dependencies": { 37 | "multikey-hash": "^1.0.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /superhash.js: -------------------------------------------------------------------------------- 1 | var mkhash = require('multikey-hash'); 2 | 3 | /** 4 | * Creates a new SuperHash 5 | * 6 | * Example `entries` Array 7 | * 8 | * ``` 9 | * [[1,2,3],'foo'], [[{foo: 'bar', {blip:'blop'}],'bar']] 10 | * ``` 11 | * 12 | * @class 13 | * @param {Array} entries two dimensional array with entries to prefill the map. Keys must be an array (even if just one) 14 | * @api public 15 | */ 16 | function SuperHash(entries) { 17 | var args; 18 | 19 | this.store = {}; 20 | this.size = 0; 21 | 22 | if (Object.defineProperty) { 23 | Object.defineProperty(this, 'store', {enumerable:false}); 24 | } 25 | 26 | if (!entries) { 27 | return; 28 | } 29 | 30 | for (var i = 0; i < entries.length; i++) { 31 | args = entries[i][0]; 32 | args.push(entries[i][1]); 33 | this.set.apply(this, args); 34 | } 35 | } 36 | 37 | /** 38 | * Creates a hash from the keys if it doesn't exist and sets the last argument passed in as the value 39 | * 40 | * @param {...*} keys Used to generate hash 41 | * @param {*} value to be associated with the key 42 | * @return {Number} the hash associated with the keys 43 | * @api public 44 | */ 45 | SuperHash.prototype.set = function set() { 46 | var len = arguments.length; 47 | var value = arguments[len-1]; 48 | var keys = new Array(len-1); 49 | var hash; 50 | 51 | for (var i = 0; i < len-1; i++) { 52 | keys[i] = arguments[i]; 53 | } 54 | 55 | hash = mkhash.apply(this, keys); 56 | 57 | if (!this.store.hasOwnProperty(hash)) { 58 | this.size++; 59 | } 60 | 61 | this.store[hash] = [keys, value]; 62 | return this; 63 | }; 64 | 65 | /** 66 | * Returns the value associated with the hash generated from the keys 67 | * 68 | * @param {...*} keys Used to generate a hash for lookup 69 | * @return {Object} value associated with generated hash 70 | * @api public 71 | */ 72 | SuperHash.prototype.get = function get() { 73 | var hash = mkhash.apply(this, arguments); 74 | 75 | return this.store[hash] ? this.store[hash][1] : undefined; 76 | }; 77 | 78 | /** 79 | * Tells whether or not value associated with the hash generated from the keys is in the map 80 | * 81 | * @param {...*} keys - Used to generate a hash for lookup 82 | * @return {Boolean} true if generated hash is in the map 83 | * @api public 84 | */ 85 | SuperHash.prototype.has = function has() { 86 | var key = mkhash.apply(this, arguments); 87 | 88 | return key in this.store; 89 | }; 90 | 91 | /** 92 | * Returns all keys from the hash map 93 | * @return {Array} keys from the hash map 94 | * @api public 95 | */ 96 | SuperHash.prototype.keys = function keys() { 97 | return _getEntries(this.store, 0); 98 | }; 99 | 100 | /** 101 | * Returns all entries (key/value pairs) from the hash map 102 | * @return {Array} entries from the hash map 103 | * @api public 104 | */ 105 | SuperHash.prototype.entries = function entries() { 106 | return _getEntries(this.store); 107 | }; 108 | 109 | /** 110 | * Returns all values from the hash map 111 | * @return {Array} values from the hash map 112 | * @api public 113 | */ 114 | SuperHash.prototype.values = function values() { 115 | return _getEntries(this.store, 1); 116 | }; 117 | 118 | /** 119 | * Loops through each value in the hashmap passing it as the first argument in callack 120 | * 121 | * ```js 122 | * hashMap.forEach(function(value){ 123 | * 124 | * }); 125 | * ``` 126 | * 127 | * @param {Function} cb callback function called with `(key, value)` for each entry in the map 128 | * @param {*} context `this` context for the callback 129 | * @api public 130 | */ 131 | SuperHash.prototype.forEach = function forEach(cb, thisArg) { 132 | thisArg = thisArg || this; 133 | for (var hash in this.store) { 134 | if (this.store.hasOwnProperty(hash)) { 135 | cb.call(thisArg, this.store[hash][1], this.store[hash][0]); 136 | } 137 | } 138 | }; 139 | 140 | /** 141 | * Removes the hash generated by the keys and the associated value 142 | * 143 | * @param {...*} keys - Used to generate a hash for lookup 144 | * @return {Boolean} whether the hash existed or not 145 | * @api public 146 | */ 147 | SuperHash.prototype.delete = function() { 148 | var key = mkhash.apply(this, arguments); 149 | 150 | if (!this.store.hasOwnProperty(key)) { 151 | return false; 152 | } 153 | delete this.store[key]; 154 | this.size--; 155 | return true; 156 | }; 157 | 158 | /** 159 | * Deletes all keys and values from hash map 160 | * @api public 161 | */ 162 | SuperHash.prototype.clear = function clear() { 163 | this.store = {}; 164 | this.size = 0; 165 | }; 166 | 167 | /** 168 | * Returns all keys, values, or entries from hash map depending on entryIndex 169 | * @param {Object} store object of all hash/values 170 | * @param {Number} entryIndex 0=key, 1=value, undefined=entire entry 171 | * @return {Array} values from store 172 | * @api private 173 | */ 174 | function _getEntries(store, entryIndex) { 175 | var results = []; 176 | var value; 177 | var entry; 178 | 179 | for (var hash in store) { 180 | if (!store.hasOwnProperty(hash)){ 181 | continue; 182 | } 183 | entry = store[hash]; 184 | value = entryIndex === undefined ? entry : entry[entryIndex]; 185 | results.push(value); 186 | } 187 | return results; 188 | } 189 | 190 | module.exports = SuperHash; -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | var chai = require('chai'); 3 | var sinonChai = require('sinon-chai'); 4 | 5 | global.should = chai.should(); 6 | global.sinon = require('sinon'); 7 | 8 | chai.use(sinonChai); 9 | -------------------------------------------------------------------------------- /test/superhash-test.js: -------------------------------------------------------------------------------- 1 | describe('SuperHash', function () { 2 | var SuperHash = new require('../superhash'); 3 | var hashMap; 4 | 5 | beforeEach(function () { 6 | hashMap = new SuperHash(); 7 | }); 8 | 9 | describe('SuperHash()', function(){ 10 | it('should add entries passed to contstructor', function(){ 11 | var key1 = { foo: 'bar' }; 12 | var key2 = { blip: 'blop' }; 13 | var hmap = new SuperHash([[[1,2,3],'foo'], [[key1, key2],'bar']]); 14 | 15 | hmap.get(1,2,3).should.equal('foo'); 16 | hmap.get(key1, key2).should.equal('bar'); 17 | }); 18 | }); 19 | 20 | describe('.set(...keys, value)', function(){ 21 | it('should set keys for value', function () { 22 | var expectedValue = 'val'; 23 | hashMap.set(1,2,3,expectedValue); 24 | hashMap.get(1,2,3).should.equal(expectedValue); 25 | }); 26 | 27 | it('should return reference to self', function(){ 28 | var _hashMap = hashMap.set(1,2); 29 | _hashMap.should.equal(hashMap); 30 | }); 31 | 32 | it('should increment size when new entries are added', function(){ 33 | hashMap 34 | .set(1,2,3, 'value1') 35 | .set('a','b', 'value2'); 36 | hashMap.size.should.equal(2); 37 | }); 38 | }); 39 | 40 | describe('.get(...keys)', function(){ 41 | it('should return value for known keys', function(){ 42 | var expectedValue = 'val'; 43 | hashMap.set(1,2,3,expectedValue); 44 | hashMap.get(1,2,3).should.equal(expectedValue); 45 | }); 46 | 47 | it('should return undefined for unknown keys', function(){ 48 | var expectedValue = 'val'; 49 | var value = hashMap.get(1,2,3); 50 | should.equal(value, undefined); 51 | }); 52 | }); 53 | 54 | describe('.has(...keys)', function () { 55 | it('should return true for known keys', function(){ 56 | hashMap.set(1,2,3,'val'); 57 | hashMap.has(1,2,3).should.be.ok; 58 | }); 59 | 60 | it('should return false for unknown keys', function(){ 61 | hashMap.has(1,2,3).should.not.be.ok; 62 | }); 63 | }); 64 | 65 | describe('.delete(...keys)', function(){ 66 | it('should remove key and return true', function(){ 67 | var expectedValue = 'val'; 68 | hashMap.set(1,2,3, expectedValue); 69 | should.exist(hashMap.delete(1,2,3)); 70 | should.not.exist(hashMap.get(1,2,3)); 71 | }); 72 | 73 | it('should return false if key didn\'t exist', function(){ 74 | var expectedValue = 'val'; 75 | hashMap.set(1,2,3, expectedValue); 76 | hashMap.delete('a','b','c').should.not.be.ok; 77 | }); 78 | 79 | it('should decrease size when entries are removed', function(){ 80 | hashMap 81 | .set(1,2,3, 'value1') 82 | .set('a','b', 'value2'); 83 | hashMap.delete('a','b'); 84 | hashMap.size.should.equal(1); 85 | }); 86 | }); 87 | 88 | describe('.keys()', function () { 89 | it('should return the keys used in the map', function(){ 90 | var key1 = { foo: 'bar' }; 91 | var key2 = { blip: 'blop' }; 92 | var expectedKeys = [[1,2,3], [key1, key2]].sort(); 93 | 94 | hashMap.set(1,2,3, 'foo'); 95 | hashMap.set(key1, key2, 'bar'); 96 | hashMap.keys().sort().should.deep.equal(expectedKeys); 97 | }); 98 | 99 | it('should return empty array when there are no keys', function(){ 100 | var expectedKeys = []; 101 | hashMap.keys().should.deep.equal(expectedKeys); 102 | }); 103 | }); 104 | 105 | describe('.values()', function () { 106 | it('should return the values in the map', function(){ 107 | var key1 = { foo: 'bar' }; 108 | var key2 = { blip: 'blop' }; 109 | var expectedValues = ['foo' ,'bar'].sort(); 110 | 111 | hashMap.set(1,2,3, 'foo'); 112 | hashMap.set(key1, key2, 'bar'); 113 | hashMap.values().sort().should.deep.equal(expectedValues); 114 | }); 115 | 116 | it('should return empty array when there are no values', function(){ 117 | var expectedValues = []; 118 | hashMap.values().should.deep.equal(expectedValues); 119 | }); 120 | }); 121 | 122 | describe('.entries()', function () { 123 | it('should return the entries in the map', function(){ 124 | var key1 = { foo: 'bar' }; 125 | var key2 = { blip: 'blop' }; 126 | var expectedEntries = [[[1,2,3],'foo'], [[key1, key2],'bar']].sort(); 127 | 128 | hashMap.set(1,2,3, 'foo'); 129 | hashMap.set(key1, key2, 'bar'); 130 | hashMap.entries().sort().should.deep.equal(expectedEntries); 131 | }); 132 | 133 | it('should return empty array when there are no entries', function(){ 134 | var expectedEntries = []; 135 | hashMap.entries().should.deep.equal(expectedEntries); 136 | }); 137 | }); 138 | 139 | describe('.forEach(cb,thisArg)', function () { 140 | it('should invoke callback for each entry', function(){ 141 | var key1 = { foo: 'bar' }; 142 | var key2 = { blip: 'blop' }; 143 | var expectedEntries = [[[1,2,3],'foo'], [[key1, key2],'bar']]; 144 | var thisArg = {}; 145 | var cb = sinon.spy(function(){ 146 | this.should.equal(thisArg); 147 | }); 148 | 149 | hashMap.set(1,2,3, 'foo'); 150 | hashMap.set(key1, key2, 'bar'); 151 | hashMap.forEach(cb, thisArg); 152 | 153 | cb.should.have.been.calledTwice; 154 | cb.should.have.been.calledWith('foo', [1,2,3]); 155 | cb.should.have.been.calledWith('bar', [key1, key2]); 156 | }); 157 | }); 158 | 159 | describe('.clear()', function() { 160 | it('should remove all entries', function(){ 161 | var key1 = { foo: 'bar' }; 162 | var key2 = { blip: 'blop' }; 163 | var expectedEntries = []; 164 | 165 | hashMap.set(1,2,3, 'foo'); 166 | hashMap.set(key1, key2, 'bar'); 167 | hashMap.clear(); 168 | hashMap.entries().should.deep.equal(expectedEntries); 169 | hashMap.size.should.equal(0); 170 | }); 171 | }); 172 | }); --------------------------------------------------------------------------------