Znajdź pozycję na liście według LINQ?

226

Tutaj mam prosty przykład, aby znaleźć element na liście ciągów. Zwykle używam pętli lub anonimowego delegata, aby to zrobić w następujący sposób:

int GetItemIndex(string search)
{
   int found = -1;
   if ( _list != null )
   {
     foreach (string item in _list) // _list is an instance of List<string>
     { 
        found++;
        if ( string.Equals(search, item) )
        {
           break;
        }
      }
      /* use anonymous delegate
      string foundItem = _list.Find( delegate(string item) {
         found++;
         return string.Equals(search, item);
      });
      */
   }
   return found;
}

LINQ jest dla mnie nowy. Jestem ciekawy, czy mogę użyć LINQ, aby znaleźć pozycję na liście? Jak to możliwe?

David.Chu.ca
źródło
To wspaniale. Są to jednak wszystkie style wyrażania lamda. Używam tutaj prostej listy. Lista może być klasą z kilkoma właściwościami, a niektóre z nich służą do wyszukiwania. Tak więc każdy sposób wyszukiwania LINQ, taki jak „z… w… gdzie… wybierz…”
David.Chu.ca
Nie, przepraszam. Większość z tych metod (pierwsza, pojedyncza, dowolna, ...) nie może być bezpośrednio przetłumaczona na tę formę.
R. Martinho Fernandes
Nieważne, w rzeczywistości możesz pozbyć się lambdas w kilku przypadkach ...
R. Martinho Fernandes
Świetne odpowiedzi! Chcę tylko poznać smak wyszukiwania LINQ z przypadku wyliczenia.
David.Chu.ca

Odpowiedzi:

478

Jest kilka sposobów (pamiętaj, że to nie jest pełna lista).

1) Single zwróci pojedynczy wynik, ale zgłosi wyjątek, jeśli nie znajdzie żadnego lub więcej niż jednego (który może, ale nie musi być taki, jaki chcesz):

string search = "lookforme";
List<string> myList = new List<string>();
string result = myList.Single(s => s == search);

Uwaga SingleOrDefault()będzie zachowywać się tak samo, z tym wyjątkiem, że zwróci null dla typów referencyjnych lub wartość domyślną dla typów wartości, zamiast zgłaszać wyjątek.

2) Gdzie zwróci wszystkie elementy, które spełniają twoje kryteria, więc możesz otrzymać IEnumerable z jednym elementem:

IEnumerable<string> results = myList.Where(s => s == search);

3) Najpierw zwróci pierwszy element, który spełnia Twoje kryteria:

string result = myList.First(s => s == search);

Uwaga FirstOrDefault()będzie zachowywać się tak samo, z tym wyjątkiem, że zwróci null dla typów referencyjnych lub wartość domyślną dla typów wartości, zamiast zgłaszać wyjątek.

Rex M.
źródło
35
Świetna odpowiedź. Znalazłem SingleOrDefault jako moją odpowiedź z wyboru - tak samo jak Single, ale zwraca „null”, jeśli nie może jej znaleźć.
Eddie Parker,
2
Nie znałem ani Single (), ani SingleOrDefault (). Bardzo przydatne.
draconis
Czy tych metod można używać z innymi kolekcjami, takimi jak ReadOnlyCollectionlub ObservableCollection?
yellavon
@yellavon są to metody rozszerzenia dla dowolnego typu, który implementuje IEnumerable<T>lubIQueryable<T>
Rex M
4
Jedną z rzeczy, na które należy zwrócić uwagę przy korzystaniu z SingleOrDefault, jest to, że ponieważ zgłasza wyjątek, jeśli na liście znajduje się więcej niż jedno dopasowanie, musi iterować każdy element, w którym FirstOrDefault przestanie szukać po znalezieniu pierwszego dopasowania. msdn.microsoft.com/en-us/library/bb342451(v=vs.110).aspx
DavidWainwright
73

Jeśli chcesz indeks elementu, zrobi to:

int index = list.Select((item, i) => new { Item = item, Index = i })
                .First(x => x.Item == search).Index;

// or
var tagged = list.Select((item, i) => new { Item = item, Index = i });
int index = (from pair in tagged
            where pair.Item == search
            select pair.Index).First();

Nie możesz pozbyć się lambda w pierwszym przejściu.

Pamiętaj, że wyrzuci to, jeśli przedmiot nie istnieje. Rozwiązuje to problem, odwołując się do zerowych wartości ints:

var tagged = list.Select((item, i) => new { Item = item, Index = (int?)i });
int? index = (from pair in tagged
            where pair.Item == search
            select pair.Index).FirstOrDefault();

Jeśli chcesz przedmiot:

// Throws if not found
var item = list.First(item => item == search);
// or
var item = (from item in list
            where item == search
            select item).First();

// Null if not found
var item = list.FirstOrDefault(item => item == search);
// or
var item = (from item in list
            where item == search
            select item).FirstOrDefault();

Jeśli chcesz policzyć liczbę pasujących elementów:

int count = list.Count(item => item == search);
// or
int count = (from item in list
            where item == search
            select item).Count();

Jeśli chcesz wszystkie pasujące elementy:

var items = list.Where(item => item == search);
// or
var items = from item in list
            where item == search
            select item;

I nie zapomnij sprawdzić listy nullw żadnym z tych przypadków.

Lub użyj (list ?? Enumerable.Empty<string>())zamiast list.

Dziękujemy Pavelowi za pomoc w komentarzach.

R. Martinho Fernandes
źródło
2
Dwa punkty. Po pierwsze, nie ma tu potrzeby używania string.Equals- nie ma w tym nic złego ==. Po drugie, wspomnę również FirstOrDefault(w przypadkach, w których pozycja może nie istnieć) oraz Selectz indeksem, aby objąć przypadek, w którym potrzebny jest indeks pozycji (tak jak w próbce w samym pytaniu).
Pavel Minaev
Nie jestem jeszcze szczęśliwy. W moim przykładzie nie ma indeksu -1 (nie znaleziono). Jakieś sugestie?
R. Martinho Fernandes
Oprócz sprawdzania jego istnienia, jeśli najpierw.
R. Martinho Fernandes
Czy muszę najpierw sprawdzić, czy lista jest pusta?
David.Chu.ca
Wybierz rzuca, ArgumentNullExceptionjeśli źródło jest zerowe
R. Martinho Fernandes
13

Jeśli to naprawdę List<string>nie potrzebujesz LINQ, po prostu użyj:

int GetItemIndex(string search)
{
    return _list == null ? -1 : _list.IndexOf(search);
}

Jeśli szukasz samego przedmiotu, spróbuj:

string GetItem(string search)
{
    return _list == null ? null : _list.FirstOrDefault(s => s.Equals(search));
}
AgileJon
źródło
1
Zgodnie z logiką pierwszego przykładu możemy użyć _list.Find(search)drugiego.
jwg
12

Czy chcesz element na liście lub sam przedmiot (zakładałby sam element).

Oto kilka opcji dla Ciebie:

string result = _list.First(s => s == search);

string result = (from s in _list
                 where s == search
                 select s).Single();

string result = _list.Find(search);

int result = _list.IndexOf(search);
Kelsey
źródło
Zaraz ... niektórzy ludzie są bardzo szybcy po naciśnięciu spustu;)
Kelsey,
co powiesz na indeks jako wartość zwracaną?
David.Chu.ca
i czy muszę sprawdzić, czy _list jest pusta w postaci from .. w _list ...?
David.Chu.ca
6

Ta metoda jest łatwiejsza i bezpieczniejsza

var lOrders = new List<string>();

bool insertOrderNew = lOrders.Find(r => r == "1234") == null ? true : false

RckLN
źródło
1
Myślę, że nawet nie potrzebujemy true : falseponiżej, powinien działać tak samo bool insertOrderNew = lOrders.Find(r => r == "1234") == null;
Vbp
5

Jak o IndexOf?

Wyszukuje określony obiekt i zwraca indeks pierwszego wystąpienia na liście

Na przykład

> var boys = new List<string>{"Harry", "Ron", "Neville"};  
> boys.IndexOf("Neville")  
2
> boys[2] == "Neville"
True

Zauważ, że zwraca -1, jeśli wartość nie występuje na liście

> boys.IndexOf("Hermione")  
-1
Pułkownik Panika
źródło
2

Kiedyś korzystałem ze Słownika, który jest pewnego rodzaju zindeksowaną listą, która da mi dokładnie to, czego chcę, kiedy chcę.

Dictionary<string, int> margins = new Dictionary<string, int>();
margins.Add("left", 10);
margins.Add("right", 10);
margins.Add("top", 20);
margins.Add("bottom", 30);

Ilekroć chcę na przykład uzyskać dostęp do moich wartości marginesów, zwracam się do mojego słownika:

int xStartPos = margins["left"];
int xLimitPos = margins["right"];
int yStartPos = margins["top"];
int yLimitPos = margins["bottom"];

W zależności od tego, co robisz, słownik może być przydatny.

Will Marcouiller
źródło
Świetna odpowiedź na inne pytanie.
jwg
2

Oto jeden ze sposobów przepisania metody korzystania z LINQ:

public static int GetItemIndex(string search)
{
    List<string> _list = new List<string>() { "one", "two", "three" };

    var result = _list.Select((Value, Index) => new { Value, Index })
            .SingleOrDefault(l => l.Value == search);

    return result == null ? -1 : result.Index;
}

Tak więc nazywając to

GetItemIndex("two")powróci 1,

i

GetItemIndex("notthere")wróci -1.

Odniesienie: linqsamples.com

soczysty
źródło
1

Wypróbuj ten kod:

return context.EntitytableName.AsEnumerable().Find(p => p.LoginID.Equals(loginID) && p.Password.Equals(password)).Select(p => new ModelTableName{ FirstName = p.FirstName, UserID = p.UserID });
Nayeem Mansoori
źródło
1

Jeśli musimy znaleźć element z listy, możemy użyć metody Findi FindAllextensions, ale istnieje między nimi niewielka różnica. Oto przykład.

 List<int> items = new List<int>() { 10, 9, 8, 4, 8, 7, 8 };

  // It will return only one 8 as Find returns only the first occurrence of matched elements.
     var result = items.Find(ls => ls == 8);      
 // this will returns three {8,8,8} as FindAll returns all the matched elements.
      var result1 = items.FindAll(ls => ls == 8); 
Sheo Dayal Singh
źródło
1

Pomoże to w uzyskaniu pierwszej lub domyślnej wartości w wyszukiwaniu listy Linq

var results = _List.Where(item => item == search).FirstOrDefault();

To wyszukiwanie znajdzie pierwszą lub domyślną wartość, którą zwróci.

befree2j
źródło
0

Chcesz wyszukać obiekt na liście obiektów.

Pomoże to w uzyskaniu pierwszej lub domyślnej wartości w wyszukiwaniu listy Linq.

var item = list.FirstOrDefault(items =>  items.Reference == ent.BackToBackExternalReferenceId);

lub

var item = (from items in list
    where items.Reference == ent.BackToBackExternalReferenceId
    select items).FirstOrDefault();
Huseyin Durmus
źródło
0

Możesz użyć FirstOfDefault z rozszerzeniem Where Linq, aby uzyskać klasę MessageAction z IEnumerable. Reme

var action = Message.Actions.Where (e => e.targetByName == nazwa_klasy) .FirstOrDefault ();

gdzie

Lista działań {get; zestaw; }

złoty Lew
źródło