diff --git a/osu.Game/Lists/IntervalList.cs b/osu.Game/Lists/IntervalList.cs new file mode 100644 index 0000000000..493b6b6e72 --- /dev/null +++ b/osu.Game/Lists/IntervalList.cs @@ -0,0 +1,128 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections; +using System.Collections.Generic; +using osu.Framework.Lists; + +namespace osu.Game.Lists +{ + /// + /// Represents a list of intervals that can be used for whether a specific value falls into one of them. + /// + /// The type of interval values. + public class IntervalList : ICollection>, IReadOnlyList> + { + private static readonly IComparer type_comparer = Comparer.Default; + + private readonly SortedList> intervals = new SortedList>((x, y) => type_comparer.Compare(x.Start, y.Start)); + + /// + /// The index of the nearest interval from last call. + /// + protected int NearestIntervalIndex; + + /// + /// Whether the provided value is in any interval added to this list. + /// + /// The value to check for. + public bool IsInAnyInterval(T value) + { + if (intervals.Count == 0) + return false; + + // Clamp the nearest index in case there were intervals + // removed from the list causing the index to go out of range. + NearestIntervalIndex = Math.Clamp(NearestIntervalIndex, 0, Count - 1); + + if (type_comparer.Compare(value, this[NearestIntervalIndex].End) > 0) + { + while (type_comparer.Compare(value, this[NearestIntervalIndex].End) > 0 && NearestIntervalIndex < Count - 1) + NearestIntervalIndex++; + } + else + { + while (type_comparer.Compare(value, this[NearestIntervalIndex].Start) < 0 && NearestIntervalIndex > 0) + NearestIntervalIndex--; + } + + var nearestInterval = this[NearestIntervalIndex]; + + return type_comparer.Compare(value, nearestInterval.Start) >= 0 && + type_comparer.Compare(value, nearestInterval.End) <= 0; + } + + /// + /// Adds a new interval to the list. + /// + /// The start value of the interval. + /// The end value of the interval. + public void Add(T start, T end) => Add(new Interval(start, end)); + + #region ICollection> + + public int Count => intervals.Count; + + bool ICollection>.IsReadOnly => false; + + /// + /// Adds a new interval to the list + /// + /// The interval to add. + public void Add(Interval interval) => intervals.Add(interval); + + /// + /// Removes an existing interval from the list. + /// + /// The interval to remove. + /// Whether the provided interval exists in the list and has been removed. + public bool Remove(Interval interval) => intervals.Remove(interval); + + /// + /// Removes all intervals from the list. + /// + public void Clear() => intervals.Clear(); + + public void CopyTo(Interval[] array, int arrayIndex) => intervals.CopyTo(array, arrayIndex); + + public bool Contains(Interval item) => intervals.Contains(item); + + public IEnumerator> GetEnumerator() => intervals.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + + #region IReadOnlyList> + + public Interval this[int index] + { + get => intervals[index]; + set => intervals[index] = value; + } + + #endregion + } + + public readonly struct Interval + { + /// + /// The start value of this interval. + /// + public readonly T Start; + + /// + /// The end value of this interval. + /// + public readonly T End; + + public Interval(T start, T end) + { + bool startLessThanEnd = Comparer.Default.Compare(start, end) < 0; + + Start = startLessThanEnd ? start : end; + End = startLessThanEnd ? end : start; + } + } +}