Controlla se una stringa contiene un elemento da un elenco (di stringhe)


155

Per il seguente blocco di codice:

For I = 0 To listOfStrings.Count - 1
    If myString.Contains(lstOfStrings.Item(I)) Then
        Return True
    End If
Next
Return False

L'output è:

Caso 1:

myString: C:\Files\myfile.doc
listOfString: C:\Files\, C:\Files2\
Result: True

Caso 2:

myString: C:\Files3\myfile.doc
listOfString: C:\Files\, C:\Files2\
Result: False

L'elenco (listOfStrings) può contenere diversi elementi (minimo 20) e deve essere verificato rispetto a migliaia di stringhe (come myString).

Esiste un modo migliore (più efficiente) per scrivere questo codice?

Risposte:


359

Con LINQ e usando C # (non conosco molto VB in questi giorni):

bool b = listOfStrings.Any(s=>myString.Contains(s));

o (più breve e più efficiente, ma probabilmente meno chiaro):

bool b = listOfStrings.Any(myString.Contains);

Se stavi testando l'uguaglianza, varrebbe la pena guardare HashSetecc, ma questo non aiuterà con corrispondenze parziali a meno che tu non lo divida in frammenti e aggiungi un ordine di complessità.


aggiornamento: se intendi davvero "StartsWith", puoi ordinare l'elenco e posizionarlo in un array; quindi usa Array.BinarySearchper trovare ogni elemento - controlla per ricerca per vedere se si tratta di una corrispondenza completa o parziale.


1
Invece di contenere userei StartsWith basato sui suoi esempi.
tvanfosson,

@tvanfosson - dipende dal fatto che gli esempi siano completamente inclusivi, ma sì, sono d'accordo. Semplice da cambiare, ovviamente.
Marc Gravell

In che misura questo codice è più efficiente a livello algoritmico? È più breve e più veloce se i loop in "Any" sono più veloci, ma il problema che devi eseguire più volte la corrispondenza esatta è lo stesso.
Torsten Marek,

È possibile impostare un comparatore personalizzato se si utilizza un set.
Fortyrunner,

Il secondo non è davvero più efficiente per qualsiasi differenza misurabile nella pratica.
ICR

7

quando costruisci le tue stringhe dovrebbe essere così

bool inact = new string[] { "SUSPENDARE", "DIZOLVARE" }.Any(s=>stare.Contains(s));

5

C'erano una serie di suggerimenti da una precedente domanda simile "Il modo migliore per testare una stringa esistente con un ampio elenco di elementi comparabili ".

Regex potrebbe essere sufficiente per le tue esigenze. L'espressione sarebbe una concatenazione di tutte le sottostringhe candidate, con un " |" operatore OR tra loro. Ovviamente, dovrai fare attenzione ai caratteri senza caratteri di escape durante la creazione dell'espressione o alla mancata compilazione a causa di complessità o limiti di dimensioni.

Un altro modo per farlo sarebbe costruire una struttura di dati trie per rappresentare tutte le sottostringhe candidate (questo potrebbe in qualche modo duplicare ciò che sta facendo il matcher regex). Mentre passi attraverso ogni carattere nella stringa di test, crei un nuovo puntatore alla radice del trie e fai avanzare i puntatori esistenti al figlio appropriato (se presente). Si ottiene una corrispondenza quando un puntatore raggiunge una foglia.


5

Mi è piaciuta la risposta di Marc, ma avevo bisogno che la corrispondenza Contains fosse CaSe InSenSiTiVe.

Questa era la soluzione:

bool b = listOfStrings.Any(s => myString.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0))

Non dovrebbe essere> -1?
CSharped il

1
@CSharped Non importa come> -1 (più di meno 1) e> = 0 (più o uguale a zero) sono la stessa cosa.
WhoIsRich,

2

In base ai tuoi schemi, un miglioramento sarebbe quello di passare all'uso di StartsWith invece di Contains. Inizia con l'iterazione solo attraverso ogni stringa fino a quando non trova la prima discrepanza invece di dover riavviare la ricerca in ogni posizione di carattere quando ne trova una.

Inoltre, in base ai tuoi modelli, sembra che potresti essere in grado di estrarre la prima parte del percorso per myString, quindi invertire il confronto - cercando il percorso iniziale di myString nell'elenco delle stringhe anziché viceversa.

string[] pathComponents = myString.Split( Path.DirectorySeparatorChar );
string startPath = pathComponents[0] + Path.DirectorySeparatorChar;

return listOfStrings.Contains( startPath );

EDIT : Questo sarebbe ancora più veloce usando l'idea di HashSet menzionata da @Marc Gravell poiché potresti passare Containsa ContainsKeye la ricerca sarebbe O (1) invece di O (N). Dovresti assicurarti che i percorsi corrispondano esattamente. Nota che questa non è una soluzione generale come quella di @Marc Gravell, ma è personalizzata per i tuoi esempi.

Ci scusiamo per l'esempio C #. Non ho avuto abbastanza caffè da tradurre in VB.


Re inizia con; forse preordinare e utilizzare la ricerca binaria? Potrebbe essere di nuovo più veloce.
Marc Gravell

2

Vecchia domanda Ma dal momento che VB.NETera il requisito originale. Utilizzando gli stessi valori della risposta accettata:

listOfStrings.Any(Function(s) myString.Contains(s))

1

Hai provato la velocità?

cioè Hai creato un set di dati di esempio e lo hai profilato? Potrebbe non essere così male come pensi.

Questo potrebbe anche essere qualcosa che potresti generare in un filo separato e dare l'illusione della velocità!


0

Se la velocità è fondamentale, potresti voler cercare l' algoritmo Aho-Corasick per serie di schemi.

È un trie con collegamenti di errore, ovvero la complessità è O (n + m + k), dove n è la lunghezza del testo di input, m la lunghezza cumulativa dei motivi e k il numero di corrispondenze. Devi solo modificare l'algoritmo per terminare dopo aver trovato la prima corrispondenza.



0

Lo svantaggio del Containsmetodo è che non consente di specificare il tipo di confronto che è spesso importante quando si confrontano le stringhe. È sempre sensibile alla cultura e al maiuscolo / minuscolo. Quindi penso che la risposta di WhoIsRich sia preziosa, voglio solo mostrare un'alternativa più semplice:

listOfStrings.Any(s => s.Equals(myString, StringComparison.OrdinalIgnoreCase))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.