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
I implemented this a long time ago for the .NET framework, and even included support for DateTime
and TimeSpan
, as this was one of my main use cases (implementation of a time line in WPF). My implementation supports all you request, except unbounded intervals. This allowed me to do cool stuff like:
// Mockup of a GUI element and mouse position.
var timeBar = new { X = 100, Width = 200 };
int mouseX = 180;
// Find out which date on the time bar the mouse is positioned on,
// assuming it represents whole of 2014.
var timeRepresentation = new Interval<int>( timeBar.X, timeBar.X + timeBar.Width );
DateTime start = new DateTime( 2014, 1, 1 );
DateTime end = new DateTime( 2014, 12, 31 );
var thisYear = new Interval<DateTime, TimeSpan>( start, end );
DateTime hoverOver = timeRepresentation.Map( mouseX, thisYear );
// If the user clicks, zoom in to this position.
double zoomLevel = 0.5;
double zoomInAt = thisYear.GetPercentageFor( hoverOver );
Interval<DateTime, TimeSpan> zoomed = thisYear.Scale( zoomLevel, zoomInAt );
// Iterate over the interval, e.g. draw labels.
zoomed.EveryStepOf( TimeSpan.FromDays( 1 ), d => DrawLabel( d ) );
More recently, I have ported a base AbstractInterval<T> to an early version of .NET standard which simplifies the implementation of concrete types. For example, IntInterval which is included in the library. Due to restrictions (at the time at least) of .NET standard, I had to target .NET Core for a complete generic implementation. A fully generic Interval<T> implementation builds on top of the .NET Standard base classes.
The biggest down side to this is there are quite a few dependencies all over the place (thus copy pasting part of this project will be hard). The reason for this is the implementation of this is not trivial at all (unlike others who have commented on this). In case there still aren't any good 'interval' libraries for .NET I should really make this a separate package. The .NET Standard library is available on Nuget.
To get you started:
public class Interval<T> where T : struct, IComparable
{
public T? Start { get; set; }
public T? End { get; set; }
public Interval(T? start, T? end)
{
Start = start;
End = end;
}
public bool InRange(T value)
{
return ((!Start.HasValue || value.CompareTo(Start.Value) > 0) &&
(!End.HasValue || End.Value.CompareTo(value) > 0));
}
}
Included a starting point below.
Though this would be a nice brain teaser, so gave it a try. This is far from complete and a lot more operations could be conjured up, but it's a start.
class Program
{
public static void Main(string[] args)
{
var boundedOpenInterval = Interval<int>.Bounded(0, Edge.Open, 10, Edge.Open);
var boundedClosedInterval = Interval<int>.Bounded(0, Edge.Closed, 10, Edge.Closed);
var smallerInterval = Interval<int>.Bounded(3, Edge.Closed, 7, Edge.Closed);
var leftBoundedOpenInterval = Interval<int>.LeftBounded(10, Edge.Open);
var leftBoundedClosedInterval = Interval<int>.LeftBounded(10, Edge.Closed);
var rightBoundedOpenInterval = Interval<int>.RightBounded(0, Edge.Open);
var rightBoundedClosedInterval = Interval<int>.RightBounded(0, Edge.Closed);
Assert.That(
boundedOpenInterval.Includes(smallerInterval)
);
Assert.That(
boundedOpenInterval.Includes(5)
);
Assert.That(
leftBoundedClosedInterval.Includes(100)
);
Assert.That(
!leftBoundedClosedInterval.Includes(5)
);
Assert.That(
rightBoundedClosedInterval.Includes(-100)
);
Assert.That(
!rightBoundedClosedInterval.Includes(5)
);
}
}
public class Interval<T> where T : struct, IComparable<T>
{
private T? _left;
private T? _right;
private int _edges;
private Interval(T? left, Edge leftEdge, T? right, Edge rightEdge)
{
if (left.HasValue && right.HasValue && left.Value.CompareTo(right.Value) > 0)
throw new ArgumentException("Left edge must be lower than right edge");
_left = left;
_right = right;
_edges = (leftEdge == Edge.Closed ? 0x1 : 0) | (rightEdge == Edge.Closed ? 0x2 : 0);
}
public T? Left
{
get { return _left; }
}
public Edge LeftEdge
{
get { return _left.HasValue ? ((_edges & 0x1) != 0 ? Edge.Closed : Edge.Open) : Edge.Unbounded; }
}
public T? Right
{
get { return _right; }
}
public Edge RightEdge
{
get { return _right.HasValue ? ((_edges & 0x2) != 0 ? Edge.Closed : Edge.Open) : Edge.Unbounded; }
}
public bool Includes(T value)
{
var leftCompare = CompareLeft(value);
var rightCompare = CompareRight(value);
return
(leftCompare == CompareResult.Equals || leftCompare == CompareResult.Inside) &&
(rightCompare == CompareResult.Equals || rightCompare == CompareResult.Inside);
}
public bool Includes(Interval<T> interval)
{
var leftEdge = LeftEdge;
if (leftEdge != Edge.Unbounded)
{
if (
leftEdge == Edge.Open &&
interval.LeftEdge == Edge.Closed &&
interval._left.Equals(_left)
)
return false;
if (interval.CompareLeft(_left.Value) == CompareResult.Inside)
return false;
}
var rightEdge = RightEdge;
if (rightEdge != Edge.Unbounded)
{
if (
rightEdge == Edge.Open &&
interval.RightEdge == Edge.Closed &&
interval._right.Equals(_right)
)
return false;
if (interval.CompareRight(_right.Value) == CompareResult.Inside)
return false;
}
return true;
}
private CompareResult CompareLeft(T value)
{
var leftEdge = LeftEdge;
if (leftEdge == Edge.Unbounded)
return CompareResult.Equals;
if (leftEdge == Edge.Closed && _left.Value.Equals(value))
return CompareResult.Inside;
return _left.Value.CompareTo(value) < 0
? CompareResult.Inside
: CompareResult.Outside;
}
private CompareResult CompareRight(T value)
{
var rightEdge = RightEdge;
if (rightEdge == Edge.Unbounded)
return CompareResult.Equals;
if (rightEdge == Edge.Closed && _right.Value.Equals(value))
return CompareResult.Inside;
return _right.Value.CompareTo(value) > 0
? CompareResult.Inside
: CompareResult.Outside;
}
public static Interval<T> LeftBounded(T left, Edge leftEdge)
{
return new Interval<T>(left, leftEdge, null, Edge.Unbounded);
}
public static Interval<T> RightBounded(T right, Edge rightEdge)
{
return new Interval<T>(null, Edge.Unbounded, right, rightEdge);
}
public static Interval<T> Bounded(T left, Edge leftEdge, T right, Edge rightEdge)
{
return new Interval<T>(left, leftEdge, right, rightEdge);
}
public static Interval<T> Unbounded()
{
return new Interval<T>(null, Edge.Unbounded, null, Edge.Unbounded);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
return true;
var other = obj as Interval<T>;
if (other == null)
return false;
return
((!_left.HasValue && !other._left.HasValue) || _left.Equals(other._left)) &&
((!_right.HasValue && !other._right.HasValue) || _right.Equals(other._right)) &&
_edges == other._edges;
}
public override int GetHashCode()
{
return
(_left.HasValue ? _left.GetHashCode() : 0) ^
(_right.HasValue ? _right.GetHashCode() : 0) ^
_edges.GetHashCode();
}
public static bool operator ==(Interval<T> a, Interval<T> b)
{
return ReferenceEquals(a, b) || a.Equals(b);
}
public static bool operator !=(Interval<T> a, Interval<T> b)
{
return !(a == b);
}
public override string ToString()
{
var leftEdge = LeftEdge;
var rightEdge = RightEdge;
var sb = new StringBuilder();
if (leftEdge == Edge.Unbounded)
{
sb.Append("(-∞");
}
else
{
if (leftEdge == Edge.Open)
sb.Append('(');
else
sb.Append('[');
sb.Append(_left.Value);
}
sb.Append(',');
if (rightEdge == Edge.Unbounded)
{
sb.Append("∞)");
}
else
{
sb.Append(_right.Value);
if (rightEdge == Edge.Open)
sb.Append(')');
else
sb.Append(']');
}
return sb.ToString();
}
private enum CompareResult
{
Inside,
Outside,
Equals
}
}
public enum Edge
{
Open,
Closed,
Unbounded
}
I'd generally use the standard .NET Framework classes.
int a = 2;
int b = 10;
// a < x <= b
var interval1 = new HashSet<int>(Enumerable.Range(a + 1, b - a));
// Dump is a LINQPad extension method.
interval1.Dump();
// 3..10
// Check if point in interval
interval1.Contains(a).Dump();
// False
interval1.Contains(b).Dump();
// True
var overlappingInterval = new HashSet<int>(Enumerable.Range(9, 3));
overlappingInterval.Dump();
// 9, 10, 11
var nonOverlappingInterval = new HashSet<int>(Enumerable.Range(11, 2));
nonOverlappingInterval.Dump();
// 11, 12
interval1.Overlaps(overlappingInterval).Dump();
// True
interval1.Overlaps(nonOverlappingInterval).Dump();
// False
interval1.UnionWith(overlappingInterval);
interval1.Dump();
// 3..11
// Alternately use LINQ's Union to avoid mutating.
// Also IntersectWith, IsSubsetOf, etc. (plus all the LINQ extensions).
EDIT: If you want to enforce that this is an interval instead of a set (and/or enforce immutability) you could wrap this in a custom class.
Such a thing is trivial to implement. Note that because most primitive datatypes and also DateTime implement IComparable, you can make create a generic inval type which can work with all these types.
I've found a implementation for DateTimeOffSet only (No numerical) that works fine and have all those methods below. Since this question is top hanked in Google, I'm contributing here:
Covers(t: DateTime) : bool Join(s: IDisjointIntevalSet) : IDisjointIntevalSet Join(i: IInterval) : IDisjointIntevalSet Intersect(i : IInterval) : IDisjointIntevalSet Consolidate() : IDisjointIntevalSet
Avaliable at GitHub and nuget