From cdd9a5e2ba2e85be8f1e2883ba2235a4db37a681 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Wed, 7 Oct 2020 07:33:58 +0300 Subject: [PATCH] minor fixes, not done yet... Signed-off-by: Alex A. Naanou --- README.md | 162 ++++++++++++++++++++++++++++++++++++++++++-- containers.js | 183 ++++++++++++++++++++++++++++++++++++++++---------- package.json | 2 +- test.js | 6 ++ 4 files changed, 309 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 7d87821..39efe4c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ A library of JavaScript type extensions, types and type utilities. - [types.js](#typesjs) - - [Built-in type extenstions](#built-in-type-extenstions) + - [Installation](#installation) + - [Basic usage](#basic-usage) + - [Built-in type extensions](#built-in-type-extensions) - [`Object`](#object) - [`Object.deepKeys(..)`](#objectdeepkeys) - [`Object.match(..)`](#objectmatch) @@ -43,13 +45,52 @@ A library of JavaScript type extensions, types and type utilities. - [`RegExp`](#regexp) - [`RegExp.quoteRegExp(..)`](#regexpquoteregexp) - [Containers](#containers) - - [`UniqueKeyMap()` (`Map`)](#uniquekeymap-map) + - [`containers.UniqueKeyMap()` (`Map`)](#containersuniquekeymap-map) - [`.reset(..)`](#unique-key-mapreset) - [`.uniqueKey(..)`](#unique-key-mapuniquekey) - [`.rename(..)`](#unique-key-maprename) - [`.keysOf(..)`](#unique-key-mapkeysof) + - [`.__key_pattern__`](#unique-key-map__key_pattern__) + - [License](#license) -## Built-in type extenstions +## Installation + +```shell +$ npm install -s 'ig-types' +``` + + +## Basic usage + +To extend everything: +```javascript +require('ig-types') +``` + +To have access to library types and utilities: +```javascript +var types = require('ig-types') +``` + +`types.js` is organized so as to be able to import/extend only specific +sub-modules mostly independently so... + +In case there is a need to only extend a specific constructor: +```javascript +// require `ig-types/`... +require('ig-types/Array') +``` + +And to import specific library modules only: +```javascript +var containers = require('ig-types/containers') +``` + +Note that though mostly independent now some sub-modules may import +others in the future. + + +## Built-in type extensions ### `Object` @@ -63,6 +104,40 @@ A library of JavaScript type extensions, types and type utilities. #### `.run(..)` +``` +.run() + -> + -> +``` + +Run a function in the context of `` returning either `` +itself (if returning `undefined`) or the result. + +Note that this is accessible from all JavaScript non-primitive objects, +i.e. everything that inherits from `Object`. + +Example: +```javascript +var L = [1, 2, 3] + .map(function(e){ + return e * 2 }) + // see if the first element is 1 and prepend 1 if it is not... + .run(function(){ + if(this[0] != 1){ + this.unshift(1) } }) + +console.log(L) // -> [1, 2, 6, 8] +``` + +`.run(..)` is also available standalone via: + +```shell +$ npm install -s object-run +``` + +For more info see: +https://github.com/flynx/object-run.js + ### `Array` @@ -148,15 +223,81 @@ Generate an array with all duplicate elements removed. ## Containers -### `UniqueKeyMap()` (`Map`) +```javascript +var containers = require('ig-types').containers +``` +or, to only import containers: +```javascript +var containers = require('ig-types/containers') +``` -`UniqueKeyMap` extends the `Map` constructor. +### `containers.UniqueKeyMap()` (`Map`) + +`UniqueKeyMap` implements a key-value container (i.e. `Map`) that supports +and maintains _duplicate_ keys by appending an index to them. +The original keys are stored internally thus the renaming mechanics are +stable. + +`UniqueKeyMap` extends the `Map` constructor, so all the usual `Map` +methods and properties apply here. + +To construct an instance: +```javascript +var x = new UniqueKeyMap() +``` +or: +```javascript +// new is optional... +var y = UniqueKeyMap() +``` + +`UniqueKeyMap` supports the same initialization signature as `Map` but +treats repeating keys differently. +```javascript +var z = UniqueKeyMap([['a', 1], ['a', 2], ['b', 1]]) +``` + +The second `"a"` item will automatically get re-keyed as `"a (1)"`: +```javascript +console.log([...z.keys()]) // -> ['a', 'a (1)', 'b'] +``` + +Note that `.set(..)` will never rewrite an element: +```javascript +z.set('a', 3) + +console.log([...z.keys()]) // -> ['a', 'a (1)', 'b', 'a (2)'] + +z.get('a') // -> 1 +z.get('a (1)') // -> 2 +``` + +To get the generated key: +```javascript +var k = z.set('a', 4, true) + +console.log(k) // -> 'a (3)' +``` + +To explicitly rewrite an item: +```javascript +z.reset('a (1)', 4) + +z.get('a (1)') // -> 4 +``` + +And we can _rename_ items, i.e. change their key: +```javascript +z.rename('a (2)', 'c') + +console.log([...z.keys()]) // -> ['a', 'a (1)', 'b', 'a (3)', 'c'] +``` -XXX For more info on `Map` see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map + #### `.reset(..)` #### `.uniqueKey(..)` @@ -165,7 +306,16 @@ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects #### `.keysOf(..)` +#### `.__key_pattern__` +## License + +[BSD 3-Clause License](./LICENSE) + +Copyright (c) 2020, Alex A. Naanou, +All rights reserved. + + \ No newline at end of file diff --git a/containers.js b/containers.js index c07fa8c..847ec79 100644 --- a/containers.js +++ b/containers.js @@ -26,10 +26,22 @@ module.UniqueKeyMap = object.Constructor('UniqueKeyMap', Map, { // ]) // // XXX should .__keys_index be non-enumerable??? + __keys_index: null, get __keys(){ return (this.__keys_index = this.__keys_index || new Map()) }, + // Format: + // Map([ + // [, ], + // ... + // ]) + // + __reverse_index: null, + get __reverse(){ + return (this.__reverse_index = + this.__reverse_index || new Map()) }, + // Patter to be used to generate unique key... __key_pattern__: '$KEY ($COUNT)', @@ -47,39 +59,10 @@ module.UniqueKeyMap = object.Constructor('UniqueKeyMap', Map, { __unique_key_value__: false, - // NOTE: this will never overwrite a key's value, to overwrite use .reset(..) - set: function(key, elem, return_key=false){ - var names - var n - // index - this.__keys.set(elem, - names = this.__keys.get(elem) || new Set()) - // key/elem already exists... - if(this.__unique_key_value__ - && names.has(key)){ - return return_key ? - key - : this } - names.add(key) - // add the elem with the unique name... - var res = object.parentCall( - UniqueKeyMap.prototype, - 'set', - this, - n = this.uniqieKey(key), - elem) - return return_key ? - n - : res }, - reset: function(key, elem){ - return object.parentCall(UniqueKeyMap.prototype, 'set', this, key, elem) }, - delete: function(key){ - var s = this.__keys.get(this.get(key)) - if(s){ - s.delete(key) - s.size == 0 - & this.__keys.delete(this.get(key)) } - return object.parentCall(UniqueKeyMap.prototype, 'delete', this, key) }, + // helpers... + // + originalKey: function(key){ + return this.__reverse.get(key) }, uniqieKey: function(key){ var n = key var i = 0 @@ -89,10 +72,6 @@ module.UniqueKeyMap = object.Constructor('UniqueKeyMap', Map, { .replace(/\$KEY/, key) .replace(/\$COUNT/, i) } return n }, - rename: function(from, to, return_key=false){ - var e = this.get(from) - this.delete(from) - return this.set(to, e, return_key) }, keysOf: function(elem, mode='original'){ // get unique keys... if(mode == 'unique'){ @@ -104,6 +83,136 @@ module.UniqueKeyMap = object.Constructor('UniqueKeyMap', Map, { return res }, []) } // get keys used to set the values... return [...(this.__keys.get(elem) || [])] }, + // NOTE: we do not touch .__keys here as no renaming is ever done... + // XXX this essentially rewrites the whole map, is there a faster/better + // way to do this??? + sortKeysAs: function(keys){ + var del = object.parent(UniqueKeyMap.prototype, 'delete').bind(this) + var set = object.parent(UniqueKeyMap.prototype, 'set').bind(this) + new Set([...keys, ...this.keys()]) + .forEach(function(k){ + var v = this.get(k) + del(k) + set(k, v) }.bind(this)) + return this }, + + + // NOTE: this will never overwrite a key's value, to overwrite use .reset(..) + set: function(key, elem, return_key=false){ + // index... + var names + this.__keys.set(elem, + names = this.__keys.get(elem) || new Set()) + // key/elem already exists... + if(this.__unique_key_value__ + && names.has(key)){ + return return_key ? + key + : this } + names.add(key) + // add the elem with the unique name... + var n + var res = object.parentCall( + UniqueKeyMap.prototype, + 'set', + this, + n = this.uniqieKey(key), + elem) + // reverse index... + this.__reverse.set(n, key) + return return_key ? + n + : res }, + // XXX in-place... + // XXX feels odd.... + reset: function(key, elem, in_place=false){ + // rewrite... + if(this.has(key)){ + // remove old elem/key from .__keys... + var o = this.originalKey(key) + var s = this.__keys.get(this.get(key)) + s.delete(o) + s.size == 0 + && this.__keys.delete(this.get(key)) + // add new elem/key to .__keys... + var n + this.__keys.set(elem, (n = this.__keys.get(elem) || new Set())) + n.add(o) + + return object.parentCall(UniqueKeyMap.prototype, 'set', this, key, elem) + // add... + } else { + return this.set(...arguments) } }, + // XXX this affects order... + _reset: function(key, elem, return_key=false){ + this.delete(key) + return this.set(...arguments) }, + /*/ + // XXX this will rewrite the whole thing when it does not have to... + _reset: function(key, elem, in_place=false){ + var keys = [...this.keys()] + + this.delete(key) + var res = this.set(...arguments) + + // keep order... + this.sortKeysAs(keys) + + return res }, + //*/ + // XXX BUG: index leak... + // to reproduce: + // u = UniqueKeyMap([ ['a', 1], ['a', 2], ['a', 3] ]) + // u.delete('a (1)') + // -> .__keys still contains [2, 'a'] -- should be gone... + // XXX need a way to get the original key for a specific key, in + // this case: 'a (1)' -> 'a' + // ...add a reverse index??? + delete: function(key){ + var s = this.__keys.get(this.get(key)) + if(s){ + // XXX will this delete if key is with an index??? + //s.delete(key) + s.delete(this.originalKey(key)) + this.__reverse.delete(key) + s.size == 0 + && this.__keys.delete(this.get(key)) } + return object.parentCall(UniqueKeyMap.prototype, 'delete', this, key) }, + // XXX this affects order... + rename: function(from, to, return_key=false){ + var e = this.get(from) + this.delete(from) + return this.set(to, e, return_key) }, + // XXX in-place... + // XXX rename to .rename(..) + /*/ XXX this is ugly... + _rename: function(from, to, return_key=false){ + var res + for([k, v] of [...this.entries()]){ + this.delete(k) + if(k == from){ + res = this.set(to, v, return_key) + } else if(k != to) { + this.reset(k, v) } } + return res }, + /*/ + // XXX do not se how can we avoid rewriting the map if we want to + // keep order... + _rename: function(from, to, return_key=false){ + var keys = [...this.keys()] + + var e = this.get(from) + this.delete(from) + var n = this.set(to, e, true) + + // keep order... + keys.splice(keys.indexOf(from), 1, n) + this.sortKeysAs(keys) + + return return_key ? + n + : this }, + //*/ }) diff --git a/package.json b/package.json index db9e60c..fcdb15c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ig-types", - "version": "2.0.8", + "version": "2.0.9", "description": "Generic JavaScript types and type extensions...", "main": "main.js", "scripts": { diff --git a/test.js b/test.js index 012d856..8999c43 100755 --- a/test.js +++ b/test.js @@ -128,6 +128,7 @@ var cases = test.Cases({ }, // containers.js + // XXX .reset(..) and .rename(..) should not affect order... UniqueKeyMap: function(assert){ var a = assert(containers.UniqueKeyMap(), '') var b = assert(containers.UniqueKeyMap([]), '') @@ -162,7 +163,12 @@ var cases = test.Cases({ assert(c.keysOf(222).sort().cmp(['b', 'a'].sort())) + var k = [...c.keys()] + assert((n = c.rename('a', 'b', true)) == 'b (1)') + + // XXX should .rename(..) affect element order?? + //console.log('>>>>>', k, [...c.keys()]) }, })