diff --git a/src/libraries/System.Linq/src/System.Linq.csproj b/src/libraries/System.Linq/src/System.Linq.csproj index 9040f668ab6f1d..9ffa540d03baca 100644 --- a/src/libraries/System.Linq/src/System.Linq.csproj +++ b/src/libraries/System.Linq/src/System.Linq.csproj @@ -98,6 +98,7 @@ + diff --git a/src/libraries/System.Linq/src/System/Linq/Max.cs b/src/libraries/System.Linq/src/System/Linq/Max.cs index 2c05d2dc29dbbb..d4adcc65ec1460 100644 --- a/src/libraries/System.Linq/src/System/Linq/Max.cs +++ b/src/libraries/System.Linq/src/System/Linq/Max.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; +using System.Numerics; namespace System.Linq { @@ -15,6 +15,11 @@ public static int Max(this IEnumerable source) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (source.GetType() == typeof(int[])) + { + return Max((int[])source); + } + int value; using (IEnumerator e = source.GetEnumerator()) { @@ -37,6 +42,57 @@ public static int Max(this IEnumerable source) return value; } + private static int Max(int[] array) + { + if (array.Length == 0) + { + ThrowHelper.ThrowNoElementsException(); + } + + // Vectorize the search if possible. + int index, value; + if (Vector.IsHardwareAccelerated && array.Length >= Vector.Count * 2) + { + // The array is at least two vectors long. Create a vector from the first N elements, + // and then repeatedly compare that against the next vector from the array. At the end, + // the resulting vector will contain the maximum values found, and we then need only + // to find the max of those. + var maxes = new Vector(array); + index = Vector.Count; + do + { + maxes = Vector.Max(maxes, new Vector(array, index)); + index += Vector.Count; + } + while (index + Vector.Count <= array.Length); + + value = maxes[0]; + for (int i = 1; i < Vector.Count; i++) + { + if (maxes[i] > value) + { + value = maxes[i]; + } + } + } + else + { + value = array[0]; + index = 1; + } + + // Iterate through the remaining elements, comparing against the max. + for (int i = index; (uint)i < (uint)array.Length; i++) + { + if (array[i] > value) + { + value = array[i]; + } + } + + return value; + } + public static int? Max(this IEnumerable source) { if (source == null) @@ -106,6 +162,11 @@ public static long Max(this IEnumerable source) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (source.GetType() == typeof(long[])) + { + return Max((long[])source); + } + long value; using (IEnumerator e = source.GetEnumerator()) { @@ -128,6 +189,58 @@ public static long Max(this IEnumerable source) return value; } + private static long Max(long[] array) + { + if (array.Length == 0) + { + ThrowHelper.ThrowNoElementsException(); + } + + // Vectorize the search if possible. + int index; + long value; + if (Vector.IsHardwareAccelerated && array.Length >= Vector.Count * 2) + { + // The array is at least two vectors long. Create a vector from the first N elements, + // and then repeatedly compare that against the next vector from the array. At the end, + // the resulting vector will contain the maximum values found, and we then need only + // to find the max of those. + var maxes = new Vector(array); + index = Vector.Count; + do + { + maxes = Vector.Max(maxes, new Vector(array, index)); + index += Vector.Count; + } + while (index + Vector.Count <= array.Length); + + value = maxes[0]; + for (int i = 1; i < Vector.Count; i++) + { + if (maxes[i] > value) + { + value = maxes[i]; + } + } + } + else + { + value = array[0]; + index = 1; + } + + // Iterate through the remaining elements, comparing against the max. + for (int i = index; (uint)i < (uint)array.Length; i++) + { + if (array[i] > value) + { + value = array[i]; + } + } + + return value; + } + public static long? Max(this IEnumerable source) { if (source == null) diff --git a/src/libraries/System.Linq/src/System/Linq/Min.cs b/src/libraries/System.Linq/src/System/Linq/Min.cs index 9f9266ff1bb83b..fc2e271f420ae1 100644 --- a/src/libraries/System.Linq/src/System/Linq/Min.cs +++ b/src/libraries/System.Linq/src/System/Linq/Min.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; +using System.Numerics; namespace System.Linq { @@ -15,6 +15,11 @@ public static int Min(this IEnumerable source) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (source.GetType() == typeof(int[])) + { + return Min((int[])source); + } + int value; using (IEnumerator e = source.GetEnumerator()) { @@ -37,6 +42,57 @@ public static int Min(this IEnumerable source) return value; } + private static int Min(int[] array) + { + if (array.Length == 0) + { + ThrowHelper.ThrowNoElementsException(); + } + + // Vectorize the search if possible. + int index, value; + if (Vector.IsHardwareAccelerated && array.Length >= Vector.Count * 2) + { + // The array is at least two vectors long. Create a vector from the first N elements, + // and then repeatedly compare that against the next vector from the array. At the end, + // the resulting vector will contain the minimum values found, and we then need only + // to find the min of those. + var mins = new Vector(array); + index = Vector.Count; + do + { + mins = Vector.Min(mins, new Vector(array, index)); + index += Vector.Count; + } + while (index + Vector.Count <= array.Length); + + value = mins[0]; + for (int i = 1; i < Vector.Count; i++) + { + if (mins[i] < value) + { + value = mins[i]; + } + } + } + else + { + value = array[0]; + index = 1; + } + + // Iterate through the remaining elements, comparing against the min. + for (int i = index; (uint)i < (uint)array.Length; i++) + { + if (array[i] < value) + { + value = array[i]; + } + } + + return value; + } + public static int? Min(this IEnumerable source) { if (source == null) @@ -88,6 +144,11 @@ public static long Min(this IEnumerable source) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + if (source.GetType() == typeof(long[])) + { + return Min((long[])source); + } + long value; using (IEnumerator e = source.GetEnumerator()) { @@ -110,6 +171,58 @@ public static long Min(this IEnumerable source) return value; } + private static long Min(long[] array) + { + if (array.Length == 0) + { + ThrowHelper.ThrowNoElementsException(); + } + + // Vectorize the search if possible. + int index; + long value; + if (Vector.IsHardwareAccelerated && array.Length >= Vector.Count * 2) + { + // The array is at least two vectors long. Create a vector from the first N elements, + // and then repeatedly compare that against the next vector from the array. At the end, + // the resulting vector will contain the minimum values found, and we then need only + // to find the min of those. + var mins = new Vector(array); + index = Vector.Count; + do + { + mins = Vector.Min(mins, new Vector(array, index)); + index += Vector.Count; + } + while (index + Vector.Count <= array.Length); + + value = mins[0]; + for (int i = 1; i < Vector.Count; i++) + { + if (mins[i] < value) + { + value = mins[i]; + } + } + } + else + { + value = array[0]; + index = 1; + } + + // Iterate through the remaining elements, comparing against the min. + for (int i = index; (uint)i < (uint)array.Length; i++) + { + if (array[i] < value) + { + value = array[i]; + } + } + + return value; + } + public static long? Min(this IEnumerable source) { if (source == null) diff --git a/src/libraries/System.Linq/tests/MaxTests.cs b/src/libraries/System.Linq/tests/MaxTests.cs index 78318b64b1194e..27313826b0df1d 100644 --- a/src/libraries/System.Linq/tests/MaxTests.cs +++ b/src/libraries/System.Linq/tests/MaxTests.cs @@ -40,6 +40,8 @@ public void Max_Int_EmptySource_ThrowsInvalidOpertionException() { Assert.Throws(() => Enumerable.Empty().Max()); Assert.Throws(() => Enumerable.Empty().Max(x => x)); + Assert.Throws(() => Array.Empty().Max()); + Assert.Throws(() => new List().Max()); } public static IEnumerable Max_Int_TestData() @@ -55,6 +57,12 @@ public static IEnumerable Max_Int_TestData() yield return new object[] { new int[] { 16, 9, 10, 7, 8 }, 16 }; yield return new object[] { new int[] { 6, 9, 10, 0, 50 }, 50 }; yield return new object[] { new int[] { -6, 0, -9, 0, -10, 0 }, 0 }; + + for (int length = 2; length < 33; length++) + { + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length)), length + length - 1 }; + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).ToArray()), length + length - 1 }; + } } [Theory] @@ -78,6 +86,12 @@ public static IEnumerable Max_Long_TestData() yield return new object[] { new long[] { 250, 49, 130, 47, 28 }, 250L }; yield return new object[] { new long[] { 6, 9, 10, 0, int.MaxValue + 50L }, int.MaxValue + 50L }; yield return new object[] { new long[] { 6, 50, 9, 50, 10, 50 }, 50L }; + + for (int length = 2; length < 33; length++) + { + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (long)i)), (long)(length + length - 1) }; + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (long)i).ToArray()), (long)(length + length - 1) }; + } } [Theory] @@ -100,6 +114,8 @@ public void Max_Long_EmptySource_ThrowsInvalidOpertionException() { Assert.Throws(() => Enumerable.Empty().Max()); Assert.Throws(() => Enumerable.Empty().Max(x => x)); + Assert.Throws(() => Array.Empty().Max()); + Assert.Throws(() => new List().Max()); } public static IEnumerable Max_Float_TestData() diff --git a/src/libraries/System.Linq/tests/MinTests.cs b/src/libraries/System.Linq/tests/MinTests.cs index 31296da12c6918..8a8069b2f68013 100644 --- a/src/libraries/System.Linq/tests/MinTests.cs +++ b/src/libraries/System.Linq/tests/MinTests.cs @@ -42,6 +42,12 @@ public static IEnumerable Min_Int_TestData() yield return new object[] { new int[] { 6, 9, 10, 7, 8 }, 6 }; yield return new object[] { new int[] { 6, 9, 10, 0, -5 }, -5 }; yield return new object[] { new int[] { 6, 0, 9, 0, 10, 0 }, 0 }; + + for (int length = 2; length < 33; length++) + { + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length)), length }; + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).ToArray()), length }; + } } [Theory] @@ -64,6 +70,8 @@ public void Min_Int_EmptySource_ThrowsInvalidOperationException() { Assert.Throws(() => Enumerable.Empty().Min()); Assert.Throws(() => Enumerable.Empty().Min(x => x)); + Assert.Throws(() => Array.Empty().Min()); + Assert.Throws(() => new List().Min()); } public static IEnumerable Min_Long_TestData() @@ -79,6 +87,12 @@ public static IEnumerable Min_Long_TestData() yield return new object[] { new long[] { -250, 49, 130, 47, 28 }, -250L }; yield return new object[] { new long[] { 6, 9, 10, 0, -int.MaxValue - 50L }, -int.MaxValue - 50L }; yield return new object[] { new long[] { 6, -5, 9, -5, 10, -5 }, -5 }; + + for (int length = 2; length < 33; length++) + { + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (long)i)), (long)length }; + yield return new object[] { Shuffler.Shuffle(Enumerable.Range(length, length).Select(i => (long)i).ToArray()), (long)length }; + } } [Theory] @@ -101,6 +115,8 @@ public void Min_Long_EmptySource_ThrowsInvalidOperationException() { Assert.Throws(() => Enumerable.Empty().Min()); Assert.Throws(() => Enumerable.Empty().Min(x => x)); + Assert.Throws(() => Array.Empty().Min()); + Assert.Throws(() => new List().Min()); } public static IEnumerable Min_Float_TestData() @@ -160,6 +176,8 @@ public void Min_Float_EmptySource_ThrowsInvalidOperationException() { Assert.Throws(() => Enumerable.Empty().Min()); Assert.Throws(() => Enumerable.Empty().Min(x => x)); + Assert.Throws(() => Array.Empty().Min()); + Assert.Throws(() => new List().Min()); } public static IEnumerable Min_Double_TestData() @@ -218,6 +236,8 @@ public void Min_Double_EmptySource_ThrowsInvalidOperationException() { Assert.Throws(() => Enumerable.Empty().Min()); Assert.Throws(() => Enumerable.Empty().Min(x => x)); + Assert.Throws(() => Array.Empty().Min()); + Assert.Throws(() => new List().Min()); } public static IEnumerable Min_Decimal_TestData() @@ -248,6 +268,8 @@ public void Min_Decimal_EmptySource_ThrowsInvalidOperationException() { Assert.Throws(() => Enumerable.Empty().Min()); Assert.Throws(() => Enumerable.Empty().Min(x => x)); + Assert.Throws(() => Array.Empty().Min()); + Assert.Throws(() => new List().Min()); } [Fact] @@ -486,6 +508,8 @@ public void Min_DateTime_EmptySource_ThrowsInvalidOperationException() { Assert.Throws(() => Enumerable.Empty().Min()); Assert.Throws(() => Enumerable.Empty().Min(x => x)); + Assert.Throws(() => Array.Empty().Min()); + Assert.Throws(() => new List().Min()); } public static IEnumerable Min_String_TestData() diff --git a/src/libraries/System.Linq/tests/Shuffler.cs b/src/libraries/System.Linq/tests/Shuffler.cs index dd1dcf3a7459e6..3a8f5289a54d45 100644 --- a/src/libraries/System.Linq/tests/Shuffler.cs +++ b/src/libraries/System.Linq/tests/Shuffler.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections; using System.Collections.Generic; @@ -9,6 +8,17 @@ namespace System.Linq.Tests { public static class Shuffler { + public static T[] Shuffle(T[] array) + { + int i = array.Length; + while (i > 1) + { + int j = Random.Shared.Next(i--); + (array[i], array[j]) = (array[j], array[i]); + } + return array; + } + public static IEnumerable Shuffle(this IEnumerable source, int seed) { return new ShuffledEnumerable(source, seed);