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:
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áusulaWhere
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í.Si bien el tamaño es lo suficientemente pequeño como para no preocuparse por el rendimiento, no es
withSuffix
pasar porwithSuffix
una vez porwithoutSuffix
elementowithoutSuffix
.
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" }
.