【问题标题】:Elegant way of getting indexes of an array获取数组索引的优雅方式
【发布时间】:2012-11-26 19:53:11
【问题描述】:

我很难通过以下示例切换到 LINQ:

int[] inputs = { 5, 3, 5, 66, 4, 5 };
int[] indexes; // I want to have the indexes of '5's in 
               // the inputs array, which is { 0, 2, 5 }

// My way (traditional for loop)

List<int> indexesList = new List<int>();
for (int i = 0; i < inputs.Length; i++)
    if (inputs[i] == 5)
        indexesList.Add(i);

indexes = indexesList.ToArray();

// LINQ way

var indexes = inputs.Select((s, i) => new { i, s })
                    .Where(t => t.s == 5)
                    .Select(t => t.i).ToArray();

问题 1. 就效率(速度、内存使用)而言,如果我将代码转换为 LINQ,我会有什么优势吗?

问题 2。如果为真,是否有更优雅的方式使用 LINQ 来解决问题?

PS:请注意,这个方法在我的实际项目中被非常频繁地调用。因此,在速度或内存使用方面稍有改进将对整个过程有很大帮​​助。

【问题讨论】:

  • inputs 中平均有多少个元素。优化取决于非常具体的情况。
  • 我已经编辑了你的标题。请参阅“Should questions include “tags” in their titles?”,其中的共识是“不,他们不应该”。
  • @igrimpe, inputs 是一个包含 784 元素的数组。但是,我调用这个方法的次数超过了10k 次。

标签: c# linq for-loop


【解决方案1】:

如果您使用 LINQ 代码,您肯定不会获得性能优势。只需阅读即可明显看出:为了将每个值与其索引相关联,创建了一个新对象:

(s, i) => new { i, s }

这些匿名对象只是将索引与值粘合在一起的容器;因此,与直接保持计数器相比,所有相关的内存管理都是纯粹的开销。

有人可能会说 LINQ 提供了可读性的优势,因为大多数时候它强调的是意图而不是机制,但在这种特殊情况下,我不认为这比平淡无奇的解决方案要好。

【讨论】:

  • 是的,但是 LINQ 还是有问题。如果他只需要索引,则不需要创建对象/索引对。
  • @igrimpe:没有其他方法可以做到这一点,同时使用 LINQ 过滤值。给定的解决方案尽可能直接,除非您编写一个迭代器块(但它不再是真正的“普通 LINQ”,它是一个隐藏在方法调用后面的循环,您可以在没有 LINQ 的情况下做同样的事情)。
  • 从 i in enumerable.range(0, inputs.length) where inputs(i).s = 5 select i.
  • @igrimpe:如果inputs 被键入为IEnumerable 而不是一个数组,你就不能真正做到这一点。
  • OP 将input 定义为数组。
【解决方案2】:

我会说那里的 linq 方式实际上效率较低,因为您首先将结构转换为元组集合,然后选择索引。

尽管它可能看起来更优雅,但我也想说理解那里到底发生了什么有点困难。

我个人只是推荐迭代。


如果您真的需要提高效率,我建议您在可行的情况下对输入进行排序。第一次对它们进行排序时,需要一些时间,但如果您多次搜索同一个列表,它会收回成本。

【讨论】:

  • 输入数组是一个通用数组,每次都完全不同。因此,在这种情况下,排序可能不会有任何帮助。
【解决方案3】:

如果您想以 LINQ 方式执行此操作,我建议您实现自己的扩展方法,以返回与谓词匹配的元素的索引。

public static IEnumerable<int> IndexesWhere<TSource>(
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate)
{
    int i = 0;

    foreach (TSource element in source)
    {
        if (predicate(element))
            yield return i;

        i++;
    }
}

你可以这样称呼它:

var indexes = inputs.IndexesWhere(s => s == 5);

使用这种方法的优点是:

  1. 您避免创建匿名类型的实例(与 Select((s, i) =&gt; new { i, s }) 方法不同)。
  2. 可以按需枚举序列,这意味着如果您只想执行indexes.Take(2) 之类的操作,它比填充完整的索引数组更有效。

【讨论】:

  • 关于扩展方法的快速问题:您将扩展代码准确地保存在哪里?在使用它的同一个文件中?在一个通用的类库中?在同一项目的单独文件中?
  • 分离文件和静态类,名称对应于我要扩展的基类(或接口),加上“Extensions”一词。在上述情况下,我将其称为EnumerableExtensions。
  • 与传统循环方式相比,此答案的性能问题如何?
  • 这将归结为枚举器的性能(例如在foreach 循环中)与for 循环。上述方法的主要优点是它允许延迟执行(这是大多数 LINQ 的中心思想)。
【解决方案4】:

这是一种使用 LINQ 并避免创建匿名对象的方法。

var indexes = Enumerable.Range(0, inputs.Length)
                        .Where(x => inputs[x] == 5)
                        .ToArray();

它的性能可能类似于使用 for 循环,但您必须对其进行测试才能确定。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-01
    • 1970-01-01
    • 2020-07-25
    • 1970-01-01
    • 2012-05-17
    • 2014-11-03
    • 1970-01-01
    相关资源
    最近更新 更多