Search code examples
c#ienumerabletoarray

Convert IEnumerable<int> to int[] without ToArray()


How can I convert IEnumerable<int> to int[] without ToArray()? I need a method or function that will work faster than ToArray(). I have one big int massive state and two small massive left and right using method's Take and Skip. Code:

 int[] left = state.Take(pivot).ToArray();
 int[] right = state.Skip(pivot).ToArray();

pivot - split point.


Solution

  • There are four possible ways to get an int[] from an IEnumerable<int>. In order of speed:

    1. It already is an int[], cast it back.
    2. It is a collection type with its own Count and CopyTo, use that.
    3. It is a type for which you can find the size, allocate an array of that size, and fill it by iterating through the source.
    4. Allocate an array, fill it but if you reach the end then re-allocate a new array and copy over. Keep doing this until you are done, then trim the array if necessary.

    Linq's ToArray() will not do the first, as ToArray() is meant to protect the souce from changes to the result of ToArray().

    Of the rest, Linq tries to pick the fastest option. As such, you aren't going to get much of an improvement.

    If casting to an array would be acceptable to you then you will be able to cover that case with:

    int[] array = intEnum as int[] ?? intEnum.ToArray();
    

    (If you know intEnum is always an array, then you can just do the cast directly. Conversely if you know intEnum is never an array then the above is just slower, as it always has the cost of attempting without ever having the benefit of achieving that approach).

    Otherwise while you aren't going to be able to do significantly better, except in the case where you know the size of the enumerable, but it is not an ICollection<int> so linq can't find it itself.

    Edit:

    A comment added to the question:

    i have one big int array. By method's Take and Skip I divide it into two massive.

    If you're using .NET Core then this case is already optimised for as of a commit merged into the blessed repository last month, so if you use the latest build of corefx or wait for an update then you've already got this optimised.

    Otherwise you can apply the same logic as that version of Linq does:

    Let's call our source array sourceArray.

    We have an IEnumerable<int> from something like var en = sourceArray.Skip(amountSkipped).Take(amountTaken);

    If amountSkipped or amountTaken are less than zero, then zero should be used here. If the Skip() was left out then zero should be used for amountSkipped. If the Take() was left out then int.MaxValue should be used for amountTaken.

    The size of the output array will be:

    int count = Math.Min(sourceArray.Length - amountSkipped, amountTaken);
    

    We can then create and allocate to an array:

    int[] array = new int[count];
    int index = 0;
    foreach (int item in en)
      array[index] = item;
    

    Now array will be filled without the need to allocate and trim, saving roughly log count allocations. This will indeed be faster.

    Again though, if you're using the lastest corefx then this is already done for you, with a further optimisation of being able to avoid the enumerator allocation.

    Still, if all that was done was to Skip and Take then you could just drop that entirely and operate directly:

    int count = Math.Min(sourceArray.Length - amountSkipped, amountTaken);
    int[] array = new int[count];
    Array.Copy(sourceArray, amountSkipped, array, 0, count);
    

    No need to Skip() and Take() at all.

    Edit again:

    The question now has:

    int[] left = state.Take(pivot).ToArray();
    int[] right = state.Skip(pivot).ToArray();
    

    We can make this simply:

    int leftCount = Math.Min(state.Length, Math.Max(pivot, 0));
    int[] left = new int[leftCount];
    Array.Copy(state, 0, left, 0, leftCount);
    
    int rightCount = Math.Min(state.Length - leftCount);
    int[] right = new int[rightCount];
    Array.Copy(state, leftCount, right, 0, rightCount);
    

    And this will indeed be a considerable improvement.

    If we know pivot is between 0 and state.Length, then we can use the simpler:

    int[] left = new int[pivot];
    Array.Copy(state, 0, left, 0, pivot);
    
    int[] right = new int[state.Length - pivot];
    Array.Copy(state, pivot, right, 0, state.Length - pivot);