Разделить список по элементу

У меня есть список 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

так лучше ли переводить каждый элемент в строку, присоединять их и делать то, что я показываю, что можно сделать со строкой?

Ответ 1

Вы можете решить свою проблему, преобразуя входную последовательность в последовательность последовательностей, как это делает LINQ GroupBy. Однако в вашем случае вы группируете изменения во входной последовательности. Возможно, существует возможность объединения существующих операторов LINQ, таких как GroupBy, Zip и Skip, в то, что делает то, что вы хотите, но я думаю, что проще (и работает лучше) создать блок итератора, который смотрит на пары элементы во входной последовательности:

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

Вы также можете использовать этот подход, если входные данные являются экземплярами класса, и вы хотите сгруппировать по свойству этого класса. Затем вам необходимо изменить предикат, чтобы сравнить свойства. (Я знаю, что вам это нужно, потому что вы задали вопрос, который теперь был удален, что было немного сложнее, когда входная последовательность была не просто числами, а классами с свойством number.)

Ответ 2

Вы можете написать метод расширения следующим образом:

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

Ответ 3

Вы можете просто группировать по индексу следующего нуля:

    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

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

Ответ 5

Возможно, что-то проще:

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;
}

Ответ 6

Нижеприведенный код разбивается на итерирование по списку и сохранение подпоследовательностей "1" в списке списков.

using System;
using System.Collections.Generic;

namespace SplitList
{
    class Program
    {
        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 };

            List<List<int>> splitSequences = new List<List<int>>();

            List<int> curSequence = new List<int>();

            foreach (var item in list)
            {
                if (item == 1) {
                    curSequence.Add(item);
                } else {
                    //Only push the current sequence onto the list of sequences if it is not empty,
                    //which could happen if multiple zeroes are encountered in a row
                    if (curSequence.Count > 0) {
                        splitSequences.Add(curSequence);
                        curSequence = new List<int>();
                    }
                }
            }
            //push any final list
            if (curSequence.Count > 0)
            {
                splitSequences.Add(curSequence);
            }

            foreach (var seq in splitSequences) {
                String line = String.Join(",", seq);
                Console.WriteLine(line);
            }

            Console.WriteLine("");
            Console.WriteLine("Press any key to exit");
            var discard = Console.ReadKey();
        }
    }
}