2009-07-20 14 views
5

Ho bisogno di un metodo veloce per determinare se una determinata stringa è in una lista di stringhe.Confronto stringa veloce con lista

L'elenco di stringhe non è noto fino al runtime, ma da quel momento in poi non cambierà.

potrebbe semplicemente avere un List<String> chiamato strings e poi fare:

if (strings.Contains(item)) 

Tuttavia questo si esibirà male se ci sono molte stringhe nella lista.

Potrei anche usare un HashSet<String>, ma questo richiederebbe chiamare GetHashCode su ogni stringa in entrata e Equals, che sarebbe uno spreco se ci sono, ad es. solo 3 stringhe nella lista. Ho detto che questo deve essere veloce?

quando ho potuto impostazione, decidere di utilizzare un List o una HashSet a seconda del numero di stringhe (per esempio uso Elenco per meno di 10 corde, HashSet altrimenti), piuttosto come la logica in HybridDictionary.

Poiché le stringhe sono unicode, una struttura Trie standard non funzionerà, anche se potrebbe esistere un albero Radix/trie Patricia. Ci sono delle buone implementazioni in C# con benchmark?

Alcuni hanno menzionato l'esclusione di GetHashCodeString e l'utilizzo di una funzione di hash più veloce. Ci sono dei punti di riferimento là fuori?

L'utilizzo di espressioni LINQ per creare essenzialmente una dichiarazione switch ottimizzata è un nuovo approccio che sembra molto interessante.

Cos'altro potrebbe funzionare? Il costo di installazione non è importante, solo la velocità di ricerca.

Se è importante, i valori di stringa in entrata appariranno raramente nell'elenco.

+0

Ho aggiornato la mia risposta per includere collegamenti a informazioni sui tentativi ripiegati per Unicode. –

risposta

5

È possibile utilizzare uno trie per contenere l'elenco di stringhe; i tentativi sono stati progettati per il re veloce trie val. Ecco one example di implementare un trie in C#.

Aggiornamento: Powerpoint presentation on folded tries for Unicode e Ifo on implementation of a folded trie for Unicode (not C#)

+0

Un trie sarebbe fantastico se le stringhe fossero solo A-Z, o anche solo ASCII. Ma questi sono unicode. –

+0

Dall'articolo di Wikipedia mi sono collegato a: "Anche se è più comune, i tentativi non devono essere immessi dalle stringhe di caratteri. Gli stessi algoritmi possono essere facilmente adattati per servire funzioni simili di elenchi ordinati di qualsiasi costrutto, ad esempio permutazioni su un elenco di cifre, permutazioni su un elenco di forme, ecc. " Quindi potresti farlo con, ad es. punti di codice da una stringa Unicode. –

+0

Hai un collegamento a un'implementazione Unicode? Sì, potrei usare 'GetBytes' e accendere i singoli byte, ma ho il sospetto che non funzionerà bene. –

2

Hai pensato di usare la classe HashSet (in .NET 3), invece?

+0

... che chiamerà nuovamente .GetHashCode e .Equals su ogni stringa in arrivo. –

+1

è possibile costruire un HashSet con il vostro operatore di confronto scelto con un sovraccarico: HashSet (T) Costruttore (IEqualityComparer (T)) http://msdn.microsoft.com/en-us/library/bb359100.aspx –

2

Re il suo "Quando la lista è piccola" preoccupazione; se non ti dispiace usare collezioni non generiche, System.Collections.Specialized.HybridDictionary fa qualcosa del genere; incapsula un System.Collections.Specialized.ListDictionary quando è piccolo, oppure un System.Collections.Hashtable quando diventa più grande (>10). Vale la pena dare un'occhiata?


Altrimenti; potresti forse usare con un comparatore personalizzato?Poi si può scegliere quanto costoso GetHashCode() è ...

using System; 
using System.Collections.Generic; 

class CustomStringComparer : IEqualityComparer<string> { 
    public bool Equals(string x, string y) { 
     return string.Equals(x, y); 
    } 
    public int GetHashCode(string s) { 
     return string.IsNullOrEmpty(s) ? 0 : 
      s.Length + 273133 * (int)s[0]; 
    } 
    private CustomStringComparer() { } 
    public static readonly CustomStringComparer Default 
     = new CustomStringComparer(); 
} 
static class Program { 
    static void Main() { 
     HashSet<string> set = new HashSet<string>(
      new string[] { "abc", "def", "ghi" }, CustomStringComparer.Default); 
     Console.WriteLine(set.Contains("abc")); 
     Console.WriteLine(set.Contains("abcde")); 
    } 
} 
+1

È una buona idea, ma su un'ulteriore riflessione scegliere la giusta funzione di hash quando non si sa quante stringhe saranno nella lista è molto difficile.Se è semplice come la funzione che hai scritto sopra, ci saranno molte collisioni con elenchi più grandi. –

2

Forse il HybridDictionary è una scelta migliore qui. Il suo uso interno dipende da quanti elementi sono presenti nella collezione.

0

Per inciso, se la memoria serve, quando viene costruita una stringa, il suo HashValue viene precalcolato e memorizzato con la stringa come ottimizzazione per questo tipo di caso d'uso. Se stai usando un array di caratteri o StringBuilder, questo ovviamente non si applica, ma per una stringa immutabile dovrebbe.

MODIFICA: non sono corretto ... Java memorizza nella cache un HashCode di una stringa, C# no.

+0

Penso che in questo caso la memoria non serva. Non vedo tracce di cache di hashcode quando si guarda 'System.String' con Reflector. –

+0

Hai ragione. Java lo fa e ho pensato che C# avrebbe portato la pratica. – CoderTao

2

ho finito per fare questo:

private static bool Contains(List<string> list, string value) 
{ 
    bool contains = null != list.Find(str => str.ToLower().Equals(value.ToLower())); 

    return contains; 
} 

Sto indovinando si potrebbe creare un metodo di estensione per List<string>, ma questo era sufficiente per le mie esigenze.

+0

Non penso che questo funzionerà abbastanza velocemente per le mie esigenze;) –

0

È possibile utilizzare internamento stringa per eseguire questa operazione molto rapidamente. Quando si crea l'elenco, è necessario memorizzare il formato interno della stringa richiesta (il risultato di string.Intern()). Quindi, è necessario confrontare una stringa internata con object.ReferenceEquals - poiché le stringhe internate hanno lo stesso riferimento.

List<string> BuildList() { 
    List<string> result; 
    foreach (string str from StringSource()) 
     result.Add(str.Intern()); 
    return result; 
} 

bool CheckList(List<string> list, string stringToFind) { // list must be interned for this to work! 
    return list.Find(str => object.ReferenceEquals(str, stringToFind)) != null; 
} 

Ciò comporterà un confronto quattro byte per ciascuna lista, e uno passaggio sulla stringa originale. Il pool interno di stringhe è stato creato appositamente per il confronto rapido delle stringhe e per trovare se ne esiste già uno, quindi l'operazione interna dovrebbe essere abbastanza veloce.

+0

Sfortunatamente 'String.Intern' non è poi così veloce, e avrebbe l'indesiderabile effetto collaterale di memorizzare permanentemente la stringa fino a quando il mio processo non ha esaurito la memoria (questo l'applicazione elabora molte stringhe). Inoltre, la ricerca successiva dell'elenco utilizzando ReferenceEquals sarebbe un'operazione O (N). –

+0

È più veloce del normale confronto tra stringhe, ma sì, questo non sarebbe un vantaggio per l'elaborazione di molte stringhe. – configurator

Problemi correlati