Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve ToArrayByIndex Operator #186

Merged
merged 4 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
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
199 changes: 103 additions & 96 deletions Source/SuperLinq/ToArrayByIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,126 +3,127 @@
public static partial class SuperEnumerable
{
/// <summary>
/// Creates an array from an <see cref="IEnumerable{T}"/> where a
/// function is used to determine the index at which an element will
/// be placed in the array.
/// Creates an array from an <see cref="IEnumerable{T}"/> where a function is used to determine the index at which
/// an element will be placed in the array.
/// </summary>
/// <param name="source">The source sequence for the array.</param>
/// <param name="indexSelector">
/// A function that maps an element to its index.</param>
/// <typeparam name="T">
/// The type of the element in <paramref name="source"/>.</typeparam>
/// <returns>
/// An array that contains the elements from the input sequence. The
/// size of the array will be as large as the highest index returned
/// by the <paramref name="indexSelector"/> plus 1.
/// An array that contains the elements from <paramref name="source"/>. The size of the array will be as large as
/// the highest index returned by the <paramref name="indexSelector"/> plus 1.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="indexSelector"/> is <see
/// langword="null"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException">An index returned by <paramref name="indexSelector"/> is less than
/// <c>0</c>.</exception>
/// <remarks>
/// This method forces immediate query evaluation. It should not be
/// used on infinite sequences. If more than one element maps to the
/// same index then the latter element overwrites the former in the
/// resulting array.
/// This method forces immediate query evaluation. It should not be used on infinite sequences. If more than one
/// element maps to the same index then the latter element overwrites the former in the resulting array.
/// </remarks>

public static T[] ToArrayByIndex<T>(this IEnumerable<T> source,
public static T?[] ToArrayByIndex<T>(
this IEnumerable<T> source,
Func<T, int> indexSelector)
{
return source.ToArrayByIndex(indexSelector, (e, _) => e);
}

/// <summary>
/// Creates an array from an <see cref="IEnumerable{T}"/> where a
/// function is used to determine the index at which an element will
/// be placed in the array. The elements are projected into the array
/// via an additional function.
/// Creates an array from an <see cref="IEnumerable{T}"/> where a function is used to determine the index at which
/// an element will be placed in the array. The elements are projected into the array via an additional function.
/// </summary>
/// <param name="source">The source sequence for the array.</param>
/// <param name="indexSelector">
/// A function that maps an element to its index.</param>
/// <param name="resultSelector">
/// A function to project a source element into an element of the
/// resulting array.</param>
/// A function to project a source element into an element of the resulting array.</param>
/// <typeparam name="T">
/// The type of the element in <paramref name="source"/>.</typeparam>
/// <typeparam name="TResult">
/// The type of the element in the resulting array.</typeparam>
/// <returns>
/// An array that contains the projected elements from the input
/// sequence. The size of the array will be as large as the highest
/// index returned by the <paramref name="indexSelector"/> plus 1.
/// An array that contains the projected elements from <paramref name="source"/>. The size of the array will be as
/// large as the highest index returned by the <paramref name="indexSelector"/> plus 1.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/>, <paramref name="indexSelector"/>, or
/// <paramref name="resultSelector"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException">An index returned by <paramref name="indexSelector"/> is less than
/// <c>0</c>.</exception>
/// <remarks>
/// This method forces immediate query evaluation. It should not be
/// used on infinite sequences. If more than one element maps to the
/// same index then the latter element overwrites the former in the
/// resulting array.
/// This method forces immediate query evaluation. It should not be used on infinite sequences. If more than one
/// element maps to the same index then the latter element overwrites the former in the resulting array.
/// </remarks>

public static TResult[] ToArrayByIndex<T, TResult>(this IEnumerable<T> source,
Func<T, int> indexSelector, Func<T, TResult> resultSelector)
public static TResult?[] ToArrayByIndex<T, TResult>(
this IEnumerable<T> source,
Func<T, int> indexSelector,
Func<T, TResult> resultSelector)
{
Guard.IsNotNull(resultSelector);
return source.ToArrayByIndex(indexSelector, (e, _) => resultSelector(e));
}

/// <summary>
/// Creates an array from an <see cref="IEnumerable{T}"/> where a
/// function is used to determine the index at which an element will
/// be placed in the array. The elements are projected into the array
/// via an additional function.
/// Creates an array from an <see cref="IEnumerable{T}"/> where a function is used to determine the index at which
/// an element will be placed in the array. The elements are projected into the array via an additional function.
/// </summary>
/// <param name="source">The source sequence for the array.</param>
/// <param name="indexSelector">
/// A function that maps an element to its index.</param>
/// <param name="resultSelector">
/// A function to project a source element into an element of the
/// resulting array.</param>
/// A function to project a source element into an element of the resulting array.</param>
/// <typeparam name="T">
/// The type of the element in <paramref name="source"/>.</typeparam>
/// <typeparam name="TResult">
/// The type of the element in the resulting array.</typeparam>
/// <returns>
/// An array that contains the projected elements from the input
/// sequence. The size of the array will be as large as the highest
/// index returned by the <paramref name="indexSelector"/> plus 1.
/// An array that contains the projected elements from <paramref name="source"/>. The size of the array will be as
/// large as the highest index returned by the <paramref name="indexSelector"/> plus 1.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/>, <paramref name="indexSelector"/>, or
/// <paramref name="resultSelector"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException">An index returned by <paramref name="indexSelector"/> is less than
/// <c>0</c>.</exception>
/// <remarks>
/// This method forces immediate query evaluation. It should not be
/// used on infinite sequences. If more than one element maps to the
/// same index then the latter element overwrites the former in the
/// resulting array.
/// This method forces immediate query evaluation. It should not be used on infinite sequences. If more than one
/// element maps to the same index then the latter element overwrites the former in the resulting array.
/// </remarks>

public static TResult[] ToArrayByIndex<T, TResult>(this IEnumerable<T> source,
Func<T, int> indexSelector, Func<T, int, TResult> resultSelector)
public static TResult?[] ToArrayByIndex<T, TResult>(
this IEnumerable<T> source,
Func<T, int> indexSelector,
Func<T, int, TResult> resultSelector)
{
Guard.IsNotNull(source);
Guard.IsNotNull(indexSelector);
Guard.IsNotNull(resultSelector);

var lastIndex = -1;
var indexed = (List<KeyValuePair<int, T>>?)null;
List<KeyValuePair<int, T>> Indexed() => indexed ??= new List<KeyValuePair<int, T>>();
var indexed = new List<(int, T)>();

foreach (var e in source)
{
var i = indexSelector(e);
if (i < 0)
throw new InvalidOperationException("'indexSelector' returned an invalid index for the current object.");
Guard.IsGreaterThanOrEqualTo(i, 0, "indexSelector(e)");
lastIndex = Math.Max(i, lastIndex);
Indexed().Add(new KeyValuePair<int, T>(i, e));
indexed.Add((i, e));
}

if (lastIndex == -1)
return Array.Empty<TResult>();

var length = lastIndex + 1;
return length == 0
? Array.Empty<TResult>()
: Indexed().ToArrayByIndex(length, e => e.Key, e => resultSelector(e.Value, e.Key));
var array = new TResult?[length];

foreach (var (idx, el) in indexed)
array[idx] = resultSelector(el, idx);

return array;
}

/// <summary>
/// Creates an array of user-specified length from an
/// <see cref="IEnumerable{T}"/> where a function is used to determine
/// the index at which an element will be placed in the array.
/// Creates an array of user-specified length from an <see cref="IEnumerable{T}"/> where a function is used to
/// determine the index at which an element will be placed in the array.
/// </summary>
/// <param name="source">The source sequence for the array.</param>
/// <param name="length">The (non-negative) length of the resulting array.</param>
Expand All @@ -131,99 +132,105 @@ public static TResult[] ToArrayByIndex<T, TResult>(this IEnumerable<T> source,
/// <typeparam name="T">
/// The type of the element in <paramref name="source"/>.</typeparam>
/// <returns>
/// An array of size <paramref name="length"/> that contains the
/// elements from the input sequence.
/// An array of size <paramref name="length"/> that contains the elements from <paramref name="source"/>.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="indexSelector"/> is <see
/// langword="null"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is less than <c>0</c>. -or- An index
/// returned by <paramref name="indexSelector"/> is invalid for an array of size <paramref
/// name="length"/>.</exception>
/// <remarks>
/// This method forces immediate query evaluation. It should not be
/// used on infinite sequences. If more than one element maps to the
/// same index then the latter element overwrites the former in the
/// resulting array.
/// This method forces immediate query evaluation. It should not be used on infinite sequences. If more than one
/// element maps to the same index then the latter element overwrites the former in the resulting array.
/// </remarks>

public static T[] ToArrayByIndex<T>(this IEnumerable<T> source, int length,
public static T?[] ToArrayByIndex<T>(
this IEnumerable<T> source,
int length,
Func<T, int> indexSelector)
{
return source.ToArrayByIndex(length, indexSelector, (e, _) => e);
}

/// <summary>
/// Creates an array of user-specified length from an
/// <see cref="IEnumerable{T}"/> where a function is used to determine
/// the index at which an element will be placed in the array. The
/// elements are projected into the array via an additional function.
/// Creates an array of user-specified length from an <see cref="IEnumerable{T}"/> where a function is used to
/// determine the index at which an element will be placed in the array. The elements are projected into the array
/// via an additional function.
/// </summary>
/// <param name="source">The source sequence for the array.</param>
/// <param name="length">The (non-negative) length of the resulting array.</param>
/// <param name="indexSelector">
/// A function that maps an element to its index.</param>
/// <param name="resultSelector">
/// A function to project a source element into an element of the
/// resulting array.</param>
/// A function to project a source element into an element of the resulting array.</param>
/// <typeparam name="T">
/// The type of the element in <paramref name="source"/>.</typeparam>
/// <typeparam name="TResult">
/// The type of the element in the resulting array.</typeparam>
/// <returns>
/// An array of size <paramref name="length"/> that contains the
/// projected elements from the input sequence.
/// An array of size <paramref name="length"/> that contains the projected elements from <paramref name="source"/>.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/>, <paramref name="indexSelector"/>, <paramref
/// name="resultSelector"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is less than <c>0</c>. -or- An index
/// returned by <paramref name="indexSelector"/> is invalid for an array of size <paramref
/// name="length"/>.</exception>
/// <remarks>
/// This method forces immediate query evaluation. It should not be
/// used on infinite sequences. If more than one element maps to the
/// same index then the latter element overwrites the former in the
/// resulting array.
/// This method forces immediate query evaluation. It should not be used on infinite sequences. If more than one
/// element maps to the same index then the latter element overwrites the former in the resulting array.
/// </remarks>

public static TResult[] ToArrayByIndex<T, TResult>(this IEnumerable<T> source, int length,
Func<T, int> indexSelector, Func<T, TResult> resultSelector)
public static TResult?[] ToArrayByIndex<T, TResult>(
this IEnumerable<T> source,
int length,
Func<T, int> indexSelector,
Func<T, TResult> resultSelector)
{
Guard.IsNotNull(resultSelector);
return source.ToArrayByIndex(length, indexSelector, (e, _) => resultSelector(e));
}

/// <summary>
/// Creates an array of user-specified length from an
/// <see cref="IEnumerable{T}"/> where a function is used to determine
/// the index at which an element will be placed in the array. The
/// elements are projected into the array via an additional function.
/// Creates an array of user-specified length from an <see cref="IEnumerable{T}"/> where a function is used to
/// determine the index at which an element will be placed in the array. The elements are projected into the array
/// via an additional function.
/// </summary>
/// <param name="source">The source sequence for the array.</param>
/// <param name="length">The (non-negative) length of the resulting array.</param>
/// <param name="indexSelector">
/// A function that maps an element to its index.</param>
/// <param name="resultSelector">
/// A function to project a source element into an element of the
/// resulting array.</param>
/// A function to project a source element into an element of the resulting array.</param>
/// <typeparam name="T">
/// The type of the element in <paramref name="source"/>.</typeparam>
/// <typeparam name="TResult">
/// The type of the element in the resulting array.</typeparam>
/// <returns>
/// An array of size <paramref name="length"/> that contains the
/// projected elements from the input sequence.
/// An array of size <paramref name="length"/> that contains the projected elements from the input sequence.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/>, <paramref name="indexSelector"/>, <paramref
/// name="resultSelector"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is less than <c>0</c>. -or- An index
/// returned by <paramref name="indexSelector"/> is invalid for an array of size <paramref
/// name="length"/>.</exception>
/// <remarks>
/// This method forces immediate query evaluation. It should not be
/// used on infinite sequences. If more than one element maps to the
/// same index then the latter element overwrites the former in the
/// resulting array.
/// This method forces immediate query evaluation. It should not be used on infinite sequences. If more than one
/// element maps to the same index then the latter element overwrites the former in the resulting array.
/// </remarks>

public static TResult[] ToArrayByIndex<T, TResult>(this IEnumerable<T> source, int length,
Func<T, int> indexSelector, Func<T, int, TResult> resultSelector)
public static TResult?[] ToArrayByIndex<T, TResult>(
this IEnumerable<T> source,
int length,
Func<T, int> indexSelector,
Func<T, int, TResult> resultSelector)
{
Guard.IsNotNull(source);
Guard.IsGreaterThanOrEqualTo(length, 0);
Guard.IsNotNull(indexSelector);
Guard.IsNotNull(resultSelector);

var array = new TResult[length];
var array = new TResult?[length];
foreach (var e in source)
{
var i = indexSelector(e);
if (i < 0 || i >= array.Length)
throw new InvalidOperationException("'indexSelector' returned an invalid index for the current object.");
Guard.IsBetween(i, -1, array.Length, "indexSelector(e)");
array[i] = resultSelector(e, i);
}
return array;
Expand Down
8 changes: 4 additions & 4 deletions Tests/SuperLinq.Test/ToArrayByIndexTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ public void ToArrayByIndex(bool withLength, int[] indices)
[Fact]
public void ToArrayByIndexWithBadIndexSelectorThrows()
{
Assert.Throws<InvalidOperationException>(() =>
Assert.Throws<ArgumentOutOfRangeException>(() =>
Seq(42).ToArrayByIndex(_ => -1));

Assert.Throws<InvalidOperationException>(() =>
Assert.Throws<ArgumentOutOfRangeException>(() =>
Seq(42).ToArrayByIndex(_ => -1, BreakingFunc.Of<int, object>()));
}

Expand All @@ -51,10 +51,10 @@ public void ToArrayByIndexWithBadIndexSelectorThrows()
[InlineData(10, 10)]
public void ToArrayByIndexWithLengthWithBadIndexSelectorThrows(int length, int badIndex)
{
Assert.Throws<InvalidOperationException>(() =>
Assert.Throws<ArgumentOutOfRangeException>(() =>
Seq(42).ToArrayByIndex(length, _ => badIndex));

Assert.Throws<InvalidOperationException>(() =>
Assert.Throws<ArgumentOutOfRangeException>(() =>
Seq(42).ToArrayByIndex(length, _ => badIndex, BreakingFunc.Of<int, object>()));
}

Expand Down