470 lines
9.7 KiB
C#

using System.Collections;
using System.Text;
namespace AssetRipper.Numerics;
/// <summary>
/// An immutable structure representing a discontinuous, possibly empty, range of objects.
/// </summary>
/// <typeparam name="T"></typeparam>
public readonly struct DiscontinuousRange<T> : IEquatable<DiscontinuousRange<T>>, IEquatable<Range<T>>, IReadOnlyList<Range<T>>
where T : notnull, IComparable<T>, IEquatable<T>
{
private static readonly List<Range<T>> defaultRangeList = new List<Range<T>>(0);
private readonly List<Range<T>> rangeList;
public static DiscontinuousRange<T> Empty { get; } = new();
public DiscontinuousRange()
{
rangeList = defaultRangeList;
}
public DiscontinuousRange(Range<T> range)
{
rangeList = new List<Range<T>>(1) { range };
}
public DiscontinuousRange(IEnumerable<Range<T>> ranges)
{
rangeList = new();
Add(ranges);
if (rangeList.Count == 0)
{
rangeList = defaultRangeList;
}
}
public DiscontinuousRange(IReadOnlyList<Range<T>> ranges)
{
if (ranges.Count == 0)
{
rangeList = defaultRangeList;
}
else
{
rangeList = new List<Range<T>>(ranges.Count);
Add(ranges);
}
}
public DiscontinuousRange(params Range<T>[] ranges)
{
rangeList = new List<Range<T>>(ranges.Length);
Add(ranges);
}
/// <summary>
/// Used only for internal creations of the range list.
/// </summary>
/// <param name="ranges"></param>
private DiscontinuousRange(List<Range<T>> rangeList)
{
this.rangeList = rangeList;
}
/// <summary>
/// Constructor for the union of two ranges.
/// </summary>
/// <param name="range1"></param>
/// <param name="range2"></param>
public DiscontinuousRange(DiscontinuousRange<T> range1, DiscontinuousRange<T> range2)
{
if (range1.Count == 0)
{
rangeList = range2.rangeList;//prevent unnecessary allocation of an additional list
}
else if (range2.Count == 0)
{
rangeList = range1.rangeList;//prevent unnecessary allocation of an additional list
}
else
{
rangeList = new List<Range<T>>(range1.Count);
rangeList.AddRange(range1.rangeList);//Copy the elements of range1
Add(range2.rangeList);//Add the elements of range2
}
}
public int Count => rangeList.Count;
public Range<T> this[int index] => rangeList[index];
public override bool Equals(object? obj)
{
return obj is Range<T> range && Equals(range)
|| obj is DiscontinuousRange<T> discontinuousRange && Equals(discontinuousRange);
}
public bool Equals(Range<T> other)
{
return Count == 1 && this[0] == other;
}
public bool Equals(DiscontinuousRange<T> other)
{
if (Count != other.Count)
{
return false;
}
for (int i = 0; i < rangeList.Count; i++)
{
if (!this[i].Equals(other[i]))
{
return false;
}
}
return true;
}
public bool Contains(T point)
{
return rangeList.Any(r => r.Contains(point));
}
public bool Contains(Range<T> range)
{
return rangeList.Any(r => r.Contains(range));
}
public bool Contains(DiscontinuousRange<T> other)
{
foreach (Range<T> range in other.rangeList)
{
if (!Contains(range))
{
return false;
}
}
return true;
}
public bool Intersects(Range<T> range)
{
return rangeList.Any(r => r.Intersects(range));
}
public bool Intersects(DiscontinuousRange<T> other)
{
int i = 0;
int j = 0;
while (i < Count && j < other.Count)
{
Range<T> thisRange = this[i];
Range<T> otherRange = other[j];
if (thisRange.Intersects(otherRange))
{
return true;
}
else if (thisRange.IsStrictlyLess(otherRange))
{
i++;
}
else//otherRange is strictly less than thisRange
{
j++;
}
}
return false;
}
public override int GetHashCode()
{
HashCode hc = new();
for (int i = Count - 1; i >= 0; i--)
{
hc.Add(this[i]);
}
return hc.ToHashCode();
}
/// <summary>
/// Only used during initialization
/// </summary>
/// <param name="range"></param>
private void Add(Range<T> range)
{
int firstUnionIndex = Count;
int lastUnionIndex = Count;
for (int i = 0; i < Count; i++)
{
if (this[i].CanUnion(range))
{
if (firstUnionIndex == Count)
{
firstUnionIndex = i;
}
lastUnionIndex = i;
}
else if (firstUnionIndex < Count)
{
break;
}
else if (rangeList[i].IsStrictlyGreater(range))
{
rangeList.Insert(i, range);
return;
}
}
if (firstUnionIndex == Count)
{
rangeList.Add(range);
}
else
{
rangeList[firstUnionIndex] = rangeList[firstUnionIndex].MakeUnion(range);
if (firstUnionIndex < lastUnionIndex)
{
rangeList.RemoveRange(firstUnionIndex + 1, lastUnionIndex - firstUnionIndex);
}
}
}
/// <summary>
/// Only used during initialization
/// </summary>
/// <param name="ranges"></param>
private void Add(IEnumerable<Range<T>> ranges)
{
foreach (Range<T> range in ranges)
{
Add(range);
}
}
/// <summary>
/// Only used during initialization
/// </summary>
/// <param name="ranges"></param>
private void Add(Range<T>[] ranges)
{
for (int i = 0; i < ranges.Length; i++)
{
Add(ranges[i]);
}
}
/// <summary>
/// This range contains no points and is equal to the <see cref="Empty"/> range.
/// </summary>
/// <returns></returns>
public bool IsEmpty() => Count == 0;
/// <summary>
/// This range is continuous.
/// </summary>
/// <remarks>
/// The <see cref="Empty"/> range is treated as not continuous.
/// </remarks>
/// <returns></returns>
public bool IsContinuous() => Count == 1;
/// <summary>
/// This range is continuous.
/// </summary>
/// <remarks>
/// The <see cref="Empty"/> range is treated as not continuous.
/// </remarks>
/// <param name="range"></param>
/// <returns></returns>
public bool IsContinuous(out Range<T> range)
{
if (Count == 1)
{
range = this[0];
return true;
}
else
{
range = default;
return false;
}
}
public DiscontinuousRange<T> Negate(T minimum, T maximum)
{
if (IsEmpty())
{
return new DiscontinuousRange<T>(new Range<T>(minimum, maximum));
}
else
{
List<Range<T>> newRangeList = new List<Range<T>>(Count + 1);//Count + 1 is the maximum possible size of this list.
T start = rangeList[0].Start;
if (!start.Equals(minimum))
{
newRangeList.Add(new Range<T>(minimum, start));
}
for (int i = 0; i < Count - 1; i++)
{
newRangeList.Add(new Range<T>(rangeList[i].End, rangeList[i].Start));
}
T end = rangeList[Count - 1].End;
if (!end.Equals(maximum))
{
newRangeList.Add(new Range<T>(end, maximum));
}
return new DiscontinuousRange<T>(newRangeList);
}
}
public IEnumerator<Range<T>> GetEnumerator() => rangeList.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public static bool operator ==(DiscontinuousRange<T> left, DiscontinuousRange<T> right)
{
return left.Equals(right);
}
public static bool operator !=(DiscontinuousRange<T> left, DiscontinuousRange<T> right)
{
return !(left == right);
}
public static implicit operator DiscontinuousRange<T>(Range<T> range) => new DiscontinuousRange<T>(range);
public static explicit operator Range<T>(DiscontinuousRange<T> range)
{
return range.IsContinuous(out Range<T> continuousRange)
? continuousRange
: throw new ArgumentException($"{nameof(range)} is not continuous.", nameof(range));
}
public static DiscontinuousRange<T> Union(DiscontinuousRange<T> range1, DiscontinuousRange<T> range2)
{
return new DiscontinuousRange<T>(range1, range2);
}
public DiscontinuousRange<T> Union(DiscontinuousRange<T> other)
{
return new DiscontinuousRange<T>(this, other);
}
public static DiscontinuousRange<T> Intersect(DiscontinuousRange<T> range1, DiscontinuousRange<T> range2)
{
return range1.Intersect(range2);
}
public DiscontinuousRange<T> Intersect(DiscontinuousRange<T> other)
{
List<Range<T>> ranges = new();
int i = 0;
int j = 0;
while (i < Count && j < other.Count)
{
Range<T> thisRange = this[i];
Range<T> otherRange = other[j];
if (thisRange.Intersects(otherRange, out Range<T> intersection))
{
ranges.Add(intersection);
if (thisRange.End.CompareTo(otherRange.End) <= 0)
{
i++;
}
else
{
j++;
}
}
else if (thisRange.IsStrictlyLess(otherRange))
{
i++;
}
else//otherRange is strictly less than thisRange
{
j++;
}
}
return new DiscontinuousRange<T>(ranges);
}
public DiscontinuousRange<T> Subtract(DiscontinuousRange<T> other)
{
List<Range<T>> ranges = new();
int i = 0;
int j = 0;
bool endedInside = false;
T? nextStartingPoint = default;
while (i < Count && j < other.Count)
{
Range<T> thisRange = this[i];
Range<T> otherRange = other[j];
if (thisRange.IsStrictlyLess(otherRange))
{
if (endedInside)
{
ranges.Add(new Range<T>(nextStartingPoint!, thisRange.End));
endedInside = false;
nextStartingPoint = default;
}
i++;
}
else if (thisRange.IsStrictlyGreater(otherRange))
{
j++;
}
else
{
if (thisRange.Start.IsLess(otherRange.Start))
{
if (endedInside)
{
ranges.Add(new Range<T>(nextStartingPoint!, otherRange.Start));
endedInside = false;
nextStartingPoint = default;
}
else
{
ranges.Add(new Range<T>(thisRange.Start, otherRange.Start));
}
}
if (thisRange.End.IsLessEqual(otherRange.End))
{
i++;
}
else
{
if (j < other.Count - 1)
{
endedInside = true;
nextStartingPoint = otherRange.End;
}
else
{
ranges.Add(new Range<T>(otherRange.End, thisRange.End));
}
j++;
}
}
}
return new DiscontinuousRange<T>(ranges);
}
public override string ToString()
{
if (Count == 0)
{
return "Empty";
}
else
{
StringBuilder sb = new();
sb.Append(this[0]);
for (int i = 1; i < Count; i++)
{
sb.Append(", ");
sb.Append(this[i]);
}
return sb.ToString();
}
}
}