export class Set {
public static [Symbol.species] = Set;
public [Symbol.toStringTag] = "Set";
public size = 0;
private firstKey: T | undefined;
private lastKey: T | undefined;
private nextKey = new LuaTable();
private previousKey = new LuaTable();
constructor(values?: Iterable | T[]) {
if (values === undefined) return;
const iterable = values as Iterable;
if (iterable[Symbol.iterator]) {
// Iterate manually because Set is compiled with ES5 which doesn't support Iterables in for...of
const iterator = iterable[Symbol.iterator]();
while (true) {
const result = iterator.next();
if (result.done) {
break;
}
this.add(result.value);
}
} else {
const array = values as T[];
for (const value of array) {
this.add(value);
}
}
}
public add(value: T): Set {
const isNewValue = !this.has(value);
if (isNewValue) {
this.size++;
}
// Do order bookkeeping
if (this.firstKey === undefined) {
this.firstKey = value;
this.lastKey = value;
} else if (isNewValue) {
this.nextKey.set(this.lastKey!, value);
this.previousKey.set(value, this.lastKey!);
this.lastKey = value;
}
return this;
}
public clear(): void {
this.nextKey = new LuaTable();
this.previousKey = new LuaTable();
this.firstKey = undefined;
this.lastKey = undefined;
this.size = 0;
}
public delete(value: T): boolean {
const contains = this.has(value);
if (contains) {
this.size--;
// Do order bookkeeping
const next = this.nextKey.get(value);
const previous = this.previousKey.get(value);
if (next !== undefined && previous !== undefined) {
this.nextKey.set(previous, next);
this.previousKey.set(next, previous);
} else if (next !== undefined) {
this.firstKey = next;
this.previousKey.set(next, undefined!);
} else if (previous !== undefined) {
this.lastKey = previous;
this.nextKey.set(previous, undefined!);
} else {
this.firstKey = undefined;
this.lastKey = undefined;
}
this.nextKey.set(value, undefined!);
this.previousKey.set(value, undefined!);
}
return contains;
}
public forEach(callback: (value: T, key: T, set: Set) => any): void {
for (const key of this.keys()) {
callback(key, key, this);
}
}
public has(value: T): boolean {
return this.nextKey.get(value) !== undefined || this.lastKey === value;
}
public [Symbol.iterator](): IterableIterator {
return this.values();
}
public entries(): IterableIterator<[T, T]> {
const getFirstKey = () => this.firstKey;
const nextKey = this.nextKey;
let key: T | undefined;
let started = false;
return {
[Symbol.iterator](): IterableIterator<[T, T]> {
return this;
},
next(): IteratorResult<[T, T]> {
if (!started) {
started = true;
key = getFirstKey();
} else {
key = nextKey.get(key!);
}
return { done: !key, value: [key!, key!] as [T, T] };
},
};
}
public keys(): IterableIterator {
const getFirstKey = () => this.firstKey;
const nextKey = this.nextKey;
let key: T | undefined;
let started = false;
return {
[Symbol.iterator](): IterableIterator {
return this;
},
next(): IteratorResult {
if (!started) {
started = true;
key = getFirstKey();
} else {
key = nextKey.get(key!);
}
return { done: !key, value: key! };
},
};
}
public values(): IterableIterator {
const getFirstKey = () => this.firstKey;
const nextKey = this.nextKey;
let key: T | undefined;
let started = false;
return {
[Symbol.iterator](): IterableIterator {
return this;
},
next(): IteratorResult {
if (!started) {
started = true;
key = getFirstKey();
} else {
key = nextKey.get(key!);
}
return { done: !key, value: key! };
},
};
}
/**
* @returns a new Set containing all the elements in this Set and also all the elements in the argument.
*/
public union(other: ReadonlySet): Set {
const result = new Set(this);
for (const item of other) {
result.add(item);
}
return result;
}
/**
* @returns a new Set containing all the elements which are both in this Set and in the argument.
*/
public intersection(other: ReadonlySet) {
const result = new Set();
for (const item of this) {
if (other.has(item)) {
result.add(item);
}
}
return result;
}
/**
* @returns a new Set containing all the elements in this Set which are not also in the argument.
*/
public difference(other: ReadonlySet): Set {
const result = new Set(this);
for (const item of other) {
result.delete(item);
}
return result;
}
/**
* @returns a new Set containing all the elements which are in either this Set or in the argument, but not in both.
*/
public symmetricDifference(other: ReadonlySet): Set {
const result = new Set(this);
for (const item of other) {
if (this.has(item)) {
result.delete(item);
} else {
result.add(item);
}
}
return result;
}
/**
* @returns a boolean indicating whether all the elements in this Set are also in the argument.
*/
public isSubsetOf(other: ReadonlySet): boolean {
for (const item of this) {
if (!other.has(item)) {
return false;
}
}
return true;
}
/**
* @returns a boolean indicating whether all the elements in the argument are also in this Set.
*/
public isSupersetOf(other: ReadonlySet): boolean {
for (const item of other) {
if (!this.has(item as T)) {
return false;
}
}
return true;
}
/**
* @returns a boolean indicating whether this Set has no elements in common with the argument.
*/
public isDisjointFrom(other: ReadonlySetLike): boolean {
for (const item of this) {
if (other.has(item)) {
return false;
}
}
return true;
}
}