Skip to content

Commit

Permalink
Make DoubleRange.of const constructable (and other optimizations).
Browse files Browse the repository at this point in the history
  • Loading branch information
renggli committed Jan 20, 2024
1 parent b677f1f commit 0d1aafb
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 92 deletions.
94 changes: 41 additions & 53 deletions lib/src/collection/range/double.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import '../../../comparator.dart';
import '../range.dart';

/// A virtual range of doubles containing an arithmetic progressions.
/// A range of doubles containing an arithmetic progressions.
///
/// The progression is defined by a `start`, `stop` and `step` parameter. A
/// range essentially implements a lazy list that is also produced by the
Expand All @@ -11,8 +11,7 @@ import '../range.dart';
/// ...
///
class DoubleRange extends Range<double> {
/// Creates a virtual range of numbers containing an arithmetic progressions
/// of double values.
/// Creates an arithmetic progressions of [double] values.
///
/// The constructor called without any arguments returns the empty range.
/// For example, `DoubleRange()` yields `<double>[]`.
Expand All @@ -23,49 +22,50 @@ class DoubleRange extends Range<double> {
///
/// The constructor called with two arguments returns the range between
/// the two numbers (including the start, but excluding the end). For example,
/// `DoubleRange(3.0, 6.0)` yields `<double>[3.0, 4.0, 5.0]`.
/// `DoubleRange(3.0, 6.0)` yields `<double>[3.0, 4.0, 5.0]`. If the second
/// number is smaller than the first return a decreasing range, for example
/// `DoubleRange(6.0, 3.0)` yields `<double>[6.0, 5.0, 4.0]`.
///
/// The constructor called with three arguments returns the range between
/// the first two numbers (including the start, but excluding the end) and the
/// step value. For example, `DoubleRange(1.0, 7.0, 2.1)` yields
/// `<double>[1.0, 3.1, 5.2]`.
factory DoubleRange([double? a, double? b, double? c]) {
var start = 0.0;
var end = 0.0;
var step = 1.0;
if (c != null) {
start = a!;
end = b!;
step = c;
} else if (b != null) {
start = a!;
end = b;
step = start <= end ? 1.0 : -1.0;
} else if (a != null) {
end = a;
if (a != null && b != null && c != null) {
if (c == 0) throw ArgumentError.value(c, 'step');
return DoubleRange.of(start: a, end: b, step: c);
} else if (a != null && b != null && c == null) {
return DoubleRange.of(start: a, end: b);
} else if (a != null && b == null && c == null) {
return DoubleRange.of(end: a, step: 1);
} else if (a == null && b == null && c == null) {
return const DoubleRange._c3(0, 0, 1);
}
if (start < end) {
if (step == 1.0) {
return DoubleRange._(start, end, step, (end - start).ceil());
} else if (step > 0.0) {
return DoubleRange._(start, end, step, ((end - start) / step).ceil());
}
} else if (start > end) {
if (step == -1.0) {
return DoubleRange._(start, end, step, (start - end).ceil());
} else if (step < 0.0) {
return DoubleRange._(start, end, step, ((start - end) / -step).ceil());
}
} else {
if (step != 0) {
return DoubleRange._(start, end, step, 0);
}
}
throw ArgumentError.value(
step, 'step', 'Invalid step size for range $start..$end');
throw ArgumentError('Invalid range: $a, $b, $c');
}

DoubleRange._(this.start, this.end, this.step, this.length);
/// Const constructor to create an arithmetic progressions of [double] values
/// between [start] (inclusive) and [end] (exclusive); and a step-value
/// [step].
const DoubleRange.of({double start = 0, double end = 0, double? step})
: this._c3(start, end, step ?? (start <= end ? 1 : -1));

// Internal const-constructor that infers the length.
const DoubleRange._c3(double start, double end, double step)
: this._c4(
start,
end,
step,
0 < step && start < end
? (end - start) ~/ step + ((end - start) % step > 0 ? 1 : 0)
: 0 > step && start > end
? (start - end) ~/ -step +
((start - end) % -step > 0 ? 1 : 0)
: 0);

// Internal const-constructor that initializes all state.
const DoubleRange._c4(this.start, this.end, this.step, this.length)
: assert(step != 0, 'step must not be zero');

@override
final double start;
Expand Down Expand Up @@ -99,33 +99,21 @@ class DoubleRange extends Range<double> {

@override
DoubleRange get reversed =>
isEmpty ? this : DoubleRange._(last, first - step, -step, length);
DoubleRange._c4(start + (length - 1) * step, start - step, -step, length);

@override
DoubleRange getRange(int startIndex, int endIndex) {
RangeError.checkValidRange(startIndex, endIndex, length);
return DoubleRange._(start + startIndex * step, start + endIndex * step,
return DoubleRange._c4(start + startIndex * step, start + endIndex * step,
step, endIndex - startIndex);
}

@override
String toString() {
if (length == 0) {
return 'DoubleRange()';
} else if (start == 0.0 && step == 1.0) {
return 'DoubleRange($end)';
} else if (step == 1.0) {
return 'DoubleRange($start, $end)';
} else {
return 'DoubleRange($start, $end, $step)';
}
}
}

extension DoubleRangeExtension on double {
/// Shorthand to create a range of [double] numbers, starting with the
/// receiver (inclusive) up to but not including [end] (exclusive).
Range<double> to(double end, {double? step}) => DoubleRange(this, end, step);
Range<double> to(double end, {double? step}) =>
DoubleRange.of(start: this, end: end, step: step);
}

const _naturalComparator = naturalComparable<num>;
Expand Down
98 changes: 59 additions & 39 deletions test/collection_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3411,15 +3411,13 @@ void main() {
});
test('positive step size', () {
for (var end = 31; end <= 40; end++) {
final range = IntegerRange(10, end, 10);
verifyRange(range,
verifyRange(IntegerRange(10, end, 10),
included: [10, 20, 30], excluded: [5, 15, 25, 35, 40]);
}
});
test('negative step size', () {
for (var end = 9; end >= 0; end--) {
final range = IntegerRange(30, end, -10);
verifyRange(range,
verifyRange(IntegerRange(30, end, -10),
included: [30, 20, 10], excluded: [0, 5, 15, 25, 35]);
}
});
Expand All @@ -3432,7 +3430,7 @@ void main() {
included: [2, 4], excluded: [1, 3, 5, 6]);
});
test('shorthand', () {
verifyRange(0.to(3), included: [0, 1, 2], excluded: [-1, 4]);
verifyRange(0.to(3), included: [0, 1, 2], excluded: [-1, 3]);
verifyRange(3.to(0), included: [3, 2, 1], excluded: [4, 0]);
verifyRange(2.to(8, step: 2),
included: [2, 4, 6], excluded: [1, 3, 5, 7, 8]);
Expand Down Expand Up @@ -3552,62 +3550,91 @@ void main() {
verifyRange(DoubleRange(), included: [], excluded: [0.0]);
});
test('1 argument', () {
verifyRange(DoubleRange(0.0),
verifyRange(DoubleRange(-1),
included: [], excluded: [-1.0, 0.0, 1.0]);
verifyRange(DoubleRange(1.0), included: [0.0], excluded: [-1.0, 1.0]);
verifyRange(DoubleRange(2.0),
verifyRange(DoubleRange(0), included: [], excluded: [-1.0, 0.0, 1.0]);
verifyRange(DoubleRange(1), included: [0.0], excluded: [-1.0, 1.0]);
verifyRange(DoubleRange(2),
included: [0.0, 1.0], excluded: [-1.0, 2.0]);
verifyRange(DoubleRange(3.0),
verifyRange(DoubleRange(3),
included: [0.0, 1.0, 2.0], excluded: [-1.0, 3.0]);
});
test('2 argument', () {
verifyRange(DoubleRange(0.0, 0.0),
verifyRange(DoubleRange(0, 0),
included: [], excluded: [-1.0, 0.0, 1.0]);
verifyRange(DoubleRange(0.0, 4.0),
verifyRange(DoubleRange(0, 4),
included: [0.0, 1.0, 2.0, 3.0], excluded: [-1.0, 4.0]);
verifyRange(DoubleRange(5.0, 9.0),
verifyRange(DoubleRange(4, 0),
included: [4.0, 3.0, 2.0, 1.0], excluded: [5.0, 0.0]);
verifyRange(DoubleRange(5, 9),
included: [5.0, 6.0, 7.0, 8.0], excluded: [4.0, 9.0]);
verifyRange(DoubleRange(9.0, 5.0),
verifyRange(DoubleRange(9, 5),
included: [9.0, 8.0, 7.0, 6.0], excluded: [10.0, 5.0]);
});
test('3 argument (positive step)', () {
verifyRange(DoubleRange(0.0, 0.0, 1.0),
verifyRange(DoubleRange(0, 0, 1),
included: [], excluded: [-1.0, 0.0, 1.0]);
verifyRange(DoubleRange(2.0, 8.0, 1.5),
verifyRange(DoubleRange(2, 8, 1.5),
included: [2.0, 3.5, 5.0, 6.5], excluded: [0.5, 3.0, 8.0]);
verifyRange(DoubleRange(3.0, 8.0, 1.5),
verifyRange(DoubleRange(3, 8, 1.5),
included: [3.0, 4.5, 6.0, 7.5], excluded: [1.5, 5.0, 9.0]);
verifyRange(DoubleRange(4.0, 8.0, 1.5),
included: [4.0, 5.5, 7.0], excluded: [3.5, 5.0, 6.0, 8.5]);
verifyRange(DoubleRange(2.0, 7.0, 1.5),
verifyRange(DoubleRange(4, 8, 1.5),
included: [4.0, 5.5, 7.0], excluded: [3.5, 5, 6, 8.5]);
verifyRange(DoubleRange(2, 7, 1.5),
included: [2.0, 3.5, 5.0, 6.5], excluded: [0.5, 4.0, 8.0]);
verifyRange(DoubleRange(2.0, 6.0, 1.5),
verifyRange(DoubleRange(2, 6, 1.5),
included: [2.0, 3.5, 5.0], excluded: [0.5, 3.0, 4.0, 6.0]);
});
test('3 argument (negative step)', () {
verifyRange(DoubleRange(0.0, 0.0, -1.0),
verifyRange(DoubleRange(0, 0, -1),
included: [], excluded: [-1.0, 0.0, 1.0]);
verifyRange(DoubleRange(8.0, 2.0, -1.5),
verifyRange(DoubleRange(8, 2, -1.5),
included: [8.0, 6.5, 5.0, 3.5], excluded: [9.5, 6.0, 2.0]);
verifyRange(DoubleRange(8.0, 3.0, -1.5),
verifyRange(DoubleRange(8, 3, -1.5),
included: [8.0, 6.5, 5.0, 3.5], excluded: [9.5, 6.0, 2.0]);
verifyRange(DoubleRange(8.0, 4.0, -1.5),
verifyRange(DoubleRange(8, 4, -1.5),
included: [8.0, 6.5, 5.0], excluded: [9.5, 5.5, 3.5]);
verifyRange(DoubleRange(7.0, 2.0, -1.5),
included: [7.0, 5.5, 4.0, 2.5], excluded: [8.5, 3.0, 2.0]);
verifyRange(DoubleRange(6.0, 2.0, -1.5),
verifyRange(DoubleRange(7, 2, -1.5),
included: [7.0, 5.5, 4.0, 2.5], excluded: [8.5, 3, 2.0]);
verifyRange(DoubleRange(6, 2, -1.5),
included: [6.0, 4.5, 3.0], excluded: [7.5, 4.0, 1.5]);
});
test('positive step size', () {
for (var end = 31; end <= 40; end++) {
verifyRange(DoubleRange(10, end.toDouble(), 10),
included: [10.0, 20.0, 30.0],
excluded: [5.0, 15.0, 25.0, 35.0, 40.0]);
}
});
test('negative step size', () {
for (var end = 9; end >= 0; end--) {
verifyRange(DoubleRange(30, end.toDouble(), -10),
included: [30.0, 20.0, 10.0],
excluded: [0.0, 5.0, 15.0, 25.0, 35.0]);
}
});
test('const', () {
verifyRange(const DoubleRange.of(end: 2),
included: [0.0, 1.0], excluded: [-1.0, 2.0]);
verifyRange(const DoubleRange.of(start: 2, end: 0),
included: [2.0, 1.0], excluded: [0.0, 3.0]);
verifyRange(const DoubleRange.of(start: 2, end: 3, step: 0.5),
included: [2.0, 2.5], excluded: [0.5, 3.0]);
});
test('shorthand', () {
verifyRange(0.0.to(3.0),
included: [0.0, 1.0, 2.0], excluded: [-1.0, 0.5, 1.5, 2.5, 3.0]);
included: [0.0, 1.0, 2.0], excluded: [-1.0, 3.0]);
verifyRange(3.0.to(0.0),
included: [3.0, 2.0, 1.0], excluded: [4.0, 0.0]);
verifyRange(4.0.to(8.0, step: 1.5),
included: [4.0, 5.5, 7.0], excluded: [2.5, 5.0, 6.0, 8.5]);
included: [4.0, 5.5, 7.0], excluded: [5.0, 6.0, 6.5, 8.0]);
verifyRange(8.0.to(4.0, step: -1.5),
included: [8.0, 6.5, 5.0], excluded: [3.5, 4.0, 6.0, 7.5]);
});
test('invalid', () {
expect(() => DoubleRange(0.0, 2.0, 0.0), throwsArgumentError);
expect(() => DoubleRange(0.0, 2.0, -1.5), throwsArgumentError);
expect(() => DoubleRange(2.0, 0.0, 1.5), throwsArgumentError);
expect(() => DoubleRange(0, 0, 0), throwsArgumentError);
expect(() => DoubleRange(null, 1), throwsArgumentError);
expect(() => DoubleRange(null, null, 1), throwsArgumentError);
});
});
group('sublist', () {
Expand Down Expand Up @@ -3645,13 +3672,6 @@ void main() {
expect(() => DoubleRange(3.0).getRange(0, 4), throwsRangeError);
});
});
test('printing', () {
expect(DoubleRange().toString(), 'DoubleRange()');
expect(DoubleRange(1.2).toString(), 'DoubleRange(1.2)');
expect(DoubleRange(1.2, 3.4).toString(), 'DoubleRange(1.2, 3.4)');
expect(DoubleRange(1.2, 3.4, 0.5).toString(),
'DoubleRange(1.2, 3.4, 0.5)');
});
test('unmodifiable', () {
final list = DoubleRange(1.0, 5.0);
expect(() => list[0] = 5.0, throwsUnsupportedError);
Expand Down

0 comments on commit 0d1aafb

Please sign in to comment.