import * as Equal from "@effect/data/Equal" import * as Dual from "@effect/data/Function" import { identity, pipe } from "@effect/data/Function" import * as Hash from "@effect/data/Hash" import type * as HM from "@effect/data/HashMap" import { NodeInspectSymbol, toJSON, toString } from "@effect/data/Inspectable" import { fromBitmap, hashFragment, toBitmap } from "@effect/data/internal/HashMap/bitwise" import { SIZE } from "@effect/data/internal/HashMap/config" import * as Node from "@effect/data/internal/HashMap/node" import * as Option from "@effect/data/Option" import { pipeArguments } from "@effect/data/Pipeable" import { isObject } from "@effect/data/Predicate" /** @internal */ export const HashMapTypeId: HM.TypeId = Symbol.for("@effect/data/HashMap") as HM.TypeId type TraversalFn = (k: K, v: V) => A type Cont = | [ len: number, children: Array>, i: number, f: TraversalFn, cont: Cont ] | undefined interface VisitResult { value: A cont: Cont } /** @internal */ export interface HashMapImpl extends HM.HashMap { _editable: boolean _edit: number _root: Node.Node _size: number } const HashMapProto: HM.HashMap = { [HashMapTypeId]: HashMapTypeId, [Symbol.iterator](this: HashMapImpl): Iterator<[K, V]> { return new HashMapIterator(this, (k, v) => [k, v]) }, [Hash.symbol](): number { let hash = Hash.hash("HashMap") for (const item of this) { hash ^= Hash.combine(Hash.hash(item[0]))(Hash.hash(item[1])) } return hash }, [Equal.symbol](this: HashMapImpl, that: unknown): boolean { if (isHashMap(that)) { if ((that as HashMapImpl)._size !== this._size) { return false } for (const item of this) { const elem = pipe( that as HM.HashMap, getHash(item[0], Hash.hash(item[0])) ) if (Option.isNone(elem)) { return false } else { if (!Equal.equals(item[1], elem.value)) { return false } } } return true } return false }, toString(this: HashMapImpl) { return toString(this.toJSON()) }, toJSON() { return { _id: "HashMap", values: Array.from(this).map(toJSON) } }, [NodeInspectSymbol]() { return this.toJSON() }, pipe() { return pipeArguments(this, arguments) } } const makeImpl = ( editable: boolean, edit: number, root: Node.Node, size: number ): HashMapImpl => { const map = Object.create(HashMapProto) map._editable = editable map._edit = edit map._root = root map._size = size return map } class HashMapIterator implements IterableIterator { v: Option.Option> constructor(readonly map: HashMapImpl, readonly f: TraversalFn) { this.v = visitLazy(this.map._root, this.f, undefined) } next(): IteratorResult { if (Option.isNone(this.v)) { return { done: true, value: undefined } } const v0 = this.v.value this.v = applyCont(v0.cont) return { done: false, value: v0.value } } [Symbol.iterator](): IterableIterator { return new HashMapIterator(this.map, this.f) } } const applyCont = (cont: Cont): Option.Option> => cont ? visitLazyChildren(cont[0], cont[1], cont[2], cont[3], cont[4]) : Option.none() const visitLazy = ( node: Node.Node, f: TraversalFn, cont: Cont = undefined ): Option.Option> => { switch (node._tag) { case "LeafNode": { if (Option.isSome(node.value)) { return Option.some({ value: f(node.key, node.value.value), cont }) } return applyCont(cont) } case "CollisionNode": case "ArrayNode": case "IndexedNode": { const children = node.children return visitLazyChildren(children.length, children, 0, f, cont) } default: { return applyCont(cont) } } } const visitLazyChildren = ( len: number, children: Array>, i: number, f: TraversalFn, cont: Cont ): Option.Option> => { while (i < len) { const child = children[i++] if (child && !Node.isEmptyNode(child)) { return visitLazy(child, f, [len, children, i, f, cont]) } } return applyCont(cont) } const _empty = makeImpl(false, 0, new Node.EmptyNode(), 0) /** @internal */ export const empty = (): HM.HashMap => _empty /** @internal */ export const make = >( ...entries: Entries ): HM.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): HM.HashMap => { const map = beginMutation(empty()) for (const entry of entries) { set(entry[0], entry[1])(map) } return endMutation(map) } /** @internal */ export const isHashMap: { (u: Iterable): u is HM.HashMap (u: unknown): u is HM.HashMap } = (u: unknown): u is HM.HashMap => isObject(u) && HashMapTypeId in u /** @internal */ export const isEmpty = (self: HM.HashMap): boolean => self && Node.isEmptyNode((self as HashMapImpl)._root) /** @internal */ export const get = Dual.dual< (key: K1) => (self: HM.HashMap) => Option.Option, (self: HM.HashMap, key: K1) => Option.Option >(2, (self, key) => getHash(self, key, Hash.hash(key))) /** @internal */ export const getHash = Dual.dual< (key: K1, hash: number) => (self: HM.HashMap) => Option.Option, (self: HM.HashMap, key: K1, hash: number) => Option.Option >(3, (self: HM.HashMap, key: K1, hash: number) => { let node = (self as HashMapImpl)._root let shift = 0 // eslint-disable-next-line no-constant-condition while (true) { switch (node._tag) { case "LeafNode": { return Equal.equals(key, node.key) ? node.value : Option.none() } case "CollisionNode": { if (hash === node.hash) { const children = node.children for (let i = 0, len = children.length; i < len; ++i) { const child = children[i]! if ("key" in child && Equal.equals(key, child.key)) { return child.value } } } return Option.none() } case "IndexedNode": { const frag = hashFragment(shift, hash) const bit = toBitmap(frag) if (node.mask & bit) { node = node.children[fromBitmap(node.mask, bit)]! shift += SIZE break } return Option.none() } case "ArrayNode": { node = node.children[hashFragment(shift, hash)]! if (node) { shift += SIZE break } return Option.none() } default: return Option.none() } } }) /** @internal */ export const unsafeGet = Dual.dual< (key: K1) => (self: HM.HashMap) => V, (self: HM.HashMap, key: K1) => V >(2, (self, key) => { const element = getHash(self, key, Hash.hash(key)) if (Option.isNone(element)) { throw new Error("Error: Expected map to contain key") } return element.value }) /** @internal */ export const has = Dual.dual< (key: K1) => (self: HM.HashMap) => boolean, (self: HM.HashMap, key: K1) => boolean >(2, (self, key) => Option.isSome(getHash(self, key, Hash.hash(key)))) /** @internal */ export const hasHash = Dual.dual< (key: K1, hash: number) => (self: HM.HashMap) => boolean, (self: HM.HashMap, key: K1, hash: number) => boolean >(3, (self, key, hash) => Option.isSome(getHash(self, key, hash))) /** @internal */ export const set = Dual.dual< (key: K, value: V) => (self: HM.HashMap) => HM.HashMap, (self: HM.HashMap, key: K, value: V) => HM.HashMap >(3, (self, key, value) => modifyAt(self, key, () => Option.some(value))) /** @internal */ export const setTree = Dual.dual< (newRoot: Node.Node, newSize: number) => (self: HM.HashMap) => HM.HashMap, (self: HM.HashMap, newRoot: Node.Node, newSize: number) => HM.HashMap >(3, (self: HM.HashMap, newRoot: Node.Node, newSize: number) => { if ((self as HashMapImpl)._editable) { ;(self as HashMapImpl)._root = newRoot ;(self as HashMapImpl)._size = newSize return self } return newRoot === (self as HashMapImpl)._root ? self : makeImpl( (self as HashMapImpl)._editable, (self as HashMapImpl)._edit, newRoot, newSize ) }) /** @internal */ export const keys = (self: HM.HashMap): IterableIterator => new HashMapIterator(self as HashMapImpl, (key) => key) /** @internal */ export const values = (self: HM.HashMap): IterableIterator => new HashMapIterator(self as HashMapImpl, (_, value) => value) /** @internal */ export const size = (self: HM.HashMap): number => (self as HashMapImpl)._size /** @internal */ export const beginMutation = (self: HM.HashMap): HM.HashMap => makeImpl( true, (self as HashMapImpl)._edit + 1, (self as HashMapImpl)._root, (self as HashMapImpl)._size ) /** @internal */ export const endMutation = (self: HM.HashMap): HM.HashMap => { ;(self as HashMapImpl)._editable = false return self } /** @internal */ export const mutate = Dual.dual< (f: (self: HM.HashMap) => void) => (self: HM.HashMap) => HM.HashMap, (self: HM.HashMap, f: (self: HM.HashMap) => void) => HM.HashMap >(2, (self, f) => { const transient = beginMutation(self) f(transient) return endMutation(transient) }) /** @internal */ export const modifyAt = Dual.dual< (key: K, f: HM.HashMap.UpdateFn) => (self: HM.HashMap) => HM.HashMap, (self: HM.HashMap, key: K, f: HM.HashMap.UpdateFn) => HM.HashMap >(3, (self, key, f) => modifyHash(self, key, Hash.hash(key), f)) /** @internal */ export const modifyHash = Dual.dual< (key: K, hash: number, f: HM.HashMap.UpdateFn) => (self: HM.HashMap) => HM.HashMap, (self: HM.HashMap, key: K, hash: number, f: HM.HashMap.UpdateFn) => HM.HashMap >(4, (self: HM.HashMap, key: K, hash: number, f: HM.HashMap.UpdateFn) => { const size = { value: (self as HashMapImpl)._size } const newRoot = (self as HashMapImpl)._root.modify( (self as HashMapImpl)._editable ? (self as HashMapImpl)._edit : NaN, 0, f, hash, key, size ) return pipe(self, setTree(newRoot, size.value)) }) /** @internal */ export const modify = Dual.dual< (key: K, f: (v: V) => V) => (self: HM.HashMap) => HM.HashMap, (self: HM.HashMap, key: K, f: (v: V) => V) => HM.HashMap >(3, (self, key, f) => modifyAt(self, key, Option.map(f))) /** @internal */ export const union = Dual.dual< ( that: HM.HashMap ) => (self: HM.HashMap) => HM.HashMap, ( self: HM.HashMap, that: HM.HashMap ) => HM.HashMap >(2, (self: HM.HashMap, that: HM.HashMap) => { const result: HM.HashMap = beginMutation(self) forEach(that, (v, k) => set(result, k, v)) return endMutation(result) }) /** @internal */ export const remove = Dual.dual< (key: K) => (self: HM.HashMap) => HM.HashMap, (self: HM.HashMap, key: K) => HM.HashMap >(2, (self, key) => modifyAt(self, key, Option.none)) /** @internal */ export const removeMany = Dual.dual< (keys: Iterable) => (self: HM.HashMap) => HM.HashMap, (self: HM.HashMap, keys: Iterable) => HM.HashMap >(2, (self, keys) => mutate(self, (map) => { for (const key of keys) { remove(key)(map) } })) /** * Maps over the entries of the `HashMap` using the specified function. * * @since 1.0.0 * @category mapping */ export const map = Dual.dual< (f: (value: V, key: K) => A) => (self: HM.HashMap) => HM.HashMap, (self: HM.HashMap, f: (value: V, key: K) => A) => HM.HashMap >(2, (self, f) => reduce( self, empty(), (map, value, key) => set(map, key, f(value, key)) )) /** @internal */ export const flatMap = Dual.dual< ( f: (value: A, key: K) => HM.HashMap ) => (self: HM.HashMap) => HM.HashMap, (self: HM.HashMap, f: (value: A, key: K) => HM.HashMap) => HM.HashMap >( 2, (self, f) => reduce(self, empty(), (zero, value, key) => mutate( zero, (map) => forEach(f(value, key), (value, key) => set(map, key, value)) )) ) /** @internal */ export const forEach = Dual.dual< (f: (value: V, key: K) => void) => (self: HM.HashMap) => void, (self: HM.HashMap, f: (value: V, key: K) => void) => void >(2, (self, f) => reduce(self, void 0 as void, (_, value, key) => f(value, key))) /** @internal */ export const reduce = Dual.dual< (zero: Z, f: (accumulator: Z, value: V, key: K) => Z) => (self: HM.HashMap) => Z, (self: HM.HashMap, zero: Z, f: (accumulator: Z, value: V, key: K) => Z) => Z >(3, (self: HM.HashMap, zero: Z, f: (accumulator: Z, value: V, key: K) => Z) => { const root = (self as HashMapImpl)._root if (root._tag === "LeafNode") { return Option.isSome(root.value) ? f(zero, root.value.value, root.key) : zero } if (root._tag === "EmptyNode") { return zero } const toVisit = [root.children] let children while ((children = toVisit.pop())) { for (let i = 0, len = children.length; i < len;) { const child = children[i++] if (child && !Node.isEmptyNode(child)) { if (child._tag === "LeafNode") { if (Option.isSome(child.value)) { zero = f(zero, child.value.value, child.key) } } else { toVisit.push(child.children) } } } } return zero }) /** @internal */ export const filter = Dual.dual< { (f: (a: A, k: K) => a is B): (self: HM.HashMap) => HM.HashMap (f: (a: A, k: K) => boolean): (self: HM.HashMap) => HM.HashMap }, { (self: HM.HashMap, f: (a: A, k: K) => a is B): HM.HashMap (self: HM.HashMap, f: (a: A, k: K) => boolean): HM.HashMap } >(2, (self: HM.HashMap, f: (a: A, k: K) => boolean) => mutate(empty(), (map) => { for (const [k, a] of self) { if (f(a, k)) { set(map, k, a) } } })) /** @internal */ export const compact = (self: HM.HashMap>) => filterMap(self, identity) /** @internal */ export const filterMap = Dual.dual< ( f: (value: A, key: K) => Option.Option ) => (self: HM.HashMap) => HM.HashMap, (self: HM.HashMap, f: (value: A, key: K) => Option.Option) => HM.HashMap >(2, (self, f) => mutate(empty(), (map) => { for (const [k, a] of self) { const option = f(a, k) if (Option.isSome(option)) { set(map, k, option.value) } } })) /** @internal */ export const findFirst: { (predicate: (k: K, a: A) => boolean): (self: HM.HashMap) => Option.Option<[K, A]> (self: HM.HashMap, predicate: (k: K, a: A) => boolean): Option.Option<[K, A]> } = Dual.dual( 2, (self: HM.HashMap, predicate: (k: K, a: A) => boolean): Option.Option<[K, A]> => { for (const ka of self) { if (predicate(ka[0], ka[1])) { return Option.some(ka) } } return Option.none() } )