I\'m looking an interval data type for .NET 4.0. For example the interval (a,b], all point x such that a What i would like to be able to do are create interv
EDIT 2019: As of C# 8.0/.NET Core 3.x/.NET Standard 2.1 there is now a System.Range that provides minimal interval functionality with endpoints. I'm leaving the rest of this answer as-is.
As others have stated, there are no integrated interval type. Depending on the needs of your project, a simple Tuple<T1, T2> or call to Enumerable.Range with a few additional lines of code might suffice. The HashSet<T> contains set operation methods, such as UnionWith, IntersectWith and more, but still stores all the items, not just the endpoints.
Many implementations can be found online. There is the basic generic Range class part of the Microsoft Research Dynamic Data Display project and another from Kevin Gadd. The AForge project contains a non-generic IntInterval/DoubleInterval implementation. Other (1, 2) SO questions might also be of interest. Andy Clymer has an interesting dynamically compiled implementation on his blog. More complete solutions can be found on CodeProject, in Jon Skeet's book and From Russia with Love. There seems to be a few (1, 2) commercial solutions as well. I've seen others before that I can't find at the moment.
Whatever you do, please watch out when using a generic interval type. It's actually hard to write a correct monolithic generic interval class because integer and floating point intervals have different mathematical properties. For example all integer intervals can be represented with closed endpoints and the pair [1,2] [3,6]
can be considered as contiguous, equivalent to [1,6]
. None of this is true with floating points intervals. See Wikipedia for details. A group of classes might be better, with an abstract generic base class and typed derived classes IntInterval or DoubleInterval to implement the different behaviors.
Aside from the math, there are a few more implementation difficulties with generic interval types. It's not possible to easily do arithmetic with generics in C#, and there is floating point NaN and rounding errors to take care of. See the Boost library documentation for Interval<T> for more on this. (A lot of it translates to C# and .NET.) Luckily many operations can be done with just IComparable<T>
.
As I mentioned before, the choice of what is appropriate in terms of functionality and correctness all depends on the requirements of your projects.
The following allows open ended ranges of any type that implements IComparable
. An obvious extension would be to allow you to pass your own comparer (in much the same way that Hashset<T>
does.
The range in this case is a<=x
It includes overlap and merge. Other functions should be reasonably easy to add.
public class Interval<T> where T : IComparable
{
public T Start { get; private set; }
public T End { get; private set; }
public bool HasStart { get; private set; }
public bool HasEnd { get; private set; }
private Interval()
{
}
public bool Overlaps(Interval<T> other)
{
if (this.HasStart && other.IsInRange(this.Start))
return true;
if (this.HasEnd && other.IsInRange(this.End))
return true;
return false;
}
public static Interval<T> Merge(Interval<T> int1, Interval<T> int2)
{
if (!int1.Overlaps(int2))
{
throw new ArgumentException("Interval ranges do not overlap.");
}
bool hasStart = false;
bool hasEnd = false;
T start = default(T);
T end = default(T);
if (int1.HasStart && int2.HasStart)
{
hasStart = true;
start = (int1.Start.CompareTo(int2.Start) < 0) ? int1.Start : int2.Start;
}
if (int1.HasEnd && int2.HasEnd)
{
hasEnd = true;
end = (int1.End.CompareTo(int2.End) > 0) ? int1.Start : int2.Start;
}
return CreateInternal(start, hasStart, end, hasEnd);
}
private static Interval<T> CreateInternal(T start, bool hasStart, T end, bool hasEnd)
{
var i = new Interval<T>();
i.Start = start;
i.End = end;
i.HasEnd = hasEnd;
i.HasStart = hasStart;
return i;
}
public static Interval<T> Create(T start, T end)
{
return CreateInternal(start, true, end, true);
}
public static Interval<T> CreateLowerBound(T start)
{
return CreateInternal(start, true, default(T), false);
}
public static Interval<T> CreateUpperBound(T end)
{
return CreateInternal(default(T), false, end, true);
}
public bool IsInRange(T item)
{
if (HasStart && item.CompareTo(Start) < 0)
{
return false;
}
if (HasEnd && item.CompareTo(End) >= 0)
{
return false;
}
return true;
}
}