Tengo unos pocos métodos de extensión que siempre guardo que hacen que este tipo de procesamiento sea muy simple. La solución en su totalidad va a ser más larga que otras, pero estos son métodos útiles para tener cerca, y una vez que tenga los métodos de extensión en su lugar, la respuesta es muy corta y fácil de leer.
En primer lugar, hay un método cremallera que lleva un número arbitrario de secuencias:
public static class EnumerableExtensions
{
public static IEnumerable<T> Zip<T>(
this IEnumerable<IEnumerable<T>> sequences,
Func<IEnumerable<T>, T> aggregate)
{
var enumerators = sequences.Select(s => s.GetEnumerator()).ToArray();
try
{
while (enumerators.All(e => e.MoveNext()))
{
var items = enumerators.Select(e => e.Current);
yield return aggregate(items);
}
}
finally
{
foreach (var enumerator in enumerators)
{
enumerator.Dispose();
}
}
}
}
Luego hay un método de Split, que hace más o menos lo mismo a un IEnumerable<T>
que string.Split
hace en una cadena:
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items,
Predicate<T> splitCondition)
{
using (IEnumerator<T> enumerator = items.GetEnumerator())
{
while (enumerator.MoveNext())
{
yield return GetNextItems(enumerator, splitCondition).ToArray();
}
}
}
private static IEnumerable<T> GetNextItems<T>(IEnumerator<T> enumerator,
Predicate<T> stopCondition)
{
do
{
T item = enumerator.Current;
if (stopCondition(item))
{
yield break;
}
yield return item;
} while (enumerator.MoveNext());
}
una vez que tenga estas extensiones en su lugar, la solución del problema de canto lírico es un pedazo de la torta:
string lyrics = ...
var verseGroups = lyrics
.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
.Select(s => s.Trim()) // Optional, if there might be whitespace
.Split(s => string.IsNullOrEmpty(s))
.Zip(seq => string.Join(Environment.NewLine, seq.ToArray()))
.Select(s => s + Environment.NewLine); // Optional, add space between groups
¡Método ZIP muy útil! – Larsenal