Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 115 additions & 26 deletions src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ internal IEnumerator<TElement> GetEnumerator(int minIdx, int maxIdx)
}
}

private EnumerableSorter<TElement> GetEnumerableSorter() => GetEnumerableSorter(null);
private EnumerableSorter<TElement> GetEnumerableSorter() => GetEnumerableSorter(EnumerableSorterRoot<TElement>.Instance);

internal abstract EnumerableSorter<TElement> GetEnumerableSorter(EnumerableSorter<TElement>? next);
internal abstract EnumerableSorter<TElement> GetEnumerableSorter(IEnumerableSorter<TElement> next);

private CachingComparer<TElement> GetComparer() => GetComparer(null);

Expand Down Expand Up @@ -167,7 +167,7 @@ internal OrderedEnumerable(IEnumerable<TElement> source, Func<TElement, TKey> ke
_descending = descending;
}

internal override EnumerableSorter<TElement> GetEnumerableSorter(EnumerableSorter<TElement>? next)
internal override EnumerableSorter<TElement> GetEnumerableSorter(IEnumerableSorter<TElement> next)
{
// Special case the common use of string with default comparer. Comparer<string>.Default checks the
// thread's Culture on each call which is an overhead which is not required, because we are about to
Expand Down Expand Up @@ -273,41 +273,88 @@ internal override void SetElement(TElement element)
}
}

internal abstract class EnumerableSorter<TElement>
internal interface IEnumerableSorter<TElement>
{
internal abstract void ComputeKeys(TElement[] elements, int count);
void ComputeKeys(TElement[] elements, int count);

internal abstract int CompareAnyKeys(int index1, int index2);
int CompareAnyKeys(int index1, int index2);

private int[] ComputeMap(TElement[] elements, int count)
bool AreAllLayersAscending { get; }

void SortByLayer(TElement[] elements, int[] indexes, int startIdx, int count);
}

internal class EnumerableSorterRoot<TElement> : IEnumerableSorter<TElement>
{
public static IEnumerableSorter<TElement> Instance { get; } = new EnumerableSorterRoot<TElement>();

private EnumerableSorterRoot() { }

public int CompareAnyKeys(int index1, int index2) => index1 - index2;

public void ComputeKeys(TElement[] elements, int count) { }

public bool AreAllLayersAscending => true;

public void SortByLayer(TElement[] elements, int[] indexes, int startIdx, int count) => Array.Sort(indexes, startIdx, count);
}

