Last active
March 7, 2019 18:25
-
-
Save DracoLi/ad2fbcbe765ea53e521fa81aabaf5fc2 to your computer and use it in GitHub Desktop.
range.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Task: Implement a 'Range Collection' class. | |
// A pair of integers define a range, for example: [1, 5). This range includes integers: 1, 2, 3, and 4. | |
// A range collection is an aggregate of these ranges: [1, 5), [10, 11), [100, 201) | |
// | |
/** | |
* RangeCollection class | |
* NOTE: Feel free to add any extra member variables/functions you like. | |
*/ | |
/** | |
* A class that encapsules two integers that specify | |
* beginning and end of range. | |
*/ | |
class Range { | |
constructor(a, b) { | |
this.a = a; | |
this.b = b; | |
} | |
/** | |
* Returns the intersection of this range and the supplied range. | |
* @param {Range} other The range to intersect with | |
* @return {Range} The intersecting range | |
*/ | |
intersection(other) { | |
// Return null if no intersection | |
if (other.b <= this.a || other.a >= this.b) { | |
return null; | |
} | |
const minA = Math.max(this.a, other.a); | |
const minB = Math.min(this.b, other.b); | |
return new Range(minA, minB); | |
} | |
/** | |
* Check if this range contains the supplied range | |
* @param {Range} other The range to check for contains | |
* @return {Boolean} True if supplied range is contained in this range | |
*/ | |
contains(other) { | |
return this.a <= other.a && other.b <= this.b; | |
} | |
/** | |
* Check if this range equals to the supplied range | |
* @param {Range} other The range to check for equality | |
* @return {Bool} True if other range equals to this range | |
*/ | |
equals(other) { | |
return this.a == other.a && this.b == other.b; | |
} | |
/** | |
* Check if other range can be merged with this range. | |
* @param {Range} other The range to check for | |
* @return {Boolean} True if they can be merged. | |
*/ | |
canMergeWithRange(other) { | |
if (this.intersection(other) != null || | |
(this.a == other.b || this.b == other.a)) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Return a new range that is merged using this range and the supplied range. | |
* Null is returned if the ranges cannot be merged. | |
* @param {Range} other The range to merge with | |
* @return {Range} The merged range | |
*/ | |
mergeWithRange(other) { | |
if (!this.canMergeWithRange(other)) { | |
return null; | |
} | |
return new Range(Math.min(this.a, other.a), Math.max(this.b, other.b)); | |
} | |
isValid() { | |
return Number.isInteger(this.a) && Number.isInteger(this.b) && this.b > this.a; | |
} | |
toString() { | |
return '[' + this.a + ', ' + this.b + ')'; | |
} | |
/** | |
* Returns a range array into a Range instance. | |
* @param {Array<number>} range - Array of two integers that specify beginning and end of range. | |
*/ | |
static fromArray(range) { | |
if (range instanceof Array && range.length == 2) { | |
return new Range(range[0], range[1]); | |
} | |
return null; | |
} | |
} | |
class RangeCollection { | |
constructor() { | |
this.ranges = []; | |
} | |
/** | |
* Adds a range to the collection | |
* @param {Array<number>} range - Array of two integers that specify beginning and end of range. | |
*/ | |
add(input) { | |
let range = Range.fromArray(input); | |
// Do nothing on invalid range | |
if (!range.isValid()) { | |
return; | |
} | |
// Loop through all ranges to try to add the new range | |
let isRangeAdded = false; | |
const newRanges = []; | |
for (let i = 0; i < this.ranges.length; i++) { | |
const current = this.ranges[i]; | |
// Just add current range if new range is already added | |
if (isRangeAdded) { | |
newRanges.push(current); | |
continue; | |
} | |
// Merge current range with target range if able to | |
if (current.canMergeWithRange(range)) { | |
range = current.mergeWithRange(range); | |
// If merged range does not go beyond current range then we | |
// can just add it. | |
if (range.b <= current.b) { | |
newRanges.push(range); | |
isRangeAdded = true; | |
} | |
// Add new and current range if new range is before current range | |
} else if (range.b < current.a) { | |
newRanges.push(range); | |
newRanges.push(current); | |
isRangeAdded = true; | |
} else { | |
newRanges.push(current); | |
} | |
} | |
// Last, if range has not been added then add it | |
if (!isRangeAdded) { | |
newRanges.push(range); | |
} | |
this.ranges = newRanges; | |
} | |
/** | |
* Removes a range from the collection | |
* @param {Array<number>} range - Array of two integers that specify beginning and end of range. | |
*/ | |
remove(input) { | |
const range = Range.fromArray(input); | |
// Do nothing if range is not valid or nothing to remove | |
if (this.ranges.length == 0 || !range.isValid()) { | |
return; | |
} | |
const newRanges = []; | |
for (let i = 0; i < this.ranges.length; i++) { | |
const current = this.ranges[i]; | |
const intersection = current.intersection(range); | |
// No changes to current range if target range does not intersect | |
if (intersection == null) { | |
newRanges.push(current); | |
// Split the current range only if the intersection is not | |
// the entire current range. | |
} else if (!current.equals(intersection)) { | |
if (current.a != intersection.a) { | |
newRanges.push(new Range(current.a, intersection.a)); | |
} | |
if (current.b != intersection.b) { | |
newRanges.push(new Range(intersection.b, current.b)) | |
} | |
} | |
} | |
this.ranges = newRanges; | |
} | |
/** | |
* Prints out the list of ranges in the range collection | |
*/ | |
print() { | |
const results = this.ranges.map(x => x.toString()); | |
console.log(results.join(" ")); | |
} | |
} | |
// Example run | |
const rc = new RangeCollection(); | |
rc.add([1, 5]); | |
rc.print(); | |
// Should display: [1, 5) | |
rc.add([10, 20]); | |
rc.print(); | |
// Should display: [1, 5) [10, 20) | |
rc.add([20, 20]); | |
rc.print(); | |
// Should display: [1, 5) [10, 20) | |
rc.add([20, 21]); | |
rc.print(); | |
// Should display: [1, 5) [10, 21) | |
rc.add([2, 4]); | |
rc.print(); | |
// Should display: [1, 5) [10, 21) | |
rc.add([3, 8]); | |
rc.print(); | |
// Should display: [1, 8) [10, 21) | |
rc.remove([10, 10]); | |
rc.print(); | |
// Should display: [1, 8) [10, 21) | |
rc.remove([10, 11]); | |
rc.print(); | |
// Should display: [1, 8) [11, 21) | |
rc.remove([15, 17]); | |
rc.print(); | |
// Should display: [1, 8) [11, 15) [17, 21) | |
rc.remove([3, 19]); | |
rc.print(); | |
// Should display: [1, 3) [19, 21) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment