#nullable enable
// Copyright (c) 2016 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
namespace ICSharpCode.Decompiler.Util
{
///
/// An immutable set of longs, that is implemented as a list of intervals.
///
public struct LongSet : IEquatable
{
///
/// The intervals in this set of longs.
///
///
/// Invariant: the intervals in this array are non-empty, non-overlapping, non-touching, and sorted.
///
/// This invariant ensures every LongSet is always in a normalized representation.
///
public readonly ImmutableArray Intervals;
private LongSet(ImmutableArray intervals)
{
this.Intervals = intervals;
#if DEBUG
// Check invariant
long minValue = long.MinValue;
for (int i = 0; i < intervals.Length; i++)
{
Debug.Assert(!intervals[i].IsEmpty);
Debug.Assert(minValue <= intervals[i].Start);
if (intervals[i].InclusiveEnd == long.MaxValue - 1 || intervals[i].InclusiveEnd == long.MaxValue)
{
// An inclusive end of long.MaxValue-1 or long.MaxValue means (after the gap of 1 element),
// there isn't any room for more non-empty intervals.
Debug.Assert(i == intervals.Length - 1);
}
else
{
minValue = checked(intervals[i].End + 1); // enforce least 1 gap between intervals
}
}
#endif
}
///
/// Create a new LongSet that contains a single value.
///
public LongSet(long value)
: this(ImmutableArray.Create(LongInterval.Inclusive(value, value)))
{
}
///
/// Create a new LongSet that contains the values from the interval.
///
public LongSet(LongInterval interval)
: this(interval.IsEmpty ? Empty.Intervals : ImmutableArray.Create(interval))
{
}
///
/// Creates a new LongSet the contains the values from the specified intervals.
///
public LongSet(IEnumerable intervals)
: this(MergeOverlapping(intervals.Where(i => !i.IsEmpty).OrderBy(i => i.Start)).ToImmutableArray())
{
}
///
/// The empty LongSet.
///
public static readonly LongSet Empty = new LongSet(ImmutableArray.Create());
///
/// The LongSet that contains all possible long values.
///
public static readonly LongSet Universe = new LongSet(LongInterval.Inclusive(long.MinValue, long.MaxValue));
public bool IsEmpty {
get { return Intervals.IsEmpty; }
}
///
/// Gets the number of values in this LongSet.
/// Note: for LongSet.Universe, the number of values does not fit into ulong.
/// Instead, this property returns the off-by-one value ulong.MaxValue to avoid overflow.
///
public ulong Count()
{
unchecked
{
ulong count = 0;
foreach (var interval in Intervals)
{
count += (ulong)(interval.End - interval.Start);
}
if (count == 0 && !Intervals.IsEmpty)
return ulong.MaxValue;
else
return count;
}
}
IEnumerable DoIntersectWith(LongSet other)
{
var enumA = this.Intervals.GetEnumerator();
var enumB = other.Intervals.GetEnumerator();
bool moreA = enumA.MoveNext();
bool moreB = enumB.MoveNext();
while (moreA && moreB)
{
LongInterval a = enumA.Current;
LongInterval b = enumB.Current;
LongInterval intersection = a.Intersect(b);
if (!intersection.IsEmpty)
{
yield return intersection;
}
if (a.InclusiveEnd < b.InclusiveEnd)
{
moreA = enumA.MoveNext();
}
else
{
moreB = enumB.MoveNext();
}
}
}
public bool Overlaps(LongSet other)
{
return DoIntersectWith(other).Any();
}
public LongSet IntersectWith(LongSet other)
{
return new LongSet(DoIntersectWith(other).ToImmutableArray());
}
///
/// Given an enumerable of non-empty intervals sorted by the starting position,
/// merges overlapping or touching intervals to create a valid interval array for LongSet.
///
static IEnumerable MergeOverlapping(IEnumerable input)
{
long start = long.MinValue;
long end = long.MinValue;
bool empty = true;
foreach (var element in input)
{
Debug.Assert(start <= element.Start);
Debug.Assert(!element.IsEmpty);
if (!empty && element.Start <= end)
{
// element overlaps or touches [start, end), so combine the intervals:
if (element.End == long.MinValue)
{
// special case: element goes all the way up to long.MaxValue inclusive
end = long.MinValue;
}
else
{
end = Math.Max(end, element.End);
}
}
else
{
// flush existing interval:
if (!empty)
{
yield return new LongInterval(start, end);
}
else
{
empty = false;
}
start = element.Start;
end = element.End;
}
if (end == long.MinValue)
{
// special case: element goes all the way up to long.MaxValue inclusive
// all further intervals in the input must be contained in [start, end),
// so ignore them (and avoid trouble due to the overflow in `end`).
break;
}
}
if (!empty)
{
yield return new LongInterval(start, end);
}
}
public LongSet UnionWith(LongSet other)
{
var mergedIntervals = this.Intervals.Merge(other.Intervals, (a, b) => a.Start.CompareTo(b.Start));
return new LongSet(MergeOverlapping(mergedIntervals).ToImmutableArray());
}
///
/// Creates a new LongSet where val is added to each element of this LongSet.
///
public LongSet AddOffset(long val)
{
if (val == 0)
{
return this;
}
var newIntervals = new List(Intervals.Length + 1);
foreach (var element in Intervals)
{
long newStart = unchecked(element.Start + val);
long newInclusiveEnd = unchecked(element.InclusiveEnd + val);
if (newStart <= newInclusiveEnd)
{
newIntervals.Add(LongInterval.Inclusive(newStart, newInclusiveEnd));
}
else
{
// interval got split by integer overflow
newIntervals.Add(LongInterval.Inclusive(newStart, long.MaxValue));
newIntervals.Add(LongInterval.Inclusive(long.MinValue, newInclusiveEnd));
}
}
newIntervals.Sort((a, b) => a.Start.CompareTo(b.Start));
return new LongSet(MergeOverlapping(newIntervals).ToImmutableArray());
}
///
/// Creates a new set that contains all values that are in this, but not in other.
///
public LongSet ExceptWith(LongSet other)
{
return IntersectWith(other.Invert());
}
///
/// Creates a new LongSet that contains all elements not contained in this LongSet.
///
public LongSet Invert()
{
// The loop below assumes a non-empty LongSet, so handle the empty case specially.
if (IsEmpty)
{
return Universe;
}
List newIntervals = new List(Intervals.Length + 1);
long prevEnd = long.MinValue; // previous exclusive end
foreach (var interval in Intervals)
{
if (interval.Start > prevEnd)
{
newIntervals.Add(new LongInterval(prevEnd, interval.Start));
}
prevEnd = interval.End;
}
// create a final interval up to long.MaxValue inclusive
if (prevEnd != long.MinValue)
{
newIntervals.Add(new LongInterval(prevEnd, long.MinValue));
}
return new LongSet(newIntervals.ToImmutableArray());
}
///
/// Gets whether this set is a subset of other, or equal.
///
public bool IsSubsetOf(LongSet other)
{
// TODO: optimize IsSubsetOf -- there's no need to build a temporary set
return this.UnionWith(other).SetEquals(other);
}
///
/// Gets whether this set is a superset of other, or equal.
///
public bool IsSupersetOf(LongSet other)
{
return other.IsSubsetOf(this);
}
public bool IsProperSubsetOf(LongSet other)
{
return IsSubsetOf(other) && !SetEquals(other);
}
public bool IsProperSupersetOf(LongSet other)
{
return IsSupersetOf(other) && !SetEquals(other);
}
public bool Contains(long val)
{
int index = upper_bound(val);
return index > 0 && Intervals[index - 1].Contains(val);
}
internal int upper_bound(long val)
{
int min = 0, max = Intervals.Length - 1;
while (max >= min)
{
int m = min + (max - min) / 2;
LongInterval i = Intervals[m];
if (val < i.Start)
{
max = m - 1;
continue;
}
if (val > i.End)
{
min = m + 1;
continue;
}
return m + 1;
}
return min;
}
public IEnumerable Values {
get { return Intervals.SelectMany(i => i.Range()); }
}
public override string ToString()
{
return string.Join(",", Intervals);
}
#region Equals and GetHashCode implementation
public override bool Equals(object? obj)
{
return obj is LongSet && SetEquals((LongSet)obj);
}
public override int GetHashCode()
{
throw new NotImplementedException();
}
[Obsolete("Explicitly call SetEquals() instead.")]
public bool Equals(LongSet other)
{
return SetEquals(other);
}
public bool SetEquals(LongSet other)
{
if (Intervals.Length != other.Intervals.Length)
return false;
for (int i = 0; i < Intervals.Length; i++)
{
if (Intervals[i] != other.Intervals[i])
return false;
}
return true;
}
#endregion
}
}