使用Linq获取列表中所有匹配值的索引

39

大家好,有Linq方面的专家吗?

我刚才问了一个非常类似的问题,并且知道解决方案可能非常简单,但仍然发现自己无法理解如何使用linq以最有效的方式完成这个相当简单的任务。

我的基本情况是我有一个值的列表,例如:

Lst1:
a
a
b
b
c
b
a
c
a

我想创建一个新的列表,其中包含Lst1中所有值为"a"的索引。

LstIndexes:
0
1
6
8

现在,我知道我可以使用循环来完成这个任务(但我更倾向于使用Linq),我甚至已经想出一种用以下方式使用Linq完成此任务的方法:

LstIndexes= Lst1.Select(Function(item As String, index As Integer) index) _
                .Where(Function(index As Integer) Lst1(index) = "a").ToList

我的问题是它要两次遍历列表,因此效率低下。

如何使用Linq以最高效的方式获得结果?

谢谢!!!


6
它在哪里对列表进行了两次迭代? - Austin Salonen
我想更好的问题是 - 你为什么认为它要两次迭代列表? - Austin Salonen
3个回答

76

首先,你的代码实际上并没有对列表进行两次迭代,它只迭代了一次。

话虽如此,你的Select实际上只是获取了所有索引的序列;这可以更容易地使用Enumerable.Range完成:

var result = Enumerable.Range(0, lst1.Count)
             .Where(i => lst1[i] == "a")
             .ToList();

理解为何列表实际上没有被迭代两次需要一些时间来适应。我尝试给出一个基本的解释。

大多数LINQ方法(例如Select和Where)应该被看作是一个管道。每个方法都完成一些微小的工作。在Select的情况下,你给它一个方法,它基本上会说:“每当有人要求我下一个项目时,我将首先向我的输入序列请求一个项目,然后使用我拥有的方法将其转换为其他东西,最后将该项目提供给使用我的人。”而Where则更多地表示:“每当有人要求我一个项目时,我将向我的输入序列请求一个项目,如果函数说它很好,我就会传递它,如果不是,我将继续请求项目直到我获得通过的一个。”

因此,当你把它们链起来时,ToList会请求第一个项目,它会向Where请求第一个项目,Where会向Select请求第一个项目,Select会去列表中请求第一个项目。然后列表提供它的第一个项目。 Select然后将该项转换为它需要输出的内容(在这种情况下,只是int 0),并将其提供给WhereWhere获取该项并运行其函数,确定它为真,然后将0输出到ToList,将其添加到列表中。这整个过程会重复9次。这意味着Select将从列表中恰好请求每个项目一次,并直接将其结果提供给Where,后者将“通过测试”的结果直接提供给ToList,将其存储在列表中。所有的LINQ方法都被精心设计为只在迭代一次时才迭代源序列。

请注意,虽然这可能一开始对你来说很复杂,但计算机实际上很容易完成所有这些工作。它实际上并不像乍看起来那么性能密集。


Servy,感谢您添加了那个解释 - 这确实帮助我更好地理解事情!!!真的非常感谢您!!! - John Bustos
@JohnBustos 就记录而言,我的代码执行时间与你的代码并没有太大差别,只是对于读者来说更加清晰,并且在屏幕上占用的代码更少。 - Servy
实际上,@Servy,Enumerable.Range(0, lst1.Count)是正确的,因为Range自动排除最高索引。您编写的代码将在输出中漏掉'8'。 - Dean Radcliffe
注意:我对 Enumerable.Range 表现出的原因的解释并不完全正确,但我认为没有人注意到 :) - Dean Radcliffe
@DeanRadcliffe 是的,我注意到了,但由于我的代码作为答案没有起作用,所以我不觉得指责你是正确的。当然,原因是第二个参数不是结束索引,而是要在结果序列中包含的项目数。由于它从 0 开始(第一个参数),这意味着返回的最后一个值将是计数 - 1。 - Servy
1
+1 因为与其他两个答案相比,它更易于阅读和理解查询的作用。 - StupidOne

8

这样做是有效的,但可能不够简洁。

var result = list1.Select((x, i) => new {x, i})
                  .Where(x => x.x == "a")
                  .Select(x => x.i);

2
这个很不错,对我来说运行得非常好。
   static void Main(string[] args)
    {
        List<char> Lst1 = new List<char>();
        Lst1.Add('a'); 
        Lst1.Add('a');   
        Lst1.Add('b');   
        Lst1.Add('b');   
        Lst1.Add('c');   
        Lst1.Add('b');   
        Lst1.Add('a');   
        Lst1.Add('c');
        Lst1.Add('a');

        var result = Lst1.Select((c, i) => new { character = c, index = i })
                         .Where(list => list.character == 'a')
                         .ToList();
    }

这将导致一个匿名类型的列表,其中包含索引和字符; 他只需要索引。 - Servy
@Servy,嗯,...Select(o => o.index); :) - tukaef
1
@2kay 或者你可以使用我提出的解决方案,这样你就不需要两个选择器来执行并撤销映射。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接