@effect-ts/system
Version:
Effect-TS is a zero dependency set of libraries to write highly productive, purely functional TypeScript at scale.
749 lines (671 loc) • 18.2 kB
text/typescript
// ets_tracing: off
import "../../../Operator/index.js"
import type { Refinement } from "../../../Function/index.js"
import { constant, identity, tuple } from "../../../Function/index.js"
import { NoSuchElementException } from "../../../GlobalExceptions/index.js"
import * as I from "../../../Iterable/index.js"
import * as O from "../../../Option/index.js"
import * as St from "../../../Structural/index.js"
import * as Tp from "../Tuple/index.js"
import { fromBitmap, hashFragment, toBitmap } from "./Bitwise/index.js"
import { SIZE } from "./Config/index.js"
import type { Node, UpdateFn } from "./Nodes/index.js"
import { Empty, isEmptyNode } from "./Nodes/index.js"
const HashMapHash = St.hashString("HashMap")
export class HashMap<K, V> implements Iterable<readonly [K, V]> {
readonly _K!: () => K
readonly _V!: () => V
constructor(
public editable: boolean,
public edit: number,
public root: Node<K, V>,
public size: number
) {}
[Symbol.iterator](): Iterator<readonly [K, V]> {
return new HashMapIterator(this, identity)
}
readonly tupleIterator: Iterable<Tp.Tuple<[K, V]>> = {
[Symbol.iterator]: () => new HashMapIterator(this, ([k, v]) => Tp.tuple(k, v))
}
get [St.hashSym](): number {
let hash = HashMapHash
for (const item of this) {
hash ^= St.combineHash(St.hashUnknown(item[0]), St.hashUnknown(item[1]))
}
return hash
}
[St.equalsSym](that: unknown): boolean {
if (that instanceof HashMap) {
if (that.size !== this.size) {
return false
}
for (const item of this) {
const elem = getHash_(that, item[0], St.hash(item[0]))
if (elem._tag === "None") {
return false
} else {
if (!St.equals(item[1], elem.value)) {
return false
}
}
}
return true
}
return false
}
}
export class HashMapIterator<K, V, T> implements IterableIterator<T> {
v = visitLazy(this.map.root, this.f, undefined)
constructor(readonly map: HashMap<K, V>, readonly f: TraversalFn<K, V, T>) {}
next(): IteratorResult<T> {
if (O.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<T> {
return new HashMapIterator(this.map, this.f)
}
}
/**
* Creates a new map
*/
export function make<K, V>() {
return new HashMap<K, V>(false, 0, new Empty(), 0)
}
/**
* Creates a new map from an Iterable
*/
export function from<K, V>(xs: Iterable<readonly [K, V]>): HashMap<K, V> {
return I.reduce_(xs, make<K, V>(), (m, p) => set_(m, ...p))
}
/**
* Set the root of the map
*/
export function setTree_<K, V>(
map: HashMap<K, V>,
newRoot: Node<K, V>,
newSize: number
) {
if (map.editable) {
map.root = newRoot
map.size = newSize
return map
}
return newRoot === map.root
? map
: new HashMap(map.editable, map.edit, newRoot, newSize)
}
/**
* Lookup the value for `key` in `map` using custom hash.
*/
export function tryGetHash_<K, V>(
map: HashMap<K, V>,
key: K,
hash: number
): O.Option<V> {
let node = map.root
let shift = 0
// eslint-disable-next-line no-constant-condition
while (true)
switch (node._tag) {
case "LeafNode": {
return St.equals(key, node.key) ? node.value : O.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 && St.equals(key, child.key)) return child.value
}
}
return O.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 O.none
}
case "ArrayNode": {
node = node.children[hashFragment(shift, hash)]!
if (node) {
shift += SIZE
break
}
return O.none
}
default:
return O.none
}
}
/**
* Lookup the value for `key` in `map` using custom hash.
*/
export function getHash_<K, V>(map: HashMap<K, V>, key: K, hash: number): O.Option<V> {
return tryGetHash_(map, key, hash)
}
/**
* Lookup the value for `key` in `map` using internal hash function.
*/
export function unsafeGet_<K, V>(map: HashMap<K, V>, key: K): V {
const element = tryGetHash_(map, key, St.hash(key))
if (O.isNone(element)) {
throw new NoSuchElementException()
}
return element.value
}
/**
* Lookup the value for `key` in `map` using internal hash function.
*
* @ets_data_first unsafeGet_
*/
export function unsafeGet<K>(key: K) {
return <V>(map: HashMap<K, V>) => unsafeGet_(map, key)
}
/**
* Lookup the value for `key` in `map` using internal hash function.
*/
export function get_<K, V>(map: HashMap<K, V>, key: K): O.Option<V> {
return tryGetHash_(map, key, St.hash(key))
}
/**
* Lookup the value for `key` in `map` using internal hash function.
*
* @ets_data_first get_
*/
export function get<K>(key: K) {
return <V>(map: HashMap<K, V>) => get_(map, key)
}
/**
* Does an entry exist for `key` in `map`? Uses custom `hash`.
*/
export function hasHash_<K, V>(map: HashMap<K, V>, key: K, hash: number): boolean {
return O.isSome(tryGetHash_(map, key, hash))
}
/**
* Does an entry exist for `key` in `map`? Uses internal hash function.
*/
export function has_<K, V>(map: HashMap<K, V>, key: K): boolean {
return O.isSome(tryGetHash_(map, key, St.hash(key)))
}
/**
* Does an entry exist for `key` in `map`? Uses internal hash function.
*
* @ets_data_first has_
*/
export function has<K>(key: K) {
return <V>(map: HashMap<K, V>) => has_(map, key)
}
/**
* Does `map` contain any elements?
*/
export function isEmpty<K, V>(map: HashMap<K, V>): boolean {
return map && !!isEmptyNode(map.root)
}
/**
* Alter the value stored for `key` in `map` using function `f` using custom hash.
*
* `f` is invoked with the current value for `k` if it exists,
* or no arguments if no such value exists.
*
* `modify` will always either update or insert a value into the map.
* Returns a map with the modified value. Does not alter `map`.
*/
export function modifyHash_<K, V>(
map: HashMap<K, V>,
key: K,
hash: number,
f: UpdateFn<V>
): HashMap<K, V> {
const size = { value: map.size }
const newRoot = map.root.modify(map.editable ? map.edit : NaN, 0, f, hash, key, size)
return setTree_(map, newRoot, size.value)
}
/**
* Alter the value stored for `key` in `map` using function `f` using internal hash function.
*
* `f` is invoked with the current value for `k` if it exists,
* or no arguments if no such value exists.
*
* `modify` will always either update or insert a value into the map.
* Returns a map with the modified value. Does not alter `map`.
*/
export function modify_<K, V>(map: HashMap<K, V>, key: K, f: UpdateFn<V>) {
return modifyHash_(map, key, St.hash(key), f)
}
/**
* Alter the value stored for `key` in `map` using function `f` using internal hash function.
*
* `f` is invoked with the current value for `k` if it exists,
* or no arguments if no such value exists.
*
* `modify` will always either update or insert a value into the map.
* Returns a map with the modified value. Does not alter `map`.
*
* @ets_data_first modify_
*/
export function modify<K, V>(key: K, f: UpdateFn<V>) {
return (map: HashMap<K, V>) => modify_(map, key, f)
}
/**
* Store `value` for `key` in `map` using internal hash function.
*/
export function set_<K, V>(map: HashMap<K, V>, key: K, value: V) {
return modify_(map, key, constant(O.some(value)))
}
/**
* Store `value` for `key` in `map` using internal hash function.
*
* @ets_data_first set_
*/
export function set<K, V>(key: K, value: V) {
return (map: HashMap<K, V>) => set_(map, key, value)
}
/**
* Remove the entry for `key` in `map` using internal hash.
*/
export function remove_<K, V>(map: HashMap<K, V>, key: K) {
return modify_(map, key, constant(O.none))
}
/**
* Remove the entry for `key` in `map` using internal hash.
*
* @ets_data_first remove_
*/
export function remove<K>(key: K) {
return <V>(map: HashMap<K, V>) => remove_(map, key)
}
/**
* Mark `map` as mutable.
*/
export function beginMutation<K, V>(map: HashMap<K, V>) {
return new HashMap(true, map.edit + 1, map.root, map.size)
}
/**
* Mark `map` as immutable.
*/
export function endMutation<K, V>(map: HashMap<K, V>) {
map.editable = false
return map
}
/**
* Mutate `map` within the context of `f`.
*
* @ets_data_first mutate_
*/
export function mutate<K, V>(f: (map: HashMap<K, V>) => void) {
return (map: HashMap<K, V>) => mutate_(map, f)
}
/**
* Mutate `map` within the context of `f`.
*/
export function mutate_<K, V>(map: HashMap<K, V>, f: (map: HashMap<K, V>) => void) {
const transient = beginMutation(map)
f(transient)
return endMutation(transient)
}
export type Cont<K, V, A> =
| [
len: number,
children: Node<K, V>[],
i: number,
f: TraversalFn<K, V, A>,
cont: Cont<K, V, A>
]
| undefined
export function applyCont<K, V, A>(cont: Cont<K, V, A>) {
return cont ? visitLazyChildren(cont[0], cont[1], cont[2], cont[3], cont[4]) : O.none
}
export function visitLazyChildren<K, V, A>(
len: number,
children: Node<K, V>[],
i: number,
f: TraversalFn<K, V, A>,
cont: Cont<K, V, A>
): O.Option<VisitResult<K, V, A>> {
while (i < len) {
const child = children[i++]
if (child && !isEmptyNode(child)) {
return visitLazy(child, f, [len, children, i, f, cont])
}
}
return applyCont(cont)
}
export interface VisitResult<K, V, A> {
value: A
cont: Cont<K, V, A>
}
export type TraversalFn<K, V, A> = (node: readonly [K, V]) => A
/**
* Visit each leaf lazily
*/
export function visitLazy<K, V, A>(
node: Node<K, V>,
f: TraversalFn<K, V, A>,
cont: Cont<K, V, A> = undefined
): O.Option<VisitResult<K, V, A>> {
switch (node._tag) {
case "LeafNode": {
return O.isSome(node.value)
? O.some({
value: f(tuple(node.key, node.value.value)),
cont
})
: applyCont(cont)
}
case "CollisionNode":
case "ArrayNode":
case "IndexedNode": {
const children = node.children
return visitLazyChildren(children.length, children, 0, f, cont)
}
default: {
return applyCont(cont)
}
}
}
/**
* Get an IterableIterator of the map keys
*/
export function keys<K, V>(map: HashMap<K, V>): IterableIterator<K> {
return new HashMapIterator(map, ([k]) => k)
}
/**
* Get an IterableIterator of the map values
*/
export function values<K, V>(map: HashMap<K, V>): IterableIterator<V> {
return new HashMapIterator(map, ([, v]) => v)
}
/**
* Update a value if exists
*/
export function update_<K, V>(map: HashMap<K, V>, key: K, f: (v: V) => V) {
return modify_(map, key, O.map(f))
}
/**
* Update a value if exists
*
* @ets_data_first update_
*/
export function update<K, V>(key: K, f: (v: V) => V) {
return (map: HashMap<K, V>) => update_(map, key, f)
}
/**
* Reduce a state over the map entries
*/
export function reduceWithIndex_<K, V, Z>(
map: HashMap<K, V>,
z: Z,
f: (z: Z, k: K, v: V) => Z
): Z {
const root = map.root
if (root._tag === "LeafNode")
return O.isSome(root.value) ? f(z, root.key, root.value.value) : z
if (root._tag === "Empty") {
return z
}
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 && !isEmptyNode(child)) {
if (child._tag === "LeafNode") {
if (O.isSome(child.value)) {
z = f(z, child.key, child.value.value)
}
} else toVisit.push(child.children)
}
}
}
return z
}
/**
* Reduce a state over the map entries
*
* @ets_data_first reduceWithIndex_
*/
export function reduceWithIndex<K, V, Z>(z: Z, f: (z: Z, k: K, v: V) => Z) {
return (map: HashMap<K, V>) => reduceWithIndex_(map, z, f)
}
/**
* Reduce a state over the map entries
*/
export function reduce_<K, V, Z>(map: HashMap<K, V>, z: Z, f: (z: Z, v: V) => Z): Z {
return reduceWithIndex_(map, z, (z, _, v) => f(z, v))
}
/**
* Reduce a state over the map entries
*
* @ets_data_first reduce_
*/
export function reduce<V, Z>(z: Z, f: (z: Z, v: V) => Z) {
return <K>(map: HashMap<K, V>) => reduce_(map, z, f)
}
/**
* Apply f to each element
*/
export function forEachWithIndex_<K, V>(map: HashMap<K, V>, f: (k: K, v: V) => void) {
reduceWithIndex_(map, undefined as void, (_, key, value) => f(key, value))
}
/**
* Apply f to each element
*
* @ets_data_first forEachWithIndex_
*/
export function forEachWithIndex<K, V>(f: (k: K, v: V) => void) {
return (map: HashMap<K, V>) => forEachWithIndex_(map, f)
}
/**
* Apply f to each element
*/
export function forEach_<K, V>(map: HashMap<K, V>, f: (v: V) => void) {
forEachWithIndex_(map, (_, value) => f(value))
}
/**
* Apply f to each element
*
* @ets_data_first forEach_
*/
export function forEach<V>(f: (v: V) => void) {
return <K>(map: HashMap<K, V>) => forEach_(map, f)
}
/**
* Maps over the map entries
*/
export function mapWithIndex_<K, V, A>(map: HashMap<K, V>, f: (k: K, v: V) => A) {
return reduceWithIndex_(map, make<K, A>(), (z, k, v) => set_(z, k, f(k, v)))
}
/**
* Maps over the map entries
*
* @ets_data_first mapWithIndex_
*/
export function mapWithIndex<K, V, A>(f: (k: K, v: V) => A) {
return (map: HashMap<K, V>) => mapWithIndex_(map, f)
}
/**
* Maps over the map entries
*/
export function map_<K, V, A>(map: HashMap<K, V>, f: (v: V) => A) {
return reduceWithIndex_(map, make<K, A>(), (z, k, v) => set_(z, k, f(v)))
}
/**
* Maps over the map entries
*
* @ets_data_first map_
*/
export function map<V, A>(f: (v: V) => A) {
return <K>(map: HashMap<K, V>) => map_(map, f)
}
/**
* Chain over the map entries, the hash and equal of the 2 maps has to be the same
*/
export function chain_<K, V, A>(map: HashMap<K, V>, f: (v: V) => HashMap<K, A>) {
return reduceWithIndex_(map, make<K, A>(), (z, _, v) =>
mutate_(z, (m) => {
forEachWithIndex_(f(v), (_k, _a) => {
set_(m, _k, _a)
})
})
)
}
/**
* Chain over the map entries, the hash and equal of the 2 maps has to be the same
*
* @ets_data_first chain_
*/
export function chain<K, V, A>(f: (v: V) => HashMap<K, A>) {
return (map: HashMap<K, V>) => chain_(map, f)
}
/**
* Chain over the map entries, the hash and equal of the 2 maps has to be the same
*/
export function chainWithIndex_<K, V, A>(
map: HashMap<K, V>,
f: (k: K, v: V) => HashMap<K, A>
) {
return reduceWithIndex_(map, make<K, A>(), (z, k, v) =>
mutate_(z, (m) => {
forEachWithIndex_(f(k, v), (_k, _a) => {
set_(m, _k, _a)
})
})
)
}
/**
* Chain over the map entries, the hash and equal of the 2 maps has to be the same
*
* @ets_data_first chainWithIndex_
*/
export function chainWithIndex<K, V, A>(f: (k: K, v: V) => HashMap<K, A>) {
return (map: HashMap<K, V>) => chainWithIndex_(map, f)
}
/**
* Removes None values
*/
export function compact<K, A>(fa: HashMap<K, O.Option<A>>): HashMap<K, A> {
return filterMapWithIndex_(fa, (_, a) => a)
}
/**
* Filter out None and map
*/
export function filterMapWithIndex_<K, A, B>(
fa: HashMap<K, A>,
f: (k: K, a: A) => O.Option<B>
): HashMap<K, B> {
const m = make<K, B>()
return mutate_(m, (m) => {
for (const [k, a] of fa) {
const o = f(k, a)
if (O.isSome(o)) {
set_(m, k, o.value)
}
}
})
}
/**
* Filter out None and map
*
* @ets_data_first filterMapWithIndex_
*/
export function filterMapWithIndex<K, A, B>(f: (k: K, a: A) => O.Option<B>) {
return (fa: HashMap<K, A>) => filterMapWithIndex_(fa, f)
}
/**
* Filter out None and map
*/
export function filterMap_<E, A, B>(
fa: HashMap<E, A>,
f: (a: A) => O.Option<B>
): HashMap<E, B> {
return filterMapWithIndex_(fa, (_, a) => f(a))
}
/**
* Filter out None and map
*
* @ets_data_first filterMap_
*/
export function filterMap<A, B>(f: (a: A) => O.Option<B>) {
return <E>(fa: HashMap<E, A>) => filterMap_(fa, f)
}
/**
* Filter out by predicate
*/
export function filterWithIndex_<K, A>(
fa: HashMap<K, A>,
p: (k: K, a: A) => boolean
): HashMap<K, A> {
const m = make<K, A>()
return mutate_(m, (m) => {
for (const [k, a] of fa) {
if (p(k, a)) {
set_(m, k, a)
}
}
})
}
/**
* Filter out by predicate
*
* @ets_data_first filterWithIndex_
*/
export function filterWithIndex<K, A>(p: (k: K, a: A) => boolean) {
return (fa: HashMap<K, A>) => filterWithIndex_(fa, p)
}
/**
* Filter out by predicate
*/
export function filter_<K, A, B extends A>(
fa: HashMap<K, A>,
p: Refinement<A, B>
): HashMap<K, B>
export function filter_<K, A>(fa: HashMap<K, A>, p: (a: A) => boolean): HashMap<K, A>
export function filter_<K, A>(fa: HashMap<K, A>, p: (a: A) => boolean): HashMap<K, A> {
return filterWithIndex_(fa, (_, a) => p(a))
}
/**
* Filter out by predicate
*
* @ets_data_first filter_
*/
export function filter<A, B extends A>(
p: Refinement<A, B>
): <K>(fa: HashMap<K, A>) => HashMap<K, A>
export function filter<A>(
p: (a: A) => boolean
): <K>(fa: HashMap<K, A>) => HashMap<K, A> {
return (fa) => filter_(fa, p)
}
/**
* Calculate the number of key/value pairs in a map
*/
export function size<K, V>(map: HashMap<K, V>) {
return map.size
}
/**
* Remove many keys
*/
export function removeMany_<K, V>(self: HashMap<K, V>, ks: Iterable<K>): HashMap<K, V> {
return mutate_(self, (m) => {
for (const k of ks) {
remove_(m, k)
}
})
}
/**
* Remove many keys
*
* @ets_data_first removeMany_
*/
export function removeMany<K>(ks: Iterable<K>) {
return <V>(self: HashMap<K, V>) => removeMany_(self, ks)
}