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