projects/wms-framework/src/lib/baseframework/GeneralDictionary.ts
Set of Hashable
objects.
Properties |
|
Methods |
Accessors |
Private bucket |
Type : KeyValue<K, V>[]
|
Default value : []
|
add | |||||||||
add(key: K, value: V)
|
|||||||||
Adds the given key-value into the bucket. If the bucket already has an equivalent key, the new value overwrites the previous value.
Parameters :
Returns :
number
{number} |
Private equalKeys | |||||||||
equalKeys(a: K, b: K)
|
|||||||||
If the key has an
Parameters :
Returns :
boolean
{boolean} |
find | ||||||
find(key: K)
|
||||||
Returns the value associated with the given key. If the bucket does not
contain the key,
Parameters :
Returns :
V
{V} |
remove | ||||||
remove(key: K)
|
||||||
Removes the given key (and it's corresponding value) from the bucket. If the bucket does not contain the key, no operation is performed.
Parameters :
Returns :
boolean
{boolean} |
toArray |
toArray()
|
Returns the key-values in the bucket as an array of
Returns :
[]
{[K,V][]} |
keys |
getkeys()
|
Gets all keys on the bucket.
Returns :
K[]
|
values |
getvalues()
|
Gets all values on the bucket.
Returns :
V[]
|
length |
getlength()
|
Gets the amount of entries on the bucket.
Returns :
number
|
isEmpty |
getisEmpty()
|
Returns
Returns :
boolean
|
import { Debugger } from '../diagnostics/Debugger';
import { ICollection, SimpleList } from './collections';
import { Dictionary } from './Dictionary';
import { Hashable } from './Hashable';
import { KeyValue } from './KeyValue';
/**
* Set of `Hashable` objects.
*
* @class Bucket
* @template K
* @template V
*/
class Bucket<K extends Hashable, V> {
private bucket: KeyValue<K, V>[] = [];
/**
* Adds the given key-value into the bucket. If the bucket already has
* an equivalent key, the new value overwrites the previous value.
*
* @param {K} key
* @param {V} value
* @return {*} {number}
* @memberof Bucket
*/
add(key: K, value: V): number {
const prevLength = this.length;
this.remove(key); // Avoid duplicate keys.
this.bucket.push(new KeyValue(key, value));
return this.length - prevLength;
}
/**
* Removes the given key (and it's corresponding value) from the bucket.
* If the bucket does not contain the key, no operation is performed.
*
* @param {K} key
* @return {*} {boolean}
* @memberof Bucket
*/
remove(key: K): boolean {
const idx = this.bucket.findIndex((x) => this.equalKeys(key, x.Key));
if (idx !== -1) {
this.bucket.splice(idx, 1);
return true;
}
return false;
}
/**
* Returns the value associated with the given key. If the bucket does not
* contain the key, `undefined` is returned.
*
* @param {K} key
* @return {*} {V}
* @memberof Bucket
*/
find(key: K): V {
return this.bucket.find((x) => this.equalKeys(key, x.Key))?.Value;
}
/**
* Returns the key-values in the bucket as an array of `[key,value]` entries.
*
* @return {*} {[K,V][]}
* @memberof Bucket
*/
toArray(): [K, V][] {
return this.bucket as unknown as [K, V][];
}
/**
* Gets all keys on the bucket.
*
* @readonly
* @type {K[]}
* @memberof Bucket
*/
get keys(): K[] {
return this.bucket.map((x) => x.Key);
}
/**
* Gets all values on the bucket.
*
* @readonly
* @type {V[]}
* @memberof Bucket
*/
get values(): V[] {
return this.bucket.map((x) => x.Value);
}
/**
* Gets the amount of entries on the bucket.
*
* @readonly
* @type {number}
* @memberof Bucket
*/
get length(): number {
return this.bucket.length;
}
/**
* Returns `true` if the bucket has no entries,
* `false` otherwise.
*
* @readonly
* @type {boolean}
* @memberof Bucket
*/
get isEmpty(): boolean {
return this.length === 0;
}
/**
* If the key has an `Equals()` function, use it to compare keys.
* Otherwise, use `===` comparison.
*
* @private
* @param {K} a
* @param {K} b
* @return {*} {boolean}
* @memberof Bucket
*/
private equalKeys(a: K, b: K): boolean {
return typeof a.Equals === 'function' ? a.Equals(b) : a === b;
}
}
/**
* A dictionary to store `Hashable` keys.
*
* @export
* @class GeneralDictionary
* @extends {Dictionary<K, V>}
* @template K
* @template V
*/
export class GeneralDictionary<K extends Hashable, V> extends Dictionary<K, V> {
/**
* A map from hash values to buckets.
*
* @private
* @type {Map<number, Bucket<K,V>>}
* @memberof GeneralDictionary
*/
private internalMap: Map<number, Bucket<K, V>> = new Map<
number,
Bucket<K, V>
>();
/**
* Count of entries on this dictionary.
*
* @private
* @memberof GeneralDictionary
*/
private internalCount = 0;
/**
* Gets the value associated with the given key.
* If the key is not present on the dictionay, `null` is returned.
*
* @param {K} key
* @return {*} {V}
* @memberof GeneralDictionary
*/
getItem(key: K): V {
const bucket = this.getBucket(key);
const found = bucket?.find(key);
return found ?? null;
}
/**
* Inserts the given key-value into the dictionary.
* If the dictionary already has an equivalent key, the associated
* value is replaced with the given (new) value.
*
* @param {K} key
* @param {V} value
* @memberof GeneralDictionary
*/
setItem(key: K, value: V): void {
const hash = this.getHash(key);
let bucket = this.getBucketByHash(hash);
if (bucket == null) {
bucket = new Bucket();
this.internalMap.set(hash, bucket);
}
this.internalCount += bucket.add(key, value);
}
/**
* Inserts the given key-value into the dictionary.
* If the dictionary already has an equivalent key, the associated
* value is replaced with the given (new) value.
*
* @param {K} key
* @param {V} value
* @memberof GeneralDictionary
*/
addEntry(key: K, value: V): void {
this.setItem(key, value);
}
/**
* Returns `true` if there is an equivalent key in the dictionary,
* `false` otherwise.
*
* @param {K} key
* @return {*} {boolean}
* @memberof GeneralDictionary
*/
hasKey(key: K): boolean {
const foundValue = this.getBucket(key)?.find(key);
return foundValue != null;
}
/**
* Removes the given key (and it's associated value) from the dictionary.
* If there is not an equivalent key in the dictionary, no operation
* is performed.
*
* @param {K} key
* @memberof GeneralDictionary
*/
removeEntry(key: K): void {
const hash = this.getHash(key);
const bucket = this.getBucketByHash(hash);
if (bucket?.remove(key)) {
this.internalCount--;
if (bucket.isEmpty) {
this.internalMap.delete(hash);
}
}
}
/**
* Attempts to obtain the value associated with the given key.
* If the key exists on the dictionary, `value()` is called with the key's
* value as parameter and `true` is returned.
* If the key is not present on the dictionary, `value()` is not called
* and `false` is returned.
*
* @param {K} key
* @param {(v: V) => void} value
* @return {*} {boolean}
* @memberof GeneralDictionary
*/
tryGetValue(key: K, value: (v: V) => void): boolean {
const foundValue = this.getBucket(key)?.find(key);
if (foundValue != null) {
value(foundValue);
return true;
}
return false;
}
/**
* Returns `true` if there is an equivalent key in the dictionary,
* `false` otherwise.
*
* @param {K} key
* @return {*} {boolean}
* @memberof GeneralDictionary
*/
containsKey(key: K): boolean {
return this.hasKey(key);
}
/**
* Inserts the given key-value into the dictionary.
* If the dictionary already has an equivalent key, the associated
* value is replaced with the given (new) value.
*
* @param {[K, V]} value
* @memberof GeneralDictionary
*/
add(value: [K, V]): void {
this.setItem(value[0], value[1]);
}
/**
* Removes all entries from the dictionary.
*
* @memberof GeneralDictionary
*/
clear(): void {
this.internalMap.clear();
this.internalCount = 0;
}
contains(value: [K, V]): boolean {
Debugger.Throw('Method not implemented.');
return false;
}
remove(value: [K, V]): boolean {
Debugger.Throw('Method not implemented.');
return false;
}
copyTo(target: [K, V][], index: number): void {
Debugger.Throw('Method not implemented.');
}
[Symbol.iterator](): Iterator<[K, V], any, undefined> {
return this.internalArray[Symbol.iterator]();
}
/**
* Gets an array with all entries on the dictionary.
*
* @readonly
* @type {[K, V][]}
* @memberof GeneralDictionary
*/
get internalArray(): [K, V][] {
const arr = [];
for (const bucket of this.internalMap.values()) {
arr.push(...bucket.toArray());
}
return arr;
}
/**
* Gets the number of entries on the dictionary.
*
* @readonly
* @type {number}
* @memberof GeneralDictionary
*/
get count(): number {
return this.internalCount;
}
/**
* Gets all keys on the dictionary.
*
* @readonly
* @type {ICollection<K>}
* @memberof GeneralDictionary
*/
get keys(): ICollection<K> {
const keys: K[] = [];
for (const bucket of this.internalMap.values()) {
keys.push(...bucket.keys);
}
return new SimpleList(keys);
}
/**
* Gets all values on the dictionary.
*
* @readonly
* @type {ICollection<V>}
* @memberof GeneralDictionary
*/
get values(): ICollection<V> {
const values: V[] = [];
for (const bucket of this.internalMap.values()) {
values.push(...bucket.values);
}
return new SimpleList(values);
}
/**
* Gets the bucket associated with the given key.
*
* @private
* @param {K} key
* @return {*} {Bucket<K,V>}
* @memberof GeneralDictionary
*/
private getBucket(key: K): Bucket<K, V> {
return this.getBucketByHash(this.getHash(key));
}
/**
* Gets the bucket associated with the given hash.
*
* @private
* @param {number} hash
* @return {*} {Bucket<K,V>}
* @memberof GeneralDictionary
*/
private getBucketByHash(hash: number): Bucket<K, V> {
return this.internalMap.get(hash);
}
/**
* If the key has a `GetHashCode()` function, use it to obtain the hash.
* Otherwise, return a defaul hash value.
*
* @private
* @param {K} key
* @return {*} {number}
* @memberof GeneralDictionary
*/
private getHash(key: K): number {
return typeof key.GetHashCode === 'function' ? key.GetHashCode() : 4373;
// NOTE: The default hash is a prime number (4373) for
// no particular reason. Any number should do.
}
}