Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions csharp/Platform.Collections.Tests/SegmentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
using System;
using System.Linq;
using Xunit;
using Platform.Collections.Segments;

namespace Platform.Collections.Tests
{
public static class SegmentTests
{
[Fact]
public static void EmptySegmentTest()
{
var empty = Segment<int>.Empty;
Assert.Equal(0, empty.Length);
Assert.Equal(0, empty.Count);
Assert.Equal(0, empty.Offset);
Assert.NotNull(empty.Base);
}

[Fact]
public static void ArrayConstructorTest()
{
var array = new int[] { 1, 2, 3, 4, 5 };
var segment = new Segment<int>(array);

Assert.Equal(array, segment.Array);
Assert.Equal(array, segment.Base);
Assert.Equal(0, segment.Offset);
Assert.Equal(5, segment.Length);
Assert.Equal(5, segment.Count);
}

[Fact]
public static void ArrayWithOffsetAndCountConstructorTest()
{
var array = new int[] { 1, 2, 3, 4, 5 };
var segment = new Segment<int>(array, 1, 3);

Assert.Equal(array, segment.Array);
Assert.Equal(array, segment.Base);
Assert.Equal(1, segment.Offset);
Assert.Equal(3, segment.Length);
Assert.Equal(3, segment.Count);

// Test indexing
Assert.Equal(2, segment[0]);
Assert.Equal(3, segment[1]);
Assert.Equal(4, segment[2]);
}

[Fact]
public static void ArrayConstructorValidationTest()
{
Assert.Throws<ArgumentNullException>(() => new Segment<int>((int[])null));
Assert.Throws<ArgumentNullException>(() => new Segment<int>(null, 0, 0));

var array = new int[] { 1, 2, 3 };
Assert.Throws<ArgumentOutOfRangeException>(() => new Segment<int>(array, -1, 1));
Assert.Throws<ArgumentOutOfRangeException>(() => new Segment<int>(array, 0, -1));
Assert.Throws<ArgumentException>(() => new Segment<int>(array, 2, 3)); // offset + count > array.Length
}

[Fact]
public static void SliceTest()
{
var array = new int[] { 1, 2, 3, 4, 5 };
var segment = new Segment<int>(array, 1, 4); // [2, 3, 4, 5]

// Slice from index 1 to end
var slice1 = segment.Slice(1);
Assert.Equal(2, slice1.Offset); // 1 + 1
Assert.Equal(3, slice1.Length); // 4 - 1
Assert.Equal(3, slice1[0]); // array[2]
Assert.Equal(4, slice1[1]); // array[3]
Assert.Equal(5, slice1[2]); // array[4]

// Slice with specific count
var slice2 = segment.Slice(1, 2);
Assert.Equal(2, slice2.Offset); // 1 + 1
Assert.Equal(2, slice2.Length); // specified count
Assert.Equal(3, slice2[0]); // array[2]
Assert.Equal(4, slice2[1]); // array[3]
}

[Fact]
public static void SliceValidationTest()
{
var array = new int[] { 1, 2, 3, 4, 5 };
var segment = new Segment<int>(array, 1, 3);

Assert.Throws<ArgumentOutOfRangeException>(() => segment.Slice(-1));
Assert.Throws<ArgumentOutOfRangeException>(() => segment.Slice(4)); // index > Length
Assert.Throws<ArgumentOutOfRangeException>(() => segment.Slice(0, -1));
Assert.Throws<ArgumentOutOfRangeException>(() => segment.Slice(2, 2)); // index + count > Length
}

[Fact]
public static void ToArrayTest()
{
var array = new int[] { 1, 2, 3, 4, 5 };
var segment = new Segment<int>(array, 1, 3); // [2, 3, 4]

var result = segment.ToArray();

Assert.Equal(3, result.Length);
Assert.Equal(2, result[0]);
Assert.Equal(3, result[1]);
Assert.Equal(4, result[2]);

// Ensure it's a copy, not the same array
Assert.NotSame(array, result);

// Modify original array and ensure copy is unchanged
array[2] = 99;
Assert.Equal(3, result[1]); // Should still be 3
}

[Fact]
public static void ArrayPropertyTest()
{
var array = new int[] { 1, 2, 3, 4, 5 };
var segment = new Segment<int>(array);

Assert.Same(array, segment.Array);

// Test with IList<T> (not array)
var list = new System.Collections.Generic.List<int> { 1, 2, 3 };
var listSegment = new Segment<int>(list, 0, 2);
Assert.Null(listSegment.Array); // Should return null for non-array IList<T>
}

[Fact]
public static void EnumerationTest()
{
var array = new int[] { 1, 2, 3, 4, 5 };
var segment = new Segment<int>(array, 1, 3); // [2, 3, 4]

var result = segment.ToArray();
var expected = new int[] { 2, 3, 4 };

Assert.True(result.SequenceEqual(expected));
}

[Fact]
public static void CountPropertyCompatibilityTest()
{
var array = new int[] { 1, 2, 3, 4, 5 };
var segment = new Segment<int>(array, 1, 3);

// Count should be the same as Length (ArraySegment compatibility)
Assert.Equal(segment.Length, segment.Count);
Assert.Equal(3, segment.Count);
}

[Fact]
public static void ArraySegmentSemanticCompatibilityTest()
{
var array = new int[] { 10, 20, 30, 40, 50 };

// Test behavior similar to ArraySegment<T>
var segment1 = new Segment<int>(array); // Entire array
var segment2 = new Segment<int>(array, 2, 2); // [30, 40]

// ArraySegment-like properties
Assert.Same(array, segment1.Array);
Assert.Same(array, segment2.Array);
Assert.Equal(0, segment1.Offset);
Assert.Equal(2, segment2.Offset);
Assert.Equal(5, segment1.Count);
Assert.Equal(2, segment2.Count);

// ArraySegment-like methods
var slice = segment1.Slice(1, 3); // [20, 30, 40]
Assert.Equal(3, slice.Count);
Assert.Equal(20, slice[0]);
Assert.Equal(30, slice[1]);
Assert.Equal(40, slice[2]);

var copyArray = slice.ToArray();
Assert.Equal(new int[] { 20, 30, 40 }, copyArray);
}
}
}
101 changes: 101 additions & 0 deletions csharp/Platform.Collections/Segments/Segment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ namespace Platform.Collections.Segments
/// <typeparam name="T"><para>The segment elements type.</para><para>Тип элементов сегмента.</para></typeparam>
public class Segment<T> : IEquatable<Segment<T>>, IList<T>
{
/// <summary>
/// <para>Gets an empty segment.</para>
/// <para>Возвращает пустой сегмент.</para>
/// </summary>
public static Segment<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Segment<T>(new T[0], 0, 0);
}
/// <summary>
/// <para>Gets the original list (this segment is a part of it).</para>
/// <para>Возвращает исходный список (частью которого является этот сегмент).</para>
Expand Down Expand Up @@ -44,6 +53,17 @@ public int Length
get;
}

