fast-ternary-string-set
Advanced tools
Comparing version 2.2.0 to 2.3.0
@@ -48,3 +48,3 @@ /** | ||
/** | ||
* Returns the number of unique strings stored in this set. | ||
* Returns the number of unique strings in this set. | ||
* | ||
@@ -63,8 +63,8 @@ * @returns The non-negative integer number of string elements in the set. | ||
* Adding an already present string has no effect. | ||
* If inserting multiple strings in sorted (lexicographic) order, prefer | ||
* `addAll` over this method. | ||
* If inserting multiple strings in sorted order, prefer `addAll` | ||
* over this method. | ||
* | ||
* @param s The non-null string to add. | ||
* @returns This set, allowing chained calls. | ||
* @throws TypeError If the argument is not a string. | ||
* @throws `TypeError` If the argument is not a string. | ||
*/ | ||
@@ -74,17 +74,34 @@ add(s: string): this; | ||
/** | ||
* Adds an entire array, or subarray, of strings to this set. | ||
* Adds zero or more strings to this set. | ||
* | ||
* If the array is sorted in ascending order and no other strings have been | ||
* If the collection is sorted in ascending order and no other strings have been | ||
* added to this set, the underlying tree is guaranteed to be balanced, ensuring | ||
* good search performance. If the array is in random order, the tree is *likely* | ||
* good search performance. If the collection is in random order, the tree is *likely* | ||
* to be nearly balanced. | ||
* | ||
* @param strings The non-null array of strings to add. | ||
* @param start The index of the first element to add (inclusive, default is 0). | ||
* @param end The index of the last element to add (exclusive, default is `strings.length`) | ||
* @param strings Zero or more strings to be added to the set. | ||
* @returns This set, allowing chained calls. | ||
* @throws `ReferenceError` If the array is null. | ||
* @throws `TypeError` If any element is not a string or if the start or end are not numbers. | ||
* @throws `RangeError` If the start or end are out of the array bounds. | ||
* @throws `TypeError` If any of the arguments is not a string. | ||
*/ | ||
addAll(...strings: string[]): TernaryStringSet; | ||
/** | ||
* Adds an entire array, or subarray, of strings to this set. By default, | ||
* the entire collection is added. If the `start` and/or `end` are specified, | ||
* only the elements in the specified range are added. | ||
* | ||
* If the collection is sorted in ascending order and no other strings have been | ||
* added to this set, the underlying tree is guaranteed to be balanced, ensuring | ||
* good search performance. If the collection is in random order, the tree is *likely* | ||
* to be nearly balanced. | ||
* | ||
* @param strings The non-null collection of strings to add. | ||
* @param start The optional index of the first element to add (inclusive, default is 0). | ||
* @param end The optional index of the last element to add (exclusive, default is all elements) | ||
* @returns This set, allowing chained calls. | ||
* @throws `ReferenceError` If the collection is null. | ||
* @throws `TypeError` If `strings` is not an array or if any element is not a string | ||
* or if the start or end are not integer numbers. | ||
* @throws `RangeError` If the start or end are out of bounds, that is, less than 0 | ||
* or greater than `strings.length`. | ||
*/ | ||
addAll(strings: readonly string[], start?: number, end?: number): TernaryStringSet; | ||
@@ -112,2 +129,9 @@ private _addAllImpl; | ||
/** | ||
* Removes multiple elements from this set. | ||
* | ||
* @param elements The elements to remove. | ||
* @returns True if every element was present and was removed. | ||
*/ | ||
deleteAll(...elements: string[]): boolean; | ||
/** | ||
* Returns all strings in this set that can be composed from combinations of the characters | ||
@@ -186,3 +210,3 @@ * in the specified string. Unlike an anagram, not all pattern characters need to appear for a match | ||
* @returns A (possibly empty) array of elements that pass the test. | ||
* @throws {TypeError} If the predicate is not a function. | ||
* @throws `TypeError` If the predicate is not a function. | ||
*/ | ||
@@ -230,2 +254,78 @@ getMatchesOf(predicate: (value: string) => boolean): string[]; | ||
/** | ||
* Returns a new set containing all of the strings in this set that pass a | ||
* test implemented by the specified function. | ||
* | ||
* @param predicate A function that accepts strings from this set and returns | ||
* true if the string should be included in the new set. | ||
* @param thisArg An optional value to use as `this` when calling the predicate. | ||
* @returns A new set containing only those elements for which the predicate return | ||
* value is true. | ||
* @throws `TypeError` If the predicate is not a function. | ||
*/ | ||
filter(predicate: (value: string, index: number, set: TernaryStringSet) => boolean, thisArg?: unknown): TernaryStringSet; | ||
/** | ||
* Returns a new set populated with the results of calling the specified mapping | ||
* function on each element of this set. Like `Array.map()`, the mapping function | ||
* can return any value, but non-string values will be coerced for | ||
* compatibility with the new set. | ||
* | ||
* @param mapper A function that accepts strings from this set and returns | ||
* the string to be added to the new set. | ||
* @param thisArg An optional value to use as `this` when calling the mapping function. | ||
* @returns A new set containing the results of applying the mapping function to each | ||
* element in this set. | ||
* @throws `TypeError` If the mapping function is not a function. | ||
*/ | ||
map(mapper: (value: string, index: number, set: TernaryStringSet) => any, thisArg?: unknown): TernaryStringSet; | ||
/** | ||
* Returns a string that reduces this set to a single accumulated | ||
* value by calling the specified reducer function with each element | ||
* in turn. The reducer is passed the accumulator and the next element | ||
* and returns the new value of the accumulator. | ||
* | ||
* @param reducer A function called with the previous accumulator value, | ||
* the next element to reduce, the element index, and this set. | ||
* @param initialValue An optional initial value for the accumulator. | ||
* If no none is provided, the first element is used. | ||
* @returns The final value of the accumulator. | ||
* @throws `TypeError` If the reducer is not a function or if the set is | ||
* empty and no initial value is provided. | ||
*/ | ||
reduce<T>(reducer: (previous: T, current: string, index: number, set: TernaryStringSet) => T, initialValue: T): T; | ||
reduce(reducer: (previous: string, current: string, index: number, set: TernaryStringSet) => string, initialValue?: string): string; | ||
/** | ||
* Returns the first element in this set that satisfies a test implemented by | ||
* the specified function. If no element satisfies the test, the result is | ||
* `undefined`. | ||
* | ||
* @param predicate A function that accepts strings from this set and returns | ||
* true if the string passes the desired test. | ||
* @param thisArg An optional value to use as `this` when calling the predicate. | ||
* @returns The first string to pass the test when tested in sorted order, or `undefined`. | ||
*/ | ||
find(predicate: (value: string, index: number, set: TernaryStringSet) => boolean, thisArg?: unknown): string | undefined; | ||
/** | ||
* Returns whether at least one element in this set passes a test implemented | ||
* by the specified function. | ||
* | ||
* @param predicate A function that accepts strings from this set and returns | ||
* true if the string passes the desired test. | ||
* @param thisArg An optional value to use as `this` when calling the predicate. | ||
* @returns True if at least one element in this set passes the test. | ||
* @throws `TypeError` If the predicate is not a function. | ||
*/ | ||
some(predicate: (value: string, index: number, set: TernaryStringSet) => boolean, thisArg?: unknown): boolean; | ||
/** | ||
* Returns whether every element in this set passes a test implemented | ||
* by the specified function. | ||
* | ||
* @param predicate A function that accepts strings from this set and returns | ||
* true if the string passes the desired test. | ||
* @param thisArg An optional value to use as `this` when calling the predicate. | ||
* @returns True if at every element in this set passes the test. | ||
* @throws `TypeError` If the predicate is not a function. | ||
*/ | ||
every(predicate: (value: string, index: number, set: TernaryStringSet) => boolean, thisArg?: unknown): boolean; | ||
private _predicateImpl; | ||
/** | ||
* Calls the specified callback function once for each string in this set, passing the string | ||
@@ -236,3 +336,3 @@ * and this set. The string is passed as both value and key to align with `Map.forEach`. | ||
* @param callbackFn The function to call for each string. | ||
* @param thisArg Optional value to use as `this` when calling the function. | ||
* @param thisArg An optional value to use as `this` when calling the function. | ||
* @throws `TypeError` If the callback function is not a function. | ||
@@ -273,72 +373,93 @@ */ | ||
/** | ||
* Returns whether this set contains exactly the same elements as the specified set. | ||
* If passed an object that is not a `TernaryStringSet`, this method returns false. | ||
* Returns a string that is the concatenation of all strings in this set, | ||
* separated by a comma or the specified separator. | ||
* | ||
* @param separator Optional string to use as separator. Default is `","`. | ||
* @returns A string containing all of the set's elements, in sorted order, | ||
* separated by the specified string. | ||
*/ | ||
join(separator?: string): string; | ||
/** | ||
* Returns whether this set contains exactly the same elements as the specified iterable. | ||
* Any object is accepted for comparison; if it is not a set or iterable, the result | ||
* is always `false`. | ||
* | ||
* @param rhs The set (or other object) to compare this set to. | ||
* @returns True if the specified object is also a `TernaryStringSet` and it contains the same elements. | ||
* @returns True if the specified object is iterable, has the same number of elements | ||
* as this set, and this set also contains each of those elements. | ||
*/ | ||
equals(rhs: any): boolean; | ||
/** | ||
* Returns whether this set is a subset of the specified set. This set is a subset if every | ||
* element in this set is also contained in the other set. By this definition, | ||
* equal sets are also subsets of each other. To test if this set is a *proper* subset | ||
* of the specified set, use code like this: `lhs.size < rhs.size && lhs.isSubsetOf(rhs)`. | ||
* Returns whether this set is disjoint from the elements of the specified iterable, | ||
* that is, whether this set has no elements in common with the iterable. | ||
* | ||
* @param rhs The iterable whose elements should be tested against this set's. | ||
* @returns True if `this.intersection(rhs)` is empty. | ||
* @throws `TypeError` If the argument is not an iterable. | ||
*/ | ||
isDisjointFrom(rhs: Iterable<any>): boolean; | ||
/** | ||
* Returns whether this set is a subset of the elements of the specified iterable, | ||
* that is, whether every element in this set is also an element of the iterable. | ||
* | ||
* @param rhs The set to compare this set to. | ||
* @returns True if this set is a subset of, or equal to, the specified set. | ||
* @throws `TypeError` If the specified set is not a `TernaryStringSet`. | ||
* @returns True if this set is a proper subset of, or equal to, the specified iterable. | ||
* @throws `TypeError` If the argument is not an iterable. | ||
*/ | ||
isSubsetOf(rhs: TernaryStringSet): boolean; | ||
isSubsetOf(rhs: Iterable<any>): boolean; | ||
/** | ||
* Returns whether this set is a superset of the specified set. This set is a superset if every | ||
* element in the other set is also contained in this set. By this definition, | ||
* equal sets are also supersets of each other. To test if this set is a *proper* superset | ||
* of the specified set, use code like this: `lhs.size > rhs.size && lhs.isSupersetOf(rhs)`. | ||
* Returns whether this set is a superset of the elements of the specified iterable, | ||
* that is, whether every element of the iterable is also an element in this set. | ||
* | ||
* @param rhs The set to compare this set to. | ||
* @returns True if this set is a superset of, or equal to, the specified set. | ||
* @throws `TypeError` If the specified set is not a `TernaryStringSet`. | ||
* @returns True if this set is a proper superset of, or equal to, the specified iterable. | ||
* @throws `TypeError` If the argument is not an iterable. | ||
*/ | ||
isSupersetOf(rhs: TernaryStringSet): boolean; | ||
isSupersetOf(rhs: Iterable<any>): boolean; | ||
/** | ||
* Returns a new set that is the union of this set and the specified set. | ||
* The new set will include any element that is a member of either set. | ||
* Returns a new set that is the union of this set and the elements of the | ||
* specified iterable. The new set will include any element that is a | ||
* member of either. | ||
* | ||
* @param rhs The set to form a union with. | ||
* @returns A new set containing the elements of both sets. | ||
* @throws `TypeError` If the specified set is not a `TernaryStringSet`. | ||
* @param rhs The iterable whose elements should be united with this set. | ||
* @returns A new set containing the elements of both this set and the iterable. | ||
* @throws `TypeError` If the specified target is not iterable or any | ||
* element is not a string. | ||
*/ | ||
union(rhs: TernaryStringSet): TernaryStringSet; | ||
union(rhs: Iterable<string>): TernaryStringSet; | ||
/** | ||
* Returns a new set that is the intersection of this set and the specified set. | ||
* The new set will include only those elements that are members of both sets. | ||
* Returns a new set that is the intersection of this set and the elements | ||
* of the specified iterable. The new set will include only those elements | ||
* that are members of both. | ||
* | ||
* @param rhs The set to intersect with this set. | ||
* @returns A new set containing only elements in both sets. | ||
* @throws `TypeError` If the specified set is not a `TernaryStringSet`. | ||
* @param rhs The iterable to intersect with this set. | ||
* @returns A new set containing only elements in both this set and the iterable. | ||
* @throws `TypeError` If the specified target is not iterable. | ||
*/ | ||
intersection(rhs: TernaryStringSet): TernaryStringSet; | ||
intersection(rhs: Iterable<any>): TernaryStringSet; | ||
/** @deprecated since 2.2.0, to be removed in 3.0.0. Use `difference` instead. */ | ||
subtract(rhs: TernaryStringSet): TernaryStringSet; | ||
/** | ||
* Returns a new set that is the difference of this set and the specified set. | ||
* The new set will include all of the elements of this set *except* for those | ||
* in the specified set. | ||
* Returns a new set that is the difference of this set and the elements | ||
* of the specified iterable. The new set will contain all elements | ||
* that are only members of this set and not both. | ||
* | ||
* @param rhs The set to subtract from this set. | ||
* @returns A new set containing only those elements in this set that are not | ||
* in the specified set. | ||
* @throws `TypeError` If the specified set is not a `TernaryStringSet`. | ||
* @param rhs The iterable whose elements should be subtracted from this set; | ||
* the iterable's elements do not have to be strings. | ||
* @returns A new set containing only those elements in this set and that are not | ||
* in the specified iterable. | ||
* @throws `TypeError` If the specified target is not iterable. | ||
*/ | ||
difference(rhs: TernaryStringSet): TernaryStringSet; | ||
difference(rhs: Iterable<any>): TernaryStringSet; | ||
/** | ||
* Returns a new set that is the symmetric difference of this set and the specified set. | ||
* The new set will include all of the elements that are either set, but not in *both* sets. | ||
* Returns a new set that is the symmetric difference of this set and the elements | ||
* of the specified iterable. The new set will include all of the elements that | ||
* are in either, but not in *both*. | ||
* | ||
* @param rhs The set to take the symmetric difference of from this set. | ||
* @returns A new set containing only those elements in this set or the specified set, | ||
* but not both. | ||
* @throws `TypeError` If the specified set is not a `TernaryStringSet`. | ||
* @param rhs The iterable whose elements should be exclusive-or'd with this set. | ||
* @returns A new set containing those elements either in this set or the iterable, | ||
* but not both or neither. | ||
* @throws `TypeError` If the specified target is not iterable. | ||
*/ | ||
symmetricDifference(rhs: TernaryStringSet): TernaryStringSet; | ||
symmetricDifference(rhs: Iterable<string>): TernaryStringSet; | ||
/** | ||
@@ -380,7 +501,6 @@ * Returns an iterator over the strings in this set, in ascending lexicographic order. | ||
* than a non-compacted set. The tradeoff is that compact sets cannot be modified. | ||
* Any method that mutates the set, including | ||
* `add`, `addAll`, `balance`, and `delete` | ||
* Any method that mutates the set, such as `add`, `balance`, oe `delete`, | ||
* can therefore cause the set to revert to an non-compacted state. | ||
* | ||
* Compaction is an excellent option if the primary purpose of a set matching | ||
* Compaction is an excellent option if the primary purpose of a set is matching | ||
* against a fixed collection of strings, such as a dictionary. Since | ||
@@ -387,0 +507,0 @@ * compaction and decompaction are expensive operations, it is less attractive |
324
lib/index.js
@@ -9,2 +9,3 @@ "use strict"; | ||
const CP_MIN_SURROGATE = 0x10000; | ||
const BALANCE_LIMIT = 100; | ||
let REGEX_LIT_STOP; | ||
@@ -18,3 +19,3 @@ let REGEX_LIT_ZERO; | ||
if (source != null) { | ||
if (typeof source[Symbol.iterator] !== "function") { | ||
if (!(source[Symbol.iterator] instanceof Function)) { | ||
throw new TypeError("source object is not iterable"); | ||
@@ -28,7 +29,9 @@ } | ||
} | ||
else if (Array.isArray(source)) { | ||
this.addAll(source); | ||
} | ||
else { | ||
this.addAll(Array.from(source)); | ||
if (Array.isArray(source)) { | ||
this.addAll(source); | ||
} | ||
else { | ||
this.addAll(...source); | ||
} | ||
} | ||
@@ -97,20 +100,31 @@ } | ||
} | ||
addAll(strings, start = 0, end = strings.length) { | ||
if (strings == null) | ||
throw new ReferenceError("null strings"); | ||
if (!Array.isArray(strings)) { | ||
throw new TypeError("strings must be an array"); | ||
addAll(...args) { | ||
if (args.length === 0) | ||
return this; | ||
let strings; | ||
let start; | ||
let end; | ||
if (Array.isArray(args[0])) { | ||
strings = args[0]; | ||
const len = strings.length; | ||
start = args[1] ?? 0; | ||
end = args[2] ?? len; | ||
if (typeof start !== "number" || start !== Math.trunc(start)) { | ||
throw new TypeError("start must be an integer"); | ||
} | ||
if (typeof end !== "number" || end !== Math.trunc(end)) { | ||
throw new TypeError("end must be an integer"); | ||
} | ||
if (start < 0 || start > len) { | ||
throw new RangeError("start: " + start); | ||
} | ||
if (end < 0 || end > len) { | ||
throw new RangeError("end: " + end); | ||
} | ||
} | ||
if (typeof start !== "number" || start !== Math.trunc(start)) { | ||
throw new TypeError("start must be an integer"); | ||
else { | ||
strings = args; | ||
start = 0; | ||
end = strings.length; | ||
} | ||
if (start < 0 || start > strings.length) { | ||
throw new RangeError("start: " + start); | ||
} | ||
if (typeof end !== "number" || end !== Math.trunc(end)) { | ||
throw new TypeError("end must be an integer"); | ||
} | ||
if (end < 0 || end > strings.length) { | ||
throw new RangeError("end: " + end); | ||
} | ||
if (start < end) { | ||
@@ -214,2 +228,9 @@ this._addAllImpl(strings, start, end); | ||
} | ||
deleteAll(...elements) { | ||
let allDeleted = true; | ||
for (const el of elements) { | ||
allDeleted = this.delete(el) && allDeleted; | ||
} | ||
return allDeleted; | ||
} | ||
getArrangementsOf(charPattern) { | ||
@@ -538,4 +559,139 @@ if (charPattern == null) | ||
} | ||
filter(predicate, thisArg) { | ||
if (!(predicate instanceof Function)) { | ||
throw new TypeError("predicate must be a function"); | ||
} | ||
if (this._size === 0) | ||
return new TernaryStringSet(); | ||
if (thisArg !== undefined) | ||
predicate = predicate.bind(thisArg); | ||
const results = this._cloneDecompacted(); | ||
let index = 0; | ||
if (this._hasEmpty) { | ||
if (!predicate("", index++, this)) { | ||
results._hasEmpty = false; | ||
--results._size; | ||
} | ||
} | ||
this._visitCodePoints(0, [], (cp) => { | ||
if (!predicate(String.fromCodePoint(...cp), index++, this)) { | ||
const node = results._hasCodePoints(0, cp, 0); | ||
results._tree[node] &= ~EOS; | ||
--results._size; | ||
} | ||
}); | ||
if (results._size === 0) { | ||
results.clear(); | ||
} | ||
else if (results._size <= BALANCE_LIMIT) { | ||
results.balance(); | ||
} | ||
return results; | ||
} | ||
map(mapper, thisArg) { | ||
if (!(mapper instanceof Function)) { | ||
throw new TypeError("mapper must be a function"); | ||
} | ||
if (thisArg !== undefined) | ||
mapper = mapper.bind(thisArg); | ||
const array = this.toArray(); | ||
for (let i = 0; i < array.length; ++i) { | ||
array[i] = String(mapper(array[i], i, this)); | ||
} | ||
return new TernaryStringSet(array); | ||
} | ||
reduce(reducer, initialValue) { | ||
if (!(reducer instanceof Function)) { | ||
throw new TypeError("reducer must be a function"); | ||
} | ||
if (this._size === 0 && initialValue === undefined) { | ||
throw new TypeError("reduce of empty set with no initial value"); | ||
} | ||
let index = 0; | ||
let initialized = false; | ||
let accumulator; | ||
if (initialValue !== undefined) { | ||
accumulator = initialValue; | ||
initialized = true; | ||
} | ||
if (this._hasEmpty) { | ||
if (!initialized) { | ||
accumulator = ""; | ||
initialized = true; | ||
} | ||
else { | ||
accumulator = reducer(accumulator, "", 0, this); | ||
} | ||
index = 1; | ||
} | ||
this._visitCodePoints(0, [], (cp) => { | ||
const s = String.fromCodePoint(...cp); | ||
if (!initialized) { | ||
++index; | ||
accumulator = s; | ||
initialized = true; | ||
} | ||
else { | ||
accumulator = reducer(accumulator, s, index++, this); | ||
} | ||
}); | ||
return accumulator; | ||
} | ||
find(predicate, thisArg) { | ||
if (!(predicate instanceof Function)) { | ||
throw new TypeError("predicate must be a function"); | ||
} | ||
if (this._size === 0) | ||
return undefined; | ||
if (thisArg !== undefined) | ||
predicate = predicate.bind(thisArg); | ||
let index = 0; | ||
if (this._hasEmpty && predicate("", index++, this)) { | ||
return ""; | ||
} | ||
let result = undefined; | ||
this._searchCodePoints(0, [], (cp) => { | ||
const s = String.fromCodePoint(...cp); | ||
if (predicate(s, index++, this)) { | ||
if (result === undefined) { | ||
result = s; | ||
return true; | ||
} | ||
} | ||
return false; | ||
}); | ||
return result; | ||
} | ||
some(predicate, thisArg) { | ||
return this._predicateImpl(true, predicate, thisArg); | ||
} | ||
every(predicate, thisArg) { | ||
return this._predicateImpl(false, predicate, thisArg); | ||
} | ||
_predicateImpl(cond, predicate, thisArg) { | ||
if (!(predicate instanceof Function)) { | ||
throw new TypeError("predicate must be a function"); | ||
} | ||
if (this._size === 0) | ||
return !cond; | ||
if (thisArg !== undefined) | ||
predicate = predicate.bind(thisArg); | ||
let index = 0; | ||
if (this._hasEmpty) { | ||
if (cond == !!predicate("", index++, this)) { | ||
return cond; | ||
} | ||
} | ||
let result = !cond; | ||
this._searchCodePoints(0, [], (cp) => { | ||
if (cond == !!predicate(String.fromCodePoint(...cp), index++, this)) { | ||
result = cond; | ||
return true; | ||
} | ||
return false; | ||
}); | ||
return result; | ||
} | ||
forEach(callbackFn, thisArg) { | ||
if (typeof callbackFn !== "function") { | ||
if (!(callbackFn instanceof Function)) { | ||
throw new TypeError("callbackFn must be a function"); | ||
@@ -587,5 +743,32 @@ } | ||
} | ||
join(separator = ",") { | ||
separator = String(separator); | ||
let result = ""; | ||
let first = !this._hasEmpty; | ||
this._visitCodePoints(0, [], (s) => { | ||
if (first) { | ||
first = false; | ||
} | ||
else { | ||
result += separator; | ||
} | ||
result += String.fromCodePoint(...s); | ||
}); | ||
return result; | ||
} | ||
equals(rhs) { | ||
if (!(rhs instanceof TernaryStringSet)) | ||
return false; | ||
if (this === rhs) | ||
return true; | ||
if (!(rhs instanceof TernaryStringSet)) { | ||
if (rhs == null || !(rhs[Symbol.iterator] instanceof Function)) { | ||
return false; | ||
} | ||
let rhsSize = 0; | ||
for (const el of rhs) { | ||
if (!this.has(el)) | ||
return false; | ||
++rhsSize; | ||
} | ||
return this.size === rhsSize; | ||
} | ||
if (this._size !== rhs._size) | ||
@@ -595,2 +778,29 @@ return false; | ||
} | ||
isDisjointFrom(rhs) { | ||
if (this === rhs) | ||
return false; | ||
if (!(rhs instanceof TernaryStringSet)) { | ||
if (rhs == null || !(rhs[Symbol.iterator] instanceof Function)) { | ||
throw new TypeError("rhs is not iterable"); | ||
} | ||
for (const el of rhs) { | ||
if (this.has(el)) | ||
return false; | ||
} | ||
return true; | ||
} | ||
if (this._size === 0 || rhs._size === 0) | ||
return true; | ||
if (this._hasEmpty && rhs._hasEmpty) | ||
return false; | ||
let disjoint = true; | ||
this._searchCodePoints(0, [], (s) => { | ||
if (rhs._hasCodePoints(0, s, 0) >= 0) { | ||
disjoint = false; | ||
return true; | ||
} | ||
return false; | ||
}); | ||
return disjoint; | ||
} | ||
isSubsetOf(rhs) { | ||
@@ -600,3 +810,13 @@ if (this === rhs) | ||
if (!(rhs instanceof TernaryStringSet)) { | ||
throw new TypeError("not a TernaryStringSet"); | ||
if (rhs == null || !(rhs[Symbol.iterator] instanceof Function)) { | ||
throw new TypeError("rhs is not iterable"); | ||
} | ||
const rhset = rhs instanceof Set ? rhs : new Set(rhs); | ||
if (this._size > rhset.size) | ||
return false; | ||
for (const s of this) { | ||
if (!rhset.has(s)) | ||
return false; | ||
} | ||
return true; | ||
} | ||
@@ -618,4 +838,15 @@ if (this._size > rhs._size) | ||
isSupersetOf(rhs) { | ||
if (this === rhs) | ||
return true; | ||
if (!(rhs instanceof TernaryStringSet)) { | ||
throw new TypeError("not a TernaryStringSet"); | ||
if (rhs == null || !(rhs[Symbol.iterator] instanceof Function)) { | ||
throw new TypeError("rhs is not iterable"); | ||
} | ||
let rhsSize = 0; | ||
for (const el of rhs) { | ||
if (!this.has(el)) | ||
return false; | ||
++rhsSize; | ||
} | ||
return this.size >= rhsSize; | ||
} | ||
@@ -626,3 +857,13 @@ return rhs.isSubsetOf(this); | ||
if (!(rhs instanceof TernaryStringSet)) { | ||
throw new TypeError("not a TernaryStringSet"); | ||
if (rhs == null || !(rhs[Symbol.iterator] instanceof Function)) { | ||
throw new TypeError("rhs is not iterable"); | ||
} | ||
const union = this._cloneDecompacted(); | ||
if (Array.isArray(rhs)) { | ||
union.addAll(rhs); | ||
} | ||
else { | ||
union.addAll(...rhs); | ||
} | ||
return union; | ||
} | ||
@@ -644,3 +885,12 @@ if (rhs._size > this._size) { | ||
if (!(rhs instanceof TernaryStringSet)) { | ||
throw new TypeError("not a TernaryStringSet"); | ||
if (rhs == null || !(rhs[Symbol.iterator] instanceof Function)) { | ||
throw new TypeError("rhs is not iterable"); | ||
} | ||
const intersect = []; | ||
for (const s of rhs) { | ||
if (this.has(s)) { | ||
intersect.push(s); | ||
} | ||
} | ||
return new TernaryStringSet(intersect); | ||
} | ||
@@ -655,3 +905,2 @@ if (rhs._size < this._size) { | ||
} | ||
intersect._hasEmpty && (intersect._hasEmpty = rhs._hasEmpty); | ||
intersect._visitCodePoints(0, [], (s, node) => { | ||
@@ -670,3 +919,10 @@ if (rhs._hasCodePoints(0, s, 0) < 0) { | ||
if (!(rhs instanceof TernaryStringSet)) { | ||
throw new TypeError("not a TernaryStringSet"); | ||
if (rhs == null || !(rhs[Symbol.iterator] instanceof Function)) { | ||
throw new TypeError("rhs is not iterable"); | ||
} | ||
const diff = this._cloneDecompacted(); | ||
for (const s of rhs) { | ||
diff.delete(s); | ||
} | ||
return diff; | ||
} | ||
@@ -688,3 +944,6 @@ const diff = this._cloneDecompacted(); | ||
if (!(rhs instanceof TernaryStringSet)) { | ||
throw new TypeError("not a TernaryStringSet"); | ||
if (rhs == null || !(rhs[Symbol.iterator] instanceof Function)) { | ||
throw new TypeError("rhs is not iterable"); | ||
} | ||
rhs = new TernaryStringSet(rhs); | ||
} | ||
@@ -727,2 +986,3 @@ const diff = this._cloneDecompacted(); | ||
let source = this._tree; | ||
this._tree = null; | ||
for (;;) { | ||
@@ -915,3 +1175,3 @@ const compacted = compactionPass(source); | ||
++i; | ||
cps.push(cp); | ||
cps[cps.length] = cp; | ||
} | ||
@@ -918,0 +1178,0 @@ return cps; |
{ | ||
"name": "fast-ternary-string-set", | ||
"version": "2.2.0", | ||
"version": "2.3.0", | ||
"description": "", | ||
@@ -47,8 +47,8 @@ "keywords": [ | ||
"eslint-plugin-prettier": "^3.4.1", | ||
"eslint-plugin-react": "^7.26.1", | ||
"jest": "^27.3.1", | ||
"prettier": "^2.4.1", | ||
"eslint-plugin-react": "^7.27.1", | ||
"jest": "^27.4.3", | ||
"prettier": "^2.5.0", | ||
"ts-jest": "^27.0.7", | ||
"typedoc": "^0.22.6", | ||
"typescript": "^4.4.4" | ||
"typedoc": "^0.22.10", | ||
"typescript": "^4.5.2" | ||
}, | ||
@@ -55,0 +55,0 @@ "files": [ |
@@ -9,3 +9,3 @@ # Fast ternary string set | ||
[API docs](https://cgjennings.github.io/fast-ternary-string-set/classes/TernaryStringSet.html) | ||
**Jump to:** [Features](#features) / [Installation](#installation) / [Examples](#examples) / [Usage notes](#usage-notes) / [Serialization format](#serialization-format) / [API docs](https://cgjennings.github.io/fast-ternary-string-set/classes/TernaryStringSet.html) | ||
@@ -17,3 +17,5 @@ ## Features | ||
- Search and iteration methods return elements in ascending sorted order (lexicographic by code point). | ||
- Set relations (equality, subset, superset) and operations (union, intersection, difference, symmetric difference). | ||
- Set relations (equal, subset, superset, disjoint). | ||
- Set operations (union, intersection, difference, symmetric difference). | ||
- `Array`-like functional methods (forEach, filter, map, find, reduce, join, some, every). | ||
- Several approximate matching methods: | ||
@@ -73,3 +75,3 @@ 1. List strings that complete a prefix. | ||
// or alternatively | ||
set.addAll(["aardvark", "beaver", "dog", "fish", "hamster"]); | ||
set.addAll("aardvark", "beaver", "dog", "fish", "hamster"); | ||
set.has("cat"); | ||
@@ -89,3 +91,3 @@ // => true | ||
// otherSet could be any Iterable<string>, such as a string array | ||
// or even another TernaryStringSet | ||
// or another TernaryStringSet | ||
let otherSet = new Set(["fish", "hippo"]); | ||
@@ -128,3 +130,3 @@ let set = new TernaryStringSet(otherSet); | ||
Get all elements within edit distance 1 of `"cat"`: | ||
Get all elements within edit distance (Levenshtein distance) 1 of `"cat"`: | ||
@@ -143,2 +145,30 @@ ```js | ||
Create a new set with the elements reversed: | ||
```js | ||
set.map((el) => [...el].reverse().join("")).toArray(); | ||
// => ["olleH", "rotcoD"] (for example) | ||
``` | ||
Get the subset of 4-letter strings: | ||
```js | ||
set.filter((el) => el.length === 4).toArray(); | ||
// => ["bank", "cave", "door", "four"] (for example) | ||
``` | ||
List elements in reverse sorted order: | ||
```js | ||
set.reduce((acc, el) => `${el}, ${acc}`); | ||
// => "cherry, banana, apple" (for example) | ||
``` | ||
Test if any element is longer than 9 characters: | ||
```js | ||
set.add("ambidextrous").some((el) => el.length > 9); | ||
// => true | ||
``` | ||
Compare sets: | ||
@@ -229,10 +259,11 @@ | ||
`TernaryStringSet` supports a superset of the standard `Set` interface, but it is not a subclass of `Set`. | ||
`TernaryStringSet`s behave like standard JS `Set`s, with minor differences: | ||
JavaScript `Set` objects guarantee that they iterate over elements in the order that they are added. | ||
`TernaryStringSet`s always return results in sorted order. | ||
- They iterate over their elements in sorted order (ascending lexicographic order by code point); `Sets` iterate over objects in the order they were added. | ||
- They support a superset of the standard `Set` interface, but are not a subclass of `Set`. Testing them with `instanceof Set` will return `false`. | ||
- They can contain the empty string, but cannot contain non-strings—not even `null` or `undefined`. | ||
- Methods that would return a new `Set`, such as `filter` or `union`, return a new `TernaryStringSet`. | ||
- Methods expect that `this` to be a `TernaryStringSet`; they should not be `call`ed with arbitrary objects. | ||
- The `addAll` method accepts either a list of string arguments (like `Set`s), or an `Iterable<string>` with an optional range. | ||
`TernaryStringSet`s can contain the empty string, but cannot contain non-strings. | ||
Not even `null` or `undefined`. | ||
### Tree health | ||
@@ -242,6 +273,6 @@ | ||
Adding strings in sorted order produces a worst-case tree structure. | ||
This can be avoided by adding strings all at once using the constructor or `addAll()`. | ||
This can be avoided by adding strings all at once using the constructor or `addAll`. | ||
Given sorted input, both of these methods will produce an optimal tree structure. | ||
If this is not practical, adding strings in random order will typically yield a tree that's "close enough" to balanced for most applications. | ||
Calling `balance()` will rebuild the tree in optimal form, but it can be expensive. | ||
Calling `balance` will rebuild the tree in optimal form, but it can be expensive. | ||
@@ -265,6 +296,6 @@ Since most `TernaryStringSet` methods are recursive, extremely unbalanced trees can provoke "maximum call stack size exceeded" errors. | ||
Calling `compact()` can significantly reduce a set's memory footprint. | ||
For large sets of typical strings, this can reduce the set's footprint by around 50–80%. | ||
However, no strings can be added or deleted without first undoing the compaction (this is done automatically when needed). | ||
Compaction is relatively expensive, but can be a one-time or even ahead-of-time step for many use cases. | ||
Calling `compact` can significantly reduce a set's memory footprint. | ||
For large sets of typical strings, this can reduce the set's footprint by more than 50%. | ||
However, no strings can be added or deleted without first undoing the compaction (this is done automatically). | ||
Compaction is relatively expensive, but can be a one-time or ahead-of-time step for many use cases. | ||
@@ -280,5 +311,5 @@ ### Serialization | ||
1. Create a set and insert the desired strings using `add()` or `addAll()`. | ||
2. Minimize the tree size by calling `balance()` followed by `compact()`. | ||
3. Create the buffer with `toBuffer()` and write the result to a file. | ||
1. Create a set and insert the desired strings using `add` or `addAll`. | ||
2. Minimize the tree size by calling `balance` followed by `compact`. | ||
3. Create the buffer with `toBuffer` and write the result to a file. | ||
4. Optionally, compress the result and configure the server to serve the compressed version where supported by the browser. | ||
@@ -347,3 +378,3 @@ | ||
A value of 0 implies that the file is not valid. | ||
Other values would indicate newer versions of the format specification. | ||
Other values would indicate newer versions of the format. | ||
@@ -350,0 +381,0 @@ **Tree flags (int8)** |
100873
2022
478