【问题标题】:Split array into chunks - is there faster method?将数组拆分成块 - 有更快的方法吗?
【发布时间】:2018-05-20 20:39:34
【问题描述】:

我正在寻找将数组拆分为固定大小块的最快方法(当然,最后一个可以更小)。我浏览了整个网站,没有发现任何性能方面的比较,所以我写了它们,结果如下:

以微秒为单位的时间,平均值/错误/标准差

对于int[] - 30.02 | 0.1002 | 0.0937

对于IEnumerable<int> - 76.67 | 0.2146 | 0.1902

更新:以下版本(在@Markus 的回答中)是 139.5 | 0.6702 | 0.5597

在 SO 上最受欢迎且经常推荐使用 LINQ 的 GroupByindex/chunkSize 的方法是不行的 - 267 微秒比上述任何一种实现都长得多。

有没有更快的分割数组的方法?

附: 这是ArrayIEnumerable<T> 的代码:

    /// <summary>
    /// Splits <paramref name="source"/> into chunks of size not greater than <paramref name="chunkMaxSize"/>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source">Array to be split</param>
    /// <param name="chunkMaxSize">Max size of chunk</param>
    /// <returns><see cref="IEnumerable{T}"/> of <see cref="Array"/> of <typeparam name="T"/></returns>
    public static IEnumerable<T[]> AsChunks<T>(this T[] source, int chunkMaxSize)
    {
        var pos = 0;
        var sourceLength = source.Length;
        do
        {
            var len = Math.Min(pos + chunkMaxSize, sourceLength) - pos;
            if (len == 0)
            {
                yield break;;
            }
            var arr = new T[len];
            Array.Copy(source, pos, arr, 0, len);
            pos += len;
            yield return arr;
        } while (pos < sourceLength);
    }

    /// <summary>
    /// Splits <paramref name="source"/> into chunks of size not greater than <paramref name="chunkMaxSize"/>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"><see cref="IEnumerable{T}"/> to be split</param>
    /// <param name="chunkMaxSize">Max size of chunk</param>
    /// <returns><see cref="IEnumerable{T}"/> of <see cref="Array"/> of <typeparam name="T"/></returns>
    public static IEnumerable<T[]> AsChunks<T>(this IEnumerable<T> source, int chunkMaxSize)
    {
        var arr = new T[chunkMaxSize];
        var pos = 0;
        foreach (var item in source)
        {
            arr[pos++] = item;
            if (pos == chunkMaxSize)
            {
                yield return arr;
                arr = new T[chunkMaxSize];
                pos = 0;
            }
        }
        if (pos > 0)
        {
            Array.Resize(ref arr, pos);
            yield return arr;
        }
    }

P.P.S 带有 BenchmarkDotNet 测试的完整解决方案是 on GitHub

【问题讨论】:

  • Span&lt;T&gt; 来了。
  • @ErikPhilips,这是一件坏事吗?如果实现起来既快速又容易——为什么还要坚持缓慢的实现呢?
  • @mjwills 基于 LINQ 的速度非常慢,我怀疑这个会更快。将添加到测试并更新问题。
  • @ErikPhilips 您对一般优先级的看法是正确的,但总有一个“但是”,对吧?尽管您可能不了解我的需求,但如果可以在其他地方讨论这些主题,我将不胜感激。我请求了其他程序员的帮助,仅此而已。

标签: c# .net arrays performance


【解决方案1】:

不太确定这是如何叠加的 (ArraySegment),但请尝试一下。我已经避免使用迭代器,但不确定这是否真的是一个收获。

public static IEnumerable<T[]> AsChunks<T>(
    this T[] source, int chunkMaxSize)
{
    var chunks = source.Length / chunkMaxSize;
    var leftOver = source.Length % chunkMaxSize;
    var result = new List<T[]>(chunks + 1);
    var offset = 0;

    for (var i = 0; i < chunks; i++)
    {
        result.Add(new ArraySegment<T>(source,
                                       offset, 
                                       chunkMaxSize).ToArray());
        offset += chunkMaxSize;
    }

    if (leftOver > 0)
    {
        result.Add(new ArraySegment<T>(source, 
                                       offset, 
                                       leftOver).ToArray());
    }

    return result;
}

【讨论】:

  • 谢谢。和我的差不多。 30.64 我们 | 0.1637 我们 | 0.1531 我们。
  • 更感谢ArraySegment。看来我已经完全错过了那个。学习新东西永远不会晚:)
  • @AlexSeleznyov 请注意,您可以考虑直接返回ArraySegments,而不调用ToArray。然后根本不会发生复制。对于许多用例,这与返回多个数组一样好。
  • @evk 好点,我应该提到这一点。我想保留原来的签名,但如果你能逃脱 IEnumerable&lt;ArraySegment&lt;T&gt;&gt; 的收获是巨大的。
  • 是的,最好重构为IEnumerable&lt;IReadOnlyList&lt;T&gt;&gt;。但是最好不要返回 ArraySegment 本身,它甚至明确实现了索引器,所以很难使用。
【解决方案2】:

我使用了这个代码:

来源:http://blogs.msdn.com/b/pfxteam/archive/2012/11/16/plinq-and-int32-maxvalue.aspx

public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int batchSize) {
    if (batchSize <= 0) {
        throw new ArgumentOutOfRangeException(nameof(batchSize));
    }
    using(var enumerator = source.GetEnumerator()) {
        while (enumerator.MoveNext()) {
            yield return YieldBatchElements(enumerator, batchSize - 1);
        }
    }
}

private static IEnumerable<T> YieldBatchElements<T>(IEnumerator<T> source, int batchSize) {
    yield return source.Current;
    for (var i = 0; i < batchSize && source.MoveNext(); i++) {
        yield return source.Current;
    }
}

【讨论】:

  • 139.5 微秒。不如Group 差,但比我的任何一个都慢。
【解决方案3】:

你可以使用这几行代码

                int chunkSize = 4;
                int skipIndex = 0;
                string[] words = { "one", "two", "three", "four", "five", "six" };
                string[][] chunckResult = new string[words.Length][];

                for (int index = 0; index < words.Length; index++)
                {
                    chunckResult[index] = words.Skip(skipIndex).Take(chunkSize).ToArray();
                    skipIndex += chunkSize;
                    if (skipIndex == words.Length)
                    {
                        break;
                    }
                }

【讨论】:

  • 您是否从性能角度测试过您的代码?几点了?
  • 欢迎来到 Stack Overflow!一般来说,如果答案包含对代码的用途的解释,以及为什么在不介绍其他人的情况下解决了问题,那么答案会更有帮助。
  • 代码以 100% 的包含率和 0% 的独占率执行
  • 执行时间为 TotalSeconds 0.0002724
猜你喜欢
  • 2018-12-24
  • 2016-03-29
  • 2012-01-19
  • 2012-03-02
  • 1970-01-01
  • 1970-01-01
  • 2011-10-26
  • 1970-01-01
相关资源
最近更新 更多