From c8f895de0e92d79a8117a2543418abff3e4079ee Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 8 Nov 2026 18:24:54 +0100 Subject: [PATCH] Merge Sort: upgrade to .NET 10, copy-per-call benchmark, align repo with article Target net10.0 with BenchmarkDotNet 0.15.8, MSTest 4.5.2, Microsoft.NET.Test.Sdk 18.11.0 and coverlet.collector 10.1.0. Move the benchmark into MergeBenchmarks, which copies its input before every call so each invocation sorts unsorted data, and add MemoryDiagnoser. Drop the benchmark-only arrayName parameter so SortArray matches the article. Fix the CollectionAssert argument order and add oracle and edge-case tests. --- .../MergeSort/MergeSort/Merge.cs | 170 ++++++++---------- .../MergeSort/MergeSort/MergeBenchmarks.cs | 34 ++++ .../MergeSort/MergeSort/MergeSort.csproj | 4 +- .../MergeSort/MergeSort/Program.cs | 2 +- .../MergeSortTest/MergeSortTest.csproj | 15 +- .../MergeSort/MergeSortTest/MergeUnitTests.cs | 90 ++++++---- 6 files changed, 183 insertions(+), 132 deletions(-) create mode 100644 csharp-algorithms/MergeSort/MergeSort/MergeBenchmarks.cs diff --git a/csharp-algorithms/MergeSort/MergeSort/Merge.cs b/csharp-algorithms/MergeSort/MergeSort/Merge.cs index 8840af5895..4c93066a9a 100644 --- a/csharp-algorithms/MergeSort/MergeSort/Merge.cs +++ b/csharp-algorithms/MergeSort/MergeSort/Merge.cs @@ -1,97 +1,81 @@ -using BenchmarkDotNet.Attributes; +namespace MergeSort; -namespace MergeSort +public class Merge { - public class Merge + public int[] SortArray(int[] array, int left, int right) { - public IEnumerable ArrayData() - { - yield return new object[] { GenerateRandomNumber(200), 0, 199, "Small Unsorted" }; - yield return new object[] { GenerateRandomNumber(2000), 0, 1999, "Medium Unsorted" }; - yield return new object[] { GenerateRandomNumber(20000), 0, 19999, "Large Unsorted" }; - yield return new object[] { GenerateSortedNumber(200), 0, 199,"Small Sorted" }; - yield return new object[] { GenerateSortedNumber(2000), 0, 1999, "Medium Sorted" }; - yield return new object[] { GenerateSortedNumber(20000), 0, 19999, "Large Sorted" }; - } - - public void MergeArray(int[] array, int left, int middle, int right) - { - var leftArrayLength = middle - left + 1; - var rightArrayLength = right - middle; - var leftTempArray = new int[leftArrayLength]; - var rightTempArray = new int[rightArrayLength]; - int i, j; - - for (i = 0; i < leftArrayLength; ++i) - leftTempArray[i] = array[left + i]; - for (j = 0; j < rightArrayLength; ++j) - rightTempArray[j] = array[middle + 1 + j]; - - i = 0; - j = 0; - int k = left; - - while (i < leftArrayLength && j < rightArrayLength) - { - if (leftTempArray[i] <= rightTempArray[j]) - { - array[k++] = leftTempArray[i++]; - } - else - { - array[k++] = rightTempArray[j++]; - } - } - - while (i < leftArrayLength) - { - array[k++] = leftTempArray[i++]; - } - - while (j < rightArrayLength) - { - array[k++] = rightTempArray[j++]; - } - } - - [ArgumentsSource(nameof(ArrayData))] - [Benchmark] - public int[] SortArray(int[] array, int left, int right, string arrayName) - { - if (left < right) - { - int middle = left + (right - left) / 2; - - SortArray(array, left, middle, arrayName); - SortArray(array, middle + 1, right, arrayName); - - MergeArray(array, left, middle, right); - } - - return array; - } - - public static int[] GenerateRandomNumber(int size) - { - var array = new int[size]; - var rand = new Random(); - var maxNum = 10000; - - for (int i = 0; i < size; i++) - array[i] = rand.Next(maxNum + 1); - - return array; - } - - public static int[] GenerateSortedNumber(int size) - { - var array = new int[size]; - - for (int i = 0; i < size; i++) - array[i] = i; - - return array; - } - } -} + if (left < right) + { + int middle = left + (right - left) / 2; + + SortArray(array, left, middle); + SortArray(array, middle + 1, right); + + MergeArray(array, left, middle, right); + } + + return array; + } + + public void MergeArray(int[] array, int left, int middle, int right) + { + var leftArrayLength = middle - left + 1; + var rightArrayLength = right - middle; + var leftTempArray = new int[leftArrayLength]; + var rightTempArray = new int[rightArrayLength]; + int i, j; + + for (i = 0; i < leftArrayLength; ++i) + leftTempArray[i] = array[left + i]; + for (j = 0; j < rightArrayLength; ++j) + rightTempArray[j] = array[middle + 1 + j]; + + i = 0; + j = 0; + int k = left; + + while (i < leftArrayLength && j < rightArrayLength) + { + if (leftTempArray[i] <= rightTempArray[j]) + { + array[k++] = leftTempArray[i++]; + } + else + { + array[k++] = rightTempArray[j++]; + } + } + + while (i < leftArrayLength) + { + array[k++] = leftTempArray[i++]; + } + + while (j < rightArrayLength) + { + array[k++] = rightTempArray[j++]; + } + } + + public static int[] GenerateRandomNumber(int size) + { + var array = new int[size]; + var rand = new Random(); + var maxNum = 10000; + for (int i = 0; i < size; i++) + array[i] = rand.Next(maxNum + 1); + + return array; + } + + public static int[] GenerateSortedNumber(int size) + { + var array = new int[size]; + + for (int i = 0; i < size; i++) + array[i] = i; + + return array; + } +} diff --git a/csharp-algorithms/MergeSort/MergeSort/MergeBenchmarks.cs b/csharp-algorithms/MergeSort/MergeSort/MergeBenchmarks.cs new file mode 100644 index 0000000000..531cc82f28 --- /dev/null +++ b/csharp-algorithms/MergeSort/MergeSort/MergeBenchmarks.cs @@ -0,0 +1,34 @@ +using BenchmarkDotNet.Attributes; + +namespace MergeSort; + +[MemoryDiagnoser] +public class MergeBenchmarks +{ + private readonly Merge _merge = new(); + private int[] _source = []; + private int[] _work = []; + + [Params(200, 2_000, 20_000)] + public int Size { get; set; } + + [Params("Random", "Sorted")] + public string Input { get; set; } = "Random"; + + [GlobalSetup] + public void Setup() + { + _source = Input == "Random" + ? Merge.GenerateRandomNumber(Size) + : Merge.GenerateSortedNumber(Size); + _work = new int[Size]; + } + + [Benchmark] + public int[] SortArray() + { + _source.CopyTo(_work, 0); + + return _merge.SortArray(_work, 0, _work.Length - 1); + } +} diff --git a/csharp-algorithms/MergeSort/MergeSort/MergeSort.csproj b/csharp-algorithms/MergeSort/MergeSort/MergeSort.csproj index 79e7148fd0..62be4f867f 100644 --- a/csharp-algorithms/MergeSort/MergeSort/MergeSort.csproj +++ b/csharp-algorithms/MergeSort/MergeSort/MergeSort.csproj @@ -2,13 +2,13 @@ Exe - net6.0 + net10.0 enable enable - + diff --git a/csharp-algorithms/MergeSort/MergeSort/Program.cs b/csharp-algorithms/MergeSort/MergeSort/Program.cs index 1376e8ed53..7812d3657d 100644 --- a/csharp-algorithms/MergeSort/MergeSort/Program.cs +++ b/csharp-algorithms/MergeSort/MergeSort/Program.cs @@ -1,4 +1,4 @@ using BenchmarkDotNet.Running; using MergeSort; -var summary = BenchmarkRunner.Run(); \ No newline at end of file +BenchmarkRunner.Run(args: args); diff --git a/csharp-algorithms/MergeSort/MergeSortTest/MergeSortTest.csproj b/csharp-algorithms/MergeSort/MergeSortTest/MergeSortTest.csproj index 9a378fd9a3..ebc67b0f1a 100644 --- a/csharp-algorithms/MergeSort/MergeSortTest/MergeSortTest.csproj +++ b/csharp-algorithms/MergeSort/MergeSortTest/MergeSortTest.csproj @@ -1,17 +1,22 @@ - net6.0 + net10.0 + enable enable false - - - - + + + + + + + + diff --git a/csharp-algorithms/MergeSort/MergeSortTest/MergeUnitTests.cs b/csharp-algorithms/MergeSort/MergeSortTest/MergeUnitTests.cs index 7267ef6e92..bec5483748 100644 --- a/csharp-algorithms/MergeSort/MergeSortTest/MergeUnitTests.cs +++ b/csharp-algorithms/MergeSort/MergeSortTest/MergeUnitTests.cs @@ -1,44 +1,72 @@ using MergeSort; -using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace MergeSortTest +namespace MergeSortTest; + +[TestClass] +public class MergeUnitTests { - [TestClass] - public class MergeUnitTests + [TestMethod] + public void GivenUnsortedArray_ThenReturnSortedArray() { - [TestMethod] - public void GivenUnsortedArray_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 Merge(); + 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 Merge(); + + var sortedArray = sortFunction.SortArray(array, 0, array.Length - 1); + + Assert.IsNotNull(sortedArray); + CollectionAssert.AreEqual(expected, sortedArray); + } - var sortedArray = sortFunction.SortArray(array, 0, array.Length - 1, string.Empty); + [TestMethod] + public void GivenUnsortedArray_CheckInstanceType() + { + var sortFunction = new Merge(); + var array = Merge.GenerateRandomNumber(200); - Assert.IsNotNull(sortedArray); - CollectionAssert.AreEqual(sortedArray, expected); - } + var sortedArray = sortFunction.SortArray(array, 0, array.Length - 1); - [TestMethod] - public void GivenUnsortedArray_CheckInstanceType() - { - var sortFunction = new Merge(); - var array = Merge.GenerateRandomNumber(200); + Assert.IsInstanceOfType(sortedArray, typeof(int[])); + } - var sortedArray = sortFunction.SortArray(array, 0, array.Length-1, string.Empty); + [TestMethod] + public void GivenUnsortedArray_CheckSortedNotNull() + { + var sortFunction = new Merge(); + var array = Merge.GenerateRandomNumber(200); - Assert.IsInstanceOfType(sortedArray, typeof(int[])); - } + var sortedArray = sortFunction.SortArray(array, 0, array.Length - 1); - [TestMethod] - public void GivenUnsortedArray_CheckSortedNotNull() - { - var sortFunction = new Merge(); - var array = Merge.GenerateRandomNumber(200); + Assert.IsNotNull(sortedArray); + } + + [TestMethod] + public void GivenRandomArray_WhenSorted_ThenMatchesArraySort() + { + var sortFunction = new Merge(); + var array = Merge.GenerateRandomNumber(1_000); + var expected = (int[])array.Clone(); + Array.Sort(expected); + + var sortedArray = sortFunction.SortArray(array, 0, array.Length - 1); + + CollectionAssert.AreEqual(expected, sortedArray); + } + + [TestMethod] + [DataRow(new int[] { })] + [DataRow(new int[] { 42 })] + [DataRow(new int[] { 5, 3, 5, 1, 3, 5 })] + [DataRow(new int[] { 1, 2, 3, 4, 5, 6 })] + [DataRow(new int[] { 6, 5, 4, 3, 2, 1 })] + public void GivenEdgeCaseArray_WhenSorted_ThenMatchesArraySort(int[] array) + { + var sortFunction = new Merge(); + var expected = (int[])array.Clone(); + Array.Sort(expected); - var sortedArray = sortFunction.SortArray(array, 0, array.Length-1, string.Empty); + var sortedArray = sortFunction.SortArray(array, 0, array.Length - 1); - Assert.IsNotNull(sortedArray); - } + CollectionAssert.AreEqual(expected, sortedArray); } -} \ No newline at end of file +}