import * as Equal from "@effect/data/Equal"
import * as Dual from "@effect/data/Function"
import * as Hash from "@effect/data/Hash"
import type { HashMap } from "@effect/data/HashMap"
import type * as HS from "@effect/data/HashSet"
import { NodeInspectSymbol, toJSON, toString } from "@effect/data/Inspectable"
import * as HM from "@effect/data/internal/HashMap"
import { pipeArguments } from "@effect/data/Pipeable"
import type { Predicate, Refinement } from "@effect/data/Predicate"
import { isObject } from "@effect/data/Predicate"
/** @internal */
export const HashSetTypeId: HS.TypeId = Symbol.for("@effect/data/HashSet") as HS.TypeId
/** @internal */
export interface HashSetImpl extends HS.HashSet {
readonly _keyMap: HashMap
}
const HashSetProto: Omit, "_keyMap"> = {
[HashSetTypeId]: HashSetTypeId,
[Symbol.iterator](this: HashSetImpl): Iterator {
return HM.keys(this._keyMap)
},
[Hash.symbol](this: HashSetImpl): number {
return Hash.combine(Hash.hash(this._keyMap))(Hash.hash("HashSet"))
},
[Equal.symbol](this: HashSetImpl, that: unknown): boolean {
if (isHashSet(that)) {
return (
HM.size(this._keyMap) === HM.size((that as HashSetImpl)._keyMap) &&
Equal.equals(this._keyMap, (that as HashSetImpl)._keyMap)
)
}
return false
},
toString() {
return toString(this.toJSON())
},
toJSON() {
return {
_id: "HashSet",
values: Array.from(this).map(toJSON)
}
},
[NodeInspectSymbol]() {
return this.toJSON()
},
pipe() {
return pipeArguments(this, arguments)
}
}
/** @internal */
export const makeImpl = (keyMap: HashMap): HashSetImpl => {
const set = Object.create(HashSetProto)
set._keyMap = keyMap
return set
}
/** @internal */
export const isHashSet: {
(u: Iterable): u is HS.HashSet
(u: unknown): u is HS.HashSet
} = (u: unknown): u is HS.HashSet => isObject(u) && HashSetTypeId in u
const _empty = makeImpl(HM.empty())
/** @internal */
export const empty = (): HS.HashSet => _empty
/** @internal */
export const fromIterable = (elements: Iterable): HS.HashSet => {
const set = beginMutation(empty())
for (const value of elements) {
add(set, value)
}
return endMutation(set)
}
/** @internal */
export const make = >(...elements: As): HS.HashSet => {
const set = beginMutation(empty())
for (const value of elements) {
add(set, value)
}
return endMutation(set)
}
/** @internal */
export const has = Dual.dual<
(value: A) => (self: HS.HashSet) => boolean,
(self: HS.HashSet, value: A) => boolean
>(2, (self: HS.HashSet, value: A) => HM.has((self as HashSetImpl)._keyMap, value))
/** @internal */
export const some = Dual.dual<
(f: Predicate) => (self: HS.HashSet) => boolean,
(self: HS.HashSet, f: Predicate) => boolean
>(2, (self, f) => {
let found = false
for (const value of self) {
found = f(value)
if (found) {
break
}
}
return found
})
/** @internal */
export const every: {
(refinement: Refinement): (self: HS.HashSet) => self is HS.HashSet
(predicate: Predicate): (self: HS.HashSet) => boolean
(self: HS.HashSet, refinement: Refinement): self is HS.HashSet
(self: HS.HashSet, predicate: Predicate): boolean
} = Dual.dual(
2,
(self: HS.HashSet, refinement: Refinement): self is HS.HashSet =>
!some(self, (a) => !refinement(a))
)
/** @internal */
export const isSubset = Dual.dual<
(that: HS.HashSet) => (self: HS.HashSet) => boolean,
(self: HS.HashSet, that: HS.HashSet) => boolean
>(2, (self, that) => every(self, (value) => has(that, value)))
/** @internal */
export const values = (self: HS.HashSet): IterableIterator => HM.keys((self as HashSetImpl)._keyMap)
/** @internal */
export const size = (self: HS.HashSet): number => HM.size((self as HashSetImpl)._keyMap)
/** @internal */
export const beginMutation = (self: HS.HashSet): HS.HashSet =>
makeImpl(HM.beginMutation((self as HashSetImpl)._keyMap))
/** @internal */
export const endMutation = (self: HS.HashSet): HS.HashSet => {
;((self as HashSetImpl)._keyMap as HM.HashMapImpl)._editable = false
return self
}
/** @internal */
export const mutate = Dual.dual<
(f: (set: HS.HashSet) => void) => (self: HS.HashSet) => HS.HashSet,
(self: HS.HashSet, f: (set: HS.HashSet) => void) => HS.HashSet
>(2, (self, f) => {
const transient = beginMutation(self)
f(transient)
return endMutation(transient)
})
/** @internal */
export const add = Dual.dual<
(value: A) => (self: HS.HashSet) => HS.HashSet,
(self: HS.HashSet, value: A) => HS.HashSet
>(
2,
(self: HS.HashSet, value: A) =>
((self as HashSetImpl)._keyMap as HM.HashMapImpl)._editable
? (HM.set(value as A, true as unknown)((self as HashSetImpl)._keyMap), self)
: makeImpl(HM.set(value as A, true as unknown)((self as HashSetImpl)._keyMap))
)
/** @internal */
export const remove = Dual.dual<
(value: A) => (self: HS.HashSet) => HS.HashSet,
(self: HS.HashSet, value: A) => HS.HashSet
>(
2,
(self: HS.HashSet, value: A) =>
(((self as HashSetImpl)._keyMap) as HM.HashMapImpl)._editable
? (HM.remove(value)((self as HashSetImpl)._keyMap), self)
: makeImpl(HM.remove(value)((self as HashSetImpl)._keyMap))
)
/** @internal */
export const difference = Dual.dual<
(that: Iterable) => (self: HS.HashSet) => HS.HashSet,
(self: HS.HashSet, that: Iterable) => HS.HashSet
>(2, (self, that) =>
mutate(self, (set) => {
for (const value of that) {
remove(set, value)
}
}))
/** @internal */
export const intersection = Dual.dual<
(that: Iterable) => (self: HS.HashSet) => HS.HashSet,
(self: HS.HashSet, that: Iterable) => HS.HashSet
>(2, (self, that) =>
mutate(empty(), (set) => {
for (const value of that) {
if (has(value)(self)) {
add(value)(set)
}
}
}))
/** @internal */
export const union = Dual.dual<
(that: Iterable) => (self: HS.HashSet) => HS.HashSet,
(self: HS.HashSet, that: Iterable) => HS.HashSet
>(2, (self, that) =>
mutate(empty(), (set) => {
forEach(self, (value) => add(set, value))
for (const value of that) {
add(set, value)
}
}))
/** @internal */
export const toggle = Dual.dual<
(value: A) => (self: HS.HashSet) => HS.HashSet,
(self: HS.HashSet, value: A) => HS.HashSet
>(2, (self, value) => has(self, value) ? remove(self, value) : add(self, value))
/** @internal */
export const map = Dual.dual<
(f: (a: A) => B) => (self: HS.HashSet) => HS.HashSet,
(self: HS.HashSet, f: (a: A) => B) => HS.HashSet
>(2, (self, f) =>
mutate(empty(), (set) => {
forEach(self, (a) => {
const b = f(a)
if (!has(set, b)) {
add(set, b)
}
})
}))
/** @internal */
export const flatMap = Dual.dual<
(f: (a: A) => Iterable) => (self: HS.HashSet) => HS.HashSet,
(self: HS.HashSet, f: (a: A) => Iterable) => HS.HashSet
>(2, (self, f) =>
mutate(empty(), (set) => {
forEach(self, (a) => {
for (const b of f(a)) {
if (!has(set, b)) {
add(set, b)
}
}
})
}))
/** @internal */
export const forEach = Dual.dual<
(f: (value: A) => void) => (self: HS.HashSet) => void,
(self: HS.HashSet, f: (value: A) => void) => void
>(2, (self: HS.HashSet, f: (value: A) => void) =>
HM.forEach(
(self as HashSetImpl)._keyMap,
(_, k) => f(k)
))
/** @internal */
export const reduce = Dual.dual<
(zero: Z, f: (accumulator: Z, value: A) => Z) => (self: HS.HashSet) => Z,
(self: HS.HashSet, zero: Z, f: (accumulator: Z, value: A) => Z) => Z
>(3, (self: HS.HashSet, zero: Z, f: (accumulator: Z, value: A) => Z) =>
HM.reduce(
(self as HashSetImpl)._keyMap,
zero,
(z, _, a) => f(z, a)
))
/** @internal */
export const filter = Dual.dual<
{
(f: Refinement): (self: HS.HashSet) => HS.HashSet
(f: Predicate): (self: HS.HashSet) => HS.HashSet
},
{
(self: HS.HashSet, f: Refinement): HS.HashSet
(self: HS.HashSet, f: Predicate): HS.HashSet
}
>(2, (self: HS.HashSet, f: Predicate) => {
return mutate(empty(), (set) => {
const iterator = values(self)
let next: IteratorResult
while (!(next = iterator.next()).done) {
const value = next.value
if (f(value)) {
add(set, value)
}
}
})
})
/** @internal */
export const partition: {
(
refinement: Refinement
): (self: HS.HashSet) => [HS.HashSet>, HS.HashSet]
(predicate: (a: A) => boolean): (self: HS.HashSet) => [HS.HashSet, HS.HashSet]
(
self: HS.HashSet,
refinement: Refinement
): [HS.HashSet>, HS.HashSet]
(self: HS.HashSet, predicate: (a: A) => boolean): [HS.HashSet, HS.HashSet]
} = Dual.dual(2, (self: HS.HashSet, f: Predicate) => {
const iterator = values(self)
let next: IteratorResult
const right = beginMutation(empty())
const left = beginMutation(empty())
while (!(next = iterator.next()).done) {
const value = next.value
if (f(value)) {
add(right, value)
} else {
add(left, value)
}
}
return [endMutation(left), endMutation(right)] as const
})