diff --git a/lib/interval.dart b/lib/interval.dart index 6795478..e7279ba 100644 --- a/lib/interval.dart +++ b/lib/interval.dart @@ -221,6 +221,67 @@ class Interval> { upperClosed: upperClosed); } + /// The maximal interval which is enclosed in each interval in [intervals]. + /// + /// If [intervals] is empty, the returned interval contains all values. + /// + /// If [intervals] do not overlap, `null` is returned. + factory Interval.intersectAll(Iterable> intervals) { + + var iterator = intervals.iterator; + if (!iterator.moveNext()) return new Interval.all(); + var interval = iterator.current; + var lower = interval.lower; + var upper = interval.upper; + var lowerClosed = interval.lowerClosed; + var upperClosed = interval.upperClosed; + while (iterator.moveNext()) { + interval = iterator.current; + if (lower == null) { + lower = interval.lower;; + lowerClosed = interval.lowerClosed; + } else { + if (interval.lower != null) { + var cmp = Comparable.compare(lower, interval.lower); + if (cmp<=0) { + lower = interval.lower; + lowerClosed = (cmp!=0||lowerClosed) && interval.lowerClosed; + } + } + } + if (upper == null) { + upper = interval.upper; + upperClosed = interval.upperClosed; + } else { + if (interval.upper != null) { + var cmp = Comparable.compare(upper, interval.upper); + if (cmp>=0) { + upper = interval.upper; + upperClosed = (cmp!=0||upperClosed) && interval.upperClosed; + } + } + } + } + var cmp = Comparable.compare(lower, upper); + if (cmp>0) return null; + if (cmp==0&&(!upperClosed||!lowerClosed)) return null; + return new Interval( + lower: lower, + upper: upper, + lowerClosed: lowerClosed, + upperClosed: upperClosed); + } + + /// Returns the intersection of `this` interval with [other]. + /// + /// If the intervals do not intersect, `null` is returned. + Interval intersect(Interval other) => new Interval.intersectAll([this,other]); + + /// Returns minimal interval that [encloses] both `this` and [other]. + Interval enclose(Interval other) => new Interval.encloseAll([this,other]); + + + /// Whether `this` contains [test]. bool contains(T test) { if (lower != null) { @@ -261,6 +322,9 @@ class Interval> { return true; } + /// Whether the intersection of `this` and [other] is not empty. + bool intersects(Interval other) => intersect(other)!=null; + /// Whether the union of `this` and [other] is connected (i.e. is an /// [Interval]). bool connectedTo(Interval other) { diff --git a/pubspec.yaml b/pubspec.yaml index c0d061e..a85902c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,4 +4,4 @@ author: Sean Eagan description: Provdes an Interval class, a contiguous set of values. homepage: https://github.com/seaneagan/interval dev_dependencies: - unittest: any + test: any diff --git a/test/interval_test.dart b/test/interval_test.dart index c70cd4b..9d7ae1a 100644 --- a/test/interval_test.dart +++ b/test/interval_test.dart @@ -2,7 +2,7 @@ library interval.test; import 'package:interval/interval.dart'; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; main() { group('Interval', () { @@ -111,6 +111,50 @@ main() { }); + group('intersectAll', () { + + test('should return null if iterable is empty', () { + var interval = new Interval.intersectAll([]); + expect(interval, null); + }); + + test('should return null when input interval do not overlap', () { + var interval = new Interval.intersectAll([ + new Interval.atMost(0), + new Interval.atLeast(1)]); + expect(interval, null); + }); + + test('should have bounds matching extreme input interval bounds', () { + var interval = new Interval.intersectAll([ + new Interval.closed(0, 3), + new Interval.closed(-1, 1), + new Interval.closed(-8, 10), + new Interval.closed(-5, 7)]); + expect(interval.lower, 0); + expect(interval.upper, 1); + }); + + test('should have open bound when any corresponding extreme input ' + 'interval bound does', () { + var interval = new Interval.intersectAll([ + new Interval.closedOpen(0, 1), + new Interval.openClosed(0, 1)]); + expect(interval.lowerClosed, isFalse); + expect(interval.upperClosed, isFalse); + }); + + test('should have closed bound when all extreme input interval bounds ' + 'do', () { + var interval = new Interval.intersectAll([ + new Interval.closed(0, 1), + new Interval.closed(0, 1)]); + expect(interval.lowerClosed, isTrue); + expect(interval.upperClosed, isTrue); + }); + + }); + }); group('contains', () {