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

sorted array

parent 89a34d16
No related branches found
No related tags found
No related merge requests found
...@@ -10,6 +10,8 @@ import * as Sort from '../collections/sort' ...@@ -10,6 +10,8 @@ import * as Sort from '../collections/sort'
import OrderedSet from '../collections/ordered-set' import OrderedSet from '../collections/ordered-set'
import LinkedIndex from '../collections/linked-index' import LinkedIndex from '../collections/linked-index'
import EquivalenceClasses from '../collections/equivalence-classes' import EquivalenceClasses from '../collections/equivalence-classes'
import Interval from '../collections/interval'
import SortedArray from '../collections/sorted-array'
function iteratorToArray<T>(it: Iterator<T>): T[] { function iteratorToArray<T>(it: Iterator<T>): T[] {
const ret = []; const ret = [];
...@@ -125,6 +127,114 @@ describe('qsort-dual array', () => { ...@@ -125,6 +127,114 @@ describe('qsort-dual array', () => {
test('shuffled', data, true); test('shuffled', data, true);
}) })
describe('interval', () => {
function testI(name: string, a: Interval, b: Interval) {
it(name, () => expect(Interval.areEqual(a, b)).toBe(true));
}
function test(name: string, a: any, b: any) {
it(name, () => expect(a).toEqual(b));
}
const e = Interval.Empty;
const r05 = Interval.ofRange(0, 5);
const se05 = Interval.ofBounds(0, 5);
test('size', Interval.size(e), 0);
test('size', Interval.size(r05), 6);
test('size', Interval.size(se05), 5);
test('min/max', [Interval.min(e), Interval.max(e)], [0, -1]);
test('min/max', [Interval.min(r05), Interval.max(r05)], [0, 5]);
test('min/max', [Interval.min(se05), Interval.max(se05)], [0, 4]);
test('start/end', [Interval.start(e), Interval.end(e)], [0, 0]);
test('start/end', [Interval.start(r05), Interval.end(r05)], [0, 6]);
test('start/end', [Interval.start(se05), Interval.end(se05)], [0, 5]);
test('has', Interval.has(e, 5), false);
test('has', Interval.has(r05, 5), true);
test('has', Interval.has(r05, 6), false);
test('has', Interval.has(r05, -1), false);
test('has', Interval.has(se05, 5), false);
test('has', Interval.has(se05, 4), true);
test('indexOf', Interval.indexOf(e, 5), -1);
test('indexOf', Interval.indexOf(r05, 5), 5);
test('indexOf', Interval.indexOf(r05, 6), -1);
test('getAt', Interval.getAt(r05, 5), 5);
test('areEqual', Interval.areEqual(r05, se05), false);
test('areIntersecting1', Interval.areIntersecting(r05, se05), true);
test('areIntersecting2', Interval.areIntersecting(r05, e), false);
test('areIntersecting3', Interval.areIntersecting(e, r05), false);
test('areIntersecting4', Interval.areIntersecting(e, e), true);
test('areIntersecting5', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(-4, 3)), true);
test('areIntersecting6', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(-4, -3)), false);
test('areIntersecting7', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(1, 2)), true);
test('areIntersecting8', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(3, 6)), true);
test('isSubInterval', Interval.isSubInterval(Interval.ofRange(0, 5), Interval.ofRange(3, 6)), false);
test('isSubInterval', Interval.isSubInterval(Interval.ofRange(0, 5), Interval.ofRange(3, 5)), true);
testI('intersect', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(-4, 3)), Interval.ofRange(0, 3));
testI('intersect1', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(1, 3)), Interval.ofRange(1, 3));
testI('intersect2', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(3, 5)), Interval.ofRange(3, 5));
testI('intersect3', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(-4, -3)), Interval.Empty);
test('predIndex1', Interval.findPredecessorIndex(r05, 5), 5);
test('predIndex2', Interval.findPredecessorIndex(r05, -1), 0);
test('predIndex3', Interval.findPredecessorIndex(r05, 6), 6);
test('predIndexInt', Interval.findPredecessorIndexInInterval(r05, 0, Interval.ofRange(2, 3)), 2);
test('predIndexInt1', Interval.findPredecessorIndexInInterval(r05, 4, Interval.ofRange(2, 3)), 4);
testI('findRange', Interval.findRange(r05, 2, 3), Interval.ofRange(2, 3));
});
describe('sortedArray', () => {
function testI(name: string, a: Interval, b: Interval) {
it(name, () => expect(Interval.areEqual(a, b)).toBe(true));
}
function test(name: string, a: any, b: any) {
it(name, () => expect(a).toEqual(b));
}
const a1234 = SortedArray.ofSortedArray([1, 2, 3, 4]);
const a2468 = SortedArray.ofSortedArray([2, 4, 6, 8]);
test('size', SortedArray.size(a1234), 4);
test('min/max', [SortedArray.min(a1234), SortedArray.max(a1234)], [1, 4]);
test('start/end', [SortedArray.start(a1234), SortedArray.end(a1234)], [1, 5]);
test('has', SortedArray.has(a1234, 5), false);
test('has', SortedArray.has(a1234, 4), true);
it('has-all', () => {
for (let i = 1; i <= 4; i++) expect(SortedArray.has(a1234, i)).toBe(true);
});
test('indexOf', SortedArray.indexOf(a2468, 5), -1);
test('indexOf', SortedArray.indexOf(a2468, 2), 0);
test('getAt', SortedArray.getAt(a2468, 1), 4);
test('areEqual', SortedArray.areEqual(a2468, a2468), true);
test('areEqual1', SortedArray.areEqual(a2468, SortedArray.create([4, 2, 8, 6])), true);
test('areEqual2', SortedArray.areEqual(a1234, a2468), false);
test('predIndex1', SortedArray.findPredecessorIndex(a1234, 5), 4);
test('predIndex2', SortedArray.findPredecessorIndex(a1234, 2), 1);
test('predIndex3', SortedArray.findPredecessorIndex(a2468, 4), 1);
test('predIndex4', SortedArray.findPredecessorIndex(a2468, 3), 1);
test('predIndexInt', SortedArray.findPredecessorIndexInInterval(a1234, 0, Interval.ofRange(2, 3)), 2);
testI('findRange', SortedArray.findRange(a2468, 2, 4), Interval.ofRange(0, 1));
});
describe('ordered set', () => { describe('ordered set', () => {
function ordSetToArray(set: OrderedSet) { function ordSetToArray(set: OrderedSet) {
const ret = []; const ret = [];
......
/**
* Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import IntTuple from '../int-tuple'
export const Empty = IntTuple.Zero;
export function ofRange(min: number, max: number) { return max < min ? Empty : IntTuple.create(min, max + 1); }
export function ofBounds(min: number, max: number) { return max <= min ? Empty : IntTuple.create(min, max); }
export const is = IntTuple.is;
export const start = IntTuple.fst;
export const end = IntTuple.snd;
export const min = IntTuple.fst;
export function max(i: IntTuple) { return IntTuple.snd(i) - 1; }
export function size(i: IntTuple) { return IntTuple.snd(i) - IntTuple.fst(i); }
export const hashCode = IntTuple.hashCode;
export function has(int: IntTuple, v: number) { return IntTuple.fst(int) <= v && v < IntTuple.snd(int); }
export function indexOf(int: IntTuple, x: number) { const m = start(int); return x >= m && x < end(int) ? x - m : -1; }
export function getAt(int: IntTuple, i: number) { return IntTuple.fst(int) + i; }
export const areEqual = IntTuple.areEqual;
export function areIntersecting(a: IntTuple, b: IntTuple) {
const sa = size(a), sb = size(b);
if (sa === 0 && sb === 0) return true;
return sa > 0 && sb > 0 && max(a) >= min(b) && min(a) <= max(b);
}
export function isSubInterval(a: IntTuple, b: IntTuple) {
if (!size(a)) return size(b) === 0;
if (!size(b)) return true;
return start(a) <= start(b) && end(a) >= end(b);
}
export function findPredecessorIndex(int: IntTuple, v: number) {
const s = start(int);
if (v <= s) return 0;
const e = end(int);
if (v >= e) return e - s;
return v - s;
}
export function findPredecessorIndexInInterval(int: IntTuple, x: number, bounds: IntTuple) {
const ret = findPredecessorIndex(int, x);
const s = start(bounds), e = end(bounds);
return ret <= s ? s : ret >= e ? e : ret;
}
export function findRange(int: IntTuple, min: number, max: number) {
return ofBounds(findPredecessorIndex(int, min), findPredecessorIndex(int, max + 1));
}
export function intersect(a: IntTuple, b: IntTuple) {
if (!areIntersecting(a, b)) return Empty;
return ofBounds(Math.max(start(a), start(b)), Math.min(end(a), end(b)));
}
\ 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 { sortArray } from '../sort'
import { hash3, hash4 } from '../hash-functions'
import Interval from '../interval'
type Nums = ArrayLike<number>
export function ofSortedArray(xs: Nums) {
if (xs.length < 1) throw new Error('Sorted arrays must be non-empty.');
return xs;
}
export function ofUnsortedArray(xs: Nums) { sortArray(xs); return xs; }
export function is(xs: any): xs is Nums { return xs && (xs instanceof Array || !!xs.buffer); }
export function start(xs: Nums) { return xs[0]; }
export function end(xs: Nums) { return xs[xs.length - 1] + 1; }
export function min(xs: Nums) { return xs[0]; }
export function max(xs: Nums) { return xs[xs.length - 1]; }
export function size(xs: Nums) { return xs.length; }
export function hashCode(xs: Nums) {
// hash of tuple (size, min, max, mid)
const s = xs.length;
if (!s) return 0;
if (s > 2) return hash4(s, xs[0], xs[s - 1], xs[s << 1]);
return hash3(s, xs[0], xs[s - 1]);
}
export function indexOf(xs: Nums, v: number) {
const l = xs.length;
return l === 0 ? -1 : xs[0] <= v && v <= xs[l - 1] ? binarySearchRange(xs, v, 0, l) : -1;
}
export function has(xs: Nums, v: number) { return indexOf(xs, v) >= 0; }
export function getAt(xs: Nums, i: number) { return xs[i]; }
export function areEqual(a: Nums, b: Nums) {
if (a === b) return true;
const aSize = a.length;
if (aSize !== b.length || a[0] !== b[0] || a[aSize - 1] !== b[aSize - 1]) return false;
for (let i = 0; i < aSize; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
export function findPredecessorIndex(xs: Nums, v: number) {
const len = xs.length;
if (v <= xs[0]) return 0;
if (v > xs[len - 1]) return len;
return binarySearchPredIndexRange(xs, v, 0, len);
}
export function findPredecessorIndexInInterval(xs: Nums, v: number, bounds: Interval) {
const s = Interval.start(bounds), e = Interval.end(bounds);
if (v <= xs[s]) return s;
if (e > s && v > xs[e - 1]) return e;
return binarySearchPredIndexRange(xs, v, s, e);
}
export function findRange(xs: Nums, min: number, max: number) {
return Interval.ofBounds(findPredecessorIndex(xs, min), findPredecessorIndex(xs, max + 1));
}
function binarySearchRange(xs: Nums, value: number, start: number, end: number) {
let min = start, max = end - 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 binarySearchPredIndexRange(xs: Nums, value: number, start: number, end: number) {
let min = start, max = end - 1;
while (min < max) {
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;
}
if (min > max) return max + 1;
return xs[min] >= value ? min : min + 1;
}
\ No newline at end of file
...@@ -4,44 +4,39 @@ ...@@ -4,44 +4,39 @@
* @author David Sehnal <david.sehnal@gmail.com> * @author David Sehnal <david.sehnal@gmail.com>
*/ */
import IntTuple from './int-tuple' import * as Impl from './impl/interval'
/** Closed/Open inteval [a, b) (iterate as a <= i < b) */
namespace Interval { namespace Interval {
export const Empty: Interval = Impl.Empty as any; export const Empty: Interval = Impl.Empty as any;
// export const create: (min: number, max: number) => Interval = Impl.create as any; /** Create interval [min, max] */
export const ofRange: (min: number, max: number) => Interval = Impl.ofRange as any;
/** Create interval [min, max) */
export const ofBounds: (start: number, end: number) => Interval = Impl.ofBounds as any;
export const is: (v: any) => v is Interval = Impl.is as any;
// export const indexOf: (set: Interval, x: number) => number = Impl.indexOf as any; export const has: (interval: Interval, x: number) => boolean = Impl.has as any;
// export const getAt: (set: Interval, i: number) => number = Impl.getAt as any; export const indexOf: (interval: Interval, x: number) => number = Impl.indexOf as any;
export const getAt: (interval: Interval, i: number) => number = Impl.getAt as any;
// export const start: (set: Interval) => number = Impl.start as any; export const start: (interval: Interval) => number = Impl.start as any;
// export const end: (set: Interval) => number = Impl.end as any; export const end: (interval: Interval) => number = Impl.end as any;
export const min: (interval: Interval) => number = Impl.min as any;
export const max: (interval: Interval) => number = Impl.max as any;
export const size: (interval: Interval) => number = Impl.size as any;
export const hashCode: (interval: Interval) => number = Impl.hashCode as any;
// export const min: (set: Interval) => number = Impl.min as any; export const areEqual: (a: Interval, b: Interval) => boolean = Impl.areEqual as any;
// export const max: (set: Interval) => number = Impl.max as any; export const areIntersecting: (a: Interval, b: Interval) => boolean = Impl.areIntersecting as any;
// export const size: (set: Interval) => number = Impl.size as any; export const isSubInterval: (a: Interval, b: Interval) => boolean = Impl.isSubInterval as any;
// export const hashCode: (set: Interval) => number = Impl.hashCode as any;
// export const areEqual: (a: Interval, b: Interval) => boolean = Impl.areEqual as any; export const findPredecessorIndex: (interval: Interval, x: number) => number = Impl.findPredecessorIndex as any;
// export const areIntersecting: (a: Interval, b: Interval) => boolean = Impl.areIntersecting as any; export const findPredecessorIndexInInterval: (interval: Interval, x: number, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any;
// export const isSubset: (a: Interval, b: Interval) => boolean = Impl.isSubset as any; export const findRange: (interval: Interval, min: number, max: number) => Interval = Impl.findRange as any;
export const intersect: (a: Interval, b: Interval) => Interval = Impl.intersect as any;
} }
interface Interval { '@type': 'int-interval' } interface Interval { '@type': 'int-interval' }
export default Interval export default Interval
\ No newline at end of file
namespace Impl {export const Empty = IntTuple.Zero;
export function create(min: number, max: number) { return max < min ? Empty : IntTuple.create(min, max + 1); }
export const start = IntTuple.fst
export const end = IntTuple.snd
export const min = IntTuple.fst
export function max(i: IntTuple) { return IntTuple.snd(i) + 1; }
export function size(i: IntTuple) { return IntTuple.snd(i) - IntTuple.fst(i); }
export function has(int: IntTuple, v: number) { return IntTuple.fst(int) <= v && v < IntTuple.snd(int); }
}
\ 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 * as Impl from './impl/sorted-array'
import Interval from './interval'
namespace SortedArray {
/** Create interval [min, max] */
export const create: (xs: ArrayLike<number>) => SortedArray = Impl.ofUnsortedArray as any;
/** Create interval [min, max) */
export const ofSortedArray: (xs: ArrayLike<number>) => SortedArray = Impl.ofSortedArray as any;
export const is: (v: any) => v is Interval = Impl.is as any;
export const has: (interval: SortedArray, x: number) => boolean = Impl.has as any;
export const indexOf: (interval: SortedArray, x: number) => number = Impl.indexOf as any;
export const getAt: (interval: SortedArray, i: number) => number = Impl.getAt as any;
export const start: (interval: SortedArray) => number = Impl.start as any;
export const end: (interval: SortedArray) => number = Impl.end as any;
export const min: (interval: SortedArray) => number = Impl.min as any;
export const max: (interval: SortedArray) => number = Impl.max as any;
export const size: (interval: SortedArray) => number = Impl.size as any;
export const hashCode: (interval: SortedArray) => number = Impl.hashCode as any;
export const areEqual: (a: SortedArray, b: SortedArray) => boolean = Impl.areEqual as any;
export const findPredecessorIndex: (interval: SortedArray, x: number) => number = Impl.findPredecessorIndex as any;
export const findPredecessorIndexInInterval: (interval: SortedArray, x: number, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any;
export const findRange: (interval: SortedArray, min: number, max: number) => Interval = Impl.findRange as any;
}
interface SortedArray { '@type': 'int-sorted-array' }
export default SortedArray
\ 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