2012-11-08 15 views
40

Zakładając Mam następującą tablicę ciągów:LINQ znaleźć indeksy tablicy wartości

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

Czy to possbile używać LINQ, aby uzyskać listę indeksów pasujących jeden ciąg?

Jako przykład chciałbym szukać napisu „średnio” i uzyskać listę zawierającą

2, 4

co oznacza, że ​​„średnio” można znaleźć na str [ 2] i str [4].

Odpowiedz

72

.Select ma rzadko używane przeciążenie, które produkuje indeks. Można go używać tak:

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

Wynikiem będzie lista zawierająca 2 i 4.

Documentation here

+6

Należy zauważyć, że również ['Where'] (http://msdn.microsoft.com/en-us/library/bb549418.aspx) zapewnia to przeciążenie. –

+0

super duper niesamowite dziękuję za to! – Jonesopolis

+0

kod jest nieco mądrzejszy z Where - thanks Tim! – Sven

13

można zrobić to tak:

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 

Kluczem krok używa the overload of Select, który dostarcza bieżący indeks do twojego funktora.

6

Można użyć przeciążenie Enumerable.Select która przechodzi indeksu, a następnie użyć Enumerable.Where na typ anonimowy:

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

Jeśli chcesz tylko do znalezienia pierwszego/ostatniego indeksu, masz również metody wbudowane List.IndexOf i List.LastIndexOf:

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

(lub użyć this overload że biorą indeks początkowy, aby określić pozycję startową)

2

Chociaż można użyć kombinacji Select i Where, to prawdopodobnie dobrym kandydatem do tworzenia własnych funkcji:

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

Nice alternatywa. Twierdzę, że zerowy "itemToFind" powinien być dozwolonym przypadkiem użycia, ale należy głosować na coś innego. – recursive

0

trzeba łączyć wybrać i gdzie operator, porównując z przyjętą odpowiedź to będzie tańsze ponieważ nie będzie wymagać obiektów pośrednich:

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

po pierwsze, Twój kod faktycznie nie iteracyjne nad listy dwa razy, to tylko iteracje raz.

Powiedziawszy, twój Select naprawdę dostaje sekwencję wszystkich indeksów; że łatwiej jest zrobić z Enumerable.Range:

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

Zrozumienie, dlaczego ta lista nie jest faktycznie powtórzyć dwukrotnie zajmie trochę przyzwyczaić. Spróbuję podać podstawowe wyjaśnienie.

Powinieneś pomyśleć o większości metod LINQ, takich jak Wybierz i Gdzie jako potok. Każda metoda wykonuje niewielką część pracy.W przypadku Select dajesz mu metodę i zasadniczo mówi: "Kiedy ktoś poprosi mnie o mój następny przedmiot, najpierw zapytam o moją sekwencję wejściową dla elementu, a następnie użyję metody, którą mam, aby przekształcić ją w coś innego, a następnie oddaj ten przedmiot komuś, kto mnie używa ". Gdzie, mniej więcej, mówi "kiedy ktoś poprosi mnie o przedmiot, zapytam o moją sekwencję wejściową dla przedmiotu, jeśli funkcja mówi, że jest dobra, przekażę go, jeśli nie, będę nadal prosić o przedmioty dopóki nie dostanę tego, który przechodzi. "

Tak więc, gdy je łączysz, to co się dzieje, ToList prosi o pierwszy przedmiot, przechodzi do Where to as to dla pierwszego przedmiotu, Where goes to Select i prosi o pierwszy przedmiot, Select idzie do listy, aby zapytać to dla jego pierwszego przedmiotu. Lista zawiera wtedy pierwszy przedmiot. Wybierz, a następnie zamień ten element na to, czego potrzebuje, aby wypluć (w tym przypadku tylko int 0) i nadaj mu wartość Where. Skąd bierze ten element i uruchamia jego funkcję, która określa, że ​​jest prawdziwa i wypluwa 0 do ToList, co dodaje go do listy. To wszystko dzieje się jeszcze 9 razy. Oznacza to, że Select w końcu poprosi o każdy element z listy dokładnie jeden raz, i będzie podawał każdy z jego wyników bezpośrednio do Where, który będzie podawał wyniki, które "przechodzą test" bezpośrednio do ToList, który przechowuje je na liście . Wszystkie metody LINQ są starannie zaprojektowane, aby tylko raz wykonać sekwencję źródłową (gdy są one iterowane raz).

Należy zauważyć, że chociaż początkowo wydaje się to skomplikowane, w rzeczywistości jest to całkiem łatwe dla komputera. W rzeczywistości nie jest tak intensywny, jak może się wydawać na początku.

Powiązane problemy