mirror of
https://github.com/AlexMacocian/SystemExtensions.git
synced 2026-07-15 14:19:29 +00:00
Circular buffer implementation
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace System.Collections.Generic;
|
||||
public sealed class CircularBuffer<T> : IList<T>
|
||||
{
|
||||
private readonly T[] buffer;
|
||||
private int head;
|
||||
private int tail;
|
||||
|
||||
public int Count { get; private set; }
|
||||
public bool IsReadOnly { get; } = false;
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index > this.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
return this.buffer[(this.head + index) % this.buffer.Length];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (index > this.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
this.buffer[(this.head + index) % this.buffer.Length] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public CircularBuffer(int capacity)
|
||||
{
|
||||
if (capacity < 1) throw new ArgumentException($"{nameof(capacity)} cannot be smaller than 1");
|
||||
|
||||
this.buffer = new T[capacity];
|
||||
this.head = 0;
|
||||
this.tail = 0;
|
||||
}
|
||||
|
||||
public CircularBuffer(T[] buffer, int head, int tail)
|
||||
{
|
||||
if (buffer.Length == 0) throw new ArgumentException($"{nameof(buffer)} cannot be of length 0");
|
||||
|
||||
this.buffer = buffer;
|
||||
this.head = head;
|
||||
this.tail = tail;
|
||||
this.Count = this.tail < this.head ?
|
||||
this.tail + this.buffer.Length - this.head + 1 :
|
||||
this.tail - this.head + 1;
|
||||
}
|
||||
|
||||
public CircularBuffer(T[] buffer)
|
||||
{
|
||||
if (buffer.Length == 0) throw new ArgumentException($"{nameof(buffer)} cannot be of length 0");
|
||||
|
||||
this.buffer = buffer;
|
||||
this.head = 0;
|
||||
this.tail = this.buffer.Length - 1;
|
||||
this.Count = this.buffer.Length;
|
||||
}
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
if (this.Count == this.buffer.Length)
|
||||
{
|
||||
this.buffer[this.tail] = item;
|
||||
this.tail = (this.tail + 1) % this.buffer.Length;
|
||||
this.head = (this.head + 1) % this.buffer.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.buffer[this.tail] = item;
|
||||
this.tail = (this.tail + 1) % this.buffer.Length;
|
||||
this.Count++;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.head = 0;
|
||||
this.tail = 0;
|
||||
this.Count = 0;
|
||||
}
|
||||
|
||||
public void DeepClear()
|
||||
{
|
||||
this.Clear();
|
||||
Array.Clear(this.buffer, 0, this.buffer.Length);
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
var comparer = EqualityComparer<T>.Default;
|
||||
foreach(var itItem in this)
|
||||
{
|
||||
if (comparer.Equals(itItem, item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
var index = this.head;
|
||||
var comparer = EqualityComparer<T>.Default;
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
{
|
||||
if (comparer.Equals(this.buffer[index % this.buffer.Length], item))
|
||||
{
|
||||
int nextIndex;
|
||||
for (var j = 0; j < this.Count - i - 1; j++)
|
||||
{
|
||||
nextIndex = (index + 1) % this.buffer.Length;
|
||||
this.buffer[index] = this.buffer[nextIndex];
|
||||
index = nextIndex;
|
||||
}
|
||||
|
||||
this.Count--;
|
||||
this.tail = this.tail == 0 ? this.buffer.Length - 1 : this.tail - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
index = (index + 1) % this.buffer.Length;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
foreach(var item in this)
|
||||
{
|
||||
array[arrayIndex++] = item;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
var index = this.head;
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
{
|
||||
yield return this.buffer[index];
|
||||
index = (index + 1) % this.buffer.Length;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
var comparer = EqualityComparer<T>.Default;
|
||||
for (var i = 0; i < this.Count; i++)
|
||||
{
|
||||
if (comparer.Equals(this[i], item))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
if (index < 0 || index > this.Count) throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
||||
if (this.Count == this.buffer.Length) throw new InvalidOperationException("Cannot insert into a full buffer.");
|
||||
|
||||
if (index == this.Count)
|
||||
{
|
||||
this.Add(item);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = this.Count; i > index; i--)
|
||||
{
|
||||
this.buffer[(this.head + i) % this.buffer.Length] = this.buffer[(this.head + i - 1) % this.buffer.Length];
|
||||
}
|
||||
|
||||
this.buffer[(this.head + index) % this.buffer.Length] = item;
|
||||
this.tail = (this.tail + 1) % this.buffer.Length;
|
||||
this.Count++;
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
if (index < 0 || index >= this.Count) throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
||||
for (var i = index; i < this.Count - 1; i++)
|
||||
{
|
||||
this.buffer[(this.head + i) % this.buffer.Length] = this.buffer[(this.head + i + 1) % this.buffer.Length];
|
||||
}
|
||||
|
||||
this.tail = (this.tail - 1 + this.buffer.Length) % this.buffer.Length;
|
||||
this.Count--;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<RootNamespace>System</RootNamespace>
|
||||
<Version>1.6.2</Version>
|
||||
<Version>1.6.3</Version>
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
||||
<Description>Extensions for the System namespace</Description>
|
||||
|
||||
@@ -0,0 +1,395 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Collections.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class CircularBufferTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Constructor_WithCapacity_RespectsCapacity()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10);
|
||||
|
||||
buffer.Count.Should().Be(0);
|
||||
|
||||
for (var i = 0; i < 11; i++)
|
||||
{
|
||||
buffer.Add(i);
|
||||
}
|
||||
|
||||
buffer.Count.Should().Be(10);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Constructor_WithBuffer_ReturnsExpectedElements()
|
||||
{
|
||||
var array = new int[10];
|
||||
for(var i = 0; i < 10; i++)
|
||||
{
|
||||
array[i] = i;
|
||||
}
|
||||
|
||||
var buffer = new CircularBuffer<int>(array, 0, 9);
|
||||
|
||||
buffer.Count.Should().Be(10);
|
||||
buffer.Should().BeEquivalentTo(array);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Constructor_WithBuffer_RespectsOrder()
|
||||
{
|
||||
var expectedArray = new int[] { 3, 4, 5, 6, 7, 8, 9, 0, 1, 2 };
|
||||
var array = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
|
||||
var buffer = new CircularBuffer<int>(array, 3, 2);
|
||||
|
||||
buffer.Count.Should().Be(10);
|
||||
buffer.Should().BeEquivalentTo(expectedArray);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Constructor_WithBuffer_RespectsInternalBuffer()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(new int[] { 0, 1, 2, 3, 4, 5 });
|
||||
|
||||
buffer.Count.Should().Be(6);
|
||||
buffer.Should().BeEquivalentTo(new int[] { 0, 1, 2, 3, 4, 5 });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AddItem_AddsItem()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
1
|
||||
};
|
||||
|
||||
buffer.Should().HaveCount(1);
|
||||
buffer.First().Should().Be(1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AddItem_AddsItem_RespectsCapacity()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10
|
||||
};
|
||||
|
||||
buffer.Add(0);
|
||||
|
||||
buffer.Should().HaveCount(10);
|
||||
buffer.Last().Should().Be(0);
|
||||
buffer.First().Should().Be(2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RemoveItem_RemovesDesiredItem()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10
|
||||
};
|
||||
|
||||
var result = buffer.Remove(5);
|
||||
|
||||
result.Should().BeTrue();
|
||||
buffer.Count.Should().Be(9);
|
||||
buffer.Should().BeEquivalentTo(new int[] { 1, 2, 3, 4, 6, 7, 8, 9, 10 });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RemoveItem_UnknownItem_DoesNotRemoveItem()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10
|
||||
};
|
||||
|
||||
var result = buffer.Remove(15);
|
||||
|
||||
result.Should().BeFalse();
|
||||
buffer.Count.Should().Be(10);
|
||||
buffer.Should().BeEquivalentTo(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Contains_ItemExists_ReturnsTrue()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10
|
||||
};
|
||||
|
||||
var result = buffer.Contains(5);
|
||||
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Contains_ItemDoesNotExists_ReturnsFalse()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10
|
||||
};
|
||||
|
||||
var result = buffer.Contains(15);
|
||||
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Clear_ClearsBuffer()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10
|
||||
};
|
||||
|
||||
buffer.Clear();
|
||||
|
||||
buffer.Count.Should().Be(0);
|
||||
buffer.Should().BeEquivalentTo(Array.Empty<int>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DeepClear_ClearsBuffer()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10
|
||||
};
|
||||
|
||||
buffer.DeepClear();
|
||||
|
||||
buffer.Count.Should().Be(0);
|
||||
buffer.Should().BeEquivalentTo(Array.Empty<int>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Insert_FullBuffer_ThrowsException()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10
|
||||
};
|
||||
|
||||
var action = () =>
|
||||
{
|
||||
buffer.Insert(0, 1);
|
||||
};
|
||||
|
||||
action.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Insert_ShouldInsertItemAtPosition()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
};
|
||||
|
||||
buffer.Insert(0, 0);
|
||||
|
||||
buffer.Should().BeEquivalentTo(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RemoveAt_RemovesExpectedItem()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
};
|
||||
|
||||
buffer.RemoveAt(0);
|
||||
|
||||
buffer.Should().BeEquivalentTo(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Indexer_ReturnsExpectedItem()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
};
|
||||
|
||||
buffer[5].Should().Be(5);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Indexer_SetsExpectedItem()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
};
|
||||
|
||||
buffer[6] = 5;
|
||||
|
||||
buffer.Should().BeEquivalentTo(new int[] { 0, 1, 2, 3, 4, 5, 5, 7, 8, 9 });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CircularBuffer_AddAndRemoveOperations_PerformAsExpected()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
};
|
||||
|
||||
buffer.Add(10);
|
||||
buffer.Remove(9);
|
||||
buffer.RemoveAt(5);
|
||||
buffer.Add(11);
|
||||
|
||||
buffer.Should().BeEquivalentTo(new int[] { 1, 2, 3, 4, 5, 7, 8, 10, 11 });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CircularBuffer_AddItems_RotatesBuffer()
|
||||
{
|
||||
var buffer = new CircularBuffer<int>(10)
|
||||
{
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
};
|
||||
|
||||
for(var i = 0; i < 10; i++)
|
||||
{
|
||||
buffer.Add(i + 10);
|
||||
}
|
||||
|
||||
buffer.Should().BeEquivalentTo(new int[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user