【问题标题】:Convert IEnumerable<int> to int[] without ToArray() [closed]在没有 ToArray() 的情况下将 IEnumerable<int> 转换为 int[] [关闭]
【发布时间】:2016-03-10 18:36:34
【问题描述】:

如何在没有ToArray() 的情况下将IEnumerable&lt;int&gt; 转换为int[]?我需要一个比ToArray() 工作得更快的方法或函数。 我有一个大型 int 大型 state 和两个小型大型 left 和 right 使用方法的 Take and Skip。代码:

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

枢轴 - 分割点。

【问题讨论】:

  • 如果您提供更多信息,您只会更快地获得它。你有比简单的IEnumerable&lt;T&gt; 更多的信息吗?
  • How to create a Minimal, Complete, and Verifiable example 这样人们就可以用它来重现问题并给出更好的答案。
  • 我能够使用 ToArray() 在约 240 毫秒内将 1000 万个整数的枚举转换为数组。这还不够快吗?会不会有其他问题导致它需要这么长时间?
  • @AndersForsgren but just because a question isn't good doesn't mean it deserves downvotes. 实际上,正是的意思。问题需要不仅仅是“清楚”才能成为适当的高质量问题。问题确实需要明确,但不只是需要明确。除此之外,它们还需要经过充分研究、范围合理、主题明确且有用。
  • “我有一个大的 int 数组。通过方法的 Take and Skip 我将它分成两个大的”。把它放在问题中,它会大大改善。

标签: c# ienumerable toarray


【解决方案1】:

有四种可能的方法可以从IEnumerable&lt;int&gt; 获取int[]。按速度排序:

  1. 它已经是一个int[],把它扔回去。
  2. 它是一个集合类型,有自己的Count 和CopyTo,使用它。
  3. 它是一种类型,您可以找到它的大小,分配该大小的数组,并通过遍历源来填充它。
  4. 分配一个数组,填充它,但如果到达末尾,则重新分配一个新数组并复制过来。继续这样做,直到完成,然后根据需要修剪数组。

Linq 的 ToArray() 不会做第一个,因为 ToArray() 旨在保护源代码免受 ToArray() 的结果的更改。

在其余部分中,Linq 尝试选择最快的选项。因此,您不会得到太大的改进。

如果您可以接受强制转换为数组,那么您可以通过以下方式解决这种情况:

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

(如果您知道intEnum 始终是一个数组,那么您可以直接进行强制转换。相反,如果您知道intEnum 从来不是一个数组,那么上面的方法会更慢,因为它总是有尝试的成本却从未受益于实现这种方法)。

否则,虽然您不会做得更好,除非您知道可枚举的大小,但它不是ICollection&lt;int&gt;,因此 linq 无法找到它本身。

编辑:

添加到问题的评论:

我有一个大的 int 数组。通过method的Take and Skip我把它分成了两个大块。

如果您使用的是 .NET Core,那么这个案例已经针对上个月合并到祝福存储库中的提交进行了优化,所以如果您使用最新版本的 corefx 或等待更新,那么您已经获得这个优化了。

否则,您可以应用与该版本的 Linq 相同的逻辑:

让我们调用我们的源数组sourceArray。

我们有一个来自var en = sourceArray.Skip(amountSkipped).Take(amountTaken); 之类的IEnumerable&lt;int&gt;

如果amountSkipped 或amountTaken 小于零,则此处应使用零。如果Skip() 被忽略,那么amountSkipped 应该使用零。如果Take() 被忽略,那么int.MaxValue 应该用于amountTaken。

输出数组的大小为:

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

然后我们可以创建并分配给一个数组:

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

现在array 将被填充而无需分配和修剪,大约节省了log count 分配。这确实会更快。

同样,如果您使用的是最新的 corefx,那么这已经为您完成了,并进一步优化了能够避免枚举器分配。

不过,如果只对Skip 和Take 做了所有操作,那么您可以完全放弃它并直接操作:

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

根本不需要Skip() 和Take()。

再次编辑:

现在的问题是:

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

我们可以简单地做到这一点:

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);

而这确实会是一个相当大的进步。

如果我们知道pivot 介于0 和state.Length 之间,那么我们可以使用更简单的:

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);

【讨论】:

  • 2和3不是基本一样吗?或者至少ToArray 没有专门处理第三种情况。要么是ICollection&lt;T&gt;,要么是 2,否则是 4。
  • @juharr in 3 我的意思是迭代,而不是CopyTo。 ToArray() 始终处理 2 和 4,在当前 .NET Core 版本中处理 3,包括 IList 上的 Select() 之类的情况(它可以通过在源列表上执行 Count 找到大小,但必须实现CopyTo 本身)。
【解决方案2】:

这很大程度上取决于 IEnumerable 背后的实际类型。如果它已经是一个数组,你可以转换它。如果是其他任何事情,除了ToArray() 之外,通常没有办法做到这一点。您可能需要考虑为什么它很慢。也许下面有一个 LINQ 查询可以进行数据库调用,可以优化

【讨论】:

    猜你喜欢
    • 2014-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-11
    • 1970-01-01
    • 2019-08-27
    • 2011-07-11
    • 1970-01-01
    相关资源
    最近更新 更多