You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
6.6 KiB
266 lines
6.6 KiB
import type { BlankNode, NamedNode } from "@rdfjs/types";
|
|
import { _getUnderlyingNode } from "../../types.js";
|
|
import type { RawValue } from "../../util/RawObject.js";
|
|
import type { LdSet } from "./LdSet.js";
|
|
import { blankNode } from "@ldo/rdf-utils";
|
|
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
export class BasicLdSet<T extends NonNullable<RawValue> = NonNullable<RawValue>>
|
|
implements LdSet<T>
|
|
{
|
|
private hashMap: Map<string, T>;
|
|
|
|
constructor(values?: Iterable<T> | null) {
|
|
this.hashMap = new Map();
|
|
if (values) {
|
|
for (const value of values) {
|
|
this.add(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
private hashFn(value: T): string {
|
|
if (typeof value !== "object") return value.toString();
|
|
if (value[_getUnderlyingNode]) {
|
|
return (value[_getUnderlyingNode] as NamedNode | BlankNode).value;
|
|
} else if (!value["@id"]) {
|
|
return blankNode().value;
|
|
} else if (typeof value["@id"] === "string") {
|
|
return value["@id"];
|
|
} else {
|
|
return value["@id"].value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ===========================================================================
|
|
* Base Set Functions
|
|
* ===========================================================================
|
|
*/
|
|
|
|
add(value: T): this {
|
|
const key = this.hashFn(value);
|
|
if (!this.hashMap.has(key)) {
|
|
this.hashMap.set(key, value);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
clear(): void {
|
|
this.hashMap.clear();
|
|
}
|
|
|
|
delete(value: T): boolean {
|
|
const key = this.hashFn(value);
|
|
return this.hashMap.delete(key);
|
|
}
|
|
|
|
has(value: T): boolean {
|
|
const key = this.hashFn(value);
|
|
return this.hashMap.has(key);
|
|
}
|
|
|
|
get size(): number {
|
|
return this.hashMap.size;
|
|
}
|
|
|
|
*entries(): IterableIterator<[T, T]> {
|
|
for (const [, value] of this.hashMap.entries()) {
|
|
yield [value, value];
|
|
}
|
|
}
|
|
|
|
keys(): IterableIterator<T> {
|
|
return this.hashMap.values();
|
|
}
|
|
|
|
values(): IterableIterator<T> {
|
|
return this.hashMap.values();
|
|
}
|
|
[Symbol.iterator](): IterableIterator<T> {
|
|
return this.hashMap.values();
|
|
}
|
|
|
|
get [Symbol.toStringTag]() {
|
|
// TODO: Change this to be human readable.
|
|
return "BasicLdSet";
|
|
}
|
|
|
|
/**
|
|
* ===========================================================================
|
|
* Array Functions
|
|
* ===========================================================================
|
|
*/
|
|
|
|
every<S extends T>(
|
|
predicate: (value: T, set: LdSet<T>) => value is S,
|
|
thisArg?: any,
|
|
): this is LdSet<S>;
|
|
every(
|
|
predicate: (value: T, set: LdSet<T>) => unknown,
|
|
thisArg?: any,
|
|
): boolean;
|
|
every(predicate: (value: T, set: LdSet<T>) => any, thisArg?: any): boolean {
|
|
for (const value of this) {
|
|
if (!predicate.call(thisArg, value, this)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
some(
|
|
predicate: (value: T, set: LdSet<T>) => unknown,
|
|
thisArg?: any,
|
|
): boolean {
|
|
for (const value of this) {
|
|
if (predicate.call(thisArg, value, this)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
forEach(
|
|
callbackfn: (value: T, value2: T, set: LdSet<T>) => void,
|
|
thisArg?: any,
|
|
): void {
|
|
for (const value of this) {
|
|
callbackfn.call(thisArg, value, value, this);
|
|
}
|
|
}
|
|
|
|
map<U>(callbackfn: (value: T, set: LdSet<T>) => U, thisArg?: any): U[] {
|
|
const returnValues: U[] = [];
|
|
for (const value of this) {
|
|
returnValues.push(callbackfn.call(thisArg, value, this));
|
|
}
|
|
return returnValues;
|
|
}
|
|
|
|
filter<S extends T>(
|
|
predicate: (value: T, set: LdSet<T>) => value is S,
|
|
thisArg?: any,
|
|
): LdSet<S>;
|
|
filter(
|
|
predicate: (value: T, set: LdSet<T>) => unknown,
|
|
thisArg?: any,
|
|
): LdSet<T>;
|
|
filter(
|
|
predicate: (value: T, set: LdSet<T>) => any,
|
|
thisArg?: unknown,
|
|
): LdSet<T> {
|
|
const newSet = new BasicLdSet<T>();
|
|
for (const value of this) {
|
|
if (predicate.call(thisArg, value, this)) newSet.add(value);
|
|
}
|
|
return newSet;
|
|
}
|
|
|
|
reduce(
|
|
callbackfn: (previousValue: T, currentValue: T, set: LdSet<T>) => T,
|
|
): T;
|
|
reduce(
|
|
callbackfn: (previousValue: T, currentValue: T, set: LdSet<T>) => T,
|
|
initialValue: T,
|
|
): T;
|
|
reduce<U>(
|
|
callbackfn: (previousValue: U, currentValue: T, set: LdSet<T>) => U,
|
|
initialValue: U,
|
|
): U;
|
|
reduce(callbackfn: any, initialValue?: any): any {
|
|
const iterator = this[Symbol.iterator]();
|
|
let accumulator;
|
|
|
|
if (initialValue === undefined) {
|
|
const first = iterator.next();
|
|
if (first.done) {
|
|
throw new TypeError("Reduce of empty collection with no initial value");
|
|
}
|
|
accumulator = first.value;
|
|
} else {
|
|
accumulator = initialValue;
|
|
}
|
|
|
|
let result = iterator.next();
|
|
while (!result.done) {
|
|
accumulator = callbackfn(accumulator, result.value, this);
|
|
result = iterator.next();
|
|
}
|
|
|
|
return accumulator;
|
|
}
|
|
|
|
toArray(): T[] {
|
|
const arr: T[] = [];
|
|
this.forEach((value) => arr.push(value));
|
|
return arr;
|
|
}
|
|
|
|
toJSON(): T[] {
|
|
return this.toArray();
|
|
}
|
|
|
|
/**
|
|
* ===========================================================================
|
|
* Set Methods
|
|
* ===========================================================================
|
|
*/
|
|
|
|
difference(other: Set<T>): LdSet<T> {
|
|
return this.filter((value) => !other.has(value));
|
|
}
|
|
|
|
intersection(other: Set<T>): LdSet<T> {
|
|
const newSet = new BasicLdSet<T>();
|
|
const iteratingSet = this.size < other.size ? this : other;
|
|
const comparingSet = this.size < other.size ? other : this;
|
|
for (const value of iteratingSet) {
|
|
if (comparingSet.has(value)) {
|
|
newSet.add(value);
|
|
}
|
|
}
|
|
return newSet;
|
|
}
|
|
|
|
isDisjointFrom(other: Set<T>): boolean {
|
|
const iteratingSet = this.size < other.size ? this : other;
|
|
const comparingSet = this.size < other.size ? other : this;
|
|
for (const value of iteratingSet) {
|
|
if (comparingSet.has(value)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
isSubsetOf(other: Set<T>): boolean {
|
|
if (this.size > other.size) return false;
|
|
for (const value of this) {
|
|
if (!other.has(value)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
isSupersetOf(other: Set<T>): boolean {
|
|
if (this.size < other.size) return false;
|
|
for (const value of other) {
|
|
if (!this.has(value)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
symmetricDifference(other: Set<T>): LdSet<T> {
|
|
const newSet = new BasicLdSet<T>();
|
|
this.forEach((value) => newSet.add(value));
|
|
other.forEach((value) => {
|
|
if (newSet.has(value)) {
|
|
newSet.delete(value);
|
|
} else {
|
|
newSet.add(value);
|
|
}
|
|
});
|
|
return newSet;
|
|
}
|
|
|
|
union(other: Set<T>): LdSet<T> {
|
|
const newSet = new BasicLdSet<T>();
|
|
this.forEach((value) => newSet.add(value));
|
|
other.forEach((value) => newSet.add(value));
|
|
return newSet;
|
|
}
|
|
}
|
|
|