我希望以简洁的方式执行以下转换。我想改变歌词。输入看起来像这样:
Verse 1 lyrics line 1
Verse 1 lyrics line 2
Verse 1 lyrics line 3
Verse 1 lyrics line 4
Verse 2 lyrics line 1
Verse 2 lyrics line 2
Verse 2 lyrics line 3
Verse 2 lyrics line 4
我想要对它们进行转换,以便将每节经文的第一行组合在一起,如下所示:
Verse 1 lyrics line 1
Verse 2 lyrics line 1
Verse 1 lyrics line 2
Verse 2 lyrics line 2
Verse 1 lyrics line 3
Verse 2 lyrics line 3
Verse 1 lyrics line 4
Verse 2 lyrics line 4
歌词显然是未知的,但空白行标志着输入中的经文之间的分歧。
答案 0 :(得分:3)
我总是保留一些扩展方法,这使得这种处理非常简单。整个解决方案将比其他解决方案更长,但这些方法是有用的方法,一旦你有扩展方法,那么答案非常简短易读。
首先,有一个Zip方法,它采用任意数量的序列:
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();
}
}
}
}
然后有一个Split方法,它对IEnumerable<T>
string.Split
对字符串的作用大致相同:
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());
}
一旦你有了这些扩展,解决歌词抒情问题就是小菜一碟:
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
答案 1 :(得分:1)
可能有一种更简洁的方法可以做到这一点,但这里有一个解决方案可以提供有效的输入:
var output = String.Join("\r\n\r\n", // join it all in the end
Regex.Split(input, "\r\n\r\n") // split on blank lines
.Select(v => Regex.Split(v, "\r\n")) // now split lines in each verse
.SelectMany(vl => vl.Select((lyrics, i) => new { Line = i, Lyrics = lyrics })) // flatten things out, but attach line number
.GroupBy(b => b.Line).Select(c => new { Key = c.Key, Value = c }) // group by line number
.Select(e => String.Join("\r\n", e.Value.Select(f => f.Lyrics).ToArray())).ToArray());
显然这很难看。完全不是生产代码的建议。
答案 2 :(得分:1)
LINQ太棒了......我只是喜欢它。
static void Main(string[] args)
{
var lyrics = @"Verse 1 lyrics line 1
Verse 1 lyrics line 2
Verse 1 lyrics line 3
Verse 1 lyrics line 4
Verse 2 lyrics line 1
Verse 2 lyrics line 2
Verse 2 lyrics line 3
Verse 2 lyrics line 4";
var x = 0;
var indexed = from lyric in lyrics.Split(new[] { Environment.NewLine },
StringSplitOptions.None)
let line = lyric.Trim()
let indx = line == string.Empty ? x = 0: ++x
where line != string.Empty
group line by indx;
foreach (var trans in indexed)
{
foreach (var item in trans)
Console.WriteLine(item);
Console.WriteLine();
}
/*
Verse 1 lyrics line 1
Verse 2 lyrics line 1
Verse 1 lyrics line 2
Verse 2 lyrics line 2
Verse 1 lyrics line 3
Verse 2 lyrics line 3
Verse 1 lyrics line 4
Verse 2 lyrics line 4
*/
}
答案 3 :(得分:0)
将您的输入作为一个大字符串。然后确定一节经文中的行数。
使用.Split获取字符串数组,每个项目现在都是一行。然后遍历你拥有的行数,并使用stringbuilder附加SplitStrArray(i)和SplitStrArray(i +中的行+)。
我认为这将是最好的方法。我不是说LINQ不是很棒,但似乎很愚蠢地说,“我有一个问题,我想用这个工具来解决它”。
“我必须把螺丝钉在墙上 - 但我想用锤子”。如果你有决心,你可能会找到一种方法来使用锤子;但恕我直言,这不是最好的行动方案。也许别人会有一个非常棒的LINQ例子让它变得非常简单,我会觉得发布这个很傻....
答案 4 :(得分:0)
试一试。 Regex.Split
用于防止额外的空白条目String.Split
可用于在Array.FindIndex
方法的帮助下确定第一个空白行的位置。这表示每个空白行之间可用的经文数量(当然,格式是一致的)。接下来,我们过滤掉空行并确定每一行的索引,并按上述索引的模数对它们进行分组。
string input = @"Verse 1 lyrics line 1
Verse 1 lyrics line 2
Verse 1 lyrics line 3
Verse 1 lyrics line 4
Verse 1 lyrics line 5
Verse 2 lyrics line 1
Verse 2 lyrics line 2
Verse 2 lyrics line 3
Verse 2 lyrics line 4
Verse 2 lyrics line 5
Verse 3 lyrics line 1
Verse 3 lyrics line 2
Verse 3 lyrics line 3
Verse 3 lyrics line 4
Verse 3 lyrics line 5
";
// commented original Regex.Split approach
//var split = Regex.Split(input, Environment.NewLine);
var split = input.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
// find first blank line to determine # of verses
int index = Array.FindIndex(split, s => s == "");
var result = split.Where(s => s != "")
.Select((s, i) => new { Value = s, Index = i })
.GroupBy(item => item.Index % index);
foreach (var group in result)
{
foreach (var item in group)
{
Console.WriteLine(item.Value);
}
Console.WriteLine();
}