de mi experiencia pasada y stackoverflow, aprendí que String.ToLower() tiene un mal rendimiento. Ahora tengo el siguiente problema, necesito filtrar o realizar una acción específica cuando hay palabras específicas en una lista gigante.
Viejo enfoque, que quiero arreglar:
if (input.Any(i => i.ToLower() == "alle" || i.ToLower() == "all" || i.ToLower() == "none") { // do something }Estaba pensando en usar un hashset, pero estoy cuestionando el rendimiento y cómo maneja la distinción entre mayúsculas y minúsculas, básicamente no me importa la distinción entre mayúsculas y minúsculas. ¿Tiene sentido para mí usar el hashset?
mi sugerencia actual como solución:
var unwantedInputsSet = new HashSet<string> {"alle", "all", "none"}; if (input.Any(i => i => unwantedInputsSet.Contains(i))) { // do something }¿Hay alguna alternativa mejor a esto o no? ¿Tiene alguna idea de cómo abordar esto mejor?
Puede pasar el comparador al HashSet , por ejemplo StringComparer.InvariantCultureIgnoreCase :
var unwantedInputsSet = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) {"alle", "all", "none"}; if (input.Any(i => unwantedInputsSet.Contains(i))) { // do something }O, como se sugiere en los comentarios, use la coincidencia de patrones:
if (input.Any(i => i.ToLower() is "alle" or "all" or "none") { // do something } El compilador debe convertirlo en un código similar al suyo (aunque ToLower debe llamarse una vez).
En cuanto al rendimiento, puede depender en gran medida de los datos reales y debe medirlo utilizando conjuntos de datos esperados. Para un conjunto de búsqueda pequeño, HashSet puede funcionar peor que varias comparaciones como:
var cmpr = StringComparison.InvariantCultureIgnoreCase; if (input.Any(i => string.Equals(i, "alle", cmpr) || string.Equals(i, "all", cmpr) || string.Equals(i, "none", cmpr))) { // do something }Para tal evaluación comparativa, recomiendo buscar en BenchmarkDotNet .
Puede especificar un IEqualityComparer<T> al declarar un HashSet<T> .
static readonly HashSet<string> unwanted = new(StringComparer.OrdinalIgnoreCase) { "alle", "all", "none" };Además, haría de este un campo estático de solo lectura para anular la creación repetida.
La ventaja de la solución HashSet es que se puede extender fácilmente a más casos. También le permite leer las palabras no deseadas de un archivo de configuración.
Desde C# 9.0, puede usar new expresiones de tipo Target . Si está utilizando una versión anterior a C# 9.0:
static readonly HashSet<string> unwanted = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "alle", "all", "none" };Uso:
if (input.Any(i => unwanted.Contains(i))) { // do something } sin ToLower() !
Si desea que el código tenga el máximo rendimiento, realice las comparaciones de cadenas mediante string.Equals() con un parámetro StringComparison.OrdinalIgnoreCase .
Si desea que el código sea más legible y no le importa mucho el rendimiento, puede usar un método de extensión simple para comparar una cadena con varias cadenas de destino:
public static class StringExt { public static bool EqualsAnyOf(this string value, params string[] targets) { return targets.Any(target => target.Equals(value, StringComparison.OrdinalIgnoreCase)); } }Entonces podrías escribir tu código así:
if (input.Any(item => item.EqualsAnyOf("alle", "all", "none"))) { // ... } Si quisiera ser realmente elegante, también podría escribir un método de extensión AnyEqualsAnyOf() :
public static class StringExt { public static bool EqualsAnyOf(this string value, params string[] targets) { return targets.Any(target => target.Equals(value, StringComparison.OrdinalIgnoreCase)); } public static bool AnyEqualsAnyOf(this IEnumerable<string> sequence, params string[] targets) { return sequence.Any(item => item.EqualsAnyOf(targets)); } }Y entonces tu código sería simplemente:
if (input.AnyEqualsAnyOf("alle", "all", "none")) { // ... }Personalmente, no creo que valga la pena hacerlo a menos que te encuentres escribiendo este tipo de código con bastante frecuencia, pero ciertamente es una opción.