using System;
using System.Collections.Generic;
using System.Linq;
using BizHawk.Common.NumberExtensions;
namespace BizHawk.Common
{
/// represents a closed range of (class invariant: ≤ )
public interface Range where T : unmanaged, IComparable
{
T Start { get; }
T EndInclusive { get; }
}
/// represents a closed range of which can be grown or shrunk (class invariant: ≤ )
public class MutableRange : Range where T : unmanaged, IComparable
{
private (T Start, T EndInclusive) r;
///
public MutableRange(T start, T endInclusive) => Overwrite(start, endInclusive);
/// (from setter) >
public T Start
{
get => r.Start;
set => r.Start = r.EndInclusive.CompareTo(value) < 0
? throw new ArgumentOutOfRangeException(nameof(value), value, "attempted to set start > end")
: value;
}
/// (from setter) <
public T EndInclusive
{
get => r.EndInclusive;
set => r.EndInclusive = value.CompareTo(r.Start) < 0
? throw new ArgumentOutOfRangeException(nameof(value), value, "attempted to set end < start")
: value;
}
/// is / and either bound is
/// <
public void Overwrite(T start, T endInclusive)
{
if (endInclusive.CompareTo(start) < 0) throw new ArgumentOutOfRangeException(nameof(endInclusive), endInclusive, "range end < start");
if (start is float fs)
{
if (float.IsNaN(fs)) throw new ArgumentException("range start is NaN", nameof(start));
if (endInclusive is float fe && float.IsNaN(fe)) throw new ArgumentException("range end is NaN", nameof(endInclusive));
}
else if (start is double ds)
{
if (double.IsNaN(ds)) throw new ArgumentException("range start is NaN", nameof(start));
if (endInclusive is double de && double.IsNaN(de)) throw new ArgumentException("range end is NaN", nameof(endInclusive));
}
r = (start, endInclusive);
}
}
/// contains most of the logic for ranges
///
/// non-generic overloads are used where the method requires an increment or decrement
/// TODO which Enumerate algorithm is faster - yield return in loop or ?
///
public static class RangeExtensions
{
private const ulong MIN_LONG_NEGATION_AS_ULONG = 9223372036854775808UL;
private static readonly ArithmeticException ExclusiveRangeMinValExc = new ArithmeticException("exclusive range end is min value of integral type");
/// if it's contained in , or else whichever bound of is closest to
public static T ConstrainWithin(this T value, Range range) where T : unmanaged, IComparable => value.CompareTo(range.Start) < 0
? range.Start
: range.EndInclusive.CompareTo(value) < 0
? range.EndInclusive
: value;
/// true iff is contained in ( is considered to be in the range if it's exactly equal to either bound)
///
public static bool Contains(this Range range, T value) where T : unmanaged, IComparable => !(value.CompareTo(range.Start) < 0 || range.EndInclusive.CompareTo(value) < 0);
public static uint Count(this Range range) => (uint) (range.EndInclusive - range.Start + 1);
/// beware integer overflow when contains every value
public static uint Count(this Range range) => (uint) ((long) range.EndInclusive - range.Start) + 1U;
///
public static ulong Count(this Range range) => (range.Contains(0L)
? (range.Start == long.MinValue ? MIN_LONG_NEGATION_AS_ULONG : (ulong) -range.Start) + (ulong) range.EndInclusive
: (ulong) (range.EndInclusive - range.Start)
) + 1UL;
public static uint Count(this Range range) => (uint) (range.EndInclusive - range.Start + 1);
public static uint Count(this Range range) => (uint) (range.EndInclusive - range.Start + 1);
///
public static uint Count(this Range range) => range.EndInclusive - range.Start + 1U;
///
public static ulong Count(this Range range) => range.EndInclusive - range.Start + 1UL;
public static uint Count(this Range range) => (uint) (range.EndInclusive - range.Start + 1);
public static void Deconstruct(this Range range, out T start, out T endInclusive)
where T : unmanaged, IComparable
{
start = range.Start;
endInclusive = range.EndInclusive;
}
public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, (int) range.Count()).Select(i => (byte) i);
///
public static IEnumerable Enumerate(this Range range, double step)
{
var d = range.Start;
while (d < range.EndInclusive)
{
yield return d;
d += step;
}
if (d.HawkFloatEquality(range.EndInclusive)) yield return d;
}
/// beware precision errors
public static IEnumerable Enumerate(this Range range, float step)
{
var f = range.Start;
while (f < range.EndInclusive)
{
yield return f;
f += step;
}
if (f.HawkFloatEquality(range.EndInclusive)) yield return f;
}
public static IEnumerable Enumerate(this Range range)
{
var i = range.Start;
while (i < range.EndInclusive) yield return i++;
yield return i;
}
public static IEnumerable Enumerate(this Range range)
{
var l = range.Start;
while (l < range.EndInclusive) yield return l++;
yield return l;
}
public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, (int) range.Count()).Select(i => (sbyte) i);
public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, (int) range.Count()).Select(i => (short) i);
public static IEnumerable Enumerate(this Range range)
{
var i = range.Start;
while (i < range.EndInclusive) yield return i++;
yield return i;
}
public static IEnumerable Enumerate(this Range range)
{
var l = range.Start;
while (l < range.EndInclusive) yield return l++;
yield return l;
}
public static IEnumerable Enumerate(this Range range) => Enumerable.Range(range.Start, (int) range.Count()).Select(i => (ushort) i);
public static Range GetImmutableCopy(this Range range) where T : unmanaged, IComparable => GetMutableCopy(range);
public static MutableRange GetMutableCopy(this Range range) where T : unmanaged, IComparable => new MutableRange(range.Start, range.EndInclusive);
///
public static MutableRange MutableRangeTo(this T start, T endInclusive) where T : unmanaged, IComparable => new MutableRange(start, endInclusive);
///
public static MutableRange MutableRangeToExclusive(this byte start, byte endExclusive) => endExclusive == byte.MinValue
? throw ExclusiveRangeMinValExc
: new MutableRange(start, (byte) (endExclusive - 1U));
///
public static MutableRange MutableRangeToExclusive(this int start, int endExclusive) => endExclusive == int.MinValue
? throw ExclusiveRangeMinValExc
: new MutableRange(start, endExclusive - 1);
///
public static MutableRange MutableRangeToExclusive(this long start, long endExclusive) => endExclusive == long.MinValue
? throw ExclusiveRangeMinValExc
: new MutableRange(start, endExclusive - 1L);
///
public static MutableRange MutableRangeToExclusive(this sbyte start, sbyte endExclusive) => endExclusive == sbyte.MinValue
? throw ExclusiveRangeMinValExc
: new MutableRange(start, (sbyte) (endExclusive - 1));
///
public static MutableRange MutableRangeToExclusive(this short start, short endExclusive) => endExclusive == short.MinValue
? throw ExclusiveRangeMinValExc
: new MutableRange(start, (short) (endExclusive - 1));
///
public static MutableRange MutableRangeToExclusive(this uint start, uint endExclusive) => endExclusive == uint.MinValue
? throw ExclusiveRangeMinValExc
: new MutableRange(start, endExclusive - 1U);
///
public static MutableRange MutableRangeToExclusive(this ulong start, ulong endExclusive) => endExclusive == ulong.MinValue
? throw ExclusiveRangeMinValExc
: new MutableRange(start, endExclusive - 1UL);
///
public static MutableRange MutableRangeToExclusive(this ushort start, ushort endExclusive) => endExclusive == ushort.MinValue
? throw ExclusiveRangeMinValExc
: new MutableRange(start, (ushort) (endExclusive - 1U));
///
public static Range RangeTo(this T start, T endInclusive) where T : unmanaged, IComparable => start.MutableRangeTo(endInclusive);
///
public static Range RangeToExclusive(this byte start, byte endExclusive) => MutableRangeToExclusive(start, endExclusive);
/// ≤ (empty ranges where = are not permitted)
/// is min value of integral type (therefore ≤ )
public static Range RangeToExclusive(this int start, int endExclusive) => MutableRangeToExclusive(start, endExclusive);
///
public static Range RangeToExclusive(this long start, long endExclusive) => MutableRangeToExclusive(start, endExclusive);
///
public static Range RangeToExclusive(this sbyte start, sbyte endExclusive) => MutableRangeToExclusive(start, endExclusive);
///
public static Range RangeToExclusive(this short start, short endExclusive) => MutableRangeToExclusive(start, endExclusive);
///
public static Range RangeToExclusive(this uint start, uint endExclusive) => MutableRangeToExclusive(start, endExclusive);
///
public static Range RangeToExclusive(this ulong start, ulong endExclusive) => MutableRangeToExclusive(start, endExclusive);
///
public static Range RangeToExclusive(this ushort start, ushort endExclusive) => MutableRangeToExclusive(start, endExclusive);
/// true iff is strictly contained in ( is considered to be OUTSIDE the range if it's exactly equal to either bound)
///
public static bool StrictlyBoundedBy(this T value, Range range) where T : unmanaged, IComparable => range.Start.CompareTo(value) < 0 && value.CompareTo(range.EndInclusive) < 0;
}
}