.NET-Community-Toolkit/tests/CommunityToolkit.HighPerfor.../Extensions/Test_ArrayExtensions.2D.cs

459 lines
14 KiB
C#

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using CommunityToolkit.HighPerformance.Enumerables;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CommunityToolkit.HighPerformance.UnitTests.Extensions;
public partial class Test_ArrayExtensions
{
[TestMethod]
public void Test_ArrayExtensions_2D_DangerousGetReference_Int()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
// See comments in Test_ArrayExtensions.1D for how these tests work
ref int r0 = ref array.DangerousGetReference();
ref int r1 = ref array[0, 0];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestMethod]
public void Test_ArrayExtensions_2D_DangerousGetReference_String()
{
string[,] array =
{
{ "a", "bb", "ccc" },
{ "dddd", "eeeee", "ffffff" }
};
ref string r0 = ref array.DangerousGetReference();
ref string r1 = ref array[0, 0];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestMethod]
public void Test_ArrayExtensions_2D_DangerousGetReferenceAt_Zero()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
ref int r0 = ref array.DangerousGetReferenceAt(0, 0);
ref int r1 = ref array[0, 0];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestMethod]
public void Test_ArrayExtensions_2D_DangerousGetReferenceAt_Index()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
ref int r0 = ref array.DangerousGetReferenceAt(1, 3);
ref int r1 = ref array[1, 3];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestMethod]
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayMid()
{
bool[,] test = new bool[4, 5];
// To fill an array we now go through the Span2D<T> type, which includes all
// the necessary logic to perform the operation. In these tests we just create
// one through the extension, slice it and then fill it. For instance in this
// one, we're creating a Span2D<bool> from coordinates (1, 1), with a height of
// 2 and a width of 2, and then filling it. Then we just compare the results.
test.AsSpan2D(1, 1, 2, 3).Fill(true);
bool[,]? expected = new[,]
{
{ false, false, false, false, false },
{ false, true, true, true, false },
{ false, true, true, true, false },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(expected, test);
}
[TestMethod]
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayTwice()
{
bool[,] test = new bool[4, 5];
test.AsSpan2D(0, 0, 2, 1).Fill(true);
test.AsSpan2D(1, 3, 2, 2).Fill(true);
bool[,]? expected = new[,]
{
{ true, false, false, false, false },
{ true, false, false, true, true },
{ false, false, false, true, true },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(expected, test);
}
[TestMethod]
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayBottomEdgeBoundary()
{
bool[,] test = new bool[4, 5];
test.AsSpan2D(1, 2, 3, 2).Fill(true);
bool[,]? expected = new[,]
{
{ false, false, false, false, false },
{ false, false, true, true, false },
{ false, false, true, true, false },
{ false, false, true, true, false },
};
CollectionAssert.AreEqual(expected, test);
}
[TestMethod]
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayBottomRightCornerBoundary()
{
bool[,] test = new bool[5, 4];
test.AsSpan2D(3, 2, 2, 2).Fill(true);
bool[,]? expected = new[,]
{
{ false, false, false, false },
{ false, false, false, false },
{ false, false, false, false },
{ false, false, true, true },
{ false, false, true, true },
};
CollectionAssert.AreEqual(expected, test);
}
[TestMethod]
public void Test_ArrayExtensions_2D_GetRow_Rectangle()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
// Here we use the enumerator on the RefEnumerator<T> type to traverse items in a row
// by reference. For each one, we check that the reference does in fact point to the
// item we expect in the underlying array (in this case, items on row 1).
int j = 0;
foreach (ref int value in array.GetRow(1))
{
Assert.IsTrue(Unsafe.AreSame(ref value, ref array[1, j++]));
}
// Check that RefEnumerable<T>.ToArray() works correctly
CollectionAssert.AreEqual(array.GetRow(1).ToArray(), new[] { 5, 6, 7, 8 });
// Test an empty array
Assert.AreSame(new int[1, 0].GetRow(0).ToArray(), Array.Empty<int>());
_ = Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(-1));
_ = Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(3));
_ = Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(20));
}
[TestMethod]
public void Test_ArrayExtensions_2D_GetColumn_Rectangle()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
// Same as above, but this time we iterate a column instead (so non contiguous items)
int i = 0;
foreach (ref int value in array.GetColumn(1))
{
Assert.IsTrue(Unsafe.AreSame(ref value, ref array[i++, 1]));
}
CollectionAssert.AreEqual(array.GetColumn(1).ToArray(), new[] { 2, 6, 10 });
_ = Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(-1));
_ = Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(4));
_ = Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(20));
}
[TestMethod]
public void Test_ArrayExtensions_2D_GetRow_Empty()
{
int[,] array = new int[0, 0];
// Try to get a row from an empty array (the row index isn't in range)
_ = Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(0).ToArray());
}
[TestMethod]
public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 }
};
// Get a row and test the Clear method. Note that the Span2D<T> here is sliced
// starting from the second column, so this method should clear the row from index 1.
array.AsSpan2D(1, 1, 3, 3).GetRow(0).Clear();
int[,] expected =
{
{ 1, 2, 3, 4 },
{ 5, 0, 0, 0 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 }
};
CollectionAssert.AreEqual(array, expected);
// Same as before, but this time we fill a column with a value
array.GetColumn(2).Fill(42);
expected = new[,]
{
{ 1, 2, 42, 4 },
{ 5, 0, 42, 0 },
{ 9, 10, 42, 12 },
{ 13, 14, 42, 16 }
};
CollectionAssert.AreEqual(array, expected);
int[] copy = new int[4];
// Get a row and copy items to a target span (in this case, wrapping an array)
array.GetRow(2).CopyTo(copy);
int[] result = { 9, 10, 42, 12 };
CollectionAssert.AreEqual(copy, result);
// Same as above, but copying from a column (so we test non contiguous sequences too)
array.GetColumn(1).CopyTo(copy);
result = new[] { 2, 0, 10, 14 };
CollectionAssert.AreEqual(copy, result);
// Some invalid attempts to copy to an empty span or sequence
_ = Assert.ThrowsException<ArgumentException>(() => array.GetRow(0).CopyTo(default(RefEnumerable<int>)));
_ = Assert.ThrowsException<ArgumentException>(() => array.GetRow(0).CopyTo(default(Span<int>)));
_ = Assert.ThrowsException<ArgumentException>(() => array.GetColumn(0).CopyTo(default(RefEnumerable<int>)));
_ = Assert.ThrowsException<ArgumentException>(() => array.GetColumn(0).CopyTo(default(Span<int>)));
// Same as CopyTo, but this will fail gracefully with an invalid target
Assert.IsTrue(array.GetRow(2).TryCopyTo(copy));
Assert.IsFalse(array.GetRow(0).TryCopyTo(default(Span<int>)));
result = new[] { 9, 10, 42, 12 };
CollectionAssert.AreEqual(copy, result);
// Also fill a row and then further down clear a column (trying out all possible combinations)
array.GetRow(2).Fill(99);
expected = new[,]
{
{ 1, 2, 42, 4 },
{ 5, 0, 42, 0 },
{ 99, 99, 99, 99 },
{ 13, 14, 42, 16 }
};
CollectionAssert.AreEqual(array, expected);
array.GetColumn(2).Clear();
expected = new[,]
{
{ 1, 2, 0, 4 },
{ 5, 0, 0, 0 },
{ 99, 99, 0, 99 },
{ 13, 14, 0, 16 }
};
CollectionAssert.AreEqual(array, expected);
}
[TestMethod]
public void Test_ArrayExtensions_2D_ReadOnlyGetRowOrColumn_Helpers()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 }
};
// This test pretty much does the same things as the method above, but this time
// using a source ReadOnlySpan2D<T>, so that the sequence type being tested is
// ReadOnlyRefEnumerable<T> instead (which shares most features but is separate).
ReadOnlySpan2D<int> span2D = array;
int[] copy = new int[4];
span2D.GetRow(2).CopyTo(copy);
int[] result = { 9, 10, 11, 12 };
CollectionAssert.AreEqual(copy, result);
span2D.GetColumn(1).CopyTo(copy);
result = new[] { 2, 6, 10, 14 };
CollectionAssert.AreEqual(copy, result);
_ = Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetRow(0).CopyTo(default(RefEnumerable<int>)));
_ = Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetRow(0).CopyTo(default(Span<int>)));
_ = Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetColumn(0).CopyTo(default(RefEnumerable<int>)));
_ = Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetColumn(0).CopyTo(default(Span<int>)));
Assert.IsTrue(span2D.GetRow(2).TryCopyTo(copy));
Assert.IsFalse(span2D.GetRow(2).TryCopyTo(default(Span<int>)));
result = new[] { 9, 10, 11, 12 };
CollectionAssert.AreEqual(copy, result);
}
[TestMethod]
public void Test_ArrayExtensions_2D_RefEnumerable_Misc()
{
int[,] array1 =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 }
};
int[,] array2 = new int[4, 4];
// Copy to enumerable with source step == 1, destination step == 1
array1.GetRow(0).CopyTo(array2.GetRow(0));
// Copy enumerable with source step == 1, destination step != 1
array1.GetRow(1).CopyTo(array2.GetColumn(1));
// Copy enumerable with source step != 1, destination step == 1
array1.GetColumn(2).CopyTo(array2.GetRow(2));
// Copy enumerable with source step != 1, destination step != 1
array1.GetColumn(3).CopyTo(array2.GetColumn(3));
int[,] result =
{
{ 1, 5, 3, 4 },
{ 0, 6, 0, 8 },
{ 3, 7, 11, 12 },
{ 0, 8, 0, 16 }
};
CollectionAssert.AreEqual(array2, result);
// Test a valid and an invalid TryCopyTo call with the RefEnumerable<T> overload
bool shouldBeTrue = array1.GetRow(0).TryCopyTo(array2.GetColumn(0));
bool shouldBeFalse = array1.GetRow(0).TryCopyTo(default(RefEnumerable<int>));
result = new[,]
{
{ 1, 5, 3, 4 },
{ 2, 6, 0, 8 },
{ 3, 7, 11, 12 },
{ 4, 8, 0, 16 }
};
CollectionAssert.AreEqual(array2, result);
Assert.IsTrue(shouldBeTrue);
Assert.IsFalse(shouldBeFalse);
}
[TestMethod]
public void Test_ArrayExtensions_2D_GetColumn_Empty()
{
int[,] array = new int[0, 0];
_ = Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(0).ToArray());
}
#if NETCOREAPP3_1_OR_GREATER
[TestMethod]
public void Test_ArrayExtensions_2D_AsSpan_Empty()
{
int[,] array = new int[0, 0];
Span<int> span = array.AsSpan();
// Check that the empty array was loaded properly
Assert.AreEqual(span.Length, array.Length);
Assert.IsTrue(span.IsEmpty);
}
[TestMethod]
public void Test_ArrayExtensions_2D_AsSpan_Populated()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
Span<int> span = array.AsSpan();
// Test the total length of the span
Assert.AreEqual(span.Length, array.Length);
ref int r0 = ref array[0, 0];
ref int r1 = ref span[0];
// Similarly to the top methods, here we compare a given reference to
// ensure they point to the right element back in the original array.
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
#endif
}