/// <summary>
/// <para>Gets the original array when the base is an array. Compatible with ArraySegment semantics.</para>
/// <para>Возвращает исходный массив, когда основой является массив. Совместим с семантикой ArraySegment.</para>
/// </summary>
public T[] Array
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Base as T[];
}


/// <summary>
/// <para>Initializes a new instance of the <see cref="Segment"/> class, using the <paramref name="base"/> list, <paramref name="offset"/> of the segment and its <paramref name="length" />.</para>
/// <para>Инициализирует новый экземпляр класса <see cref="Segment"/>, используя список <paramref name="base"/>, <paramref name="offset"/> сегмента и его <paramref name="length"/>.</para>
Expand All @@ -58,6 +78,43 @@ public Segment(IList<T> @base, int offset, int length)
Offset = offset;
Length = length;
}

/// <summary>
/// <para>Initializes a new instance of the <see cref="Segment"/> class that delimits the entire array. Compatible with ArraySegment constructor.</para>
/// <para>Инициализирует новый экземпляр класса <see cref="Segment"/>, который разграничивает весь массив. Совместим с конструктором ArraySegment.</para>
/// </summary>
/// <param name="array"><para>The array to wrap in the segment.</para><para>Массив для обертывания в сегмент.</para></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Segment(T[] array)
{
Base = array ?? throw new ArgumentNullException(nameof(array));
Offset = 0;
Length = array.Length;
}

