From 5d518f7389c8de10b46b9114ddb91f698921c188 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 8 Nov 2026 18:25:30 +0100 Subject: [PATCH] Heap Sort: .NET 10, copy-per-call benchmark, stronger tests Move both projects to net10.0 with BenchmarkDotNet 0.15.8, MSTest 4.5.2, Microsoft.NET.Test.Sdk 18.11.0 and coverlet.collector 10.1.0. The benchmark moves into HeapSortBenchmarks, which copies the generated input into a work array on every call, so the random case no longer sorts an already sorted array after the first call. SortArray takes (array, size) as the article prints it. Tests use (expected, actual) order and cover the article example, empty, sorted, identical and random input checked against Array.Sort. --- .../HeapSort/HeapSort/HeapSort.csproj | 4 +- .../HeapSort/HeapSort/HeapSortBenchmarks.cs | 35 +++++ .../HeapSort/HeapSort/HeapSortMethods.cs | 130 ++++++++---------- .../HeapSort/HeapSort/Program.cs | 2 +- .../HeapSortTests/HeapSortTests.csproj | 10 +- .../HeapSortTests/HeapSortUnitTests.cs | 116 ++++++++++------ 6 files changed, 178 insertions(+), 119 deletions(-) create mode 100644 csharp-algorithms/HeapSort/HeapSort/HeapSortBenchmarks.cs diff --git a/csharp-algorithms/HeapSort/HeapSort/HeapSort.csproj b/csharp-algorithms/HeapSort/HeapSort/HeapSort.csproj index 6103222f24..c96a8eb1e7 100644 --- a/csharp-algorithms/HeapSort/HeapSort/HeapSort.csproj +++ b/csharp-algorithms/HeapSort/HeapSort/HeapSort.csproj @@ -2,13 +2,13 @@ Exe - net6.0 + net10.0 enable enable - + diff --git a/csharp-algorithms/HeapSort/HeapSort/HeapSortBenchmarks.cs b/csharp-algorithms/HeapSort/HeapSort/HeapSortBenchmarks.cs new file mode 100644 index 0000000000..367751dbb3 --- /dev/null +++ b/csharp-algorithms/HeapSort/HeapSort/HeapSortBenchmarks.cs @@ -0,0 +1,35 @@ +using BenchmarkDotNet.Attributes; + +namespace HeapSort; + +public class HeapSortBenchmarks +{ + private const int Size = 2_000_000; + + private readonly HeapSortMethods _heapSort = new(); + private int[] _source = []; + private int[] _work = []; + + [Params("Random", "Sorted", "Identical")] + public string Input { get; set; } = "Random"; + + [GlobalSetup] + public void Setup() + { + _source = Input switch + { + "Sorted" => HeapSortMethods.CreateSortedArray(Size), + "Identical" => HeapSortMethods.CreateIdenticalArray(Size, 1, Size), + _ => HeapSortMethods.CreateRandomArray(Size, 1, Size) + }; + _work = new int[Size]; + } + + [Benchmark] + public int[] SortArray() + { + _source.CopyTo(_work, 0); + + return _heapSort.SortArray(_work, Size); + } +} diff --git a/csharp-algorithms/HeapSort/HeapSort/HeapSortMethods.cs b/csharp-algorithms/HeapSort/HeapSort/HeapSortMethods.cs index 69aa92f9d9..40c7101b86 100644 --- a/csharp-algorithms/HeapSort/HeapSort/HeapSortMethods.cs +++ b/csharp-algorithms/HeapSort/HeapSort/HeapSortMethods.cs @@ -1,100 +1,88 @@ -using BenchmarkDotNet.Attributes; +namespace HeapSort; -namespace HeapSort +public class HeapSortMethods { - public class HeapSortMethods + public int[] SortArray(int[] array, int size) { - public IEnumerable SampleArrays() + if (size <= 1) + return array; + + for (int i = size / 2 - 1; i >= 0; i--) { - yield return new object[] { CreateSortedArray(2000000), 2000000, "Worst Case" }; - yield return new object[] { CreateRandomArray(2000000, 1, 2000000), 2000000, "Average Case" }; - yield return new object[] { CreateIdenticalArray(2000000, 1, 2000000), 2000000, "Best Case" }; + Heapify(array, size, i); } - [Benchmark] - [ArgumentsSource(nameof(SampleArrays))] - public int[] SortArray(int[] array, int size, string arrayName) + for (int i = size - 1; i >= 0; i--) { - if (size <= 1) - return array; + var tempVar = array[0]; + array[0] = array[i]; + array[i] = tempVar; - for (int i = size / 2 - 1; i >= 0; i--) - { - Heapify(array, size, i); - } + Heapify(array, i, 0); + } - for (int i = size - 1; i >= 0; i--) - { - var tempVar = array[0]; - array[0] = array[i]; - array[i] = tempVar; + return array; + } - Heapify(array, i, 0); - } + static void Heapify(int[] array, int size, int index) + { + var largestIndex = index; + var leftChild = 2 * index + 1; + var rightChild = 2 * index + 2; - return array; + if (leftChild < size && array[leftChild] > array[largestIndex]) + { + largestIndex = leftChild; } - static void Heapify(int[] array, int size, int index) + if (rightChild < size && array[rightChild] > array[largestIndex]) { - var largestIndex = index; - var leftChild = 2 * index + 1; - var rightChild = 2 * index + 2; - - if (leftChild < size && array[leftChild] > array[largestIndex]) - { - largestIndex = leftChild; - } - - if (rightChild < size && array[rightChild] > array[largestIndex]) - { - largestIndex = rightChild; - } - - if (largestIndex != index) - { - var tempVar = array[index]; - array[index] = array[largestIndex]; - array[largestIndex] = tempVar; - - Heapify(array, size, largestIndex); - } + largestIndex = rightChild; } - public static int[] CreateRandomArray(int size, int lower, int upper) + if (largestIndex != index) { - var array = new int[size]; - var rand = new Random(); - - for (int i = 0; i < size; i++) - array[i] = rand.Next(lower, upper); + var tempVar = array[index]; + array[index] = array[largestIndex]; + array[largestIndex] = tempVar; - return array; + Heapify(array, size, largestIndex); } + } - public static int[] CreateSortedArray(int size) - { - var array = new int[size]; + public static int[] CreateRandomArray(int size, int lower, int upper) + { + var array = new int[size]; + var rand = new Random(); - for (int i = 0; i < size; i++) - array[i] = i; + for (int i = 0; i < size; i++) + array[i] = rand.Next(lower, upper); - return array; - } + return array; + } - public static int[] CreateIdenticalArray(int size, int lower, int upper) - { - var array = new int[size]; - var rand = new Random(); + public static int[] CreateSortedArray(int size) + { + var array = new int[size]; + + for (int i = 0; i < size; i++) + array[i] = i; - var selectedElement = rand.Next(lower, upper); + return array; + } - for (int i = 0; i < size; i++) - { - array[i] = selectedElement; - } + public static int[] CreateIdenticalArray(int size, int lower, int upper) + { + var array = new int[size]; + var rand = new Random(); - return array; + var selectedElement = rand.Next(lower, upper); + + for (int i = 0; i < size; i++) + { + array[i] = selectedElement; } + + return array; } } diff --git a/csharp-algorithms/HeapSort/HeapSort/Program.cs b/csharp-algorithms/HeapSort/HeapSort/Program.cs index 533817cc22..0408e19d24 100644 --- a/csharp-algorithms/HeapSort/HeapSort/Program.cs +++ b/csharp-algorithms/HeapSort/HeapSort/Program.cs @@ -1,4 +1,4 @@ using BenchmarkDotNet.Running; using HeapSort; -var summary = BenchmarkRunner.Run(); +BenchmarkRunner.Run(args: args); diff --git a/csharp-algorithms/HeapSort/HeapSortTests/HeapSortTests.csproj b/csharp-algorithms/HeapSort/HeapSortTests/HeapSortTests.csproj index 803f463106..ab11c66aad 100644 --- a/csharp-algorithms/HeapSort/HeapSortTests/HeapSortTests.csproj +++ b/csharp-algorithms/HeapSort/HeapSortTests/HeapSortTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net10.0 enable enable @@ -9,10 +9,10 @@ - - - - + + + + diff --git a/csharp-algorithms/HeapSort/HeapSortTests/HeapSortUnitTests.cs b/csharp-algorithms/HeapSort/HeapSortTests/HeapSortUnitTests.cs index c8d608c553..435f7736f3 100644 --- a/csharp-algorithms/HeapSort/HeapSortTests/HeapSortUnitTests.cs +++ b/csharp-algorithms/HeapSort/HeapSortTests/HeapSortUnitTests.cs @@ -1,57 +1,93 @@ using HeapSort; -namespace HeapSortTests +namespace HeapSortTests; + +[TestClass] +public class HeapSortUnitTests { - [TestClass] - public class HeapSortUnitTests + [TestMethod] + public void GivenUnsortedArray_WhenArrayIsNotEmpty_ThenReturnSortedArray() { - [TestMethod] - public void GivenUnsortedArray_WhenArrayIsNotEmpty_ThenReturnSortedArray() - { - var array = new int[] { 73, 57, 49, 99, 133, 20, 1 }; - var expected = new int[] { 1, 20, 49, 57, 73, 99, 133 }; - var sortFunction = new HeapSortMethods(); + var array = new int[] { 73, 57, 49, 99, 133, 20, 1 }; + var expected = new int[] { 1, 20, 49, 57, 73, 99, 133 }; + var sortFunction = new HeapSortMethods(); - var sortedArray = sortFunction.SortArray(array, array.Length, string.Empty); + var sortedArray = sortFunction.SortArray(array, array.Length); - Assert.IsNotNull(sortedArray); - CollectionAssert.AreEqual(sortedArray, expected); - } + Assert.IsNotNull(sortedArray); + CollectionAssert.AreEqual(expected, sortedArray); + } - [TestMethod] - public void GivenUnsortedArray_WhenArrayIsRandomized_ThenCheckInstanceType() - { - var sortFunction = new HeapSortMethods(); - var array = HeapSortMethods.CreateRandomArray(200, 0, 200); + [TestMethod] + public void GivenArticleExample_WhenSorted_ThenNegativeValueComesFirst() + { + var array = new int[] { 12, 2, 24, 51, 8, -5 }; + var expected = new int[] { -5, 2, 8, 12, 24, 51 }; + var sortFunction = new HeapSortMethods(); - var sortedArray = sortFunction.SortArray(array, array.Length, string.Empty); + var sortedArray = sortFunction.SortArray(array, array.Length); - Assert.IsInstanceOfType(sortedArray, typeof(int[])); - } + CollectionAssert.AreEqual(expected, sortedArray); + } - [TestMethod] - public void GivenUnsortedArray_WhenArrayIsSorted_ThenResultingArrayIsNotNull() - { - var sortFunction = new HeapSortMethods(); ; - var array = HeapSortMethods.CreateRandomArray(200, 0, 200); + [TestMethod] + public void GivenArray_WhenArrayHasSingleElement_ThenReturnSortedArray() + { + var array = new int[] { 73 }; + var expected = new int[] { 73 }; + var sortFunction = new HeapSortMethods(); - var sortedArray = sortFunction.SortArray(array, array.Length, string.Empty); + var sortedArray = sortFunction.SortArray(array, array.Length); - Assert.IsNotNull(sortedArray); - } + Assert.IsNotNull(sortedArray); + CollectionAssert.AreEqual(expected, sortedArray); + } - [TestMethod] - public void GivenArray_WhenArrayHasSingleElement_ThenReturnSortedArray() - { - var array = new int[] { 73 }; - var expected = new int[] { 73 }; - var sortFunction = new HeapSortMethods(); + [TestMethod] + public void GivenEmptyArray_WhenSorted_ThenReturnEmptyArray() + { + var array = Array.Empty(); + var sortFunction = new HeapSortMethods(); + + var sortedArray = sortFunction.SortArray(array, array.Length); - var sortedArray = sortFunction.SortArray(array, array.Length, string.Empty); + Assert.AreEqual(0, sortedArray.Length); + } + + [TestMethod] + public void GivenSortedArray_WhenSorted_ThenArrayStaysSorted() + { + var array = HeapSortMethods.CreateSortedArray(1_000); + var expected = HeapSortMethods.CreateSortedArray(1_000); + var sortFunction = new HeapSortMethods(); + + var sortedArray = sortFunction.SortArray(array, array.Length); + + CollectionAssert.AreEqual(expected, sortedArray); + } + + [TestMethod] + public void GivenIdenticalValues_WhenSorted_ThenValuesAreUnchanged() + { + var array = HeapSortMethods.CreateIdenticalArray(1_000, 1, 100); + var expected = (int[])array.Clone(); + var sortFunction = new HeapSortMethods(); + + var sortedArray = sortFunction.SortArray(array, array.Length); + + CollectionAssert.AreEqual(expected, sortedArray); + } + + [TestMethod] + public void GivenRandomArrayWithDuplicates_WhenSorted_ThenMatchesArraySort() + { + var array = HeapSortMethods.CreateRandomArray(5_000, 0, 500); + var expected = (int[])array.Clone(); + Array.Sort(expected); + var sortFunction = new HeapSortMethods(); - Assert.IsNotNull(sortedArray); - CollectionAssert.AreEqual(sortedArray, expected); - } + var sortedArray = sortFunction.SortArray(array, array.Length); + CollectionAssert.AreEqual(expected, sortedArray); } -} \ No newline at end of file +}