Array: add capability to insert while keeping a specific order

Adds two new functions, Util.lowerBound and Util.insertSorted,
that take an array, a value and a comparator, and find the
first position at which the value can be inserted without
violating the order, in optimal time.

https://bugzilla.gnome.org/show_bug.cgi?id=666429
This commit is contained in:
Giovanni Campagna 2011-12-17 23:52:11 +01:00
parent 56f312dc03
commit 09ab13cf04
3 changed files with 122 additions and 0 deletions

View File

@ -232,3 +232,53 @@ function fixupPCIDescription(desc) {
return out.join(' ');
}
// lowerBound:
// @array: an array or array-like object, already sorted
// according to @cmp
// @val: the value to add
// @cmp: a comparator (or undefined to compare as numbers)
//
// Returns the position of the first element that is not
// lower than @val, according to @cmp.
// That is, returns the first position at which it
// is possible to insert @val without violating the
// order.
// This is quite like an ordinary binary search, except
// that it doesn't stop at first element comparing equal.
function lowerBound(array, val, cmp) {
let min, max, mid, v;
cmp = cmp || function(a, b) { return a - b; };
if (array.length == 0)
return 0;
min = 0; max = array.length;
while (min < (max - 1)) {
mid = Math.floor((min + max) / 2);
v = cmp(array[mid], val);
if (v < 0)
min = mid + 1;
else
max = mid;
}
return (cmp(array[min], val) < 0) ? max : min;
}
// insertSorted:
// @array: an array sorted according to @cmp
// @val: a value to insert
// @cmp: the sorting function
//
// Inserts @val into @array, preserving the
// sorting invariants.
// Returns the position at which it was inserted
function insertSorted(array, val, cmp) {
let pos = lowerBound(array, val, cmp);
array.splice(pos, 0, val);
return pos;
}

View File

@ -20,6 +20,7 @@ TEST_JS = \
testcommon/face-plain.png \
testcommon/ui.js \
unit/format.js \
unit/insertSorted.js \
unit/markup.js \
unit/jsParse.js \
unit/url.js

View File

@ -0,0 +1,71 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
// Test cases for Util.insertSorted
const JsUnit = imports.jsUnit;
// Needed so that Util can bring some UI stuff
// we don't actually use
const Environment = imports.ui.environment;
Environment.init();
const Util = imports.misc.util;
function assertArrayEquals(errorMessage, array1, array2) {
JsUnit.assertEquals(errorMessage + ' length',
array1.length, array2.length);
for (let j = 0; j < array1.length; j++) {
JsUnit.assertEquals(errorMessage + ' item ' + j,
array1[j], array2[j]);
}
}
function cmp(one, two) {
return one-two;
}
let arrayInt = [1, 2, 3, 5, 6];
Util.insertSorted(arrayInt, 4, cmp);
assertArrayEquals('first test', [1,2,3,4,5,6], arrayInt);
// no comparator, integer sorting is implied
Util.insertSorted(arrayInt, 3);
assertArrayEquals('second test', [1,2,3,3,4,5,6], arrayInt);
let obj1 = { a: 1 };
let obj2 = { a: 2, b: 0 };
let obj3 = { a: 2, b: 1 };
let obj4 = { a: 3 };
function objCmp(one, two) {
return one.a - two.a;
}
let arrayObj = [obj1, obj3, obj4];
// obj2 compares equivalent to obj3, should be
// inserted before
Util.insertSorted(arrayObj, obj2, objCmp);
assertArrayEquals('object test', [obj1, obj2, obj3, obj4], arrayObj);
function checkedCmp(one, two) {
if (typeof one != 'number' ||
typeof two != 'number')
throw new TypeError('Invalid type passed to checkedCmp');
return one-two;
}
let arrayEmpty = [];
// check that no comparisons are made when
// inserting in a empty array
Util.insertSorted(arrayEmpty, 3, checkedCmp);
// Some more insertions
Util.insertSorted(arrayEmpty, 2, checkedCmp);
Util.insertSorted(arrayEmpty, 1, checkedCmp);
assertArrayEquals('checkedCmp test', [1, 2, 3], arrayEmpty);