impl AlternateIndexList
This commit is contained in:
parent
b69f32c450
commit
08b328c16f
211
src/ObservableCollections/Internal/AlternateIndexList.cs
Normal file
211
src/ObservableCollections/Internal/AlternateIndexList.cs
Normal file
@ -0,0 +1,211 @@
|
||||
#pragma warning disable CS0436
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
public class AlternateIndexList<T> : IEnumerable<T>
|
||||
{
|
||||
List<IndexedValue> list; // alternate index is ordered
|
||||
|
||||
public AlternateIndexList()
|
||||
{
|
||||
this.list = new();
|
||||
}
|
||||
|
||||
public AlternateIndexList(IEnumerable<(int OrderedAlternateIndex, T Value)> values)
|
||||
{
|
||||
this.list = values.Select(x => new IndexedValue(x.OrderedAlternateIndex, x.Value)).ToList();
|
||||
}
|
||||
|
||||
void UpdateAlternateIndex(int startIndex, int incr)
|
||||
{
|
||||
var span = CollectionsMarshal.AsSpan(list);
|
||||
for (int i = startIndex; i < span.Length; i++)
|
||||
{
|
||||
span[i].AlternateIndex += incr;
|
||||
}
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => list[index].Value;
|
||||
}
|
||||
|
||||
public int Count => list.Count;
|
||||
|
||||
public void Insert(int alternateIndex, T value)
|
||||
{
|
||||
var index = list.BinarySearch(alternateIndex);
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
list.Insert(index, new(alternateIndex, value));
|
||||
UpdateAlternateIndex(index + 1, 1);
|
||||
}
|
||||
|
||||
public void InsertRange(int startingAlternateIndex, IEnumerable<T> values)
|
||||
{
|
||||
var index = list.BinarySearch(startingAlternateIndex);
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
using var iter = new InsertIterator(startingAlternateIndex, values);
|
||||
list.InsertRange(index, iter);
|
||||
UpdateAlternateIndex(index + iter.ConsumedCount, iter.ConsumedCount);
|
||||
}
|
||||
|
||||
public void Remove(T value)
|
||||
{
|
||||
var index = list.FindIndex(x => EqualityComparer<T>.Default.Equals(x.Value, value));
|
||||
if (index != -1)
|
||||
{
|
||||
list.RemoveAt(index);
|
||||
UpdateAlternateIndex(index, -1);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAt(int alternateIndex)
|
||||
{
|
||||
var index = list.BinarySearch(alternateIndex);
|
||||
if (index != -1)
|
||||
{
|
||||
list.RemoveAt(index);
|
||||
UpdateAlternateIndex(index, -1);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRange(int alternateIndex, int count)
|
||||
{
|
||||
var index = list.BinarySearch(alternateIndex);
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
list.RemoveRange(index, count);
|
||||
UpdateAlternateIndex(index, -count);
|
||||
}
|
||||
|
||||
public bool TryGetAtAlternateIndex(int alternateIndex, [MaybeNullWhen(true)] out T value)
|
||||
{
|
||||
var index = list.BinarySearch(alternateIndex);
|
||||
if (index < 0)
|
||||
{
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
value = list[index].Value!;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySetAtAlternateIndex(int alternateIndex, T value)
|
||||
{
|
||||
var index = list.BinarySearch(alternateIndex);
|
||||
if (index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
CollectionsMarshal.AsSpan(list)[index].Value = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
// Can't implement add, reverse and sort because alternate index is unknown
|
||||
// void Add();
|
||||
// void AddRange();
|
||||
// void Reverse();
|
||||
// void Sort();
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
yield return item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public IEnumerable<(int AlternateIndex, T Value)> GetIndexedValues()
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
yield return (item.AlternateIndex, item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
class InsertIterator(int startingIndex, IEnumerable<T> values) : IEnumerable<IndexedValue>, IEnumerator<IndexedValue>
|
||||
{
|
||||
IEnumerator<T> iter = values.GetEnumerator();
|
||||
IndexedValue current;
|
||||
|
||||
public int ConsumedCount { get; private set; }
|
||||
|
||||
public IndexedValue Current => current;
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose() => iter.Reset();
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (iter.MoveNext())
|
||||
{
|
||||
ConsumedCount++;
|
||||
current = new(startingIndex++, iter.Current);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset() => iter.Reset();
|
||||
|
||||
public IEnumerator<IndexedValue> GetEnumerator() => this;
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
struct IndexedValue : IComparable<IndexedValue>
|
||||
{
|
||||
public int AlternateIndex; // mutable
|
||||
public T Value; // mutable
|
||||
|
||||
public IndexedValue(int alternateIndex, T value)
|
||||
{
|
||||
this.AlternateIndex = alternateIndex;
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
public static implicit operator IndexedValue(int alternateIndex) // for query
|
||||
{
|
||||
return new IndexedValue(alternateIndex, default!);
|
||||
}
|
||||
|
||||
public int CompareTo(IndexedValue other)
|
||||
{
|
||||
return AlternateIndex.CompareTo(other.AlternateIndex);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return (AlternateIndex, Value).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,4 +25,11 @@
|
||||
<None Include="../../Icon.png" Pack="true" PackagePath="/" />
|
||||
<EmbeddedResource Include="..\..\LICENSE" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="PolySharp" Version="1.14.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
100
tests/ObservableCollections.Tests/AlternateIndexListTest.cs
Normal file
100
tests/ObservableCollections.Tests/AlternateIndexListTest.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using ObservableCollections.Internal;
|
||||
|
||||
namespace ObservableCollections.Tests;
|
||||
|
||||
public class AlternateIndexListTest
|
||||
{
|
||||
[Fact]
|
||||
public void Insert()
|
||||
{
|
||||
var list = new AlternateIndexList<string>();
|
||||
|
||||
list.Insert(0, "foo");
|
||||
list.Insert(1, "bar");
|
||||
list.Insert(2, "baz");
|
||||
list.GetIndexedValues().Should().Equal((0, "foo"), (1, "bar"), (2, "baz"));
|
||||
|
||||
list.Insert(1, "new-bar");
|
||||
list.GetIndexedValues().Should().Equal((0, "foo"), (1, "new-bar"), (2, "bar"), (3, "baz"));
|
||||
|
||||
|
||||
list.Insert(6, "zoo");
|
||||
list.GetIndexedValues().Should().Equal((0, "foo"), (1, "new-bar"), (2, "bar"), (3, "baz"), (6, "zoo"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertRange()
|
||||
{
|
||||
var list = new AlternateIndexList<string>();
|
||||
|
||||
list.Insert(0, "foo");
|
||||
list.Insert(1, "bar");
|
||||
list.Insert(2, "baz");
|
||||
|
||||
list.InsertRange(1, new[] { "new-foo", "new-bar", "new-baz" });
|
||||
list.GetIndexedValues().Should().Equal((0, "foo"), (1, "new-foo"), (2, "new-bar"), (3, "new-baz"), (4, "bar"), (5, "baz"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertSparsed()
|
||||
{
|
||||
var list = new AlternateIndexList<string>();
|
||||
|
||||
list.Insert(2, "foo");
|
||||
list.Insert(8, "baz"); // baz
|
||||
list.Insert(4, "bar");
|
||||
list.GetIndexedValues().Should().Equal((2, "foo"), (4, "bar"), (9, "baz"));
|
||||
|
||||
list.InsertRange(3, new[] { "new-foo", "new-bar", "new-baz" });
|
||||
list.GetIndexedValues().Should().Equal((2, "foo"), (3, "new-foo"), (4, "new-bar"), (5, "new-baz"), (7, "bar"), (12, "baz"));
|
||||
|
||||
list.InsertRange(1, new[] { "zoo" });
|
||||
list.GetIndexedValues().Should().Equal((1, "zoo"), (3, "foo"), (4, "new-foo"), (5, "new-bar"), (6, "new-baz"), (8, "bar"), (13, "baz"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove()
|
||||
{
|
||||
var list = new AlternateIndexList<string>();
|
||||
|
||||
list.Insert(0, "foo");
|
||||
list.Insert(1, "bar");
|
||||
list.Insert(2, "baz");
|
||||
|
||||
list.Remove("bar");
|
||||
list.GetIndexedValues().Should().Equal((0, "foo"), (1, "baz"));
|
||||
|
||||
list.RemoveAt(0);
|
||||
list.GetIndexedValues().Should().Equal((0, "baz"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveRange()
|
||||
{
|
||||
var list = new AlternateIndexList<string>();
|
||||
|
||||
list.Insert(0, "foo");
|
||||
list.Insert(1, "bar");
|
||||
list.Insert(2, "baz");
|
||||
|
||||
list.RemoveRange(1, 2);
|
||||
list.GetIndexedValues().Should().Equal((0, "foo"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetSet()
|
||||
{
|
||||
var list = new AlternateIndexList<string>();
|
||||
|
||||
list.Insert(0, "foo");
|
||||
list.Insert(2, "bar");
|
||||
list.Insert(4, "baz");
|
||||
|
||||
list.TryGetAtAlternateIndex(2, out var bar).Should().BeTrue();
|
||||
bar.Should().Be("bar");
|
||||
|
||||
list.TrySetAtAlternateIndex(4, "new-baz").Should().BeTrue();
|
||||
list.TryGetAtAlternateIndex(4, out var baz).Should().BeTrue();
|
||||
baz.Should().Be("new-baz");
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user