Skip to content

Commit

Permalink
Implement dotnet#28776: LINQ APIs for index and range. Update unit te…
Browse files Browse the repository at this point in the history
…sts.
  • Loading branch information
Dixin committed Feb 20, 2021
1 parent 88d55cd commit 9e7e807
Show file tree
Hide file tree
Showing 13 changed files with 1,197 additions and 691 deletions.
33 changes: 15 additions & 18 deletions src/libraries/System.Linq/src/System/Linq/ElementAt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,17 @@ public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int i
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}

if (source is IPartition<TSource> partition)
{
TSource? element = partition.TryGetElementAt(index, out bool found);
if (found)
{
return element!;
}
}
else if (source is IList<TSource> list)
if (source is IList<TSource> list)
{
return list[index];
}
else if (TryGetElement(source, index, out TSource? element))

if (!TryGetElement(source, index, out TSource? element))
{
return element;
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}

ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
return default;
return element;
}

/// <summary>Returns the element at a specified index in a sequence.</summary>
Expand Down Expand Up @@ -79,11 +71,6 @@ public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, Index
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}

if (source is IPartition<TSource> partition)
{
return partition.TryGetElementAt(index, out bool _);
}

if (source is IList<TSource> list)
{
return index >= 0 && index < list.Count ? list[index] : default;
Expand Down Expand Up @@ -129,6 +116,11 @@ private static bool TryGetElement<TSource>(IEnumerable<TSource> source, int inde
{
Debug.Assert(source != null);

if (source is IPartition<TSource> partition)
{
return partition.TryGetElementAt(index, isIndexFromEnd: false, out element);
}

if (index >= 0)
{
using IEnumerator<TSource> e = source.GetEnumerator();
Expand All @@ -152,6 +144,11 @@ private static bool TryGetElementFromEnd<TSource>(IEnumerable<TSource> source, i
{
Debug.Assert(source != null);

if (source is IPartition<TSource> partition)
{
return partition.TryGetElementAt(indexFromEnd, isIndexFromEnd: true, out element);
}

if (indexFromEnd > 0)
{
using IEnumerator<TSource> e = source.GetEnumerator();
Expand Down
30 changes: 25 additions & 5 deletions src/libraries/System.Linq/src/System/Linq/IPartition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,55 @@ internal interface IPartition<TElement> : IIListProvider<TElement>
/// </summary>
/// <param name="count">The number of elements to skip.</param>
/// <returns>An <see cref="IPartition{TElement}"/> with the first <paramref name="count"/> items removed.</returns>
IPartition<TElement> Skip(int count);
IPartition<TElement> Skip(int count) => Take(count, 0, false, true);

IPartition<TElement> SkipLast(int count) => Take(0, count, false, true);

IPartition<TElement> Take(int startIndexInclusive, int endIndexExclusive, bool isStartIndexFromEnd, bool isEndIndexFromEnd);

/// <summary>
/// Creates a new partition that takes the specified number of elements from this sequence.
/// </summary>
/// <param name="count">The number of elements to take.</param>
/// <returns>An <see cref="IPartition{TElement}"/> with only the first <paramref name="count"/> items.</returns>
IPartition<TElement> Take(int count);
IPartition<TElement> Take(int count) => Take(0, count, false, false);

IPartition<TElement> TakeLast(int count) => Take(count, 0, true, true);

/// <summary>
/// Gets the item associated with a 0-based index in this sequence.
/// </summary>
/// <param name="index">The 0-based index to access.</param>
/// <param name="found"><c>true</c> if the sequence contains an element at that index, <c>false</c> otherwise.</param>
/// <returns>The element if <paramref name="found"/> is <c>true</c>, otherwise, the default value of <typeparamref name="TElement"/>.</returns>
TElement? TryGetElementAt(int index, out bool found);
TElement? TryGetElementAt(int index, out bool found)
{
found = TryGetElementAt(index, false, out TElement? element);
return element;
}

bool TryGetElementAt(int index, bool isIndexFromEnd, [MaybeNullWhen(false)] out TElement element);

/// <summary>
/// Gets the first item in this sequence.
/// </summary>
/// <param name="found"><c>true</c> if the sequence contains an element, <c>false</c> otherwise.</param>
/// <returns>The element if <paramref name="found"/> is <c>true</c>, otherwise, the default value of <typeparamref name="TElement"/>.</returns>
TElement? TryGetFirst(out bool found);
TElement? TryGetFirst(out bool found)
{
found = TryGetElementAt(0, isIndexFromEnd: false, out TElement? element);
return element;
}

/// <summary>
/// Gets the last item in this sequence.
/// </summary>
/// <param name="found"><c>true</c> if the sequence contains an element, <c>false</c> otherwise.</param>
/// <returns>The element if <paramref name="found"/> is <c>true</c>, otherwise, the default value of <typeparamref name="TElement"/>.</returns>
TElement? TryGetLast(out bool found);
TElement? TryGetLast(out bool found)
{
found = TryGetElementAt(0, isIndexFromEnd: true, out TElement? element);
return element;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,84 +56,87 @@ public int GetCount(bool onlyIfCheap)
return !onlyIfCheap || _source is ICollection<TElement> || _source is ICollection ? _source.Count() : -1;
}

internal TElement[] ToArray(int minIdx, int maxIdx)
internal TElement[] ToArray(int minIndexInclusive, int maxIndexInclusive)
{
Buffer<TElement> buffer = new Buffer<TElement>(_source);
int count = buffer._count;
if (count <= minIdx)
if (count <= minIndexInclusive)
{
return Array.Empty<TElement>();
}

if (count <= maxIdx)
if (count <= maxIndexInclusive)
{
maxIdx = count - 1;
maxIndexInclusive = count - 1;
}

if (minIdx == maxIdx)
if (minIndexInclusive == maxIndexInclusive)
{
return new TElement[] { GetEnumerableSorter().ElementAt(buffer._items, count, minIdx) };
return new TElement[] { GetEnumerableSorter().ElementAt(buffer._items, count, minIndexInclusive) };
}

int[] map = SortedMap(buffer, minIdx, maxIdx);
TElement[] array = new TElement[maxIdx - minIdx + 1];
int[] map = SortedMap(buffer, minIndexInclusive, maxIndexInclusive);
TElement[] array = new TElement[maxIndexInclusive - minIndexInclusive + 1];
int idx = 0;
while (minIdx <= maxIdx)
while (minIndexInclusive <= maxIndexInclusive)
{
array[idx] = buffer._items[map[minIdx]];
array[idx] = buffer._items[map[minIndexInclusive]];
++idx;
++minIdx;
++minIndexInclusive;
}

return array;
}

internal List<TElement> ToList(int minIdx, int maxIdx)
internal List<TElement> ToList(int minIndexInclusive, int maxIndexInclusive)
{
Buffer<TElement> buffer = new Buffer<TElement>(_source);
int count = buffer._count;
if (count <= minIdx)
if (count <= minIndexInclusive)
{
return new List<TElement>();
}

if (count <= maxIdx)
if (count <= maxIndexInclusive)
{
maxIdx = count - 1;
maxIndexInclusive = count - 1;
}

if (minIdx == maxIdx)
if (minIndexInclusive == maxIndexInclusive)
{
return new List<TElement>(1) { GetEnumerableSorter().ElementAt(buffer._items, count, minIdx) };
return new List<TElement>(1) { GetEnumerableSorter().ElementAt(buffer._items, count, minIndexInclusive) };
}

int[] map = SortedMap(buffer, minIdx, maxIdx);
List<TElement> list = new List<TElement>(maxIdx - minIdx + 1);
while (minIdx <= maxIdx)
int[] map = SortedMap(buffer, minIndexInclusive, maxIndexInclusive);
List<TElement> list = new List<TElement>(maxIndexInclusive - minIndexInclusive + 1);
while (minIndexInclusive <= maxIndexInclusive)
{
list.Add(buffer._items[map[minIdx]]);
++minIdx;
list.Add(buffer._items[map[minIndexInclusive]]);
++minIndexInclusive;
}

return list;
}

internal int GetCount(int minIdx, int maxIdx, bool onlyIfCheap)
internal int GetCount(int minIndexInclusive, int maxIndexInclusive, bool onlyIfCheap)
{
int count = GetCount(onlyIfCheap);
if (count <= 0)
{
return count;
}

if (count <= minIdx)
if (count <= minIndexInclusive)
{
return 0;
}

return (count <= maxIdx ? count : maxIdx + 1) - minIdx;
return (count <= maxIndexInclusive ? count : maxIndexInclusive + 1) - minIndexInclusive;
}

public IPartition<TElement> Take(int startIndexInclusive, int endIndexExclusive, bool isStartIndexFromEnd, bool isEndIndexFromEnd) =>
this.ToPartition(startIndexInclusive, endIndexExclusive, isStartIndexFromEnd, isEndIndexFromEnd);

public IPartition<TElement> Skip(int count) => new OrderedPartition<TElement>(this, count, int.MaxValue);

public IPartition<TElement> Take(int count) => new OrderedPartition<TElement>(this, 0, count - 1);
Expand All @@ -160,6 +163,42 @@ internal int GetCount(int minIdx, int maxIdx, bool onlyIfCheap)
return default;
}

public bool TryGetElementAt(int index, bool isIndexFromEnd, [MaybeNullWhen(false)] out TElement element)
{
if (index == 0)
{
if (isIndexFromEnd)
{
element = TryGetLast(out bool found);
return found;
}
else
{
element = TryGetFirst(out bool found);
return found;
}
}

if (index > 0)
{
Buffer<TElement> buffer = new(_source);
int count = buffer._count;
if (isIndexFromEnd)
{
index = count - index;
}

if (index >= 0 && index < count)
{
element = GetEnumerableSorter().ElementAt(buffer._items, count, index);
return true;
}
}

element = default;
return false;
}

public TElement? TryGetFirst(out bool found)
{
CachingComparer<TElement> comparer = GetComparer();
Expand Down Expand Up @@ -214,18 +253,18 @@ internal int GetCount(int minIdx, int maxIdx, bool onlyIfCheap)
}
}

public TElement? TryGetLast(int minIdx, int maxIdx, out bool found)
public TElement? TryGetLast(int minIndexInclusive, int maxIndexInclusive, out bool found)
{
Buffer<TElement> buffer = new Buffer<TElement>(_source);
int count = buffer._count;
if (minIdx >= count)
if (minIndexInclusive >= count)
{
found = false;
return default;
}

found = true;
return (maxIdx < count - 1) ? GetEnumerableSorter().ElementAt(buffer._items, count, maxIdx) : Last(buffer);
return (maxIndexInclusive < count - 1) ? GetEnumerableSorter().ElementAt(buffer._items, count, maxIndexInclusive) : Last(buffer);
}

private TElement Last(Buffer<TElement> buffer)
Expand Down
Loading

0 comments on commit 9e7e807

Please sign in to comment.