/// <summary>
/// <para>Initializes a new instance of the <see cref="Segment"/> class that delimits a range of elements in an array. Compatible with ArraySegment constructor.</para>
/// <para>Инициализирует новый экземпляр класса <see cref="Segment"/>, который разграничивает диапазон элементов в массиве. Совместим с конструктором ArraySegment.</para>
/// </summary>
/// <param name="array"><para>The array to wrap in the segment.</para><para>Массив для обертывания в сегмент.</para></param>
/// <param name="offset"><para>The zero-based index of the first element in the range delimited by the array segment.</para><para>Отсчитываемый от нуля индекс первого элемента в диапазоне, разделенном сегментом массива.</para></param>
/// <param name="count"><para>The number of elements in the range delimited by the array segment.</para><para>Количество элементов в диапазоне, разделенном сегментом массива.</para></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Segment(T[] array, int offset, int count)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset));
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
if (offset + count > array.Length)
throw new ArgumentException("Offset and count exceed array bounds.");

Base = array;
Offset = offset;
Length = count;
}

/// <summary>
/// <para>Gets the hash code of the current <see cref="Segment"/> instance.</para>
Expand Down Expand Up @@ -96,6 +153,50 @@ public Segment(IList<T> @base, int offset, int length)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj) => obj is Segment<T> other ? Equals(other) : false;

/// <summary>
/// <para>Forms a slice out of the current segment that begins at a specified index. Compatible with ArraySegment semantics.</para>
/// <para>Формирует срез из текущего сегмента, который начинается с указанного индекса. Совместим с семантикой ArraySegment.</para>
/// </summary>
/// <param name="index"><para>The index at which to begin the slice.</para><para>Индекс, с которого начинается срез.</para></param>
/// <returns><para>A segment that consists of all elements of the current segment from <paramref name="index"/> to the end.</para><para>Сегмент, который состоит из всех элементов текущего сегмента от <paramref name="index"/> до конца.</para></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Segment<T> Slice(int index)
{
if (index < 0 || index > Length)
throw new ArgumentOutOfRangeException(nameof(index));
return new Segment<T>(Base, Offset + index, Length - index);
}

/// <summary>
/// <para>Forms a slice out of the current segment starting at a specified index for a specified length. Compatible with ArraySegment semantics.</para>
/// <para>Формирует срез из текущего сегмента, начиная с указанного индекса для указанной длины. Совместим с семантикой ArraySegment.</para>
/// </summary>
/// <param name="index"><para>The index at which to begin the slice.</para><para>Индекс, с которого начинается срез.</para></param>
/// <param name="count"><para>The desired length for the slice.</para><para>Желаемая длина среза.</para></param>
/// <returns><para>A segment that consists of <paramref name="count"/> elements from the current segment starting at <paramref name="index"/>.</para><para>Сегмент, который состоит из <paramref name="count"/> элементов текущего сегмента, начиная с <paramref name="index"/>.</para></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Segment<T> Slice(int index, int count)
{
if (index < 0 || index > Length)
throw new ArgumentOutOfRangeException(nameof(index));
if (count < 0 || index + count > Length)
throw new ArgumentOutOfRangeException(nameof(count));
return new Segment<T>(Base, Offset + index, count);
}

/// <summary>
/// <para>Copies the contents of this segment into a new array. Compatible with ArraySegment semantics.</para>
/// <para>Копирует содержимое этого сегмента в новый массив. Совместим с семантикой ArraySegment.</para>
/// </summary>
/// <returns><para>An array containing copies of the elements of the current segment.</para><para>Массив, содержащий копии элементов текущего сегмента.</para></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] ToArray()
{
var result = new T[Length];
CopyTo(result, 0);
return result;
}

#region IList

/// <summary>
Expand Down
Loading