/** * @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 extends Iterable<[Key, Value]>, Equal_.Equal, Pipeable, Inspectable { readonly [HashMapTypeId]: HashMapTypeId } /** @internal */ export declare namespace HashMap { export type UpdateFn = (option: Option.Option) => Option.Option } // 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( edit: number, shift: number, hash1: number, node1: Node, hash2: number, node2: Node ): Node { 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> = (bit1 >>> 0) < (bit2 >>> 0) ? [node1, node2] : [node2, node1] return new IndexedNode(edit, bitmap, children) } /** @internal */ abstract class Node { 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 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 abstract remove( edit: number, shift: number, hash: number, key: K, removed: { value: boolean } ): Node | undefined abstract iterator(): Iterator<[K, V]> abstract [Symbol.iterator](): Iterator<[K, V]> canEdit(edit: number): boolean { return this.edit === edit } } /** @internal */ class EmptyNode extends Node { readonly _tag = "EmptyNode" readonly edit = 0 get size(): number { return 0 } get(_shift: number, _hash: number, _key: K): Option.Option { 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 { added.value = true return new LeafNode(edit, hash, key, value) } remove( _edit: number, _shift: number, _hash: number, _key: K, _removed: { value: boolean } ): Node | 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 extends Node { 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 { 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 { 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> = (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 | 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 extends Node { 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 { 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 { 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 | 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 extends Node { readonly _tag = "IndexedNode" edit: number private _size: number | undefined bitmap: number children: Array> constructor( edit: number, bitmap: number, children: Array> ) { 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 { 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 { 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 | 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>): ArrayNode { const nodes: Array | 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 extends Node { readonly _tag = "ArrayNode" edit: number private _size: number | undefined count: number children: Array | undefined> constructor( edit: number, count: number, children: Array | undefined> ) { super() this.edit = edit this.count = count this.children = children } get size(): number { if (this._size === undefined) { this._size = this.children.reduce((acc, child) => acc + (child?.size ?? 0), 0) } return this._size } get(shift: number, hash: number, key: K): Option.Option { 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 { 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 | 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 | undefined ): IndexedNode { const children: Array> = [] 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 implements HashMap { readonly [HashMapTypeId]: HashMapTypeId = HashMapTypeId _editable: boolean _edit: number _root: Node _size: number constructor( editable: boolean, edit: number, root: Node, 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 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() /** @internal */ export const isHashMap: { (u: Iterable): u is HashMap (u: unknown): u is HashMap } = (u: unknown): u is HashMap => hasProperty(u, HashMapTypeId) /** @internal */ export const empty = (): HashMap => new HashMapImpl(false, 0, emptyNode, 0) /** @internal */ export const make = >( ...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 = (entries: Iterable): HashMap => { let root: Node = 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 = (self: HashMap): boolean => (self as HashMapImpl).size === 0 /** @internal */ export const get = dual< (key: K1) => (self: HashMap) => Option.Option, (self: HashMap, key: K1) => Option.Option >(2, (self: HashMap, key: K): Option.Option => { const impl = self as HashMapImpl return impl._root.get(0, Hash.hash(key), key) }) /** @internal */ export const getHash = dual< (key: K1, hash: number) => (self: HashMap) => Option.Option, (self: HashMap, key: K1, hash: number) => Option.Option >(3, (self: HashMap, key: K, hash: number): Option.Option => { const impl = self as HashMapImpl return impl._root.get(0, hash, key) }) /** @internal */ export const getUnsafe = dual< (key: K1) => (self: HashMap) => V, (self: HashMap, key: K1) => V >(2, (self: HashMap, 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< (key: K1) => (self: HashMap) => boolean, (self: HashMap, key: K1) => boolean >(2, (self: HashMap, key: K): boolean => { const impl = self as HashMapImpl return impl._root.has(0, Hash.hash(key), key) }) /** @internal */ export const hasHash = dual< (key: K1, hash: number) => (self: HashMap) => boolean, (self: HashMap, key: K1, hash: number) => boolean >(3, (self: HashMap, key: K, hash: number): boolean => { const impl = self as HashMapImpl return impl._root.has(0, hash, key) }) /** @internal */ export const hasBy = dual< (predicate: (value: NoInfer, key: NoInfer) => boolean) => (self: HashMap) => boolean, (self: HashMap, predicate: (value: NoInfer, key: NoInfer) => boolean) => boolean >(2, (self: HashMap, 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< (key: K, value: V) => (self: HashMap) => HashMap, (self: HashMap, key: K, value: V) => HashMap >(3, (self: HashMap, key: K, value: V): HashMap => { const impl = self as HashMapImpl 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 = (self: HashMap): IterableIterator => { 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 = (self: HashMap): IterableIterator => { 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 = (self: HashMap): IterableIterator<[K, V]> => { const iterator = self[Symbol.iterator]() return { [Symbol.iterator]() { return this }, next() { return iterator.next() } } } /** @internal */ export const size = (self: HashMap): number => (self as HashMapImpl).size /** @internal */ export const beginMutation = (self: HashMap): HashMap => { const impl = self as HashMapImpl return new HashMapImpl(true, impl._edit + 1, impl._root, impl._size) } /** @internal */ export const endMutation = (self: HashMap): HashMap => { const impl = self as HashMapImpl impl._editable = false return self } /** @internal */ export const mutate = dual< (f: (self: HashMap) => void) => (self: HashMap) => HashMap, (self: HashMap, f: (self: HashMap) => void) => HashMap >(2, (self: HashMap, f: (self: HashMap) => void): HashMap => { const mutable = beginMutation(self) f(mutable) return endMutation(mutable) }) /** @internal */ export const modifyAt = dual< (key: K, f: HashMap.UpdateFn) => (self: HashMap) => HashMap, (self: HashMap, key: K, f: HashMap.UpdateFn) => HashMap >(3, (self: HashMap, key: K, f: HashMap.UpdateFn): HashMap => { 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< (key: K, hash: number, f: HashMap.UpdateFn) => (self: HashMap) => HashMap, (self: HashMap, key: K, hash: number, f: HashMap.UpdateFn) => HashMap >(4, (self: HashMap, key: K, hash: number, f: HashMap.UpdateFn): HashMap => { 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< (key: K, f: (v: V) => V) => (self: HashMap) => HashMap, (self: HashMap, key: K, f: (v: V) => V) => HashMap >(3, (self: HashMap, key: K, f: (v: V) => V): HashMap => { return modifyAt(self, key, Option.map(f)) }) /** @internal */ export const union = dual< (that: HashMap) => (self: HashMap) => HashMap, (self: HashMap, that: HashMap) => HashMap >(2, (self: HashMap, that: HashMap): HashMap => { let result = self as HashMap for (const [key, value] of that) { result = set(result, key, value) } return result }) /** @internal */ export const remove = dual< (key: K) => (self: HashMap) => HashMap, (self: HashMap, key: K) => HashMap >(2, (self: HashMap, key: K): HashMap => { const impl = self as HashMapImpl 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< (keys: Iterable) => (self: HashMap) => HashMap, (self: HashMap, keys: Iterable) => HashMap >(2, (self: HashMap, keys: Iterable): HashMap => { let result = self for (const key of keys) { result = remove(result, key) } return result }) /** @internal */ export const setMany = dual< (entries: Iterable) => (self: HashMap) => HashMap, (self: HashMap, entries: Iterable) => HashMap >(2, (self: HashMap, entries: Iterable): HashMap => { let result = self for (const [key, value] of entries) { result = set(result, key, value) } return result }) /** @internal */ export const map = dual< (f: (value: V, key: K) => A) => (self: HashMap) => HashMap, (self: HashMap, f: (value: V, key: K) => A) => HashMap >(2, (self: HashMap, f: (value: V, key: K) => A): HashMap => { let result = empty() for (const [key, value] of self) { result = set(result, key, f(value, key)) } return result }) /** @internal */ export const flatMap = dual< (f: (value: A, key: K) => HashMap) => (self: HashMap) => HashMap, (self: HashMap, f: (value: A, key: K) => HashMap) => HashMap >(2, (self: HashMap, f: (value: A, key: K) => HashMap): HashMap => { let result = empty() for (const [key, value] of self) { result = union(result, f(value, key)) } return result }) /** @internal */ export const forEach = dual< (f: (value: V, key: K) => void) => (self: HashMap) => void, (self: HashMap, f: (value: V, key: K) => void) => void >(2, (self: HashMap, f: (value: V, key: K) => void): void => { for (const [key, value] of self) { f(value, key) } }) /** @internal */ export const reduce = dual< (zero: Z, f: (accumulator: Z, value: V, key: K) => Z) => (self: HashMap) => Z, (self: HashMap, zero: Z, f: (accumulator: Z, value: V, key: K) => Z) => Z >(3, (self: HashMap, 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< (f: (a: NoInfer, k: K) => boolean) => (self: HashMap) => HashMap, (self: HashMap, f: (a: A, k: K) => boolean) => HashMap >(2, (self: HashMap, f: (a: A, k: K) => boolean) => { let result = empty() for (const [key, value] of self) { if (f(value, key)) { result = set(result, key, value) } } return result }) /** @internal */ export const compact = (self: HashMap>): HashMap => { let result = empty() for (const [key, value] of self) { if (Option.isSome(value)) { result = set(result, key, value.value) } } return result } /** @internal */ export const filterMap = dual< (f: (input: A, key: K) => Result.Result) => (self: HashMap) => HashMap, (self: HashMap, f: (input: A, key: K) => Result.Result) => HashMap >(2, (self: HashMap, f: (input: A, key: K) => Result.Result): HashMap => { let result = empty() 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< (predicate: (a: NoInfer, k: K) => boolean) => (self: HashMap) => Option.Option<[K, A]>, (self: HashMap, predicate: (a: A, k: K) => boolean) => Option.Option<[K, A]> >(2, (self: HashMap, 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< (predicate: (a: NoInfer, k: K) => boolean) => (self: HashMap) => boolean, (self: HashMap, predicate: (a: A, k: K) => boolean) => boolean >(2, (self: HashMap, 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< (predicate: (a: NoInfer, k: K) => boolean) => (self: HashMap) => boolean, (self: HashMap, predicate: (a: A, k: K) => boolean) => boolean >(2, (self: HashMap, predicate: (a: A, k: K) => boolean): boolean => { for (const [key, value] of self) { if (!predicate(value, key)) { return false } } return true })