代码之家  ›  专栏  ›  技术社区  ›  Roger Johansson

Linq中的按元素分组

  •  2
  • Roger Johansson  · 技术社区  · 14 年前

    假设我们有以下数组

    var arr = new string[] {"foo","bar","jar","\r","a","b,"c","\r","x","y","z","\r");
    

    我想按序列中的每个“\r”对这些元素进行分组。 也就是说,我想要一个带有“foo”、“bar”、“jar”的数组/可枚举数组,另一个带有“a”、“b”、“c”等。

    ienumerable扩展中是否有任何东西可以让我这样做,或者我必须在这里按方法滚动我自己的组?

    3 回复  |  直到 14 年前
        1
  •  4
  •   Timwi    14 年前

    为此,我编写了一个扩展方法,可以在任何 IEnumerable<T> .

    /// <summary>
    /// Splits the specified IEnumerable at every element that satisfies a
    /// specified predicate and returns a collection containing each sequence
    /// of elements in between each pair of such elements. The elements
    /// satisfying the predicate are not included.
    /// </summary>
    /// <param name="splitWhat">The collection to be split.</param>
    /// <param name="splitWhere">A predicate that determines which elements
    /// constitute the separators.</param>
    /// <returns>A collection containing the individual pieces taken from the
    /// original collection.</returns>
    public static IEnumerable<IEnumerable<T>> Split<T>(
            this IEnumerable<T> splitWhat, Func<T, bool> splitWhere)
    {
        if (splitWhat == null)
            throw new ArgumentNullException("splitWhat");
        if (splitWhere == null)
            throw new ArgumentNullException("splitWhere");
        return splitIterator(splitWhat, splitWhere);
    }
    private static IEnumerable<IEnumerable<T>> splitIterator<T>(
            IEnumerable<T> splitWhat, Func<T, bool> splitWhere)
    {
        int prevIndex = 0;
        foreach (var index in splitWhat
            .Select((elem, ind) => new { e = elem, i = ind })
            .Where(x => splitWhere(x.e)))
        {
            yield return splitWhat.Skip(prevIndex).Take(index.i - prevIndex);
            prevIndex = index.i + 1;
        }
        yield return splitWhat.Skip(prevIndex);
    }
    

    例如,在您的案例中,您可以这样使用它:

    var arr = new string[] { "foo", "bar", "jar", "\r", "a", "b", "c", "\r", "x", "y", "z", "\r" };
    var results = arr.Split(elem => elem == "\r");
    
    foreach (var result in results)
        Console.WriteLine(string.Join(", ", result));
    

    foo, bar, jar
    a, b, c
    x, y, z
    

    (包括结尾的空行,因为 "\r"

        2
  •  1
  •   Ronald Wildenberg    14 年前

    如果你想使用标准 IEnumerable 扩展方法,你必须使用 Aggregate (但这并不像Timwi的解决方案那样可重用):

    var list = new[] { "foo","bar","jar","\r","a","b","c","\r","x","y","z","\r" };
    var res = list.Aggregate(new List<List<string>>(),
                             (l, s) =>
                             {
                                 if (s == "\r")
                                 {
                                     l.Add(new List<string>());
                                 }
                                 else
                                 {
                                     if (!l.Any())
                                     {
                                         l.Add(new List<string>());
                                     }
                                     l.Last().Add(s);
                                 }
                                 return l;
                             });
    
        3
  •  0
  •   Community CDub    8 年前

    看到这个了吗 nest yields to return IEnumerable<IEnumerable<T>> with lazy evaluation 我也是。你可以喝一杯 SplitBy 接受要拆分的谓词的扩展方法:

    public static IEnumerable<IList<T>> SplitBy<T>(this IEnumerable<T> source, 
                                                  Func<T, bool> separatorPredicate,
                                                  bool includeEmptyEntries = false, 
                                                  bool includeSeparators = false)
    {
        var l = new List<T>();
        foreach (var x in source)
        {
            if (!separatorPredicate(x))
                l.Add(x);
            else
            {
                if (includeEmptyEntries || l.Count != 0)
                {
                    if (includeSeparators)
                        l.Add(x);
    
                    yield return l;
                }
    
                l = new List<T>();
            }
        }
    
        if (l.Count != 0)
            yield return l;
    }
    

    var arr = new string[] {"foo","bar","jar","\r","a","b,"c","\r","x","y","z","\r");
    foreach (var items in arr.SplitBy(x => x == "\r"))
        foreach (var item in items)
        {
        }