diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs b/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs index a99ed5d9d46b9a..5ddc1f16fee841 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs @@ -23,7 +23,7 @@ public class PriorityQueue /// /// Custom comparer used to order the heap. /// - private IComparer? _comparer; + private readonly IComparer? _comparer; /// /// Lazily-initialized collection used to expose the contents of the queue. diff --git a/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Generic.Tests.cs index 7100b9fdb9fe50..5a0ac9e0b80c32 100644 --- a/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Generic.Tests.cs @@ -3,36 +3,34 @@ using System.Collections.Generic; using System.Linq; -using System.Reflection; using Xunit; namespace System.Collections.Tests { - public abstract class PriorityQueue_Generic_Tests : TestBase<(TElement, TPriority)> + public abstract class PriorityQueue_Generic_Tests : TestBase { - #region Helper methods + protected abstract TElement CreateElement(int seed); - protected IEnumerable<(TElement, TPriority)> GenericIEnumerableFactory(int count) + #region Helper methods + protected IEnumerable<(TElement, TPriority)> CreateItems(int count) { const int MagicValue = 34; int seed = count * MagicValue; for (int i = 0; i < count; i++) { - yield return CreateT(seed++); + yield return (CreateElement(seed++), CreateT(seed++)); } } - protected PriorityQueue GenericPriorityQueueFactory( + protected PriorityQueue CreateEmptyPriorityQueue(int initialCapacity = 0) + => new PriorityQueue(initialCapacity, GetIComparer()); + + protected PriorityQueue CreatePriorityQueue( int initialCapacity, int countOfItemsToGenerate, out List<(TElement element, TPriority priority)> generatedItems) { - generatedItems = this.GenericIEnumerableFactory(countOfItemsToGenerate).ToList(); - - var queue = new PriorityQueue(initialCapacity); - foreach (var (element, priority) in generatedItems) - { - queue.Enqueue(element, priority); - } - + generatedItems = CreateItems(countOfItemsToGenerate).ToList(); + var queue = new PriorityQueue(initialCapacity, GetIComparer()); + queue.EnqueueRange(generatedItems); return queue; } @@ -41,7 +39,7 @@ protected PriorityQueue GenericPriorityQueueFactory( #region Constructors [Fact] - public void PriorityQueue_Generic_Constructor() + public void PriorityQueue_DefaultConstructor_ComparerEqualsDefaultComparer() { var queue = new PriorityQueue(); @@ -52,114 +50,90 @@ public void PriorityQueue_Generic_Constructor() [Theory] [MemberData(nameof(ValidCollectionSizes))] - public void PriorityQueue_Generic_Constructor_int(int initialCapacity) + public void PriorityQueue_EmptyCollection_UnorderedItemsIsEmpty(int initialCapacity) { var queue = new PriorityQueue(initialCapacity); - Assert.Empty(queue.UnorderedItems); } [Fact] - public void PriorityQueue_Generic_Constructor_int_Negative_ThrowsArgumentOutOfRangeException() + public void PriorityQueue_ComparerConstructor_ComparerShouldEqualParameter() { - AssertExtensions.Throws("initialCapacity", () => new PriorityQueue(-1)); - AssertExtensions.Throws("initialCapacity", () => new PriorityQueue(int.MinValue)); - } - - [Fact] - public void PriorityQueue_Generic_Constructor_IComparer() - { - IComparer comparer = Comparer.Default; + IComparer comparer = GetIComparer(); var queue = new PriorityQueue(comparer); - Assert.Equal(comparer, queue.Comparer); } [Fact] - public void PriorityQueue_Generic_Constructor_IComparer_Null() + public void PriorityQueue_ComparerConstructorNull_ComparerShouldEqualDefaultComparer() { - var queue = new PriorityQueue((IComparer)null); - Assert.Equal(Comparer.Default, queue.Comparer); + var queue = new PriorityQueue(comparer: null); + Assert.Equal(0, queue.Count); + Assert.Same(Comparer.Default, queue.Comparer); } [Theory] [MemberData(nameof(ValidCollectionSizes))] - public void PriorityQueue_Generic_Constructor_int_IComparer(int initialCapacity) + public void PriorityQueue_CapacityConstructor_ComparerShouldEqualDefaultComparer(int initialCapacity) { - IComparer comparer = Comparer.Default; var queue = new PriorityQueue(initialCapacity); - Assert.Empty(queue.UnorderedItems); - Assert.Equal(comparer, queue.Comparer); + Assert.Same(Comparer.Default, queue.Comparer); } [Theory] [MemberData(nameof(ValidCollectionSizes))] - public void PriorityQueue_Generic_Constructor_IEnumerable(int count) + public void PriorityQueue_EnumerableConstructor_ShouldContainAllElements(int count) { - HashSet<(TElement, TPriority)> itemsToEnqueue = this.GenericIEnumerableFactory(count).ToHashSet(); + (TElement, TPriority)[] itemsToEnqueue = CreateItems(count).ToArray(); PriorityQueue queue = new PriorityQueue(itemsToEnqueue); - Assert.True(itemsToEnqueue.SetEquals(queue.UnorderedItems)); + Assert.Equal(itemsToEnqueue.Length, queue.Count); + AssertExtensions.CollectionEqual(itemsToEnqueue, queue.UnorderedItems, EqualityComparer<(TElement, TPriority)>.Default); } #endregion - #region Enqueue, Dequeue, Peek + #region Enqueue, Dequeue, Peek, EnqueueDequeue [Theory] [MemberData(nameof(ValidCollectionSizes))] - public void PriorityQueue_Generic_Enqueue(int count) - { - PriorityQueue queue = GenericPriorityQueueFactory(count, count, out var generatedItems); - HashSet<(TElement, TPriority)> expectedItems = generatedItems.ToHashSet(); - - Assert.Equal(count, queue.Count); - var actualItems = queue.UnorderedItems.ToArray(); - Assert.True(expectedItems.SetEquals(actualItems)); - } - - [Fact] - public void PriorityQueue_Generic_Dequeue_EmptyCollection() + public void PriorityQueue_Enqueue_IEnumerable(int count) { - var queue = new PriorityQueue(); - - Assert.False(queue.TryDequeue(out _, out _)); - Assert.Throws(() => queue.Dequeue()); - } + (TElement, TPriority)[] itemsToEnqueue = CreateItems(count).ToArray(); + PriorityQueue queue = CreateEmptyPriorityQueue(); - [Fact] - public void PriorityQueue_Generic_Peek_EmptyCollection() - { - var queue = new PriorityQueue(); + foreach ((TElement element, TPriority priority) in itemsToEnqueue) + { + queue.Enqueue(element, priority); + } - Assert.False(queue.TryPeek(out _, out _)); - Assert.Throws(() => queue.Peek()); + AssertExtensions.CollectionEqual(itemsToEnqueue, queue.UnorderedItems, EqualityComparer<(TElement, TPriority)>.Default); } [Theory] [MemberData(nameof(ValidPositiveCollectionSizes))] - public void PriorityQueue_Generic_Peek_PositiveCount(int count) + public void PriorityQueue_Peek_ShouldReturnMinimalElement(int count) { - IReadOnlyCollection<(TElement, TPriority)> itemsToEnqueue = this.GenericIEnumerableFactory(count).ToArray(); - (TElement element, TPriority priority) expectedPeek = itemsToEnqueue.First(); - PriorityQueue queue = new PriorityQueue(); + IReadOnlyCollection<(TElement, TPriority)> itemsToEnqueue = CreateItems(count).ToArray(); + PriorityQueue queue = CreateEmptyPriorityQueue(); + (TElement Element, TPriority Priority) minItem = itemsToEnqueue.First(); - foreach (var (element, priority) in itemsToEnqueue) + foreach ((TElement element, TPriority priority) in itemsToEnqueue) { - if (queue.Comparer.Compare(priority, expectedPeek.priority) < 0) + if (queue.Comparer.Compare(priority, minItem.Priority) < 0) { - expectedPeek = (element, priority); + minItem = (element, priority); } queue.Enqueue(element, priority); - var actualPeekElement = queue.Peek(); - var actualTryPeekSuccess = queue.TryPeek(out TElement actualTryPeekElement, out TPriority actualTryPeekPriority); + TElement actualPeekElement = queue.Peek(); + Assert.Equal(minItem.Element, actualPeekElement); - Assert.Equal(expectedPeek.element, actualPeekElement); + bool actualTryPeekSuccess = queue.TryPeek(out TElement actualTryPeekElement, out TPriority actualTryPeekPriority); Assert.True(actualTryPeekSuccess); - Assert.Equal(expectedPeek.element, actualTryPeekElement); - Assert.Equal(expectedPeek.priority, actualTryPeekPriority); + Assert.Equal(minItem.Element, actualTryPeekElement); + Assert.Equal(minItem.Priority, actualTryPeekPriority); } } @@ -167,21 +141,21 @@ public void PriorityQueue_Generic_Peek_PositiveCount(int count) [InlineData(0, 5)] [InlineData(1, 1)] [InlineData(3, 100)] - public void PriorityQueue_Generic_PeekAndDequeue(int initialCapacity, int count) + public void PriorityQueue_PeekAndDequeue(int initialCapacity, int count) { - PriorityQueue queue = this.GenericPriorityQueueFactory(initialCapacity, count, out var generatedItems); + PriorityQueue queue = CreatePriorityQueue(initialCapacity, count, out List<(TElement element, TPriority priority)> generatedItems); - var expectedPeekPriorities = generatedItems + TPriority[] expectedPeekPriorities = generatedItems .Select(x => x.priority) .OrderBy(x => x, queue.Comparer) .ToArray(); - for (var i = 0; i < count; ++i) + for (int i = 0; i < count; ++i) { - var expectedPeekPriority = expectedPeekPriorities[i]; + TPriority expectedPeekPriority = expectedPeekPriorities[i]; - var actualTryPeekSuccess = queue.TryPeek(out TElement actualTryPeekElement, out TPriority actualTryPeekPriority); - var actualTryDequeueSuccess = queue.TryDequeue(out TElement actualTryDequeueElement, out TPriority actualTryDequeuePriority); + bool actualTryPeekSuccess = queue.TryPeek(out TElement actualTryPeekElement, out TPriority actualTryPeekPriority); + bool actualTryDequeueSuccess = queue.TryDequeue(out TElement actualTryDequeueElement, out TPriority actualTryDequeuePriority); Assert.True(actualTryPeekSuccess); Assert.True(actualTryDequeueSuccess); @@ -196,113 +170,48 @@ public void PriorityQueue_Generic_PeekAndDequeue(int initialCapacity, int count) [Theory] [MemberData(nameof(ValidCollectionSizes))] - public void PriorityQueue_Generic_EnqueueRange_IEnumerable(int count) + public void PriorityQueue_EnqueueRange_IEnumerable(int count) { - HashSet<(TElement, TPriority)> itemsToEnqueue = this.GenericIEnumerableFactory(count).ToHashSet(); - PriorityQueue queue = new PriorityQueue(); + (TElement, TPriority)[] itemsToEnqueue = CreateItems(count).ToArray(); + PriorityQueue queue = CreateEmptyPriorityQueue(); queue.EnqueueRange(itemsToEnqueue); - Assert.True(itemsToEnqueue.SetEquals(queue.UnorderedItems)); + AssertExtensions.CollectionEqual(itemsToEnqueue, queue.UnorderedItems, EqualityComparer<(TElement, TPriority)>.Default); } - #endregion - - #region Clear - [Theory] [MemberData(nameof(ValidCollectionSizes))] - public void PriorityQueue_Generic_Clear(int count) + public void PriorityQueue_EnqueueDequeue(int count) { - PriorityQueue queue = this.GenericPriorityQueueFactory(initialCapacity: 0, count, out _); + (TElement Element, TPriority Priority)[] itemsToEnqueue = CreateItems(2 * count).ToArray(); + PriorityQueue queue = CreateEmptyPriorityQueue(); + queue.EnqueueRange(itemsToEnqueue.Take(count)); - Assert.Equal(count, queue.Count); - queue.Clear(); - Assert.Equal(expected: 0, queue.Count); + foreach ((TElement element, TPriority priority) in itemsToEnqueue.Skip(count)) + { + queue.EnqueueDequeue(element, priority); + } + + IEnumerable<(TElement Element, TPriority Priority)> expectedItems = itemsToEnqueue.OrderByDescending(x => x.Priority, queue.Comparer).Take(count); + AssertExtensions.CollectionEqual(expectedItems, queue.UnorderedItems, EqualityComparer<(TElement, TPriority)>.Default); } #endregion - #region EnsureCapacity, TrimExcess - - [Fact] - public void PriorityQueue_Generic_EnsureCapacity_Negative() - { - PriorityQueue queue = new PriorityQueue(); - AssertExtensions.Throws("capacity", () => queue.EnsureCapacity(-1)); - AssertExtensions.Throws("capacity", () => queue.EnsureCapacity(int.MinValue)); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(0, 5)] - [InlineData(1, 1)] - [InlineData(3, 100)] - public void PriorityQueue_Generic_TrimExcess_ValidQueueThatHasntBeenRemovedFrom(int initialCapacity, int count) - { - PriorityQueue queue = this.GenericPriorityQueueFactory(initialCapacity, count, out _); - queue.TrimExcess(); - } + #region Clear [Theory] [MemberData(nameof(ValidCollectionSizes))] - public void PriorityQueue_Generic_TrimExcess_Repeatedly(int count) + public void PriorityQueue_Clear(int count) { - PriorityQueue queue = this.GenericPriorityQueueFactory(initialCapacity: 0, count, out _); - + PriorityQueue queue = CreatePriorityQueue(initialCapacity: 0, count, out _); Assert.Equal(count, queue.Count); - queue.TrimExcess(); - queue.TrimExcess(); - queue.TrimExcess(); - Assert.Equal(count, queue.Count); - } - - [Theory] - [MemberData(nameof(ValidPositiveCollectionSizes))] - public void PriorityQueue_Generic_EnsureCapacityAndTrimExcess(int count) - { - IReadOnlyCollection<(TElement, TPriority)> itemsToEnqueue = this.GenericIEnumerableFactory(count).ToArray(); - PriorityQueue queue = new PriorityQueue(); - int expectedCount = 0; - Random random = new Random(Seed: 34); - int getNextEnsureCapacity() => random.Next(0, count * 2); - void trimAndEnsureCapacity() - { - queue.TrimExcess(); - - int capacityAfterEnsureCapacity = queue.EnsureCapacity(getNextEnsureCapacity()); - Assert.Equal(capacityAfterEnsureCapacity, GetUnderlyingBufferCapacity(queue)); - - int capacityAfterTrimExcess = (queue.Count < (int)(capacityAfterEnsureCapacity * 0.9)) ? queue.Count : capacityAfterEnsureCapacity; - queue.TrimExcess(); - Assert.Equal(capacityAfterTrimExcess, GetUnderlyingBufferCapacity(queue)); - }; - - foreach (var (element, priority) in itemsToEnqueue) - { - trimAndEnsureCapacity(); - queue.Enqueue(element, priority); - expectedCount++; - Assert.Equal(expectedCount, queue.Count); - } - - while (expectedCount > 0) - { - queue.Dequeue(); - trimAndEnsureCapacity(); - expectedCount--; - Assert.Equal(expectedCount, queue.Count); - } - trimAndEnsureCapacity(); - Assert.Equal(0, queue.Count); - } + queue.Clear(); - private static int GetUnderlyingBufferCapacity(PriorityQueue queue) - { - FieldInfo nodesType = queue.GetType().GetField("_nodes", BindingFlags.NonPublic | BindingFlags.Instance); - var nodes = ((TElement Element, TPriority Priority)[])nodesType.GetValue(queue); - return nodes.Length; + Assert.Equal(expected: 0, queue.Count); + Assert.False(queue.TryPeek(out _, out _)); } #endregion @@ -313,7 +222,7 @@ private static int GetUnderlyingBufferCapacity(PriorityQueue queue = this.GenericPriorityQueueFactory(initialCapacity: 0, count, out _); + PriorityQueue queue = CreatePriorityQueue(initialCapacity: 0, count, out _); (TElement, TPriority)[] firstEnumeration = queue.UnorderedItems.ToArray(); (TElement, TPriority)[] secondEnumeration = queue.UnorderedItems.ToArray(); @@ -322,35 +231,6 @@ public void PriorityQueue_Enumeration_OrderingIsConsistent(int count) Assert.True(firstEnumeration.SequenceEqual(secondEnumeration)); } - [Theory] - [MemberData(nameof(ValidPositiveCollectionSizes))] - public void PriorityQueue_Enumeration_InvalidationOnModifiedCollection(int count) - { - IReadOnlyCollection<(TElement, TPriority)> itemsToEnqueue = this.GenericIEnumerableFactory(count).ToArray(); - PriorityQueue queue = new PriorityQueue(); - queue.EnqueueRange(itemsToEnqueue.Take(count - 1)); - var enumerator = queue.UnorderedItems.GetEnumerator(); - - (TElement element, TPriority priority) = itemsToEnqueue.Last(); - queue.Enqueue(element, priority); - Assert.Throws(() => enumerator.MoveNext()); - } - - [Theory] - [MemberData(nameof(ValidPositiveCollectionSizes))] - public void PriorityQueue_Enumeration_InvalidationOnModifiedCapacity(int count) - { - PriorityQueue queue = this.GenericPriorityQueueFactory(initialCapacity: 0, count, out _); - var enumerator = queue.UnorderedItems.GetEnumerator(); - - int capacityBefore = GetUnderlyingBufferCapacity(queue); - queue.EnsureCapacity(count * 2 + 4); - int capacityAfter = GetUnderlyingBufferCapacity(queue); - - Assert.NotEqual(capacityBefore, capacityAfter); - Assert.Throws(() => enumerator.MoveNext()); - } - #endregion } } diff --git a/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Generic.cs b/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Generic.cs index 624649bdbaa6fb..b7f964f1fd77a5 100644 --- a/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Generic.cs +++ b/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Generic.cs @@ -1,16 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; + namespace System.Collections.Tests { public class PriorityQueue_Generic_Tests_string_string : PriorityQueue_Generic_Tests { - protected override (string, string) CreateT(int seed) - { - var element = this.CreateString(seed); - var priority = this.CreateString(seed); - return (element, priority); - } + protected override string CreateT(int seed) => CreateString(seed); + protected override string CreateElement(int seed) => CreateString(seed); protected string CreateString(int seed) { @@ -24,13 +22,19 @@ protected string CreateString(int seed) public class PriorityQueue_Generic_Tests_int_int : PriorityQueue_Generic_Tests { - protected override (int, int) CreateT(int seed) - { - var element = this.CreateInt(seed); - var priority = this.CreateInt(seed); - return (element, priority); - } + protected override int CreateT(int seed) => CreateInt(seed); + protected override int CreateElement(int seed) => CreateInt(seed); protected int CreateInt(int seed) => new Random(seed).Next(); } + + public class PriorityQueue_Generic_Tests_string_string_CustomComparer : PriorityQueue_Generic_Tests_string_string + { + protected override IComparer GetIComparer() => StringComparer.InvariantCultureIgnoreCase; + } + + public class PriorityQueue_Generic_Tests_int_int_CustomComparer : PriorityQueue_Generic_Tests_int_int + { + protected override IComparer GetIComparer() => Comparer.Create((x, y) => -x.CompareTo(y)); + } } diff --git a/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.PropertyTests.cs b/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.PropertyTests.cs new file mode 100644 index 00000000000000..d6035079a9cab9 --- /dev/null +++ b/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.PropertyTests.cs @@ -0,0 +1,205 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.Collections.Tests +{ + public static class PriorityQueue_PropertyTests + { + const int MaxTest = 100; + const int Seed = 42; + + private readonly static IComparer s_stringComparer = StringComparer.Ordinal; + + [Theory] + [MemberData(nameof(GetRandomStringArrays))] + public static void HeapSort_Heapify_String(string[] elements) + { + IEnumerable expected = elements.OrderBy(e => e, s_stringComparer); + IEnumerable actual = HeapSort_Heapify(elements, s_stringComparer); + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(GetRandomIntArrays))] + public static void HeapSort_Heapify_Int(int[] elements) + { + IEnumerable expected = elements.OrderBy(e => e); + IEnumerable actual = HeapSort_Heapify(elements); + Assert.Equal(expected, actual); + } + + private static IEnumerable HeapSort_Heapify(IEnumerable inputs, IComparer? comparer = null) + { + var queue = new PriorityQueue(inputs.Select(e => (e, e)), comparer); + foreach ((TElement element, TElement priority) in DrainHeap(queue)) + { + Assert.Equal(element, priority); + yield return element; + } + } + + [Theory] + [MemberData(nameof(GetRandomStringArrays))] + public static void HeapSort_EnqueueRange_String(string[] elements) + { + IEnumerable expected = elements.OrderBy(e => e, s_stringComparer); + IEnumerable actual = HeapSort_EnqueueRange(elements, s_stringComparer); + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(GetRandomIntArrays))] + public static void HeapSort_EnqueueRange_Int(int[] elements) + { + IEnumerable expected = elements.OrderBy(e => e); + IEnumerable actual = HeapSort_EnqueueRange(elements); + Assert.Equal(expected, actual); + } + + private static IEnumerable HeapSort_EnqueueRange(IEnumerable inputs, IComparer? comparer = null) + { + var queue = new PriorityQueue(comparer); + queue.EnqueueRange(inputs.Select(e => (e, e))); + foreach ((TElement element, TElement priority) in DrainHeap(queue)) + { + Assert.Equal(element, priority); + yield return element; + } + } + + [Theory] + [MemberData(nameof(GetRandomStringArrays))] + public static void HeapSort_Enqueue_String(string[] elements) + { + IEnumerable expected = elements.OrderBy(e => e, s_stringComparer); + IEnumerable actual = HeapSort_Enqueue(elements, s_stringComparer); + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(GetRandomIntArrays))] + public static void HeapSort_Enqueue_Int(int[] elements) + { + IEnumerable expected = elements.OrderBy(e => e); + IEnumerable actual = HeapSort_Enqueue(elements); + Assert.Equal(expected, actual); + } + + private static IEnumerable HeapSort_Enqueue(IEnumerable inputs, IComparer? comparer = null) + { + var queue = new PriorityQueue(comparer); + + foreach (TElement input in inputs) + { + queue.Enqueue(input, input); + } + + foreach ((TElement element, TElement priority) in DrainHeap(queue)) + { + Assert.Equal(element, priority); + yield return element; + } + } + + [Theory] + [MemberData(nameof(GetRandomStringArrays))] + public static void KMaxElements_String(string[] elements) + { + const int k = 5; + IEnumerable expected = elements.OrderByDescending(e => e, s_stringComparer).Take(k); + IEnumerable actual = KMaxElements(elements, k, s_stringComparer); + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(GetRandomIntArrays))] + public static void KMaxElements_Int(int[] elements) + { + const int k = 5; + IEnumerable expected = elements.OrderByDescending(e => e).Take(k); + IEnumerable actual = KMaxElements(elements, k); + Assert.Equal(expected, actual); + } + + private static IEnumerable KMaxElements(TElement[] elements, int k, IComparer? comparer = null) + { + var queue = new PriorityQueue(comparer); + comparer = queue.Comparer; + + int heapSize = Math.Min(k, elements.Length); + for (int i = 0; i < heapSize; i++) + { + TElement element = elements[i]; + queue.Enqueue(element, element); + } + + for (int i = k; i < elements.Length; i++) + { + TElement element = elements[i]; + TElement dequeued = queue.EnqueueDequeue(element, element); + Assert.True(comparer.Compare(dequeued, element) <= 0); + Assert.Equal(k, queue.Count); + } + + foreach ((TElement element, TElement priority) in DrainHeap(queue).Reverse()) + { + Assert.Equal(element, priority); + yield return element; + } + } + + private static IEnumerable<(TElement Element, TPriority Priority)> DrainHeap(PriorityQueue queue) + { + while (queue.Count > 0) + { + Assert.True(queue.TryPeek(out TElement element, out TPriority priority)); + Assert.True(queue.TryDequeue(out TElement element2, out TPriority priority2)); + Assert.Equal(element, element2); + Assert.Equal(priority, priority2); + yield return (element, priority); + } + + Assert.False(queue.TryPeek(out _, out _)); + } + + public static IEnumerable GetRandomStringArrays() => GenerateMemberData(random => GenArray(GenString, random)); + public static IEnumerable GetRandomIntArrays() => GenerateMemberData(random => GenArray(GenInt, random)); + + private static IEnumerable GenerateMemberData(Func genElement) + { + var random = new Random(Seed); + for (int i = 0; i < MaxTest; i++) + { + yield return new object[] { genElement(random) }; + }; + } + + private static T[] GenArray(Func genElement, Random random) + { + const int MaxArraySize = 100; + int arraySize = random.Next(MaxArraySize); + var array = new T[arraySize]; + for (int i = 0; i < arraySize; i++) + { + array[i] = genElement(random); + } + + return array; + } + + private static int GenInt(Random random) => random.Next(); + + private static string GenString(Random random) + { + const int MaxSize = 50; + int size = random.Next(MaxSize); + var buffer = new byte[size]; + random.NextBytes(buffer); + return Convert.ToBase64String(buffer); + } + } +} diff --git a/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Tests.cs b/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Tests.cs index 94af9b07e8412c..4201e90de8c59a 100644 --- a/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/PriorityQueue/PriorityQueue.Tests.cs @@ -2,13 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Linq; +using System.Reflection; using Xunit; namespace System.Collections.Tests { public class PriorityQueue_NonGeneric_Tests : TestBase { - protected PriorityQueue SmallPriorityQueueFactory(out HashSet<(string, int)> items) + protected PriorityQueue CreateSmallPriorityQueue(out HashSet<(string, int)> items) { items = new HashSet<(string, int)> { @@ -21,6 +23,17 @@ protected PriorityQueue SmallPriorityQueueFactory(out HashSet<(stri return queue; } + protected PriorityQueue CreatePriorityQueue(int initialCapacity, int count) + { + var pq = new PriorityQueue(initialCapacity); + for (int i = 0; i < count; i++) + { + pq.Enqueue(i, i); + } + + return pq; + } + [Fact] public void PriorityQueue_Generic_EnqueueDequeue_Empty() { @@ -33,7 +46,7 @@ public void PriorityQueue_Generic_EnqueueDequeue_Empty() [Fact] public void PriorityQueue_Generic_EnqueueDequeue_SmallerThanMin() { - PriorityQueue queue = SmallPriorityQueueFactory(out HashSet<(string, int)> enqueuedItems); + PriorityQueue queue = CreateSmallPriorityQueue(out HashSet<(string, int)> enqueuedItems); string actualElement = queue.EnqueueDequeue("zero", 0); @@ -44,7 +57,7 @@ public void PriorityQueue_Generic_EnqueueDequeue_SmallerThanMin() [Fact] public void PriorityQueue_Generic_EnqueueDequeue_LargerThanMin() { - PriorityQueue queue = SmallPriorityQueueFactory(out HashSet<(string, int)> enqueuedItems); + PriorityQueue queue = CreateSmallPriorityQueue(out HashSet<(string, int)> enqueuedItems); string actualElement = queue.EnqueueDequeue("four", 4); @@ -57,7 +70,7 @@ public void PriorityQueue_Generic_EnqueueDequeue_LargerThanMin() [Fact] public void PriorityQueue_Generic_EnqueueDequeue_EqualToMin() { - PriorityQueue queue = SmallPriorityQueueFactory(out HashSet<(string, int)> enqueuedItems); + PriorityQueue queue = CreateSmallPriorityQueue(out HashSet<(string, int)> enqueuedItems); string actualElement = queue.EnqueueDequeue("one-not-to-enqueue", 1); @@ -104,5 +117,166 @@ public void PriorityQueue_Generic_EnqueueRange_Null() Assert.Equal("not null", queue.Dequeue()); } + + [Fact] + public void PriorityQueue_Constructor_int_Negative_ThrowsArgumentOutOfRangeException() + { + AssertExtensions.Throws("initialCapacity", () => new PriorityQueue(-1)); + AssertExtensions.Throws("initialCapacity", () => new PriorityQueue(int.MinValue)); + } + + [Fact] + public void PriorityQueue_Constructor_Enumerable_null_ThrowsArgumentNullException() + { + AssertExtensions.Throws("items", () => new PriorityQueue(items: null)); + AssertExtensions.Throws("items", () => new PriorityQueue(items: null, comparer: Comparer.Default)); + } + + [Fact] + public void PriorityQueue_EnqueueRange_null_ThrowsArgumentNullException() + { + var queue = new PriorityQueue(); + AssertExtensions.Throws("items", () => queue.EnqueueRange(null)); + } + + [Fact] + public void PriorityQueue_EmptyCollection_Dequeue_ShouldThrowException() + { + var queue = new PriorityQueue(); + + Assert.Equal(0, queue.Count); + Assert.False(queue.TryDequeue(out _, out _)); + Assert.Throws(() => queue.Dequeue()); + } + + [Fact] + public void PriorityQueue_EmptyCollection_Peek_ShouldReturnFalse() + { + var queue = new PriorityQueue(); + + Assert.False(queue.TryPeek(out _, out _)); + Assert.Throws(() => queue.Peek()); + } + + #region EnsureCapacity, TrimExcess + + [Fact] + public void PriorityQueue_EnsureCapacity_Negative_ShouldThrowException() + { + PriorityQueue queue = new PriorityQueue(); + AssertExtensions.Throws("capacity", () => queue.EnsureCapacity(-1)); + AssertExtensions.Throws("capacity", () => queue.EnsureCapacity(int.MinValue)); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(0, 5)] + [InlineData(1, 1)] + [InlineData(3, 100)] + public void PriorityQueue_TrimExcess_ShouldNotChangeCount(int initialCapacity, int count) + { + PriorityQueue queue = CreatePriorityQueue(initialCapacity, count); + + Assert.Equal(count, queue.Count); + queue.TrimExcess(); + Assert.Equal(count, queue.Count); + } + + [Theory] + [MemberData(nameof(ValidPositiveCollectionSizes))] + public void PriorityQueue_TrimExcess_Repeatedly_ShouldNotChangeCount(int count) + { + PriorityQueue queue = CreatePriorityQueue(initialCapacity: count, count); + + Assert.Equal(count, queue.Count); + queue.TrimExcess(); + queue.TrimExcess(); + queue.TrimExcess(); + Assert.Equal(count, queue.Count); + } + + [Theory] + [MemberData(nameof(ValidPositiveCollectionSizes))] + public void PriorityQueue_Generic_EnsureCapacityAndTrimExcess(int count) + { + IReadOnlyCollection<(int, int)> itemsToEnqueue = Enumerable.Range(1, count).Select(i => (i, i)).ToArray(); + var queue = new PriorityQueue(); + int expectedCount = 0; + Random random = new Random(Seed: 34); + int getNextEnsureCapacity() => random.Next(0, count * 2); + void trimAndEnsureCapacity() + { + queue.TrimExcess(); + + int capacityAfterEnsureCapacity = queue.EnsureCapacity(getNextEnsureCapacity()); + Assert.Equal(capacityAfterEnsureCapacity, GetUnderlyingBufferCapacity(queue)); + + int capacityAfterTrimExcess = (queue.Count < (int)(capacityAfterEnsureCapacity * 0.9)) ? queue.Count : capacityAfterEnsureCapacity; + queue.TrimExcess(); + Assert.Equal(capacityAfterTrimExcess, GetUnderlyingBufferCapacity(queue)); + }; + + foreach ((int element, int priority) in itemsToEnqueue) + { + trimAndEnsureCapacity(); + queue.Enqueue(element, priority); + expectedCount++; + Assert.Equal(expectedCount, queue.Count); + } + + while (expectedCount > 0) + { + queue.Dequeue(); + trimAndEnsureCapacity(); + expectedCount--; + Assert.Equal(expectedCount, queue.Count); + } + + trimAndEnsureCapacity(); + Assert.Equal(0, queue.Count); + } + + private static int GetUnderlyingBufferCapacity(PriorityQueue queue) + { + FieldInfo nodesField = queue.GetType().GetField("_nodes", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.NotNull(nodesField); + var nodes = ((TElement Element, TPriority Priority)[])nodesField.GetValue(queue); + return nodes.Length; + } + + #endregion + + [Theory] + [MemberData(nameof(ValidPositiveCollectionSizes))] + public void PriorityQueue_Enumeration_InvalidateOnModifiedCollection(int count) + { + IReadOnlyCollection<(int, int)> itemsToEnqueue = Enumerable.Range(1, count).Select(i => (i, i)).ToArray(); + PriorityQueue queue = new PriorityQueue(); + queue.EnqueueRange(itemsToEnqueue.Take(count - 1)); + var enumerator = queue.UnorderedItems.GetEnumerator(); + + (int element, int priority) = itemsToEnqueue.Last(); + queue.Enqueue(element, priority); + Assert.Throws(() => enumerator.MoveNext()); + } + + #region Enumeration + + [Theory] + [MemberData(nameof(ValidPositiveCollectionSizes))] + public void PriorityQueue_Enumeration_InvalidationOnModifiedCapacity(int count) + { + PriorityQueue queue = CreatePriorityQueue(initialCapacity: 0, count); + var enumerator = queue.UnorderedItems.GetEnumerator(); + + int capacityBefore = GetUnderlyingBufferCapacity(queue); + queue.EnsureCapacity(count * 2 + 4); + int capacityAfter = GetUnderlyingBufferCapacity(queue); + + Assert.NotEqual(capacityBefore, capacityAfter); + Assert.Throws(() => enumerator.MoveNext()); + } + + #endregion } } diff --git a/src/libraries/System.Collections/tests/System.Collections.Tests.csproj b/src/libraries/System.Collections/tests/System.Collections.Tests.csproj index eb646000406169..344f3548559813 100644 --- a/src/libraries/System.Collections/tests/System.Collections.Tests.csproj +++ b/src/libraries/System.Collections/tests/System.Collections.Tests.csproj @@ -97,6 +97,7 @@ +