代码之家  ›  专栏  ›  技术社区  ›  Larsenal

LINQ中复杂的字符串转换(希望如此)

  •  5
  • Larsenal  · 技术社区  · 15 年前

    我希望有一种简洁的方式来执行下面的转换。我想改变歌词。输入将如下所示:

    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
    

    歌词显然是未知的,但是空白行标记输入中的诗句之间的划分。

    5 回复  |  直到 15 年前
        1
  •  3
  •   Aaronaught    15 年前

    我有一些我一直保留的扩展方法,使这种类型的处理非常简单。解决方案的整体长度将比其他方法长,但这些方法都是有用的,一旦您有了扩展方法,那么答案将非常简短,并且易于阅读。

    首先,有一个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();
                }
            }
        }
    }
    

    然后有一个分割方法,它对 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
    
        2
  •  1
  •   Matthew Whited    15 年前

    林肯是如此的甜美…我就是喜欢它。

    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
  •  1
  •   Larsenal    15 年前

    可能有一种更简洁的方法可以做到这一点,但这里有一种解决方案在提供有效输入的情况下有效:

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

    很明显这很难看。一点也不是生产代码的建议。

        4
  •  0
  •   Rob P.    15 年前

    将输入作为一个大字符串。然后确定一首诗的行数。

    使用.split获取字符串数组,每个项现在都是一行。然后循环遍历您拥有的行数,并使用StringBuilder附加splitstrarray(i)和splitstrarray(i+行在一首诗中)。

    我认为这是最好的办法。我不是说Linq不太棒,但说“我有问题,我想用这个工具来解决它”似乎很傻。

    “我得把螺丝钉在墙上,但我想用锤子”。如果你下定决心,你可能会找到一种使用锤子的方法;但依我看,这不是最好的方法。也许其他人会有一个非常棒的LINQ例子,让它变得非常容易,我会为发布这个感到很傻…

        5
  •  0
  •   Ahmad Mageed    15 年前

    试试看。 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();
    }