按元素拆分列表

时间:2015-04-25 16:03:22

标签: c# linq

我有这样的1和0的列表:

var list = new List<int>{1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1}

两个项目之间,只能是一个零。 如何将该列表拆分为0的子列表?

换句话说:如果我有这样的字符串:string myString = "111011011110111111011101"那么很容易将它分成0个字符串。 但如何用列表来做呢?这个例子shoudl产生这些子列表:

1,1,1
1,1
1,1,1,1
1,1,1,1,1,1
1,1,1
1

那么有没有更好的方法然后将每个元素转换为字符串,加入它们并做我展示可以用字符串做什么?

7 个答案:

答案 0 :(得分:10)

您可以通过将输入序列转换为序列序列来解决您的问题,就像LINQ GroupBy那样。但是,在您的情况下,您正在对输入序列的更改进行分组。也许有可能将现有的LINQ运算符(如GroupByZipSkip组合成可以满足您需求的东西,但我认为创建迭代器更容易(并且表现更好)查看输入序列中项目对的块:

static class EnumerableExtensions {

  public static IEnumerable<IEnumerable<T>> GroupOnChange<T>(
    this IEnumerable<T> source,
    Func<T, T, Boolean> changePredicate
  ) {
    if (source == null)
      throw new ArgumentNullException("source");
    if (changePredicate == null)
      throw new ArgumentNullException("changePredicate");

    using (var enumerator = source.GetEnumerator()) {
      if (!enumerator.MoveNext())
        yield break;
      var firstValue = enumerator.Current;
      var currentGroup = new List<T>();
      currentGroup.Add(firstValue);
      while (enumerator.MoveNext()) {
        var secondValue = enumerator.Current;
        var change = changePredicate(firstValue, secondValue);
        if (change) {
          yield return currentGroup;
          currentGroup = new List<T>();
        }
        currentGroup.Add(secondValue);
        firstValue = secondValue;
      }
      yield return currentGroup;
    }
  }

}

GroupOnChange将获取输入序列中的项目并将它们分组为一系列序列。当changePredicate为真时,将启动一个新组。

您可以使用GroupOnChange完全按照自己的意愿分割输入序列。然后,您必须使用Where删除作为值为零的组。

var groups = items
  .GroupOnChange((first, second) => first != second)
  .Where(group => group.First() != 0);

如果输入是类实例并且您希望按该类的属性进行分组,则也可以使用此方法。然后,您必须相应地修改谓词以比较属性。 (我知道你需要这个,因为你问了一个现在删除的问题,这个问题稍微复杂一点,输入序列不仅仅是数字,而是带有数字属性的类。)

答案 1 :(得分:6)

您可以编写如下的扩展方法:

public static class Extensions
{
    public static IEnumerable<IEnumerable<TSource>> Split<TSource>(this IEnumerable<TSource> source, TSource splitOn, IEqualityComparer<TSource> comparer = null)
    {
        if (source == null)
            throw new ArgumentNullException("source");
        return SplitIterator(source, splitOn, comparer);
    }

    private static IEnumerable<IEnumerable<TSource>> SplitIterator<TSource>(this IEnumerable<TSource> source, TSource splitOn, IEqualityComparer<TSource> comparer)
    {
        comparer = comparer ?? EqualityComparer<TSource>.Default;
        var current = new List<TSource>();
        foreach (var item in source)
        {
            if (comparer.Equals(item, splitOn))
            {
                if (current.Count > 0)
                {
                    yield return current;
                    current = new List<TSource>();
                }
            }
            else
            {
                current.Add(item);
            }
        }

        if (current.Count > 0)
            yield return current;
    }
}

并像这样使用它:

var list = new List<int>{1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1};
var result = list.Split(0);

答案 2 :(得分:5)

int c = 0;
var list = new List<int>{1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1};

var res = list
    // split in groups and set their numbers
    // c is a captured variable
    .Select(x=>new {Item = x, Subgroup = x==1 ? c : c++})
    // remove zeros
    .Where(x=>x.Item!=0)
    // create groups
    .GroupBy(x=>x.Subgroup)
    // convert to format List<List<int>>
    .Select(gr=>gr.Select(w=>w.Item).ToList())
    .ToList();

答案 3 :(得分:5)

您可以按下一个零的索引进行分组:

    static void Main(string[] args)
    {
        var list = new List<int> { 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1 };
        var result = list.Select((e, i) => new { Element = e, Index = i })
            .Where(e => e.Element == 1)
            .GroupBy(e => list.IndexOf(0, e.Index));
    }   

答案 4 :(得分:3)

也许更简单:

IEnumerable<IEnumerable<int>> Split(IEnumerable<int> items)
{
    List<int> result = new List<int>();
    foreach (int item in items)
        if (item == 0 && result.Any())
        {
            yield return result;
            result = new List<int>();
        }
        else
            result.Add(item);
    if (result.Any())
        yield return result;
}

答案 5 :(得分:3)

下面的代码通过迭代列表并存储&#39; 1&#39;的子序列来分割。在列表列表中。

X

答案 6 :(得分:0)

我最喜欢ASh的solution。我用它做了一点改动。 “ my”变体中没有捕获的变量:

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

        var res = list
            // Step 1: associate every value with an index
            .Select((x, i) => (Value: x, Index: i))
            // Step 2: remove separator values
            .Where(x => x.Value != 0)
            // Step 3: adjacent items will have the same result
            // subtracting real position from the index marked during the 1st step 
            .Select((tuple, realIndex) => (tuple.Value, GroupId: tuple.Index - realIndex))
            // Step 4: group by groupId
            .GroupBy(tuple => tuple.GroupId)
            // Step 5: convert to List<List<int>>
            .Select(group => group.Select(tuple => tuple.Value).ToList())
            .ToList();

有关步骤3的更多信息

比方说,我们有5个带有索引的项目:

[ 0  1  2  3  4 ]
{ 1, 1, 0, 1, 1 }

从步骤2过滤后,数字列表如下:

[ 0  1  3  4 ]
{ 1, 1, 1, 1 }

步骤3:

real indices (ri):              [ 0  1  2  3 ]
indices from the 1st step (i):  [ 0  1  3  4 ]
numbers:                        { 1, 1, 1, 1 }
i - ri:                         [ 0, 0, 1, 1 ]

第4步只是按减法i - ri(即所谓的GroupID)的结果进行分组。