diff --git a/src/ol/structs/integerset.js b/src/ol/structs/integerset.js index bdccb8e854..b30857e567 100644 --- a/src/ol/structs/integerset.js +++ b/src/ol/structs/integerset.js @@ -1,43 +1,23 @@ -// FIXME refactor to use a packed array of integers to reduce GC load - -goog.provide('ol.structs.IntegerRange'); goog.provide('ol.structs.IntegerSet'); -goog.require('goog.array'); goog.require('goog.asserts'); -/** - * @typedef {{start: number, stop: number}} - */ -ol.structs.IntegerRange; - - -/** - * @param {ol.structs.IntegerRange} range1 Range 1. - * @param {ol.structs.IntegerRange} range2 Range 2. - * @return {number} Compare. - */ -ol.structs.IntegerRange.compare = function(range1, range2) { - return range1.start - range2.start || range1.stop - range2.stop; -}; - - /** * A set of integers represented as a set of integer ranges. * This implementation is designed for the case when the number of distinct * integer ranges is small. * @constructor - * @param {Array.=} opt_ranges Ranges. + * @param {Array.=} opt_arr Array. */ -ol.structs.IntegerSet = function(opt_ranges) { +ol.structs.IntegerSet = function(opt_arr) { /** * @private * @type {Array.} */ - this.ranges_ = goog.isDef(opt_ranges) ? opt_ranges : []; + this.arr_ = goog.isDef(opt_arr) ? opt_arr : []; if (goog.DEBUG) { this.assertValid(); @@ -51,18 +31,8 @@ ol.structs.IntegerSet = function(opt_ranges) { * @return {ol.structs.IntegerSet} Integer set. */ ol.structs.IntegerSet.unpack = function(arr) { - var n = arr.length; - goog.asserts.assert(n % 2 === 0); - var ranges = new Array(n / 2); - var rangeIndex = 0; - var i; - for (i = 0; i < n; i += 2) { - ranges[rangeIndex++] = { - start: arr[i], - stop: arr[i + 1] - }; - } - return new ol.structs.IntegerSet(ranges); + // FIXME is this needed? + return new ol.structs.IntegerSet(arr); }; @@ -75,8 +45,18 @@ ol.structs.IntegerSet.prototype.addRange = function(addStart, addStop) { if (addStart == addStop) { return; } - var range = {start: addStart, stop: addStop}; - goog.array.binaryInsert(this.ranges_, range, ol.structs.IntegerRange.compare); + var arr = this.arr_; + var n = arr.length; + var i; + for (i = 0; i < n; i += 2) { + if (addStart <= arr[i]) { + // FIXME check if splice is really needed + arr.splice(i, 0, addStart, addStop); + this.compactRanges_(); + return; + } + } + arr.push(addStart, addStop); this.compactRanges_(); }; @@ -85,8 +65,11 @@ ol.structs.IntegerSet.prototype.addRange = function(addStart, addStop) { * FIXME empty description for jsdoc */ ol.structs.IntegerSet.prototype.assertValid = function() { - var arr = this.pack(); - for (i = 1; i < arr.length; ++i) { + var arr = this.arr_; + var n = arr.length; + goog.asserts.assert(n % 2 === 0); + var i; + for (i = 1; i < n; ++i) { goog.asserts.assert(arr[i] > arr[i - 1]); } }; @@ -96,7 +79,7 @@ ol.structs.IntegerSet.prototype.assertValid = function() { * FIXME empty description for jsdoc */ ol.structs.IntegerSet.prototype.clear = function() { - this.ranges_.length = 0; + this.arr_.length = 0; }; @@ -104,24 +87,24 @@ ol.structs.IntegerSet.prototype.clear = function() { * @private */ ol.structs.IntegerSet.prototype.compactRanges_ = function() { - var ranges = this.ranges_; - var n = ranges.length; + var arr = this.arr_; + var n = arr.length; var rangeIndex = 0; var lastRange = null; var i; - for (i = 0; i < n; ++i) { - var range = ranges[i]; - if (range.start == range.stop) { + for (i = 0; i < n; i += 2) { + if (arr[i] == arr[i + 1]) { // pass - } else if (!goog.isNull(lastRange) && - lastRange.start <= range.start && - range.start <= lastRange.stop) { - lastRange.stop = Math.max(lastRange.stop, range.stop); + } else if (rangeIndex > 0 && + arr[rangeIndex - 2] <= arr[i] && + arr[i] <= arr[rangeIndex - 1]) { + arr[rangeIndex - 1] = Math.max(arr[rangeIndex - 1], arr[i + 1]); } else { - lastRange = ranges[rangeIndex++] = range; + arr[rangeIndex++] = arr[i]; + arr[rangeIndex++] = arr[i + 1]; } } - ranges.length = rangeIndex; + arr.length = rangeIndex; }; @@ -133,21 +116,20 @@ ol.structs.IntegerSet.prototype.compactRanges_ = function() { */ ol.structs.IntegerSet.prototype.findRange = function(minSize) { goog.asserts.assert(minSize > 0); - var ranges = this.ranges_; - var n = ranges.length; - var bestRange = null; + var arr = this.arr_; + var n = arr.length; + var bestIndex = -1; var bestSize, i, size; - for (i = 0; i < n; ++i) { - range = ranges[i]; - size = range.stop - range.start; + for (i = 0; i < n; i += 2) { + size = arr[i + 1] - arr[i]; if (size == minSize) { - return range.start; - } else if (size > minSize && (goog.isNull(bestRange) || size < bestSize)) { - bestRange = range; + return arr[i]; + } else if (size > minSize && (bestIndex == -1 || size < bestSize)) { + bestIndex = arr[i]; bestSize = size; } } - return goog.isNull(bestRange) ? -1 : bestRange.start; + return bestIndex; }; @@ -158,11 +140,11 @@ ol.structs.IntegerSet.prototype.findRange = function(minSize) { * @template T */ ol.structs.IntegerSet.prototype.forEachRange = function(f, opt_obj) { - var ranges = this.ranges_; - var n = ranges.length; + var arr = this.arr_; + var n = arr.length; var i; - for (i = 0; i < n; ++i) { - f.call(opt_obj, ranges[i].start, ranges[i].stop); + for (i = 0; i < n; i += 2) { + f.call(opt_obj, arr[i], arr[i + 1]); } }; @@ -178,20 +160,20 @@ ol.structs.IntegerSet.prototype.forEachRange = function(f, opt_obj) { ol.structs.IntegerSet.prototype.forEachRangeInverted = function(start, stop, f, opt_obj) { goog.asserts.assert(start < stop); - var ranges = this.ranges_; - var n = ranges.length; + var arr = this.arr_; + var n = arr.length; if (n === 0) { f.call(opt_obj, start, stop); } else { - if (start < ranges[0].start) { - f.call(opt_obj, start, ranges[0].start); + if (start < arr[0]) { + f.call(opt_obj, start, arr[0]); } var i; - for (i = 1; i < n; ++i) { - f.call(opt_obj, ranges[i - 1].stop, ranges[i].start); + for (i = 1; i < n - 1; i += 2) { + f.call(opt_obj, arr[i], arr[i + 1]); } - if (ranges[n - 1].stop < stop) { - f.call(opt_obj, ranges[n - 1].stop, stop); + if (arr[n - 1] < stop) { + f.call(opt_obj, arr[n - 1], stop); } } }; @@ -201,9 +183,7 @@ ol.structs.IntegerSet.prototype.forEachRangeInverted = * @return {Array.} Array. */ ol.structs.IntegerSet.prototype.getArray = function() { - // FIXME this should return the underlying array when the representation is - // FIXME updated to use a packed array - return this.pack(); + return this.arr_; }; @@ -212,7 +192,7 @@ ol.structs.IntegerSet.prototype.getArray = function() { * @return {number} Start. */ ol.structs.IntegerSet.prototype.getFirst = function() { - return this.ranges_.length === 0 ? -1 : this.ranges_[0].start; + return this.arr_.length === 0 ? -1 : this.arr_[0]; }; @@ -222,18 +202,8 @@ ol.structs.IntegerSet.prototype.getFirst = function() { * @return {number} Last. */ ol.structs.IntegerSet.prototype.getLast = function() { - var n = this.ranges_.length; - return n === 0 ? -1 : this.ranges_[n - 1].stop; -}; - - -/** - * @return {Array.} Array. - */ -ol.structs.IntegerSet.prototype.getRanges = function() { - // FIXME this should be removed when the implementation is updated to use a - // FIXME packed array - return this.ranges_; + var n = this.arr_.length; + return n === 0 ? -1 : this.arr_[n - 1]; }; @@ -242,11 +212,12 @@ ol.structs.IntegerSet.prototype.getRanges = function() { * @return {number} Size. */ ol.structs.IntegerSet.prototype.getSize = function() { - var ranges = this.ranges_; - var n = ranges.length; + var arr = this.arr_; + var n = arr.length; var size = 0; - for (i = 0; i < n; ++i) { - size += ranges[i].stop - ranges[i].start; + var i; + for (i = 0; i < n; i += 2) { + size += arr[i + 1] - arr[i]; } return size; }; @@ -256,7 +227,7 @@ ol.structs.IntegerSet.prototype.getSize = function() { * @return {boolean} Is empty. */ ol.structs.IntegerSet.prototype.isEmpty = function() { - return this.ranges_.length === 0; + return this.arr_.length === 0; }; @@ -264,15 +235,7 @@ ol.structs.IntegerSet.prototype.isEmpty = function() { * @return {Array.} Array. */ ol.structs.IntegerSet.prototype.pack = function() { - var ranges = this.ranges_; - var n = ranges.length; - var arr = new Array(2 * n); - var i; - for (i = 0; i < n; ++i) { - arr[2 * i] = ranges[i].start; - arr[2 * i + 1] = ranges[i].stop; - } - return arr; + return this.arr_; }; @@ -284,48 +247,47 @@ ol.structs.IntegerSet.prototype.removeRange = function(removeStart, removeStop) { // FIXME this could be more efficient goog.asserts.assert(removeStart <= removeStop); - var ranges = this.ranges_; - var n = ranges.length; - for (i = 0; i < n; ++i) { - var range = ranges[i]; - if (removeStop < range.start || range.stop < removeStart) { + var arr = this.arr_; + var n = arr.length; + var i; + for (i = 0; i < n; i += 2) { + if (removeStop < arr[i] || arr[i + 1] < removeStart) { continue; - } else if (range.start > removeStop) { + } else if (arr[i] > removeStop) { break; } - if (removeStart < range.start) { - if (removeStop == range.start) { + if (removeStart < arr[i]) { + if (removeStop == arr[i]) { break; - } else if (removeStop < range.stop) { - range.start = Math.max(range.start, removeStop); + } else if (removeStop < arr[i + 1]) { + arr[i] = Math.max(arr[i], removeStop); break; } else { - ranges.splice(i, 1); - --i; - --n; + arr.splice(i, 2); + i -= 2; + n -= 2; } - } else if (removeStart == range.start) { - if (removeStop < range.stop) { - range.start = removeStop; + } else if (removeStart == arr[i]) { + if (removeStop < arr[i + 1]) { + arr[i] = removeStop; break; - } else if (removeStop == range.stop) { - ranges.splice(i, 1); + } else if (removeStop == arr[i + 1]) { + arr.splice(i, 2); break; } else { - ranges.splice(i, 1); - --i; - --n; + arr.splice(i, 2); + i -= 2; + n -= 2; } } else { - if (removeStop < range.stop) { - ranges.splice(i, 1, {start: range.start, stop: removeStart}, - {start: removeStop, stop: range.stop}); + if (removeStop < arr[i + 1]) { + arr.splice(i, 2, arr[i], removeStart, removeStop, arr[i + 1]); break; - } else if (removeStop == range.stop) { - range.stop = removeStart; + } else if (removeStop == arr[i + 1]) { + arr[i + 1] = removeStart; break; } else { - range.stop = removeStart; + arr[i + 1] = removeStart; } } } diff --git a/test/spec/ol/structs/integerset.test.js b/test/spec/ol/structs/integerset.test.js index 3513fde9e0..964c660dff 100644 --- a/test/spec/ol/structs/integerset.test.js +++ b/test/spec/ol/structs/integerset.test.js @@ -10,7 +10,7 @@ describe('ol.structs.IntegerSet', function() { it('constructs an empty instance', function() { var is = new ol.structs.IntegerSet(); expect(is).to.be.an(ol.structs.IntegerSet); - expect(is.pack()).to.be.empty(); + expect(is.getArray()).to.be.empty(); }); }); @@ -22,7 +22,7 @@ describe('ol.structs.IntegerSet', function() { it('constructs with a valid array', function() { var is = ol.structs.IntegerSet.unpack([0, 2, 4, 6]); expect(is).to.be.an(ol.structs.IntegerSet); - expect(is.pack()).to.equalArray([0, 2, 4, 6]); + expect(is.getArray()).to.equalArray([0, 2, 4, 6]); }); it('throws an exception with an odd number of elements', function() { @@ -50,7 +50,7 @@ describe('ol.structs.IntegerSet', function() { it('creates a new element', function() { is.addRange(0, 2); - expect(is.pack()).to.equalArray([0, 2]); + expect(is.getArray()).to.equalArray([0, 2]); }); }); @@ -129,52 +129,52 @@ describe('ol.structs.IntegerSet', function() { it('inserts before the first element', function() { is.addRange(0, 2); - expect(is.pack()).to.equalArray([0, 2, 4, 6, 8, 10, 12, 14]); + expect(is.getArray()).to.equalArray([0, 2, 4, 6, 8, 10, 12, 14]); }); it('extends the first element to the left', function() { is.addRange(0, 4); - expect(is.pack()).to.equalArray([0, 6, 8, 10, 12, 14]); + expect(is.getArray()).to.equalArray([0, 6, 8, 10, 12, 14]); }); it('extends the first element to the right', function() { is.addRange(6, 7); - expect(is.pack()).to.equalArray([4, 7, 8, 10, 12, 14]); + expect(is.getArray()).to.equalArray([4, 7, 8, 10, 12, 14]); }); it('merges the first two elements', function() { is.addRange(6, 8); - expect(is.pack()).to.equalArray([4, 10, 12, 14]); + expect(is.getArray()).to.equalArray([4, 10, 12, 14]); }); it('extends middle elements to the left', function() { is.addRange(7, 8); - expect(is.pack()).to.equalArray([4, 6, 7, 10, 12, 14]); + expect(is.getArray()).to.equalArray([4, 6, 7, 10, 12, 14]); }); it('extends middle elements to the right', function() { is.addRange(10, 11); - expect(is.pack()).to.equalArray([4, 6, 8, 11, 12, 14]); + expect(is.getArray()).to.equalArray([4, 6, 8, 11, 12, 14]); }); it('merges the last two elements', function() { is.addRange(10, 12); - expect(is.pack()).to.equalArray([4, 6, 8, 14]); + expect(is.getArray()).to.equalArray([4, 6, 8, 14]); }); it('extends the last element to the left', function() { is.addRange(11, 12); - expect(is.pack()).to.equalArray([4, 6, 8, 10, 11, 14]); + expect(is.getArray()).to.equalArray([4, 6, 8, 10, 11, 14]); }); it('extends the last element to the right', function() { is.addRange(14, 15); - expect(is.pack()).to.equalArray([4, 6, 8, 10, 12, 15]); + expect(is.getArray()).to.equalArray([4, 6, 8, 10, 12, 15]); }); it('inserts after the last element', function() { is.addRange(16, 18); - expect(is.pack()).to.equalArray([4, 6, 8, 10, 12, 14, 16, 18]); + expect(is.getArray()).to.equalArray([4, 6, 8, 10, 12, 14, 16, 18]); }); }); @@ -183,7 +183,7 @@ describe('ol.structs.IntegerSet', function() { it('clears the instance', function() { is.clear(); - expect(is.pack()).to.be.empty(); + expect(is.getArray()).to.be.empty(); }); }); @@ -281,57 +281,57 @@ describe('ol.structs.IntegerSet', function() { it('removes the first part of the first element', function() { is.removeRange(4, 5); - expect(is.pack()).to.equalArray([5, 6, 8, 10, 12, 14]); + expect(is.getArray()).to.equalArray([5, 6, 8, 10, 12, 14]); }); it('removes the last part of the first element', function() { is.removeRange(5, 6); - expect(is.pack()).to.equalArray([4, 5, 8, 10, 12, 14]); + expect(is.getArray()).to.equalArray([4, 5, 8, 10, 12, 14]); }); it('removes the first element', function() { is.removeRange(4, 6); - expect(is.pack()).to.equalArray([8, 10, 12, 14]); + expect(is.getArray()).to.equalArray([8, 10, 12, 14]); }); it('removes the first part of a middle element', function() { is.removeRange(8, 9); - expect(is.pack()).to.equalArray([4, 6, 9, 10, 12, 14]); + expect(is.getArray()).to.equalArray([4, 6, 9, 10, 12, 14]); }); it('removes the last part of a middle element', function() { is.removeRange(9, 10); - expect(is.pack()).to.equalArray([4, 6, 8, 9, 12, 14]); + expect(is.getArray()).to.equalArray([4, 6, 8, 9, 12, 14]); }); it('removes a middle element', function() { is.removeRange(8, 10); - expect(is.pack()).to.equalArray([4, 6, 12, 14]); + expect(is.getArray()).to.equalArray([4, 6, 12, 14]); }); it('removes the first part of the last element', function() { is.removeRange(12, 13); - expect(is.pack()).to.equalArray([4, 6, 8, 10, 13, 14]); + expect(is.getArray()).to.equalArray([4, 6, 8, 10, 13, 14]); }); it('removes the last part of the last element', function() { is.removeRange(13, 14); - expect(is.pack()).to.equalArray([4, 6, 8, 10, 12, 13]); + expect(is.getArray()).to.equalArray([4, 6, 8, 10, 12, 13]); }); it('removes the last element', function() { is.removeRange(12, 14); - expect(is.pack()).to.equalArray([4, 6, 8, 10]); + expect(is.getArray()).to.equalArray([4, 6, 8, 10]); }); it('can remove multiple ranges near the start', function() { is.removeRange(3, 11); - expect(is.pack()).to.equalArray([12, 14]); + expect(is.getArray()).to.equalArray([12, 14]); }); it('can remove multiple ranges near the start', function() { is.removeRange(7, 15); - expect(is.pack()).to.equalArray([4, 6]); + expect(is.getArray()).to.equalArray([4, 6]); }); it('throws an exception when passed an invalid range', function() { @@ -400,29 +400,29 @@ describe('ol.structs.IntegerSet', function() { it('removing an empty range has no effect', function() { is.removeRange(0, 0); - expect(is.pack()).to.equalArray( + expect(is.getArray()).to.equalArray( [0, 1, 2, 4, 5, 8, 9, 12, 13, 15, 16, 17]); }); it('can remove elements from the middle of range', function() { is.removeRange(6, 7); - expect(is.pack()).to.equalArray( + expect(is.getArray()).to.equalArray( [0, 1, 2, 4, 5, 6, 7, 8, 9, 12, 13, 15, 16, 17]); }); it('can remove multiple ranges', function() { is.removeRange(2, 12); - expect(is.pack()).to.equalArray([0, 1, 13, 15, 16, 17]); + expect(is.getArray()).to.equalArray([0, 1, 13, 15, 16, 17]); }); it('can remove multiple ranges and reduce others', function() { is.removeRange(0, 10); - expect(is.pack()).to.equalArray([10, 12, 13, 15, 16, 17]); + expect(is.getArray()).to.equalArray([10, 12, 13, 15, 16, 17]); }); it('can remove all ranges', function() { is.removeRange(0, 18); - expect(is.pack()).to.equalArray([]); + expect(is.getArray()).to.equalArray([]); }); }); @@ -446,7 +446,7 @@ describe('ol.structs.IntegerSet', function() { this.integers_ = {}; }; - SimpleIntegerSet.prototype.pack = function() { + SimpleIntegerSet.prototype.getArray = function() { var integers = goog.array.map( goog.object.getKeys(this.integers_), Number); goog.array.sort(integers); @@ -489,7 +489,7 @@ describe('ol.structs.IntegerSet', function() { addStop = addStart + goog.math.randomInt(16); is.addRange(addStart, addStop); sis.addRange(addStart, addStop); - expect(is.pack()).to.equalArray(sis.pack()); + expect(is.getArray()).to.equalArray(sis.getArray()); } }); @@ -502,7 +502,7 @@ describe('ol.structs.IntegerSet', function() { removeStop = removeStart + goog.math.randomInt(16); is.removeRange(removeStart, removeStop); sis.removeRange(removeStart, removeStop); - expect(is.pack()).to.equalArray(sis.pack()); + expect(is.getArray()).to.equalArray(sis.getArray()); } }); @@ -518,7 +518,7 @@ describe('ol.structs.IntegerSet', function() { is.removeRange(start, stop); sis.removeRange(start, stop); } - expect(is.pack()).to.equalArray(sis.pack()); + expect(is.getArray()).to.equalArray(sis.getArray()); } }); @@ -538,7 +538,7 @@ describe('ol.structs.IntegerSet', function() { is.clear(); sis.clear(); } - expect(is.pack()).to.equalArray(sis.pack()); + expect(is.getArray()).to.equalArray(sis.getArray()); } });