Compruebe si una cadena contiene un elemento de una lista (de cadenas)

155

Para el siguiente bloque de código:

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

El resultado es:

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

La lista (listOfStrings) puede contener varios elementos (mínimo 20) y debe verificarse con miles de cadenas (como myString).

¿Hay alguna forma mejor (más eficiente) de escribir este código?

usuario57175
fuente

Respuestas:

359

Con LINQ y usando C # (no sé mucho VB en estos días):

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

o (más corto y más eficiente, pero posiblemente menos claro):

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

Si estuviera probando la igualdad, valdría la pena mirar HashSet, etc., pero esto no ayudará con coincidencias parciales a menos que lo divida en fragmentos y agregue un orden de complejidad.


actualización: si realmente quiere decir "StartsWith", puede ordenar la lista y colocarla en una matriz; luego use Array.BinarySearchpara encontrar cada elemento: verifique por búsqueda para ver si es una coincidencia total o parcial.

Marc Gravell
fuente
1
En lugar de Contins, usaría StartsWith según sus ejemplos.
tvanfosson 01 de
@tvanfosson: eso depende de si los ejemplos son totalmente inclusivos, pero sí, estoy de acuerdo. Simple de cambiar, por supuesto.
Marc Gravell
¿Hasta qué punto es este código más eficiente a nivel algorítmico? Es más corto y más rápido si los bucles en "Any" son más rápidos, pero el problema de que tiene que realizar una coincidencia exacta muchas veces es el mismo.
Torsten Marek
Puede configurar un comparador personalizado si está utilizando un conjunto.
Fortyrunner 01 de
El segundo no es realmente más eficiente por cualquier diferencia medible en la práctica.
ICR
7

cuando construyes las tuyas debería ser así

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

Hubo una serie de sugerencias de una pregunta similar anterior "La mejor manera de probar la cadena existente en una gran lista de comparables ".

Regex podría ser suficiente para su requerimiento. La expresión sería una concatenación de todas las subcadenas candidatas, con un |operador OR " " entre ellas. Por supuesto, tendrá que tener cuidado con los caracteres sin escape al construir la expresión, o si no se compila debido a la complejidad o las limitaciones de tamaño.

Otra forma de hacer esto sería construir una estructura de datos trie para representar todas las subcadenas candidatas (esto puede duplicar algo lo que está haciendo el comparador de expresiones regulares). A medida que avanza por cada carácter en la cadena de prueba, crearía un nuevo puntero a la raíz del trie y avanzaría los punteros existentes al niño apropiado (si lo hubiera). Obtienes una coincidencia cuando cualquier puntero alcanza una hoja.

Zach Scrivena
fuente
5

Me gustó la respuesta de Marc, pero necesitaba que la coincidencia Contains fuera CaSe InSenSiTiVe.

Esta fue la solución:

bool b = listOfStrings.Any(s => myString.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0))
WhoIsRich
fuente
¿No debería ser> -1?
CSharped
1
@CSharped No importa ya que> -1 (más de menos 1) y> = 0 (más o igual a cero) son lo mismo.
WhoIsRich
2

Según sus patrones, una mejora sería cambiar a usar StartsWith en lugar de Contains. StartsWith solo necesita iterar a través de cada cadena hasta que encuentre la primera falta de coincidencia en lugar de tener que reiniciar la búsqueda en cada posición de carácter cuando encuentre una.

Además, según sus patrones, parece que puede extraer la primera parte de la ruta de myString, luego invertir la comparación, buscando la ruta de inicio de myString en la lista de cadenas en lugar de al revés.

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

return listOfStrings.Contains( startPath );

EDITAR : Esto sería aún más rápido utilizando la idea HashSet que menciona @Marc Gravell ya que podría cambiar Containsa ContainsKeyy la búsqueda sería O (1) en lugar de O (N). Tendría que asegurarse de que las rutas coincidan exactamente. Tenga en cuenta que esta no es una solución general como la de @Marc Gravell, sino que se adapta a sus ejemplos.

Perdón por el ejemplo de C #. No he tenido suficiente café para traducir a VB.

tvanfosson
fuente
Re comienza con; tal vez pre-ordenar y usar la búsqueda binaria? Eso podría ser más rápido de nuevo.
Marc Gravell
2

Vieja pregunta Pero como VB.NETera el requisito original. Usando los mismos valores de la respuesta aceptada:

listOfStrings.Any(Function(s) myString.Contains(s))
Luis Lavieri
fuente
1

¿Has probado la velocidad?

es decir, ¿ha creado un conjunto de datos de muestra y lo ha perfilado? Puede que no sea tan malo como crees.

¡Esto también podría ser algo que podría generar en un hilo separado y dar la ilusión de velocidad!

Fortyrunner
fuente
0

Si la velocidad es crítica, es posible que desee buscar el algoritmo Aho-Corasick para conjuntos de patrones.

Es un trie con enlaces de falla, es decir, la complejidad es O (n + m + k), donde n es la longitud del texto de entrada, m la longitud acumulativa de los patrones yk el número de coincidencias. Solo tiene que modificar el algoritmo para terminar después de encontrar la primera coincidencia.

Torsten Marek
fuente
0
myList.Any(myString.Contains);
WIRN
fuente
0

El inconveniente del Containsmétodo es que no permite especificar el tipo de comparación, que a menudo es importante al comparar cadenas. Siempre es sensible a la cultura y a mayúsculas y minúsculas. Así que creo que la respuesta de WhoIsRich es valiosa, solo quiero mostrar una alternativa más simple:

listOfStrings.Any(s => s.Equals(myString, StringComparison.OrdinalIgnoreCase))
Al Kepp
fuente