Skip to content

Instantly share code, notes, and snippets.

@slavanap
Last active August 6, 2020 18:27
Show Gist options
  • Save slavanap/779dd0228025ba197937fd3fd4ea3b68 to your computer and use it in GitHub Desktop.
Save slavanap/779dd0228025ba197937fd3fd4ea3b68 to your computer and use it in GitHub Desktop.
Interval list
/*
MIT license
Copyright 2020 Vyacheslav Napadovsky.
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.Linq;
using System.Reflection;
namespace slavanap {
public class IntervalList<TKey, TValue> : SortedList<TKey, TValue> {
readonly FieldInfo _keysF, _valuesF, _sizeF, _versionF;
#pragma warning disable IDE1006 // Naming Styles
TKey[] _baseKeys { get => (TKey[])_keysF.GetValue(this); }
TValue[] _baseValues { get => (TValue[])_valuesF.GetValue(this); }
int _baseSize { get => (int)_sizeF.GetValue(this); set => _sizeF.SetValue(this, value); }
int _baseVersion { get => (int)_versionF.GetValue(this); set => _versionF.SetValue(this, value); }
#pragma warning restore IDE1006 // Naming Styles
public TKey MinKeyLimit { get; }
public TKey MaxKeyLimit { get; }
public IntervalList(TKey minKeyLimit, TKey maxKeyLimit, TValue defaultValue) {
if (!(Comparer.Compare(minKeyLimit, maxKeyLimit) < 0))
throw new ArgumentException("Invalid limits");
Add(minKeyLimit, defaultValue);
MinKeyLimit = minKeyLimit;
MaxKeyLimit = maxKeyLimit;
var t = typeof(SortedList<TKey, TValue>);
_keysF = t.GetField("keys", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidProgramException();
_valuesF = t.GetField("values", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidProgramException();
_sizeF = t.GetField("_size", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidProgramException();
_versionF = t.GetField("version", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidProgramException();
}
#region Helpers
// See SortedList.EnsureCapacity @ https://source.dot.net/#System.Collections.NonGeneric/System/Collections/SortedList.cs,a972180be50fbd36
private void EnsureCapacity(int min) {
const int MaxArrayLength = 0x7FEFFFFF;
var keys = _baseKeys;
int newCapacity = keys.Length == 0 ? 16 : keys.Length * 2;
if ((uint)newCapacity > MaxArrayLength)
newCapacity = MaxArrayLength;
if (newCapacity < min) newCapacity = min;
Capacity = newCapacity;
}
// New helper: Remove [startIndex, endIndex)
private void RemoveRange(int startIndex, int endIndex) {
if (startIndex >= endIndex)
return;
int size = _baseSize;
if (0 > startIndex || startIndex >= size)
throw new ArgumentException("Invalid index", nameof(startIndex));
if (0 > endIndex || endIndex > size)
throw new ArgumentException("Invalid index", nameof(endIndex));
var keys = _baseKeys;
var values = _baseValues;
int copyLength = size - endIndex;
int clearLength = endIndex - startIndex;
int clearIndex = size - clearLength;
Array.Copy(keys, endIndex, keys, startIndex, copyLength);
Array.Clear(keys, clearIndex, clearLength);
Array.Copy(values, endIndex, values, startIndex, copyLength);
Array.Clear(values, clearIndex, clearLength);
_baseSize = clearIndex;
_baseVersion++;
}
#endregion
public new TValue this[TKey index] {
get {
if (Comparer.Compare(index, MinKeyLimit) < 0 || Comparer.Compare(MaxKeyLimit, index) < 0)
throw new ArgumentException("Invalid key", nameof(index));
return _baseValues[UpperBound(Array.BinarySearch(_baseKeys, 0, _baseSize, index, Comparer)) - 1];
}
}
static int UpperBound(int x) => x >= 0 ? (x + 1) : (~x);
static int LowerBound(int x) => x >= 0 ? x : (~x);
public void Assign(TKey startKey, TKey endKey, TValue value) {
if (Comparer.Compare(startKey, endKey) < 0) {
if (Comparer.Compare(startKey, MinKeyLimit) < 0)
throw new ArgumentException("Invalid key", nameof(startKey));
if (Comparer.Compare(MaxKeyLimit, endKey) < 0)
throw new ArgumentException("Invalid key", nameof(endKey));
var keys = _baseKeys;
var values = _baseValues;
var size = _baseSize;
var cmp = Comparer;
var begin = LowerBound(Array.BinarySearch(keys, 0, size, startKey, cmp));
bool needToInsertAtBegin = begin == 0 || !values[begin - 1].Equals(value);
bool needToInsertAtEnd = false;
var end = UpperBound(Array.BinarySearch(keys, 0, size, endKey, cmp)) - 1;
if (values[end].Equals(value))
end++;
else if (Comparer.Compare(keys[end], endKey) < 0)
needToInsertAtEnd = true;
// ensure size
int space = end - begin;
if (needToInsertAtBegin)
space--;
if (space < 0) {
if (size - space > keys.Length) {
EnsureCapacity(size - space);
keys = _baseKeys;
values = _baseValues;
}
Array.Copy(keys, end, keys, end - space, size - end);
Array.Copy(values, end, values, end - space, size - end);
end -= space;
size -= space;
_baseSize = size;
}
if (needToInsertAtEnd)
keys[end] = endKey;
if (needToInsertAtBegin) {
keys[begin] = startKey;
values[begin] = value;
begin++;
}
RemoveRange(begin, end);
_baseVersion++;
}
}
public IEnumerable<(TKey Start, TKey End, TValue Value)> GetIntervals() {
using var enu = GetEnumerator();
enu.MoveNext();
var prev = enu.Current;
KeyValuePair<TKey, TValue> current = prev;
while (enu.MoveNext()) {
current = enu.Current;
yield return (Start: prev.Key, End: current.Key, Value: prev.Value);
prev = current;
}
yield return (Start: current.Key, End: MaxKeyLimit, Value: current.Value);
}
}
class Tests {
static void Main(string[] args) => RunTests();
static void RunTests() {
var intervals = new IntervalList<TimeSpan, string>(TimeSpan.Zero, TimeSpan.FromDays(1), "Free time");
intervals.Assign(TimeSpan.Parse("01:00:00"), TimeSpan.Parse("02:00:00"), "Task #1");
Print(intervals);
intervals.Assign(TimeSpan.Parse("05:00:00"), TimeSpan.Parse("08:00:00"), "Task #2");
Print(intervals);
intervals.Assign(TimeSpan.Parse("00:00:00"), TimeSpan.Parse("12:00:00"), "Overlapped Task");
Print(intervals);
Console.WriteLine("Press Enter to run full test");
Console.ReadLine();
RandomTest();
}
static void Print<K, V>(IntervalList<K, V> intervals) {
foreach (var item in intervals.GetIntervals())
Console.WriteLine(item);
Console.WriteLine();
}
static void RandomTest() {
var rand = new Random();
int values_num = 16;
var values = new char[values_num];
Array.Fill(values, 'a');
const int shift = 42;
var x = new IntervalList<int, char>(shift, shift + values_num, 'a');
for (int round = 0; round < 100000; ++round) {
int start = rand.Next(0, values_num);
int end = rand.Next(0, values_num - start - 1) + start + 1;
char c = "abcd"[rand.Next(3)];
for (int i = start; i < end; ++i)
values[i] = c;
Console.WriteLine("x.Assign({0}, {1}, '{2}');", start + shift, end + shift, c);
x.Assign(start + shift, end + shift, c);
Console.WriteLine("x._dict = ( {0} );", string.Join(", ", x.Select(s => string.Format("({0},'{1}')", s.Key, s.Value))));
char f = 'z';
foreach (var kv in x) {
if (f == kv.Value) {
Console.WriteLine("Incorrect list");
throw new InvalidProgramException();
}
f = kv.Value;
}
for (int i = 0; i < values_num; ++i) {
var v = values[i];
var xv = x[i + shift];
if (v != xv) {
Console.WriteLine("Test mismatch at #{0}: expected '{1}', got '{2}'", i + shift, v, xv);
throw new InvalidProgramException();
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment