Introduction
By default in C#, we have Enumerable.Range(Int32, Int32)
which generates a sequence of integral numbers within a specified range. Here in this article, we are going to explore a few more options to extend the range related operations.
Background
What Are We Going to Do?
- Create a model, which will:
- Define a range
- Check if an item is inside the range
- Check if a range is inside the range
- Check if a range is overlapping the range
- Create a utility class:
- Populating items of the range
- List to sequence ranges
- List to sequence range string list specifying the start and end item
- Find overlapping items in input subranges
- Find missing items in input subranges
- Find unknown items in input subranges
Range Model
Here is our range model:
using System;
public class IntegerRangeModel
{
public int StartFrom { get; protected set; }
public int EndTo { get; protected set; }
public int Distance { get; protected set; }
public bool IncludeStartFrom { get; protected set; }
public bool IncludeEndTo { get; protected set; }
public int ActualStartFrom { get; protected set; }
public int ActualEndTo { get; protected set; }
public IntegerRangeModel(int startFrom, int endTo, int distance = 1,
bool includeStartFrom = true, bool includeEndTo = true)
{
StartFrom = startFrom;
EndTo = endTo;
Distance = distance;
IncludeStartFrom = includeStartFrom;
IncludeEndTo = includeEndTo;
ActualStartFrom = IncludeStartFrom ? StartFrom : StartFrom + distance;
ActualEndTo = IncludeEndTo ? EndTo : EndTo - distance;
if (ActualStartFrom > ActualEndTo)
{
throw new ArgumentException("Range start shouldn't be greater than range start");
}
}
public bool Includes(int value)
{
bool includes = ActualStartFrom <= value && value <= ActualEndTo;
return includes;
}
public bool Includes(IntegerRangeModel range)
{
bool includes = Includes(range.ActualStartFrom) && Includes(range.ActualEndTo);
return includes;
}
public bool Overlapes(IntegerRangeModel range)
{
bool includes = false;
includes = ActualStartFrom <= range.ActualEndTo &&
ActualEndTo >= range.ActualStartFrom;
return includes;
}
}
Utility Class
Using the range model in the utility class:
using System;
using System.Collections.Generic;
using System.Linq;
public class IntegerRangeUtility
{
private static IEnumerable<int> Range(int start, int end)
{
if (start > end)
{
throw new ArgumentException("Range start shouldn't be greater than range start");
}
int count = end - start + 1;
return Enumerable.Range(start, count);
}
public static IEnumerable<int> Range(IntegerRangeModel model)
{
int start = model.ActualStartFrom;
int end = model.ActualEndTo;
return Range(start, end);
}
public static IEnumerable<int> Overlappings
(IEnumerable<int> expectedRangeItems, IEnumerable<IntegerRangeModel> sourceRanges)
{
IEnumerable<int> overlapping = expectedRangeItems.Where
(i => sourceRanges.Count(t => t.ActualStartFrom <= i && t.ActualEndTo >= i) > 1);
return overlapping;
}
public static IEnumerable<int> Missings(IEnumerable<int> expectedRangeItems,
IEnumerable<IntegerRangeModel> sourceRanges)
{
IEnumerable<int> missing = expectedRangeItems.Where
(i => sourceRanges.All(t => t.ActualStartFrom > i || t.ActualEndTo < i));
return missing;
}
public static IEnumerable<int> Unknowns(IEnumerable<int> expectedRangeItems,
IEnumerable<IntegerRangeModel> sourceRanges)
{
HashSet<int> hash = new HashSet<int>();
foreach (var sourceRange in sourceRanges.OrderBy(x => x.ActualStartFrom))
{
foreach (var item in Range(sourceRange.ActualStartFrom, sourceRange.ActualEndTo))
{
if (!expectedRangeItems.Contains(item))
{
if (hash.Add(item))
{
yield return item;
}
}
}
}
}
public static IEnumerable<List<T>> ToContiguousSequences<T>
(IEnumerable<T> sequence, Func<T, T> next)
{
sequence = sequence.OrderBy(x => x);
var e = sequence.GetEnumerator();
if (!e.MoveNext())
{
throw new InvalidOperationException("Sequence is empty.");
}
var currentList = new List<T> { e.Current };
while (e.MoveNext())
{
T current = e.Current;
if (current.Equals(next(currentList.Last())))
{
currentList.Add(current);
}
else
{
yield return currentList;
currentList = new List<T> { current };
}
}
yield return currentList;
}
public static IEnumerable<List<int>> ToContiguousSequences
(IEnumerable<int> source, int distance = 1)
{
Func<int, int> nextFunc = n => n + distance;
return ToContiguousSequences(source, nextFunc);
}
public static IEnumerable<List<int>> ToContiguousSequences
(IEnumerable<int> source, IntegerRangeModel sourceRange)
{
return ToContiguousSequences(source, sourceRange.Distance);
}
public static IEnumerable<string> ToRangesString(IEnumerable<int> source)
{
foreach (var sequence in ToContiguousSequences(source))
{
string rangeString = String.Format(@"{0}-{1}", sequence.First(), sequence.Last());
yield return rangeString;
}
}
}
Using the Range Model
Define an Expected Range
var intRange = new IntegerRangeModel(1, 100);
bool result;
Check if an Item Is in the Range
result = intRange.Includes(0);
result = intRange.Includes(1);
result = intRange.Includes(100);
result = intRange.Includes(50);
result = intRange.Includes(101);
Check if a Range Is in the Range
result = intRange.Includes(new IntegerRangeModel(-10, 10));
result = intRange.Includes(new IntegerRangeModel(1, 100));
result = intRange.Includes(new IntegerRangeModel(2, 99));
result = intRange.Includes(new IntegerRangeModel(90, 110));
Check if a Range Is Overlapping the Range
result = intRange.Overlapes(new IntegerRangeModel(-20, -10));
result = intRange.Overlapes(new IntegerRangeModel(-10, 10));
result = intRange.Overlapes(new IntegerRangeModel(1, 100));
result = intRange.Overlapes(new IntegerRangeModel(2, 99));
result = intRange.Overlapes(new IntegerRangeModel(90, 110));
result = intRange.Overlapes(new IntegerRangeModel(101, 110));
Let's get started with the utility class.
Using the Utility Class
var expectedRange = new IntegerRangeModel(1, 100);
var inputSubRanges = new List<IntegerRangeModel>()
{
new IntegerRangeModel(-10, 0),
new IntegerRangeModel(-10, 0),
new IntegerRangeModel(1, 10),
new IntegerRangeModel(5, 15),
new IntegerRangeModel(31, 40),
new IntegerRangeModel(31, 40),
new IntegerRangeModel(41, 70),
new IntegerRangeModel(81, 100),
new IntegerRangeModel(101, 115),
new IntegerRangeModel(105, 120),
};
Populating a Range of Items
List<int> range = IntegerRangeUtility.Range(expectedRange).ToList();
List to Sequence Ranges
List<List<int>> ranges = IntegerRangeUtility.ToContiguousSequences
(range).ToList();
List<List<int>> rangesAsSourceDistance = IntegerRangeUtility.ToContiguousSequences
(range, expectedRange).ToList();
List to Sequence Range String List Specifying the Start and End Item
List<string> rangeStrings = IntegerRangeUtility.ToRangesString(range).ToList();
Find Overlapping Items in Input Subranges
List<int> overlappings = IntegerRangeUtility.Overlappings(range, inputSubRanges).ToList();
List<string> overlappingRangeStrings =
IntegerRangeUtility.ToRangesString(overlappings).ToList();
Find Missing Items in Input Subranges
List<int> missings = IntegerRangeUtility.Missings(range, inputSubRanges).ToList();
List<string> missingRangeStrings = IntegerRangeUtility.ToRangesString(missings).ToList();
Find Unknown Items in Input Subranges
List<int> unkowns = IntegerRangeUtility.Unknowns(range, inputSubRanges).ToList();
List<string> unkownRangeStrings = IntegerRangeUtility.ToRangesString(unkowns).ToList();
Generic Solution
To manage ranges of different types like DateTime
, double
or any user-defined type please check C#: Generic Range Helper
Future Improvements
Combining overlapping items http://stackoverflow.com/questions/32196549/combining-overlapping-date-ranges-java
Please find Visual Studio 2017 solution as an attachment.
History
- 2nd June, 2019: Initial version