< Summary

Information
Class: MyNet.Primitives.Intervals.Interval<T>
Assembly: MyNet.Primitives
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Primitives/Intervals/Interval.cs
Tag: 323_28699572109
Line coverage
75%
Covered lines: 3
Uncovered lines: 1
Coverable lines: 4
Total lines: 600
Line coverage: 75%
Branch coverage
100%
Covered branches: 4
Total branches: 4
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)100%44100%
Create(...)100%210%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Primitives/Intervals/Interval.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="Interval.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Diagnostics.CodeAnalysis;
 10using System.Linq;
 11
 12namespace MyNet.Primitives.Intervals;
 13
 14/// <summary>
 15/// Represents an interval of values of type T, where T is a struct that implements <see cref="IComparable{T}"/>. This c
 16/// </summary>
 17/// <param name="start">The start boundary of the interval.</param>
 18/// <param name="end">The end boundary of the interval.</param>
 19/// <param name="allowWrappedBounds">Indicates whether wrapped bounds are allowed.</param>
 20/// <exception cref="ArgumentException">Thrown when the start boundary is greater than the end boundary or when the inte
 21public class Interval<T>(IntervalBoundary<T>? start, IntervalBoundary<T>? end, bool allowWrappedBounds = false)
 5122    : Interval<T, Interval<T>>(start, end, allowWrappedBounds)
 23    where T : struct, IComparable<T>
 24{
 25    /// <summary>
 26    /// Initializes a new instance of the <see cref="Interval{T}"/> class with the specified start and end values. The c
 27    /// </summary>
 28    /// <param name="start">The start value of the interval.</param>
 29    /// <param name="to">The end value of the interval.</param>
 30    /// <param name="isInclusive">Indicates whether the boundaries are inclusive.</param>
 31    public Interval(T? start, T? to, bool isInclusive = true)
 5132        : this(start.HasValue ? new IntervalBoundary<T>(start.Value, isInclusive) : null, to.HasValue ? new IntervalBoun
 33    {
 5134    }
 35
 36    /// <inheritdoc/>
 037    protected override Interval<T> Create(IntervalBoundary<T>? start, IntervalBoundary<T>? end) => new(start, end);
 38}
 39
 40/// <summary>
 41/// Represents an interval of values of type T, where T is a struct that implements <see cref="IComparable{T}"/>.
 42/// </summary>
 43/// <typeparam name="T">The type of the values in the interval.</typeparam>
 44/// <typeparam name="TSelf">The type of the interval.</typeparam>
 45public abstract class Interval<T, TSelf> : IInterval<T>
 46    where T : struct, IComparable<T>
 47    where TSelf : Interval<T, TSelf>
 48{
 49    /// <summary>
 50    /// Initializes a new instance of the <see cref="Interval{T, TSelf}"/> class with the specified start and end bounda
 51    /// </summary>
 52    /// <param name="start">The start boundary of the interval.</param>
 53    /// <param name="end">The end boundary of the interval.</param>
 54    /// <param name="allowWrappedBounds">Indicates whether wrapped bounds are allowed.</param>
 55    /// <exception cref="ArgumentException">Thrown when the start boundary is greater than the end boundary or when the 
 56    protected Interval(IntervalBoundary<T>? start, IntervalBoundary<T>? end, bool allowWrappedBounds = false)
 57    {
 58        if (start.HasValue && end.HasValue)
 59        {
 60            var comparison = start.Value.Value.CompareTo(end.Value.Value);
 61
 62            switch (comparison)
 63            {
 64                case > 0 when !allowWrappedBounds:
 65                    throw new ArgumentException("Start must be before end.");
 66                case 0 when !start.Value.IsInclusive || !end.Value.IsInclusive:
 67                    throw new ArgumentException("Empty interval.");
 68            }
 69        }
 70
 71        Start = start;
 72        End = end;
 73    }
 74
 75    /// <summary>
 76    /// Gets the start boundary of the interval. The start boundary is represented by an <see cref="IntervalBoundary{T}"
 77    /// </summary>
 78    public IntervalBoundary<T>? Start { get; }
 79
 80    /// <summary>
 81    /// Gets the end boundary of the interval. The end boundary is represented by an <see cref="IntervalBoundary{T}"/> s
 82    /// </summary>
 83    public IntervalBoundary<T>? End { get; }
 84
 85    /// <summary>
 86    /// Gets a value indicating whether the interval has a start boundary. An interval is considered to have a start bou
 87    /// </summary>
 88    public bool HasStart => Start.HasValue;
 89
 90    /// <summary>
 91    /// Gets a value indicating whether the interval has an end boundary. An interval is considered to have an end bound
 92    /// </summary>
 93    public bool HasEnd => End.HasValue;
 94
 95    /// <summary>
 96    /// Gets a value indicating whether the interval is unbounded on the lower end. An interval is considered unbounded 
 97    /// </summary>
 98    public bool IsUnboundedStart => !HasStart;
 99
 100    /// <summary>
 101    /// Gets a value indicating whether the interval is unbounded on the upper end. An interval is considered unbounded 
 102    /// </summary>
 103    public bool IsUnboundedEnd => !HasEnd;
 104
 105    /// <summary>
 106    /// Gets a value indicating whether the interval represents a single point. An interval is considered a point if it 
 107    /// </summary>
 108    public bool IsPoint => HasStart && HasEnd && Start!.Value.Value.CompareTo(End!.Value.Value) == 0;
 109
 110    /// <summary>
 111    /// Merges a collection of intervals by combining any overlapping or adjacent intervals into a single interval. The 
 112    /// </summary>
 113    /// <param name="intervals">The collection of intervals to merge.</param>
 114    /// <returns>A collection of merged intervals.</returns>
 115    public static IReadOnlyCollection<TSelf> MergeOverlapping(IEnumerable<TSelf> intervals)
 116    {
 117        var ordered = intervals.Order().ToList();
 118
 119        if (ordered.Count == 0)
 120            return [];
 121
 122        var result = new List<TSelf>();
 123
 124        var current = ordered[0];
 125
 126        for (var i = 1; i < ordered.Count; i++)
 127        {
 128            var next = ordered[i];
 129
 130            if (current.TryMerge(next, out var merged))
 131            {
 132                current = merged;
 133                continue;
 134            }
 135
 136            result.Add(current);
 137            current = next;
 138        }
 139
 140        result.Add(current);
 141
 142        return result;
 143    }
 144
 145    /// <summary>
 146    /// Determines whether the current interval is adjacent to another interval. Two intervals are considered adjacent i
 147    /// </summary>
 148    /// <param name="right">The interval to check for adjacency.</param>
 149    /// <returns><c>true</c> if the intervals are adjacent; otherwise, <c>false</c>.</returns>
 150    public bool IsAdjacentTo(TSelf right) => End?.Value.CompareNullableTo(right.Start?.Value) == 0 || right.End?.Value.C
 151
 152    /// <summary>
 153    /// Creates a new instance of the interval with the specified start and end boundaries. This method is abstract and 
 154    /// </summary>
 155    /// <param name="start">The start boundary of the interval.</param>
 156    /// <param name="end">The end boundary of the interval.</param>
 157    /// <returns>A new instance of the interval with the specified boundaries.</returns>
 158    protected abstract TSelf Create(IntervalBoundary<T>? start, IntervalBoundary<T>? end);
 159
 160    /// <summary>
 161    /// Determines whether the interval contains a specified value.
 162    /// </summary>
 163    /// <param name="value">The value to check.</param>
 164    /// <returns><c>true</c> if the interval contains the specified value; otherwise, <c>false</c>.</returns>
 165    public virtual bool Contains(T value)
 166    {
 167        var afterStart =
 168            !HasStart ||
 169            value.CompareTo(Start!.Value.Value) > 0 ||
 170            (value.CompareTo(Start.Value.Value) == 0 && Start.Value.IsInclusive);
 171
 172        var beforeEnd =
 173            !HasEnd ||
 174            value.CompareTo(End!.Value.Value) < 0 ||
 175            (value.CompareTo(End.Value.Value) == 0 && End.Value.IsInclusive);
 176
 177        return afterStart && beforeEnd;
 178    }
 179
 180    /// <summary>
 181    /// Determines whether the current interval contains another interval. An interval A is considered to contain anothe
 182    /// </summary>
 183    /// <param name="other">The interval to check for containment.</param>
 184    /// <returns><c>true</c> if the current interval contains the specified interval; otherwise, <c>false</c>.</returns>
 185    public virtual bool Contains(IInterval<T> other) => ContainsStart(other) && ContainsEnd(other);
 186
 187    /// <summary>
 188    /// Determines whether the current interval intersects with another interval. Two intervals are considered to inters
 189    /// </summary>
 190    /// <param name="other">The interval to check for intersection.</param>
 191    /// <returns><c>true</c> if the current interval intersects with the specified interval; otherwise, <c>false</c>.</r
 192    public virtual bool Intersects(IInterval<T> other)
 193    {
 194        if (End.HasValue && other.Start.HasValue)
 195        {
 196            var comparison = End.Value.Value.CompareTo(other.Start.Value.Value);
 197
 198            switch (comparison)
 199            {
 200                case < 0:
 201                case 0 when !End.Value.IsInclusive || !other.Start.Value.IsInclusive:
 202                    return false;
 203            }
 204        }
 205
 206        if (other.End.HasValue && Start.HasValue)
 207        {
 208            var comparison = other.End.Value.Value.CompareTo(Start.Value.Value);
 209
 210            switch (comparison)
 211            {
 212                case < 0:
 213                case 0 when !other.End.Value.IsInclusive || !Start.Value.IsInclusive:
 214                    return false;
 215            }
 216        }
 217
 218        return true;
 219    }
 220
 221    /// <summary>
 222    /// Determines whether the current interval can be merged with the specified interval. Two intervals can be merged i
 223    /// </summary>
 224    /// <param name="right">The interval to compare with the current interval.</param>
 225    /// <returns><c>true</c> if the current interval can be merged with the specified interval; otherwise, <c>false</c>.
 226    public bool CanMerge(TSelf right) => Intersects(right) || this.Touches(right);
 227
 228    /// <summary>
 229    /// Attempts to merge the current interval with the specified interval. If the intervals can be merged (i.e., they o
 230    /// </summary>
 231    /// <param name="right">The interval to merge with the current interval.</param>
 232    /// <param name="merged">When this method returns, contains the merged interval if the merge was successful; otherwi
 233    /// <returns><c>true</c> if the intervals were successfully merged; otherwise, <c>false</c>.</returns>
 234    public bool TryMerge(TSelf right, [NotNullWhen(true)] out TSelf? merged)
 235    {
 236        if (!CanMerge(right))
 237        {
 238            merged = null;
 239            return false;
 240        }
 241
 242        var start = MinStart((TSelf)this, right);
 243        var end = MaxEnd((TSelf)this, right);
 244
 245        merged = Create(start, end);
 246
 247        return true;
 248    }
 249
 250    /// <summary>
 251    /// Calculates the intersection of the current interval with the specified interval. If the intervals intersect, it 
 252    /// </summary>
 253    /// <param name="right">The interval to intersect with the current interval.</param>
 254    /// <returns>The intersection interval if the intervals intersect; otherwise, <c>null</c>.</returns>
 255    public TSelf? Intersection(TSelf right)
 256    {
 257        if (!Intersects(right))
 258            return null;
 259
 260        var start = MaxStart((TSelf)this, right);
 261        var end = MinEnd((TSelf)this, right);
 262
 263        return Create(start, end);
 264    }
 265
 266    /// <summary>
 267    /// Calculates the union of the current interval with the specified interval. If the intervals can be merged (i.e., 
 268    /// </summary>
 269    /// <param name="right">The interval to intersect with the current interval.</param>
 270    /// <returns>The union interval if the intervals union; otherwise, <c>null</c>.</returns>
 271    public IReadOnlyCollection<TSelf> Union(TSelf right)
 272        => TryMerge(right, out var merged) ? [merged] : new List<TSelf> { (TSelf)this, right }.Order().ToArray();
 273
 274    /// <summary>
 275    /// Calculates the gap between the current interval and the specified interval. If the intervals do not intersect or
 276    /// </summary>
 277    /// <param name="right">The interval to intersect with the current interval.</param>
 278    /// <returns>The gap interval if the intervals do not intersect or touch; otherwise, <c>null</c>.</returns>
 279    public TSelf? Gap(TSelf right)
 280    {
 281        if (Intersects(right) || this.Touches(right))
 282            return null;
 283
 284        var ordered = CompareTo(right) <= 0 ? ((TSelf)this, right) : (right, (TSelf)this);
 285
 286        return Create(new IntervalBoundary<T>(ordered.Item1.End!.Value.Value, !ordered.Item1.End.Value.IsInclusive),
 287            new IntervalBoundary<T>(ordered.Item2.Start!.Value.Value, !ordered.Item2.Start.Value.IsInclusive));
 288    }
 289
 290    /// <summary>
 291    /// Expands the current interval to include the specified value. If the value is outside the current interval, it cr
 292    /// </summary>
 293    /// <param name="value">The value to include in the interval.</param>
 294    /// <param name="inclusive">Specifies whether the new boundaries should be inclusive or exclusive.</param>
 295    /// <returns>A new interval that includes the specified value.</returns>
 296    public TSelf ExpandTo(T value, bool inclusive = true)
 297    {
 298        var start = Start;
 299        var end = End;
 300
 301        if (!HasStart || value.CompareTo(Start!.Value.Value) < 0)
 302        {
 303            start = new IntervalBoundary<T>(value, inclusive);
 304        }
 305
 306        if (!HasEnd || value.CompareTo(End!.Value.Value) > 0)
 307        {
 308            end = new IntervalBoundary<T>(value, inclusive);
 309        }
 310
 311        return Create(start, end);
 312    }
 313
 314    /// <summary>
 315    /// Expands the current interval to include another interval. If the other interval is outside the current interval,
 316    /// </summary>
 317    /// <param name="other">The interval to include in the current interval.</param>
 318    /// <returns>A new interval that includes the specified interval.</returns>
 319    public IInterval<T> ExpandTo(TSelf other) => Create(MinStart((TSelf)this, other), MaxEnd((TSelf)this, other));
 320
 321    /// <summary>
 322    /// Determines whether the current interval contains another interval. An interval A is considered to contain anothe
 323    /// </summary>
 324    /// <param name="other">The interval to check for containment.</param>
 325    /// <returns><c>true</c> if the current interval contains the specified interval; otherwise, <c>false</c>.</returns>
 326    private bool ContainsStart(IInterval<T> other)
 327    {
 328        if (!HasStart)
 329            return true;
 330
 331        if (!other.HasStart)
 332            return false;
 333
 334        var comparison =
 335            Start!.Value.Value.CompareTo(other.Start!.Value.Value);
 336
 337        return comparison switch
 338        {
 339            < 0 => true,
 340            > 0 => false,
 341            _ => Start.Value.IsInclusive || !other.Start.Value.IsInclusive
 342        };
 343    }
 344
 345    /// <summary>
 346    /// Determines whether the current interval contains another interval. An interval A is considered to contain anothe
 347    /// </summary>
 348    /// <param name="other">The interval to check for containment.</param>
 349    /// <returns><c>true</c> if the current interval contains the specified interval; otherwise, <c>false</c>.</returns>
 350    private bool ContainsEnd(IInterval<T> other)
 351    {
 352        if (!HasEnd)
 353            return true;
 354
 355        if (!other.HasEnd)
 356            return false;
 357
 358        var comparison =
 359            End!.Value.Value.CompareTo(other.End!.Value.Value);
 360
 361        return comparison switch
 362        {
 363            > 0 => true,
 364            < 0 => false,
 365            _ => End.Value.IsInclusive || !other.End.Value.IsInclusive
 366        };
 367    }
 368
 369    /// <summary>
 370    /// Determines whether two intervals are equal by comparing their boundaries. Two intervals are considered equal if 
 371    /// </summary>
 372    /// <param name="left">The left interval to compare.</param>
 373    /// <param name="right">The right interval to compare.</param>
 374    /// <returns><c>true</c> if the left interval is equal to the right interval; otherwise, <c>false</c>.</returns>
 375    public static bool operator ==(Interval<T, TSelf> left, IInterval<T> right) => left.Equals(right);
 376
 377    /// <summary>
 378    /// Determines whether two intervals are not equal by comparing their boundaries. Two intervals are considered not e
 379    /// </summary>
 380    /// <param name="left">The left interval to compare.</param>
 381    /// <param name="right">The right interval to compare.</param>
 382    /// <returns><c>true</c> if the left interval is not equal to the right interval; otherwise, <c>false</c>.</returns>
 383    public static bool operator !=(Interval<T, TSelf> left, IInterval<T> right) => !left.Equals(right);
 384
 385    /// <summary>
 386    /// Determines whether the current interval is greater than another interval. An interval A is considered to be grea
 387    /// </summary>
 388    /// <param name="left">The left interval to compare.</param>
 389    /// <param name="right">The right interval to compare.</param>
 390    /// <returns><c>true</c> if the left interval is greater than the right interval; otherwise, <c>false</c>.</returns>
 391    public static bool operator >(Interval<T, TSelf> left, IInterval<T> right) => left.CompareTo(right) > 0;
 392
 393    /// <summary>
 394    /// Determines whether the current interval is less than another interval. An interval A is considered to be less th
 395    /// </summary>
 396    /// <param name="left">The left interval to compare.</param>
 397    /// <param name="right">The right interval to compare.</param>
 398    /// <returns><c>true</c> if the left interval is less than the right interval; otherwise, <c>false</c>.</returns>
 399    public static bool operator <(Interval<T, TSelf> left, IInterval<T> right) => left.CompareTo(right) < 0;
 400
 401    /// <summary>
 402    /// Determines whether the current interval is greater than or equal to another interval. An interval A is considere
 403    /// </summary>
 404    /// <param name="left">The left interval to compare.</param>
 405    /// <param name="right">The right interval to compare.</param>
 406    /// <returns><c>true</c> if the left interval is greater than or equal to the right interval; otherwise, <c>false</c
 407    public static bool operator >=(Interval<T, TSelf> left, IInterval<T> right) => left.CompareTo(right) >= 0;
 408
 409    /// <summary>
 410    /// Determines whether the current interval is less than or equal to another interval. An interval A is considered t
 411    /// </summary>
 412    /// <param name="left">The left interval to compare.</param>
 413    /// <param name="right">The right interval to compare.</param>
 414    /// <returns><c>true</c> if the left interval is less than or equal to the right interval; otherwise, <c>false</c>.<
 415    public static bool operator <=(Interval<T, TSelf> left, IInterval<T> right) => left.CompareTo(right) <= 0;
 416
 417    /// <summary>
 418    /// Compares the current interval with another interval and returns an integer that indicates whether the current in
 419    /// </summary>
 420    /// <param name="other">The interval to compare with the current interval.</param>
 421    /// <returns>An integer that indicates whether the current interval is less than, equal to, or greater than the othe
 422    public int CompareTo(IInterval<T>? other)
 423    {
 424        if (other is null)
 425            return 1;
 426
 427        switch (HasStart)
 428        {
 429            case false when other.HasStart:
 430                return -1;
 431            case true when !other.HasStart:
 432                return 1;
 433            case true:
 434                {
 435                    var startComparison = Start!.Value.Value.CompareTo(other.Start!.Value.Value);
 436                    if (startComparison != 0)
 437                        return startComparison;
 438
 439                    if (Start.Value.IsInclusive != other.Start.Value.IsInclusive)
 440                        return Start.Value.IsInclusive ? -1 : 1;
 441                    break;
 442                }
 443        }
 444
 445        switch (HasEnd)
 446        {
 447            case false when other.HasEnd:
 448                return 1;
 449            case true when !other.HasEnd:
 450                return -1;
 451            case false:
 452                return 0;
 453            default:
 454                {
 455                    var endComparison = End!.Value.Value.CompareTo(other.End!.Value.Value);
 456                    return endComparison != 0 ? endComparison : End.Value.IsInclusive == other.End.Value.IsInclusive ? 0
 457                }
 458        }
 459    }
 460
 461    /// <summary>
 462    /// Determines whether the specified object is equal to the current interval. Two intervals are considered equal if 
 463    /// </summary>
 464    /// <param name="obj">The object to compare with the current interval.</param>
 465    /// <returns><c>true</c> if the specified object is equal to the current interval; otherwise, <c>false</c>.</returns
 466    public override bool Equals(object? obj) => obj is IInterval<T> interval && Start.Equals(interval.Start) && End.Equa
 467
 468    /// <summary>
 469    /// Returns a hash code for the current interval. The hash code is computed based on the Start and End properties of
 470    /// </summary>
 471    /// <returns>A hash code for the current interval.</returns>
 472    public override int GetHashCode() => HashCode.Combine(Start, End);
 473
 474    /// <summary>
 475    /// Returns a string that represents the current interval. The string is formatted as "Start - End", where Start and
 476    /// </summary>
 477    /// <returns>A string that represents the current interval.</returns>
 478    public override string ToString()
 479    {
 480        var startBracket = Start?.IsInclusive == true ? "[" : "]";
 481        var endBracket = End?.IsInclusive == true ? "]" : "[";
 482
 483        var start = HasStart ? Start!.Value.Value.ToString() : "-∞";
 484        var end = HasEnd ? End!.Value.Value.ToString() : "+∞";
 485
 486        return $"{startBracket}{start}; {end}{endBracket}";
 487    }
 488
 489    /// <summary>
 490    /// Determines the minimum start boundary between two intervals. If one of the intervals does not have a defined sta
 491    /// </summary>
 492    /// <param name="left">The first interval to compare.</param>
 493    /// <param name="right">The second interval to compare.</param>
 494    /// <returns>The minimum start boundary between the two intervals.</returns>
 495    private static IntervalBoundary<T>? MinStart(TSelf left, TSelf right)
 496    {
 497        if (!left.HasStart)
 498            return left.Start;
 499
 500        if (!right.HasStart)
 501            return right.Start;
 502
 503        var comparison =
 504            left.Start!.Value.Value.CompareTo(
 505                right.Start!.Value.Value);
 506
 507        return comparison switch
 508        {
 509            < 0 => left.Start,
 510            > 0 => right.Start,
 511            _ => left.Start.Value.IsInclusive ? left.Start : right.Start
 512        };
 513    }
 514
 515    /// <summary>
 516    /// Determines the maximum start boundary between two intervals. If one of the intervals does not have a defined sta
 517    /// </summary>
 518    /// <param name="left">The first interval to compare.</param>
 519    /// <param name="right">The second interval to compare.</param>
 520    /// <returns>The maximum start boundary between the two intervals.</returns>
 521    private static IntervalBoundary<T>? MaxStart(TSelf left, TSelf right)
 522    {
 523        if (!left.HasStart)
 524            return right.Start;
 525
 526        if (!right.HasStart)
 527            return left.Start;
 528
 529        var comparison =
 530            left.Start!.Value.Value.CompareTo(
 531                right.Start!.Value.Value);
 532
 533        return comparison switch
 534        {
 535            > 0 => left.Start,
 536            < 0 => right.Start,
 537            _ => !left.Start.Value.IsInclusive ? left.Start : right.Start
 538        };
 539    }
 540
 541    /// <summary>
 542    /// Determines the minimum end boundary between two intervals. If one of the intervals does not have a defined end b
 543    /// </summary>
 544    /// <param name="left">The first interval to compare.</param>
 545    /// <param name="right">The second interval to compare.</param>
 546    /// <returns>The minimum end boundary between the two intervals.</returns>
 547    private static IntervalBoundary<T>? MinEnd(TSelf left, TSelf right)
 548    {
 549        if (!left.HasEnd)
 550            return right.End;
 551
 552        if (!right.HasEnd)
 553            return left.End;
 554
 555        var comparison =
 556            left.End!.Value.Value.CompareTo(
 557                right.End!.Value.Value);
 558
 559        return comparison switch
 560        {
 561            < 0 => left.End,
 562            > 0 => right.End,
 563            _ => !left.End.Value.IsInclusive ? left.End : right.End
 564        };
 565    }
 566
 567    /// <summary>
 568    /// Determines the maximum end boundary between two intervals. If one of the intervals does not have a defined end b
 569    /// </summary>
 570    /// <param name="left">The first interval to compare.</param>
 571    /// <param name="right">The second interval to compare.</param>
 572    /// <returns>The maximum end boundary between the two intervals.</returns>
 573    private static IntervalBoundary<T>? MaxEnd(TSelf left, TSelf right)
 574    {
 575        if (!left.HasEnd)
 576            return left.End;
 577
 578        if (!right.HasEnd)
 579            return right.End;
 580
 581        var comparison = left.End!.Value.Value.CompareTo(right.End!.Value.Value);
 582
 583        return comparison switch
 584        {
 585            > 0 => left.End,
 586            < 0 => right.End,
 587            _ => left.End.Value.IsInclusive ? left.End : right.End
 588        };
 589    }
 590}
 591
 592/// <summary>
 593/// Represents a boundary of an interval, which can be either inclusive or exclusive. An inclusive boundary includes the
 594/// </summary>
 595/// <param name="Value">The value of the boundary.</param>
 596/// <param name="IsInclusive">Indicates whether the boundary is inclusive.</param>
 597/// <typeparam name="T">The type of the value.</typeparam>
 598public readonly record struct IntervalBoundary<T>(T Value, bool IsInclusive = true)
 599    where T : struct, IComparable<T>;
 600