Skip to content

Instantly share code, notes, and snippets.

@hongymagic
Last active December 15, 2023 13:09
Show Gist options
  • Save hongymagic/877f1e083d5f0855597a to your computer and use it in GitHub Desktop.
Save hongymagic/877f1e083d5f0855597a to your computer and use it in GitHub Desktop.
Representing Intervals in C# with Generics support `Interval<T>`

Interval type in C#

Interval struct represents mathemetical intervals; It has two ends which can be open or closed. The following intervals are supported:

  • [a, b] Closed intervals;
  • [a, b) Right-open intervals;
  • (a, b] Left-open intervals; and
  • (a, b) Open intervals.

Using certain types, you can also defined boundedness of the intervals:

var positives = Interval.Range(0d, double.PositiveInfinity, IntervalType.Open);
// (0, +∞] Right-unbounded

var negatives = Interval.Range(double.NegativeInfinity, 0d, IntervalType.Closed, IntervalType.Open);
// [-∞, 0) Left-unbounded

var answer = Interval.Range(double.NegativeInfinity, double.PositiveInfinity);
// [-∞, +∞] Unbounded

API

Contains(T point)

To check whenter or not a point lies within an interval, use Contains function:

var ten = Interval.Range(0, 10);
ten.Contains(0); // true
ten.Contains(-1); // false
using System;
namespace Hongy
{
/// <summary>
/// Represents vectorless interval of the form [a, b] or (a, b) or any
/// combination of exclusive and inclusive end points.
/// </summary>
/// <typeparam name="T">Any comparent type</typeparam>
/// <remarks>
/// This is a vectorless interval, therefore if end component is larger
/// than start component, the interval will swap the two ends around
/// such that a is always %lt; b.
/// </remarks>
public struct Interval<T> where T : struct, IComparable
{
public T LowerBound { get; private set; }
public T UpperBound { get; private set; }
public IntervalType LowerBoundIntervalType { get; private set; }
public IntervalType UpperBoundIntervalType { get; private set; }
public Interval(
T lowerbound,
T upperbound,
IntervalType lowerboundIntervalType = IntervalType.Closed,
IntervalType upperboundIntervalType = IntervalType.Closed)
: this()
{
var a = lowerbound;
var b = upperbound;
var comparison = a.CompareTo(b);
if (comparison > 0)
{
a = upperbound;
b = lowerbound;
}
LowerBound = a;
UpperBound = b;
LowerBoundIntervalType = lowerboundIntervalType;
UpperBoundIntervalType = upperboundIntervalType;
}
/// <summary>
/// Check if given point lies within the interval.
/// </summary>
/// <param name="point">Point to check</param>
/// <returns>True if point lies within the interval, otherwise false</returns>
public bool Contains(T point)
{
if (LowerBound.GetType() != typeof (T)
|| UpperBound.GetType() != typeof (T))
{
throw new ArgumentException("Type mismatch", "point");
}
var lower = LowerBoundIntervalType == IntervalType.Open
? LowerBound.CompareTo(point) < 0
: LowerBound.CompareTo(point) <= 0;
var upper = UpperBoundIntervalType == IntervalType.Open
? UpperBound.CompareTo(point) > 0
: UpperBound.CompareTo(point) >= 0;
return lower && upper;
}
/// <summary>
/// Convert to mathematical notation using open and closed parenthesis:
/// (, ), [, and ].
/// </summary>
/// <returns></returns>
public override string ToString()
{
return string.Format(
"{0}{1}, {2}{3}",
LowerBoundIntervalType == IntervalType.Open ? "(" : "[",
LowerBound,
UpperBound,
UpperBoundIntervalType == IntervalType.Open ? ")" : "]"
);
}
}
/// <summary>
/// Static class to generate regular Intervals using common types.
/// </summary>
public static class Interval
{
public static Interval<double> Range(double lowerbound, double upperbound, IntervalType lowerboundIntervalType = IntervalType.Closed, IntervalType upperboundIntervalType = IntervalType.Closed)
{
return new Interval<double>(lowerbound, upperbound, lowerboundIntervalType, upperboundIntervalType);
}
public static Interval<decimal> Range(decimal lowerbound, decimal upperbound, IntervalType lowerboundIntervalType = IntervalType.Closed, IntervalType upperboundIntervalType = IntervalType.Closed)
{
return new Interval<decimal>(lowerbound, upperbound, lowerboundIntervalType, upperboundIntervalType);
}
public static Interval<int> Range(int lowerbound, int upperbound, IntervalType lowerboundIntervalType = IntervalType.Closed, IntervalType upperboundIntervalType = IntervalType.Closed)
{
return new Interval<int>(lowerbound, upperbound, lowerboundIntervalType, upperboundIntervalType);
}
}
/// <summary>
/// An interval could be open and closed or combination of both at either
/// end.
/// </summary>
public enum IntervalType
{
Open,
Closed
}
}
using System;
using FluentAssertions;
using NUnit.Framework;
namespace Hongy.Tests
{
[TestFixture]
public class IntervalTests
{
static readonly object[] TestCases = {
// Closed interval: [a, b]
new object[] { Interval.Range(1, 10), 1, true },
new object[] { Interval.Range(1, 10), 10, true },
new object[] { Interval.Range(1, 10), 11, false },
new object[] { Interval.Range(1, 10), 0, false },
new object[] { Interval.Range(1m, 10m), 1m, true },
new object[] { Interval.Range(1m, 10m), 10m, true },
new object[] { Interval.Range(1m, 10m), 11m, false },
new object[] { Interval.Range(1m, 10m), 0m, false },
// Empty interval: (a, a], [a, a), (a, a)
new object[] { Interval.Range(1, 1, IntervalType.Open), 1, false },
new object[] { Interval.Range(1, 1, IntervalType.Open, IntervalType.Open), 1, false },
// Degernate interval: [a, a] = {a}
new object[] { Interval.Range(1, 1), 1, true },
// Lower bounded interval: (a, +∞), [a, +∞), (a, +∞], [a, +∞]
new object[] { Interval.Range(-100d, double.PositiveInfinity), double.PositiveInfinity, true }, // []
new object[] { Interval.Range(-100d, double.PositiveInfinity), 1d, true }, // []
new object[] { Interval.Range(-100d, double.PositiveInfinity), -100d, true }, // []
new object[] { Interval.Range(-100d, double.PositiveInfinity), -101d, false }, // []
new object[] { Interval.Range(-100d, double.PositiveInfinity, IntervalType.Open), 1d, true }, // (]
new object[] { Interval.Range(-100d, double.PositiveInfinity, IntervalType.Open), -100d, false }, // (]
new object[] { Interval.Range(-100d, double.PositiveInfinity, IntervalType.Closed, IntervalType.Open), double.PositiveInfinity, false }, // [)
new object[] { Interval.Range(-100d, double.PositiveInfinity, IntervalType.Closed, IntervalType.Open), 1d, true }, // [)
new object[] { Interval.Range(-100d, double.PositiveInfinity, IntervalType.Open, IntervalType.Open), 1d, true }, // ()
// Upper bounded interval: (-∞, b), [-∞, b), (-∞, b], [-∞, b]
new object[] { Interval.Range(double.NegativeInfinity, 0), -1d, true },
new object[] { Interval.Range(double.NegativeInfinity, 0), double.NegativeInfinity, true },
new object[] { Interval.Range(double.NegativeInfinity, 0, IntervalType.Open), double.NegativeInfinity, false },
// Unbounded interval: (-∞, +∞), [-∞, +∞] etc
new object[] { Interval.Range(double.NegativeInfinity, double.PositiveInfinity), 1d, true },
new object[] { Interval.Range(double.NegativeInfinity, double.PositiveInfinity), double.NegativeInfinity, true },
new object[] { Interval.Range(double.NegativeInfinity, double.PositiveInfinity), double.PositiveInfinity, true },
new object[] { Interval.Range(double.NegativeInfinity, double.PositiveInfinity, IntervalType.Open, IntervalType.Open), 1d, true },
new object[] { Interval.Range(double.NegativeInfinity, double.PositiveInfinity, IntervalType.Open, IntervalType.Open), double.NegativeInfinity, false },
new object[] { Interval.Range(double.NegativeInfinity, double.PositiveInfinity, IntervalType.Open, IntervalType.Open), double.PositiveInfinity, false },
// Implicit swapping of a and b, i.e., when b < a
new object[] { Interval.Range(10, 1), 1, true },
new object[] { Interval.Range(10, 1), 0, false },
new object[] { Interval.Range(10, 1), 11, false }
};
[Test, TestCaseSource("TestCases")]
public void Contains_should_return_correct_value_for<T>(Interval<T> interval, T point, bool expected) where T : struct, IComparable
{
interval.Contains(point).ShouldBeEquivalentTo(expected);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment