fix #189 (.max, .maxOrNull, .min, .minOrNull) for Iterable<int> and Iterable<double> (#212)
* add .min / .max on Iterable<num> to special case NaN and added tests
* fixes #189
diff --git a/AUTHORS b/AUTHORS
index e8063a8..76494b6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -4,3 +4,4 @@
# Name/Organization <email address>
Google Inc.
+TimWhiting tim@whitings.org
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68333cb..85f17ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
## 1.16.0-dev
* Use a stable sort algorithm in the `IterableExtension.sortedBy` method.
+* Add `min`, `max`, `minOrNull` and `maxOrNull` getters to `IterableDoubleExtension`, `IterableNumberExtension` and `IterableIntegerExtension`
## 1.15.0
diff --git a/lib/src/iterable_extensions.dart b/lib/src/iterable_extensions.dart
index 942e7af..fae5f8e 100644
--- a/lib/src/iterable_extensions.dart
+++ b/lib/src/iterable_extensions.dart
@@ -567,7 +567,64 @@
}
/// Extensions that apply to iterables of numbers.
+///
+/// Specialized version of some extensions of [IterableComparableExtension]
+/// since doubles require special handling of [double.nan].
extension IterableNumberExtension on Iterable<num> {
+ /// A minimal element of the iterable, or `null` it the iterable is empty.
+ num? get minOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ if (value.isNaN) {
+ return value;
+ }
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue.isNaN) {
+ return newValue;
+ }
+ if (newValue < value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A minimal element of the iterable.
+ ///
+ /// The iterable must not be empty.
+ num get min => minOrNull ?? (throw StateError('No element'));
+
+ /// A maximal element of the iterable, or `null` if the iterable is empty.
+ num? get maxOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ if (value.isNaN) {
+ return value;
+ }
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue.isNaN) {
+ return newValue;
+ }
+ if (newValue > value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A maximal element of the iterable.
+ ///
+ /// The iterable must not be empty.
+ num get max => maxOrNull ?? (throw StateError('No element'));
+
/// The sum of the elements.
///
/// The sum is zero if the iterable is empty.
@@ -599,8 +656,51 @@
/// Extension on iterables of integers.
///
-/// Specialized version of some extensions of [IterableNumberExtension].
+/// Specialized version of some extensions of [IterableNumberExtension] or
+/// [IterableComparableExtension] since integers are only `Comparable<num>`.
extension IterableIntegerExtension on Iterable<int> {
+ /// A minimal element of the iterable, or `null` it the iterable is empty.
+ int? get minOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue < value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A minimal element of the iterable.
+ ///
+ /// The iterable must not be empty.
+ int get min => minOrNull ?? (throw StateError('No element'));
+
+ /// A maximal element of the iterable, or `null` if the iterable is empty.
+ int? get maxOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue > value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A maximal element of the iterable.
+ ///
+ /// The iterable must not be empty.
+ int get max => maxOrNull ?? (throw StateError('No element'));
+
/// The sum of the elements.
///
/// The sum is zero if the iterable is empty.
@@ -641,8 +741,63 @@
/// Extension on iterables of double.
///
-/// Specialized version of some extensions of [IterableNumberExtension].
+/// Specialized version of some extensions of [IterableNumberExtension] or
+/// [IterableComparableExtension] since doubles are only `Comparable<num>`.
extension IterableDoubleExtension on Iterable<double> {
+ /// A minimal element of the iterable, or `null` it the iterable is empty.
+ double? get minOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ if (value.isNaN) {
+ return value;
+ }
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue.isNaN) {
+ return newValue;
+ }
+ if (newValue < value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A minimal element of the iterable.
+ ///
+ /// The iterable must not be empty.
+ double get min => minOrNull ?? (throw StateError('No element'));
+
+ /// A maximal element of the iterable, or `null` if the iterable is empty.
+ double? get maxOrNull {
+ var iterator = this.iterator;
+ if (iterator.moveNext()) {
+ var value = iterator.current;
+ if (value.isNaN) {
+ return value;
+ }
+ while (iterator.moveNext()) {
+ var newValue = iterator.current;
+ if (newValue.isNaN) {
+ return newValue;
+ }
+ if (newValue > value) {
+ value = newValue;
+ }
+ }
+ return value;
+ }
+ return null;
+ }
+
+ /// A maximal element of the iterable.
+ ///
+ /// The iterable must not be empty.
+ double get max => maxOrNull ?? (throw StateError('No element'));
+
/// The sum of the elements.
///
/// The sum is zero if the iterable is empty.
@@ -689,26 +844,12 @@
}
return value;
}
- return null;
}
/// A minimal element of the iterable.
///
/// The iterable must not be empty.
- T get min {
- var iterator = this.iterator;
- if (iterator.moveNext()) {
- var value = iterator.current;
- while (iterator.moveNext()) {
- var newValue = iterator.current;
- if (value.compareTo(newValue) > 0) {
- value = newValue;
- }
- }
- return value;
- }
- throw StateError('No element');
- }
+ T get min => minOrNull ?? (throw StateError('No element'));
/// A maximal element of the iterable, or `null` if the iterable is empty.
T? get maxOrNull {
@@ -729,20 +870,7 @@
/// A maximal element of the iterable.
///
/// The iterable must not be empty.
- T get max {
- var iterator = this.iterator;
- if (iterator.moveNext()) {
- var value = iterator.current;
- while (iterator.moveNext()) {
- var newValue = iterator.current;
- if (value.compareTo(newValue) < 0) {
- value = newValue;
- }
- }
- return value;
- }
- throw StateError('No element');
- }
+ T get max => maxOrNull ?? (throw StateError('No element'));
/// Creates a sorted list of the elements of the iterable.
///
diff --git a/test/extensions_test.dart b/test/extensions_test.dart
index 51aac9e..741a3fd 100644
--- a/test/extensions_test.dart
+++ b/test/extensions_test.dart
@@ -887,6 +887,90 @@
expect(iterable(<num>[1, 3, 5, 9]).average, 4.5);
});
});
+ group('.min', () {
+ test('empty', () {
+ expect(() => iterable(<int>[]).min, throwsStateError);
+ expect(() => iterable(<double>[]).min, throwsStateError);
+ expect(() => iterable(<num>[]).min, throwsStateError);
+ });
+ test('single', () {
+ expect(iterable(<int>[1]).min, 1);
+ expect(iterable(<double>[1.0]).min, 1.0);
+ expect(iterable(<num>[1.0]).min, 1.0);
+ });
+ test('multiple', () {
+ expect(iterable(<int>[3, 1, 2]).min, 1);
+ expect(iterable(<double>[3.0, 1.0, 2.5]).min, 1.0);
+ expect(iterable(<num>[3, 1, 2.5]).min, 1.0);
+ });
+ test('nan', () {
+ expect(iterable(<double>[3.0, 1.0, double.nan]).min, isNaN);
+ expect(iterable(<num>[3.0, 1, double.nan]).min, isNaN);
+ });
+ });
+ group('.minOrNull', () {
+ test('empty', () {
+ expect(iterable(<int>[]).minOrNull, null);
+ expect(iterable(<double>[]).minOrNull, null);
+ expect(iterable(<num>[]).minOrNull, null);
+ });
+ test('single', () {
+ expect(iterable(<int>[1]).minOrNull, 1);
+ expect(iterable(<double>[1.0]).minOrNull, 1.0);
+ expect(iterable(<num>[1.0]).minOrNull, 1.0);
+ });
+ test('multiple', () {
+ expect(iterable(<int>[3, 1, 2]).minOrNull, 1);
+ expect(iterable(<double>[3.0, 1.0, 2.5]).minOrNull, 1.0);
+ expect(iterable(<num>[3, 1, 2.5]).minOrNull, 1.0);
+ });
+ test('nan', () {
+ expect(iterable(<double>[3.0, 1.0, double.nan]).minOrNull, isNaN);
+ expect(iterable(<num>[3.0, 1, double.nan]).minOrNull, isNaN);
+ });
+ });
+ group('.max', () {
+ test('empty', () {
+ expect(() => iterable(<int>[]).max, throwsStateError);
+ expect(() => iterable(<double>[]).max, throwsStateError);
+ expect(() => iterable(<num>[]).max, throwsStateError);
+ });
+ test('single', () {
+ expect(iterable(<int>[1]).max, 1);
+ expect(iterable(<double>[1.0]).max, 1.0);
+ expect(iterable(<num>[1.0]).max, 1.0);
+ });
+ test('multiple', () {
+ expect(iterable(<int>[3, 1, 2]).max, 3);
+ expect(iterable(<double>[3.0, 1.0, 2.5]).max, 3.0);
+ expect(iterable(<num>[3, 1, 2.5]).max, 3);
+ });
+ test('nan', () {
+ expect(iterable(<double>[3.0, 1.0, double.nan]).max, isNaN);
+ expect(iterable(<num>[3.0, 1, double.nan]).max, isNaN);
+ });
+ });
+ group('.maxOrNull', () {
+ test('empty', () {
+ expect(iterable(<int>[]).maxOrNull, null);
+ expect(iterable(<double>[]).maxOrNull, null);
+ expect(iterable(<num>[]).maxOrNull, null);
+ });
+ test('single', () {
+ expect(iterable(<int>[1]).maxOrNull, 1);
+ expect(iterable(<double>[1.0]).maxOrNull, 1.0);
+ expect(iterable(<num>[1.0]).maxOrNull, 1.0);
+ });
+ test('multiple', () {
+ expect(iterable(<int>[3, 1, 2]).maxOrNull, 3);
+ expect(iterable(<double>[3.0, 1.0, 2.5]).maxOrNull, 3.0);
+ expect(iterable(<num>[3, 1, 2.5]).maxOrNull, 3);
+ });
+ test('nan', () {
+ expect(iterable(<double>[3.0, 1.0, double.nan]).maxOrNull, isNaN);
+ expect(iterable(<num>[3.0, 1, double.nan]).maxOrNull, isNaN);
+ });
+ });
});
group('of iterable', () {
group('.flattened', () {