Path: blob/trunk/third_party/closure/goog/math/rangeset.js
2868 views
// Copyright 2009 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview A RangeSet is a structure that manages a list of ranges.16* Numeric ranges may be added and removed from the RangeSet, and the set may17* be queried for the presence or absence of individual values or ranges of18* values.19*20* This may be used, for example, to track the availability of sparse elements21* in an array without iterating over the entire array.22*23* @author [email protected] (Shawn Brenneman)24*/2526goog.provide('goog.math.RangeSet');2728goog.require('goog.array');29goog.require('goog.iter.Iterator');30goog.require('goog.iter.StopIteration');31goog.require('goog.math.Range');32333435/**36* Constructs a new RangeSet, which can store numeric ranges.37*38* Ranges are treated as half-closed: that is, they are exclusive of their end39* value [start, end).40*41* New ranges added to the set which overlap the values in one or more existing42* ranges will be merged.43*44* @struct45* @constructor46* @final47*/48goog.math.RangeSet = function() {49/**50* A sorted list of ranges that represent the values in the set.51* @type {!Array<!goog.math.Range>}52* @private53*/54this.ranges_ = [];55};565758if (goog.DEBUG) {59/**60* @return {string} A debug string in the form [[1, 5], [8, 9], [15, 30]].61* @override62*/63goog.math.RangeSet.prototype.toString = function() {64return '[' + this.ranges_.join(', ') + ']';65};66}676869/**70* Compares two sets for equality.71*72* @param {goog.math.RangeSet} a A range set.73* @param {goog.math.RangeSet} b A range set.74* @return {boolean} Whether both sets contain the same values.75*/76goog.math.RangeSet.equals = function(a, b) {77// Fast check for object equality. Also succeeds if a and b are both null.78return a == b ||79!!(a && b &&80goog.array.equals(a.ranges_, b.ranges_, goog.math.Range.equals));81};828384/**85* @return {!goog.math.RangeSet} A new RangeSet containing the same values as86* this one.87*/88goog.math.RangeSet.prototype.clone = function() {89var set = new goog.math.RangeSet();9091for (var i = this.ranges_.length; i--;) {92set.ranges_[i] = this.ranges_[i].clone();93}9495return set;96};979899/**100* Adds a range to the set. If the new range overlaps existing values, those101* ranges will be merged.102*103* @param {goog.math.Range} a The range to add.104*/105goog.math.RangeSet.prototype.add = function(a) {106if (a.end <= a.start) {107// Empty ranges are ignored.108return;109}110111a = a.clone();112113// Find the insertion point.114for (var i = 0, b; b = this.ranges_[i]; i++) {115if (a.start <= b.end) {116a.start = Math.min(a.start, b.start);117break;118}119}120121var insertionPoint = i;122123for (; b = this.ranges_[i]; i++) {124if (a.end < b.start) {125break;126}127a.end = Math.max(a.end, b.end);128}129130this.ranges_.splice(insertionPoint, i - insertionPoint, a);131};132133134/**135* Removes a range of values from the set.136*137* @param {goog.math.Range} a The range to remove.138*/139goog.math.RangeSet.prototype.remove = function(a) {140if (a.end <= a.start) {141// Empty ranges are ignored.142return;143}144145// Find the insertion point.146for (var i = 0, b; b = this.ranges_[i]; i++) {147if (a.start < b.end) {148break;149}150}151152if (!b || a.end < b.start) {153// The range being removed doesn't overlap any existing range. Exit early.154return;155}156157var insertionPoint = i;158159if (a.start > b.start) {160// There is an overlap with the nearest range. Modify it accordingly.161insertionPoint++;162163if (a.end < b.end) {164goog.array.insertAt(165this.ranges_, new goog.math.Range(a.end, b.end), insertionPoint);166}167b.end = a.start;168}169170for (i = insertionPoint; b = this.ranges_[i]; i++) {171b.start = Math.max(a.end, b.start);172if (a.end < b.end) {173break;174}175}176177this.ranges_.splice(insertionPoint, i - insertionPoint);178};179180181/**182* Determines whether a given range is in the set. Only succeeds if the entire183* range is available.184*185* @param {goog.math.Range} a The query range.186* @return {boolean} Whether the entire requested range is set.187*/188goog.math.RangeSet.prototype.contains = function(a) {189if (a.end <= a.start) {190return false;191}192193for (var i = 0, b; b = this.ranges_[i]; i++) {194if (a.start < b.end) {195if (a.end >= b.start) {196return goog.math.Range.contains(b, a);197}198break;199}200}201return false;202};203204205/**206* Determines whether a given value is set in the RangeSet.207*208* @param {number} value The value to test.209* @return {boolean} Whether the given value is in the set.210*/211goog.math.RangeSet.prototype.containsValue = function(value) {212for (var i = 0, b; b = this.ranges_[i]; i++) {213if (value < b.end) {214if (value >= b.start) {215return true;216}217break;218}219}220return false;221};222223224/**225* Returns the union of this RangeSet with another.226*227* @param {goog.math.RangeSet} set Another RangeSet.228* @return {!goog.math.RangeSet} A new RangeSet containing all values from229* either set.230*/231goog.math.RangeSet.prototype.union = function(set) {232// TODO(brenneman): A linear-time merge would be preferable if it is ever a233// bottleneck.234set = set.clone();235236for (var i = 0, a; a = this.ranges_[i]; i++) {237set.add(a);238}239240return set;241};242243244/**245* Subtracts the ranges of another set from this one, returning the result246* as a new RangeSet.247*248* @param {!goog.math.RangeSet} set The RangeSet to subtract.249* @return {!goog.math.RangeSet} A new RangeSet containing all values in this250* set minus the values of the input set.251*/252goog.math.RangeSet.prototype.difference = function(set) {253var ret = this.clone();254255for (var i = 0, a; a = set.ranges_[i]; i++) {256ret.remove(a);257}258259return ret;260};261262263/**264* Intersects this RangeSet with another.265*266* @param {goog.math.RangeSet} set The RangeSet to intersect with.267* @return {!goog.math.RangeSet} A new RangeSet containing all values set in268* both this and the input set.269*/270goog.math.RangeSet.prototype.intersection = function(set) {271if (this.isEmpty() || set.isEmpty()) {272return new goog.math.RangeSet();273}274275return this.difference(set.inverse(this.getBounds()));276};277278279/**280* Creates a subset of this set over the input range.281*282* @param {goog.math.Range} range The range to copy into the slice.283* @return {!goog.math.RangeSet} A new RangeSet with a copy of the values in the284* input range.285*/286goog.math.RangeSet.prototype.slice = function(range) {287var set = new goog.math.RangeSet();288if (range.start >= range.end) {289return set;290}291292for (var i = 0, b; b = this.ranges_[i]; i++) {293if (b.end <= range.start) {294continue;295}296if (b.start > range.end) {297break;298}299300set.add(301new goog.math.Range(302Math.max(range.start, b.start), Math.min(range.end, b.end)));303}304305return set;306};307308309/**310* Creates an inverted slice of this set over the input range.311*312* @param {goog.math.Range} range The range to copy into the slice.313* @return {!goog.math.RangeSet} A new RangeSet containing inverted values from314* the original over the input range.315*/316goog.math.RangeSet.prototype.inverse = function(range) {317var set = new goog.math.RangeSet();318319set.add(range);320for (var i = 0, b; b = this.ranges_[i]; i++) {321if (range.start >= b.end) {322continue;323}324if (range.end < b.start) {325break;326}327328set.remove(b);329}330331return set;332};333334335/**336* @return {number} The sum of the lengths of ranges covered in the set.337*/338goog.math.RangeSet.prototype.coveredLength = function() {339return /** @type {number} */ (340goog.array.reduce(this.ranges_, function(res, range) {341return res + range.end - range.start;342}, 0));343};344345346/**347* @return {goog.math.Range} The total range this set covers, ignoring any348* gaps between ranges.349*/350goog.math.RangeSet.prototype.getBounds = function() {351if (this.ranges_.length) {352return new goog.math.Range(353this.ranges_[0].start, goog.array.peek(this.ranges_).end);354}355356return null;357};358359360/**361* @return {boolean} Whether any ranges are currently in the set.362*/363goog.math.RangeSet.prototype.isEmpty = function() {364return this.ranges_.length == 0;365};366367368/**369* Removes all values in the set.370*/371goog.math.RangeSet.prototype.clear = function() {372this.ranges_.length = 0;373};374375376/**377* Returns an iterator that iterates over the ranges in the RangeSet.378*379* @param {boolean=} opt_keys Ignored for RangeSets.380* @return {!goog.iter.Iterator} An iterator over the values in the set.381*/382goog.math.RangeSet.prototype.__iterator__ = function(opt_keys) {383var i = 0;384var list = this.ranges_;385386var iterator = new goog.iter.Iterator();387iterator.next = function() {388if (i >= list.length) {389throw goog.iter.StopIteration;390}391return list[i++].clone();392};393394return iterator;395};396397398