Wyszukiwanie list bez rozróżniania wielkości liter

144

Mam listę testListzawierającą kilka ciągów. Chciałbym dodać nowy ciąg do testListjedynego, jeśli nie istnieje jeszcze na liście. Dlatego muszę przeszukiwać listę bez rozróżniania wielkości liter i sprawić, by była wydajna. Nie mogę używać, Containsbo to nie uwzględnia obudowy. Nie chcę też używać ToUpper/ToLowerze względu na wydajność. Trafiłem na tę metodę, która działa:

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

To działa, ale dopasowuje również częściowe słowa. Jeśli lista zawiera słowo „koza”, nie mogę dodać słowa „owies”, ponieważ twierdzi, że „owies” jest już na liście. Czy istnieje sposób na efektywne przeszukiwanie list bez rozróżniania wielkości liter, gdzie słowa muszą dokładnie pasować? dzięki

Brap
źródło

Odpowiedzi:

180

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

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

Alternatywnie użyj niektórych metod LINQ (które również zatrzymują się na pierwszej trafionej)

if( testList.Any( s => s.Equals(keyword, StringComparison.OrdinalIgnoreCase) ) )
    Console.WriteLine("found in list");
Adam Sills
źródło
Dodam, że w kilku szybkich testach wydaje się, że pierwsza metoda jest około 50% szybsza. Może ktoś inny może to potwierdzić / zaprzeczyć.
Brap
8
Od wersji .NET 2.0 można to teraz łatwo zrobić - spójrz na odpowiedź shaxby poniżej.
Joe
3
Odwołanie Shaxby do metody Contains (które ma przeciążenie, które pobiera IEqualityComparer) jest częścią LINQ, więc z pewnością nie było dostępne od .NET 2.0. Po prostu klasa StringComparer istnieje już od jakiegoś czasu. List <T> nie ma tej metody, podobnie jak ArrayList lub StringCollection (rzeczy, do których mógł łatwo odwołać się jako swoją „listę”).
Adam Sills
Cóż, ponieważ faktycznie potrzebowałem indeksu, była to zdecydowanie najlepsza odpowiedź dla mnie.
Nyerguds
1
Pierwsze rozwiązanie powinno korzystać z List<>.Exists(Predicate<>)metody instancji. Pamiętaj również, że jeśli lista zawiera nullwpisy, może to wybuchnąć. W takim przypadku bezpieczniej jest powiedzieć keyword.Equals(x, StringComparison.OrdinalIgnoreCase)niż x.Equals(keyword, StringComparison.OrdinalIgnoreCase)(jeśli możesz zagwarantować, że keywordnigdy nie jest zerowy).
Jeppe Stig Nielsen
360

Zdaję sobie sprawę, że jest to stary post, ale tylko w przypadku ktoś szuka, to można wykorzystać Containspoprzez dostarczenie wielkość liter ma znaczenie porównywarka równości ciąg jak w przykładzie:

using System.Linq;

// ...

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

To było dostępne od .net 2.0 zgodnie z msdn .

shaxby
źródło
21
Zdecydowanie najlepsza odpowiedź tutaj. :)
Joe
22
Enumerable <T> .Contains (to, do czego się odwołujesz) nie istniało od .NET 2.0. Nie ma List <T> .Zawiera, które ma przeciążenie, którego używasz.
Adam Sills
@AdamSills ma rację. W List <T> nie ma takiej metody zawierającej. A jeśli jest to leniwa kolekcja, może ją powtórzyć kilka razy, tak jak robią to inne metody Enumerable <T>. Imho, ta metoda nie powinna być używana w takich przypadkach, ponieważ nie jest to tak logiczne w tym przypadku.
Sergey Litvinov
40
Na początku też nie widziałem tego przeciążenia, ale musisz dodać za pomocą System.Linq, a potem się pojawi.
Michael,
1
StringComparerKlasa zostało około od 2,0, ale to przeciążenie Zawiera został wprowadzony w 3.5. msdn.microsoft.com/en-us/library/bb339118(v=vs.110).aspx
Denise Skidmore
18

