DevTools: Replace binarySearch with lowerBound and upperBound functions
This allows to avoid index-mapping-to-negative-axis-trick in
binarySearch and its usages.
It also makes insertionIndexForObjectInListSortedByFunction to work in
O(log(n)) time instead of O(n).
R=caseq@chromium.org
Review URL: https://codereview.chromium.org/18828002
git-svn-id: svn://svn.chromium.org/blink/trunk@153679 bbb929c8-8fbe-4397-9dbb-9b2b20218538
diff --git a/LayoutTests/inspector/utilities-expected.txt b/LayoutTests/inspector/utilities-expected.txt
index 45f26b9..2bf8bf4 100644
--- a/LayoutTests/inspector/utilities-expected.txt
+++ b/LayoutTests/inspector/utilities-expected.txt
@@ -3,6 +3,10 @@
Running: binaryIndexOfTest
+Running: lowerBoundTest
+
+Running: upperBoundTest
+
Running: qselectTest
Array: []
Reference: {}
diff --git a/LayoutTests/inspector/utilities.html b/LayoutTests/inspector/utilities.html
index 013eb0d..d38fc8d 100644
--- a/LayoutTests/inspector/utilities.html
+++ b/LayoutTests/inspector/utilities.html
@@ -16,14 +16,14 @@
[-100, -50, 0, 50, 100],
[-100, -14, -13, -12, -11, -10, -1]
];
-
+
function testArray(array)
{
function comparator(a, b)
{
return a < b ? -1 : (a > b ? 1 : 0);
}
-
+
for (var i = -100; i <= 100; ++i) {
var reference = array.indexOf(i);
var actual = array.binaryIndexOf(i, comparator);
@@ -31,12 +31,72 @@
}
return true;
}
-
+
for (var i = 0, l = testArrays.length; i < l; ++i)
testArray(testArrays[i]);
next();
},
+ function lowerBoundTest(next)
+ {
+ var testArrays = [
+ [],
+ [1],
+ [-1, -1, 0, 0, 0, 0, 2, 3, 4, 4, 4, 7, 9, 9, 9]
+ ];
+
+ function testArray(array, useComparator)
+ {
+ function comparator(a, b)
+ {
+ return a < b ? -1 : (a > b ? 1 : 0);
+ }
+
+ for (var value = -2; value <= 12; ++value) {
+ var index = useComparator ? array.lowerBound(value, comparator) : array.lowerBound(value);
+ InspectorTest.assertTrue(0 <= index && index <= array.length, "index is within bounds");
+ InspectorTest.assertTrue(index === 0 || array[index - 1] < value, "array[index - 1] < value");
+ InspectorTest.assertTrue(index === array.length || array[index] >= value, "array[index] >= value");
+ }
+ }
+
+ for (var i = 0, l = testArrays.length; i < l; ++i) {
+ testArray(testArrays[i], false);
+ testArray(testArrays[i], true);
+ }
+ next();
+ },
+
+ function upperBoundTest(next)
+ {
+ var testArrays = [
+ [],
+ [1],
+ [-1, -1, 0, 0, 0, 0, 2, 3, 4, 4, 4, 7, 9, 9, 9]
+ ];
+
+ function testArray(array, useComparator)
+ {
+ function comparator(a, b)
+ {
+ return a < b ? -1 : (a > b ? 1 : 0);
+ }
+
+ for (var value = -2; value <= 12; ++value) {
+ var index = useComparator ? array.upperBound(value, comparator) : array.upperBound(value);
+ InspectorTest.assertTrue(0 <= index && index <= array.length, "index is within bounds");
+ InspectorTest.assertTrue(index === 0 || array[index - 1] <= value, "array[index - 1] <= value");
+ InspectorTest.assertTrue(index === array.length || array[index] > value, "array[index] > value");
+ }
+ }
+
+ for (var i = 0, l = testArrays.length; i < l; ++i) {
+ testArray(testArrays[i], false);
+ testArray(testArrays[i], true);
+ }
+ next();
+ },
+
function qselectTest(next)
{
var testArrays = [
diff --git a/Source/devtools/front_end/DefaultTextEditor.js b/Source/devtools/front_end/DefaultTextEditor.js
index 9783478..93abe95 100644
--- a/Source/devtools/front_end/DefaultTextEditor.js
+++ b/Source/devtools/front_end/DefaultTextEditor.js
@@ -1429,7 +1429,7 @@
return 0;
return value - object.startColumn;
}
- var index = binarySearch(column, highlight.ranges, compare);
+ var index = highlight.ranges.binaryIndexOf(column, compare);
if (index >= 0) {
var range = highlight.ranges[index];
return {
diff --git a/Source/devtools/front_end/MemoryStatistics.js b/Source/devtools/front_end/MemoryStatistics.js
index 743fa1f..9ca0ac2 100644
--- a/Source/devtools/front_end/MemoryStatistics.js
+++ b/Source/devtools/front_end/MemoryStatistics.js
@@ -264,18 +264,12 @@
{
return value - sample.time;
}
- var firstIndex = binarySearch(start, this._counters, comparator);
- var lastIndex = binarySearch(end, this._counters, comparator);
- if (firstIndex < 0)
- firstIndex = Math.max(0, -firstIndex - 2);
- if (lastIndex < 0)
- lastIndex = Math.min(-lastIndex - 1, this._counters.length - 1);
// Maximum index of element whose time <= start.
- this._minimumIndex = firstIndex;
+ this._minimumIndex = Number.constrain(this._counters.upperBound(start, comparator) - 1, 0, this._counters.length - 1);
// Minimum index of element whose time >= end.
- this._maximumIndex = lastIndex;
+ this._maximumIndex = Number.constrain(this._counters.lowerBound(end, comparator), 0, this._counters.length - 1);
// Current window bounds.
this._minTime = start;
diff --git a/Source/devtools/front_end/externs.js b/Source/devtools/front_end/externs.js
index a19f206..28c0480 100644
--- a/Source/devtools/front_end/externs.js
+++ b/Source/devtools/front_end/externs.js
@@ -87,8 +87,18 @@
/** @param {boolean=} onlyFirst */
Array.prototype.remove = function(obj, onlyFirst) {}
Array.prototype.keySet = function() {}
-/** @return {number} */
-Array.prototype.upperBound = function(anchor) {}
+/**
+ * @param {*} object
+ * @param {function(*,*):number=} comparator
+ * @return {number}
+ */
+Array.prototype.lowerBound = function(object, comparator) {}
+/**
+ * @param {*} object
+ * @param {function(*,*):number=} comparator
+ * @return {number}
+ */
+Array.prototype.upperBound = function(object, comparator) {}
/** @return {number} */
Array.prototype.binaryIndexOf = function(anchor) {}
Array.prototype.sortRange = function(comparator, leftBound, rightBound, k) {}
diff --git a/Source/devtools/front_end/utilities.js b/Source/devtools/front_end/utilities.js
index f94e3b9..98ca021 100644
--- a/Source/devtools/front_end/utilities.js
+++ b/Source/devtools/front_end/utilities.js
@@ -330,30 +330,6 @@
}
});
-Object.defineProperty(Array.prototype, "upperBound",
-{
- /**
- * @param {number} value
- * @return {number}
- * @this {Array.<number>}
- */
- value: function(value)
- {
- var first = 0;
- var count = this.length;
- while (count > 0) {
- var step = count >> 1;
- var middle = first + step;
- if (value >= this[middle]) {
- first = middle + 1;
- count -= step + 1;
- } else
- count = step;
- }
- return first;
- }
-});
-
Object.defineProperty(Array.prototype, "rotate",
{
/**
@@ -470,45 +446,84 @@
}
});
-/**
- * @param {*} object
- * @param {Array.<*>} array
- * @param {function(*, *): number} comparator
- * @return {number}
- */
-function binarySearch(object, array, comparator)
+Object.defineProperty(Array.prototype, "lowerBound",
{
- var first = 0;
- var last = array.length - 1;
-
- while (first <= last) {
- var mid = (first + last) >> 1;
- var c = comparator(object, array[mid]);
- if (c > 0)
- first = mid + 1;
- else if (c < 0)
- last = mid - 1;
- else
- return mid;
+ /**
+ * Return index of the leftmost element that is equal or greater
+ * than the specimen object. If there's no such element (i.e. all
+ * elements are smaller than the specimen) returns array.length.
+ * The function works for sorted array.
+ *
+ * @this {Array.<*>}
+ * @param {*} object
+ * @param {function(*,*):number=} comparator
+ * @return {number}
+ */
+ value: function(object, comparator)
+ {
+ function defaultComparator(a, b)
+ {
+ return a - b;
+ }
+ comparator = comparator || defaultComparator;
+ var l = 0;
+ var r = this.length;
+ while (l < r) {
+ var m = (l + r) >> 1;
+ if (comparator(object, this[m]) > 0)
+ l = m + 1;
+ else
+ r = m;
+ }
+ return r;
}
+});
- // Return the nearest lesser index negated minus 2, i.e.:
- // "-1" means "-1", "-2" means "0, "-3" means "1", etc.
- return -(first + 1);
-}
+Object.defineProperty(Array.prototype, "upperBound",
+{
+ /**
+ * Return index of the leftmost element that is greater
+ * than the specimen object. If there's no such element (i.e. all
+ * elements are smaller than the specimen) returns array.length.
+ * The function works for sorted array.
+ *
+ * @this {Array.<*>}
+ * @param {*} object
+ * @param {function(*,*):number=} comparator
+ * @return {number}
+ */
+ value: function(object, comparator)
+ {
+ function defaultComparator(a, b)
+ {
+ return a - b;
+ }
+ comparator = comparator || defaultComparator;
+ var l = 0;
+ var r = this.length;
+ while (l < r) {
+ var m = (l + r) >> 1;
+ if (comparator(object, this[m]) >= 0)
+ l = m + 1;
+ else
+ r = m;
+ }
+ return r;
+ }
+});
Object.defineProperty(Array.prototype, "binaryIndexOf",
{
/**
- * @param {*} value
- * @param {function(*, *): number} comparator
- * @return {number}
* @this {Array.<*>}
+ * @param {*} value
+ * @param {function(*,*):number} comparator
+ * @return {number}
*/
value: function(value, comparator)
{
- var result = binarySearch(value, this, comparator);
- return result >= 0 ? result : -1;
+ var index = this.lowerBound(value, comparator);
+ return index < this.length && comparator(value, this[index]) === 0 ? index : -1;
}
});
@@ -541,30 +556,18 @@
});
/**
- * @param {*} anObject
- * @param {Array.<*>} aList
- * @param {function(*, *)} aFunction
+ * @param {*} object
+ * @param {Array.<*>} list
+ * @param {function(*,*):number=} comparator
* @param {boolean=} insertionIndexAfter
* @return {number}
*/
-function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction, insertionIndexAfter)
+function insertionIndexForObjectInListSortedByFunction(object, list, comparator, insertionIndexAfter)
{
- var index = binarySearch(anObject, aList, aFunction);
- if (index < 0) {
- // See binarySearch implementation.
- return -index - 1;
- }
-
- if (!insertionIndexAfter) {
- // Return the first occurence of an item in the list.
- while (index > 0 && aFunction(anObject, aList[index - 1]) === 0)
- index--;
- return index;
- }
- // Return the last occurence of an item in the list.
- while (index < aList.length && aFunction(anObject, aList[index]) === 0)
- index++;
- return index;
+ if (insertionIndexAfter)
+ return list.upperBound(object, comparator);
+ else
+ return list.lowerBound(object, comparator);
}
/**