2012-11-08 37 views
40

假設我有以下字符串數組的數組索引:LINQ找到一個價值

string[] str = new string[] {"max", "min", "avg", "max", "avg", "min"} 

是否possbile使用LINQ獲得匹配一個字符串索引列表?

作爲一個例子,我想要搜索的字符串「平均」,並獲得含有

2的列表,4

意味着「平均」可以在STR [發現2]和str [4]。

回答

72

.Select有一個很少使用的產生索引的重載。您可以使用它像這樣:

str.Select((s, i) => new {i, s}) 
    .Where(t => t.s == "avg") 
    .Select(t => t.i) 
    .ToList() 

結果將是包含2和4

Documentation here

+6

請注意,還有['Where'](http://msdn.microsoft.com/en-us/library/bb549418.aspx)提供了過載。 –

+0

超級duper真棒謝謝你! – Jonesopolis

+0

代碼有點聰明與哪裏 - 謝謝蒂姆! – Sven

13

列表可以像下面這樣做:

str.Select((v,i) => new {Index = i, Value = v}) // Pair up values and indexes 
    .Where(p => p.Value == "avg") // Do the filtering 
    .Select(p => p.Index); // Keep the index and drop the value 

的關鍵步驟是使用the overload of Select將當前索引提供給函子。

6

您可以使用Enumerable.Select是經過指數的過載,然後使用上一個匿名類型Enumerable.Where

List<int> result = str.Select((s, index) => new { s, index }) 
         .Where(x => x.s== "avg") 
         .Select(x => x.index) 
         .ToList(); 

如果你只是想找到的第一個/最後一個索引,你還內建方法List.IndexOfList.LastIndexOf

int firstIndex = str.IndexOf("avg"); 
int lastIndex = str.LastIndexOf("avg"); 

(或者你可以使用this overload是採取起始索引指定起始位置)

2

雖然你可以使用的SelectWhere組合,這很可能是爲使自己的功能一個很好的候選人:

public static IEnumerable<int> Indexes<T>(IEnumerable<T> source, T itemToFind) 
{ 
    if (source == null) 
     throw new ArgumentNullException("source"); 

    int i = 0; 
    foreach (T item in source) 
    { 
     if (object.Equals(itemToFind, item)) 
     { 
      yield return i; 
     } 

     i++; 
    } 
} 
+0

不錯的選擇。我認爲一個空的'itemToFind'應該是一個合法用例,但是對不同的東西有投票權。 – recursive

0

你需要組合選擇並在運營商,比較公認的答案,這將是更便宜,因爲不需要中間對象:

public static IEnumerable<TResult> SelectWhere<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, bool> filter, Func<TSource, int, TResult> selector) 
     { 
      int index = -1; 
      foreach (var s in source) 
      { 
       checked{ ++index; } 
       if (filter(s)) 
        yield return selector(s, index); 
      } 
     } 
1

首先,您的代碼實際上並未在列表中迭代兩次,它只是迭代一次。

也就是說,你的Select實際上只是得到所有索引的序列;這是更容易與Enumerable.Range完成:

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

理解爲什麼這個列表實際上不重複兩次將需要一些時間來適應。我會盡量給出一個基本的解釋。

您應該考慮大多數LINQ方法,例如Select和Where作爲管道。每種方法都會做一些小小的工作。在Select的情況下,你給它一個方法,它實質上是這樣說的:「每當有人問我爲我的下一個項目,我會先問我的輸入序列的一個項目,然後使用我必須將其轉換爲其他東西的方法,然後把這件物品交給誰來使用我。「或多或少地說,「每當有人問我一件物品時,我會問我的輸入序列中是否有物品,如果該功能說不錯,我會把它傳下去,如果不是,我會繼續詢問物品直到我得到一個通過。「

所以,當你鏈接他們會發生什麼是ToList要求的第一個項目,它去哪裏,因爲它的第一個項目,去哪裏選擇並要求它的第一個項目,選擇去列表問它的第一個項目。該清單然後提供它的第一個項目。然後選擇將該項目轉換爲需要吐出的項目(在這種情況下,只是int 0)並將其提供給Where。哪裏需要該項目,並運行它的功能,它確定它是真實的,因此吐出0到ToList,它將它添加到列表中。那整件事再發生9次。這意味着Select將最終要求從列表中的每個項目只需要一次,並且它會將每個結果直接提供給Where,這會將「通過測試」的結果直接提供給ToList,將ToList存儲在列表中。所有的LINQ方法都經過精心設計,只有一次迭代源序列(迭代一次)。

請注意,雖然這在起初看起來很複雜,但實際上計算機很容易完成所有這些工作。這實際上並不像起初看起來那樣表現密集。