Skip to content

Commit

Permalink
start with iterable interface similar to scala
Browse files Browse the repository at this point in the history
  • Loading branch information
sgratzl committed Nov 23, 2018
1 parent d3e1d7a commit 7c7b2ad
Show file tree
Hide file tree
Showing 2 changed files with 259 additions and 11 deletions.
246 changes: 246 additions & 0 deletions src/internal/interable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@


export interface IIterable<T> extends Iterable<T> {
readonly length: number;
filter(callback: (v: T, i: number) => boolean): IIterable<T>;
map<U>(callback: (v: T, i: number) => U): IIterable<U>;
forEach(callback: (v: T, i: number) => void): void;
}

class LazyFilter<T> implements IIterable<T> {
private _length = -1;
constructor(private readonly it: IIterable<T>, private readonly filters: ((v: T, i: number) => boolean)[]) {

}

get length() {
if (this._length >= 0) {
return this._length;
}
let l = 0;
this.forEach(() => l++);
this._length = l;
return l;
}

filter(callback: (v: T, i: number) => boolean): IIterable<T> {
return new LazyFilter(this.it, this.filters.concat(callback));
}

map<U>(callback: (v: T, i: number) => U): IIterable<U> {
return new LazyMap1(this, callback);
}

forEach(callback: (v: T, i: number) => void) {
let i = 0;
this.it.forEach((v) => {
for (const f of this.filters) {
if (!f(v, i)) {
return;
}
}
callback(v, i++);
});
}

[Symbol.iterator]() {
const it = this.it[Symbol.iterator]();
const next = () => {
let v = it.next();
let i = 0;
outer: while (!v.done) {
for (const f of this.filters) {
if (f(v.value, i)) {
continue;
}
// invalid go to next
v = it.next();
i++;
continue outer;
}
}
return v;
};
return { next };
}
}

class LazyMap1<T1, T2> implements IIterable<T2> {
constructor(private readonly it: IIterable<T1>, private readonly map12: (v: T1, i: number) => T2) {

}

get length() {
return this.it.length;
}

filter(callback: (v: T2, i: number) => boolean): IIterable<T2> {
return new LazyFilter(this, [callback]);
}

map<U>(callback: (v: T2, i: number) => U): IIterable<U> {
return new LazyMap2(this.it, this.map12, callback);
}

forEach(callback: (v: T2, i: number) => void) {
this.it.forEach((v, i) => {
callback(this.map12(v, i), i);
});
}

[Symbol.iterator]() {
const it = this.it[Symbol.iterator]();
let i = 0;
const next = () => {
const v = it.next();
if (v.done) {
return {
value: <T2><any>undefined,
done: true
};
}
const value = this.map12(v.value, ++i);
i++;
return {
value,
done: false
};
};
return { next };
}
}

class LazyMap2<T, T2, T3> implements IIterable<T3> {
constructor(private readonly it: IIterable<T>, private readonly map12: (v: T, i: number) => T2, private readonly map23: (v: T2, i: number) => T3) {

}

get length() {
return this.it.length;
}

filter(callback: (v: T3, i: number) => boolean): IIterable<T3> {
return new LazyFilter(this, [callback]);
}

map<U>(callback: (v: T3, i: number) => U): IIterable<U> {
return new LazyMap3(this.it, this.map12, this.map23, callback);
}

forEach(callback: (v: T3, i: number) => void) {
this.it.forEach((v, i) => {
callback(this.map23(this.map12(v, i), i), i);
});
}

[Symbol.iterator]() {
const it = this.it[Symbol.iterator]();
let i = 0;
const next = () => {
const v = it.next();
if (v.done) {
return {
value: <T3><any>undefined,
done: true
};
}
const value = this.map23(this.map12(v.value, i), i);
i++;
return {
value,
done: false
};
};
return { next };
}
}


class LazyMap3<T1, T2, T3, T4> implements IIterable<T4> {
constructor(private readonly it: IIterable<T1>, private readonly map12: (v: T1, i: number) => T2, private readonly map23: (v: T2, i: number) => T3, private readonly map34: (v: T3, i: number) => T4) {

}

get length() {
return this.it.length;
}

filter(callback: (v: T4, i: number) => boolean): IIterable<T4> {
return new LazyFilter(this, [callback]);
}

map<U>(callback: (v: T4, i: number) => U): IIterable<U> {
const map1U = (v: T1, i: number) => callback(this.map34(this.map23(this.map12(v, i), i), i), i);
return new LazyMap1(this.it, map1U);
}

forEach(callback: (v: T4, i: number) => void) {
this.it.forEach((v, i) => {
callback(this.map34(this.map23(this.map12(v, i), i), i), i);
});
}

[Symbol.iterator]() {
const it = this.it[Symbol.iterator]();
let i = 0;
const next = () => {
const v = it.next();
if (v.done) {
return {
value: <T4><any>undefined,
done: true
};
}
i++;
const value = this.map34(this.map23(this.map12(v.value, i), i), i);
return {
value,
done: false
};
};
return { next };
}
}

