Skip to content

Commit

Permalink
add support for interval notation (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
lezram authored Mar 22, 2020
1 parent 0a7aeec commit 0117d42
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 33 deletions.
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,34 @@ console.log(timeInterval);

## API

* new Interval(start: number, end: number, includeStart: boolean, includeEnd: boolean)
* `new Interval(start: number, end: number, includeStart: boolean, includeEnd: boolean)`
Create an interval object
```typescript
new Interval(1, 2, true, false);
```
* Interval.union(interval1, interval2, ...)
* `Interval.from(intervalNotation: string): Interval`
Create an interval object from mathematical interval notation
```typescript
Interval.from("[1,2]");
```
* `Interval.sort(interval1, interval2, ...): Interval[]`
Sort intervals with following order: empty, lowest start, lowest end
* `Interval.union(interval1, interval2, ...): Interval[]`
Union elements and sort
```typescript
const interval1 = new Interval(1, 2, true, true);
const interval2 = new Interval(2, 4, false, false);
const union = Interval.union(interval1, interval2);
// union = new Interval(1, 4, true, false);
```
* `obj.isOverlapping(interval: Interval): boolean`
Checks if passed interval overlaps/intersect
* `obj.contains(numberOrInterval: number | Interval): boolean`
Contains passed number or interval completely
* `obj.isEmpty(): boolean`
If is an empty interval `{}`, `[2,1]`, `(1,1)`, `(1,1]`, `[1,1)`
* `obj.copy(): Interval`
Copy interval
* `obj.isEqual(interval: Interval): boolean`
Check if interval is equal
5 changes: 3 additions & 2 deletions e2e/test-build.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const assert = require("assert");
const IntervalModule = require("../dist/node");
const Interval = IntervalModule.Interval;
const Interval = require("../dist/node").Interval;

const result = new Interval(1, 2);

assert.equal(result.contains(1), true);
assert.deepEqual(result, {start: 1, end: 2, includeStart: true, includeEnd: true});
21 changes: 19 additions & 2 deletions src/Interval.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {IntervalEmpty, IntervalOverlap, IntervalSort, IntervalUnion} from "./interval-util";
import {IntervalEmpty, IntervalFrom, IntervalMatch, IntervalSort, IntervalUnion} from "./interval-util";
import {Util} from "./util/Util";

export class Interval {
Expand Down Expand Up @@ -41,7 +41,20 @@ export class Interval {
}

public isOverlapping(interval: Interval): boolean {
return IntervalOverlap.areOverlapping(this, interval);
return IntervalMatch.isOverlapping(this, interval);
}

public contains(numberOrInterval: number | Interval): boolean {

let interval: Interval = new Interval(null, null);
if (numberOrInterval && numberOrInterval instanceof Interval) {
interval = numberOrInterval;
} else if (numberOrInterval && typeof numberOrInterval === "number") {
const number = Number(numberOrInterval);
interval = new Interval(number, number, true, true);
}

return IntervalMatch.contains(this, interval);
}

public copy(): Interval {
Expand All @@ -66,4 +79,8 @@ export class Interval {
public static union(...intervals: Interval[]): Interval[] {
return IntervalUnion.union(intervals);
}

public static from(intervalNotation: string): Interval {
return IntervalFrom.from(intervalNotation);
}
}
31 changes: 31 additions & 0 deletions src/interval-util/IntervalFrom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {Interval} from "../Interval";

export class IntervalFrom {
public static from(intervalNotation: string): Interval {

if (null === intervalNotation || !(typeof intervalNotation === "string")) {
throw new Error(`Invalid interval definition`);
}

const regex = new RegExp(/^\{\}|\{\s*([^\s\t\n\r,]+)\s*\}|([\(\[\]])\s*([^\s\t\n\r,]+)\s*,\s*([^\s\t\n\r,]+)\s*([\)\]\[])$/);
const matches = regex.exec(intervalNotation);

if (matches && matches.length === 6) {
if (matches[1] !== undefined) {
const start = Number(matches[1]);
const end = Number(matches[1]);
const includeStart = true;
const includeEnd = true;
return new Interval(start, end, includeStart, includeEnd);
} else {
const start = Number(matches[3]);
const end = Number(matches[4]);
const includeStart = (matches[2] === "[");
const includeEnd = (matches[5] === "]");
return new Interval(start, end, includeStart, includeEnd);
}
}

throw new Error(`Invalid interval definition ${intervalNotation}`);
}
}
45 changes: 45 additions & 0 deletions src/interval-util/IntervalMatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {Interval} from "../Interval";

export class IntervalMatch {

public static isOverlapping(interval1: Interval, interval2: Interval): boolean {
if (!this.isIntervalComparable(interval1, interval2)) {
return false;
}

if (interval1.start === interval2.end) {
return (interval1.includeStart || interval2.includeEnd);
}

if (interval1.end === interval2.start) {
return interval1.includeEnd || interval2.includeStart;
}

return (interval1.start <= interval2.start && interval2.start <= interval1.end) ||
(interval1.start <= interval2.end && interval2.end <= interval1.end) ||
(interval2.start <= interval1.start && interval1.start <= interval2.end) ||
(interval2.start <= interval1.end && interval1.end <= interval2.end);
}


public static contains(interval: Interval, intervalToCheck: Interval): boolean {
if (!this.isIntervalComparable(interval, intervalToCheck)) {
return false;
}

const startIsEqual = (interval.start === intervalToCheck.start && (interval.includeStart || !intervalToCheck.includeStart));
const endIsEqual = (interval.end === intervalToCheck.end && (interval.includeEnd || !intervalToCheck.includeEnd));

return ((interval.start < intervalToCheck.start || startIsEqual) &&
(intervalToCheck.end < interval.end || endIsEqual));
}

private static isIntervalComparable(interval1: Interval, interval2: Interval): boolean {
if (!interval1 || !interval2 || interval1.isEmpty() || interval2.isEmpty()) {
return false;
}

return true
}

}
24 changes: 0 additions & 24 deletions src/interval-util/IntervalOverlap.ts

This file was deleted.

3 changes: 2 additions & 1 deletion src/interval-util/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export {IntervalEmpty} from "./IntervalEmpty";
export {IntervalOverlap} from "./IntervalOverlap";
export {IntervalMatch} from "./IntervalMatch";
export {IntervalSort} from "./IntervalSort";
export {IntervalUnion} from "./IntervalUnion";
export {IntervalFrom} from "./IntervalFrom";
12 changes: 12 additions & 0 deletions test/IntervalCreate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ describe("IntervalCreateTest", () => {
});
});

test("testIntervalWithZero", () => {
let interval = new Interval(0, 0);

expect(interval).toEqual({
start: 0,
end: 0,
includeStart: true,
includeEnd: true
});
});

test.each([
[{s: null, e: null}],
[{s: undefined, e: undefined}],
Expand All @@ -38,6 +49,7 @@ describe("IntervalCreateTest", () => {
[2, 1, true, true],
[1, 1, false, true],
[1, 1, true, false],
[NaN, NaN, true, true],
])("testIntervalEmpty (%d, %d, %s, %s)", (start: number, end: number, includeStart: boolean, includeEnd: boolean) => {
let interval = new Interval(start, end, includeStart, includeEnd);

Expand Down
70 changes: 70 additions & 0 deletions test/IntervalFrom.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {Interval} from "../src";

describe("IntervalFromTest", () => {
const testee = Interval;

test.each([
["[", "]", true, true],
["]", "[", false, false],
["[", "[", true, false],
["]", "]", false, true],
["(", ")", false, false],
["(", "]", false, true],
["[", ")", true, false],
["(", "[", false, false],
["]", ")", false, false],
])("testFrom (%s, %s, %s, %s)", (includeStart: string,
includeEnd: string,
includeStartExpected: boolean,
includeEndExpected: boolean) => {

let interval = testee.from(includeStart + '1,3' + includeEnd)

expect(interval).toEqual({
start: 1,
end: 3,
includeStart: includeStartExpected,
includeEnd: includeEndExpected
});
});

test("testFromDegenerated", () => {
let interval = testee.from('{1}')

expect(interval).toEqual({
start: 1,
end: 1,
includeStart: true,
includeEnd: true
});
});

test("testFromEmpty", () => {
let interval = testee.from('{}')

expect(interval).toEqual({
start: null,
end: null,
includeStart: null,
includeEnd: null
});
});

test.each([
"[1,2,3]",
"1",
"1,2",
"a,b]",
"[]",
"()",
213,
{a: 1}
])("testFromInvalidString", (value: string) => {
let from = (): void => {
testee.from(value);
}

expect(from).toThrowError();
});
});

38 changes: 37 additions & 1 deletion test/IntervalOverlap.test.ts → test/IntervalMatch.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Interval} from "../src/";

describe("IntervalOverlapTest", () => {
describe("IntervalMatchTest", () => {

test.each([
[2, 2, true, true],
Expand Down Expand Up @@ -45,6 +45,42 @@ describe("IntervalOverlapTest", () => {
expect(isOverlapping).toBeFalsy();
});

test.each([
2,
3,
4,
5,
new Interval(2, 3, true, true),
new Interval(3, 4, true, true),
new Interval(4, 5, true, true),
])("testContains(%o)", (value: number | Interval) => {
let interval = new Interval(2, 5, true, true);

const contains = interval.contains(value);

expect(contains).toBeTruthy();
});

test.each([
null,
"23",
1,
2,
5,
6,
new Interval(null, null, null, null),
new Interval(1, 2, true, true),
new Interval(1, 3, true, true),
new Interval(4, 7, true, true),
new Interval(5, 7, true, true),
])("testContainsNoMatch(%o)", (value: number | Interval) => {
let interval = new Interval(2, 5, false, false);

const contains = interval.contains(value);

expect(contains).toBeFalsy();
});


});

0 comments on commit 0117d42

Please sign in to comment.