UNPKG

search-tree

Version:

SearchTree data structure which stores array-like in an efficient way

240 lines (189 loc) 4.59 kB
'use strict' const {custom = Symbol('unavailable: custom')} = require('util').inspect const {stringify} = require('search-tree-utils') const {assign, getPrototypeOf} = Object const {iterator, toStringTag} = Symbol function SearchTreeMapperBase (map, fn) { const mkmapfn = transform => () => new map.constructor( Array.from(map).map(([key, value]) => transform(fn(value, key, map), {key, value})) ) assign(this, { pair: mkmapfn(pair => [...pair]), key: mkmapfn((key, {value}) => [key, value]), value: mkmapfn((value, {key}) => [key, value]) }) } class SearchTreeMapper extends SearchTreeMapperBase { static create (...args) { return new this(...args) } } class SearchTree extends Map { constructor (elements = []) { super() Array.from(elements).forEach(x => this.set(...x)) } has ([first, ...rest]) { const spval = super.get(first) return Boolean(spval && ( rest.length ? spval.subtree.has(rest) : spval.exists )) } get ([first, ...rest]) { const spval = super.get(first) return spval ? (rest.length ? spval.subtree.get(rest) : spval.value ) : undefined } set ([first, ...rest], value) { const spval = super.get(first) if (spval) { if (rest.length) { spval.subtree.set(rest, value) } else { const {subtree = new SearchTree()} = spval assign(spval, {subtree, exists: true, value}) } } else { const {length} = rest const subtree = new SearchTree(length ? [[rest, value]] : []) super.set(first, {subtree, exists: !length, value, __proto__: null}) } return this } delete ([first, ...rest]) { const spval = super.get(first) if (!spval) return false const {subtree} = spval if (rest.length) { if (subtree) { return subtree.delete(rest) } else { super.delete(first) return true } } else { if (subtree) { const {exists} = spval spval.exists = false return exists } else { super.delete(first) return true } } } clear () { super.clear() return this } get size () { let sz = 0 for (const {1: spval} of super.entries()) { if (!spval) continue const {subtree, exists} = spval if (!subtree) continue sz += subtree.size + (exists ? 1 : 0) } return sz } * entries () { for (const [keyel, spval] of super.entries()) { if (!spval) continue const {subtree, exists, value} = spval if (exists) yield [keyel, value] if (subtree) { for (const [rest, value] of subtree) { yield [[keyel, ...rest], value] } } } } [iterator] () { return this.entries() } forEach (fn) { for (const [key, value] of this) { fn(value, key, this) } return this } map (fn) { const {Mapper = SearchTreeMapper} = this.constructor return new Mapper(this, fn) } toString ({ prefix = `${this[toStringTag]}\n`, suffix = '', arrow = ' => ', delimiter = ',\n ', brackets: [ openBracket = '{ ', closeBracket = ' }' ] = [] } = {}) { const middle = Array.from(this) .map(x => x.map(stringify).join(arrow)) .join(delimiter) return prefix + openBracket + middle + closeBracket + suffix } get [toStringTag] () { return this.constructor.name } get [custom] () { return this.toString() } _showInternalStructure () { return new Map(Array.from(super.entries()) .map(([keyel, spval]) => { if (!spval) return [keyel, spval] const {subtree, exists, value} = spval return [ keyel, subtree ? { subtree: subtree._showInternalStructure(), exists, value } : spval ] }) ) } get _interalStructure () { return this._showInternalStructure() } remove (key) { this.remove(key) return this } get length () { return this.size } get count () { return this.size } static get name () { return 'SearchTree' } static get [toStringTag] () { return `class ${this.name} extends ${getPrototypeOf(this).name}` } static toString () { return this[toStringTag] } static create (...args) { return new this(...args) } static get new () { return this.create() } } SearchTree.Mapper = SearchTreeMapper module.exports = SearchTree