From 6fa62a27617785ec5e0d9efb49d9e46f8e565026 Mon Sep 17 00:00:00 2001 From: neuecc Date: Thu, 19 Aug 2021 19:14:33 +0900 Subject: [PATCH] Ring --- .../ObservableRingBuffer.cs | 12 +- src/ObservableCollections/RingBuffer.cs | 284 ++++++++++++++++++ .../RingBufferTest.cs | 129 ++++++++ 3 files changed, 423 insertions(+), 2 deletions(-) create mode 100644 src/ObservableCollections/RingBuffer.cs create mode 100644 tests/ObservableCollections.Tests/RingBufferTest.cs diff --git a/src/ObservableCollections/ObservableRingBuffer.cs b/src/ObservableCollections/ObservableRingBuffer.cs index e025d6b..1ab0b5a 100644 --- a/src/ObservableCollections/ObservableRingBuffer.cs +++ b/src/ObservableCollections/ObservableRingBuffer.cs @@ -1,30 +1,38 @@ namespace ObservableCollections { + public sealed partial class ObservableRingBuffer { // TODO:not yet. readonly T[] buffer; + int head; + int count; + public ObservableRingBuffer(int capacity) { this.buffer = new T[capacity]; } - public int Count => buffer.Length; + public int Count => count; public T this[int index] { get { - return this.buffer[index]; + var i = (head + index) % buffer.Length; + return this.buffer[i]; } set { + } } public void AddLast() { + + // AddLast // AddFirst //new LinkedList().remo diff --git a/src/ObservableCollections/RingBuffer.cs b/src/ObservableCollections/RingBuffer.cs new file mode 100644 index 0000000..4c4c4a3 --- /dev/null +++ b/src/ObservableCollections/RingBuffer.cs @@ -0,0 +1,284 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; + +namespace ObservableCollections +{ + public sealed class RingBuffer : IList + { + T[] buffer; + int head; + int count; + int mask; + + public RingBuffer() + { + this.buffer = new T[8]; + this.head = 0; + this.count = 0; + this.mask = buffer.Length - 1; + } + + public RingBuffer(int capacity) + { + this.buffer = new T[CalculateCapacity(capacity)]; + this.head = 0; + this.count = 0; + this.mask = buffer.Length - 1; + } + + static int CalculateCapacity(int size) + { + size--; + size |= size >> 1; + size |= size >> 2; + size |= size >> 4; + size |= size >> 8; + size |= size >> 16; + size += 1; + + if (size < 8) + { + size = 8; + } + return size; + } + + public T this[int index] + { + get + { + var i = (head + index) & mask; + return buffer[i]; + } + set + { + var i = (head + index) & mask; + buffer[i] = value; + } + } + + public int Count => count; + + public bool IsReadOnly => false; + + public void AddLast(T item) + { + if (count == buffer.Length) EnsureCapacity(); + + var index = (head + count) & mask; + buffer[index] = item; + count++; + } + + public void AddFirst(T item) + { + if (count == buffer.Length) EnsureCapacity(); + + head = (head - 1) & mask; + buffer[head] = item; + count++; + } + + public void RemoveLast() + { + if (count == 0) ThrowForEmpty(); + + var index = (head + count) & mask; + buffer[index] = default!; + count--; + } + + public void RemoveFirst() + { + if (count == 0) ThrowForEmpty(); + + var index = head & mask; + buffer[index] = default!; + head = head + 1; + count--; + } + + void EnsureCapacity() + { + var newBuffer = new T[buffer.Length * 2]; + + var i = head & mask; + buffer.AsSpan(i).CopyTo(newBuffer); + + if (i != 0) + { + buffer.AsSpan(0, i).CopyTo(newBuffer.AsSpan(buffer.Length - i)); + } + + head = 0; + buffer = newBuffer; + mask = newBuffer.Length - 1; + } + + void ICollection.Add(T item) + { + AddLast(item); + } + + public void Clear() + { + Array.Clear(buffer); + head = 0; + count = 0; + } + + public RingBufferSpan GetSpan() + { + var start = head & mask; + var end = (head + count) & mask; + + if (end > start) + { + var first = buffer.AsSpan(start, count); + var second = Array.Empty().AsSpan(); + return new RingBufferSpan(first, second, count); + } + else + { + var first = buffer.AsSpan(start, buffer.Length - start); + var second = buffer.AsSpan(0, end); + return new RingBufferSpan(first, second, count); + } + } + + public IEnumerator GetEnumerator() + { + if (count == 0) yield break; + + var start = head & mask; + var end = (head + count) & mask; + + if (end > start) + { + // start...end + for (int i = start; i < end; i++) + { + yield return buffer[i]; + } + } + else + { + // start... + for (int i = start; i < buffer.Length; i++) + { + yield return buffer[i]; + } + // 0...end + for (int i = 0; i < end; i++) + { + yield return buffer[i]; + } + } + } + + public IEnumerable Reverse() + { + var start = head & mask; + var end = (head + count) & mask; + + if (end > start) + { + // end...start + for (int i = end - 1; i >= start; i--) + { + yield return buffer[i]; + } + } + else + { + // end...0 + for (int i = end - 1; i >= 0; i--) + { + yield return buffer[i]; + } + + // ...start + for (int i = buffer.Length - 1; i >= start; i--) + { + yield return buffer[i]; + } + } + } + + public bool Contains(T item) + { + return IndexOf(item) != -1; + } + + public void CopyTo(T[] array, int arrayIndex) + { + var span = GetSpan(); + var dest = array.AsSpan(arrayIndex); + span.First.CopyTo(dest); + span.Second.CopyTo(dest.Slice(span.First.Length)); + } + + public int IndexOf(T item) + { + var span = GetSpan(); + var i = 0; + foreach (var v in span.First) + { + if (EqualityComparer.Default.Equals(item, v)) + { + return i; + } + i++; + } + foreach (var v in span.Second) + { + if (EqualityComparer.Default.Equals(item, v)) + { + return i; + } + i++; + } + return -1; + } + + void IList.Insert(int index, T item) + { + throw new NotSupportedException(); + } + + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + [DoesNotReturn] + static void ThrowForEmpty() + { + throw new InvalidOperationException("RingBuffer is empty."); + } + } + + public ref struct RingBufferSpan + { + public readonly Span First; + public readonly Span Second; + public readonly int Count; + + public RingBufferSpan(Span first, Span second, int count) + { + First = first; + Second = second; + Count = count; + } + } +} \ No newline at end of file diff --git a/tests/ObservableCollections.Tests/RingBufferTest.cs b/tests/ObservableCollections.Tests/RingBufferTest.cs new file mode 100644 index 0000000..fd2c0f9 --- /dev/null +++ b/tests/ObservableCollections.Tests/RingBufferTest.cs @@ -0,0 +1,129 @@ +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace ObservableCollections.Tests +{ + public class RingBufferTest + { + [Fact] + public void Foo() + { + var list = new RingBuffer(); + + // befin from last... + list.AddLast(1); list.Should().Equal(1); + list.AddLast(2); list.Should().Equal(1, 2); + list.AddLast(3); list.Should().Equal(1, 2, 3); + list.AddLast(4); list.Should().Equal(1, 2, 3, 4); + list.AddLast(5); list.Should().Equal(1, 2, 3, 4, 5); + list.AddLast(6); list.Should().Equal(1, 2, 3, 4, 5, 6); + + list.RemoveLast(); + list.RemoveLast(); + list.Should().Equal(1, 2, 3, 4); + list.Reverse().Should().Equal(4, 3, 2, 1); + + list.RemoveFirst(); + list.RemoveFirst(); + list.Should().Equal(3, 4); + + list.AddFirst(99); + list.AddLast(88); + list.Should().Equal(99, 3, 4, 88); + + // Adding Loop + list.AddLast(5); + list.AddLast(6); + list.AddLast(7); + list.AddLast(8); + list.Should().Equal(99, 3, 4, 88, 5, 6, 7, 8); + list.Reverse().Should().Equal(8, 7, 6, 5, 88, 4, 3, 99); + + + // copy + { + var newArray = new int[10]; + list.CopyTo(newArray, 0); + newArray.Should().Equal(99, 3, 4, 88, 5, 6, 7, 8, 0, 0); + } + { + var newArray = new int[10]; + list.CopyTo(newArray, 1); + newArray.Should().Equal(0, 99, 3, 4, 88, 5, 6, 7, 8, 0); + } + + list.Clear(); + + // befin from first... + list.AddFirst(1); + list.AddFirst(2); + list.AddFirst(3); + list.AddFirst(4); + list.AddFirst(5); + list.AddFirst(6); + + list.Should().Equal(6, 5, 4, 3, 2, 1); + list.Reverse().Should().Equal(1, 2, 3, 4, 5, 6); + + list.RemoveLast(); + list.RemoveLast(); + list.Should().Equal(6, 5, 4, 3); + + list.RemoveFirst(); + list.RemoveFirst(); + list.Should().Equal(4, 3); + + list.AddFirst(99); + list.AddLast(88); + list.Should().Equal(99, 4, 3, 88); + + list.AddFirst(5); + list.AddFirst(6); + list.AddFirst(7); + list.AddFirst(8); + list.Should().Equal(8, 7, 6, 5, 99, 4, 3, 88); + + // set, get + list[0].Should().Be(8); + list[1].Should().Be(7); + list[2].Should().Be(6); + list[3].Should().Be(5); + list[4].Should().Be(99); + list[5].Should().Be(4); + list[6].Should().Be(3); + list[7].Should().Be(88); + + list[0] = 999; + list[4] = 1099; + list[7] = 888; + + // ensure capacity + list.AddFirst(9); + list.Should().Equal(9, 999, 7, 6, 5, 1099, 4, 3, 888); + list.Reverse().Should().Equal(888, 3, 4, 1099, 5, 6, 7, 999, 9); + + list.AddFirst(199); + list.AddLast(299); + list.Should().Equal(199, 9, 999, 7, 6, 5, 1099, 4, 3, 888, 299); + list.Reverse().Should().Equal(299, 888, 3, 4, 1099, 5, 6, 7, 999, 9, 199); + + // copy + { + var newArray = new int[15]; + list.CopyTo(newArray, 0); + newArray.Should().Equal(199, 9, 999, 7, 6, 5, 1099, 4, 3, 888, 299, 0, 0, 0, 0); + } + { + var newArray = new int[15]; + list.CopyTo(newArray, 2); + newArray.Should().Equal(0, 0, 199, 9, 999, 7, 6, 5, 1099, 4, 3, 888, 299, 0, 0); + } + + } + } +}