使用Linq按特殊值打破列表?

时间:2016-02-17 23:08:38

标签: c# linq

我正在尝试使用Linq将IEnumerable<int>转换为IEnumerable<List<int>> - 输入流将以特殊值0分隔。

IEnumerable<List<int>> Parse(IEnumerable<int> l) 
{
    l.Select(x => {
      .....; //?
      return new List<int>();
    });
}
var l = new List<int> {0,1,3,5,0,3,4,0,1,4,0};
Parse(l) // returns {{1,3,5}, {3, 4}, {1,4}}

如何使用Linq而不是命令式循环来实现它? 或者Linq不满足于此要求,因为逻辑取决于输入流的顺序?

5 个答案:

答案 0 :(得分:4)

简单循环将是不错的选择。

替代方案:

汇总样本

var result = list.Aggregate(new List<List<int>>(),
    (sum,current) => { 
       if(current == 0) 
            sum.Add(new List<int>());
       else 
            sum.Last().Add(current);
    return sum;
});

注意:这只是适用于{0,1,2,0,3,4}等非常友好输入的方法示例。

甚至可以将聚合聚合到不可变列表中,但对于基本的.Net类型,这看起来会很疯狂。

答案 1 :(得分:2)

这是一个懒惰地枚举源可枚举的答案,但是在零之间急切地枚举每个返回列表的内容。它正确地抛出空输入或者给出一个不以零开头的列表(尽管允许空列表通过 - 这实际上是一个你必须决定的实现细节)。它最终不会返回额外的空列表,就像至少其他答案的可能建议一样。

public static IEnumerable<List<int>> Parse(this IEnumerable<int> source, int splitValue = 0) {
   if (source == null) {
      throw new ArgumentNullException(nameof (source));
   }
   using (var enumerator = source.GetEnumerator()) {
      if (!enumerator.MoveNext()) {
         return Enumerable.Empty<List<int>>();
      }
      if (enumerator.Current != splitValue) {
         throw new ArgumentException(nameof (source), $"Source enumerable must begin with a {splitValue}.");
      }
      return ParseImpl(enumerator, splitValue);
   }
}

private static IEnumerable<List<int>> ParseImpl(IEnumerator<int> enumerator, int splitValue) {
   var list = new List<int>();
   while (enumerator.MoveNext()) {
      if (enumerator.Current == splitValue) {
         yield return list;
         list = new List<int>(); 
      }
      else {
         list.Add(enumerator.Current);
      }
   }
   if (list.Any()) {
      yield return list;
   }
}

这可以很容易地适用于通用而不是int,只需将Parse更改为Parse<T>,将int更改为T,并使用{ {1}}或a.Equals(b)代替!a.Equals(b)a == b

答案 2 :(得分:1)

你可以创建一个像这样的扩展方法:

    public static IEnumerable<IEnumerable<T>> SplitBy<T>(this IEnumerable<T> source, T value)
    {
        using (var e = source.GetEnumerator())
        {
            if (e.MoveNext())
            {
                var list = new List<T> { };
                //In case the source doesn't start with 0
                if (!e.Current.Equals(value))
                {
                    list.Add(e.Current);
                }
                while (e.MoveNext())
                {
                    if ( !e.Current.Equals(value))
                    {
                        list.Add(e.Current);
                    }
                    else
                    {
                        yield return list;
                        list = new List<T> { };
                    }

                }
                //In case the source doesn't end with 0
                if (list.Count>0)
                {
                    yield return list;
                }

            }
        }
    }

然后,您可以执行以下操作:

var l = new List<int> { 0, 1, 3, 5, 0, 3, 4, 0, 1, 4, 0 };
var result = l.SplitBy(0);

答案 3 :(得分:0)

您可以将GroupBy与计数器一起使用。

var list = new List<int> {0,1,3,5,0,3,4,0,1,4,0};

int counter = 0;
var result = list.GroupBy(x => x==0 ? counter++ : counter)
                 .Select(g => g.TakeWhile(x => x!=0).ToList())
                 .Where(l => l.Any());

答案 4 :(得分:-2)

已编辑以修复数字中零的可能性

这是一个半LINQ解决方案:

var l = new List<int> {0,1,3,5,0,3,4,0,1,4,0};
string
    .Join(",", l.Select(x => x == 0 ? "|" : x.ToString()))
    .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
    .Select(x => x.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));

由于性能和其他原因,这可能不适合使用循环,但它应该可以工作。