import * as Equal from "../Equal.js" import { dual, identity, pipe } from "../Function.js" import * as Hash from "../Hash.js" import { format, NodeInspectSymbol, toJSON } from "../Inspectable.js" import * as Option from "../Option.js" import type * as Ordering from "../Ordering.js" import { pipeArguments } from "../Pipeable.js" import { hasProperty } from "../Predicate.js" import type * as TR from "../Trie.js" import type { NoInfer } from "../Types.js" const TrieSymbolKey = "effect/Trie" /** @internal */ export const TrieTypeId: TR.TypeId = Symbol.for(TrieSymbolKey) as TR.TypeId type TraversalMap = (k: K, v: V) => A type TraversalFilter = (k: K, v: V) => boolean /** @internal */ export interface TrieImpl extends TR.Trie { readonly _root: Node | undefined readonly _count: number } const trieVariance = { /* c8 ignore next */ _Value: (_: never) => _ } const TrieProto: TR.Trie = { [TrieTypeId]: trieVariance, [Symbol.iterator](this: TrieImpl): Iterator<[string, V]> { return new TrieIterator(this, (k, v) => [k, v], () => true) }, [Hash.symbol](this: TR.Trie): number { let hash = Hash.hash(TrieSymbolKey) for (const item of this) { hash ^= pipe(Hash.hash(item[0]), Hash.combine(Hash.hash(item[1]))) } return Hash.cached(this, hash) }, [Equal.symbol](this: TrieImpl, that: unknown): boolean { if (isTrie(that)) { const entries = Array.from(that) return Array.from(this).every((itemSelf, i) => { const itemThat = entries[i] return Equal.equals(itemSelf[0], itemThat[0]) && Equal.equals(itemSelf[1], itemThat[1]) }) } return false }, toString() { return format(this.toJSON()) }, toJSON() { return { _id: "Trie", values: Array.from(this).map(toJSON) } }, [NodeInspectSymbol]() { return this.toJSON() }, pipe() { return pipeArguments(this, arguments) } } const makeImpl = (root: Node | undefined): TrieImpl => { const trie = Object.create(TrieProto) trie._root = root trie._count = root?.count ?? 0 return trie } class TrieIterator implements IterableIterator { stack: Array<[Node, string, boolean]> = [] constructor( readonly trie: TrieImpl, readonly f: TraversalMap, readonly filter: TraversalFilter ) { const root = trie._root !== undefined ? trie._root : undefined if (root !== undefined) { this.stack.push([root, "", false]) } } next(): IteratorResult { while (this.stack.length > 0) { const [node, keyString, isAdded] = this.stack.pop()! if (isAdded) { const value = node.value if (value !== undefined) { const key = keyString + node.key if (this.filter(key, value)) { return { done: false, value: this.f(key, value) } } } } else { this.addToStack(node, keyString) } } return { done: true, value: undefined } } addToStack(node: Node, keyString: string) { if (node.right !== undefined) { this.stack.push([node.right, keyString, false]) } if (node.mid !== undefined) { this.stack.push([node.mid, keyString + node.key, false]) } this.stack.push([node, keyString, true]) if (node.left !== undefined) { this.stack.push([node.left, keyString, false]) } } [Symbol.iterator](): IterableIterator { return new TrieIterator(this.trie, this.f, this.filter) } } /** @internal */ export const isTrie: { (u: Iterable): u is TR.Trie (u: unknown): u is TR.Trie } = (u: unknown): u is TR.Trie => hasProperty(u, TrieTypeId) /** @internal */ export const empty = (): TR.Trie => makeImpl(undefined) /** @internal */ export const fromIterable = (entries: Iterable) => { let trie = empty() for (const [key, value] of entries) { trie = insert(trie, key, value) } return trie } /** @internal */ export const make = >(...entries: Entries): TR.Trie< Entries[number] extends readonly [any, infer V] ? V : never > => { return fromIterable(entries) } /** @internal */ export const insert = dual< (key: string, value: V) => (self: TR.Trie) => TR.Trie, (self: TR.Trie, key: string, value: V) => TR.Trie >(3, (self: TR.Trie, key: string, value: V) => { if (key.length === 0) return self // -1:left | 0:mid | 1:right const dStack: Array = [] const nStack: Array> = [] let n: Node = (self as TrieImpl)._root ?? { key: key[0], count: 0 } const count = n.count + 1 let cIndex = 0 while (cIndex < key.length) { const c = key[cIndex] nStack.push(n) if (c > n.key) { dStack.push(1) if (n.right === undefined) { n = { key: c, count } } else { n = n.right } } else if (c < n.key) { dStack.push(-1) if (n.left === undefined) { n = { key: c, count } } else { n = n.left } } else { if (cIndex === key.length - 1) { n.value = value } else if (n.mid === undefined) { dStack.push(0) n = { key: key[cIndex + 1], count } } else { dStack.push(0) n = n.mid } cIndex += 1 } } // Rebuild path to leaf node (Path-copying immutability) for (let s = nStack.length - 2; s >= 0; --s) { const n2 = nStack[s] const d = dStack[s] if (d === -1) { // left nStack[s] = { key: n2.key, count, value: n2.value, left: nStack[s + 1], mid: n2.mid, right: n2.right } } else if (d === 1) { // right nStack[s] = { key: n2.key, count, value: n2.value, left: n2.left, mid: n2.mid, right: nStack[s + 1] } } else { // mid nStack[s] = { key: n2.key, count, value: n2.value, left: n2.left, mid: nStack[s + 1], right: n2.right } } } nStack[0].count = count return makeImpl(nStack[0]) }) /** @internal */ export const size = (self: TR.Trie): number => (self as TrieImpl)._root?.count ?? 0 /** @internal */ export const isEmpty = (self: TR.Trie): boolean => size(self) === 0 /** @internal */ export const keys = (self: TR.Trie): IterableIterator => new TrieIterator(self as TrieImpl, (key) => key, () => true) /** @internal */ export const values = (self: TR.Trie): IterableIterator => new TrieIterator(self as TrieImpl, (_, value) => value, () => true) /** @internal */ export const entries = (self: TR.Trie): IterableIterator<[string, V]> => new TrieIterator(self as TrieImpl, (key, value) => [key, value], () => true) /** @internal */ export const reduce = dual< ( zero: Z, f: (accumulator: Z, value: V, key: string) => Z ) => (self: TR.Trie) => Z, (self: TR.Trie, zero: Z, f: (accumulator: Z, value: V, key: string) => Z) => Z >(3, (self, zero, f) => { let accumulator = zero for (const entry of self) { accumulator = f(accumulator, entry[1], entry[0]) } return accumulator }) /** @internal */ export const map = dual< (f: (value: V, key: string) => A) => (self: TR.Trie) => TR.Trie, (self: TR.Trie, f: (value: V, key: string) => A) => TR.Trie >(2, (self, f) => reduce( self, empty(), (trie, value, key) => insert(trie, key, f(value, key)) )) /** @internal */ export const filter: { (f: (a: NoInfer, k: string) => a is B): (self: TR.Trie) => TR.Trie (f: (a: NoInfer, k: string) => boolean): (self: TR.Trie) => TR.Trie (self: TR.Trie, f: (a: A, k: string) => a is B): TR.Trie (self: TR.Trie, f: (a: A, k: string) => boolean): TR.Trie } = dual( 2, (self: TR.Trie, f: (a: A, k: string) => boolean): TR.Trie => reduce( self, empty(), (trie, value, key) => f(value, key) ? insert(trie, key, value) : trie ) ) /** @internal */ export const filterMap = dual< ( f: (value: A, key: string) => Option.Option ) => (self: TR.Trie) => TR.Trie, (self: TR.Trie, f: (value: A, key: string) => Option.Option) => TR.Trie >(2, (self, f) => reduce( self, empty(), (trie, value, key) => { const option = f(value, key) return Option.isSome(option) ? insert(trie, key, option.value) : trie } )) /** @internal */ export const compact = (self: TR.Trie>) => filterMap(self, identity) /** @internal */ export const forEach = dual< (f: (value: V, key: string) => void) => (self: TR.Trie) => void, (self: TR.Trie, f: (value: V, key: string) => void) => void >(2, (self, f) => reduce(self, void 0 as void, (_, value, key) => f(value, key))) /** @internal */ export const keysWithPrefix = dual< (prefix: string) => (self: TR.Trie) => IterableIterator, (self: TR.Trie, prefix: string) => IterableIterator >( 2, (self: TR.Trie, prefix: string): IterableIterator => new TrieIterator(self as TrieImpl, (key) => key, (key) => key.startsWith(prefix)) ) /** @internal */ export const valuesWithPrefix = dual< (prefix: string) => (self: TR.Trie) => IterableIterator, (self: TR.Trie, prefix: string) => IterableIterator >( 2, (self: TR.Trie, prefix: string): IterableIterator => new TrieIterator(self as TrieImpl, (_, value) => value, (key) => key.startsWith(prefix)) ) /** @internal */ export const entriesWithPrefix = dual< (prefix: string) => (self: TR.Trie) => IterableIterator<[string, V]>, (self: TR.Trie, prefix: string) => IterableIterator<[string, V]> >( 2, (self: TR.Trie, prefix: string): IterableIterator<[string, V]> => new TrieIterator(self as TrieImpl, (key, value) => [key, value], (key) => key.startsWith(prefix)) ) /** @internal */ export const toEntriesWithPrefix = dual< (prefix: string) => (self: TR.Trie) => Array<[string, V]>, (self: TR.Trie, prefix: string) => Array<[string, V]> >( 2, (self: TR.Trie, prefix: string): Array<[string, V]> => Array.from(entriesWithPrefix(self, prefix)) ) /** @internal */ export const get = dual< (key: string) => (self: TR.Trie) => Option.Option, (self: TR.Trie, key: string) => Option.Option >( 2, (self: TR.Trie, key: string) => { let n: Node | undefined = (self as TrieImpl)._root if (n === undefined || key.length === 0) return Option.none() let cIndex = 0 while (cIndex < key.length) { const c = key[cIndex] if (c > n.key) { if (n.right === undefined) { return Option.none() } else { n = n.right } } else if (c < n.key) { if (n.left === undefined) { return Option.none() } else { n = n.left } } else { if (cIndex === key.length - 1) { return Option.fromNullable(n.value) } else { if (n.mid === undefined) { return Option.none() } else { n = n.mid cIndex += 1 } } } } return Option.none() } ) /** @internal */ export const has = dual< (key: string) => (self: TR.Trie) => boolean, (self: TR.Trie, key: string) => boolean >(2, (self, key) => Option.isSome(get(self, key))) /** @internal */ export const unsafeGet = dual< (key: string) => (self: TR.Trie) => V, (self: TR.Trie, key: string) => V >(2, (self, key) => { const element = get(self, key) if (Option.isNone(element)) { throw new Error("Expected trie to contain key") } return element.value }) /** @internal */ export const remove = dual< (key: string) => (self: TR.Trie) => TR.Trie, (self: TR.Trie, key: string) => TR.Trie >( 2, (self: TR.Trie, key: string) => { let n: Node | undefined = (self as TrieImpl)._root if (n === undefined || key.length === 0) return self const count = n.count - 1 // -1:left | 0:mid | 1:right const dStack: Array = [] const nStack: Array> = [] let cIndex = 0 while (cIndex < key.length) { const c = key[cIndex] if (c > n.key) { if (n.right === undefined) { return self } else { nStack.push(n) dStack.push(1) n = n.right } } else if (c < n.key) { if (n.left === undefined) { return self } else { nStack.push(n) dStack.push(-1) n = n.left } } else { if (cIndex === key.length - 1) { if (n.value !== undefined) { nStack.push(n) dStack.push(0) cIndex += 1 } else { return self } } else { if (n.mid === undefined) { return self } else { nStack.push(n) dStack.push(0) n = n.mid cIndex += 1 } } } } const removeNode = nStack[nStack.length - 1] nStack[nStack.length - 1] = { key: removeNode.key, count, left: removeNode.left, mid: removeNode.mid, right: removeNode.right } // Rebuild path to leaf node (Path-copying immutability) for (let s = nStack.length - 2; s >= 0; --s) { const n2 = nStack[s] const d = dStack[s] const child = nStack[s + 1] const nc = child.left === undefined && child.mid === undefined && child.right === undefined ? undefined : child if (d === -1) { // left nStack[s] = { key: n2.key, count, value: n2.value, left: nc, mid: n2.mid, right: n2.right } } else if (d === 1) { // right nStack[s] = { key: n2.key, count, value: n2.value, left: n2.left, mid: n2.mid, right: nc } } else { // mid nStack[s] = { key: n2.key, count, value: n2.value, left: n2.left, mid: nc, right: n2.right } } } nStack[0].count = count return makeImpl(nStack[0]) } ) /** @internal */ export const removeMany = dual< (keys: Iterable) => (self: TR.Trie) => TR.Trie, (self: TR.Trie, keys: Iterable) => TR.Trie >(2, (self, keys) => { let trie = self for (const key of keys) { trie = remove(key)(trie) } return trie }) /** @internal */ export const insertMany = dual< (iter: Iterable<[string, V]>) => (self: TR.Trie) => TR.Trie, (self: TR.Trie, iter: Iterable<[string, V]>) => TR.Trie >(2, (self, iter) => { let trie = self for (const [key, value] of iter) { trie = insert(key, value)(trie) } return trie }) /** @internal */ export const modify = dual< (key: string, f: (v: V) => V) => (self: TR.Trie) => TR.Trie, (self: TR.Trie, key: string, f: (v: V) => V) => TR.Trie >( 3, (self: TR.Trie, key: string, f: (v: V) => V): TR.Trie => { let n: Node | undefined = (self as TrieImpl)._root if (n === undefined || key.length === 0) return self // -1:left | 0:mid | 1:right const dStack: Array = [] const nStack: Array> = [] let cIndex = 0 while (cIndex < key.length) { const c = key[cIndex] if (c > n.key) { if (n.right === undefined) { return self } else { nStack.push(n) dStack.push(1) n = n.right } } else if (c < n.key) { if (n.left === undefined) { return self } else { nStack.push(n) dStack.push(-1) n = n.left } } else { if (cIndex === key.length - 1) { if (n.value !== undefined) { nStack.push(n) dStack.push(0) cIndex += 1 } else { return self } } else { if (n.mid === undefined) { return self } else { nStack.push(n) dStack.push(0) n = n.mid cIndex += 1 } } } } const updateNode = nStack[nStack.length - 1] if (updateNode.value === undefined) { return self } nStack[nStack.length - 1] = { key: updateNode.key, count: updateNode.count, value: f(updateNode.value), // Update left: updateNode.left, mid: updateNode.mid, right: updateNode.right } // Rebuild path to leaf node (Path-copying immutability) for (let s = nStack.length - 2; s >= 0; --s) { const n2 = nStack[s] const d = dStack[s] const child = nStack[s + 1] if (d === -1) { // left nStack[s] = { key: n2.key, count: n2.count, value: n2.value, left: child, mid: n2.mid, right: n2.right } } else if (d === 1) { // right nStack[s] = { key: n2.key, count: n2.count, value: n2.value, left: n2.left, mid: n2.mid, right: child } } else { // mid nStack[s] = { key: n2.key, count: n2.count, value: n2.value, left: n2.left, mid: child, right: n2.right } } } return makeImpl(nStack[0]) } ) /** @internal */ export const longestPrefixOf = dual< (key: string) => (self: TR.Trie) => Option.Option<[string, V]>, (self: TR.Trie, key: string) => Option.Option<[string, V]> >( 2, (self: TR.Trie, key: string) => { let n: Node | undefined = (self as TrieImpl)._root if (n === undefined || key.length === 0) return Option.none() let longestPrefixNode: [string, V] | undefined = undefined let cIndex = 0 while (cIndex < key.length) { const c = key[cIndex] if (n.value !== undefined) { longestPrefixNode = [key.slice(0, cIndex + 1), n.value] } if (c > n.key) { if (n.right === undefined) { break } else { n = n.right } } else if (c < n.key) { if (n.left === undefined) { break } else { n = n.left } } else { if (n.mid === undefined) { break } else { n = n.mid cIndex += 1 } } } return Option.fromNullable(longestPrefixNode) } ) interface Node { key: string count: number value?: V | undefined left?: Node | undefined mid?: Node | undefined right?: Node | undefined }