A range is just a closed-open pair of integers.
A rangelist
is a list of closed-open ranges of integers.
var Range = function(lower, upper) { if(lower !== Number(lower)) { throw new Error("Non-numeric lower bound"); }/* www. java2 s .c om*/ if(upper !== Number(upper)) { throw new Error("Non-numeric upper bound"); } if(lower > upper) { throw new Error("Disordered range"); } if(lower === upper) { throw new Error("Empty range"); } this.lower = lower; this.upper = upper; }; Range.prototype.contains = function(other) { return this.lower <= other.lower && other.upper <= this.upper; }; Range.prototype.cardinality = function() { return this.upper - this.lower; }; Range.prototype.create = function(lower, upper) { if(lower !== Number(lower)) { throw new Error("Lower bound is not a number"); } if(upper !== Number(upper)) { throw new Error("Upper bound is not a number"); } if(upper <= lower) { throw new Error("Disordered range"); } }; Range.prototype.compare = function(other) { return (this.lower - other.lower) || (this.upper - other.upper); }; Range.prototype.overlaps = function(other) { return !(this.lower > other.upper || this.upper < other.lower); }; Range.prototype.equals = function(other) { return this.lower === other.lower && this.upper === other.upper; }; Range.prototype.intersection = function(other) { // 1 or 0 ranges if(this.overlaps(other)) { return [ new Range( Math.max(this.lower, other.lower), Math.min(this.upper, other.upper) ) ]; } return []; }; Range.prototype.union = function(other) { // 1 or 2 ranges if(this.overlaps(other)) { return [new Range( Math.min(this.lower, other.lower), Math.max(this.upper, other.upper) )]; } return [this, other]; }; Range.prototype.difference = function(other) { // 0, 1 or 2 ranges var result = []; if(this.lower < other.lower) { result.push(new Range( this.lower, Math.min(this.upper, other.lower) )); } if(this.upper > other.upper) { result.push(new Range( Math.max(this.lower, other.upper), this.upper )); } return result; }; console.log(new Range(0, 2).overlaps(new Range(1, 3))); console.log(new Range(1, 3).overlaps(new Range(0, 2))); console.log(new Range(0, 2).union(new Range(1, 3))[0].equals(new Range(0, 3))); console.log(new Range(1, 3).union(new Range(0, 2))[0].equals(new Range(0, 3))); console.log(new Range(0, 2).union(new Range(2, 3))[0].equals(new Range(0, 3))); console.log(new Range(2, 3).union(new Range(0, 2))[0].equals(new Range(0, 3))); console.log(new Range(0, 1).cardinality() === 1); console.log(new Range(0, 2).cardinality() === 2); console.log(new Range(0, 11).cardinality() === 11); console.log("---"); var RangeList = function(ranges){ ranges = ranges || []; // These are kept sorted. Each range is always separated from the next by a // gap of at least one. E.g. [[0, 1], [1, 2]] is illegal, it should collapse // to [[0, 2]]. for(var i = 0; i + 1 < ranges.length; i++) { if(ranges[i].upper >= ranges[i + 1].lower) { throw new Error("Indistinct ranges"); } } this.ranges = ranges; }; // Iterate through all the bounds of both RangeLists, determining at // each stage whether we are "in" the new RangeList or not, according to the // supplied test. RangeList.prototype.combine = function(other, test) { if(other instanceof Range) { other = new RangeList([other]); } var n = -Infinity, i = -1, j = -1, inThis = false, inOther = false, inNew = test(inThis, inOther); var newRanges = []; var newRangeLower = null; while(n < Infinity) { var nextThis = inThis ? this.ranges[i].upper : i + 1 < this.ranges.length ? this.ranges[i + 1].lower : Infinity; var nextOther = inOther ? other.ranges[j].upper : j + 1 < other.ranges.length ? other.ranges[j + 1].lower : Infinity; if(nextThis <= nextOther) { n = nextThis; inThis = !inThis; i += inThis ? 1 : 0; } if(nextOther <= nextThis) { n = nextOther; inOther = !inOther; j += inOther ? 1 : 0; } if(inNew !== test(inThis, inOther)) { if(inNew) { newRanges.push(new Range(newRangeLower, n)); newRangeLower = null; } else { newRangeLower = n; } inNew = !inNew; } } return new RangeList(newRanges); }; RangeList.prototype.contains = function(otherRange) { return this.ranges.some(function(range) { return range.contains(otherRange); }); }; RangeList.prototype.cardinality = function() { var cardinality = 0; this.ranges.forEach(function(range) { cardinality += range.cardinality(); }); return cardinality; }; RangeList.prototype.union = function(other) { return this.combine(other, function(inThis, inOther) { return inThis || inOther; }); }; RangeList.prototype.intersection = function(other) { return this.combine(other, function(inThis, inOther) { return inThis && inOther; }); }; RangeList.prototype.difference = function(other) { return this.combine(other, function(inThis, inOther) { return inThis && !inOther; }); }; module.exports = RangeList; var rla = new RangeList(); rla = rla.union(new Range(0, 1)); console.log(rla.ranges.length === 1); console.log(rla.ranges[0].equals(new Range(0, 1))); rla = rla.union(new Range(2, 3)); console.log(rla.ranges.length === 2); console.log(rla.ranges[0].equals(new Range(0, 1))); console.log(rla.ranges[1].equals(new Range(2, 3))); rla = rla.union(new Range(3, 4)); console.log(rla.ranges.length === 2); console.log(rla.ranges[0].equals(new Range(0, 1))); console.log(rla.ranges[1].equals(new Range(2, 4))); rla = rla.union(new Range(-2, 1)); console.log(rla.ranges.length === 2); console.log(rla.ranges[0].equals(new Range(-2, 1))); console.log(rla.ranges[1].equals(new Range(2, 4))); rla = rla.union(new Range(0, 78)); console.log(rla.ranges.length === 1); console.log(rla.ranges[0].equals(new Range(-2, 78))); rla = rla.difference(new Range(-3, 1)); console.log(rla.ranges.length === 1); console.log(rla.ranges[0].equals(new Range(1, 78))); rla = rla.difference(new Range(76, 90)); console.log(rla.ranges.length === 1); console.log(rla.ranges[0].equals(new Range(1, 76))); rla = rla.difference(new Range(35, 48)); console.log(rla.ranges.length === 2); console.log(rla.ranges[0].equals(new Range(1, 35))); console.log(rla.ranges[1].equals(new Range(48, 76))); rla = rla.difference(new Range(0, 40)); console.log(rla.ranges.length === 1); console.log(rla.ranges[0].equals(new Range(48, 76))); rla = rla.intersection(new Range(49, 77)); console.log(rla.ranges.length === 1); console.log(rla.ranges[0].equals(new Range(49, 76))); console.log("---");