| /** | |
| * @since 2.0.0 | |
| */ | |
| import * as Equal_ from "../Equal.ts" | |
| import { format } from "../Formatter.ts" | |
| import { dual, pipe } from "../Function.ts" | |
| import * as Hash from "../Hash.ts" | |
| import type { Inspectable } from "../Inspectable.ts" | |
| import { NodeInspectSymbol, toJson } from "../Inspectable.ts" | |
| import * as Option from "../Option.ts" | |
| import type { Pipeable } from "../Pipeable.ts" | |
| import { pipeArguments } from "../Pipeable.ts" | |
| import { hasProperty } from "../Predicate.ts" | |
| import * as Result from "../Result.ts" | |
| import type { NoInfer } from "../Types.ts" | |
| /** @internal */ | |
| export const HashMapTypeId = "~effect/collections/HashMap" | |
| /** @internal */ | |
| export type HashMapTypeId = typeof HashMapTypeId | |
| /** @internal */ | |
| export interface HashMap<out Key, out Value> extends Iterable<[Key, Value]>, Equal_.Equal, Pipeable, Inspectable { | |
| readonly [HashMapTypeId]: HashMapTypeId | |
| } | |
| /** @internal */ | |
| export declare namespace HashMap { | |
| export type UpdateFn<V> = (option: Option.Option<V>) => Option.Option<V> | |
| } | |
| // HAMT Implementation | |
| /** @internal */ | |
| const SHIFT = 5 | |
| /** @internal */ | |
| const BUCKET_SIZE = 1 << SHIFT // 32 | |
| /** @internal */ | |
| // const BITMAP_SIZE = 1 << SHIFT // 32 | |
| /** @internal */ | |
| const MIN_ARRAY_NODE = BUCKET_SIZE / 4 // 8 | |
| /** @internal */ | |
| const MAX_INDEX_NODE = BUCKET_SIZE / 2 // 16 | |
| /** @internal */ | |
| const BITMAP_INDEX_MASK = BUCKET_SIZE - 1 // 31 | |
| /** @internal */ | |
| const popcount = (n: number): number => { | |
| n = n - ((n >>> 1) & 0x55555555) | |
| n = (n & 0x33333333) + ((n >>> 2) & 0x33333333) | |
| return (((n + (n >>> 4)) & 0xF0F0F0F) * 0x1010101) >>> 24 | |
| } | |
| /** @internal */ | |
| const mask = (hash: number, shift: number): number => (hash >>> shift) & BITMAP_INDEX_MASK | |
| /** @internal */ | |
| const bitpos = (hash: number, shift: number): number => 1 << mask(hash, shift) | |
| /** @internal */ | |
| const index = (bitmap: number, bit: number): number => popcount(bitmap & (bit - 1)) | |
| /** @internal */ | |
| function mergeLeaves<K, V>( | |
| edit: number, | |
| shift: number, | |
| hash1: number, | |
| node1: Node<K, V>, | |
| hash2: number, | |
| node2: Node<K, V> | |
| ): Node<K, V> { | |
| if (shift > 32) { | |
| throw new Error("HashMap: max depth exceeded") | |
| } | |
| const bit1 = bitpos(hash1, shift) | |
| const bit2 = bitpos(hash2, shift) | |
| if (bit1 === bit2) { | |
| const child = mergeLeaves(edit, shift + SHIFT, hash1, node1, hash2, node2) | |
| return new IndexedNode(edit, bit1, [child]) | |
| } | |
| const bitmap = bit1 | bit2 | |
| const children: Array<Node<K, V>> = (bit1 >>> 0) < (bit2 >>> 0) | |
| ? [node1, node2] | |
| : [node2, node1] | |
| return new IndexedNode(edit, bitmap, children) | |
| } | |
| /** @internal */ | |
| abstract class Node<K, V> { | |
| abstract readonly _tag: "EmptyNode" | "LeafNode" | "IndexedNode" | "CollisionNode" | "ArrayNode" | |
| abstract readonly edit: number | |
| abstract get size(): number | |
| abstract get(shift: number, hash: number, key: K): Option.Option<V> | |
| abstract has(shift: number, hash: number, key: K): boolean | |
| abstract set( | |
| edit: number, | |
| shift: number, | |
| hash: number, | |
| key: K, | |
| value: V, | |
| added: { value: boolean } | |
| ): Node<K, V> | |
| abstract remove( | |
| edit: number, | |
| shift: number, | |
| hash: number, | |
| key: K, | |
| removed: { value: boolean } | |
| ): Node<K, V> | undefined | |
| abstract iterator(): Iterator<[K, V]> | |
| abstract [Symbol.iterator](): Iterator<[K, V]> | |
| canEdit(edit: number): boolean { | |
| return this.edit === edit | |
| } | |
| } | |
| /** @internal */ | |
| class EmptyNode<K, V> extends Node<K, V> { | |
| readonly _tag = "EmptyNode" | |
| readonly edit = 0 | |
| get size(): number { | |
| return 0 | |
| } | |
| get(_shift: number, _hash: number, _key: K): Option.Option<V> { | |
| return Option.none() | |
| } | |
| has(_shift: number, _hash: number, _key: K): boolean { | |
| return false | |
| } | |
| set( | |
| edit: number, | |
| _shift: number, | |
| hash: number, | |
| key: K, | |
| value: V, | |
| added: { value: boolean } | |
| ): Node<K, V> { | |
| added.value = true | |
| return new LeafNode(edit, hash, key, value) | |
| } | |
| remove( | |
| _edit: number, | |
| _shift: number, | |
| _hash: number, | |
| _key: K, | |
| _removed: { value: boolean } | |
| ): Node<K, V> | undefined { | |
| return this | |
| } | |
| iterator(): Iterator<[K, V]> { | |
| return ([] as Array<[K, V]>)[Symbol.iterator]() | |
| } | |
| [Symbol.iterator](): Iterator<[K, V]> { | |
| return this.iterator() | |
| } | |
| override canEdit(_edit: number): boolean { | |
| return false | |
| } | |
| } | |
| /** @internal */ | |
| class LeafNode<K, V> extends Node<K, V> { | |
| readonly _tag = "LeafNode" | |
| edit: number | |
| readonly hash: number | |
| key: K | |
| value: V | |
| constructor( | |
| edit: number, | |
| hash: number, | |
| key: K, | |
| value: V | |
| ) { | |
| super() | |
| this.edit = edit | |
| this.hash = hash | |
| this.key = key | |
| this.value = value | |
| } | |
| get size(): number { | |
| return 1 | |
| } | |
| get(_shift: number, hash: number, key: K): Option.Option<V> { | |
| if (this.hash === hash && Equal_.equals(this.key, key)) { | |
| return Option.some(this.value) | |
| } | |
| return Option.none() | |
| } | |
| has(_shift: number, hash: number, key: K): boolean { | |
| return this.hash === hash && Equal_.equals(this.key, key) | |
| } | |
| set( | |
| edit: number, | |
| shift: number, | |
| hash: number, | |
| key: K, | |
| value: V, | |
| added: { value: boolean } | |
| ): Node<K, V> { | |
| if (this.hash === hash && Equal_.equals(this.key, key)) { | |
| if (Equal_.equals(this.value, value)) { | |
| return this | |
| } | |
| // Can mutate in-place if edit matches | |
| if (this.canEdit(edit)) { | |
| this.value = value | |
| return this | |
| } | |
| return new LeafNode(edit, hash, key, value) | |
| } | |
| added.value = true | |
| if (this.hash === hash) { | |
| return new CollisionNode(edit, hash, [[this.key, this.value], [key, value]]) | |
| } | |
| const newBit = bitpos(hash, shift) | |
| const existingBit = bitpos(this.hash, shift) | |
| if (newBit === existingBit) { | |
| return new IndexedNode( | |
| edit, | |
| newBit, | |
| [this.set(edit, shift + SHIFT, hash, key, value, added)] | |
| ) | |
| } | |
| const bitmap = newBit | existingBit | |
| const nodes: Array<Node<K, V>> = (newBit >>> 0) < (existingBit >>> 0) | |
| ? [new LeafNode(edit, hash, key, value), this] | |
| : [this, new LeafNode(edit, hash, key, value)] | |
| return new IndexedNode(edit, bitmap, nodes) | |
| } | |
| remove( | |
| _edit: number, | |
| _shift: number, | |
| hash: number, | |
| key: K, | |
| removed: { value: boolean } | |
| ): Node<K, V> | undefined { | |
| if (this.hash === hash && Equal_.equals(this.key, key)) { | |
| removed.value = true | |
| return undefined | |
| } | |
| return this | |
| } | |
| iterator(): Iterator<[K, V]> { | |
| return [[this.key, this.value] as [K, V]][Symbol.iterator]() | |
| } | |
| [Symbol.iterator](): Iterator<[K, V]> { | |
| return this.iterator() | |
| } | |
| } | |
| /** @internal */ | |
| class CollisionNode<K, V> extends Node<K, V> { | |
| readonly _tag = "CollisionNode" | |
| edit: number | |
| readonly hash: number | |
| entries: Array<[K, V]> | |
| constructor( | |
| edit: number, | |
| hash: number, | |
| entries: Array<[K, V]> | |
| ) { | |
| super() | |
| this.edit = edit | |
| this.hash = hash | |
| this.entries = entries | |
| } | |
| get size(): number { | |
| return this.entries.length | |
| } | |
| get(_shift: number, hash: number, key: K): Option.Option<V> { | |
| if (this.hash !== hash) { | |
| return Option.none() | |
| } | |
| for (const [k, v] of this.entries) { | |
| if (Equal_.equals(k, key)) { | |
| return Option.some(v) | |
| } | |
| } | |
| return Option.none() | |
| } | |
| has(_shift: number, hash: number, key: K): boolean { | |
| if (this.hash !== hash) { | |
| return false | |
| } | |
| for (const [k] of this.entries) { | |
| if (Equal_.equals(k, key)) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| set( | |
| edit: number, | |
| shift: number, | |
| hash: number, | |
| key: K, | |
| value: V, | |
| added: { value: boolean } | |
| ): Node<K, V> { | |
| if (this.hash !== hash) { | |
| added.value = true | |
| // Need to merge this collision node with new leaf | |
| return mergeLeaves( | |
| edit, | |
| shift, | |
| this.hash, | |
| this, | |
| hash, | |
| new LeafNode(edit, hash, key, value) | |
| ) | |
| } | |
| // Same hash - update or add to collision list | |
| for (let i = 0; i < this.entries.length; i++) { | |
| if (Equal_.equals(this.entries[i][0], key)) { | |
| if (Equal_.equals(this.entries[i][1], value)) { | |
| return this | |
| } | |
| if (this.canEdit(edit)) { | |
| this.entries[i] = [key, value] | |
| return this | |
| } | |
| const newEntries = [...this.entries] | |
| newEntries[i] = [key, value] | |
| return new CollisionNode(edit, this.hash, newEntries) | |
| } | |
| } | |
| added.value = true | |
| if (this.canEdit(edit)) { | |
| this.entries.push([key, value]) | |
| return this | |
| } | |
| return new CollisionNode(edit, this.hash, [...this.entries, [key, value]]) | |
| } | |
| remove( | |
| edit: number, | |
| _shift: number, | |
| hash: number, | |
| key: K, | |
| removed: { value: boolean } | |
| ): Node<K, V> | undefined { | |
| if (this.hash !== hash) { | |
| return this | |
| } | |
| const idx = this.entries.findIndex(([k]) => Equal_.equals(k, key)) | |
| if (idx === -1) { | |
| return this | |
| } | |
| removed.value = true | |
| if (this.entries.length === 1) { | |
| return undefined | |
| } | |
| if (this.entries.length === 2) { | |
| const remaining = this.entries[idx === 0 ? 1 : 0] | |
| return new LeafNode(edit, this.hash, remaining[0], remaining[1]) | |
| } | |
| if (this.canEdit(edit)) { | |
| this.entries.splice(idx, 1) | |
| return this | |
| } | |
| const newEntries = [...this.entries] | |
| newEntries.splice(idx, 1) | |
| return new CollisionNode(edit, this.hash, newEntries) | |
| } | |
| iterator(): Iterator<[K, V]> { | |
| return this.entries[Symbol.iterator]() | |
| } | |
| [Symbol.iterator](): Iterator<[K, V]> { | |
| return this.iterator() | |
| } | |
| } | |
| /** @internal */ | |
| class IndexedNode<K, V> extends Node<K, V> { | |
| readonly _tag = "IndexedNode" | |
| edit: number | |
| private _size: number | undefined | |
| bitmap: number | |
| children: Array<Node<K, V>> | |
| constructor( | |
| edit: number, | |
| bitmap: number, | |
| children: Array<Node<K, V>> | |
| ) { | |
| super() | |
| this.edit = edit | |
| this.bitmap = bitmap | |
| this.children = children | |
| } | |
| get size(): number { | |
| if (this._size === undefined) { | |
| this._size = this.children.reduce((acc, child) => acc + child.size, 0) | |
| } | |
| return this._size | |
| } | |
| get(shift: number, hash: number, key: K): Option.Option<V> { | |
| const bit = bitpos(hash, shift) | |
| if ((this.bitmap & bit) === 0) { | |
| return Option.none() | |
| } | |
| const idx = index(this.bitmap, bit) | |
| return this.children[idx].get(shift + SHIFT, hash, key) | |
| } | |
| has(shift: number, hash: number, key: K): boolean { | |
| const bit = bitpos(hash, shift) | |
| if ((this.bitmap & bit) === 0) { | |
| return false | |
| } | |
| const idx = index(this.bitmap, bit) | |
| return this.children[idx].has(shift + SHIFT, hash, key) | |
| } | |
| set( | |
| edit: number, | |
| shift: number, | |
| hash: number, | |
| key: K, | |
| value: V, | |
| added: { value: boolean } | |
| ): Node<K, V> { | |
| const bit = bitpos(hash, shift) | |
| const idx = index(this.bitmap, bit) | |
| if ((this.bitmap & bit) !== 0) { | |
| // Existing child - update it | |
| const child = this.children[idx] | |
| const newChild = child.set(edit, shift + SHIFT, hash, key, value, added) | |
| if (child === newChild) { | |
| return this | |
| } | |
| if (this.canEdit(edit)) { | |
| this.children[idx] = newChild | |
| return this | |
| } | |
| const newChildren = [...this.children] | |
| newChildren[idx] = newChild | |
| return new IndexedNode(edit, this.bitmap, newChildren) | |
| } else { | |
| // New child - insert | |
| added.value = true | |
| const newChild = new LeafNode(edit, hash, key, value) | |
| const newBitmap = this.bitmap | bit | |
| if (this.canEdit(edit)) { | |
| this.children.splice(idx, 0, newChild) | |
| this.bitmap = newBitmap | |
| this._size = undefined | |
| if (this.children.length > MAX_INDEX_NODE) { | |
| return this.expand(edit, newBitmap, this.children) | |
| } | |
| return this | |
| } | |
| const newChildren = [...this.children] | |
| newChildren.splice(idx, 0, newChild) | |
| if (newChildren.length > MAX_INDEX_NODE) { | |
| return this.expand(edit, newBitmap, newChildren) | |
| } | |
| return new IndexedNode(edit, newBitmap, newChildren) | |
| } | |
| } | |
| remove( | |
| edit: number, | |
| shift: number, | |
| hash: number, | |
| key: K, | |
| removed: { value: boolean } | |
| ): Node<K, V> | undefined { | |
| const bit = bitpos(hash, shift) | |
| if ((this.bitmap & bit) === 0) { | |
| return this | |
| } | |
| const idx = index(this.bitmap, bit) | |
| const child = this.children[idx] | |
| const newChild = child.remove(edit, shift + SHIFT, hash, key, removed) | |
| if (!removed.value) { | |
| return this | |
| } | |
| if (newChild === undefined) { | |
| const newBitmap = this.bitmap ^ bit | |
| if (newBitmap === 0) { | |
| return undefined | |
| } | |
| if (this.children.length === 2) { | |
| const remaining = this.children[idx === 0 ? 1 : 0] | |
| if (remaining._tag === "LeafNode") { | |
| return remaining | |
| } | |
| } | |
| if (this.canEdit(edit)) { | |
| this.children.splice(idx, 1) | |
| this.bitmap = newBitmap | |
| this._size = undefined | |
| return this | |
| } | |
| const newChildren = [...this.children] | |
| newChildren.splice(idx, 1) | |
| return new IndexedNode(edit, newBitmap, newChildren) | |
| } | |
| if (child === newChild) { | |
| return this | |
| } | |
| if (this.canEdit(edit)) { | |
| this.children[idx] = newChild | |
| return this | |
| } | |
| const newChildren = [...this.children] | |
| newChildren[idx] = newChild | |
| return new IndexedNode(edit, this.bitmap, newChildren) | |
| } | |
| private expand(edit: number, bitmap: number, children: Array<Node<K, V>>): ArrayNode<K, V> { | |
| const nodes: Array<Node<K, V> | undefined> = new globalThis.Array(BUCKET_SIZE) | |
| let j = 0 | |
| for (let i = 0; i < BUCKET_SIZE; i++) { | |
| if ((bitmap & (1 << i)) !== 0) { | |
| nodes[i] = children[j++] | |
| } | |
| } | |
| return new ArrayNode(edit, children.length, nodes) | |
| } | |
| iterator(): Iterator<[K, V]> { | |
| let childIndex = 0 | |
| let currentIterator: Iterator<[K, V]> | undefined | |
| return { | |
| next: () => { | |
| while (childIndex < this.children.length) { | |
| if (!currentIterator) { | |
| currentIterator = this.children[childIndex].iterator() | |
| } | |
| const result = currentIterator.next() | |
| if (!result.done) { | |
| return result | |
| } | |
| currentIterator = undefined | |
| childIndex++ | |
| } | |
| return { done: true, value: undefined } | |
| } | |
| } | |
| } | |
| [Symbol.iterator](): Iterator<[K, V]> { | |
| return this.iterator() | |
| } | |
| } | |
| /** @internal */ | |
| class ArrayNode<K, V> extends Node<K, V> { | |
| readonly _tag = "ArrayNode" | |
| edit: number | |
| private _size: number | undefined | |
| count: number | |
| children: Array<Node<K, V> | undefined> | |
| constructor( | |
| edit: number, | |
| count: number, | |
| children: Array<Node<K, V> | undefined> | |
| ) { | |
| super() | |
| this.edit = edit | |
| this.count = count | |
| this.children = children | |
| } | |
| get size(): number { | |
| if (this._size === undefined) { | |
| this._size = this.children.reduce<number>((acc, child) => acc + (child?.size ?? 0), 0) | |
| } | |
| return this._size | |
| } | |
| get(shift: number, hash: number, key: K): Option.Option<V> { | |
| const idx = mask(hash, shift) | |
| const child = this.children[idx] | |
| return child ? child.get(shift + SHIFT, hash, key) : Option.none() | |
| } | |
| has(shift: number, hash: number, key: K): boolean { | |
| const idx = mask(hash, shift) | |
| const child = this.children[idx] | |
| return child ? child.has(shift + SHIFT, hash, key) : false | |
| } | |
| set( | |
| edit: number, | |
| shift: number, | |
| hash: number, | |
| key: K, | |
| value: V, | |
| added: { value: boolean } | |
| ): Node<K, V> { | |
| const idx = mask(hash, shift) | |
| const child = this.children[idx] | |
| if (child) { | |
| const newChild = child.set(edit, shift + SHIFT, hash, key, value, added) | |
| if (child === newChild) { | |
| return this | |
| } | |
| if (this.canEdit(edit)) { | |
| this.children[idx] = newChild | |
| return this | |
| } | |
| const newChildren = [...this.children] | |
| newChildren[idx] = newChild | |
| return new ArrayNode(edit, this.count, newChildren) | |
| } else { | |
| added.value = true | |
| const newChild = new LeafNode(edit, hash, key, value) | |
| if (this.canEdit(edit)) { | |
| this.children[idx] = newChild | |
| this.count++ | |
| this._size = undefined | |
| return this | |
| } | |
| const newChildren = [...this.children] | |
| newChildren[idx] = newChild | |
| return new ArrayNode(edit, this.count + 1, newChildren) | |
| } | |
| } | |
| remove( | |
| edit: number, | |
| shift: number, | |
| hash: number, | |
| key: K, | |
| removed: { value: boolean } | |
| ): Node<K, V> | undefined { | |
| const idx = mask(hash, shift) | |
| const child = this.children[idx] | |
| if (!child) { | |
| return this | |
| } | |
| const newChild = child.remove(edit, shift + SHIFT, hash, key, removed) | |
| if (!removed.value) { | |
| return this | |
| } | |
| const newCount = this.count - (newChild ? 0 : 1) | |
| if (newCount < MIN_ARRAY_NODE) { | |
| return this.pack(edit, idx, newChild) | |
| } | |
| if (child === newChild) { | |
| return this | |
| } | |
| if (this.canEdit(edit)) { | |
| this.children[idx] = newChild | |
| if (!newChild) { | |
| this.count = newCount | |
| } | |
| this._size = undefined | |
| return this | |
| } | |
| const newChildren = [...this.children] | |
| newChildren[idx] = newChild | |
| return new ArrayNode(edit, newCount, newChildren) | |
| } | |
| private pack( | |
| edit: number, | |
| excludeIdx: number, | |
| newChild: Node<K, V> | undefined | |
| ): IndexedNode<K, V> { | |
| const children: Array<Node<K, V>> = [] | |
| let bitmap = 0 | |
| let bit = 1 | |
| for (let i = 0; i < this.children.length; i++) { | |
| const child = i === excludeIdx ? newChild : this.children[i] | |
| if (child) { | |
| children.push(child) | |
| bitmap |= bit | |
| } | |
| bit <<= 1 | |
| } | |
| return new IndexedNode(edit, bitmap, children) | |
| } | |
| iterator(): Iterator<[K, V]> { | |
| let childIndex = 0 | |
| let currentIterator: Iterator<[K, V]> | undefined | |
| return { | |
| next: () => { | |
| while (childIndex < this.children.length) { | |
| const child = this.children[childIndex] | |
| if (!child) { | |
| childIndex++ | |
| continue | |
| } | |
| if (!currentIterator) { | |
| currentIterator = child.iterator() | |
| } | |
| const result = currentIterator.next() | |
| if (!result.done) { | |
| return result | |
| } | |
| currentIterator = undefined | |
| childIndex++ | |
| } | |
| return { done: true, value: undefined } | |
| } | |
| } | |
| } | |
| [Symbol.iterator](): Iterator<[K, V]> { | |
| return this.iterator() | |
| } | |
| } | |
| /** @internal */ | |
| class HashMapImpl<K, V> implements HashMap<K, V> { | |
| readonly [HashMapTypeId]: HashMapTypeId = HashMapTypeId | |
| _editable: boolean | |
| _edit: number | |
| _root: Node<K, V> | |
| _size: number | |
| constructor( | |
| editable: boolean, | |
| edit: number, | |
| root: Node<K, V>, | |
| size: number | |
| ) { | |
| this._editable = editable | |
| this._edit = edit | |
| this._root = root | |
| this._size = size | |
| } | |
| get size(): number { | |
| return this._size | |
| } | |
| [Symbol.iterator](): Iterator<[K, V]> { | |
| return this._root.iterator() | |
| } | |
| [Equal_.symbol](that: Equal_.Equal): boolean { | |
| if (isHashMap(that)) { | |
| const thatImpl = that as HashMapImpl<K, V> | |
| if (this.size !== thatImpl.size) { | |
| return false | |
| } | |
| for (const [key, value] of this) { | |
| const otherValue = pipe(that, get(key)) | |
| if (Option.isNone(otherValue) || !Equal_.equals(value, otherValue.value)) { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| return false | |
| } | |
| [Hash.symbol](): number { | |
| let hash = Hash.string("HashMap") | |
| for (const [key, value] of this) { | |
| hash = hash ^ (Hash.hash(key) + Hash.hash(value)) | |
| } | |
| return hash | |
| } | |
| [NodeInspectSymbol](): unknown { | |
| return toJson(this) | |
| } | |
| toString(): string { | |
| return `HashMap(${format(Array.from(this))})` | |
| } | |
| toJSON(): unknown { | |
| return { | |
| _id: "HashMap", | |
| values: Array.from(this).map(([k, v]) => [toJson(k), toJson(v)]) | |
| } | |
| } | |
| pipe() { | |
| return pipeArguments(this, arguments) | |
| } | |
| } | |
| /** @internal */ | |
| const emptyNode = new EmptyNode<any, any>() | |
| /** @internal */ | |
| export const isHashMap: { | |
| <K, V>(u: Iterable<readonly [K, V]>): u is HashMap<K, V> | |
| (u: unknown): u is HashMap<unknown, unknown> | |
| } = (u: unknown): u is HashMap<unknown, unknown> => hasProperty(u, HashMapTypeId) | |
| /** @internal */ | |
| export const empty = <K = never, V = never>(): HashMap<K, V> => new HashMapImpl(false, 0, emptyNode, 0) | |
| /** @internal */ | |
| export const make = <Entries extends ReadonlyArray<readonly [any, any]>>( | |
| ...entries: Entries | |
| ): HashMap< | |
| Entries[number] extends readonly [infer K, any] ? K : never, | |
| Entries[number] extends readonly [any, infer V] ? V : never | |
| > => fromIterable(entries) | |
| /** @internal */ | |
| export const fromIterable = <K, V>(entries: Iterable<readonly [K, V]>): HashMap<K, V> => { | |
| let root: Node<K, V> = emptyNode | |
| let size = 0 | |
| const added = { value: false } | |
| for (const [key, value] of entries) { | |
| const hash = Hash.hash(key) | |
| added.value = false | |
| root = root.set(NaN, 0, hash, key, value, added) | |
| if (added.value) { | |
| size++ | |
| } | |
| } | |
| return new HashMapImpl(false, 0, root, size) | |
| } | |
| /** @internal */ | |
| export const isEmpty = <K, V>(self: HashMap<K, V>): boolean => (self as HashMapImpl<K, V>).size === 0 | |
| /** @internal */ | |
| export const get = dual< | |
| <K1 extends K, K>(key: K1) => <V>(self: HashMap<K, V>) => Option.Option<V>, | |
| <K1 extends K, K, V>(self: HashMap<K, V>, key: K1) => Option.Option<V> | |
| >(2, <K, V>(self: HashMap<K, V>, key: K): Option.Option<V> => { | |
| const impl = self as HashMapImpl<K, V> | |
| return impl._root.get(0, Hash.hash(key), key) | |
| }) | |
| /** @internal */ | |
| export const getHash = dual< | |
| <K1 extends K, K>(key: K1, hash: number) => <V>(self: HashMap<K, V>) => Option.Option<V>, | |
| <K1 extends K, K, V>(self: HashMap<K, V>, key: K1, hash: number) => Option.Option<V> | |
| >(3, <K, V>(self: HashMap<K, V>, key: K, hash: number): Option.Option<V> => { | |
| const impl = self as HashMapImpl<K, V> | |
| return impl._root.get(0, hash, key) | |
| }) | |
| /** @internal */ | |
| export const getUnsafe = dual< | |
| <K1 extends K, K>(key: K1) => <V>(self: HashMap<K, V>) => V, | |
| <K1 extends K, K, V>(self: HashMap<K, V>, key: K1) => V | |
| >(2, <K, V>(self: HashMap<K, V>, key: K): V => { | |
| const result = get(self, key) | |
| if (Option.isSome(result)) { | |
| return result.value | |
| } | |
| throw new Error(`HashMap.getUnsafe: key not found: ${key}`) | |
| }) | |
| /** @internal */ | |
| export const has = dual< | |
| <K1 extends K, K>(key: K1) => <V>(self: HashMap<K, V>) => boolean, | |
| <K1 extends K, K, V>(self: HashMap<K, V>, key: K1) => boolean | |
| >(2, <K, V>(self: HashMap<K, V>, key: K): boolean => { | |
| const impl = self as HashMapImpl<K, V> | |
| return impl._root.has(0, Hash.hash(key), key) | |
| }) | |
| /** @internal */ | |
| export const hasHash = dual< | |
| <K1 extends K, K>(key: K1, hash: number) => <V>(self: HashMap<K, V>) => boolean, | |
| <K1 extends K, K, V>(self: HashMap<K, V>, key: K1, hash: number) => boolean | |
| >(3, <K, V>(self: HashMap<K, V>, key: K, hash: number): boolean => { | |
| const impl = self as HashMapImpl<K, V> | |
| return impl._root.has(0, hash, key) | |
| }) | |
| /** @internal */ | |
| export const hasBy = dual< | |
| <K, V>(predicate: (value: NoInfer<V>, key: NoInfer<K>) => boolean) => (self: HashMap<K, V>) => boolean, | |
| <K, V>(self: HashMap<K, V>, predicate: (value: NoInfer<V>, key: NoInfer<K>) => boolean) => boolean | |
| >(2, <K, V>(self: HashMap<K, V>, predicate: (value: V, key: K) => boolean): boolean => { | |
| for (const [key, value] of self) { | |
| if (predicate(value, key)) { | |
| return true | |
| } | |
| } | |
| return false | |
| }) | |
| /** @internal */ | |
| export const set = dual< | |
| <K, V>(key: K, value: V) => (self: HashMap<K, V>) => HashMap<K, V>, | |
| <K, V>(self: HashMap<K, V>, key: K, value: V) => HashMap<K, V> | |
| >(3, <K, V>(self: HashMap<K, V>, key: K, value: V): HashMap<K, V> => { | |
| const impl = self as HashMapImpl<K, V> | |
| const hash = Hash.hash(key) | |
| const added = { value: false } | |
| // Pass edit context: use current edit if editable, otherwise NaN (never matches any edit) | |
| const edit = impl._editable ? impl._edit : NaN | |
| const newRoot = impl._root.set(edit, 0, hash, key, value, added) | |
| if (impl._editable) { | |
| // In-place mutation | |
| impl._root = newRoot | |
| if (added.value) { | |
| impl._size++ | |
| } | |
| return self | |
| } | |
| // Immutable: create new instance if changed | |
| if (impl._root === newRoot) { | |
| return self | |
| } | |
| return new HashMapImpl(false, impl._edit, newRoot, impl._size + (added.value ? 1 : 0)) | |
| }) | |
| /** @internal */ | |
| export const keys = <K, V>(self: HashMap<K, V>): IterableIterator<K> => { | |
| const iterator = self[Symbol.iterator]() | |
| return { | |
| [Symbol.iterator]() { | |
| return this | |
| }, | |
| next() { | |
| const result = iterator.next() | |
| if (result.done) { | |
| return { done: true, value: undefined } | |
| } | |
| return { done: false, value: result.value[0] } | |
| } | |
| } | |
| } | |
| /** @internal */ | |
| export const values = <K, V>(self: HashMap<K, V>): IterableIterator<V> => { | |
| const iterator = self[Symbol.iterator]() | |
| return { | |
| [Symbol.iterator]() { | |
| return this | |
| }, | |
| next() { | |
| const result = iterator.next() | |
| if (result.done) { | |
| return { done: true, value: undefined } | |
| } | |
| return { done: false, value: result.value[1] } | |
| } | |
| } | |
| } | |
| /** @internal */ | |
| export const entries = <K, V>(self: HashMap<K, V>): IterableIterator<[K, V]> => { | |
| const iterator = self[Symbol.iterator]() | |
| return { | |
| [Symbol.iterator]() { | |
| return this | |
| }, | |
| next() { | |
| return iterator.next() | |
| } | |
| } | |
| } | |
| /** @internal */ | |
| export const size = <K, V>(self: HashMap<K, V>): number => (self as HashMapImpl<K, V>).size | |
| /** @internal */ | |
| export const beginMutation = <K, V>(self: HashMap<K, V>): HashMap<K, V> => { | |
| const impl = self as HashMapImpl<K, V> | |
| return new HashMapImpl(true, impl._edit + 1, impl._root, impl._size) | |
| } | |
| /** @internal */ | |
| export const endMutation = <K, V>(self: HashMap<K, V>): HashMap<K, V> => { | |
| const impl = self as HashMapImpl<K, V> | |
| impl._editable = false | |
| return self | |
| } | |
| /** @internal */ | |
| export const mutate = dual< | |
| <K, V>(f: (self: HashMap<K, V>) => void) => (self: HashMap<K, V>) => HashMap<K, V>, | |
| <K, V>(self: HashMap<K, V>, f: (self: HashMap<K, V>) => void) => HashMap<K, V> | |
| >(2, <K, V>(self: HashMap<K, V>, f: (self: HashMap<K, V>) => void): HashMap<K, V> => { | |
| const mutable = beginMutation(self) | |
| f(mutable) | |
| return endMutation(mutable) | |
| }) | |
| /** @internal */ | |
| export const modifyAt = dual< | |
| <K, V>(key: K, f: HashMap.UpdateFn<V>) => (self: HashMap<K, V>) => HashMap<K, V>, | |
| <K, V>(self: HashMap<K, V>, key: K, f: HashMap.UpdateFn<V>) => HashMap<K, V> | |
| >(3, <K, V>(self: HashMap<K, V>, key: K, f: HashMap.UpdateFn<V>): HashMap<K, V> => { | |
| const current = get(self, key) | |
| const updated = f(current) | |
| if (Option.isNone(updated)) { | |
| return has(self, key) ? remove(self, key) : self | |
| } | |
| return set(self, key, updated.value) | |
| }) | |
| /** @internal */ | |
| export const modifyHash = dual< | |
| <K, V>(key: K, hash: number, f: HashMap.UpdateFn<V>) => (self: HashMap<K, V>) => HashMap<K, V>, | |
| <K, V>(self: HashMap<K, V>, key: K, hash: number, f: HashMap.UpdateFn<V>) => HashMap<K, V> | |
| >(4, <K, V>(self: HashMap<K, V>, key: K, hash: number, f: HashMap.UpdateFn<V>): HashMap<K, V> => { | |
| const current = getHash(self, key, hash) | |
| const updated = f(current) | |
| if (Option.isNone(updated)) { | |
| return hasHash(self, key, hash) ? remove(self, key) : self | |
| } | |
| return set(self, key, updated.value) | |
| }) | |
| /** @internal */ | |
| export const modify = dual< | |
| <K, V>(key: K, f: (v: V) => V) => (self: HashMap<K, V>) => HashMap<K, V>, | |
| <K, V>(self: HashMap<K, V>, key: K, f: (v: V) => V) => HashMap<K, V> | |
| >(3, <K, V>(self: HashMap<K, V>, key: K, f: (v: V) => V): HashMap<K, V> => { | |
| return modifyAt(self, key, Option.map(f)) | |
| }) | |
| /** @internal */ | |
| export const union = dual< | |
| <K1, V1>(that: HashMap<K1, V1>) => <K0, V0>(self: HashMap<K0, V0>) => HashMap<K1 | K0, V1 | V0>, | |
| <K0, V0, K1, V1>(self: HashMap<K0, V0>, that: HashMap<K1, V1>) => HashMap<K0 | K1, V0 | V1> | |
| >(2, <K0, V0, K1, V1>(self: HashMap<K0, V0>, that: HashMap<K1, V1>): HashMap<K0 | K1, V0 | V1> => { | |
| let result = self as HashMap<K0 | K1, V0 | V1> | |
| for (const [key, value] of that) { | |
| result = set(result, key, value) | |
| } | |
| return result | |
| }) | |
| /** @internal */ | |
| export const remove = dual< | |
| <K>(key: K) => <V>(self: HashMap<K, V>) => HashMap<K, V>, | |
| <K, V>(self: HashMap<K, V>, key: K) => HashMap<K, V> | |
| >(2, <K, V>(self: HashMap<K, V>, key: K): HashMap<K, V> => { | |
| const impl = self as HashMapImpl<K, V> | |
| const hash = Hash.hash(key) | |
| const removed = { value: false } | |
| const edit = impl._editable ? impl._edit : NaN | |
| const newRoot = impl._root.remove(edit, 0, hash, key, removed) | |
| if (!removed.value) { | |
| return self | |
| } | |
| if (impl._editable) { | |
| impl._root = newRoot ?? emptyNode | |
| impl._size-- | |
| return self | |
| } | |
| if (newRoot === undefined) { | |
| return empty() | |
| } | |
| return new HashMapImpl(false, impl._edit, newRoot, impl._size - 1) | |
| }) | |
| /** @internal */ | |
| export const removeMany = dual< | |
| <K>(keys: Iterable<K>) => <V>(self: HashMap<K, V>) => HashMap<K, V>, | |
| <K, V>(self: HashMap<K, V>, keys: Iterable<K>) => HashMap<K, V> | |
| >(2, <K, V>(self: HashMap<K, V>, keys: Iterable<K>): HashMap<K, V> => { | |
| let result = self | |
| for (const key of keys) { | |
| result = remove(result, key) | |
| } | |
| return result | |
| }) | |
| /** @internal */ | |
| export const setMany = dual< | |
| <K, V>(entries: Iterable<readonly [K, V]>) => (self: HashMap<K, V>) => HashMap<K, V>, | |
| <K, V>(self: HashMap<K, V>, entries: Iterable<readonly [K, V]>) => HashMap<K, V> | |
| >(2, <K, V>(self: HashMap<K, V>, entries: Iterable<readonly [K, V]>): HashMap<K, V> => { | |
| let result = self | |
| for (const [key, value] of entries) { | |
| result = set(result, key, value) | |
| } | |
| return result | |
| }) | |
| /** @internal */ | |
| export const map = dual< | |
| <A, V, K>(f: (value: V, key: K) => A) => (self: HashMap<K, V>) => HashMap<K, A>, | |
| <K, V, A>(self: HashMap<K, V>, f: (value: V, key: K) => A) => HashMap<K, A> | |
| >(2, <K, V, A>(self: HashMap<K, V>, f: (value: V, key: K) => A): HashMap<K, A> => { | |
| let result = empty<K, A>() | |
| for (const [key, value] of self) { | |
| result = set(result, key, f(value, key)) | |
| } | |
| return result | |
| }) | |
| /** @internal */ | |
| export const flatMap = dual< | |
| <A, K, B>(f: (value: A, key: K) => HashMap<K, B>) => (self: HashMap<K, A>) => HashMap<K, B>, | |
| <K, A, B>(self: HashMap<K, A>, f: (value: A, key: K) => HashMap<K, B>) => HashMap<K, B> | |
| >(2, <K, A, B>(self: HashMap<K, A>, f: (value: A, key: K) => HashMap<K, B>): HashMap<K, B> => { | |
| let result = empty<K, B>() | |
| for (const [key, value] of self) { | |
| result = union(result, f(value, key)) | |
| } | |
| return result | |
| }) | |
| /** @internal */ | |
| export const forEach = dual< | |
| <V, K>(f: (value: V, key: K) => void) => (self: HashMap<K, V>) => void, | |
| <V, K>(self: HashMap<K, V>, f: (value: V, key: K) => void) => void | |
| >(2, <V, K>(self: HashMap<K, V>, f: (value: V, key: K) => void): void => { | |
| for (const [key, value] of self) { | |
| f(value, key) | |
| } | |
| }) | |
| /** @internal */ | |
| export const reduce = dual< | |
| <Z, V, K>(zero: Z, f: (accumulator: Z, value: V, key: K) => Z) => (self: HashMap<K, V>) => Z, | |
| <K, V, Z>(self: HashMap<K, V>, zero: Z, f: (accumulator: Z, value: V, key: K) => Z) => Z | |
| >(3, <K, V, Z>(self: HashMap<K, V>, zero: Z, f: (accumulator: Z, value: V, key: K) => Z): Z => { | |
| let result = zero | |
| for (const [key, value] of self) { | |
| result = f(result, value, key) | |
| } | |
| return result | |
| }) | |
| /** @internal */ | |
| export const filter = dual< | |
| <K, A>(f: (a: NoInfer<A>, k: K) => boolean) => (self: HashMap<K, A>) => HashMap<K, A>, | |
| <K, A>(self: HashMap<K, A>, f: (a: A, k: K) => boolean) => HashMap<K, A> | |
| >(2, <K, A>(self: HashMap<K, A>, f: (a: A, k: K) => boolean) => { | |
| let result = empty<K, A>() | |
| for (const [key, value] of self) { | |
| if (f(value, key)) { | |
| result = set(result, key, value) | |
| } | |
| } | |
| return result | |
| }) | |
| /** @internal */ | |
| export const compact = <K, A>(self: HashMap<K, Option.Option<A>>): HashMap<K, A> => { | |
| let result = empty<K, A>() | |
| for (const [key, value] of self) { | |
| if (Option.isSome(value)) { | |
| result = set(result, key, value.value) | |
| } | |
| } | |
| return result | |
| } | |
| /** @internal */ | |
| export const filterMap = dual< | |
| <A, K, B, X>(f: (input: A, key: K) => Result.Result<B, X>) => (self: HashMap<K, A>) => HashMap<K, B>, | |
| <K, A, B, X>(self: HashMap<K, A>, f: (input: A, key: K) => Result.Result<B, X>) => HashMap<K, B> | |
| >(2, <K, A, B, X>(self: HashMap<K, A>, f: (input: A, key: K) => Result.Result<B, X>): HashMap<K, B> => { | |
| let result = empty<K, B>() | |
| for (const [key, value] of self) { | |
| const mapped = f(value, key) | |
| if (Result.isSuccess(mapped)) { | |
| result = set(result, key, mapped.success) | |
| } | |
| } | |
| return result | |
| }) | |
| /** @internal */ | |
| export const findFirst = dual< | |
| <K, A>(predicate: (a: NoInfer<A>, k: K) => boolean) => (self: HashMap<K, A>) => Option.Option<[K, A]>, | |
| <K, A>(self: HashMap<K, A>, predicate: (a: A, k: K) => boolean) => Option.Option<[K, A]> | |
| >(2, <K, A>(self: HashMap<K, A>, predicate: (a: A, k: K) => boolean) => { | |
| for (const [key, value] of self) { | |
| if (predicate(value, key)) { | |
| return Option.some([key, value]) | |
| } | |
| } | |
| return Option.none() | |
| }) | |
| /** @internal */ | |
| export const some = dual< | |
| <K, A>(predicate: (a: NoInfer<A>, k: K) => boolean) => (self: HashMap<K, A>) => boolean, | |
| <K, A>(self: HashMap<K, A>, predicate: (a: A, k: K) => boolean) => boolean | |
| >(2, <K, A>(self: HashMap<K, A>, predicate: (a: A, k: K) => boolean): boolean => { | |
| for (const [key, value] of self) { | |
| if (predicate(value, key)) { | |
| return true | |
| } | |
| } | |
| return false | |
| }) | |
| /** @internal */ | |
| export const every = dual< | |
| <K, A>(predicate: (a: NoInfer<A>, k: K) => boolean) => (self: HashMap<K, A>) => boolean, | |
| <K, A>(self: HashMap<K, A>, predicate: (a: A, k: K) => boolean) => boolean | |
| >(2, <K, A>(self: HashMap<K, A>, predicate: (a: A, k: K) => boolean): boolean => { | |
| for (const [key, value] of self) { | |
| if (!predicate(value, key)) { | |
| return false | |
| } | |
| } | |
| return true | |
| }) | |
Xet Storage Details
- Size:
- 34 kB
- Xet hash:
- eb29da9998d3d29184a4027e83d6a3797b2b11e3939e442a6381a9c8a747603b
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.