Interval.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. using System;
  2. namespace MonoGame.Extended;
  3. /// <summary>
  4. /// Represents a closed mathematical interval [Min, Max] defined by comparable bounds.
  5. /// </summary>
  6. /// <typeparam name="T">
  7. /// The type of values contained in the interval.
  8. /// </typeparam>
  9. public readonly struct Interval<T> : IEquatable<Interval<T>> where T : IComparable<T>
  10. {
  11. private readonly T _min;
  12. private readonly T _max;
  13. /// <summary>
  14. /// Gets the minimum bound of the interval.
  15. /// </summary>
  16. /// <exception cref="InvalidOperationException">Thrown when the interval is empty.</exception>
  17. public T Min
  18. {
  19. get
  20. {
  21. if (IsEmpty)
  22. {
  23. throw new InvalidOperationException("Cannot access bounds of an empty interval");
  24. }
  25. return _min;
  26. }
  27. }
  28. /// <summary>
  29. /// Gets the maximum bound of the interval.
  30. /// </summary>
  31. /// <exception cref="InvalidOperationException">Thrown when the interval is empty.</exception>
  32. public T Max
  33. {
  34. get
  35. {
  36. if (IsEmpty)
  37. {
  38. throw new InvalidOperationException("Cannot access bounds of an empty interval");
  39. }
  40. return _max;
  41. }
  42. }
  43. /// <summary>
  44. /// Gets a value indicating whether the interval contains no values.
  45. /// </summary>
  46. public bool IsEmpty { get; }
  47. /// <summary>
  48. /// Gets a value indicating whether the interval contains exactly one value.
  49. /// </summary>
  50. public bool IsDegenerate => !IsEmpty && _min.Equals(_max);
  51. /// <summary>
  52. /// Gets a value indicating whether the interval contains more than one value.
  53. /// </summary>
  54. public bool IsProper => !IsEmpty && !_min.Equals(_max);
  55. /// <summary>
  56. /// Gets an empty interval.
  57. /// </summary>
  58. public static Interval<T> Empty => new(true);
  59. /// <summary>
  60. /// Initializes an interval with the specified bounds.
  61. /// </summary>
  62. /// <param name="min">The minimum bound of the interval.</param>
  63. /// <param name="max">The maximum bound of the interval.</param>
  64. /// <exception cref="ArgumentException">Thrown when min is greater than max.</exception>
  65. public Interval(T min, T max)
  66. {
  67. if (min.CompareTo(max) > 0)
  68. {
  69. throw new ArgumentException("Minimum bounds cannot be greater than maximum bounds");
  70. }
  71. _min = min;
  72. _max = max;
  73. IsEmpty = false;
  74. }
  75. /// <summary>
  76. /// Initializes a degenerate interval containing only the specified value.
  77. /// </summary>
  78. /// <param name="value">The single value to be contained in the interval.</param>
  79. public Interval(T value) : this(value, value) { }
  80. private Interval(bool isEmpty)
  81. {
  82. _min = default;
  83. _max = default;
  84. IsEmpty = true;
  85. }
  86. /// <summary>
  87. /// Determines whether the interval contains the specified value.
  88. /// </summary>
  89. /// <param name="value">The value to test for containment.</param>
  90. /// <returns>true if the value is within the interval bounds; otherwise, false.</returns>
  91. public bool Contains(T value)
  92. {
  93. if (IsEmpty)
  94. {
  95. return false;
  96. }
  97. return value.CompareTo(_min) >= 0 && value.CompareTo(_max) <= 0;
  98. }
  99. /// <summary>
  100. /// Determines whether this interval completely contains another interval.
  101. /// </summary>
  102. /// <param name="other">The interval to test for containment.</param>
  103. /// <returns>true if this interval completely contains the other interval; otherwise, false.</returns>
  104. public bool Contains(Interval<T> other)
  105. {
  106. if (other.IsEmpty)
  107. {
  108. return true;
  109. }
  110. if (IsEmpty)
  111. {
  112. return false;
  113. }
  114. return _min.CompareTo(other._min) <= 0 && _max.CompareTo(other._max) >= 0;
  115. }
  116. /// <summary>
  117. /// Determines whether this interval shares any values with another interval.
  118. /// </summary>
  119. /// <param name="other">The interval to test for overlap.</param>
  120. /// <returns>true if the intervals have any values in common; otherwise, false.</returns>
  121. public bool Overlap(Interval<T> other)
  122. {
  123. if (IsEmpty || other.IsEmpty)
  124. {
  125. return false;
  126. }
  127. return _min.CompareTo(other._max) <= 0 && _max.CompareTo(other._min) >= 0;
  128. }
  129. /// <summary>
  130. /// Computes the intersection of this interval with another interval.
  131. /// </summary>
  132. /// <param name="other">The interval to intersect with.</param>
  133. /// <returns>An interval containing only values present in both intervals, or empty if no intersection exists.</returns>
  134. public Interval<T> Intersect(Interval<T> other)
  135. {
  136. if (IsEmpty || other.IsEmpty || !Overlap(other))
  137. {
  138. return Empty;
  139. }
  140. T minBound = _min.CompareTo(other._min) >= 0 ? _min : other._min;
  141. T maxBound = _max.CompareTo(other._max) <= 0 ? _max : other._max;
  142. return new Interval<T>(minBound, maxBound);
  143. }
  144. /// <summary>
  145. /// Computes the smallest interval containing both this interval and another interval.
  146. /// </summary>
  147. /// <param name="other">The interval to compute the hull with.</param>
  148. /// <returns>The smallest interval that contains both intervals.</returns>
  149. public Interval<T> Hull(Interval<T> other)
  150. {
  151. if (IsEmpty)
  152. {
  153. return other;
  154. }
  155. if (other.IsEmpty)
  156. {
  157. return this;
  158. }
  159. T minBound = _min.CompareTo(other._min) <= 0 ? _min : other._min;
  160. T maxBound = _max.CompareTo(other._max) >= 0 ? _max : other._max;
  161. return new Interval<T>(minBound, maxBound);
  162. }
  163. /// <summary>
  164. /// Creates an interval that spans both specified values.
  165. /// </summary>
  166. /// <param name="a">The first value.</param>
  167. /// <param name="b">The second value.</param>
  168. /// <returns>An interval from the minimum to maximum of the two values.</returns>
  169. public static Interval<T> Hull(T a, T b)
  170. {
  171. if (a.CompareTo(b) <= 0)
  172. {
  173. return new Interval<T>(a, b);
  174. }
  175. else
  176. {
  177. return new Interval<T>(b, a);
  178. }
  179. }
  180. /// <inheritdoc />
  181. public override bool Equals(object obj) => obj is Interval<T> other && Equals(other);
  182. /// <inheritdoc />
  183. public bool Equals(Interval<T> other)
  184. {
  185. if (IsEmpty && other.IsEmpty)
  186. {
  187. return true;
  188. }
  189. if (IsEmpty && !other.IsEmpty)
  190. {
  191. return false;
  192. }
  193. return _min.Equals(other._min) && _max.Equals(other._max);
  194. }
  195. /// <inheritdoc />
  196. public override int GetHashCode()
  197. {
  198. if (IsEmpty)
  199. {
  200. return 0;
  201. }
  202. return HashCode.Combine(_min, _max);
  203. }
  204. /// <inheritdoc />
  205. public static bool operator ==(Interval<T> left, Interval<T> right) => left.Equals(right);
  206. /// <inheritdoc />
  207. public static bool operator !=(Interval<T> left, Interval<T> right) => !left.Equals(right);
  208. /// <inheritdoc />
  209. public static implicit operator Interval<T>(T value) => new(value);
  210. /// <inheritdoc />
  211. public override string ToString()
  212. {
  213. if (IsEmpty)
  214. {
  215. return "∅";
  216. }
  217. if (IsDegenerate)
  218. {
  219. return $"[{_min}]";
  220. }
  221. return $"[{_min}, {_max}]";
  222. }
  223. }