export function lazy<T>(it: Iterable<T>): IIterable<T> {
let v: ReadonlyArray<T> | null = null;
const asArr = () => {
if (v) {
return v;
}
if (Array.isArray(it)) {
v = it;
} else {
v = Array.from(it);
}
return v;
};

const r: any = {
[Symbol.iterator]() {
return it[Symbol.iterator]();
},
filter(cb: (v: T, i: number) => boolean) {
return new LazyFilter(asArr(), [cb]);
},
map<U>(cb: (v: T, i: number) => U) {
return new LazyMap1(asArr(), cb);
},
forEach(cb: (v: T, i: number) => void) {
let i = 0;
for (const v of asArr()) {
cb(v, i++);
}
}
};

Object.defineProperty(r, 'length', {
enumerable: true,
writable: false,
get() {
return asArr().length;
}
});

return <IIterable<T>><any>r;
}
24 changes: 13 additions & 11 deletions src/internal/math.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {histogram, quantile} from 'd3-array';
import {ICategory, isMissingValue} from '../model';
import {IMappingFunction} from '../model/MappingFunction';
import {IIterable} from './interable';

export interface INumberBin {
x0: number;
Expand Down Expand Up @@ -68,12 +68,14 @@ export class LazyBoxPlotData implements IStatistics {

private readonly histGen: (data: number[]) => INumberBin[];

constructor(values: number[], histGen?: (data: number[]) => INumberBin[]) {
constructor(values: IIterable<number>, histGen?: (data: number[]) => INumberBin[]) {
// filter out NaN
let min = Number.POSITIVE_INFINITY;
let max = Number.NEGATIVE_INFINITY;
let sum = 0;
let length = 0;
this.values = Float32Array.from(values.filter((v) => {
length += 1;
if (isMissingValue(v)) {
return false;
}
Expand All @@ -82,7 +84,7 @@ export class LazyBoxPlotData implements IStatistics {
sum += v;
return true;
}));
this.missing = values.length - this.values.length;
this.missing = length - this.values.length;

if (this.values.length === 0) {
this.max = this.min = this.mean = NaN;
Expand All @@ -99,7 +101,7 @@ export class LazyBoxPlotData implements IStatistics {
return;
}
const hist = histogram();
hist.thresholds(getNumberOfBins(values.length));
hist.thresholds(getNumberOfBins(length));
this.histGen = <any>hist;
}

Expand Down Expand Up @@ -209,7 +211,7 @@ function cached() {
* @returns {{min: number, max: number, count: number, hist: histogram.Bin<number>[]}}
* @internal
*/
export function computeStats(arr: number[], range?: [number, number], bins?: number): IStatistics {
export function computeStats(arr: IIterable<number>, range?: [number, number], bins?: number): IStatistics {
if (arr.length === 0) {
return {
min: NaN,
Expand Down Expand Up @@ -243,25 +245,25 @@ export function computeStats(arr: number[], range?: [number, number], bins?: num
/**
* computes a categorical histogram
* @param arr the data array
* @param acc the accessor
* @param categories the list of known categories
* @returns {{hist: {cat: string, y: number}[]}}
* @internal
*/
export function computeHist<T>(arr: T[], acc: (row: T) => ICategory | null, categories: ICategory[]): ICategoricalStatistics {
export function computeHist(arr: IIterable<ICategory | null>, categories: ICategory[]): ICategoricalStatistics {
const m = new Map<string, number>();
let missingCount = 0;
categories.forEach((cat) => m.set(cat.name, 0));

for (const a of arr) {
const v = acc(a);
arr.forEach((v) => {
if (v == null) {
missingCount += 1;
continue;
return;
}
m.set(v.name, (m.get(v.name) || 0) + 1);
}
});

const entries: { cat: string; y: number }[] = categories.map((d) => ({cat: d.name, y: m.get(d.name)!}));

return {
maxBin: entries.reduce((a, b) => Math.max(a, b.y), Number.NEGATIVE_INFINITY),
hist: entries,
Expand Down

0 comments on commit 7c7b2ad

Please sign in to comment.