internal abstract class EnumerableSorter<TElement> : IEnumerableSorter<TElement>
{
public abstract void SortByLayer(TElement[] elements, int[] indexes, int startIdx, int count);
public abstract void ComputeKeys(TElement[] elements, int count);
public abstract int CompareAnyKeys(int index1, int index2);

private int[] ComputeKeysAndMap(TElement[] elements, int count)
{
ComputeKeys(elements, count);
return ComputeMap(count);
}

private static int[] ComputeMap(int count)
{
int[] map = new int[count];
for (int i = 0; i < map.Length; i++)
{
map[i] = i;
}

return map;
}

protected abstract bool IsOuterLayerValueType { get; }
public abstract bool AreAllLayersAscending { get; }

internal int[] Sort(TElement[] elements, int count)
{
int[] map = ComputeMap(elements, count);
QuickSort(map, 0, count - 1);
return map;
// Check that we can use the layered sort. This adds (as a first level approximation) O(N) comparisons
// to the sort, but this is offset by simpler comparers (i.e. removes a level of indirection),
// Array.Sort optimizations (for primitives), removes level of indirection from objects (i.e. accessing
// directly in array by sort, rather than an index into another array) and increases caching affects
// due to location in array.
if (IsOuterLayerValueType && AreAllLayersAscending)
{
int[] map = ComputeMap(count);
SortByLayer(elements, map, 0, count);
return map;
}
else
{
int[] map = ComputeKeysAndMap(elements, count);
QuickSort(map, 0, count - 1);
return map;
}
}

internal int[] Sort(TElement[] elements, int count, int minIdx, int maxIdx)
{
int[] map = ComputeMap(elements, count);
int[] map = ComputeKeysAndMap(elements, count);
PartialQuickSort(map, 0, count - 1, minIdx, maxIdx);
return map;
}

internal TElement ElementAt(TElement[] elements, int count, int idx)
{
int[] map = ComputeMap(elements, count);
int[] map = ComputeKeysAndMap(elements, count);
return idx == 0 ?
elements[Min(map, count)] :
elements[QuickSelect(map, count - 1, idx)];
Expand All @@ -331,40 +378,85 @@ internal sealed class EnumerableSorter<TElement, TKey> : EnumerableSorter<TEleme
private readonly Func<TElement, TKey> _keySelector;
private readonly IComparer<TKey> _comparer;
private readonly bool _descending;
private readonly EnumerableSorter<TElement>? _next;
private readonly IEnumerableSorter<TElement> _next;

private TKey[]? _keys;

internal EnumerableSorter(Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending, EnumerableSorter<TElement>? next)
internal EnumerableSorter(Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending, IEnumerableSorter<TElement> next)
{
_keySelector = keySelector;
_comparer = comparer;
_descending = descending;
_next = next;
}

internal override void ComputeKeys(TElement[] elements, int count)
protected override bool IsOuterLayerValueType => default(TKey)! != null;

public override bool AreAllLayersAscending => !_descending && _next.AreAllLayersAscending;

public override void SortByLayer(TElement[] data, int[] indexes, int startIdx, int count)
{
if (_keys == null)
{
_keys = new TKey[data.Length];
}

int exclusiveEndIdx = startIdx + count;

// copy the keys that we need
for (int idx = startIdx; idx < exclusiveEndIdx; ++idx)
{
_keys[idx] = _keySelector(data[indexes[idx]]);
}

// unstable sort
Array.Sort(_keys, indexes, startIdx, count, _comparer);

// now find duplicate keys, and go to the lower level to sort
TKey examplar = _keys[startIdx];
int examplarIdx = startIdx;

int batchCount;
for (int idx = startIdx + 1; idx < exclusiveEndIdx; ++idx)
{
if (_comparer.Compare(examplar, _keys[idx]) != 0)
{
batchCount = idx - examplarIdx;
if (batchCount > 1)
{
_next.SortByLayer(data, indexes, examplarIdx, batchCount);
}
examplar = _keys[idx];
examplarIdx = idx;
}
}

// handle the remainders
batchCount = exclusiveEndIdx - examplarIdx;
if (batchCount > 1)
{
_next.SortByLayer(data, indexes, examplarIdx, batchCount);
}
}

public override void ComputeKeys(TElement[] elements, int count)
{
_keys = new TKey[count];
for (int i = 0; i < count; i++)
{
_keys[i] = _keySelector(elements[i]);
}

_next?.ComputeKeys(elements, count);
_next.ComputeKeys(elements, count);
}

internal override int CompareAnyKeys(int index1, int index2)
public override int CompareAnyKeys(int index1, int index2)
{
Debug.Assert(_keys != null);

int c = _comparer.Compare(_keys[index1], _keys[index2]);
if (c == 0)
{
if (_next == null)
{
return index1 - index2; // ensure stability of sort
}

return _next.CompareAnyKeys(index1, index2);
}

Expand All @@ -374,14 +466,11 @@ internal override int CompareAnyKeys(int index1, int index2)
return (_descending != (c > 0)) ? 1 : -1;
}


private int CompareKeys(int index1, int index2) => index1 == index2 ? 0 : CompareAnyKeys(index1, index2);

protected override void QuickSort(int[] keys, int lo, int hi) =>
Array.Sort(keys, lo, hi - lo + 1, Comparer<int>.Create(CompareAnyKeys)); // TODO #24115: Remove Create call when delegate-based overload is available



// Sorts the k elements between minIdx and maxIdx without sorting all elements
// Time complexity: O(n + k log k) best and average case. O(n^2) worse case.
protected override void PartialQuickSort(int[] map, int left, int right, int minIdx, int maxIdx)
Expand Down