From 3b2da7704436f24f0239b64984e04e4122cd09aa Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Wed, 10 Jul 2024 15:25:44 -0700 Subject: [PATCH 1/8] Add ImmutableArray extension methods for ordering This change introduces four sets of extension methods with overloads: - OrderAsArray(...) - OrderDescendingAsArray(...) - OrderByAsArray(...) - OrderByDescendingAsArray(...) Each of these operates on an `ImmutableArray` and returns an `ImmutableArray`. --- .../ImmutableArrayExtensionsTests.cs | 98 ++++++++++++++ .../ImmutableArrayExtensions.cs | 123 ++++++++++++++++++ 2 files changed, 221 insertions(+) diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs index c8a436095c4..5dd1f43216a 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs @@ -36,4 +36,102 @@ public void GetMostRecentUniqueItems() }, s => Assert.Equal("WoRlD", s)); } + + public static TheoryData, ImmutableArray> OrderAsArrayData + { + get + { + return new() + { + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + }; + } + } + + [Theory] + [MemberData(nameof(OrderAsArrayData))] + public void OrderAsArray(ImmutableArray data, ImmutableArray expected) + { + var sorted = data.OrderAsArray(); + Assert.Equal(expected, sorted); + } + + public static TheoryData, ImmutableArray> OrderDescendingAsArrayData + { + get + { + return new() + { + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + }; + } + } + + [Theory] + [MemberData(nameof(OrderAsArrayData))] + public void OrderDescendingAsArray(ImmutableArray data, ImmutableArray expected) + { + var sorted = data.OrderAsArray(); + Assert.Equal(expected, sorted); + } + + public readonly record struct ValueHolder(int Value) + { + public static implicit operator ValueHolder(int value) + => new(value); + } + + public static TheoryData, ImmutableArray> OrderByAsArrayData + { + get + { + return new() + { + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + }; + } + } + + [Theory] + [MemberData(nameof(OrderByAsArrayData))] + public void OrderByAsArray(ImmutableArray data, ImmutableArray expected) + { + var sorted = data.OrderByAsArray(static x => x.Value); + Assert.Equal(expected, sorted); + } + + public static TheoryData, ImmutableArray> OrderByDescendingAsArrayData + { + get + { + return new() + { + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + }; + } + } + + [Theory] + [MemberData(nameof(OrderByDescendingAsArrayData))] + public void OrderByDescendingAsArray(ImmutableArray data, ImmutableArray expected) + { + var sorted = data.OrderByDescendingAsArray(static x => x.Value); + Assert.Equal(expected, sorted); + } } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs index 54ab5702f27..9bb9798a015 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs @@ -1,7 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System.Buffers; using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.PooledObjects; namespace System.Collections.Immutable; @@ -205,4 +209,123 @@ public static int BinarySearchBy(this ImmutableArray array, TArg arg return ~min; } + + public static ImmutableArray OrderAsArray(this ImmutableArray array) + => array.OrderAsArrayCore(GetComparer(comparer: null, descending: false)); + + public static ImmutableArray OrderAsArray(this ImmutableArray array, IComparer comparer) + => array.OrderAsArrayCore(GetComparer(comparer, descending: false)); + + public static ImmutableArray OrderAsArray(this ImmutableArray array, Comparison comparison) + => array.OrderAsArrayCore(GetComparer(comparison, descending: false)); + + public static ImmutableArray OrderDescendingAsArray(this ImmutableArray array) + => array.OrderAsArrayCore(GetComparer(comparer: null, descending: true)); + + public static ImmutableArray OrderDescendingAsArray(this ImmutableArray array, IComparer comparer) + => array.OrderAsArrayCore(GetComparer(comparer, descending: true)); + + public static ImmutableArray OrderDescendingAsArray(this ImmutableArray array, Comparison comparison) + => array.OrderAsArrayCore(GetComparer(comparison, descending: true)); + + public static ImmutableArray OrderByAsArray( + this ImmutableArray array, Func keySelector) + => array.OrderByAsArrayCore(keySelector, GetComparer(comparer: null, descending: false)); + + public static ImmutableArray OrderByAsArray( + this ImmutableArray array, Func keySelector, IComparer comparer) + => array.OrderByAsArrayCore(keySelector, GetComparer(comparer, descending: false)); + + public static ImmutableArray OrderByAsArray( + this ImmutableArray array, Func keySelector, Comparison comparison) + => array.OrderByAsArrayCore(keySelector, GetComparer(comparison, descending: false)); + + public static ImmutableArray OrderByDescendingAsArray( + this ImmutableArray array, Func keySelector) + => array.OrderByAsArrayCore(keySelector, GetComparer(comparer: null, descending: true)); + + public static ImmutableArray OrderByDescendingAsArray( + this ImmutableArray array, Func keySelector, IComparer comparer) + => array.OrderByAsArrayCore(keySelector, GetComparer(comparer, descending: true)); + + public static ImmutableArray OrderByDescendingAsArray( + this ImmutableArray array, Func keySelector, Comparison comparison) + => array.OrderByAsArrayCore(keySelector, GetComparer(comparison, descending: true)); + + private static IComparer GetComparer(IComparer? comparer, bool descending) + { + if (comparer is null) + { + return descending + ? DescendingComparer.Default + : Comparer.Default; + } + + return descending + ? new DescendingComparer(comparer) + : comparer; + } + + private static IComparer GetComparer(Comparison comparison, bool descending) + { + var comparer = Comparer.Create(comparison); + + return descending + ? new DescendingComparer(comparer) + : comparer; + } + + private sealed class DescendingComparer(IComparer comparer) : IComparer + { + private static IComparer? s_default; + + public static IComparer Default => s_default ??= new DescendingComparer(Comparer.Default); + + public int Compare(T? x, T? y) + => comparer.Compare(y!, x!); + } + + private static ImmutableArray OrderAsArrayCore(this ImmutableArray array, IComparer comparer) + { + if (array.IsEmpty) + { + return array; + } + + var span = array.AsSpan(); + + var length = span.Length; + var items = new T[length]; + span.CopyTo(items); + + Array.Sort(items, comparer); + + return ImmutableCollectionsMarshal.AsImmutableArray(items); + } + + private static ImmutableArray OrderByAsArrayCore( + this ImmutableArray array, Func keySelector, IComparer comparer) + { + if (array.IsEmpty) + { + return array; + } + + var span = array.AsSpan(); + + var length = span.Length; + var items = new TElement[length]; + span.CopyTo(items); + + using var _ = ArrayPool.Shared.GetPooledArray(minimumLength: length, out var keys); + + for (var i = 0; i < length; i++) + { + keys[i] = keySelector(items[i]); + } + + Array.Sort(keys, items, 0, length, comparer); + + return ImmutableCollectionsMarshal.AsImmutableArray(items); + } } From 91113cc9aba6bb112a94d6193829904c4490b146 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 11 Jul 2024 09:08:57 -0700 Subject: [PATCH 2/8] Remove unused usings --- .../ImmutableArrayExtensions.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs index 9bb9798a015..c750023359c 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs @@ -4,8 +4,6 @@ using System.Buffers; using System.Collections.Generic; using System.Runtime.InteropServices; -using System.Threading; -using Microsoft.AspNetCore.Razor; using Microsoft.AspNetCore.Razor.PooledObjects; namespace System.Collections.Immutable; From e4cfab9f53586ea20949e3d296600a6873225941 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 11 Jul 2024 09:10:52 -0700 Subject: [PATCH 3/8] Don't bother ordering ImmutableArrays of length 1 --- .../ImmutableArrayExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs index c750023359c..93407acd335 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs @@ -285,7 +285,7 @@ public int Compare(T? x, T? y) private static ImmutableArray OrderAsArrayCore(this ImmutableArray array, IComparer comparer) { - if (array.IsEmpty) + if (array.IsEmpty || array.Length == 1) { return array; } @@ -304,7 +304,7 @@ private static ImmutableArray OrderAsArrayCore(this ImmutableArray arra private static ImmutableArray OrderByAsArrayCore( this ImmutableArray array, Func keySelector, IComparer comparer) { - if (array.IsEmpty) + if (array.IsEmpty || array.Length == 1) { return array; } From 4f19d2fea2a86e1a6c4be312261a84ee10e90bde Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 11 Jul 2024 11:00:05 -0700 Subject: [PATCH 4/8] Improve perf of ImmutableArray Order* extension methods 1. Don't sort if the array has zero or 1 element. 2. Don't sort if the array or the keys are already ordered. 3. Avoid creating an `IComparer` until its time to actually sort Add tests for Comparison overloads --- .../ImmutableArrayExtensionsTests.cs | 156 ++++++--- .../ImmutableArrayExtensions.cs | 330 +++++++++++++++--- 2 files changed, 380 insertions(+), 106 deletions(-) diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs index 5dd1f43216a..398fcbfae46 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs @@ -37,20 +37,23 @@ public void GetMostRecentUniqueItems() s => Assert.Equal("WoRlD", s)); } + private static Comparison OddBeforeEven + => (x, y) => (x % 2 != 0, y % 2 != 0) switch + { + (true, false) => -1, + (false, true) => 1, + _ => x.CompareTo(y) + }; + public static TheoryData, ImmutableArray> OrderAsArrayData - { - get + => new() { - return new() - { - { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, - { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, - { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, - { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, - { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, - }; - } - } + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + }; [Theory] [MemberData(nameof(OrderAsArrayData))] @@ -60,21 +63,34 @@ public void OrderAsArray(ImmutableArray data, ImmutableArray expected) Assert.Equal(expected, sorted); } - public static TheoryData, ImmutableArray> OrderDescendingAsArrayData - { - get + public static TheoryData, ImmutableArray> OrderAsArray_OddBeforeEvenData + => new() { - return new() - { - { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, - { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, - { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, - { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, - { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, - }; - } + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] }, + }; + + [Theory] + [MemberData(nameof(OrderAsArray_OddBeforeEvenData))] + public void OrderAsArray_OddBeforeEven(ImmutableArray data, ImmutableArray expected) + { + var sorted = data.OrderAsArray(OddBeforeEven); + Assert.Equal(expected, sorted); } + public static TheoryData, ImmutableArray> OrderDescendingAsArrayData + => new() + { + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + }; + [Theory] [MemberData(nameof(OrderAsArrayData))] public void OrderDescendingAsArray(ImmutableArray data, ImmutableArray expected) @@ -83,6 +99,24 @@ public void OrderDescendingAsArray(ImmutableArray data, ImmutableArray Assert.Equal(expected, sorted); } + public static TheoryData, ImmutableArray> OrderDescendingAsArray_OddBeforeEvenData + => new() + { + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [10, 8, 6, 4, 2, 9, 7, 5, 3, 1] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [10, 8, 6, 4, 2, 9, 7, 5, 3, 1] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [10, 8, 6, 4, 2, 9, 7, 5, 3, 1] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [10, 8, 6, 4, 2, 9, 7, 5, 3, 1] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [10, 8, 6, 4, 2, 9, 7, 5, 3, 1] }, + }; + + [Theory] + [MemberData(nameof(OrderDescendingAsArray_OddBeforeEvenData))] + public void OrderDescendingAsArray_OddBeforeEven(ImmutableArray data, ImmutableArray expected) + { + var sorted = data.OrderDescendingAsArray(OddBeforeEven); + Assert.Equal(expected, sorted); + } + public readonly record struct ValueHolder(int Value) { public static implicit operator ValueHolder(int value) @@ -90,19 +124,14 @@ public static implicit operator ValueHolder(int value) } public static TheoryData, ImmutableArray> OrderByAsArrayData - { - get + => new() { - return new() - { - { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, - { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, - { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, - { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, - { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, - }; - } - } + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }, + }; [Theory] [MemberData(nameof(OrderByAsArrayData))] @@ -112,21 +141,34 @@ public void OrderByAsArray(ImmutableArray data, ImmutableArray(expected, sorted); } - public static TheoryData, ImmutableArray> OrderByDescendingAsArrayData - { - get + public static TheoryData, ImmutableArray> OrderByAsArray_OddBeforeEvenData + => new() { - return new() - { - { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, - { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, - { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, - { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, - { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, - }; - } + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] }, + }; + + [Theory] + [MemberData(nameof(OrderByAsArray_OddBeforeEvenData))] + public void OrderByAsArray_OddBeforeEvent(ImmutableArray data, ImmutableArray expected) + { + var sorted = data.OrderByAsArray(static x => x.Value, OddBeforeEven); + Assert.Equal(expected, sorted); } + public static TheoryData, ImmutableArray> OrderByDescendingAsArrayData + => new() + { + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] }, + }; + [Theory] [MemberData(nameof(OrderByDescendingAsArrayData))] public void OrderByDescendingAsArray(ImmutableArray data, ImmutableArray expected) @@ -134,4 +176,22 @@ public void OrderByDescendingAsArray(ImmutableArray data, Immutable var sorted = data.OrderByDescendingAsArray(static x => x.Value); Assert.Equal(expected, sorted); } + + public static TheoryData, ImmutableArray> OrderByDescendingAsArray_OddBeforeEvenData + => new() + { + { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [10, 8, 6, 4, 2, 9, 7, 5, 3, 1] }, + { [10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [10, 8, 6, 4, 2, 9, 7, 5, 3, 1] }, + { [1, 3, 5, 7, 9, 2, 4, 6, 8, 10], [10, 8, 6, 4, 2, 9, 7, 5, 3, 1] }, + { [2, 5, 8, 1, 3, 9, 7, 4, 10, 6], [10, 8, 6, 4, 2, 9, 7, 5, 3, 1] }, + { [6, 10, 4, 7, 9, 3, 1, 8, 5, 2], [10, 8, 6, 4, 2, 9, 7, 5, 3, 1] }, + }; + + [Theory] + [MemberData(nameof(OrderByDescendingAsArray_OddBeforeEvenData))] + public void OrderByDescendingAsArray_OddBeforeEven(ImmutableArray data, ImmutableArray expected) + { + var sorted = data.OrderByDescendingAsArray(static x => x.Value, OddBeforeEven); + Assert.Equal(expected, sorted); + } } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs index 93407acd335..29446206758 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs @@ -66,11 +66,11 @@ public static ImmutableArray SelectAsArray(this ImmutableAr { return source switch { - [] => [], - [var item] => [selector(item)], - [var item1, var item2] => [selector(item1), selector(item2)], - [var item1, var item2, var item3] => [selector(item1), selector(item2), selector(item3)], - [var item1, var item2, var item3, var item4] => [selector(item1), selector(item2), selector(item3), selector(item4)], + [] => [], + [var item] => [selector(item)], + [var item1, var item2] => [selector(item1), selector(item2)], + [var item1, var item2, var item3] => [selector(item1), selector(item2), selector(item3)], + [var item1, var item2, var item3, var item4] => [selector(item1), selector(item2), selector(item3), selector(item4)], var items => BuildResult(items, selector) }; @@ -209,121 +209,335 @@ public static int BinarySearchBy(this ImmutableArray array, TArg arg } public static ImmutableArray OrderAsArray(this ImmutableArray array) - => array.OrderAsArrayCore(GetComparer(comparer: null, descending: false)); + { + if (array.Length > 1) + { + var compareHelper = new CompareHelper(comparer: null, descending: false); + return array.OrderAsArrayCore(in compareHelper); + } + + return array; + } public static ImmutableArray OrderAsArray(this ImmutableArray array, IComparer comparer) - => array.OrderAsArrayCore(GetComparer(comparer, descending: false)); + { + if (array.Length > 1) + { + var compareHelper = new CompareHelper(comparer, descending: false); + return array.OrderAsArrayCore(in compareHelper); + } + + return array; + } public static ImmutableArray OrderAsArray(this ImmutableArray array, Comparison comparison) - => array.OrderAsArrayCore(GetComparer(comparison, descending: false)); + { + if (array.Length > 1) + { + var compareHelper = new CompareHelper(comparison, descending: false); + return array.OrderAsArrayCore(in compareHelper); + } + + return array; + } public static ImmutableArray OrderDescendingAsArray(this ImmutableArray array) - => array.OrderAsArrayCore(GetComparer(comparer: null, descending: true)); + { + if (array.Length > 1) + { + var compareHelper = new CompareHelper(comparer: null, descending: true); + return array.OrderAsArrayCore(in compareHelper); + } + + return array; + } public static ImmutableArray OrderDescendingAsArray(this ImmutableArray array, IComparer comparer) - => array.OrderAsArrayCore(GetComparer(comparer, descending: true)); + { + if (array.Length > 1) + { + var compareHelper = new CompareHelper(comparer, descending: true); + return array.OrderAsArrayCore(in compareHelper); + } + + return array; + } public static ImmutableArray OrderDescendingAsArray(this ImmutableArray array, Comparison comparison) - => array.OrderAsArrayCore(GetComparer(comparison, descending: true)); + { + if (array.Length > 1) + { + var compareHelper = new CompareHelper(comparison, descending: true); + return array.OrderAsArrayCore(in compareHelper); + } + + return array; + } public static ImmutableArray OrderByAsArray( this ImmutableArray array, Func keySelector) - => array.OrderByAsArrayCore(keySelector, GetComparer(comparer: null, descending: false)); + { + if (array.Length > 1) + { + var compareHelper = new CompareHelper(comparer: null, descending: false); + return array.OrderByAsArrayCore(keySelector, in compareHelper); + } + + return array; + } public static ImmutableArray OrderByAsArray( this ImmutableArray array, Func keySelector, IComparer comparer) - => array.OrderByAsArrayCore(keySelector, GetComparer(comparer, descending: false)); + { + if (array.Length > 1) + { + var compareHelper = new CompareHelper(comparer, descending: false); + return array.OrderByAsArrayCore(keySelector, in compareHelper); + } + + return array; + } public static ImmutableArray OrderByAsArray( this ImmutableArray array, Func keySelector, Comparison comparison) - => array.OrderByAsArrayCore(keySelector, GetComparer(comparison, descending: false)); + { + if (array.Length > 1) + { + var compareHelper = new CompareHelper(comparison, descending: false); + return array.OrderByAsArrayCore(keySelector, in compareHelper); + } + + return array; + } public static ImmutableArray OrderByDescendingAsArray( this ImmutableArray array, Func keySelector) - => array.OrderByAsArrayCore(keySelector, GetComparer(comparer: null, descending: true)); + { + if (array.Length > 1) + { + var compareHelper = new CompareHelper(comparer: null, descending: true); + return array.OrderByAsArrayCore(keySelector, in compareHelper); + } + + return array; + } public static ImmutableArray OrderByDescendingAsArray( this ImmutableArray array, Func keySelector, IComparer comparer) - => array.OrderByAsArrayCore(keySelector, GetComparer(comparer, descending: true)); + { + if (array.Length > 1) + { + var compareHelper = new CompareHelper(comparer, descending: true); + return array.OrderByAsArrayCore(keySelector, in compareHelper); + } + + return array; + } public static ImmutableArray OrderByDescendingAsArray( this ImmutableArray array, Func keySelector, Comparison comparison) - => array.OrderByAsArrayCore(keySelector, GetComparer(comparison, descending: true)); - - private static IComparer GetComparer(IComparer? comparer, bool descending) { - if (comparer is null) + if (array.Length > 1) { - return descending - ? DescendingComparer.Default - : Comparer.Default; + var compareHelper = new CompareHelper(comparison, descending: true); + return array.OrderByAsArrayCore(keySelector, in compareHelper); } - return descending - ? new DescendingComparer(comparer) - : comparer; + return array; } - private static IComparer GetComparer(Comparison comparison, bool descending) + private static ImmutableArray OrderAsArrayCore(this ImmutableArray array, ref readonly CompareHelper compareHelper) { - var comparer = Comparer.Create(comparison); + var items = array.AsSpan(); - return descending - ? new DescendingComparer(comparer) - : comparer; - } + if (AreOrdered(items, in compareHelper)) + { + // No need to sort - items are already ordered. + return array; + } - private sealed class DescendingComparer(IComparer comparer) : IComparer - { - private static IComparer? s_default; + var length = items.Length; + var newArray = new T[length]; + items.CopyTo(newArray); - public static IComparer Default => s_default ??= new DescendingComparer(Comparer.Default); + var comparer = compareHelper.GetOrCreateComparer(); - public int Compare(T? x, T? y) - => comparer.Compare(y!, x!); + Array.Sort(newArray, comparer); + + return ImmutableCollectionsMarshal.AsImmutableArray(newArray); } - private static ImmutableArray OrderAsArrayCore(this ImmutableArray array, IComparer comparer) + private static ImmutableArray OrderByAsArrayCore( + this ImmutableArray array, Func keySelector, ref readonly CompareHelper compareHelper) { - if (array.IsEmpty || array.Length == 1) + var items = array.AsSpan(); + var length = items.Length; + + using var _ = ArrayPool.Shared.GetPooledArray(minimumLength: length, out var keys); + + if (SelectKeys(items, keySelector, compareHelper, keys.AsSpan(0, length))) { + // No need to sort - keys are already ordered. return array; } - var span = array.AsSpan(); + var newArray = new TElement[length]; + items.CopyTo(newArray); - var length = span.Length; - var items = new T[length]; - span.CopyTo(items); + var comparer = compareHelper.GetOrCreateComparer(); - Array.Sort(items, comparer); + Array.Sort(keys, newArray, 0, length, comparer); - return ImmutableCollectionsMarshal.AsImmutableArray(items); + return ImmutableCollectionsMarshal.AsImmutableArray(newArray); } - private static ImmutableArray OrderByAsArrayCore( - this ImmutableArray array, Func keySelector, IComparer comparer) + /// + /// Walk through and determine whether they are already ordered using + /// the provided . + /// + /// + /// if the items are in order; otherwise . + /// + /// + /// When the items are already ordered, there's no need to perform a sort. + /// + private static bool AreOrdered(ReadOnlySpan items, ref readonly CompareHelper compareHelper) { - if (array.IsEmpty || array.Length == 1) + var isOutOfOrder = false; + + for (var i = 1; i < items.Length; i++) { - return array; + if (!compareHelper.InSortedOrder(items[i], items[i - 1])) + { + isOutOfOrder = true; + break; + } } - var span = array.AsSpan(); + return !isOutOfOrder; + } - var length = span.Length; - var items = new TElement[length]; - span.CopyTo(items); + /// + /// Walk through and convert each element to a key using . + /// While walking, each computed key is compared with the previous one using the provided + /// to determine whether they are already ordered. + /// + /// + /// if the keys are in order; otherwise . + /// + /// + /// When the keys are already ordered, there's no need to perform a sort. + /// + private static bool SelectKeys( + ReadOnlySpan items, Func keySelector, CompareHelper compareHelper, Span keys) + { + var isOutOfOrder = false; - using var _ = ArrayPool.Shared.GetPooledArray(minimumLength: length, out var keys); + keys[0] = keySelector(items[0]); - for (var i = 0; i < length; i++) + for (var i = 1; i < items.Length; i++) { keys[i] = keySelector(items[i]); + + if (!isOutOfOrder && !compareHelper.InSortedOrder(keys[i], keys[i - 1])) + { + isOutOfOrder = true; + + // Continue processing to finish converting elements to keys. However, we can stop comparing keys. + } } - Array.Sort(keys, items, 0, length, comparer); + return !isOutOfOrder; + } + + /// + /// Helper that avoids creating an until its needed. + /// + private readonly ref struct CompareHelper + { + private readonly IComparer _comparer; + private readonly Comparison _comparison; + private readonly bool _comparerSpecified; + private readonly bool _useComparer; + private readonly bool _descending; - return ImmutableCollectionsMarshal.AsImmutableArray(items); + public CompareHelper(IComparer? comparer, bool descending) + { + _comparerSpecified = comparer is not null; + _comparer = comparer ?? Comparer.Default; + _useComparer = true; + _descending = descending; + _comparison = null!; + } + + public CompareHelper(Comparison comparison, bool descending) + { + _comparison = comparison; + _useComparer = false; + _descending = descending; + _comparer = null!; + } + + public bool InSortedOrder(T? x, T? y) + { + // We assume that x and y are in sorted order if x is > y. + // We don't consider x == y to be sorted because the actual sor + // might not be stable, depending on T. + + return _useComparer + ? !_descending ? _comparer.Compare(x!, y!) > 0 : _comparer.Compare(y!, x!) > 0 + : !_descending ? _comparison(x!, y!) > 0 : _comparison(y!, x!) > 0; + } + + public IComparer GetOrCreateComparer() + // There are six cases to consider. + => (_useComparer, _comparerSpecified, _descending) switch + { + // Provided a comparer and the results are in ascending order. + (true, true, false) => _comparer, + + // Provided a comparer and the results are in descending order. + (true, true, true) => DescendingComparer.Create(_comparer), + + // Not provided a comparer and the results are in ascending order. + // In this case, _comparer was already set to Comparer.Default. + (true, false, false) => _comparer, + + // Not provided a comparer and the results are in descending order. + (true, false, true) => DescendingComparer.Default, + + // Provided a comparison delegate and the results are in ascending order. + (false, _, false) => Comparer.Create(_comparison), + + // Provided a comparison delegate and the results are in descending order. + (false, _, true) => DescendingComparer.Create(_comparison) + }; + } + + private abstract class DescendingComparer : IComparer + { + private static IComparer? s_default; + + public static IComparer Default => s_default ??= new WrappedComparer(Comparer.Default); + + public static IComparer Create(IComparer comparer) + => new WrappedComparer(comparer); + + public static IComparer Create(Comparison comparison) + => new WrappedComparison(comparison); + + public abstract int Compare(T? x, T? y); + + private sealed class WrappedComparer(IComparer comparer) : DescendingComparer + { + public override int Compare(T? x, T? y) + => comparer.Compare(y!, x!); + } + + private sealed class WrappedComparison(Comparison comparison) : DescendingComparer + { + public override int Compare(T? x, T? y) + => comparison(y!, x!); + } } } From ca16fcb387d03401d6ba740b5a5741b7cd8eebbd Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 11 Jul 2024 11:14:18 -0700 Subject: [PATCH 5/8] Fix indentation --- .../ImmutableArrayExtensions.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs index 29446206758..d4dd11c7662 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs @@ -66,11 +66,11 @@ public static ImmutableArray SelectAsArray(this ImmutableAr { return source switch { - [] => [], - [var item] => [selector(item)], - [var item1, var item2] => [selector(item1), selector(item2)], - [var item1, var item2, var item3] => [selector(item1), selector(item2), selector(item3)], - [var item1, var item2, var item3, var item4] => [selector(item1), selector(item2), selector(item3), selector(item4)], + [] => [], + [var item] => [selector(item)], + [var item1, var item2] => [selector(item1), selector(item2)], + [var item1, var item2, var item3] => [selector(item1), selector(item2), selector(item3)], + [var item1, var item2, var item3, var item4] => [selector(item1), selector(item2), selector(item3), selector(item4)], var items => BuildResult(items, selector) }; From ff73a1d7818c69955554107b8c9092bad9d8e731 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Thu, 11 Jul 2024 15:15:44 -0700 Subject: [PATCH 6/8] Push array.Length checks down to avoid code duplication --- .../ImmutableArrayExtensionsTests.cs | 121 +++++++++++++ .../ImmutableArrayExtensions.cs | 159 +++++++----------- 2 files changed, 178 insertions(+), 102 deletions(-) diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs index 398fcbfae46..7a04d9083b4 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Immutable; +using System.Runtime.InteropServices; using Xunit; namespace Microsoft.AspNetCore.Razor.Utilities.Shared.Test; @@ -194,4 +195,124 @@ public void OrderByDescendingAsArray_OddBeforeEven(ImmutableArray d var sorted = data.OrderByDescendingAsArray(static x => x.Value, OddBeforeEven); Assert.Equal(expected, sorted); } + + [Fact] + public void OrderAsArray_EmptyArrayReturnsSameArray() + { + var array = ImmutableCollectionsMarshal.AsArray(ImmutableArray.Empty); + var immutableArray = ImmutableArray.Empty; + + immutableArray = immutableArray.OrderAsArray(); + Assert.Same(array, ImmutableCollectionsMarshal.AsArray(immutableArray)); + } + + [Fact] + public void OrderAsArray_SingleElementArrayReturnsSameArray() + { + var array = new int[] { 42 }; + var immutableArray = ImmutableCollectionsMarshal.AsImmutableArray(array); + + immutableArray = immutableArray.OrderAsArray(); + Assert.Same(array, ImmutableCollectionsMarshal.AsArray(immutableArray)); + } + + [Fact] + public void OrderAsArray_SortedArrayReturnsSameArray() + { + var values = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + var immutableArray = ImmutableCollectionsMarshal.AsImmutableArray(values); + + immutableArray = immutableArray.OrderAsArray(); + Assert.Same(values, ImmutableCollectionsMarshal.AsArray(immutableArray)); + } + + [Fact] + public void OrderDescendingAsArray_EmptyArrayReturnsSameArray() + { + var array = ImmutableCollectionsMarshal.AsArray(ImmutableArray.Empty); + var immutableArray = ImmutableArray.Empty; + + immutableArray = immutableArray.OrderDescendingAsArray(); + Assert.Same(array, ImmutableCollectionsMarshal.AsArray(immutableArray)); + } + + [Fact] + public void OrderDescendingAsArray_SingleElementArrayReturnsSameArray() + { + var array = new int[] { 42 }; + var immutableArray = ImmutableCollectionsMarshal.AsImmutableArray(array); + + immutableArray = immutableArray.OrderDescendingAsArray(); + Assert.Same(array, ImmutableCollectionsMarshal.AsArray(immutableArray)); + } + + [Fact] + public void OrderDescendingAsArray_SortedArrayReturnsSameArray() + { + var values = new int[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; + var presortedArray = ImmutableCollectionsMarshal.AsImmutableArray(values); + + presortedArray = presortedArray.OrderDescendingAsArray(); + Assert.Same(values, ImmutableCollectionsMarshal.AsArray(presortedArray)); + } + + [Fact] + public void OrderByAsArray_EmptyArrayReturnsSameArray() + { + var array = ImmutableCollectionsMarshal.AsArray(ImmutableArray.Empty); + var immutableArray = ImmutableArray.Empty; + + immutableArray = immutableArray.OrderByAsArray(static x => x.Value); + Assert.Same(array, ImmutableCollectionsMarshal.AsArray(immutableArray)); + } + + [Fact] + public void OrderByAsArray_SingleElementArrayReturnsSameArray() + { + var array = new ValueHolder[] { 42 }; + var immutableArray = ImmutableCollectionsMarshal.AsImmutableArray(array); + + immutableArray = immutableArray.OrderByAsArray(static x => x.Value); + Assert.Same(array, ImmutableCollectionsMarshal.AsArray(immutableArray)); + } + + [Fact] + public void OrderByAsArray_SortedArrayReturnsSameArray() + { + var values = new ValueHolder[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + var presortedArray = ImmutableCollectionsMarshal.AsImmutableArray(values); + + presortedArray = presortedArray.OrderByAsArray(static x => x.Value); + Assert.Same(values, ImmutableCollectionsMarshal.AsArray(presortedArray)); + } + + [Fact] + public void OrderByDescendingAsArray_EmptyArrayReturnsSameArray() + { + var array = ImmutableCollectionsMarshal.AsArray(ImmutableArray.Empty); + var immutableArray = ImmutableArray.Empty; + + immutableArray = immutableArray.OrderByDescendingAsArray(static x => x.Value); + Assert.Same(array, ImmutableCollectionsMarshal.AsArray(immutableArray)); + } + + [Fact] + public void OrderByDescendingAsArray_SingleElementArrayReturnsSameArray() + { + var array = new ValueHolder[] { 42 }; + var immutableArray = ImmutableCollectionsMarshal.AsImmutableArray(array); + + immutableArray = immutableArray.OrderByDescendingAsArray(static x => x.Value); + Assert.Same(array, ImmutableCollectionsMarshal.AsArray(immutableArray)); + } + + [Fact] + public void OrderByDescendingAsArray_SortedArrayReturnsSameArray() + { + var values = new ValueHolder[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; + var presortedArray = ImmutableCollectionsMarshal.AsImmutableArray(values); + + presortedArray = presortedArray.OrderByDescendingAsArray(static x => x.Value); + Assert.Same(values, ImmutableCollectionsMarshal.AsArray(presortedArray)); + } } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs index d4dd11c7662..42e3dc5e81e 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs @@ -221,174 +221,129 @@ public static ImmutableArray OrderAsArray(this ImmutableArray array) public static ImmutableArray OrderAsArray(this ImmutableArray array, IComparer comparer) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparer, descending: false); - return array.OrderAsArrayCore(in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparer, descending: false); + return array.OrderAsArrayCore(in compareHelper); } public static ImmutableArray OrderAsArray(this ImmutableArray array, Comparison comparison) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparison, descending: false); - return array.OrderAsArrayCore(in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparison, descending: false); + return array.OrderAsArrayCore(in compareHelper); } public static ImmutableArray OrderDescendingAsArray(this ImmutableArray array) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparer: null, descending: true); - return array.OrderAsArrayCore(in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparer: null, descending: true); + return array.OrderAsArrayCore(in compareHelper); } public static ImmutableArray OrderDescendingAsArray(this ImmutableArray array, IComparer comparer) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparer, descending: true); - return array.OrderAsArrayCore(in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparer, descending: true); + return array.OrderAsArrayCore(in compareHelper); } public static ImmutableArray OrderDescendingAsArray(this ImmutableArray array, Comparison comparison) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparison, descending: true); - return array.OrderAsArrayCore(in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparison, descending: true); + return array.OrderAsArrayCore(in compareHelper); } public static ImmutableArray OrderByAsArray( this ImmutableArray array, Func keySelector) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparer: null, descending: false); - return array.OrderByAsArrayCore(keySelector, in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparer: null, descending: false); + return array.OrderByAsArrayCore(keySelector, in compareHelper); } public static ImmutableArray OrderByAsArray( this ImmutableArray array, Func keySelector, IComparer comparer) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparer, descending: false); - return array.OrderByAsArrayCore(keySelector, in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparer, descending: false); + return array.OrderByAsArrayCore(keySelector, in compareHelper); } public static ImmutableArray OrderByAsArray( this ImmutableArray array, Func keySelector, Comparison comparison) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparison, descending: false); - return array.OrderByAsArrayCore(keySelector, in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparison, descending: false); + return array.OrderByAsArrayCore(keySelector, in compareHelper); } public static ImmutableArray OrderByDescendingAsArray( this ImmutableArray array, Func keySelector) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparer: null, descending: true); - return array.OrderByAsArrayCore(keySelector, in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparer: null, descending: true); + return array.OrderByAsArrayCore(keySelector, in compareHelper); } public static ImmutableArray OrderByDescendingAsArray( this ImmutableArray array, Func keySelector, IComparer comparer) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparer, descending: true); - return array.OrderByAsArrayCore(keySelector, in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparer, descending: true); + return array.OrderByAsArrayCore(keySelector, in compareHelper); } public static ImmutableArray OrderByDescendingAsArray( this ImmutableArray array, Func keySelector, Comparison comparison) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparison, descending: true); - return array.OrderByAsArrayCore(keySelector, in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparison, descending: true); + return array.OrderByAsArrayCore(keySelector, in compareHelper); } private static ImmutableArray OrderAsArrayCore(this ImmutableArray array, ref readonly CompareHelper compareHelper) { - var items = array.AsSpan(); - - if (AreOrdered(items, in compareHelper)) + if (array.Length > 1) { - // No need to sort - items are already ordered. - return array; - } + var items = array.AsSpan(); + + if (AreOrdered(items, in compareHelper)) + { + // No need to sort - items are already ordered. + return array; + } - var length = items.Length; - var newArray = new T[length]; - items.CopyTo(newArray); + var length = items.Length; + var newArray = new T[length]; + items.CopyTo(newArray); - var comparer = compareHelper.GetOrCreateComparer(); + var comparer = compareHelper.GetOrCreateComparer(); - Array.Sort(newArray, comparer); + Array.Sort(newArray, comparer); - return ImmutableCollectionsMarshal.AsImmutableArray(newArray); + return ImmutableCollectionsMarshal.AsImmutableArray(newArray); + } + + return array; } private static ImmutableArray OrderByAsArrayCore( this ImmutableArray array, Func keySelector, ref readonly CompareHelper compareHelper) { - var items = array.AsSpan(); - var length = items.Length; + if (array.Length > 1) + { + var items = array.AsSpan(); + var length = items.Length; - using var _ = ArrayPool.Shared.GetPooledArray(minimumLength: length, out var keys); + using var _ = ArrayPool.Shared.GetPooledArray(minimumLength: length, out var keys); - if (SelectKeys(items, keySelector, compareHelper, keys.AsSpan(0, length))) - { - // No need to sort - keys are already ordered. - return array; - } + if (SelectKeys(items, keySelector, compareHelper, keys.AsSpan(0, length))) + { + // No need to sort - keys are already ordered. + return array; + } + + var newArray = new TElement[length]; + items.CopyTo(newArray); - var newArray = new TElement[length]; - items.CopyTo(newArray); + var comparer = compareHelper.GetOrCreateComparer(); - var comparer = compareHelper.GetOrCreateComparer(); + Array.Sort(keys, newArray, 0, length, comparer); - Array.Sort(keys, newArray, 0, length, comparer); + return ImmutableCollectionsMarshal.AsImmutableArray(newArray); + } - return ImmutableCollectionsMarshal.AsImmutableArray(newArray); + return array; } /// From 52debafc6068be464b24c62164dbb18738bd4c34 Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Fri, 12 Jul 2024 08:41:51 -0700 Subject: [PATCH 7/8] Remove one more unneeded array.Length check --- .../ImmutableArrayExtensions.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs index 42e3dc5e81e..c6c618123e2 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs @@ -210,13 +210,8 @@ public static int BinarySearchBy(this ImmutableArray array, TArg arg public static ImmutableArray OrderAsArray(this ImmutableArray array) { - if (array.Length > 1) - { - var compareHelper = new CompareHelper(comparer: null, descending: false); - return array.OrderAsArrayCore(in compareHelper); - } - - return array; + var compareHelper = new CompareHelper(comparer: null, descending: false); + return array.OrderAsArrayCore(in compareHelper); } public static ImmutableArray OrderAsArray(this ImmutableArray array, IComparer comparer) From 5579bc04282e3124747ccdd46407f5618eeaf91e Mon Sep 17 00:00:00 2001 From: Dustin Campbell Date: Mon, 15 Jul 2024 15:40:03 -0700 Subject: [PATCH 8/8] Change WrappedComparer and WrappedComparison to 'Reversed*' --- .../ImmutableArrayExtensions.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs index c6c618123e2..e0d7478cca7 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs @@ -468,23 +468,23 @@ private abstract class DescendingComparer : IComparer { private static IComparer? s_default; - public static IComparer Default => s_default ??= new WrappedComparer(Comparer.Default); + public static IComparer Default => s_default ??= new ReversedComparer(Comparer.Default); public static IComparer Create(IComparer comparer) - => new WrappedComparer(comparer); + => new ReversedComparer(comparer); public static IComparer Create(Comparison comparison) - => new WrappedComparison(comparison); + => new ReversedComparison(comparison); public abstract int Compare(T? x, T? y); - private sealed class WrappedComparer(IComparer comparer) : DescendingComparer + private sealed class ReversedComparer(IComparer comparer) : DescendingComparer { public override int Compare(T? x, T? y) => comparer.Compare(y!, x!); } - private sealed class WrappedComparison(Comparison comparison) : DescendingComparer + private sealed class ReversedComparison(Comparison comparison) : DescendingComparer { public override int Compare(T? x, T? y) => comparison(y!, x!);