Merge pull request #16 from abarth/lowerBound

Add lowerBound
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a39a2b5..04f883f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.2.1
+
+* Add lowerBound to binary search for values that might not be present.
+
 ## 1.2.0
 
 * Add string comparators that ignore ASCII case and sort numbers numerically.
diff --git a/lib/algorithms.dart b/lib/algorithms.dart
index 5ff0bb3..80f0107 100644
--- a/lib/algorithms.dart
+++ b/lib/algorithms.dart
@@ -10,13 +10,13 @@
 import "dart:math" show Random;
 
 /** Version of [binarySearch] optimized for comparable keys */
-int _comparableBinarySearch(List<Comparable> list, Comparable key) {
+int _comparableBinarySearch(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(key);
+    int comp = element.compareTo(value);
     if (comp == 0) return mid;
     if (comp < 0) {
       min = mid + 1;
@@ -28,7 +28,7 @@
 }
 
 /**
- * Returns a position of the [key] in [sortedList], if it is there.
+ * 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.
@@ -36,19 +36,18 @@
  * If [compare] is omitted, it defaults to calling [Comparable.compareTo] on
  * the objects.
  *
- * Returns -1 if [key] is not in the list by default.
+ * Returns -1 if [value] is not in the list by default.
  */
-int binarySearch(List sortedList, var key,
-                 { int compare(var a, var b) }) {
+int binarySearch(List sortedList, value, { int compare(a, b) }) {
   if (compare == null) {
-    return _comparableBinarySearch(sortedList, key);
+    return _comparableBinarySearch(sortedList, value);
   }
   int min = 0;
   int max = sortedList.length;
   while (min < max) {
     int mid = min + ((max - min) >> 1);
     var element = sortedList[mid];
-    int comp = compare(element, key);
+    int comp = compare(element, value);
     if (comp == 0) return mid;
     if (comp < 0) {
       min = mid + 1;
@@ -59,6 +58,54 @@
   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.
+ *
+ * Returns [sortedList.length] if all the items in [sortedList] compare less
+ * than [value].
+ */
+int lowerBound(List sortedList, value, { int compare(a, b) }) {
+  if (compare == null) {
+    return _comparableLowerBound(sortedList, value);
+  }
+  int min = 0;
+  int max = sortedList.length;
+  while (min < max) {
+    int mid = min + ((max - min) >> 1);
+    var element = sortedList[mid];
+    int comp = compare(element, value);
+    if (comp < 0) {
+      min = mid + 1;
+    } else {
+      max = mid;
+    }
+  }
+  return min;
+}
 
 /**
  * Shuffles a list randomly.
diff --git a/pubspec.yaml b/pubspec.yaml
index 3642321..3595f9c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: collection
-version: 1.2.0
+version: 1.2.1-dev
 author: Dart Team <misc@dartlang.org>
 description: Collections and utilities functions and classes related to collections.
 homepage: https://www.github.com/dart-lang/collection
diff --git a/test/algorithms_test.dart b/test/algorithms_test.dart
index 61fbf5b..a639106 100644
--- a/test/algorithms_test.dart
+++ b/test/algorithms_test.dart
@@ -99,6 +99,60 @@
     expect(binarySearch(l3, new C(12), compare: compareC), equals(-1));
   });
 
+  test("lowerbound0", () {
+    expect(lowerBound([], 2), equals(0));
+  });
+
+  test("lowerbound1", () {
+    expect(lowerBound([5], 2), equals(0));
+    expect(lowerBound([5], 5), equals(0));
+    expect(lowerBound([5], 7), equals(1));
+  });
+
+  test("lowerbound3", () {
+    expect(lowerBound([0, 5, 10], -1), equals(0));
+    expect(lowerBound([0, 5, 10], 0), equals(0));
+    expect(lowerBound([0, 5, 10], 2), equals(1));
+    expect(lowerBound([0, 5, 10], 5), equals(1));
+    expect(lowerBound([0, 5, 10], 7), equals(2));
+    expect(lowerBound([0, 5, 10], 10), equals(2));
+    expect(lowerBound([0, 5, 10], 12), equals(3));
+  });
+
+  test("lowerboundRepeat", () {
+    expect(lowerBound([5, 5, 5], 5), equals(0));
+    expect(lowerBound([0, 5, 5, 5, 10], 5), equals(1));
+  });
+
+  test("lowerboundCompare0", () {
+    expect(lowerBound([], new C(2), compare: compareC), equals(0));
+  });
+
+  test("lowerboundCompare1", () {
+    var l1 = [new C(5)];
+    expect(lowerBound(l1, new C(2), compare: compareC), equals(0));
+    expect(lowerBound(l1, new C(5), compare: compareC), equals(0));
+    expect(lowerBound(l1, new C(7), compare: compareC), equals(1));
+  });
+
+  test("lowerboundCompare3", () {
+    var l3 = [new C(0), new C(5), new C(10)];
+    expect(lowerBound(l3, new C(-1), compare: compareC), equals(0));
+    expect(lowerBound(l3, new C(0), compare: compareC), equals(0));
+    expect(lowerBound(l3, new C(2), compare: compareC), equals(1));
+    expect(lowerBound(l3, new C(5), compare: compareC), equals(1));
+    expect(lowerBound(l3, new C(7), compare: compareC), equals(2));
+    expect(lowerBound(l3, new C(10), compare: compareC), equals(2));
+    expect(lowerBound(l3, new C(12), compare: compareC), equals(3));
+  });
+
+  test("lowerboundCompareRepeat", () {
+    var l1 = [new C(5), new C(5), new C(5)];
+    var l2 = [new C(0), new C(5), new C(5), new C(5), new C(10)];
+    expect(lowerBound(l1, new C(5), compare: compareC), equals(0));
+    expect(lowerBound(l2, new C(5), compare: compareC), equals(1));
+  });
+
   test("insertionSortRandom", () {
     Random random = new Random();
     for (int i = 0; i < 25; i++) {