Сложное преобразование строк (надеюсь) в LINQ - PullRequest
5 голосов
/ 21 марта 2010

Я надеюсь, что краткий способ выполнить следующее преобразование.Я хочу изменить текст песни.Ввод будет выглядеть примерно так:

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

И я хочу преобразовать их так, чтобы первая строка каждого стиха была сгруппирована, как в:, но пустая строка отмечает разделение между стихами на входе.

Ответы [ 5 ]

3 голосов
/ 21 марта 2010

У меня есть несколько методов расширения, которые я всегда использую, чтобы сделать этот тип обработки очень простым. Решение в целом будет длиннее других, но это полезные методы, и если у вас есть методы расширения, ответ будет очень коротким и легко читаемым.

Во-первых, существует метод 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 голос
/ 21 марта 2010

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
     */
}
1 голос
/ 21 марта 2010

Вероятно, есть более краткий способ сделать это, но вот одно решение, которое работает при правильном вводе:

        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());

Очевидно, это довольно уродливо. Совсем не предложение для производственного кода.

0 голосов
/ 21 марта 2010

Дайте это попробовать. 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();
}
0 голосов
/ 21 марта 2010

Примите ваш ввод как одну большую строку. Затем определите количество строк в стихе.

Используйте .Split, чтобы получить массив строк, каждый элемент теперь является строкой. Затем переберите количество имеющихся у вас строк и используйте stringbuilder для добавления SplitStrArray (i) и SplitStrArray (i + строки в стихе).

Я думаю, что это будет лучшим подходом. Я не говорю, что LINQ - это не круто, но глупо говорить: «У меня есть проблема, и я хочу использовать этот инструмент для ее решения».

«Мне нужен винт в стену, но я хочу использовать молоток». Если вы полны решимости, вы, вероятно, найдете способ использовать молоток; но ИМХО, это не лучший курс действий. Может быть, у кого-то еще будет действительно потрясающий пример LINQ, который делает его очень простым, и я буду глупо публиковать это ....

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...