/* Function: SetTo * Sets the number set to another set's value. */ public void SetTo(NumberSet toCopy) { if (toCopy.usedRanges == 0) { if (ranges != null && ShouldShrinkTo(ranges.Length, 0) == 0) { ranges = null; } usedRanges = 0; } else { if (ranges == null || ranges.Length < toCopy.usedRanges || ShouldShrinkTo(ranges.Length, toCopy.usedRanges) != ranges.Length) { ranges = new NumberRange[toCopy.usedRanges]; } usedRanges = toCopy.usedRanges; Array.Copy(toCopy.ranges, ranges, usedRanges); } }
/* Function: InsertAtIndex * Creates a space in <ranges> for a new <NumberRange> at the specified index. If necessary, will reallocate * the array. The values in the new space are undefined. */ protected void InsertAtIndex(int index) { int length = (ranges == null ? 0 : ranges.Length); int newLength = ShouldGrowTo(length, usedRanges + 1); if (newLength > length) { NumberRange[] newArray = new NumberRange[newLength]; if (index > 0) { Array.Copy(ranges, 0, newArray, 0, index); } if (index < usedRanges) { Array.Copy(ranges, index, newArray, index + 1, usedRanges - index); } ranges = newArray; usedRanges++; } else // we don't have to reallocate the array { // This is safe to use with overlapping regions of the same array. if (index < usedRanges) { Array.Copy(ranges, index, ranges, index + 1, usedRanges - index); } usedRanges++; } }
/* Constructor: NumberSet * Creates a number set by duplicating the passed one. */ public NumberSet(NumberSet toCopy) { ranges = new NumberRange[toCopy.usedRanges]; usedRanges = toCopy.usedRanges; Array.Copy(toCopy.ranges, ranges, usedRanges); }
/* Function: RemoveAtIndex * Removes a <NumberRange> from <ranges> at the specified index and moves everything else down. */ protected void RemoveAtIndex(int index) { int shouldShrinkTo = ShouldShrinkTo(ranges.Length, usedRanges - 1); if (shouldShrinkTo == 0) { ranges = null; } else if (shouldShrinkTo < ranges.Length) { NumberRange[] newArray = new NumberRange[shouldShrinkTo]; if (index > 0) { Array.Copy(ranges, newArray, index); } if (index + 1 < usedRanges) { Array.Copy(ranges, index + 1, newArray, index, usedRanges - index - 1); } ranges = newArray; } // Otherwise just move everything down. This is safe to use with overlapping regions of the same array. else if (index != usedRanges - 1) { Array.Copy(ranges, index + 1, ranges, index, usedRanges - index - 1); } usedRanges--; }
/* Function: Clear * Removes all entries from the set, making it empty. */ public void Clear() { usedRanges = 0; int shouldShrinkTo = ShouldShrinkTo(ranges.Length, 0); if (shouldShrinkTo < ranges.Length) { ranges = new NumberRange[shouldShrinkTo]; } }
/* Function: ReadFrom * Reads a number set from the current position in a <BinaryFile>. */ public void ReadFrom(BinaryFile binaryFile) { // [int32: ranges] int newRangeCount = binaryFile.ReadInt32(); if (newRangeCount < 0) { throw new FormatException(); } // Reallocate if needed if (newRangeCount == 0) { if (ranges != null && ShouldShrinkTo(ranges.Length, 0) == 0) { ranges = null; } usedRanges = 0; } else { int arraySize = (ranges == null ? 0 : ranges.Length); int newArraySize = (newRangeCount >= arraySize ? ShouldGrowTo(arraySize, newRangeCount) : ShouldShrinkTo(arraySize, newRangeCount)); if (arraySize != newArraySize) { ranges = new NumberRange[newArraySize]; } // [int32: low] [int32: high] // [int32: low] [int32: high] // ... for (int i = 0; i < newRangeCount; i++) { ranges[i].Low = binaryFile.ReadInt32(); ranges[i].High = binaryFile.ReadInt32(); } usedRanges = newRangeCount; } if (!Validate()) { throw new FormatException(); } }
/* Function: GetEnumerator * Returns an enumerator that returns each value. This allows the number set to be used with foreach. */ IEnumerator <int> IEnumerable <int> .GetEnumerator() { for (int rangeIndex = 0; rangeIndex < usedRanges; rangeIndex++) { NumberRange range = ranges[rangeIndex]; for (int number = range.Low; number <= range.High; number++) { yield return(number); } } }
/* Constructor: NumberSet * Creates an empty number set with the passed number of ranges preallocated. */ protected NumberSet(int numberOfRanges) { if (numberOfRanges == 0) { ranges = null; } else { ranges = new NumberRange[numberOfRanges]; } usedRanges = 0; }
/* Function: InsertAtIndex * Creates a space in <ranges> for a new <NumberRange> at the specified index. If necessary, will reallocate * the array. The values in the new space are undefined. */ protected void InsertAtIndex(int index) { if (usedRanges == ranges.Length) { int newLength; if (usedRanges == 1) { newLength = 4; } else { newLength = usedRanges * 2; } NumberRange[] newArray = new NumberRange[newLength]; if (index > 0) { Array.Copy(ranges, 0, newArray, 0, index); } if (index < usedRanges) { Array.Copy(ranges, index, newArray, index + 1, usedRanges - index); } ranges = newArray; usedRanges++; } else // we don't have to reallocate the array { // This is safe to use with overlapping regions of the same array. if (index < usedRanges) { Array.Copy(ranges, index, ranges, index + 1, usedRanges - index); } usedRanges++; } }
/* Function: SetTo * Sets the NumberSet to the values encoded in a string. It is safe to pass a null or empty string. Throws an exception if * it's not in the correct format. The format isn't documented because it should only be used with strings generated by * <ToString()>. */ public void SetTo(string input) { if (string.IsNullOrEmpty(input) || input == EmptySetString) { if (ranges != null && ShouldShrinkTo(ranges.Length, 0) == 0) { ranges = null; } usedRanges = 0; return; } if (input[0] != '{') { throw new Exceptions.StringNotInValidFormat(input, this); } // First parse the string to perform basic validation and determine the array size. int newRangeCount = 1; int number; int inputIndex = 1; bool onSecondNumber = false; for (;;) { if (inputIndex >= input.Length || input[inputIndex] < '0' || input[inputIndex] > '9') { throw new Exceptions.StringNotInValidFormat(input, this); } number = (int)(input[inputIndex] - '0'); inputIndex++; while (inputIndex < input.Length && input[inputIndex] >= '0' && input[inputIndex] <= '9') { number *= 10; number += (int)(input[inputIndex] - '0'); inputIndex++; } if (inputIndex >= input.Length) { throw new Exceptions.StringNotInValidFormat(input, this); } if (input[inputIndex] == '}') { break; } else if (input[inputIndex] == ',') { newRangeCount++; onSecondNumber = false; inputIndex++; } else if (input[inputIndex] == '-') { if (onSecondNumber == false) { onSecondNumber = true; inputIndex++; } else { throw new Exceptions.StringNotInValidFormat(input, this); } } else { throw new Exceptions.StringNotInValidFormat(input, this); } } // If we're here the string is valid enough to parse, though it still may contain errors like "3-5,6-9" which should be "3-9". // Reallocate the range array if necessary. int arraySize = (ranges == null ? 0 : ranges.Length); int newArraySize = (newRangeCount >= arraySize ? ShouldGrowTo(arraySize, newRangeCount) : ShouldShrinkTo(arraySize, newRangeCount)); if (newArraySize != arraySize) { ranges = new NumberRange[newArraySize]; } usedRanges = newRangeCount; // Copy in the new data as is. inputIndex = 1; int rangeIndex = 0; onSecondNumber = false; for (;;) { number = (int)(input[inputIndex] - '0'); inputIndex++; while (input[inputIndex] >= '0' && input[inputIndex] <= '9') { number *= 10; number += (int)(input[inputIndex] - '0'); inputIndex++; } if (onSecondNumber) { ranges[rangeIndex].High = number; } else { ranges[rangeIndex].Low = number; } if (input[inputIndex] == '}') { if (onSecondNumber == false) { ranges[rangeIndex].High = number; } break; } else if (input[inputIndex] == ',') { if (onSecondNumber == false) { ranges[rangeIndex].High = number; } rangeIndex++; onSecondNumber = false; inputIndex++; } else // (input[inputIndex] == '-') { inputIndex++; onSecondNumber = true; } } // Catch any remaining errors like "3-5,6-9". if (!Validate()) { usedRanges = 0; throw new Exceptions.StringNotInValidFormat(input, this); } }
/* Function: Remove * Removes the contents of an entire set from this one. */ public void Remove(NumberSet setToRemove) { int position = 0; int setToRemovePosition = 0; while (position < usedRanges && setToRemovePosition < setToRemove.usedRanges) { // Remember that these are structs, so to update the list you have to update the original struct, not this one. NumberRange range = ranges[position]; NumberRange rangeToRemove = setToRemove.ranges[setToRemovePosition]; // If the lower bounds is less than the removal lower bounds... if (range.Low < rangeToRemove.Low) { // If the upper bounds is also less than the removal lower bounds, advance the position. if (range.High < rangeToRemove.Low) { position++; } // The upper bounds is somewhere in or past the removal range. If it is less than or equal to the removal // upper bounds, we can just truncate this range. else if (range.High <= rangeToRemove.High) { ranges[position].High = rangeToRemove.Low - 1; position++; } // The upper bounds is past the removal range. Split it. else { InsertAtIndex(position + 1); ranges[position + 1].High = range.High; ranges[position + 1].Low = rangeToRemove.High + 1; ranges[position].High = rangeToRemove.Low - 1; position++; setToRemovePosition++; } } // If the lower bounds is equal to the removal lower bounds... else if (range.Low == rangeToRemove.Low) { // If the upper bounds is less than or equal to the removal upper bounds, remove the range entirely. if (range.High <= rangeToRemove.High) { RemoveAtIndex(position); } // The upper bounds is greater than the removal upper bounds, truncate the range. else { ranges[position].Low = rangeToRemove.High + 1; setToRemovePosition++; } } // If the lower bounds is greater than the removal lower bounds... else { // If the lower bounds is also greater than the removal upper bounds, advance the removal. if (range.Low > rangeToRemove.High) { setToRemovePosition++; } // The removal upper bounds is in or past the range. If it's greater than or equal to the upper bounds, // remove the range. else if (range.High <= rangeToRemove.High) { RemoveAtIndex(position); } // Since it's less than the upper bounds, truncate the range. else { ranges[position].Low = rangeToRemove.High + 1; setToRemovePosition++; } } } }
/* Function: Add * Adds the contents of an entire set from this one. */ public void Add(NumberSet setToAdd) { if (IsEmpty) { SetTo(setToAdd); return; } int position = 0; int setToAddPosition = 0; while (position < usedRanges && setToAddPosition < setToAdd.usedRanges) { // Remember that these are structs, so to update the list you have to update the original struct, not this one. NumberRange range = ranges[position]; NumberRange rangeToAdd = setToAdd.ranges[setToAddPosition]; // If the range starts below or on the range to add... if (range.Low <= rangeToAdd.Low) { // If the entire range is below the range to add, we can just advance. if (range.High < rangeToAdd.Low - 1) { position++; } // If the entire range to add is within the existing range, we can just advance that. else if (range.High >= rangeToAdd.High) { setToAddPosition++; } // The range to add extends past the existing one. If it covers the gap between it and the next existing one, // merge them. else if (position + 1 < usedRanges && ranges[position + 1].Low <= rangeToAdd.High + 1) { ranges[position].High = ranges[position + 1].High; RemoveAtIndex(position + 1); // Go through the loop again without advancing since the range to add may merge multiple ranges into // this one. } // There are no more existing ranges or it doesn't cause them to connect. Extend the existing one. else { ranges[position].High = rangeToAdd.High; setToAddPosition++; // We can advance this too. The range we just added won't intersect with the next range to add, if there is one. // The range we just altered won't either because it now has the same high value. position++; } } // If the range starts above the range to add... else // range.Low > rangeToAdd.Low { // If the range to add extends into the existing range, extend it. if (rangeToAdd.High >= range.Low - 1) { ranges[position].Low = rangeToAdd.Low; // Go through the loop again without advancing. } // The range to add is below the existing range, insert it. else { InsertAtIndex(position); ranges[position].Low = rangeToAdd.Low; ranges[position].High = rangeToAdd.High; position++; setToAddPosition++; } } } // If there's still more ranges left to add, add them to the end. if (setToAddPosition < setToAdd.usedRanges) { int rangesLeftToAdd = setToAdd.usedRanges - setToAddPosition; int newLength = ShouldGrowTo(ranges.Length, usedRanges + rangesLeftToAdd); if (newLength > ranges.Length) { NumberRange[] newArray = new NumberRange[newLength]; Array.Copy(ranges, 0, newArray, 0, ranges.Length); ranges = newArray; } Array.Copy(setToAdd.ranges, setToAddPosition, ranges, usedRanges, rangesLeftToAdd); usedRanges += rangesLeftToAdd; } }
/* Constructor: NumberSet * Creates a number set from the passed string. It is safe to use with null or the empty string. */ public NumberSet(string input) { if (string.IsNullOrEmpty(input) || input == EmptySetString) { ranges = new NumberRange[1]; usedRanges = 0; return; } if (input[0] != '{') { throw new Exceptions.StringNotInValidFormat(input, this); } // First parse the string to perform basic validation and determine the array size. int arraySize = 1; int number; int inputIndex = 1; bool secondNumber = false; for (;;) { if (inputIndex >= input.Length || input[inputIndex] < '0' || input[inputIndex] > '9') { throw new Exceptions.StringNotInValidFormat(input, this); } number = (int)(input[inputIndex] - '0'); inputIndex++; while (inputIndex < input.Length && input[inputIndex] >= '0' && input[inputIndex] <= '9') { number *= 10; number += (int)(input[inputIndex] - '0'); inputIndex++; } if (inputIndex >= input.Length) { throw new Exceptions.StringNotInValidFormat(input, this); } if (input[inputIndex] == '}') { break; } else if (input[inputIndex] == ',') { arraySize++; secondNumber = false; inputIndex++; } else if (input[inputIndex] == '-') { if (secondNumber == false) { secondNumber = true; inputIndex++; } else { throw new Exceptions.StringNotInValidFormat(input, this); } } else { throw new Exceptions.StringNotInValidFormat(input, this); } } // If we're here the string is valid enough to parse, though it still may contain errors like "3-5,6-9" which should be "3-9". ranges = new NumberRange[arraySize]; usedRanges = arraySize; inputIndex = 1; int rangeIndex = 0; secondNumber = false; for (;;) { number = (int)(input[inputIndex] - '0'); inputIndex++; while (input[inputIndex] >= '0' && input[inputIndex] <= '9') { number *= 10; number += (int)(input[inputIndex] - '0'); inputIndex++; } if (secondNumber) { ranges[rangeIndex].High = number; } else { ranges[rangeIndex].Low = number; } if (input[inputIndex] == '}') { if (secondNumber == false) { ranges[rangeIndex].High = number; } break; } else if (input[inputIndex] == ',') { if (secondNumber == false) { ranges[rangeIndex].High = number; } rangeIndex++; secondNumber = false; inputIndex++; } else // (input[inputIndex] == '-') { inputIndex++; secondNumber = true; } } // Catch any remaining errors like "3-5,6-9". if (!Validate()) { usedRanges = 0; throw new Exceptions.StringNotInValidFormat(input, this); } }
// Group: Functions // __________________________________________________________________________ /* Constructor: NumberSet * Creates an empty number set. */ public NumberSet() { ranges = new NumberRange[1]; usedRanges = 0; }
/* Constructor: NumberSet * Reads a number set with the passed number of ranges preallocated. */ protected NumberSet(int numberOfRanges) { ranges = new NumberRange[numberOfRanges]; usedRanges = 0; }