Skip to content

Instantly share code, notes, and snippets.

@jabney
Last active February 16, 2024 20:09
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/65ff069c16798ca628ca9c77d8e9e327 to your computer and use it in GitHub Desktop.
Save jabney/65ff069c16798ca628ca9c77d8e9e327 to your computer and use it in GitHub Desktop.
Implementation
const normalizeStart = (length: number, start: number | undefined): number => {
start = start ?? 0;
if (start < 0) {
start = start + length;
} else if (start < -length) {
start = 0;
} else if (start >= length) {
start = length;
}
return start;
};
const normalizeEnd = (length: number, end: number | undefined): number => {
end = end ?? length;
if (end < 0) {
end = end + length;
} else if (end < -length) {
end = 0;
} else if (end >= length) {
end = length;
}
return end;
};
type Omitted = "slice" | "map" | "filter" | "reduce";
type ArrayView<T> = Omit<ReadonlyArray<T>, Omitted> & {
/**
* Returns a new view with start and end set in the context of the current view.
* @param start The beginning of the specified portion of the view.
* @param end The end of the specified portion of the view. This is exclusive of the element at the index 'end'.
*/
slice(start?: number, end?: number): ArrayView<T>;
/**
* Calls a defined callback function on each element of a view, and returns an array that contains the results.
* @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the view.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
map<U>(callbackfn: (value: T, index: number, view: ArrayView<T>) => U, thisArg?: any): U[];
mapAsync<U>(callbackfn: (value: T, index: number, view: ArrayView<T>) => Promise<U>, thisArg?: any): Promise<U[]>;
/**
* Returns the elements of a view that meet the condition specified in a callback function.
* @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the view.
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
*/
filter<S extends T>(predicate: (value: T, index: number, array: ArrayView<T>) => value is S, thisArg?: any): S[];
filterAsync<S extends T>(
predicate: (value: T, index: number, array: ArrayView<T>) => Promise<boolean>,
thisArg?: any
): Promise<S[]>;
/**
* Calls the specified callback function for all the elements in aa voew. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the view.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of a view value.
*/
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: ArrayView<T>) => T): T;
reduce(
callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: ArrayView<T>) => T,
initialValue: T
): T;
/**
* Calls the specified callback function for all the elements in a view. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the view.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of a view value.
*/
reduce<U>(
callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: ArrayView<T>) => U,
initialValue: U
): U;
};
const arrayIterator = <T>(array: readonly T[], start: number, end: number): IterableIterator<T> => {
let current = start;
return {
next() {
const done = current === end;
const result: IteratorResult<T> = done
? {
done: true,
value: undefined,
}
: {
value: array[current],
};
current += 1;
return result;
},
[Symbol.iterator]() {
return this;
},
};
};
const get = <T>(array: readonly T[], start: number, end: number, i: number): T | undefined => {
const index = start + i;
if (index < start || index >= end) {
return undefined;
}
return array[index];
};
const reNumeric = /\d+/;
const isNumeric = (value: string | symbol): value is string => {
return typeof value == "string" && reNumeric.test(value);
};
const disallowed: (symbol | string)[] = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"];
const notImplemented: (symbol | string)[] = [
// "at",
"concat",
"copyWithin",
// "entries",
"every",
"fill",
// "filter",
"find",
"findIndex",
"findLast",
"findLastIndex",
"flat",
"flatMap",
"forEach",
"includes",
"indexOf",
"join",
// "keys",
"lastIndexOf",
// "map",
// "reduce",
"reduceRight",
// "slice",
"some",
"toLocaleString",
"toReversed",
"toSorted",
"toSpliced",
"toString",
// "values",
"with",
];
const excluded = [...disallowed, ...notImplemented];
const augmentInstance = <T>(array: readonly T[], view: ArrayView<T>, start: number, end: number): ArrayView<T> => {
view.at = (index: number) => {
if (index < 0) {
return array[start + index + array.length];
}
return array[start + index];
};
view.keys = function* keys(): IterableIterator<number> {
for (let i = start; i < end; i++) {
yield i - start;
}
};
view.values = function* values(): IterableIterator<T> {
for (let i = start; i < end; i++) {
yield array[i - start];
}
};
view.entries = function* entries(): IterableIterator<[number, T]> {
for (let i = start; i < end; i++) {
yield [i - start, array[i - start]];
}
};
view.map = <U>(callbackfn: (value: T, index: number, view: ArrayView<T>) => U, thisArg?: any): U[] => {
const list: U[] = [];
for (let i = start; i < end; i++) {
list.push(callbackfn.call(thisArg, array[i - start], i - start, view));
}
return list;
};
view.mapAsync = async <U>(
callbackfn: (value: T, index: number, view: ArrayView<T>) => Promise<U>,
thisArg?: any
): Promise<U[]> => {
const list: U[] = [];
for (let i = start; i < end; i++) {
let value = await callbackfn.call(thisArg, array[i - start], i - start, view);
list.push(value);
}
return list;
};
view.filter = <S extends T>(
predicate: (value: T, index: number, array: ArrayView<T>) => value is S,
thisArg?: any
): S[] => {
const list: S[] = [];
for (let i = start; i < end; i++) {
if (predicate.call(thisArg, array[i - start], i - start, view) === true) {
list.push(array[i - start] as S);
}
}
return list;
};
view.filterAsync = async <S extends T>(
predicate: (value: T, index: number, array: ArrayView<T>) => Promise<boolean>,
thisArg?: any
): Promise<S[]> => {
const list: S[] = [];
for (let i = start; i < end; i++) {
const result = await predicate.call(thisArg, array[i - start], i - start, view);
if (result === true) {
list.push(array[i - start] as S);
}
}
return list;
};
view.reduce = <U>(
callbackfn: (previousValue: T | U, currentValue: T | U, currentIndex: number, array: ArrayView<T>) => U,
initialValue?: U
): U => {
const first = initialValue == null ? start + 1 : start;
let prev: T | U = initialValue == null ? array[first] : initialValue;
let curr: T | U = array[first];
for (let i = first; i < end; i++) {
const result = callbackfn(prev, curr, i - first, view);
prev = curr;
curr = result;
}
return curr as U;
};
view.slice = (start?: number, end?: number): ArrayView<T> => {
start = normalizeStart(array.length, start);
end = normalizeEnd(array.length, end);
if (end <= start) {
return view;
}
return arrayView(array, start, end);
};
return view;
};
function timeout(ms: number): Promise<void> {
return new Promise((resolve) => void setTimeout(resolve, ms));
}
function immediate(): Promise<void> {
return new Promise((resolve) => setImmediate(resolve));
}
type PromiseMaybe<T> = T | Promise<T>;
type MaybeFn<T> = () => PromiseMaybe<T>;
type PromiseFn<T> = () => Promise<T>;
export interface IScheduleController<T> {
pause: () => void;
continue: () => void;
exec: (fn: MaybeFn<T>) => Promise<T>;
}
export class Scheduler {
static readonly Timeout = async <T>(ms: number, fn: MaybeFn<T>): Promise<T> => {
await timeout(ms);
return await fn();
};
static readonly Soon = async <T>(fn: MaybeFn<T>): Promise<T> => {
await timeout(0);
return await fn();
};
static readonly Immediate = async <T>(fn: MaybeFn<T>): Promise<T> => {
await immediate();
return await fn();
};
static readonly When = async <T>(fn: MaybeFn<T>, when: PromiseFn<void>): Promise<T> => {
await when();
return await fn();
};
}
/**
* Creates a view over an array.
*/
export function arrayView<T>(array: readonly T[], start = 0, end = array.length): ArrayView<T> {
const self: ArrayView<T> = augmentInstance(array, Object.create(array), start, end);
Object.defineProperties(self, {
length: {
get() {
return end - start;
},
set(value: number) {
throw new Error("property is read only");
},
},
});
self[Symbol.iterator] = () => {
return arrayIterator(array, start, end);
};
return new Proxy(self, {
get(target, prop, receiver) {
if (isNumeric(prop)) {
return get(array, start, end, parseInt(prop));
}
if (excluded.includes(prop)) {
throw new Error("not supported");
} else {
return Reflect.get(self, prop);
}
},
});
}
let list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log("list length:", list.length, "\n");
const view1 = arrayView(list);
for (const x of view1) {
console.log("jimmy view1:", x);
}
console.log("view1 length:", view1.length, "\n");
const view2 = arrayView(list, 1, 10);
for (const x of view2) {
console.log("jimmy view2:", x);
}
console.log("view2 length:", view2.length, "\n");
const view3 = view2.slice(2, 9);
for (const x of view3) {
console.log("jimmy view3:", x);
}
console.log("view3 length:", view3.length, "\n");
for (const x of view3) {
console.log("item:", x);
}
console.log("\n");
for (let i = 0; i < view3.length; i++) {
console.log(`item at ${i}:`, view3[i]);
}
console.log("\n");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment