Fix algorithm generics.
Using "T extends Comparable" in these generics doesn't work, because we
want users to be able to pass in non-Comparable arguments with custom
comparator functions. Now if no explicit comparator is used, we rely on
runtime checks to ensure that the objects implement Comparable.
R=rnystrom@google.com
Review URL: https://codereview.chromium.org//1836163002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1425177..9bca49a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 1.4.2
+
+* Fix the types for `binarySearch()` and `lowerBound()` so they no longer
+ require all arguments to be comparable.
+
+* Add generic annotations to `insertionSort()` and `mergeSort()`.
+
## 1.4.1
* Fix all strong mode warnings.
diff --git a/lib/src/algorithms.dart b/lib/src/algorithms.dart
index 57456a0..629951d 100644
--- a/lib/src/algorithms.dart
+++ b/lib/src/algorithms.dart
@@ -4,39 +4,20 @@
import "dart:math" as math;
-/// Version of [binarySearch] optimized for comparable keys
-int _comparableBinarySearch/*<T extends Comparable<T>>*/(
- List<Comparable/*<T>*/> list, Comparable/*<T>*/ value) {
- int min = 0;
- int max = list.length;
- while (min < max) {
- int mid = min + ((max - min) >> 1);
- var element = list[mid];
- int comp = element.compareTo(value);
- if (comp == 0) return mid;
- if (comp < 0) {
- min = mid + 1;
- } else {
- max = mid;
- }
- }
- return -1;
-}
+import "utils.dart";
/// Returns a position of the [value] in [sortedList], if it is there.
///
/// If the list isn't sorted according to the [compare] function, the result
/// is unpredictable.
///
-/// If [compare] is omitted, it defaults to calling [Comparable.compareTo] on
-/// the objects.
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. If any object is not [Comparable], this throws a [CastError].
///
/// Returns -1 if [value] is not in the list by default.
-int binarySearch/*<T extends Comparable<T>>*/(
- List/*<T>*/ sortedList, /*=T*/ value, { int compare(/*=T*/ a, /*=T*/ b) }) {
- if (compare == null) {
- return _comparableBinarySearch(sortedList, value);
- }
+int binarySearch/*<T>*/(List/*<T>*/ sortedList, /*=T*/ value,
+ {int compare(/*=T*/ a, /*=T*/ b)}) {
+ compare ??= defaultCompare/*<T>*/();
int min = 0;
int max = sortedList.length;
while (min < max) {
@@ -53,39 +34,20 @@
return -1;
}
-/// Version of [lowerBound] optimized for comparable keys
-int _comparableLowerBound(List<Comparable> list, Comparable value) {
- int min = 0;
- int max = list.length;
- while (min < max) {
- int mid = min + ((max - min) >> 1);
- var element = list[mid];
- int comp = element.compareTo(value);
- if (comp < 0) {
- min = mid + 1;
- } else {
- max = mid;
- }
- }
- return min;
-}
-
/// Returns the first position in [sortedList] that does not compare less than
/// [value].
///
/// If the list isn't sorted according to the [compare] function, the result
/// is unpredictable.
///
-/// If [compare] is omitted, it defaults to calling [Comparable.compareTo] on
-/// the objects.
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. If any object is not [Comparable], this throws a [CastError].
///
/// Returns [sortedList.length] if all the items in [sortedList] compare less
/// than [value].
-int lowerBound/*<T extends Comparable<T>>*/(
- List/*<T>*/ sortedList, /*=T*/ value, { int compare(/*=T*/ a, /*=T*/ b) }) {
- if (compare == null) {
- return _comparableLowerBound(sortedList, value);
- }
+int lowerBound/*<T>*/(List/*<T>*/ sortedList, /*=T*/ value,
+ {int compare(/*=T*/ a, /*=T*/ b)}) {
+ compare ??= defaultCompare/*<T>*/();
int min = 0;
int max = sortedList.length;
while (min < max) {
@@ -133,7 +95,11 @@
}
}
-/// Sort a list using insertion sort.
+/// Sort a list between [start] (inclusive) and [end] (exclusive) using
+/// insertion sort.
+///
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. If any object is not [Comparable], this throws a [CastError].
///
/// Insertion sort is a simple sorting algorithm. For `n` elements it does on
/// the order of `n * log(n)` comparisons but up to `n` squared moves. The
@@ -144,25 +110,14 @@
///
/// This insertion sort is stable: Equal elements end up in the same order
/// as they started in.
-void insertionSort(List list,
- { int compare(a, b),
- int start: 0,
- int end: null }) {
+void insertionSort/*<T>*/(List/*<T>*/ list, {int compare(/*=T*/ a, /*=T*/ b),
+ int start: 0, int end}) {
// If the same method could have both positional and named optional
// parameters, this should be (list, [start, end], {compare}).
- if (end == null) end = list.length;
- if (compare == null) compare = Comparable.compare;
- _insertionSort(list, compare, start, end, start + 1);
-}
+ compare ??= defaultCompare/*<T>*/();
+ end ??= list.length;
-/// Internal helper function that assumes arguments correct.
-///
-/// Assumes that the elements up to [sortedUntil] (not inclusive) are
-/// already sorted. The [sortedUntil] values should always be at least
-/// `start + 1`.
-void _insertionSort(List list, int compare(a, b), int start, int end,
- int sortedUntil) {
- for (int pos = sortedUntil; pos < end; pos++) {
+ for (int pos = start + 1; pos < end; pos++) {
int min = start;
int max = pos;
var element = list[pos];
@@ -183,7 +138,11 @@
/// Limit below which merge sort defaults to insertion sort.
const int _MERGE_SORT_LIMIT = 32;
-/// Sorts a list, or a range of a list, using the merge sort algorithm.
+/// Sorts a list between [start] (inclusive) and [end] (exclusive) using the
+/// merge sort algorithm.
+///
+/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. If any object is not [Comparable], this throws a [CastError].
///
/// Merge-sorting works by splitting the job into two parts, sorting each
/// recursively, and then merging the two sorted parts.
@@ -194,13 +153,15 @@
///
/// This merge sort is stable: Equal elements end up in the same order
/// as they started in.
-void mergeSort(List list, {int start: 0, int end: null, int compare(a, b)}) {
- if (end == null) end = list.length;
- if (compare == null) compare = Comparable.compare;
+void mergeSort/*<T>*/(List/*<T>*/ list, {int start: 0, int end,
+ int compare(/*=T*/ a, /*=T*/ b)}) {
+ end ??= list.length;
+ compare ??= defaultCompare/*<T>*/();
+
int length = end - start;
if (length < 2) return;
if (length < _MERGE_SORT_LIMIT) {
- _insertionSort(list, compare, start, end, start + 1);
+ insertionSort(list, compare: compare, start: start, end: end);
return;
}
// Special case the first split instead of directly calling
@@ -213,7 +174,7 @@
int firstLength = middle - start;
int secondLength = end - middle;
// secondLength is always the same as firstLength, or one greater.
- List scratchSpace = new List(secondLength);
+ var scratchSpace = new List<T>(secondLength);
_mergeSort(list, compare, middle, end, scratchSpace, 0);
int firstTarget = end - firstLength;
_mergeSort(list, compare, start, middle, list, firstTarget);
@@ -227,8 +188,9 @@
/// one containing the original values.
///
/// It will work in-place as well.
-void _movingInsertionSort(List list, int compare(a, b), int start, int end,
- List target, int targetOffset) {
+void _movingInsertionSort/*<T>*/(List/*<T>*/ list,
+ int compare(/*=T*/ a, /*=T*/ b), int start, int end, List/*<T>*/ target,
+ int targetOffset) {
int length = end - start;
if (length == 0) return;
target[targetOffset] = list[start];
@@ -257,8 +219,8 @@
///
/// Allows target to be the same list as [list], as long as it's not
/// overlapping the `start..end` range.
-void _mergeSort(List list, int compare(a, b), int start, int end,
- List target, int targetOffset) {
+void _mergeSort/*<T>*/(List/*<T>*/ list, int compare(/*=T*/ a, /*=T*/ b),
+ int start, int end, List/*<T>*/ target, int targetOffset) {
int length = end - start;
if (length < _MERGE_SORT_LIMIT) {
_movingInsertionSort(list, compare, start, end, target, targetOffset);
@@ -290,10 +252,10 @@
/// For equal object, elements from [firstList] are always preferred.
/// This allows the merge to be stable if the first list contains elements
/// that started out earlier than the ones in [secondList]
-void _merge(int compare(a, b),
- List firstList, int firstStart, int firstEnd,
- List secondList, int secondStart, int secondEnd,
- List target, int targetOffset) {
+void _merge/*<T>*/(int compare(/*=T*/ a, /*=T*/ b),
+ List/*<T>*/ firstList, int firstStart, int firstEnd,
+ List/*<T>*/ secondList, int secondStart, int secondEnd,
+ List/*<T>*/ target, int targetOffset) {
// No empty lists reaches here.
assert(firstStart < firstEnd);
assert(secondStart < secondEnd);
diff --git a/lib/src/priority_queue.dart b/lib/src/priority_queue.dart
index de91f15..e0f65b4 100644
--- a/lib/src/priority_queue.dart
+++ b/lib/src/priority_queue.dart
@@ -4,6 +4,8 @@
import "dart:collection";
+import "utils.dart";
+
/// A priority queue is a priority based work-list of elements.
///
/// The queue allows adding elements, and removing them again in priority order.
@@ -143,8 +145,7 @@
/// is the case, `E` must implement [Comparable], and this is checked at
/// runtime for every comparison.
HeapPriorityQueue([int comparison(E e1, E e2)])
- : comparison = comparison ??
- ((e1, e2) => (e1 as Comparable).compareTo(e2));
+ : comparison = comparison ?? defaultCompare/*<E>*/();
void add(E element) {
_add(element);
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 51a80b2..abfb494 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -9,3 +9,7 @@
Pair(this.first, this.last);
}
+
+/// Returns a [Comparator] that asserts that its first argument is comparable.
+Comparator/*<T>*/ defaultCompare/*<T>*/() =>
+ (value1, value2) => (value1 as Comparable).compareTo(value2);
diff --git a/pubspec.yaml b/pubspec.yaml
index 16a5996..6f1d641 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: collection
-version: 1.4.1
+version: 1.4.2
author: Dart Team <misc@dartlang.org>
description: Collections and utilities functions and classes related to collections.
homepage: https://www.github.com/dart-lang/collection