impl AlternateIndexList

This commit is contained in:
neuecc 2024-09-02 16:27:23 +09:00
parent b69f32c450
commit 08b328c16f
3 changed files with 318 additions and 0 deletions

View 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();
}
}
}
}

View File

@ -25,4 +25,11 @@
<None Include="../../Icon.png" Pack="true" PackagePath="/" /> <None Include="../../Icon.png" Pack="true" PackagePath="/" />
<EmbeddedResource Include="..\..\LICENSE" /> <EmbeddedResource Include="..\..\LICENSE" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="PolySharp" Version="1.14.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project> </Project>

View 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");
}
}