|
@@ -11,11 +11,11 @@
|
|
|
|
|
|
using DotNetDxc;
|
|
|
using System;
|
|
|
+using System.Collections;
|
|
|
using System.Collections.Generic;
|
|
|
using System.ComponentModel;
|
|
|
using System.Diagnostics;
|
|
|
using System.Linq;
|
|
|
-using System.Xml;
|
|
|
using System.Xml.Linq;
|
|
|
|
|
|
namespace MainNs
|
|
@@ -204,6 +204,293 @@ namespace MainNs
|
|
|
#endregion Public methods.
|
|
|
}
|
|
|
|
|
|
+ /// <summary>Use this class to represent a range of int values.</summary>
|
|
|
+ [DebuggerDisplay("{Lo} - {Hi}")]
|
|
|
+ struct NumericRange
|
|
|
+ {
|
|
|
+ /// <summary>Low range, inclusive.</summary>
|
|
|
+ public int Lo;
|
|
|
+ /// <summary>High range, exclusive.</summary>
|
|
|
+ public int Hi;
|
|
|
+
|
|
|
+ public NumericRange(int lo, int hi)
|
|
|
+ {
|
|
|
+ this.Lo = lo;
|
|
|
+ this.Hi = hi;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool Equals(NumericRange other)
|
|
|
+ {
|
|
|
+ return this.IsEmpty && other.IsEmpty ||
|
|
|
+ this.Lo == other.Lo && this.Hi == other.Hi;
|
|
|
+ }
|
|
|
+
|
|
|
+ public override bool Equals(object other)
|
|
|
+ {
|
|
|
+ return other is NumericRange && Equals((NumericRange)other);
|
|
|
+ }
|
|
|
+
|
|
|
+ public override int GetHashCode()
|
|
|
+ {
|
|
|
+ return this.Lo | (this.Hi & 0xffff) << 16;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool operator==(NumericRange left, NumericRange right)
|
|
|
+ {
|
|
|
+ return left.Equals(right);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool operator !=(NumericRange left, NumericRange right)
|
|
|
+ {
|
|
|
+ return !left.Equals(right);
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool IsEmpty { get { return Lo == Hi; } }
|
|
|
+
|
|
|
+ public bool Contains(int val)
|
|
|
+ {
|
|
|
+ return (Lo <= val && val < Hi);
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool Contains(NumericRange range)
|
|
|
+ {
|
|
|
+ return (Lo <= range.Lo && range.Hi <= Hi);
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool IsAdjacent(NumericRange other)
|
|
|
+ {
|
|
|
+ return !this.IsEmpty && !other.IsEmpty &&
|
|
|
+ (other.Hi == this.Lo || this.Hi == other.Lo);
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool IsDisjoint(NumericRange other)
|
|
|
+ {
|
|
|
+ return this.Hi <= other.Lo || other.Hi <= this.Lo;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool Subtract(NumericRange other, out NumericRange reminder)
|
|
|
+ {
|
|
|
+ // If empty, nothing to subtract.
|
|
|
+ reminder = new NumericRange();
|
|
|
+ if (this.IsEmpty || other.IsEmpty)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // If disjoint, result is unchanged.
|
|
|
+ if (IsDisjoint(other))
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // If fully contained, result is empty.
|
|
|
+ if (other.Contains(this))
|
|
|
+ {
|
|
|
+ this.Lo = 0;
|
|
|
+ this.Hi = 0;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // If only low edge is contained, the high segment is a reminder.
|
|
|
+ if (other.Contains(this.Lo) && !other.Contains(this.Hi - 1))
|
|
|
+ {
|
|
|
+ this.Lo = other.Hi;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ // If only high edge is contained, the low segment is a reminder.
|
|
|
+ if (other.Contains(this.Hi - 1) && !other.Contains(this.Lo))
|
|
|
+ {
|
|
|
+ this.Hi = other.Lo;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ // In this case, the other result is contained within, so the
|
|
|
+ // subtraction produces two segments: 'this' keeps the low end,
|
|
|
+ // and 'reminder' takes the high end.
|
|
|
+ reminder.Hi = this.Hi;
|
|
|
+ this.Hi = other.Lo;
|
|
|
+ reminder.Lo = other.Hi;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool TryCoalesce(NumericRange other, out NumericRange coalesced)
|
|
|
+ {
|
|
|
+ // If either is empty, position is meaningless.
|
|
|
+ // Coalescing can happen unless disjoint.
|
|
|
+ if (this.IsEmpty || other.IsEmpty || (IsDisjoint(other) && !IsAdjacent(other)))
|
|
|
+ {
|
|
|
+ coalesced = new NumericRange();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ int newLo = Math.Min(this.Lo, other.Lo);
|
|
|
+ int newHi = Math.Max(this.Hi, other.Hi);
|
|
|
+ coalesced = new NumericRange(newLo, newHi);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ public override string ToString()
|
|
|
+ {
|
|
|
+ return this.Lo.ToString() + "-" + this.Hi.ToString();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>Use this class to represent multiple ranges that can be implicitly coalesced and split.</summary>
|
|
|
+ class NumericRanges : IEnumerable<NumericRange>
|
|
|
+ {
|
|
|
+ /// <summary>Ordered ranges.</summary>
|
|
|
+ private List<NumericRange> ranges = new List<NumericRange>();
|
|
|
+
|
|
|
+ public void Add(int lo, int hi)
|
|
|
+ {
|
|
|
+ if (lo == hi) return;
|
|
|
+ // Trivial common case - appending to last.
|
|
|
+ if (this.ranges.Count > 0)
|
|
|
+ {
|
|
|
+ NumericRange range = this.ranges[this.ranges.Count - 1];
|
|
|
+ if (range.Lo <= lo && lo <= range.Hi)
|
|
|
+ {
|
|
|
+ if (hi <= range.Hi)
|
|
|
+ {
|
|
|
+ return; // already contained
|
|
|
+ }
|
|
|
+ this.ranges[this.ranges.Count - 1] = new NumericRange(range.Lo, hi);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // To keep this simple, add the new range by lo end, then coalesce.
|
|
|
+ bool added = false;
|
|
|
+ int coalesce = 0;
|
|
|
+ for (int i = 0; i < ranges.Count; ++i)
|
|
|
+ {
|
|
|
+ if (lo < ranges[i].Lo)
|
|
|
+ {
|
|
|
+ // Comes prior to this range.
|
|
|
+ ranges.Insert(i, new NumericRange(lo, hi));
|
|
|
+ added = true;
|
|
|
+ coalesce = i - 1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (hi <= ranges[i].Hi)
|
|
|
+ {
|
|
|
+ // Fully overlapped.
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!added)
|
|
|
+ {
|
|
|
+ ranges.Add(new NumericRange(lo, hi));
|
|
|
+ coalesce = ranges.Count - 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Coalesce a few items ahead from our starting point.
|
|
|
+ coalesce = Math.Max(0, coalesce);
|
|
|
+ for (int i = coalesce; i < coalesce + 2;)
|
|
|
+ {
|
|
|
+ int next = i + 1;
|
|
|
+ if (next >= ranges.Count)
|
|
|
+ return;
|
|
|
+ NumericRange range;
|
|
|
+ if (ranges[i].TryCoalesce(ranges[next], out range))
|
|
|
+ {
|
|
|
+ ranges[i] = range;
|
|
|
+ ranges.RemoveAt(next);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Add(NumericRange range)
|
|
|
+ {
|
|
|
+ Add(range.Lo, range.Hi);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Clear()
|
|
|
+ {
|
|
|
+ this.ranges.Clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool Contains(NumericRange range)
|
|
|
+ {
|
|
|
+ foreach (var r in ranges)
|
|
|
+ {
|
|
|
+ if (range.Hi < r.Lo)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (r.Contains(range))
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool IsEmpty
|
|
|
+ {
|
|
|
+ get { return this.ranges.Count == 0; }
|
|
|
+ }
|
|
|
+
|
|
|
+ public IEnumerator<NumericRange> GetEnumerator()
|
|
|
+ {
|
|
|
+ return this.ranges.GetEnumerator();
|
|
|
+ }
|
|
|
+
|
|
|
+ public NumericRanges ListGaps(NumericRange range)
|
|
|
+ {
|
|
|
+ NumericRanges result = new NumericRanges();
|
|
|
+ result.Add(range);
|
|
|
+ foreach (var r in ranges)
|
|
|
+ {
|
|
|
+ result.Remove(r);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Remove(NumericRange range)
|
|
|
+ {
|
|
|
+ for (int i = 0; i < ranges.Count;)
|
|
|
+ {
|
|
|
+ NumericRange r = ranges[i];
|
|
|
+ if (range.Hi < r.Lo)
|
|
|
+ return;
|
|
|
+ NumericRange remainder;
|
|
|
+ if (r.Subtract(range, out remainder))
|
|
|
+ {
|
|
|
+ if (r.IsEmpty)
|
|
|
+ {
|
|
|
+ ranges.RemoveAt(i);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ranges[i] = r;
|
|
|
+ if (!remainder.IsEmpty)
|
|
|
+ {
|
|
|
+ ranges.Insert(i + 1, remainder);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ IEnumerator IEnumerable.GetEnumerator()
|
|
|
+ {
|
|
|
+ return this.ranges.GetEnumerator();
|
|
|
+ }
|
|
|
+
|
|
|
+ public override string ToString()
|
|
|
+ {
|
|
|
+ return String.Join(",", ranges.Select(r => r.ToString()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
class SettingsManager
|
|
|
{
|
|
|
#region Private fields.
|
|
@@ -347,4 +634,92 @@ namespace MainNs
|
|
|
return System.Convert.ToBase64String(bytes);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+#if FALSE
|
|
|
+ public class NumericRangesTests
|
|
|
+ {
|
|
|
+ public static void Assert(bool condition)
|
|
|
+ {
|
|
|
+ Debug.Assert(condition);
|
|
|
+ }
|
|
|
+ private static NumericRange NR(int lo, int hi) { return new NumericRange(lo, hi); }
|
|
|
+ public static void Run()
|
|
|
+ {
|
|
|
+ NumericRange empty = new NumericRange();
|
|
|
+ Assert(empty.IsEmpty);
|
|
|
+
|
|
|
+ NumericRange n = new NumericRange();
|
|
|
+ Assert(!n.Contains(0));
|
|
|
+ n.Hi = 1;
|
|
|
+ Assert(n.Contains(0));
|
|
|
+ Assert(!n.Contains(1));
|
|
|
+ Assert(n.IsDisjoint(new NumericRange()));
|
|
|
+ Assert(n.IsDisjoint(new NumericRange(1, 2)));
|
|
|
+
|
|
|
+ NumericRange remainder;
|
|
|
+ // Subtract - empty cases
|
|
|
+ Assert(false == n.Subtract(empty, out remainder));
|
|
|
+ Assert(remainder.IsEmpty);
|
|
|
+ Assert(false == empty.Subtract(n, out remainder));
|
|
|
+
|
|
|
+ // Subtract - disjoint
|
|
|
+ Assert(false == NR(0, 3).Subtract(NR(3, 5), out remainder));
|
|
|
+ n = NR(0, 3);
|
|
|
+
|
|
|
+ // Subtract - contained
|
|
|
+ Assert(n.Subtract(NR(0, 4), out remainder));
|
|
|
+ Assert(n.IsEmpty);
|
|
|
+
|
|
|
+ // Subtract - partial overlaps
|
|
|
+ n = NR(10, 12);
|
|
|
+ Assert(n.Subtract(NR(8, 11), out remainder));
|
|
|
+ Assert(n == NR(11, 12));
|
|
|
+ Assert(remainder.IsEmpty);
|
|
|
+ n = NR(10, 12);
|
|
|
+ Assert(n.Subtract(NR(11, 12), out remainder));
|
|
|
+ Assert(n == NR(10, 11));
|
|
|
+ Assert(remainder.IsEmpty);
|
|
|
+ n = NR(10, 21);
|
|
|
+ Assert(n.Subtract(NR(13, 17), out remainder));
|
|
|
+ Assert(n == NR(10, 13));
|
|
|
+ Assert(remainder == NR(17, 21));
|
|
|
+
|
|
|
+ // Coalesce - no-ops.
|
|
|
+ NumericRange coalesced;
|
|
|
+ n = NR(10, 21);
|
|
|
+ Assert(false == n.TryCoalesce(NR(0, 9), out coalesced));
|
|
|
+ Assert(false == n.TryCoalesce(empty, out coalesced));
|
|
|
+ Assert(n.TryCoalesce(NR(0, 10), out coalesced));
|
|
|
+ Assert(coalesced == NR(0, 21));
|
|
|
+ n = NR(10, 21);
|
|
|
+ Assert(n.TryCoalesce(NR(0, 100), out coalesced));
|
|
|
+ Assert(coalesced == NR(0, 100));
|
|
|
+
|
|
|
+ NumericRanges r = new NumericRanges();
|
|
|
+ Assert(r.IsEmpty);
|
|
|
+ r.Add(0, 10);
|
|
|
+ Assert(r.ToString() == "0-10");
|
|
|
+ r.Add(0, 4);
|
|
|
+ Assert(r.ToString() == "0-10");
|
|
|
+ r.Add(20, 20);
|
|
|
+ Assert(r.ToString() == "0-10");
|
|
|
+ r.Add(20, 30);
|
|
|
+ Assert(r.ToString() == "0-10,20-30");
|
|
|
+ r.Add(10, 20);
|
|
|
+ Assert(r.ToString() == "0-30");
|
|
|
+ r.Add(30, 32);
|
|
|
+ Assert(r.ToString() == "0-32");
|
|
|
+
|
|
|
+ r.Clear();
|
|
|
+ Assert(r.ToString() == "");
|
|
|
+
|
|
|
+ r.Add(NR(0, 10));
|
|
|
+ r.Add(NR(20, 30));
|
|
|
+
|
|
|
+ var gaps = r.ListGaps(NR(0, 40)).ToList();
|
|
|
+ Assert(gaps[0] == NR(10, 20));
|
|
|
+ Assert(gaps[1] == NR(30, 40));
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
}
|