Skip to content
Snippets Groups Projects
Commit 37865407 authored by David Sehnal's avatar David Sehnal
Browse files

range set

parent d661fe64
No related branches found
No related tags found
No related merge requests found
......@@ -10,28 +10,25 @@
* "Idiomatic" usage:
*
* const it = ...;
* for (let v = it.reset(data).nextValue(); !it.done; v = it.nextValue()) { ... }
* for (let v = it.nextValue(); !it.done; v = it.nextValue()) { ... }
*/
interface Iterator<T, Data = any> {
[Symbol.iterator](): Iterator<T, Data>,
interface Iterator<T> {
[Symbol.iterator](): Iterator<T>,
readonly done: boolean,
readonly value: T,
next(): { done: boolean, value: T },
reset(data: Data): Iterator<T, Data>,
nextValue(): T
}
class __EmptyIterator implements Iterator<any, any> { // tslint:disable-line:class-name
class EmptyIteratorImpl implements Iterator<any> {
[Symbol.iterator]() { return this; }
done = true;
value = void 0;
next() { return this; }
nextValue() { return this.value; }
reset(value: undefined) { return this; }
}
class __SingletonIterator<T> implements Iterator<T, T> { // tslint:disable-line:class-name
class SingletonIteratorImpl<T> implements Iterator<T> {
private yielded = false;
[Symbol.iterator]() { return this; }
......@@ -39,13 +36,10 @@ class __SingletonIterator<T> implements Iterator<T, T> { // tslint:disable-line:
value: T;
next() { this.done = this.yielded; this.yielded = true; return this; }
nextValue() { return this.next().value; }
reset(value: T) { this.value = value; this.done = false; this.yielded = false; return this; }
constructor(value: T) { this.value = value; }
}
class __ArrayIterator<T> implements Iterator<T, ArrayLike<T>> { // tslint:disable-line:class-name
class ArrayIteratorImpl<T> implements Iterator<T> {
private xs: ArrayLike<T> = [];
private index: number = -1;
private length: number = 0;
......@@ -63,7 +57,7 @@ class __ArrayIterator<T> implements Iterator<T, ArrayLike<T>> { // tslint:disabl
nextValue() { return this.next().value; }
reset(xs: ArrayLike<T>) {
constructor(xs: ArrayLike<T>) {
this.length = xs.length;
this.done = false;
this.xs = xs;
......@@ -72,47 +66,37 @@ class __ArrayIterator<T> implements Iterator<T, ArrayLike<T>> { // tslint:disabl
}
}
type Range = { min: number, max: number }
class __RangeIterator implements Iterator<number, Range> { // tslint:disable-line:class-name
private min: number;
private max: number;
class RangeIteratorImpl implements Iterator<number> {
[Symbol.iterator]() { return this; };
done = true;
value: number;
next() {
++this.value;
this.done = this.value >= this.max;
this.done = this.value > this.max;
return this;
}
nextValue() { return this.next().value; }
reset({ min, max}: Range) {
this.min = min;
this.max = max;
constructor(min: number, private max: number) {
this.value = min - 1;
this.done = false;
return this;
}
constructor(bounds: Range) { this.reset(bounds); }
}
export const EmptyIterator: Iterator<any> = new __EmptyIterator();
export function SingletonIterator<T>(value: T): Iterator<T, T> { return new __SingletonIterator(value); }
export function ArrayIterator<T>(xs?: ArrayLike<T>): Iterator<T, ArrayLike<T>> {
const ret = new __ArrayIterator<T>();
if (xs) ret.reset(xs);
return ret;
}
export function RangeIterator(bounds?: Range): Iterator<number, Range> { return new __RangeIterator(bounds || { min: 0, max: 0 }); }
namespace Iterator {
export const Empty: Iterator<any> = new EmptyIteratorImpl();
export function Singleton<T>(value: T): Iterator<T> { return new SingletonIteratorImpl(value); }
export function Array<T>(xs: ArrayLike<T>): Iterator<T> { return new ArrayIteratorImpl<T>(xs); }
export function Range(min: number, max: number): Iterator<number> { return new RangeIteratorImpl(min, max); }
export function toArray<T>(it: Iterator<T>): T[] {
const ret = [];
for (let v = it.nextValue(); !it.done; v = it.nextValue()) ret[ret.length] = v;
return ret;
export function toArray<T>(it: Iterator<T>): T[] {
const ret = [];
for (let v = it.nextValue(); !it.done; v = it.nextValue()) ret[ret.length] = v;
return ret;
}
}
export default Iterator
\ No newline at end of file
// TODO
\ No newline at end of file
/**
* Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Iterator from './iterator'
interface RangeSet {
readonly size: number,
has(x: number): boolean,
indexOf(x: number): number,
elements(): Iterator<number>
}
namespace RangeSet {
interface Impl extends RangeSet {
readonly min: number,
readonly max: number,
toArray(): ArrayLike<number>
}
export function union(a: RangeSet, b: RangeSet) {
if (a instanceof RangeImpl) {
if (b instanceof RangeImpl) return unionRR(a, b);
return unionAR(b as ArrayImpl, a);
} else if (b instanceof RangeImpl) {
return unionAR(a as ArrayImpl, b);
} else return unionAA((a as Impl).toArray(), (b as Impl).toArray());
}
export function intersect(a: RangeSet, b: RangeSet) {
if (a instanceof RangeImpl) {
if (b instanceof RangeImpl) return intersectRR(a, b);
return intersectAR(b as ArrayImpl, a);
} else if (b instanceof RangeImpl) {
return intersectAR(a as ArrayImpl, b);
} else {
const ai = a as Impl, bi = b as Impl;
if (!areRangesIntersecting(ai, bi)) return Empty;
return intersectAA(ai.toArray(), bi.toArray());
}
}
class RangeImpl implements Impl {
size: number;
has(x: number) { return x >= this.min && x <= this.max; }
indexOf(x: number) { return x >= this.min && x <= this.max ? x - this.min : -1; }
toArray() {
const ret = new Array(this.size);
for (let i = 0; i < this.size; i++) ret[i] = i + this.min;
return ret;
}
elements() { return Iterator.Range(this.min, this.max); }
constructor(public min: number, public max: number) {
this.size = max - min + 1;
}
}
class ArrayImpl implements Impl {
size: number;
public min: number;
public max: number;
has(x: number) { return x >= this.min && x <= this.max && binarySearch(this.values, x) >= 0; }
indexOf(x: number) { return x >= this.min && x <= this.max ? binarySearch(this.values, x) : -1; }
toArray() { return this.values; }
elements() { return Iterator.Array(this.values); }
constructor(public values: ArrayLike<number>) {
this.min = values[0];
this.max = values[values.length - 1];
this.size = values.length;
}
}
export function ofSingleton(value: number): RangeSet { return new RangeImpl(value, value); }
export function ofRange(min: number, max: number): RangeSet { return new RangeImpl(min, max); }
/** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */
export function ofSortedArray(xs: ArrayLike<number>): RangeSet {
if (!xs.length) return Empty;
// check if the array is just a range
if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return ofRange(xs[0], xs[xs.length - 1]);
return new ArrayImpl(xs);
}
export const Empty = ofRange(0, -1);
function binarySearch(xs: ArrayLike<number>, value: number) {
let min = 0, max = xs.length - 1;
while (min <= max) {
if (min + 11 > max) {
for (let i = min; i <= max; i++) {
if (value === xs[i]) return i;
}
return -1;
}
const mid = (min + max) >> 1;
const v = xs[mid];
if (value < v) max = mid - 1;
else if (value > v) min = mid + 1;
else return mid;
}
return -1;
}
function areRangesIntersecting(a: Impl, b: Impl) {
return a.size > 0 && b.size > 0 && a.max >= b.min && a.min <= b.max;
}
function unionRR(a: RangeImpl, b: RangeImpl) {
if (!a.size) return b;
if (!b.size) return a;
if (areRangesIntersecting(a, b)) return ofRange(Math.min(a.min, b.min), Math.max(a.max, b.max));
let l, r;
if (a.min < b.min) { l = a; r = b; }
else { l = b; r = a; }
const arr = new Int32Array(a.size + b.size);
for (let i = 0; i < l.size; i++) arr[i] = i + l.min;
for (let i = 0; i < r.size; i++) arr[i + l.size] = i + r.min;
return ofSortedArray(arr);
}
function unionAR(a: ArrayImpl, b: RangeImpl) {
if (!b.size) return a;
// is the array fully contained in the range?
if (a.min >= b.min && a.max <= b.max) return b;
const xs = a.values;
const { min, max } = b;
let start = 0, end = xs.length - 1;
while (xs[start] < min) { start++; }
while (xs[end] > max) { end--; }
end++;
const size = start + (xs.length - end) + b.size;
const indices = new Int32Array(size);
let offset = 0;
for (let i = 0; i < start; i++) indices[offset++] = xs[i];
for (let i = min; i <= max; i++) indices[offset++] = i;
for (let i = end, _i = xs.length; i < _i; i++) indices[offset] = xs[i];
return ofSortedArray(indices);
}
function unionAA(xs: ArrayLike<number>, ys: ArrayLike<number>) {
const la = xs.length, lb = ys.length;
let i = 0, j = 0, resultSize = 0;
while (i < la && j < lb) {
const x = xs[i], y = ys[j];
resultSize++;
if (x < y) { i++; }
else if (x > y) { j++; }
else { i++; j++; }
}
resultSize += Math.max(la - i, lb - j);
const indices = new Int32Array(resultSize);
let offset = 0;
i = 0;
j = 0;
while (i < la && j < lb) {
const x = xs[i], y = ys[j];
if (x < y) { indices[offset++] = x; i++; }
else if (x > y) { indices[offset++] = y; j++; }
else { indices[offset++] = x; i++; j++; }
}
for (; i < la; i++) { indices[offset++] = xs[i]; }
for (; j < lb; j++) { indices[offset++] = ys[j]; }
return ofSortedArray(indices);
}
function intersectRR(a: RangeImpl, b: RangeImpl) {
if (!areRangesIntersecting(a, b)) return Empty;
return ofRange(Math.max(a.min, b.min), Math.min(a.max, b.max));
}
function intersectAR(a: ArrayImpl, r: RangeImpl) {
const xs = a.values;
let resultSize = 0;
for (let i = 0, _i = xs.length; i < _i; i++) {
if (r.has(xs[i])) resultSize++;
}
if (!resultSize) return Empty;
const indices = new Int32Array(resultSize);
let offset = 0;
for (let i = 0, _i = xs.length; i < _i; i++) {
if (r.has(xs[i])) indices[offset++] = xs[i];
}
return ofSortedArray(indices);
}
function intersectAA(xs: ArrayLike<number>, ys: ArrayLike<number>) {
const la = xs.length, lb = ys.length;
let i = 0, j = 0, resultSize = 0;
while (i < la && j < lb) {
const x = xs[i], y = ys[j];
if (x < y) { i++; }
else if (x > y) { j++; }
else { i++; j++; resultSize++; }
}
if (!resultSize) return Empty;
const indices = new Int32Array(resultSize);
let offset = 0;
i = 0;
j = 0;
while (i < la && j < lb) {
const x = xs[i], y = ys[j];
if (x < y) { i++; }
else if (x > y) { j++; }
else { indices[offset++] = x; i++; j++; }
}
return ofSortedArray(indices);
}
}
export default RangeSet
\ No newline at end of file
......@@ -4,24 +4,22 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Iterator, * as I from '../collections/iterator'
import Iterator from '../collections/iterator'
import IntPair from '../collections/int-pair'
import * as Sort from '../collections/sort'
import RangeSet from '../collections/range-set'
describe('basic iterators', () => {
function check<T>(name: string, iter: Iterator<T>, expected: T[]) {
it(name, () => {
expect(I.toArray(iter)).toEqual(expected);
expect(Iterator.toArray(iter)).toEqual(expected);
});
}
check('empty', I.EmptyIterator, []);
check('singleton', I.SingletonIterator(10), [10]);
check('singleton reset', I.SingletonIterator(10).reset(13), [13]);
check('array', I.ArrayIterator([1, 2, 3]), [1, 2, 3]);
check('array reset', I.ArrayIterator([1, 2, 3]).reset([4]), [4]);
check('range', I.RangeIterator({ min: 0, max: 3 }), [0, 1, 2]);
check('range reset', I.RangeIterator().reset({ min: 1, max: 2 }), [1]);
check('empty', Iterator.Empty, []);
check('singleton', Iterator.Singleton(10), [10]);
check('array', Iterator.Array([1, 2, 3]), [1, 2, 3]);
check('range', Iterator.Range(0, 3), [0, 1, 2, 3]);
});
describe('int pair', () => {
......@@ -117,4 +115,66 @@ describe('qsort-dual array', () => {
}
test('sorted', data, false);
test('shuffled', data, true);
})
\ No newline at end of file
})
describe('range set', () => {
function testEq(name: string, set: RangeSet, expected: number[]) {
it(name, () => {
// copy the arrays to ensure "compatibility" between typed and native arrays
expect(Array.prototype.slice.call(Iterator.toArray(set.elements()))).toEqual(Array.prototype.slice.call(expected));
});
}
const empty = RangeSet.Empty;
const singleton = RangeSet.ofSingleton(10);
const range = RangeSet.ofRange(1, 4);
const arr = RangeSet.ofSortedArray([1, 3, 6]);
testEq('empty', empty, []);
testEq('singleton', singleton, [10]);
testEq('range', range, [1, 2, 3, 4]);
testEq('sorted array', arr, [1, 3, 6]);
expect(empty.has(10)).toBe(false);
expect(empty.indexOf(10)).toBe(-1);
expect(singleton.has(10)).toBe(true);
expect(singleton.has(11)).toBe(false);
expect(singleton.indexOf(10)).toBe(0);
expect(singleton.indexOf(11)).toBe(-1);
expect(range.has(4)).toBe(true);
expect(range.has(5)).toBe(false);
expect(range.indexOf(4)).toBe(3);
expect(range.indexOf(11)).toBe(-1);
expect(arr.has(3)).toBe(true);
expect(arr.has(4)).toBe(false);
expect(arr.indexOf(3)).toBe(1);
expect(arr.indexOf(11)).toBe(-1);
testEq('union ES', RangeSet.union(empty, singleton), [10]);
testEq('union ER', RangeSet.union(empty, range), [1, 2, 3, 4]);
testEq('union EA', RangeSet.union(empty, arr), [1, 3, 6]);
testEq('union SS', RangeSet.union(singleton, RangeSet.ofSingleton(16)), [10, 16]);
testEq('union SR', RangeSet.union(range, singleton), [1, 2, 3, 4, 10]);
testEq('union SA', RangeSet.union(arr, singleton), [1, 3, 6, 10]);
testEq('union SA1', RangeSet.union(arr, RangeSet.ofSingleton(3)), [1, 3, 6]);
testEq('union RR', RangeSet.union(range, range), [1, 2, 3, 4]);
testEq('union RR1', RangeSet.union(range, RangeSet.ofRange(6, 7)), [1, 2, 3, 4, 6, 7]);
testEq('union RR2', RangeSet.union(range, RangeSet.ofRange(3, 5)), [1, 2, 3, 4, 5]);
testEq('union RA', RangeSet.union(range, arr), [1, 2, 3, 4, 6]);
testEq('union AA', RangeSet.union(arr, RangeSet.ofSortedArray([2, 4, 6, 7])), [1, 2, 3, 4, 6, 7]);
testEq('union AA1', RangeSet.union(arr, RangeSet.ofSortedArray([2, 3, 4, 6, 7])), [1, 2, 3, 4, 6, 7]);
testEq('intersect ES', RangeSet.intersect(empty, singleton), []);
testEq('intersect ER', RangeSet.intersect(empty, range), []);
testEq('intersect EA', RangeSet.intersect(empty, arr), []);
testEq('intersect SS', RangeSet.intersect(singleton, RangeSet.ofSingleton(16)), []);
testEq('intersect SS', RangeSet.intersect(singleton, singleton), [10]);
testEq('intersect SR', RangeSet.intersect(range, singleton), []);
testEq('intersect RR', RangeSet.intersect(range, range), [1, 2, 3, 4]);
testEq('intersect RR2', RangeSet.intersect(range, RangeSet.ofRange(3, 5)), [3, 4]);
testEq('intersect RA', RangeSet.intersect(range, arr), [1, 3]);
testEq('intersect AA', RangeSet.intersect(arr, RangeSet.ofSortedArray([2, 3, 4, 6, 7])), [3, 6]);
});
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment