remarks example create c# linq .net-4.5 c#-5.0

example - xelement c#



¿Existe una solución LINQ elegante para SomeButNotAll()? (5)

Esto es lo que estoy tratando de hacer en general. Solo para ser claros, esto no es tarea o para un concurso o algo así. Con suerte, he dejado las palabras lo suficientemente claras:

Problema

Dado un conjunto de cadenas en el mismo formato, pero donde algunas terminan en una letra minúscula y otras no, devuelve un conjunto de una de cada cadena que no termina en una letra minúscula, pero que tiene al menos una cadena idéntica que termina en Una letra minúscula.

Ejemplo

Para mantenerlo simple, digamos que el formato de cadena es /d+[az]? , donde la parte común es el número. Dado que {1, 4, 3a, 1b, 3, 6c} , debería recibir una permutación de {1, 3} porque 1 y 3 tienen un elemento con y sin una letra minúscula al final.

Solución alternativa

Puedes ver esta solución aquí .

Una forma en la que pensé hacer esto fue dividir el conjunto en elementos con y sin un sufijo de letras minúsculas ( {1, 4, 3} y {3a, 1b, 6c} ), y luego regresar sin withoutSuffix.Where(x => withSuffix.Any(y => y.StartsWith(x))) .

Tengo dos problemas con esto:

  1. No veo una buena forma de particionar en los dos conjuntos, con el predicado Regex.IsMatch(input, "[az]$") . Las dos en las que pensé eran dos variables definidas de manera similar, cada una utilizando una cláusula Where y haciendo la expresión regular que coincida dos veces por elemento, o transformando el conjunto para almacenar los resultados de coincidencia de expresión regular y luego formando las dos variables a partir de eso. group...by no parece jugar bien cuando necesitas acceder a ambos conjuntos como este, pero podría estar equivocado allí.

  2. Si bien el tamaño es lo suficientemente pequeño como para no preocuparse por el rendimiento, no es withSuffix pasar por withSuffix una vez por withoutSuffix elemento withoutSuffix .

Solucion relevante

Puedes ver esta solución aquí .

La otra forma que se me ocurrió fue tomar el prefijo común y el sufijo opcional: {1 => {"", b}, 3 => {a, ""}, 4 => {""}, 6 => {c}} . Esto se logra fácilmente capturando el prefijo y el sufijo con una expresión regular ( (/d+)([az])? Y agrupando el sufijo por el prefijo en grouped .

Desde aquí, sería genial hacer:

where grouped.SomeButNotAll(x => x == string.Empty) select grouped.Key

O incluso:

where grouped.ContainsSomeButNotAll(string.Empty) select grouped.Key

Ciertamente podría crear cualquiera de estos, pero desafortunadamente, lo mejor que puedo ver en LINQ es:

where grouped.Contains(string.Empty) && grouped.Any(x => x != string.Empty) select grouped.Key

Simplemente se siente super redundante. ¿Hay algo mejor que esto en LINQ ya?

PD: Estoy abierto a mejores enfoques para resolver el problema general en lugar de hacer que este sea un problema XY. La elegancia se desea mucho más que el rendimiento, pero (tal vez solo sea yo) ser simplemente un desperdicio parece poco elegante.


En este caso, creo que la solución más eficaz no es necesariamente muy elegante en LINQ. Creo que esto debería hacer lo que quieres y hacerlo O (N) tiempo de ejecución.

values .Aggregate( new { HashSet1 = new HashSet<string>(), HashSet2 = new HashSet<string>() }, (a, x) => { // If the last character is a lowercase letter then put the string // (minus the last character) in HashSet1, otherwise, put the string // in HashSet2 if(Char.IsLower(x, x.Length - 1)) { a.HashSet1.Add(x.Substring(0, x.Length - 1)); } else { a.HashSet2.Add(x); } return a; }, a => { // Return all the strings that are present in both hash sets. return a .HashSet1 .Where(x => a.HashSet2.Contains(x)); });


No creo que realmente necesites regexen para esto. Así es como lo haría:

var withEndings = new HashSet<string>(); var withoutEndings = new HashSet<string>(); foreach (var s in input) if(char.IsLower(s[s.Length - 1])) withEndings.Add(s.Substring(0, s.Length - 1)); else withoutEndings.Add(s); var result = withEndings.Intersect(withoutEndings);


Podría agregar otra agrupación por string.IsNullOrEmpty y verificar que tiene 2 grupos (uno para false y otro para true ):

return from str in strs let match = Regex.Match(str, STR_FORMAT) group match.Groups[2].Value by match.Groups[1].Value into parts where (parts.GroupBy(string.IsNullOrEmpty).Count() == 2) select parts.Key;


Puede cambiar .Any() a !All() .

Preferiría usar la sobrecarga de conteo con un predicado y compararlo con el conteo general. Probablemente será lo más limpio y no tendrá que preocuparse por las excepciones derivadas de colecciones vacías.


.Where() en cada elemento, y para cada uno, .Where() en cada elemento de nuevo , asegurándose de que al menos uno se ajuste al patrón de expresión regular del elemento original más cualquier letra minúscula.

var input = new List<string>() { "1", "4", "3a", "1b", "3", "6c" }; var output = input.Where( x => input.Where( y => Regex.Match(y, "^" + Regex.Escape(x) + "[a-z]$").Success ).Any() );

output contiene { "1", "3" } .