From 169bc1cc46abe3e9b68aba5289aa3e3e901098f0 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 8 Nov 2026 18:24:57 +0100 Subject: [PATCH] Bucket Sort: .NET 10, corrected bucket formula and bubble sort, copy-per-call benchmark Targets net10.0 with BenchmarkDotNet 0.15.8 and MSTest 4.5.2. SortArray now scales each value's distance from the minimum to the bucket count with long arithmetic and checks the base case first; BubbleSort compares neighbours; SortArrayOptimized uses a long range and a nullable node. The benchmark moves to BucketSortBenchmarks, copies its input per call and adds an Array.Sort baseline. New edge-case, full-range and random tests. --- .../BucketSort/BucketSort/BucketSort.csproj | 4 +- .../BucketSort/BucketSortBenchmarks.cs | 50 +++++ .../BucketSort/BucketSortMethods.cs | 207 ++++++++---------- .../BucketSort/BucketSort/Program.cs | 2 +- .../BucketSortTests/BucketSortTests.csproj | 15 +- .../BucketSortTests/BucketSortUnitTests.cs | 136 ++++++++---- 6 files changed, 254 insertions(+), 160 deletions(-) create mode 100644 csharp-algorithms/BucketSort/BucketSort/BucketSortBenchmarks.cs diff --git a/csharp-algorithms/BucketSort/BucketSort/BucketSort.csproj b/csharp-algorithms/BucketSort/BucketSort/BucketSort.csproj index 79e7148fd0..62be4f867f 100644 --- a/csharp-algorithms/BucketSort/BucketSort/BucketSort.csproj +++ b/csharp-algorithms/BucketSort/BucketSort/BucketSort.csproj @@ -2,13 +2,13 @@ Exe - net6.0 + net10.0 enable enable - + diff --git a/csharp-algorithms/BucketSort/BucketSort/BucketSortBenchmarks.cs b/csharp-algorithms/BucketSort/BucketSort/BucketSortBenchmarks.cs new file mode 100644 index 0000000000..084c99ee35 --- /dev/null +++ b/csharp-algorithms/BucketSort/BucketSort/BucketSortBenchmarks.cs @@ -0,0 +1,50 @@ +using BenchmarkDotNet.Attributes; + +namespace BucketSort; + +public class BucketSortBenchmarks +{ + private readonly BucketSortMethods _sorter = 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" + ? BucketSortMethods.CreateRandomArray(Size) + : BucketSortMethods.CreateSortedArray(Size); + _work = new int[Size]; + } + + [Benchmark] + public int[] SortArray() + { + _source.CopyTo(_work, 0); + + return _sorter.SortArray(_work); + } + + [Benchmark] + public int[] SortArrayOptimized() + { + _source.CopyTo(_work, 0); + + return _sorter.SortArrayOptimized(_work); + } + + [Benchmark(Baseline = true)] + public int[] ArraySort() + { + _source.CopyTo(_work, 0); + Array.Sort(_work); + + return _work; + } +} diff --git a/csharp-algorithms/BucketSort/BucketSort/BucketSortMethods.cs b/csharp-algorithms/BucketSort/BucketSort/BucketSortMethods.cs index 6a2f5431e3..b1947d3b3c 100644 --- a/csharp-algorithms/BucketSort/BucketSort/BucketSortMethods.cs +++ b/csharp-algorithms/BucketSort/BucketSort/BucketSortMethods.cs @@ -1,155 +1,142 @@ -using BenchmarkDotNet.Attributes; +namespace BucketSort; -namespace BucketSort +public class BucketSortMethods { - public class BucketSortMethods + public int[] SortArray(int[] array) { - public IEnumerable SampleArrays() + if (array.Length <= 1) { - yield return new object[] { CreateRandomArray(200), "Small Unsorted" }; - yield return new object[] { CreateRandomArray(2000), "Medium Unsorted" }; - yield return new object[] { CreateRandomArray(20000), "Large Unsorted" }; - yield return new object[] { CreateSortedArray(200), "Small Sorted" }; - yield return new object[] { CreateSortedArray(2000), "Medium Sorted" }; - yield return new object[] { CreateSortedArray(20000), "Large Sorted" }; + return array; } - [Benchmark] - [ArgumentsSource(nameof(SampleArrays))] - public int[] SortArray(int[] array, string arrayName) - { - List sortedList = new List(); - var minValue = array[0]; - var maxValue = array[0]; - - if (array == null || array.Length <= 1) - { - return array; - } + List sortedList = []; + var minValue = array[0]; + var maxValue = array[0]; - for (int i = 1; i < array.Length; i++) - { - if (array[i] > maxValue) - maxValue = array[i]; - if (array[i] < minValue) - minValue = array[i]; - } + for (int i = 1; i < array.Length; i++) + { + if (array[i] > maxValue) + maxValue = array[i]; + if (array[i] < minValue) + minValue = array[i]; + } - var numberOfBuckets = maxValue - minValue + 1; - List[] bucket = new List[numberOfBuckets]; + var numberOfBuckets = array.Length; + var range = (long)maxValue - minValue + 1; + List[] bucket = new List[numberOfBuckets]; - for (int i = 0; i < numberOfBuckets; i++) - { - bucket[i] = new List(); - } - - for (int i = 0; i < array.Length; i++) - { - var selectedBucket = (array[i] / numberOfBuckets); - bucket[selectedBucket].Add(array[i]); - } + for (int i = 0; i < numberOfBuckets; i++) + { + bucket[i] = []; + } - for (int i = 0; i < numberOfBuckets; i++) - { - int[] temp = BubbleSort(bucket[i]); - sortedList.AddRange(temp); - } + for (int i = 0; i < array.Length; i++) + { + var selectedBucket = (int)((array[i] - (long)minValue) * numberOfBuckets / range); + bucket[selectedBucket].Add(array[i]); + } - return sortedList.ToArray(); + for (int i = 0; i < numberOfBuckets; i++) + { + int[] temp = BubbleSort(bucket[i]); + sortedList.AddRange(temp); } - public static int[] BubbleSort(List listInput) + return sortedList.ToArray(); + } + + public static int[] BubbleSort(List listInput) + { + for (int i = 0; i < listInput.Count - 1; i++) { - for (int i = 0; i < listInput.Count; i++) + for (int j = 0; j < listInput.Count - i - 1; j++) { - for (int j = 0; j < listInput.Count; j++) + if (listInput[j] > listInput[j + 1]) { - if (listInput[i] < listInput[j]) - { - var temp = listInput[i]; - listInput[i] = listInput[j]; - listInput[j] = temp; - } + var temp = listInput[j]; + listInput[j] = listInput[j + 1]; + listInput[j + 1] = temp; } } - return listInput.ToArray(); } + return listInput.ToArray(); + } - [Benchmark] - [ArgumentsSource(nameof(SampleArrays))] - public int[] SortArrayOptimized(int[] array, string arrayName) + public int[] SortArrayOptimized(int[] array) + { + if (array.Length <= 1) { - if (array == null || array.Length <= 1) - { - return array; - } + return array; + } - int maxValue = array[0]; - int minValue = array[0]; + int maxValue = array[0]; + int minValue = array[0]; - for (int i = 1; i < array.Length; i++) + for (int i = 1; i < array.Length; i++) + { + if (array[i] > maxValue) { - if (array[i] > maxValue) - { - maxValue = array[i]; - } + maxValue = array[i]; + } - if (array[i] < minValue) - { - minValue = array[i]; - } + if (array[i] < minValue) + { + minValue = array[i]; } + } - LinkedList[] bucket = new LinkedList[maxValue - minValue + 1]; + var range = (long)maxValue - minValue + 1; + LinkedList[] bucket = new LinkedList[range]; - for (int i = 0; i < array.Length; i++) + for (int i = 0; i < array.Length; i++) + { + if (bucket[array[i] - minValue] == null) { - if (bucket[array[i] - minValue] == null) - { - bucket[array[i] - minValue] = new LinkedList(); - } - - bucket[array[i] - minValue].AddLast(array[i]); + bucket[array[i] - minValue] = new LinkedList(); } - var index = 0; - for (int i = 0; i < bucket.Length; i++) + bucket[array[i] - minValue].AddLast(array[i]); + } + + var index = 0; + + for (int i = 0; i < bucket.Length; i++) + { + if (bucket[i] != null) { - if (bucket[i] != null) + LinkedListNode? node = bucket[i].First; + + while (node != null) { - LinkedListNode node = bucket[i].First; - - while (node != null) - { - array[index] = node.Value; - node = node.Next; - index++; - } + array[index] = node.Value; + node = node.Next; + index++; } } - return array; } - public static int[] CreateRandomArray(int size) - { - var array = new int[size]; - var rand = new Random(); + return array; + } - for (int i = 0; i < size; i++) - array[i] = rand.Next(1, size); + public static int[] CreateRandomArray(int size) + { + var array = new int[size]; + var rand = new Random(); - return array; - } + for (int i = 0; i < size; i++) + array[i] = rand.Next(1, size); - public static int[] CreateSortedArray(int size) - { - var array = new int[size]; + return array; + } - for (int i = 0; i < size; i++) - array[i] = i; + public static int[] CreateSortedArray(int size) + { + var array = new int[size]; - return array; - } + for (int i = 0; i < size; i++) + array[i] = i; + + return array; } } diff --git a/csharp-algorithms/BucketSort/BucketSort/Program.cs b/csharp-algorithms/BucketSort/BucketSort/Program.cs index f391cb0d28..2d4e85098f 100644 --- a/csharp-algorithms/BucketSort/BucketSort/Program.cs +++ b/csharp-algorithms/BucketSort/BucketSort/Program.cs @@ -1,4 +1,4 @@ using BenchmarkDotNet.Running; using BucketSort; -var summary = BenchmarkRunner.Run(); +BenchmarkRunner.Run(args: args); diff --git a/csharp-algorithms/BucketSort/BucketSortTests/BucketSortTests.csproj b/csharp-algorithms/BucketSort/BucketSortTests/BucketSortTests.csproj index c6051695fe..e7c59ebfb8 100644 --- a/csharp-algorithms/BucketSort/BucketSortTests/BucketSortTests.csproj +++ b/csharp-algorithms/BucketSort/BucketSortTests/BucketSortTests.csproj @@ -1,17 +1,22 @@ - net6.0 + net10.0 + enable enable false - - - - + + + + + + + + diff --git a/csharp-algorithms/BucketSort/BucketSortTests/BucketSortUnitTests.cs b/csharp-algorithms/BucketSort/BucketSortTests/BucketSortUnitTests.cs index ac27e198eb..4bc0f32d46 100644 --- a/csharp-algorithms/BucketSort/BucketSortTests/BucketSortUnitTests.cs +++ b/csharp-algorithms/BucketSort/BucketSortTests/BucketSortUnitTests.cs @@ -1,58 +1,110 @@ using BucketSort; -using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace BucketSortTests +namespace BucketSortTests; + +[TestClass] +public class BucketSortUnitTests { - [TestClass] - public class BucketSortUnitTests + [TestMethod] + public void GivenUnsortedArray_WhenArrayIsNotEmpty_ThenReturnSortedArray() + { + var expected = new int[] { 1, 20, 49, 57, 73, 99, 133 }; + var sortFunction = new BucketSortMethods(); + + var sortNormal = sortFunction.SortArray([73, 57, 49, 99, 133, 20, 1]); + var sortOptimized = sortFunction.SortArrayOptimized([73, 57, 49, 99, 133, 20, 1]); + + CollectionAssert.AreEqual(expected, sortNormal); + CollectionAssert.AreEqual(expected, sortOptimized); + } + + [TestMethod] + public void GivenUnsortedArray_WhenSortedWithSortArray_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 BucketSortMethods(); + + var sortedArray = sortFunction.SortArray(array); + + Assert.IsNotNull(sortedArray); + CollectionAssert.AreEqual(expected, sortedArray); + } + + [TestMethod] + public void GivenUnsortedArray_WhenArrayIsRandomized_ThenCheckInstanceType() + { + var sortFunction = new BucketSortMethods(); + var array = BucketSortMethods.CreateRandomArray(200); + + var sortedArray = sortFunction.SortArrayOptimized(array); + + Assert.IsInstanceOfType(sortedArray, typeof(int[])); + } + + [TestMethod] + public void GivenUnsortedArray_WhenArrayIsSorted_ThenResultingArrayIsNotNull() { - [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 BucketSortMethods(); + var sortFunction = new BucketSortMethods(); + var array = BucketSortMethods.CreateRandomArray(200); - var sortOptimized = sortFunction.SortArrayOptimized(array, string.Empty); - var sortNormal = sortFunction.SortArray(array, string.Empty); + var sortedArray = sortFunction.SortArrayOptimized(array); - CollectionAssert.AreEqual(expected, sortOptimized); - CollectionAssert.AreEqual(sortNormal, expected); - } + Assert.IsNotNull(sortedArray); + } - [TestMethod] - public void GivenUnsortedArray_WhenArrayIsRandomized_ThenCheckInstanceType() - { - var sortFunction = new BucketSortMethods(); ; - var array = BucketSortMethods.CreateRandomArray(200); + [TestMethod] + public void GivenArray_WhenArrayHasSingleElement_ThenReturnSortedArray() + { + var array = new int[] { 73 }; + var expected = new int[] { 73 }; + var sortFunction = new BucketSortMethods(); - var sortedArray = sortFunction.SortArrayOptimized(array, string.Empty); + var sortedArray = sortFunction.SortArrayOptimized(array); - Assert.IsInstanceOfType(sortedArray, typeof(int[])); - } + Assert.IsNotNull(sortedArray); + CollectionAssert.AreEqual(expected, sortedArray); + } - [TestMethod] - public void GivenUnsortedArray_WhenArrayIsSorted_ThenResultingArrayIsNotNull() - { - var sortFunction = new BucketSortMethods(); - var array = BucketSortMethods.CreateRandomArray(200); + [TestMethod] + [DataRow(new int[0])] + [DataRow(new[] { 100, 101 })] + [DataRow(new[] { 1000, 1001, 1002 })] + [DataRow(new[] { -50, -10, 3, -50 })] + [DataRow(new[] { 5, 5, 5, 5 })] + [DataRow(new[] { 42, 6, 16, 4, 12, 24, 46, 17 })] + public void GivenEdgeCaseArray_WhenSorted_ThenBothVersionsMatchArraySort(int[] input) + { + var expected = (int[])input.Clone(); + Array.Sort(expected); + var sortFunction = new BucketSortMethods(); - var sortedArray = sortFunction.SortArrayOptimized(array, string.Empty); + CollectionAssert.AreEqual(expected, sortFunction.SortArray((int[])input.Clone())); + CollectionAssert.AreEqual(expected, sortFunction.SortArrayOptimized((int[])input.Clone())); + } - Assert.IsNotNull(sortedArray); - } + [TestMethod] + public void GivenFullIntRange_WhenSorted_ThenSortArrayWorksAndOptimizedThrows() + { + var sortFunction = new BucketSortMethods(); - [TestMethod] - public void GivenArray_WhenArrayHasSingleElement_ThenReturnSortedArray() - { - var array = new int[] { 73 }; - var expected = new int[] { 73 }; - var sortFunction = new BucketSortMethods(); + CollectionAssert.AreEqual( + new[] { int.MinValue, 0, int.MaxValue }, + sortFunction.SortArray([int.MaxValue, int.MinValue, 0])); + Assert.ThrowsExactly( + () => sortFunction.SortArrayOptimized([int.MaxValue, int.MinValue, 0])); + } - var sortedArray = sortFunction.SortArrayOptimized(array, string.Empty); + [TestMethod] + public void GivenRandomArray_WhenSortedWithBothVersions_ThenResultsMatchArraySort() + { + var random = new Random(42); + var input = Enumerable.Range(0, 1_000).Select(_ => random.Next(-500, 500)).ToArray(); + var expected = (int[])input.Clone(); + Array.Sort(expected); + var sortFunction = new BucketSortMethods(); - Assert.IsNotNull(sortedArray); - CollectionAssert.AreEqual(sortedArray, expected); - } + CollectionAssert.AreEqual(expected, sortFunction.SortArray((int[])input.Clone())); + CollectionAssert.AreEqual(expected, sortFunction.SortArrayOptimized((int[])input.Clone())); } -} \ No newline at end of file +}