Skip to content

Instantly share code, notes, and snippets.

@jabney
Last active May 9, 2022 14:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jabney/a2653ad256a7de4e35129517c88b5512 to your computer and use it in GitHub Desktop.
Save jabney/a2653ad256a7de4e35129517c88b5512 to your computer and use it in GitHub Desktop.
An experimental view that can iterate over a subset of an array
/**
* An experimental view that can iterate over a subset of an array.
* - Not unit tested, there are undoubtedly bugs.
*/
"use strict";
const clamp = (min: number, max: number, value: number) => {
return Math.min(max, Math.max(min, value));
};
export class ArrayView<T, S, U> implements ReadonlyArray<T> {
readonly [n: number]: T;
// @ts-expect-error
readonly length: number;
// @ts-expect-error
readonly entries: () => IterableIterator<[number, T]>;
// @ts-expect-error
readonly slice: (start?: number, end?: number) => T[];
constructor(array: readonly T[], start: number, end: number) {
start = clamp(-array.length, array.length - 1, start >= 0 ? start : array.length + start);
end = clamp(-array.length, array.length, end >= 0 ? end : array.length + end);
const length = end - start;
Object.defineProperty(this, "length", {
configurable: false,
enumerable: false,
get: () => length,
});
Object.defineProperty(this, "entries", {
writable: false,
configurable: false,
enumerable: false,
value: () => {
let i = 0;
return {
next: () => ({ value: [i, array[start + i]], done: i++ >= length }),
[Symbol.iterator]: function () {
return this;
},
};
},
});
Object.defineProperty(this, "slice", {
writable: false,
configurable: false,
enumerable: false,
value: (_start = 0, _end = length) => {
_start = clamp(-length, length - 1, _start >= 0 ? _start : length + _start);
_end = clamp(-length, length, _end >= 0 ? _end : length + _end);
return array.slice(start + _start, start + _end);
},
});
return new Proxy(this, {
get(target, prop, receiver) {
if (typeof prop === "symbol") return Reflect.get(target, prop, receiver);
const i = Number(prop);
if (isNaN(i)) return Reflect.get(target, prop, receiver);
return i >= 0 && i < length ? array[start + i] : undefined;
},
});
}
toString(): string {
return this.slice().toString();
}
toLocaleString(): string {
return this.slice().toLocaleString();
}
concat(...items: ConcatArray<T>[]): T[];
concat(...items: (T | ConcatArray<T>)[]): T[];
concat(...items: any[]): T[] {
return this.slice().concat(...items);
}
join(separator?: string): string {
return this.slice().join(separator);
}
indexOf(searchElement: T, fromIndex: number = 0): number {
fromIndex = clamp(0, this.length - 1, fromIndex);
const { length } = this;
for (let i = fromIndex; i < length; i++) {
if (this[i] === searchElement) return i;
}
return -1;
}
lastIndexOf(searchElement: T, fromIndex?: number): number {
fromIndex = clamp(0, this.length - 1, fromIndex ?? this.length - 1);
for (let i = fromIndex; i >= 0; i--) {
if (this[i] === searchElement) return i;
}
return -1;
}
every(predicate: (value: T, index: number, array: readonly T[]) => unknown, thisArg?: any): boolean {
for (const [i, value] of this.entries()) {
if (!predicate.call(thisArg, value, i, this)) return false;
}
return true;
}
some(predicate: (value: T, index: number, array: readonly T[]) => unknown, thisArg?: any): boolean {
for (const [i, value] of this.entries()) {
if (predicate.call(thisArg, value, i, this)) return true;
}
return false;
}
forEach(callbackfn: (value: T, index: number, array: readonly T[]) => void, thisArg?: any): void {
for (const [i, value] of this.entries()) {
callbackfn.call(thisArg, value, i, this);
}
}
map<U>(callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any): U[] {
const result: U[] = [];
for (const [i, value] of this.entries()) {
result.push(callbackfn.call(thisArg, value, i, this));
}
return result;
}
filter<S extends T>(predicate: (value: T, index: number, array: readonly T[]) => value is S, thisArg?: any): S[];
filter(predicate: (value: T, index: number, array: readonly T[]) => unknown, thisArg?: any): T[];
filter(predicate: any, thisArg?: any): T[] | S[] {
const result: T[] = [];
for (const [i, value] of this.entries()) {
if (predicate.call(thisArg, value, i, this)) result.push(value);
}
return result;
}
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T): T;
reduce(
callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T,
initialValue: T
): T;
reduce<U>(
callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: readonly T[]) => U,
initialValue: U
): U;
reduce(callbackfn: any, initialValue?: any): T | U {
const { length } = this;
const [first, initial] = initialValue !== undefined ? [0, initialValue] : [1, this[0]];
let aggr = initial;
for (let i = first; i < length; i++) {
aggr = callbackfn(aggr, this[i], i, this);
}
return aggr;
}
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T): T;
reduceRight(
callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T,
initialValue: T
): T;
reduceRight<U>(
callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: readonly T[]) => U,
initialValue: U
): U;
reduceRight(callbackfn: any, initialValue?: any): T | U {
const { length } = this;
const [last, initial] =
initialValue !== undefined ? [length - 1, initialValue] : [length - 2, this[length - 1]];
let aggr = initial;
for (let i = last; i >= 0; i--) {
aggr = callbackfn(aggr, this[i], i, this);
}
return aggr;
}
find<S extends T>(
predicate: (this: void, value: T, index: number, obj: readonly T[]) => value is S,
thisArg?: any
): S | undefined;
find(predicate: (value: T, index: number, obj: readonly T[]) => unknown, thisArg?: any): T | undefined;
find(predicate: any, thisArg?: any): T | S | undefined {
const index = this.findIndex(predicate, thisArg);
if (index >= 0) return this[index];
}
findIndex(predicate: (value: T, index: number, obj: readonly T[]) => unknown, thisArg?: any): number {
const { length } = this;
for (const [i, value] of this.entries()) {
if (predicate.call(thisArg, value, i, this)) return i;
}
return -1;
}
*keys(): IterableIterator<number> {
for (const [i, _] of this.entries()) yield i;
}
*values(): IterableIterator<T> {
for (const [_, value] of this.entries()) yield value;
}
includes(searchElement: T, fromIndex?: number): boolean {
return this.indexOf(searchElement, fromIndex) >= 0;
}
flatMap<U, This = undefined>(
callback: (this: This, value: T, index: number, array: T[]) => U | readonly U[],
thisArg?: This
): U[] {
return this.slice().flatMap(callback, thisArg);
}
flat<A, D extends number = 1>(this: A & { slice: () => T[] }, depth?: D): FlatArray<A, D>[] {
return this.slice().flat(depth) as FlatArray<A, D>[];
}
at(index: number): T | undefined {
index = clamp(-this.length, this.length - 1, index);
const i = index < 0 ? this.length - index : index;
return this[i];
}
[Symbol.iterator](): IterableIterator<T> {
return this.values();
}
}
(() => {
const array = Array.from({ length: 10 }, (_, i) => i);
const view = new ArrayView(array, 1, 9 /* , { caching: true } */);
for (let i = 0; i < view.length; i++) console.log("for:", view[i]);
for (const e of view.entries()) console.log("entry:", e);
for (const o of view) console.log("of:", o);
view.forEach((o, i) => console.log("each:", o));
console.log(view);
console.log(Object.keys(view));
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment