2010-10-16 24 views
75

Mam listę testList zawierającą kilka ciągów. Chciałbym dodać nowy ciąg do testList tylko wtedy, gdy jeszcze nie istnieje na liście. Dlatego muszę wykonać wyszukiwanie listy bez uwzględniania wielkości liter i sprawić, by był wydajny. Nie mogę użyć Contains, ponieważ nie uwzględnia to obudowy. Również nie chcę używać ToUpper/ToLower ze względu na wydajność. Natknąłem się na tę metodę, która działa:Wyszukiwanie listy bez rozróżniania wielkości liter

if(testList.FindAll(x => x.IndexOf(keyword, 
         StringComparison.OrdinalIgnoreCase) >= 0).Count > 0) 
     Console.WriteLine("Found in list"); 

Działa, ale dopasowuje również częściowe słowa. Jeśli lista zawiera "kozę", nie mogę dodać "owies", ponieważ twierdzi, że "owies" jest już na liście. Czy istnieje sposób, aby skutecznie wyszukiwać listy w sposób niewrażliwy na wielkość liter, gdzie słowa muszą dokładnie odpowiadać? dzięki

Odpowiedz

105

Zamiast String.IndexOf, użyj String.Equals, aby upewnić się, że nie masz częściowych dopasowań. Nie używaj FindAll, ponieważ przechodzi przez każdy element, użyj FindIndex (zatrzymuje się na pierwszym, który trafia).

if(testList.FindIndex(x => x.Equals(keyword, 
    StringComparison.OrdinalIgnoreCase)) != -1) 
    Console.WriteLine("Found in list"); 

Alternatywnie korzystać z niektórych metod LINQ (które też zatrzymuje się na pierwszej natrafi)

if(testList.Any(s => s.Equals(keyword, StringComparison.OrdinalIgnoreCase))) 
    Console.WriteLine("found in list"); 
+0

Wystarczy dodać w kilku szybkich testów, wydaje się, że pierwsza metoda jest około 50% szybciej. Może ktoś inny może to potwierdzić/zaprzeczyć. – Brap

+4

Od wersji .NET 2.0 jest to teraz łatwe - spójrz na odpowiedź shaxby poniżej. – Joe

+3

Metoda odwołań metody shaxby (która ma przeciążenie, które wymaga programu IEqualityComparer) jest częścią LINQ, więc z pewnością nie była dostępna od .NET 2.0. Tylko klasa StringComparer istnieje już od jakiegoś czasu. Lista nie ma tej metody, ani ArrayList ani StringCollection (rzeczy, które mógł łatwo odwoływać się jako jego "lista"). –

0

jesteś sprawdzanie jeśli wynik IndexOf jest większa lub równa 0, to znaczy, czy rozpoczęciem meczu w dowolnym miejscu w ciągu znaków. Spróbuj sprawdzić, czy to równa do 0:

if (testList.FindAll(x => x.IndexOf(keyword, 
        StringComparison.OrdinalIgnoreCase) >= 0).Count > 0) 
    Console.WriteLine("Found in list"); 

Now „koza” i „owies” nie pasuje, ale „koza” i „Goa” wola. Aby tego uniknąć, możesz porównać długości dwóch łańcuchów.

Aby uniknąć wszystkich tych komplikacji, można użyć słownika zamiast listy. Ich klucz byłby ciągiem z małą literą, a wartość byłaby prawdziwym ciągiem znaków. W ten sposób wydajność nie zostanie zraniona, ponieważ nie musisz używać ToLower dla każdego porównania, ale nadal możesz używać Contains.

11

podstawie Adam parapety odpowiedzi powyżej - tu jest ładny, czysty sposób rozszerzenia dla Zawiera ... :)

///---------------------------------------------------------------------- 
/// <summary> 
/// Determines whether the specified list contains the matching string value 
/// </summary> 
/// <param name="list">The list.</param> 
/// <param name="value">The value to match.</param> 
/// <param name="ignoreCase">if set to <c>true</c> the case is ignored.</param> 
/// <returns> 
/// <c>true</c> if the specified list contais the matching string; otherwise, <c>false</c>. 
/// </returns> 
///---------------------------------------------------------------------- 
public static bool Contains(this List<string> list, string value, bool ignoreCase = false) 
{ 
    return ignoreCase ? 
     list.Any(s => s.Equals(value, StringComparison.OrdinalIgnoreCase)) : 
     list.Contains(value); 
} 
0

miałem podobny problem, musiałem indeks pozycji, ale to musiał być przypadek nieczuły, rozejrzałem się w internecie na kilka minut i nic nie znalazłem, więc po prostu napisałem mały sposób, aby to zrobić, oto co zrobiłem:

private static int getCaseInvariantIndex(List<string> ItemsList, string searchItem) 
{ 
    List<string> lowercaselist = new List<string>(); 

    foreach (string item in ItemsList) 
    { 
     lowercaselist.Add(item.ToLower()); 
    } 

    return lowercaselist.IndexOf(searchItem.ToLower()); 
} 

Dodaj ten kod do tego samego pliku, a wezwanie to tak:

int index = getCaseInvariantIndexFromList(ListOfItems, itemToFind); 

Mam nadzieję, że to pomoże, powodzenia!

+0

po co tworzyć drugą listę? To nie jest zbyt wydajne. for (var i = 0; i wesm

+0

Chyba nigdy się nie dowiemy. – Denny

204

Zdaję sobie sprawę, że jest to stary post, ale na wszelki wypadek ktoś patrzy, ty może korzystanie Contains dostarczając wielkość liter ma znaczenie porównywarka równości ciąg tak:

if (testList.Contains(keyword, StringComparer.OrdinalIgnoreCase)) 
{ 
    Console.WriteLine("Keyword Exists"); 
} 

ta jest dostępna od .net 2.0 zgodnie z msdn.

+17

Zdecydowanie najlepsza odpowiedź tutaj. :) – Joe

+18

Enumerable . Kontra (co się odwołuje) nie było od .NET 2.0. Nie ma listy . Zawiera przeciążenie, którego używasz. –

+0

@AdamSills w prawo. Nie ma takiej metody zawiera Lista . A jeśli jest to leniwy zbiór, to może go powtórzyć kilka razy, tak jak robią to inne metody Enumerable. Imho, ta metoda nie powinna być stosowana w takich przypadkach, ponieważ nie jest to tak logiczne w tym przypadku. –

0

podstawie Lance Larsen odpowiedź - oto metodę rozszerzenia z zalecaną string.Compare zamiast string.Equals

Jest wysoce zalecane, aby użyć przeciążenie String.Compare która przyjmuje parametr StringComparison. Te przeciążenia pozwalają nie tylko zdefiniować dokładne zachowanie porównawcze, które zamierzałeś, ale także sprawią, że Twój kod stanie się bardziej czytelny dla innych programistów. [Josh Free @ BCL Team Blog]

public static bool Contains(this List<string> source, string toCheck, StringComparison comp) 
{ 
    return 
     source != null && 
     !string.IsNullOrEmpty(toCheck) && 
     source.Any(x => string.Compare(x, toCheck, comp) == 0); 
} 
Powiązane problemy