name:
src/main/js/models/common/Range.ts
-rw-r--r--
2979
1import { Compare } from "../../utils/Compare";
2
3/**
4 * Range is essentially Google Guava's Range: https://github.com/google/guava/wiki/RangesExplained.
5 */
6export class Range {
7 readonly lower: number | null;
8 readonly upper: number | null;
9
10 constructor(lower: number | null, upper: number | null) {
11 this.lower = lower;
12 this.upper = upper;
13 }
14
15 /**
16 * Create a closed range.
17 * @param lower - lower bound.
18 * @param upper - upper bound.
19 */
20 static closed(lower: number, upper: number): Range {
21 return new Range(lower, upper);
22 }
23
24 /**
25 * Create a range with a lower bound, but no upper bound.
26 * @param lower - lower bound.
27 */
28 static atLeast(lower: number): Range {
29 return new Range(lower, null);
30 }
31
32 /**
33 * Returns the minimal range that encloses both this range and other. If the ranges are both connected, this is their
34 * union.
35 * @param other - other range
36 */
37 span(other: Range): Range {
38 const lowerCmp = Compare.numberComparison(this.lower, other.lower);
39 const upperCmp = Compare.numberComparison(this.upper, other.upper);
40 if (lowerCmp <= 0 && upperCmp >= 0) {
41 return this;
42 } else if (lowerCmp >= 0 && upperCmp <= 0) {
43 return other;
44 } else {
45 const newLower = lowerCmp <= 0 ? this.lower : other.lower;
46 const newUpper = upperCmp >= 0 ? this.upper : other.upper;
47 return new Range(newLower, newUpper);
48 }
49 }
50
51 /**
52 * Tests if these ranges are connected
53 * @param other - other range.
54 */
55 isConnected(other: Range): boolean {
56 return (
57 Compare.numberComparison(this.lower, other.upper === null ? Infinity : other.upper) <= 0 &&
58 Compare.numberComparison(other.lower, this.upper === null ? Infinity : this.upper) <= 0
59 );
60 }
61
62 /**
63 * Returns the maximal range enclosed by both this range and other (which exists iff these ranges are connected).
64 * @param connectedRange - connected range.
65 */
66 intersection(connectedRange: Range): Range {
67 const lowerCmp = Compare.numberComparison(this.lower, connectedRange.lower);
68 const upperCmp = Compare.numberComparison(this.upper, connectedRange.upper);
69 if (lowerCmp >= 0 && upperCmp <= 0) {
70 return this;
71 } else if (lowerCmp <= 0 && upperCmp >= 0) {
72 return connectedRange;
73 } else {
74 const newLower = lowerCmp >= 0 ? this.lower : connectedRange.lower;
75 const newUpper = upperCmp <= 0 ? this.upper : connectedRange.upper;
76 return new Range(newLower, newUpper);
77 }
78 }
79
80 /**
81 * Returns the lower endpoint of this range.
82 */
83 lowerEndpoint(): number {
84 return this.lower;
85 }
86
87 /**
88 * Returns the upper endpoint of this range.
89 */
90 upperEndpoint(): number {
91 return this.upper;
92 }
93
94 /**
95 * Tests if this range uas an upper bound.
96 */
97 hasUpperBound(): boolean {
98 return this.upper !== null;
99 }
100
101 /**
102 * Tests if this range has a lower bound.
103 */
104 hasLowerBound(): boolean {
105 return this.lower !== null;
106 }
107}