【问题标题】:Partition list by delimiter按分隔符划分的分区列表
【发布时间】:2015-03-01 19:18:15
【问题描述】:

有没有一种优雅的方式(例如使用 LINQ)通过分隔符将列表划分为子列表列表?通过delim{ 1, 2, delim, delim, 3, delim, 4, 5 } 进行分区可能会产生{ { 1, 2 }, { 3 }, { 4, 5 } }....

【问题讨论】:

  • @BalinKingOfMoria 为了使您的示例更清晰,请创建一个包含多个元素和一个不包含元素的子列表:{ 1, 2, delim, delim, 3, delim, 4, 5 }
  • 感谢大家的快速解答!

标签: c# linq split


【解决方案1】:

我认为使用默认 LINQ 方法没有一种简单而优雅的方法。但是你可以创建自己的扩展方法:

public static class MyExtensions
{
    public static IEnumerable<IEnumerable<TElement>> SplitBy<TElement>(
        this IEnumerable<TElement> source,
        TElement split,
        bool skipEmptyGroups = true)
        where TElement : IEquatable<TElement>
    {
        var group = new List<TElement>();

        foreach (var item in source)
        {
            if (split.Equals(item))
            {
                if (group.Count > 0 || !skipEmptyGroups)
                {
                    yield return group;
                    group = new List<TElement>();
                }
            }
            else
            {
                group.Add(item);
            }
        }

        if (group.Count > 0 || !skipEmptyGroups)
            yield return group;
    }
}

用法真的很简单:

var source = new List<int> { 1, 2, 3, 3, 4, 3, 5, 3, 6, 7, 8 };

var result = source.SplitBy(3);

如果您想返回空组,您可以传递额外的bool 参数:

var resultWithEmptyGroups = source.SplitBy(3, false);

【讨论】:

  • 与其将其限制为IEqualtable&lt;TElement&gt;,不如使用默认的相等比较器。保持更灵活。
  • 你可以这样做,但它可能会基于默认的、基于引用的相等性返回奇怪的结果。
  • 老实说,这对任何人来说都不足为奇。如果用户想要不同的方式来比较项目,他们应该通过传递他们自己的比较器来选择。如果没有提供,则回退到默认值。此方法在不需要时仅限于一类项目。
【解决方案2】:

直接在Linq中,我觉得会很难,但是你可以创建一个自定义的操作符。也许是这样的:

List<String> test = new List<String>() { "1", "8", ";", "2", "7", "42", ";", "3" };
var restul = test.StrangePartition(";");

与:

public static class Helper
{
    public static IEnumerable<IEnumerable<T>> StrangePartition<T>(this IEnumerable<T> source, T partitionKey)
    {
        List<List<T>> partitions = new List<List<T>>();
        List<T> partition = new List<T>();
        foreach (T item in source)
        {

            if (item.Equals(partitionKey))
            {
                partitions.Add(partition);
                partition = new List<T>();
            }
            else
            {
                partition.Add(item);
            }
        }
        partitions.Add(partition);
        return partitions;
    }
}

【讨论】:

    【解决方案3】:

    不,没有类似的东西,但实现它们很容易:

    public static class LinqEx
    {
        public static IEnumerable<List<TSource>> Split<TSource>(this IEnumerable<TSource> enu, TSource delimiter, IEqualityComparer<TSource> comparer = null)
        {
            // list == null handles the case where enu is empty
            List<TSource> list = null;
    
            if (comparer == null)
            {
                // Note how the equality comparer is "selected".
                // This is how LINQ methods do it
                // (see Enumerable.SequenceEqual<TSource>)
                comparer = EqualityComparer<TSource>.Default;
            }
    
            foreach (TSource el in enu)
            {
                if (comparer.Equals(el, delimiter))
                {
                    if (list == null)
                    {
                        list = new List<TSource>();
                    }
    
                    yield return list;
    
                    // Note that we have to recreate the list every time! 
                    // We can't simply do a list.Clear()
                    list = new List<TSource>();
                    continue;
                }
    
                if (list == null)
                {
                    list = new List<TSource>();
                }
    
                list.Add(el);
            }
    
            if (list != null)
            {
                yield return list;
            }
        }
    
        public static IEnumerable<List<TSource>> SplitRemoveEmpty<TSource>(this IEnumerable<TSource> enu, TSource delimiter, IEqualityComparer<TSource> comparer = null)
        {
            var list = new List<TSource>();
    
            if (comparer == null)
            {
                // Note how the equality comparer is "selected".
                // This is how LINQ methods do it
                // (see Enumerable.SequenceEqual<TSource>)
                comparer = EqualityComparer<TSource>.Default;
            }
    
            foreach (TSource el in enu)
            {
                if (comparer.Equals(el, delimiter))
                {
                    if (list.Count != 0)
                    {
                        yield return list;
    
                        // Note that we have to recreate the list every time! 
                        // We can't simply do a list.Clear()
                        list = new List<TSource>();
                    }
    
                    continue;
                }
    
                list.Add(el);
            }
    
            if (list.Count != 0)
            {
                yield return list;
            }
        }
    }
    

    有两种变体:第一个 (Split) 将返回空组,第二个将删除空组。

    例如:

    { delim, 1, 2, delim, delim, 3, delim, 4, 5, delim }
    

    使用Split,您将收到

    { { }, {1, 2}, { }, {3}, {4, 5}, { } }
    

    虽然SplitRemoveEmpty

    { {1, 2}, {3}, {4, 5} }
    

    像这样使用它们:

    var res = new[] { 1, 2, 0, 0, 3, 4, 0, 5 }.SplitRemoveEmpty(0);
    
    foreach (List<int> group in res)
    {
        // Do something
    }
    

    请记住,这些拆分方法是基于Equals 方法的正确性!但是有一个可选的最后一个参数,您可以在其中提供方法将使用的IEqualityComparer&lt;TSource&gt;,而不是默认参数。

    【讨论】:

      【解决方案4】:

      虽然其他人提到使用命令式方法会更容易,但它在 LINQ 中当然是可行的。 Aggregate 运算符有很多用途,但通常可读性较差。

      var testCase = new List<String> { "1", "8", ";", "2", "7", "42", ";", "3" };
      var result = testCase.Aggregate(new List<List<String>>() { new List<String>() }, (l, s) => {
          if (s == ";") {
              l.Add(new List<String>());
          } else {
              l[l.Count - 1].Add(s);
          }
          return l;
      }).ToList();
      

      它会产生所需的输出,但它不仅难以理解,而且还会在查询内产生副作用(不如在查询外产生副作用那么糟糕,但仍然不明智)。

      如果您愿意,可以将其放入您自己的扩展方法中:

      public static IEnumerable<IEnumerable<T>> PartitionBy(this IEnumerable<T> source, T delimiter) {
          return source.Aggregate(new List<List<T>>() { new List<T>() }, (l, elem) =>
              if(elem.Equals(delimiter)) {
                  l.Add(new List<T>());
              } else {
                  l[l.Count - 1].Add(elem);
              }
              return l;
          });
      }
      

      同样,考虑这个答案只是为了 LINQ 答案的完整性。

      【讨论】:

        猜你喜欢
        • 2018-06-18
        • 2018-03-12
        • 1970-01-01
        • 2019-03-27
        • 2011-06-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-18
        相关资源
        最近更新 更多