Na podstawie powyższej odpowiedzi Adama Sillsa - oto ładna, czysta metoda rozszerzeń dla Zawartości ... :)

///----------------------------------------------------------------------
/// <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);
}
Lance Larsen - Microsoft MVP
źródło
10

Możesz użyć StringComparer:

    var list = new List<string>();
    list.Add("cat");
    list.Add("dog");
    list.Add("moth");

    if (list.Contains("MOTH", StringComparer.OrdinalIgnoreCase))
    {
        Console.WriteLine("found");
    }
jlo-gmail
źródło
1
Dopóki dodasz „using System.Linq”, w przeciwnym razie nie zobaczysz tego przeciążenia dla .Contains.
Julian Melville
1

Na podstawie odpowiedzi Lance'a Larsena - oto metoda rozszerzenia z zalecanym ciągiem znaków: porównaj zamiast ciągów znaków.

Zdecydowanie zaleca się użycie przeciążenia String.Compare, które przyjmuje parametr StringComparison. Te przeciążenia nie tylko umożliwiają zdefiniowanie dokładnego zachowania porównawczego, które zamierzałeś, ale ich użycie sprawi, że Twój kod będzie bardziej czytelny dla innych programistów. [ Blog zespołu Josh Free @ BCL ]

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);
}
dontbyteme
źródło
0

Sprawdzasz, czy wynik IndexOf jest większy lub równy 0, co oznacza, czy dopasowanie zaczyna się w dowolnym miejscu w ciągu. Spróbuj sprawdzić, czy jest równe 0:

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

Teraz „koza” i „owies” nie będą pasować, ale „koza” i „goa” tak. Aby tego uniknąć, możesz porównać długości dwóch strun.

Aby uniknąć tych wszystkich komplikacji, możesz użyć słownika zamiast listy. Kluczem byłby ciąg małych liter, a wartością byłby prawdziwy ciąg. W ten sposób wydajność nie zaszkodzi, ponieważ nie musisz używać ToLowerdla każdego porównania, ale nadal możesz używać Contains.

Ilya Kogan
źródło
0

Poniżej znajduje się przykład wyszukiwania słowa kluczowego na całej liście i usuwania tego elementu:

public class Book
{
  public int BookId { get; set; }
  public DateTime CreatedDate { get; set; }
  public string Text { get; set; }
  public string Autor { get; set; }
  public string Source { get; set; }
}

Jeśli chcesz usunąć książkę, która zawiera słowo kluczowe we właściwości Text, możesz utworzyć listę słów kluczowych i usunąć ją z listy książek:

List<Book> listToSearch = new List<Book>()
   {
        new Book(){
            BookId = 1,
            CreatedDate = new DateTime(2014, 5, 27),
            Text = " test voprivreda...",
            Autor = "abc",
            Source = "SSSS"

        },
        new Book(){
            BookId = 2,
            CreatedDate = new DateTime(2014, 5, 27),
            Text = "here you go...",
            Autor = "bcd",
            Source = "SSSS"


        }
    };

var blackList = new List<string>()
            {
                "test", "b"
            }; 

foreach (var itemtoremove in blackList)
    {
        listToSearch.RemoveAll(p => p.Source.ToLower().Contains(itemtoremove.ToLower()) || p.Source.ToLower().Contains(itemtoremove.ToLower()));
    }


return listToSearch.ToList();
Himanshu Chopra
źródło
-1

Miałem podobny problem, potrzebowałem indeksu pozycji, ale musiał być bez rozróżniania wielkości liter, rozejrzałem się po sieci przez kilka minut i nic nie znalazłem, więc napisałem małą metodę, aby to zrobić, oto co zrobiłem zrobił:

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 i nazwij go w ten sposób:

int index = getCaseInvariantIndexFromList(ListOfItems, itemToFind);

Mam nadzięję, że to pomogło, powodzenia!

Małpa w piżamie
źródło
1
po co tworzyć drugą listę? To nie jest zbyt wydajne. for (var i = 0; i <itemsList.Count; i ++) {if (item.ToLower () == searchItem.ToLower ()) {return i}}
wesm
Chyba nigdy się nie dowiemy.
Denny