using System;
using System.Collections.Generic;
using System.Linq;
namespace Amphora.UnitTests
{
public class SortTests : UnitTestSharp.TestFixture
{
///
/// Returns the mask of the lowest bits of a key.
///
public static int SpanMask(int span)
{
if (span == 32)
{
return -1;
}
return (1 << span) - 1;
}
///
/// What an order array starts with in a test, which no position can be.
///
public const int Unwritten = -7;
///
/// The largest batch size that the tests of every size go up to, which is past where radix passes take over
/// from insertion.
///
public const int LargestBatchChecked = 130;
///
/// Makes keys that are spread over the whole int range, and not in any order.
///
public static int[] Scattered(int count)
{
var returnMe = new int[count];
for (int i = 0; i < count; ++i)
{
returnMe[i] = unchecked((int)(i * 2654435761u));
}
return returnMe;
}
///
/// Makes an order array of the given length that nothing has written to.
///
public static int[] UnwrittenOrder(int length)
{
var returnMe = new int[length];
for (int i = 0; i < length; ++i)
{
returnMe[i] = Unwritten;
}
return returnMe;
}
///
/// Returns the positions of the first keys in a stable sort of them.
///
public static int[] ExpectedOrder(int[] keys, int count)
{
return Enumerable.Range(0, count).OrderBy(i => keys[i]).ToArray();
}
public class RadixCrossoverTests : UnitTestSharp.TestFixture
{
public void EverySpan_IsAtLeastTheRadixMinimum()
{
for (int span = 0; span <= 32; ++span)
{
CheckTrue(Sort.RadixCrossover(span) >= Sort.RadixMinimum);
}
}
public void TheProbe_IsNoLongerThanTheRadixMinimum()
{
CheckTrue(Sort.ProbeLength <= Sort.RadixMinimum);
}
public void WiderKeys_NeverCrossSooner()
{
for (int span = 1; span <= 32; ++span)
{
CheckTrue(Sort.RadixCrossover(span) >= Sort.RadixCrossover(span - 1));
}
}
public void KeysThatDifferInEveryBit_CrossAtAboutAThousand()
{
int crossover = Sort.RadixCrossover(32);
CheckTrue(crossover >= 900 && crossover <= 1100);
}
public void NegativeSpan_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.RadixCrossover(-1);
}
public void SpanWiderThanAKey_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.RadixCrossover(33);
}
}
public class RadixPaysTests : UnitTestSharp.TestFixture
{
[UnitTestSharp.IgnoreTest]
public bool Pays(int[] keys, int count)
{
int low;
int high;
bool anyNegative;
return Sort.RadixPays(keys, count, out low, out high, out anyNegative);
}
public void FewerThanTheRadixMinimum_DoNotPay()
{
CheckFalse(Pays(RadixKeySorterTests.Scrambled(Sort.RadixMinimum - 1, 0x3), Sort.RadixMinimum - 1));
}
public void TheRadixMinimumOfNarrowKeys_Pay()
{
CheckTrue(Pays(RadixKeySorterTests.Scrambled(Sort.RadixMinimum, 0x3), Sort.RadixMinimum));
}
public void WideKeysBelowTheirCrossover_DoNotPay()
{
int count = Sort.RadixCrossover(31) - 1;
CheckFalse(Pays(RadixKeySorterTests.Scrambled(count, 0x7FFFFFFF), count));
}
public void WideKeysAtTheirCrossover_Pay()
{
int count = Sort.RadixCrossover(31);
CheckTrue(Pays(RadixKeySorterTests.Scrambled(count, 0x7FFFFFFF), count));
}
public void NarrowKeysFollowedByAWideOne_DoNotPay()
{
int[] keys = RadixKeySorterTests.Scrambled(200, 0xFF);
keys[199] = 0x7FFFFFFF;
CheckFalse(Pays(keys, 200));
}
public void WideKeysFollowedByNarrowOnes_DoNotPay()
{
int[] keys = RadixKeySorterTests.Scrambled(200, 0xFF);
for (int i = 0; i < Sort.ProbeLength; ++i)
{
keys[i] = unchecked((int)(i * 2654435761u)) & 0x7FFFFFFF;
}
CheckFalse(Pays(keys, 200));
}
public void OnlyTheFirstCountKeysAreLookedAt()
{
int[] keys = RadixKeySorterTests.Scrambled(300, 0xFF);
keys[250] = 0x7FFFFFFF;
CheckTrue(Pays(keys, 200));
}
public void Pays_ReportsTheBitsTheKeysDifferIn()
{
var keys = new int[200];
for (int i = 0; i < keys.Length; ++i)
{
keys[i] = (i % 256) << 8;
}
int low;
int high;
bool anyNegative;
CheckTrue(Sort.RadixPays(keys, 200, out low, out high, out anyNegative));
CheckEqual(8, low);
CheckEqual(16, high);
CheckFalse(anyNegative);
}
public void Pays_ReportsNegativeKeys()
{
int[] keys = RadixKeySorterTests.Scrambled(200, 0xFF);
keys[100] = -5;
int low;
int high;
bool anyNegative;
Sort.RadixPays(keys, 200, out low, out high, out anyNegative);
CheckTrue(anyNegative);
}
}
public class IsAscendingTests : UnitTestSharp.TestFixture
{
public void KeysInOrder_IsTrue()
{
CheckTrue(Sort.IsAscending(new[] { 1, 2, 2, 5 }, 4, signed: true));
}
public void EqualKeys_IsTrue()
{
CheckTrue(Sort.IsAscending(new[] { 3, 3, 3 }, 3, signed: true));
}
public void OneKey_IsTrue()
{
CheckTrue(Sort.IsAscending(new[] { 3 }, 1, signed: true));
}
public void ANeighborOutOfOrder_IsFalse()
{
CheckFalse(Sort.IsAscending(new[] { 1, 3, 2 }, 3, signed: true));
}
public void OnlyTheFirstCountKeysAreLookedAt()
{
CheckTrue(Sort.IsAscending(new[] { 1, 2, 3, 0 }, 3, signed: true));
}
public void ANegativeAfterAPositive_IsFalseWhenSigned()
{
CheckFalse(Sort.IsAscending(new[] { 1, -1 }, 2, signed: true));
}
public void ANegativeAfterAPositive_IsTrueWhenUnsigned()
{
CheckTrue(Sort.IsAscending(new[] { 1, -1 }, 2, signed: false));
}
public void UnsignedKeysOutOfOrder_IsFalse()
{
CheckFalse(Sort.IsAscending(new[] { -1, 1 }, 2, signed: false));
}
}
public class KeysTests : UnitTestSharp.TestFixture
{
///
/// Sorts the first keys and checks they come out in order and the rest stay put.
///
[UnitTestSharp.IgnoreTest]
public void CheckSorts(RadixKeySorter sorter, int[] keys, int count)
{
var expected = (int[])keys.Clone();
Array.Sort(expected, 0, count);
Sort.Keys(keys, count, sorter);
CheckEqual(expected, keys);
}
[UnitTestSharp.IgnoreTest]
public void CheckSorts(int[] keys)
{
CheckSorts(new RadixKeySorter(), keys, keys.Length);
}
[UnitTestSharp.IgnoreTest]
public void CheckSortsUnsigned(RadixKeySorter sorter, uint[] keys, int count)
{
var expected = (uint[])keys.Clone();
Array.Sort(expected, 0, count);
Sort.Keys(keys, count, sorter);
CheckEqual(expected, keys);
}
[UnitTestSharp.IgnoreTest]
public void CheckSortsUnsigned(uint[] keys)
{
CheckSortsUnsigned(new RadixKeySorter(), keys, keys.Length);
}
public void NoKeys_ChangesNothing()
{
CheckSorts(new RadixKeySorter(), new[] { 3, 1, 2 }, 0);
}
public void OneKey_ChangesNothing()
{
CheckSorts(new RadixKeySorter(), new[] { 3, 1, 2 }, 1);
}
public void AFewKeys_AreSorted()
{
CheckSorts(RadixKeySorterTests.Scrambled(10, 0xFFFF));
}
public void JustBelowTheAnalyzeLimit_AreSorted()
{
CheckSorts(RadixKeySorterTests.Scrambled(Sort.AnalyzeLimit - 1, 0xFFFF));
}
public void ExactlyTheAnalyzeLimit_AreSorted()
{
CheckSorts(RadixKeySorterTests.Scrambled(Sort.AnalyzeLimit, 0xFFFF));
}
public void ExactlyTheAnalyzeLimitAlreadyAscending_AreUnchanged()
{
CheckSorts(Enumerable.Range(0, Sort.AnalyzeLimit).ToArray());
}
public void EveryKeyWidthJustBelowTheCrossover_AreSorted()
{
for (int span = 1; span <= 32; ++span)
{
CheckSorts(RadixKeySorterTests.Scrambled(Sort.RadixCrossover(span) - 1, SpanMask(span)));
}
}
public void EveryKeyWidthExactlyAtTheCrossover_AreSorted()
{
for (int span = 1; span <= 32; ++span)
{
CheckSorts(RadixKeySorterTests.Scrambled(Sort.RadixCrossover(span), SpanMask(span)));
}
}
public void EveryKeyWidthJustOverTheCrossover_AreSorted()
{
for (int span = 1; span <= 32; ++span)
{
CheckSorts(RadixKeySorterTests.Scrambled(Sort.RadixCrossover(span) + 1, SpanMask(span)));
}
}
public void NarrowKeysOverTheCrossover_AreSortedWithAReusedSorter()
{
var sorter = new RadixKeySorter();
foreach (int count in new[] { 100, 300, 1000, 5000, 200 })
{
CheckSorts(sorter, RadixKeySorterTests.Scrambled(count, 0xFF), count);
}
}
public void KeysDifferingOnlyInTheMiddleBits_AreSorted()
{
CheckSorts(RadixKeySorterTests.Scrambled(500, 0x0000FF00));
}
public void KeysDifferingOnlyInTheSignBit_AreSorted()
{
CheckSorts(RadixKeySorterTests.Scrambled(300, int.MinValue));
}
public void OnlyNegativeKeys_AreSorted()
{
CheckSorts(RadixKeySorterTests.Scrambled(600, 0xFFFF).Select(key => ~key).ToArray());
}
public void ManyKeys_AreSorted()
{
CheckSorts(RadixKeySorterTests.Scrambled(20000, 0x7FFFFFFF));
}
public void AlreadyAscending_AreUnchanged()
{
CheckSorts(Enumerable.Range(0, 3000).ToArray());
}
public void AlreadyAscendingBelowTheCrossover_AreUnchanged()
{
CheckSorts(Enumerable.Range(0, 500).ToArray());
}
public void ManyDescending_BecomeAscending()
{
CheckSorts(Enumerable.Range(0, 3000).Reverse().ToArray());
}
public void NegativeKeys_ComeFirst()
{
int[] keys = RadixKeySorterTests.Scrambled(3000, 0x7FFFFFFF);
for (int i = 0; i < keys.Length; i += 3)
{
keys[i] = -keys[i];
}
CheckSorts(keys);
}
public void OnlyTheFirstCount_AreSorted()
{
CheckSorts(new RadixKeySorter(), RadixKeySorterTests.Scrambled(4000, 0x7FFFFFFF), 3000);
}
public void OnlyTheFirstCountOfAFew_AreSorted()
{
CheckSorts(new RadixKeySorter(), new[] { 5, 4, 3, 2, 1, 0 }, 4);
}
public void UnsignedAFewKeys_AreSorted()
{
CheckSortsUnsigned(RadixKeySorterTests.Unsigned(RadixKeySorterTests.Scrambled(10, -1)));
}
public void UnsignedExactlyTheAnalyzeLimitAlreadyAscending_AreUnchanged()
{
CheckSortsUnsigned(Enumerable.Range(0, Sort.AnalyzeLimit).Select(i => (uint)i << 27).ToArray());
}
public void UnsignedEveryKeyWidthAroundTheCrossover_AreSorted()
{
for (int span = 1; span <= 32; ++span)
{
foreach (int count in new[] { Sort.RadixCrossover(span) - 1, Sort.RadixCrossover(span),
Sort.RadixCrossover(span) + 1 })
{
CheckSortsUnsigned(RadixKeySorterTests.Unsigned(
RadixKeySorterTests.Scrambled(count, SpanMask(span))));
}
}
}
public void UnsignedKeysDifferingOnlyInTheTopBit_AreSorted()
{
CheckSortsUnsigned(RadixKeySorterTests.Unsigned(RadixKeySorterTests.Scrambled(300, int.MinValue)));
}
public void UnsignedKeysDifferingOnlyInTheMiddleBits_AreSorted()
{
CheckSortsUnsigned(RadixKeySorterTests.Unsigned(RadixKeySorterTests.Scrambled(500, 0x0000FF00)));
}
public void UnsignedKeysPastTheIntRange_AreSortedAsUnsigned()
{
CheckSortsUnsigned(RadixKeySorterTests.Unsigned(RadixKeySorterTests.Scrambled(3000, -1))
.Select(key => key | 0x80000000).ToArray());
}
public void UnsignedAlreadyAscendingPastTheIntRange_AreUnchanged()
{
CheckSortsUnsigned(Enumerable.Range(0, 3000).Select(i => 0x80000000 + (uint)i * 1000).ToArray());
}
public void UnsignedOnlyTheFirstCount_AreSorted()
{
CheckSortsUnsigned(
new RadixKeySorter(), RadixKeySorterTests.Unsigned(RadixKeySorterTests.Scrambled(4000, -1)), 3000);
}
public void NullKeys_Throws()
{
CheckThrow(typeof(ArgumentNullException));
Sort.Keys((int[])null, 0, new RadixKeySorter());
}
public void UnsignedNullKeys_Throws()
{
CheckThrow(typeof(ArgumentNullException));
Sort.Keys((uint[])null, 0, new RadixKeySorter());
}
public void NullSorter_ThrowsEvenForAFewKeys()
{
CheckThrow(typeof(ArgumentNullException));
Sort.Keys(new int[3], 3, null);
}
public void UnsignedNullSorter_ThrowsEvenForAFewKeys()
{
CheckThrow(typeof(ArgumentNullException));
Sort.Keys(new uint[3], 3, null);
}
public void NegativeCount_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.Keys(new int[3], -1, new RadixKeySorter());
}
public void CountBeyondTheKeys_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.Keys(new int[3], 4, new RadixKeySorter());
}
public void UnsignedCountBeyondTheKeys_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.Keys(new uint[3], 4, new RadixKeySorter());
}
public static readonly int[] AllocationSource = RadixKeySorterTests.Scrambled(5000, 0x7FFFFFFF);
public static readonly int[] AllocationKeys = new int[5000];
public static readonly RadixKeySorter AllocationSorter = Grown();
public static RadixKeySorter Grown()
{
var returnMe = new RadixKeySorter();
Array.Copy(AllocationSource, AllocationKeys, 5000);
Sort.Keys(AllocationKeys, 5000, returnMe);
return returnMe;
}
[UnitTestSharp.AllocationTest]
public void OnceTheSorterHasGrown_AllocatesNothing()
{
Array.Copy(AllocationSource, AllocationKeys, 5000);
Sort.Keys(AllocationKeys, 5000, AllocationSorter);
CheckNoAllocations();
CheckTrue(AllocationKeys[0] <= AllocationKeys[4999]);
}
}
public class OrderTests : UnitTestSharp.TestFixture
{
///
/// Orders the first keys and checks the positions come out as a stable sort would
/// give them, the keys are untouched, and the order past the count is untouched.
///
[UnitTestSharp.IgnoreTest]
public void CheckOrders(int[] keys, int count)
{
int[] original = (int[])keys.Clone();
int[] order = UnwrittenOrder(keys.Length + 2);
Sort.Order(keys, count, order);
var expected = new List(ExpectedOrder(original, count));
for (int i = count; i < order.Length; ++i)
{
expected.Add(Unwritten);
}
CheckEqual(expected, order.ToList());
CheckEqual(original, keys);
}
[UnitTestSharp.IgnoreTest]
public void CheckOrders(int[] keys)
{
CheckOrders(keys, keys.Length);
}
public void NoKeys_WritesNothing()
{
var order = UnwrittenOrder(3);
Sort.Order(new[] { 3, 1, 2 }, 0, order);
CheckEqual(UnwrittenOrder(3), order);
}
public void OneKey_IsPositionZero()
{
var order = UnwrittenOrder(3);
Sort.Order(new[] { 3, 1, 2 }, 1, order);
CheckEqual(new[] { 0, Unwritten, Unwritten }, order);
}
public void AFewKeys_AreOrdered()
{
CheckOrders(new[] { 5, 1, 4, 2, 3 });
}
public void AFewKeysAlreadyAscending_AreTheIdentity()
{
CheckOrders(new[] { 1, 2, 2, 5, 9 });
}
public void AFewEqualKeys_KeepTheirOrder()
{
CheckOrders(new[] { 2, 1, 2, 1, 2, 1 });
}
public void ExactlyTheUncheckedLimit_IsOrdered()
{
CheckOrders(Scattered(Sort.UncheckedLimit));
}
public void OneMoreThanTheUncheckedLimit_IsOrdered()
{
CheckOrders(Scattered(Sort.UncheckedLimit + 1));
}
public void ExactlyTheUnrolledLimit_IsOrdered()
{
CheckOrders(Scattered(Sort.UnrolledLimit));
}
public void OneMoreThanTheUnrolledLimit_IsOrdered()
{
CheckOrders(Scattered(Sort.UnrolledLimit + 1));
}
public void TheUncheckedLimit_IsNoMoreThanTheUnrolledLimit()
{
CheckTrue(Sort.UncheckedLimit <= Sort.UnrolledLimit);
}
public void EveryBatchSizeWithEqualKeys_KeepsTheirOrder()
{
foreach (int distinct in new[] { 1, 2, 3, 5, 40 })
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
CheckOrders(Enumerable.Range(0, count).Select(i => (i * 7 + 3) % distinct).ToArray());
}
}
}
public void EveryBatchSizeInReverse_IsOrdered()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
CheckOrders(Enumerable.Range(0, count).Reverse().ToArray());
}
}
public void EveryBatchSizeAlreadyAscending_AreTheIdentity()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
CheckOrders(Enumerable.Range(0, count).ToArray());
}
}
public void EveryBatchSizeWithAnExtremeOutOfPlace_IsOrdered()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
int[] keys = Enumerable.Range(0, count).ToArray();
keys[count / 2] = int.MinValue;
keys[count - 1] = int.MaxValue;
keys[0] = int.MaxValue;
CheckOrders(keys);
}
}
public void EveryBatchSizeWithNegativeKeys_IsOrdered()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
CheckOrders(Enumerable.Range(0, count).Select(i => (i * 37 % 101) - 50).ToArray());
}
}
public void EveryBatchSizeScattered_IsOrdered()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
CheckOrders(Scattered(count));
}
}
public void KeysThatSwapInPairsAndTriples_AreOrdered()
{
CheckOrders(new[] { 1, 0, 3, 2, 5, 4, 8, 6, 7, 9, 10, 11 });
}
public void ManyKeysAlreadyAscendingWithEqualKeys_KeepTheirOrder()
{
CheckOrders(Enumerable.Range(0, 30).Select(i => i / 4).ToArray());
}
public void AlreadyAscendingExactlyTheInsertionLimit_AreTheIdentity()
{
CheckOrders(Enumerable.Range(0, Sort.InsertionSortLimit).ToArray());
}
public void ManyKeysDescending_AreReversed()
{
CheckOrders(Enumerable.Range(0, 20).Reverse().ToArray());
}
public void DescendingWithTheFirstTwoKeysEqual_KeepsThemInOrder()
{
CheckOrders(Enumerable.Range(0, 20).Select(i => 30 - Math.Max(i, 1)).ToArray());
}
public void DescendingWithEqualKeysInTheMiddle_KeepsThemInOrder()
{
CheckOrders(Enumerable.Range(0, 20).Select(i => 30 - (i < 10 ? i : i - 1)).ToArray());
}
public void DescendingWithTheLastTwoKeysEqual_KeepsThemInOrder()
{
CheckOrders(Enumerable.Range(0, 20).Select(i => 30 - Math.Min(i, 18)).ToArray());
}
public void DescendingWithTheLastKeyOutOfPlace_IsOrdered()
{
int[] keys = Enumerable.Range(0, 20).Select(i => 30 - i).ToArray();
keys[19] = 100;
CheckOrders(keys);
}
public void DescendingAfterAnAscendingFirstPair_IsOrdered()
{
CheckOrders(new[] { 0 }.Concat(Enumerable.Range(0, 19).Select(i => 30 - i)).ToArray());
}
public void DescendingFromTheHighestKeyToTheLowest_IsOrdered()
{
long step = uint.MaxValue / 19u;
CheckOrders(Enumerable.Range(0, 20).Select(i => (int)(int.MaxValue - i * step)).ToArray());
}
public void ManyEqualKeysBelowTheInsertionLimit_KeepTheirOrder()
{
CheckOrders(Enumerable.Range(0, 20).Select(i => i % 3).ToArray());
}
public void ExactlyTheInsertionLimit_IsOrdered()
{
CheckOrders(Scattered(Sort.InsertionSortLimit));
}
public void OneMoreThanTheInsertionLimit_IsOrdered()
{
CheckOrders(Scattered(Sort.InsertionSortLimit + 1));
}
public void ManyScatteredKeys_AreOrdered()
{
CheckOrders(Scattered(5000));
}
public void MoreKeysThanTheFirstPassHasBuckets_AreOrdered()
{
CheckOrders(Scattered(40000));
}
public void ManyKeysWithManyTies_KeepTheirOrder()
{
CheckOrders(Enumerable.Range(0, 5000).Select(i => i * 7919 % 1000).ToArray());
}
public void ManyKeysOfTwoValues_KeepTheirOrder()
{
CheckOrders(Enumerable.Range(0, 1000).Select(i => i % 2).ToArray());
}
public void ManyKeysThatDifferOnlyInTheLowestBit_AreOrdered()
{
CheckOrders(Scattered(1000).Select(key => key & 1).ToArray());
}
public void ManyKeysThatDifferOnlyInTheMiddleBits_AreOrdered()
{
CheckOrders(Scattered(3000).Select(key => key & 0x00FF0F00).ToArray());
}
public void ManyKeysThatShareTheirHighBits_AreOrdered()
{
CheckOrders(Scattered(3000).Select(key => 100000000 + (key & 0xFFFF)).ToArray());
}
public void ManyKeysMostlyInOneBucket_AreOrdered()
{
int[] keys = Scattered(5000);
for (int i = 0; i < keys.Length; ++i)
{
if (i % 10 != 0)
{
keys[i] = 5000000 + (keys[i] & 7);
}
}
CheckOrders(keys);
}
public void ManyKeysMostlyInOneBucketWithTies_KeepTheirOrder()
{
int[] keys = Scattered(5000);
for (int i = 0; i < keys.Length; ++i)
{
keys[i] = i % 10 == 0 ? keys[i] % 50 : 5000000 + (i & 1);
}
CheckOrders(keys);
}
public void ManyKeysThatAreAllNegative_AreOrdered()
{
CheckOrders(Scattered(500).Select(key => key | int.MinValue).ToArray());
}
public void ManyNegativeAndPositiveKeys_AreOrdered()
{
CheckOrders(Scattered(2000));
}
public void ManyKeysAtTheExtremes_AreOrdered()
{
CheckOrders(Enumerable.Range(0, 500).Select(i => i % 3 == 0 ? int.MinValue : int.MaxValue).ToArray());
}
public void ManyKeysThatDifferOnlyInTheSignBit_AreOrdered()
{
CheckOrders(Enumerable.Range(0, 300).Select(i => i % 2 == 0 ? int.MinValue : 0).ToArray());
}
public void ManyKeysDescendingWithTies_KeepTheirOrder()
{
CheckOrders(Enumerable.Range(0, 2000).Select(i => (2000 - i) / 3).ToArray());
}
public void ManyKeysAlreadyAscending_AreTheIdentity()
{
CheckOrders(Enumerable.Range(0, 1000).ToArray());
}
public void ManyEqualKeys_AreTheIdentity()
{
CheckOrders(Enumerable.Repeat(7, 1000).ToArray());
}
public void OnlyTheFirstCountKeys_AreOrdered()
{
CheckOrders(Scattered(100), 60);
}
public void OnlyTheFirstCountKeys_OfAFewAreOrdered()
{
CheckOrders(Scattered(30), 20);
}
public void NullKeys_Throws()
{
CheckThrow(typeof(ArgumentNullException));
Sort.Order(null, 3, new int[3]);
}
public void NullOrder_Throws()
{
CheckThrow(typeof(ArgumentNullException));
Sort.Order(new int[3], 3, null);
}
public void NegativeCount_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.Order(new int[3], -1, new int[3]);
}
public void CountBeyondTheKeys_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.Order(new int[3], 4, new int[5]);
}
public void CountBeyondTheOrder_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.Order(new int[5], 4, new int[3]);
}
public static readonly int[][] AllocationKeys =
{
Scattered(Sort.UnrolledLimit), Scattered(Sort.InsertionSortLimit), Scattered(40000),
};
public static readonly int[] AllocationOrder = new int[40000];
public static readonly int[] ReversedKeys =
Enumerable.Range(0, Sort.InsertionSortLimit).Reverse().ToArray();
public static readonly bool Warmed = Warm();
[UnitTestSharp.IgnoreTest]
public static bool Warm()
{
foreach (int[] keys in AllocationKeys)
{
Sort.Order(keys, keys.Length, AllocationOrder);
}
Sort.Order(ReversedKeys, ReversedKeys.Length, AllocationOrder);
return true;
}
[UnitTestSharp.AllocationTest]
public void AnUnrolledBatch_AllocatesNothing()
{
Sort.Order(AllocationKeys[0], AllocationKeys[0].Length, AllocationOrder);
CheckNoAllocations();
}
[UnitTestSharp.AllocationTest]
public void AnInsertionOrderedBatch_AllocatesNothing()
{
Sort.Order(AllocationKeys[1], AllocationKeys[1].Length, AllocationOrder);
CheckNoAllocations();
}
[UnitTestSharp.AllocationTest]
public void ARadixOrderedBatch_AllocatesNothing()
{
Sort.Order(AllocationKeys[2], AllocationKeys[2].Length, AllocationOrder);
CheckNoAllocations();
}
[UnitTestSharp.AllocationTest]
public void AReversedBatch_AllocatesNothing()
{
Sort.Order(ReversedKeys, ReversedKeys.Length, AllocationOrder);
CheckNoAllocations();
}
}
public class ApplyOrderTests : UnitTestSharp.TestFixture
{
///
/// Applies an order to numbered items and checks the items follow it, the order is as it was, and the items
/// past the count are untouched.
///
[UnitTestSharp.IgnoreTest]
public void CheckApplies(int[] order, int count)
{
int[] original = (int[])order.Clone();
int[] items = Enumerable.Range(0, order.Length).Select(i => i * 10).ToArray();
Sort.ApplyOrder(items, order, count);
var expected = new List();
for (int i = 0; i < order.Length; ++i)
{
expected.Add((i < count ? original[i] : i) * 10);
}
CheckEqual(expected, items.ToList());
CheckEqual(original, order);
}
[UnitTestSharp.IgnoreTest]
public void CheckApplies(int[] order)
{
CheckApplies(order, order.Length);
}
public void NoItems_ChangesNothing()
{
CheckApplies(new[] { 1, 0 }, 0);
}
public void OneItem_ChangesNothing()
{
CheckApplies(new[] { 0 });
}
public void TheIdentity_ChangesNothing()
{
CheckApplies(new[] { 0, 1, 2, 3, 4 });
}
public void TwoItemsSwapped_AreSwapped()
{
CheckApplies(new[] { 1, 0 });
}
public void ACycleOfThree_IsFollowed()
{
CheckApplies(new[] { 1, 2, 0 });
}
public void TheOtherCycleOfThree_IsFollowed()
{
CheckApplies(new[] { 2, 0, 1 });
}
public void SeveralCyclesAndFixedPoints_AreFollowed()
{
CheckApplies(new[] { 1, 0, 2, 4, 5, 3, 6, 9, 8, 7 });
}
public void TheReversal_IsFollowed()
{
CheckApplies(Enumerable.Range(0, 11).Reverse().ToArray());
}
public void OnlyTheFirstCountItems_AreMoved()
{
CheckApplies(new[] { 2, 0, 1, 3, 4, 5 }, 3);
}
public void ManyItems_AreMoved()
{
int[] keys = Scattered(1000);
CheckApplies(Enumerable.Range(0, 1000).OrderBy(i => keys[i]).ToArray());
}
public void ItemsThatAreReferences_AreMoved()
{
var items = new[] { "a", "b", "c", "d" };
var order = new[] { 3, 1, 0, 2 };
Sort.ApplyOrder(items, order, 4);
CheckEqual(new[] { "d", "b", "a", "c" }, items);
}
public void ADuplicatePosition_ThrowsAndLeavesTheOrderAsItWas()
{
int[] order = { 1, 0, 1 };
bool threw = false;
try
{
Sort.ApplyOrder(new[] { 10, 20, 30 }, order, 3);
}
catch (ArgumentException)
{
threw = true;
}
CheckTrue(threw);
CheckEqual(new[] { 1, 0, 1 }, order);
}
public void ADuplicateThatLeavesNoCycleToClose_ThrowsAndLeavesTheOrderAsItWas()
{
int[] order = { 0, 0 };
bool threw = false;
try
{
Sort.ApplyOrder(new[] { 10, 20 }, order, 2);
}
catch (ArgumentException)
{
threw = true;
}
CheckTrue(threw);
CheckEqual(new[] { 0, 0 }, order);
}
public void APositionPastTheCount_ThrowsBeforeAnyItemMoves()
{
var items = new[] { 10, 20, 30 };
var order = new[] { 1, 0, 3 };
bool threw = false;
try
{
Sort.ApplyOrder(items, order, 3);
}
catch (ArgumentException)
{
threw = true;
}
CheckTrue(threw);
CheckEqual(new[] { 10, 20, 30 }, items);
CheckEqual(new[] { 1, 0, 3 }, order);
}
public void ANegativePosition_ThrowsBeforeAnyItemMoves()
{
var items = new[] { 10, 20, 30 };
var order = new[] { 1, 0, -1 };
bool threw = false;
try
{
Sort.ApplyOrder(items, order, 3);
}
catch (ArgumentException)
{
threw = true;
}
CheckTrue(threw);
CheckEqual(new[] { 10, 20, 30 }, items);
CheckEqual(new[] { 1, 0, -1 }, order);
}
public void NullItems_Throws()
{
CheckThrow(typeof(ArgumentNullException));
Sort.ApplyOrder(null, new int[3], 3);
}
public void NullOrder_Throws()
{
CheckThrow(typeof(ArgumentNullException));
Sort.ApplyOrder(new int[3], null, 3);
}
public void NegativeCount_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.ApplyOrder(new int[3], new int[3], -1);
}
public void CountBeyondTheItems_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.ApplyOrder(new int[3], new int[5], 4);
}
public void CountBeyondTheOrder_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.ApplyOrder(new int[5], new int[3], 4);
}
public static readonly string[] AllocationItems = new string[1000];
public static readonly int[] AllocationOrder = Enumerable.Range(0, 1000).Reverse().ToArray();
public static readonly bool Warmed = Warm();
[UnitTestSharp.IgnoreTest]
public static bool Warm()
{
Sort.ApplyOrder(AllocationItems, AllocationOrder, AllocationItems.Length);
return true;
}
[UnitTestSharp.AllocationTest]
public void AppliedToReferences_AllocatesNothing()
{
Sort.ApplyOrder(AllocationItems, AllocationOrder, AllocationItems.Length);
CheckNoAllocations();
}
}
public class ItemsTests : UnitTestSharp.TestFixture
{
///
/// Puts the first keys' items in order, with each item's original position as its
/// value. Checks the items come out as a stable sort would give them, the order holds those positions, the
/// keys are untouched, and everything past the count is untouched.
///
[UnitTestSharp.IgnoreTest]
public void CheckSorts(int[] keys, int count)
{
int[] original = (int[])keys.Clone();
int[] items = Enumerable.Range(0, keys.Length).ToArray();
int[] order = UnwrittenOrder(keys.Length + 2);
int[] expectedOrder = ExpectedOrder(original, count);
Sort.Items(keys, items, count, order);
var expectedItems = new List(expectedOrder);
var expectedOrderWithTail = new List(expectedOrder);
for (int i = count; i < keys.Length; ++i)
{
expectedItems.Add(i);
}
for (int i = count; i < order.Length; ++i)
{
expectedOrderWithTail.Add(Unwritten);
}
CheckEqual(expectedItems, items.ToList());
CheckEqual(expectedOrderWithTail, order.ToList());
CheckEqual(original, keys);
}
[UnitTestSharp.IgnoreTest]
public void CheckSorts(int[] keys)
{
CheckSorts(keys, keys.Length);
}
public void ZeroItems_ChangesNothing()
{
var keys = new[] { 3, 1, 2 };
var items = new[] { 0, 1, 2 };
var order = UnwrittenOrder(3);
Sort.Items(keys, items, 0, order);
CheckEqual(new[] { 3, 1, 2 }, keys);
CheckEqual(new[] { 0, 1, 2 }, items);
CheckEqual(UnwrittenOrder(3), order);
}
public void OneItem_ChangesNothing()
{
var keys = new[] { 3, 1, 2 };
var items = new[] { 0, 1, 2 };
var order = UnwrittenOrder(3);
Sort.Items(keys, items, 1, order);
CheckEqual(new[] { 3, 1, 2 }, keys);
CheckEqual(new[] { 0, 1, 2 }, items);
CheckEqual(new[] { 0, Unwritten, Unwritten }, order);
}
public void AFewItems_AreSortedByTheirKeys()
{
CheckSorts(new[] { 5, 1, 4, 2, 3 });
}
public void AFewItemsAlreadyAscending_AreUnchanged()
{
CheckSorts(new[] { 1, 2, 2, 5, 9 });
}
public void AFewEqualKeys_KeepTheirOrder()
{
CheckSorts(new[] { 2, 1, 2, 1, 2, 1 });
}
public void EveryBatchSizeWithEqualKeys_KeepsTheirOrder()
{
foreach (int distinct in new[] { 1, 2, 3, 5 })
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
CheckSorts(Enumerable.Range(0, count).Select(i => (i * 7 + 3) % distinct).ToArray());
}
}
}
public void EveryBatchSizeInReverse_IsSorted()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
CheckSorts(Enumerable.Range(0, count).Reverse().ToArray());
}
}
public void EveryBatchSizeAlreadyAscending_IsUnchanged()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
CheckSorts(Enumerable.Range(0, count).ToArray());
}
}
public void EveryBatchSizeWithAnExtremeOutOfPlace_IsSorted()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
int[] keys = Enumerable.Range(0, count).ToArray();
keys[count / 2] = int.MinValue;
keys[count - 1] = int.MaxValue;
keys[0] = int.MaxValue;
CheckSorts(keys);
}
}
public void EveryBatchSizeWithNegativeKeys_IsSorted()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
CheckSorts(Enumerable.Range(0, count).Select(i => (i * 37 % 101) - 50).ToArray());
}
}
public void EveryBatchSizeScattered_IsSorted()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
CheckSorts(Scattered(count));
}
}
public void EveryBatchSizeOfReferenceItems_AreMovedWithTheirKeys()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
int[] keys = Scattered(count);
string[] items = keys.Select(key => key.ToString()).ToArray();
Sort.Items(keys, items, count, new int[count]);
CheckEqual(keys.OrderBy(key => key).Select(key => key.ToString()).ToArray(), items);
}
}
public void EveryBatchSizeOfReferenceItemsInReverse_AreMovedWithTheirKeys()
{
for (int count = 2; count <= LargestBatchChecked; ++count)
{
int[] keys = Enumerable.Range(0, count).Select(i => count - i).ToArray();
string[] items = keys.Select(key => key.ToString()).ToArray();
Sort.Items(keys, items, count, new int[count]);
CheckEqual(Enumerable.Range(1, count).Select(key => key.ToString()).ToArray(), items);
}
}
public void ManyItemsAlreadyAscending_AreUnchanged()
{
CheckSorts(Enumerable.Range(0, 1000).ToArray());
}
public void ManyItemsDescending_BecomeAscending()
{
CheckSorts(Enumerable.Range(0, 2000).Reverse().ToArray());
}
public void ManyScatteredItems_AreSorted()
{
CheckSorts(Scattered(5000));
}
public void ManyItemsWithManyTies_KeepTheirOrder()
{
CheckSorts(Enumerable.Range(0, 5000).Select(i => i * 7919 % 1000).ToArray());
}
public void OnlyTheFirstCountItems_AreSorted()
{
CheckSorts(Scattered(100), 60);
}
public void OnlyTheFirstCountItems_OfAFewAreSorted()
{
CheckSorts(Scattered(30), 20);
}
public void ItemsThatAreReferences_AreSortedWithTheirKeys()
{
var keys = Enumerable.Range(0, 30).Reverse().ToArray();
var items = keys.Select(key => key.ToString()).ToArray();
Sort.Items(keys, items, 30, new int[30]);
CheckEqual(Enumerable.Range(0, 30).Select(key => key.ToString()).ToArray(), items);
}
public void NullKeys_Throws()
{
CheckThrow(typeof(ArgumentNullException));
Sort.Items(null, new int[3], 3, new int[3]);
}
public void NullItems_Throws()
{
CheckThrow(typeof(ArgumentNullException));
Sort.Items(new int[3], null, 3, new int[3]);
}
public void NullOrder_ThrowsEvenForAFewItems()
{
CheckThrow(typeof(ArgumentNullException));
Sort.Items(new int[3], new int[3], 3, null);
}
public void NegativeCount_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.Items(new int[3], new int[3], -1, new int[3]);
}
public void CountBeyondTheKeys_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.Items(new int[3], new int[5], 4, new int[5]);
}
public void CountBeyondTheItems_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.Items(new int[5], new int[3], 4, new int[5]);
}
public void CountBeyondTheOrder_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
Sort.Items(new int[5], new int[5], 4, new int[3]);
}
public static readonly int[] AllocationSource = Scattered(40000);
public static readonly int[] AllocationKeys = new int[40000];
public static readonly string[] AllocationItems = new string[40000];
public static readonly int[] AllocationOrder = new int[40000];
public static readonly bool Warmed = Warm();
[UnitTestSharp.IgnoreTest]
public static bool Warm()
{
foreach (int count in new[] { Sort.UnrolledLimit, Sort.InsertionSortLimit, AllocationKeys.Length })
{
Array.Copy(AllocationSource, AllocationKeys, count);
Sort.Items(AllocationKeys, AllocationItems, count, AllocationOrder);
}
return true;
}
[UnitTestSharp.AllocationTest]
public void AnInsertionSortedBatch_AllocatesNothing()
{
Array.Copy(AllocationSource, AllocationKeys, Sort.InsertionSortLimit);
Sort.Items(AllocationKeys, AllocationItems, Sort.InsertionSortLimit, AllocationOrder);
CheckNoAllocations();
}
[UnitTestSharp.AllocationTest]
public void AnUnrolledBatch_AllocatesNothing()
{
Array.Copy(AllocationSource, AllocationKeys, Sort.UnrolledLimit);
Sort.Items(AllocationKeys, AllocationItems, Sort.UnrolledLimit, AllocationOrder);
CheckNoAllocations();
}
[UnitTestSharp.AllocationTest]
public void ARadixSortedBatch_AllocatesNothing()
{
Array.Copy(AllocationSource, AllocationKeys, AllocationKeys.Length);
Sort.Items(AllocationKeys, AllocationItems, AllocationKeys.Length, AllocationOrder);
CheckNoAllocations();
}
[UnitTestSharp.AllocationTest]
public void AReversedBatch_AllocatesNothing()
{
for (int i = 0; i < Sort.InsertionSortLimit; ++i)
{
AllocationKeys[i] = Sort.InsertionSortLimit - i;
}
Sort.Items(AllocationKeys, AllocationItems, Sort.InsertionSortLimit, AllocationOrder);
CheckNoAllocations();
}
}
public class CopyOfKeysTests : UnitTestSharp.TestFixture
{
[UnitTestSharp.IgnoreTest]
public void CheckCopies(int[] keys)
{
var original = (int[])keys.Clone();
var expected = (int[])keys.Clone();
Array.Sort(expected);
int[] sorted = Sort.CopyOfKeys(keys);
CheckEqual(expected, sorted);
CheckEqual(original, keys);
CheckFalse(ReferenceEquals(keys, sorted));
}
[UnitTestSharp.IgnoreTest]
public void CheckCopiesUnsigned(uint[] keys)
{
var original = (uint[])keys.Clone();
var expected = (uint[])keys.Clone();
Array.Sort(expected);
uint[] sorted = Sort.CopyOfKeys(keys);
CheckEqual(expected, sorted);
CheckEqual(original, keys);
CheckFalse(ReferenceEquals(keys, sorted));
}
public void NoKeys_ReturnsAnEmptyArray()
{
CheckCopies(new int[0]);
}
public void AFewKeys_AreSortedAndTheOriginalIsUntouched()
{
CheckCopies(new[] { 7, 1, 10, 4, 3 });
}
public void NegativesAmongAFewKeys_ComeFirst()
{
CheckCopies(new[] { 5, -3, 0, -100, 2, -1 });
}
public void TheExtremeKeys_AreSorted()
{
CheckCopies(new[] { 0, int.MaxValue, -1, int.MinValue, 1 });
}
public void DuplicatesAmongAFewKeys_AreKept()
{
CheckCopies(new[] { -2, 3, -2, 3, 0 });
}
public void EveryKeyWidthAroundTheCrossover_AreSorted()
{
for (int span = 1; span <= 32; ++span)
{
foreach (int count in new[] { Sort.RadixCrossover(span) - 1, Sort.RadixCrossover(span),
Sort.RadixCrossover(span) + 1 })
{
CheckCopies(RadixKeySorterTests.Scrambled(count, SpanMask(span)));
}
}
}
public void UnsignedEveryKeyWidthAroundTheCrossover_AreSorted()
{
for (int span = 1; span <= 32; ++span)
{
foreach (int count in new[] { Sort.RadixCrossover(span) - 1, Sort.RadixCrossover(span),
Sort.RadixCrossover(span) + 1 })
{
CheckCopiesUnsigned(RadixKeySorterTests.Unsigned(
RadixKeySorterTests.Scrambled(count, SpanMask(span))));
}
}
}
public void NarrowKeysAboveTheAnalyzeLimit_AreSorted()
{
CheckCopies(RadixKeySorterTests.Scrambled(Sort.AnalyzeLimit + 10, 0xF));
}
public void ManyKeys_AreSortedAndTheOriginalIsUntouched()
{
CheckCopies(RadixKeySorterTests.Scrambled(5000, 0x7FFFFFFF));
}
public void ManyKeysWithNegatives_ComeFirst()
{
int[] keys = RadixKeySorterTests.Scrambled(5000, 0x7FFFFFFF);
for (int i = 0; i < keys.Length; i += 2)
{
keys[i] = -keys[i];
}
CheckCopies(keys);
}
public void ManyAlreadyAscending_ReturnACopy()
{
CheckCopies(Enumerable.Range(0, 3000).ToArray());
}
public void ManyEqualKeys_ReturnACopy()
{
CheckCopies(Enumerable.Repeat(7, 3000).ToArray());
}
public void UnsignedAFewKeys_AreSorted()
{
CheckCopiesUnsigned(new uint[] { 10, 7, 4, 3, 1 });
}
public void UnsignedPastTheIntRange_AreSortedAsUnsigned()
{
CheckCopiesUnsigned(new uint[] { uint.MaxValue, 1u << 31, 0, int.MaxValue, 5 });
}
public void UnsignedManyKeys_AreSortedAndTheOriginalIsUntouched()
{
CheckCopiesUnsigned(RadixKeySorterTests.Unsigned(RadixKeySorterTests.Scrambled(5000, -1)));
}
public void UnsignedManyAlreadyAscending_ReturnACopy()
{
CheckCopiesUnsigned(Enumerable.Range(0, 3000).Select(i => (uint)i * 1000).ToArray());
}
public void UnsignedManyEqualKeys_ReturnACopy()
{
CheckCopiesUnsigned(Enumerable.Repeat(7u, 3000).ToArray());
}
public void NullKeys_Throws()
{
CheckThrow(typeof(ArgumentNullException));
Sort.CopyOfKeys((int[])null);
}
public void UnsignedNullKeys_Throws()
{
CheckThrow(typeof(ArgumentNullException));
Sort.CopyOfKeys((uint[])null);
}
}
}
}