Лямбда для списка в списке [дубликат]

Для тех, у кого есть проблемы с IE и другими.

{
    float: left;
    width: 1px;
    height: 1px;
    background-color: transparent;
    border: none;
}
327
задан Draken 17 March 2017 в 18:38
поделиться

26 ответов

Попробуйте использовать следующий код.

public static IList<IList<T>> Split<T>(IList<T> source)
{
    return  source
        .Select((x, i) => new { Index = i, Value = x })
        .GroupBy(x => x.Index / 3)
        .Select(x => x.Select(v => v.Value).ToList())
        .ToList();
}

Идея состоит в том, чтобы сначала группировать элементы по индексам. Разделение на три приводит к группировке их в группы по 3. Затем преобразуйте каждую группу в список и IEnumerable из List в List из List s

321
ответ дан Mykola 17 August 2018 в 09:32
поделиться
  • 1
    GroupBy делает неявный вид. Это может убить производительность. Нам нужно что-то вроде инверсии SelectMany. – yfeldblum 7 January 2009 в 04:19
  • 2
    @Justice, GroupBy может быть реализована путем хеширования. Как вы знаете, что реализация GroupBy «может убить производительность»? – Amy B 7 January 2009 в 15:43
  • 3
    GroupBy ничего не возвращает, пока не перечислит все элементы. Вот почему это медленно. Списки OP хотят быть смежными, поэтому лучший метод может дать первый подписок [a,g,e], прежде чем перечислять больше исходного списка. – Colonel Panic 11 July 2012 в 15:20
  • 4
    Возьмите крайний пример бесконечного IEnumerable. GroupBy(x=>f(x)).First() никогда не даст группе. OP спросил о списках, но если мы напишем для работы с IEnumerable, делая только одну итерацию, мы пожинаем преимущество производительности. – Colonel Panic 11 July 2012 в 23:16
  • 5
    Тем не менее @Nick Order не сохранил ваш путь. По-прежнему хорошо знать, но вы бы группировали их (0,3,6,9, ...), (1,4,7,10, ...), (2,5,8 , 11, ...). Если порядок не имеет значения, тогда это прекрасно, но в этом случае это звучит так, как будто важно. – Reafexus 5 September 2013 в 20:52

Хорошо, вот мое занятие:

  • полностью ленив: работает с бесконечными перечислимыми
  • промежуточное копирование / буферизация
  • O (n )
  • работает также, ]

    Пример использования

    var src = new [] {1, 2, 3, 4, 5, 6}; 
    
    var c3 = src.Chunks(3);      // {{1, 2, 3}, {4, 5, 6}}; 
    var c4 = src.Chunks(4);      // {{1, 2, 3, 4}, {5, 6}}; 
    
    var sum   = c3.Select(c => c.Sum());    // {6, 15}
    var count = c3.Count();                 // 2
    var take2 = c3.Select(c => c.Take(2));  // {{1, 2}, {4, 5}}
    

    Пояснения

    Код работает путем вложенности два итератора на основе yield.

    Внешний итератор должен отслеживать, сколько элементов было эффективно использовано внутренним (куском) итератором. Это делается закрытием remaining с помощью innerMoveNext(). Неиспользуемые элементы куска отбрасываются до того, как следующий кусок будет получен внешним итератором. Это необходимо, потому что в противном случае вы получите несогласованные результаты, когда внутренние перечислимые элементы не будут (полностью) потреблены (например, c3.Count() вернет 6).

    Примечание: Ответ был обновлен для устранения недостатков, отмеченных @ aolszowka.

39
ответ дан 3dGrabber 17 August 2018 в 09:32
поделиться
  • 1
    Очень хорошо. Мой «правильный» решение было более сложным. Это ответ №1 ИМХО. – CaseyB 8 January 2014 в 23:37
  • 2
    Это связано с непредвиденным (с точки зрения API) поведением, когда вызывается ToArray (), он также не является потокобезопасным. – aolszowka 13 May 2014 в 14:57
  • 3
    @aolszowka: не могли бы вы рассказать? – 3dGrabber 14 May 2014 в 09:40
  • 4
    @ 3dGrabber Возможно, именно так я переоценил ваш код (извините его слишком долго, чтобы пройти здесь, в основном вместо метода расширения, который я передал в sourceEnumerator). Я использовал тестовый пример: int [] arrayToSort = new int [] {9, 7, 2, 6, 3, 4, 8, 5, 1, 10, 11, 12, 13}; var source = Chunkify & lt; int & gt; (arrayToSort, 3) .ToArray (); Результат в источнике указывает, что было 13 кусков (количество элементов). Это имело смысл для меня, поскольку, если вы не запросили внутренние перечисления, счетчик не был увеличен. – aolszowka 14 May 2014 в 15:40
  • 5
    @ aolszowka: очень действительные баллы. Я добавил предупреждение и раздел использования. Код предполагает, что вы перебираете внутреннюю перечислимую. С вашим решением вы теряете ленту. Я думаю, что должно быть возможно получить лучшее из обоих миров с обычным кешированием IEnumerator. Если я найду решение, я отправлю его здесь ... – 3dGrabber 20 May 2014 в 10:10

Вот процедура разбиения списка, которую я написал пару месяцев назад:

public static List<List<T>> Chunk<T>(
    List<T> theList,
    int chunkSize
)
{
    List<List<T>> result = theList
        .Select((x, i) => new {
            data = x,
            indexgroup = i / chunkSize
        })
        .GroupBy(x => x.indexgroup, x => x.data)
        .Select(g => new List<T>(g))
        .ToList();

    return result;
}
6
ответ дан Amy B 17 August 2018 в 09:32
поделиться

Это старый вопрос, но это то, с чем я столкнулся; он перечисляет перечисление только один раз, но создает списки для каждого из разделов. Он не страдает от неожиданного поведения, когда ToArray() вызывается как некоторые из реализаций:

    public static IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int chunkSize)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }

        if (chunkSize < 1)
        {
            throw new ArgumentException("Invalid chunkSize: " + chunkSize);
        }

        using (IEnumerator<T> sourceEnumerator = source.GetEnumerator())
        {
            IList<T> currentChunk = new List<T>();
            while (sourceEnumerator.MoveNext())
            {
                currentChunk.Add(sourceEnumerator.Current);
                if (currentChunk.Count == chunkSize)
                {
                    yield return currentChunk;
                    currentChunk = new List<T>();
                }
            }

            if (currentChunk.Any())
            {
                yield return currentChunk;
            }
        }
    }
5
ответ дан aolszowka 17 August 2018 в 09:32
поделиться
  • 1
    Было бы неплохо преобразовать это в метод расширения: public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int chunkSize) – krizzzn 24 June 2014 в 15:30
  • 2
    +1 за ваш ответ. Однако я рекомендую две вещи: 1. используйте foreach вместо того, чтобы использовать блок. 2. Пропустите chunkSize в конструкторе List, чтобы список знал его максимальный ожидаемый размер. – Usman Zafar 21 July 2014 в 08:26

В общем, подход, предложенный CaseyB , отлично работает, на самом деле, если вы проходите в List<T>, трудно его винить, возможно, я бы изменил его на:

public static IEnumerable<IEnumerable<T>> ChunkTrivialBetter<T>(this IEnumerable<T> source, int chunksize)
{
   var pos = 0; 
   while (source.Skip(pos).Any())
   {
      yield return source.Skip(pos).Take(chunksize);
      pos += chunksize;
   }
}

Это позволит избежать массовых цепочек вызовов. Тем не менее, этот подход имеет общий недостаток. Он материализует два перечисления на кусок, чтобы подчеркнуть проблему:

foreach (var item in Enumerable.Range(1, int.MaxValue).Chunk(8).Skip(100000).First())
{
   Console.WriteLine(item);
}
// wait forever 

Чтобы преодолеть это, мы можем попробовать подход Кэмерона , который проходит вышеупомянутый тест в летающих цветах, поскольку он только перебирает перечисление один раз.

Проблема в том, что у него есть другой недостаток, он материализует каждый элемент в каждом фрагменте, проблема с этим подходом заключается в том, что вы работаете высоко в памяти.

Чтобы проиллюстрировать эту попытку:

foreach (var item in Enumerable.Range(1, int.MaxValue)
               .Select(x => x + new string('x', 100000))
               .Clump(10000).Skip(100).First())
{
   Console.Write('.');
}
// OutOfMemoryException

Наконец, любая реализация должна иметь возможность обрабатывать итерацию кусков, например:

Enumerable.Range(1,3).Chunk(2).Reverse().ToArray()
// should return [3],[1,2]

Многие очень оптимальные решения, такие как моя первая версия этого ответа, не удалось. Такую же проблему можно найти в answer.One оптимизированный ответ .

Чтобы решить все эти проблемы, вы можете использовать следующее:

namespace ChunkedEnumerator
{
    public static class Extensions 
    {
        class ChunkedEnumerable<T> : IEnumerable<T>
        {
            class ChildEnumerator : IEnumerator<T>
            {
                ChunkedEnumerable<T> parent;
                int position;
                bool done = false;
                T current;


                public ChildEnumerator(ChunkedEnumerable<T> parent)
                {
                    this.parent = parent;
                    position = -1;
                    parent.wrapper.AddRef();
                }

                public T Current
                {
                    get
                    {
                        if (position == -1 || done)
                        {
                            throw new InvalidOperationException();
                        }
                        return current;

                    }
                }

                public void Dispose()
                {
                    if (!done)
                    {
                        done = true;
                        parent.wrapper.RemoveRef();
                    }
                }

                object System.Collections.IEnumerator.Current
                {
                    get { return Current; }
                }

                public bool MoveNext()
                {
                    position++;

                    if (position + 1 > parent.chunkSize)
                    {
                        done = true;
                    }

                    if (!done)
                    {
                        done = !parent.wrapper.Get(position + parent.start, out current);
                    }

                    return !done;

                }

                public void Reset()
                {
                    // per http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
                    throw new NotSupportedException();
                }
            }

            EnumeratorWrapper<T> wrapper;
            int chunkSize;
            int start;

            public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
            {
                this.wrapper = wrapper;
                this.chunkSize = chunkSize;
                this.start = start;
            }

            public IEnumerator<T> GetEnumerator()
            {
                return new ChildEnumerator(this);
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

        }

        class EnumeratorWrapper<T>
        {
            public EnumeratorWrapper (IEnumerable<T> source)
            {
                SourceEumerable = source;
            }
            IEnumerable<T> SourceEumerable {get; set;}

            Enumeration currentEnumeration;

            class Enumeration
            {
                public IEnumerator<T> Source { get; set; }
                public int Position { get; set; }
                public bool AtEnd { get; set; }
            }

            public bool Get(int pos, out T item) 
            {

                if (currentEnumeration != null && currentEnumeration.Position > pos)
                {
                    currentEnumeration.Source.Dispose();
                    currentEnumeration = null;
                }

                if (currentEnumeration == null)
                {
                    currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
                }

                item = default(T);
                if (currentEnumeration.AtEnd)
                {
                    return false;
                }

                while(currentEnumeration.Position < pos) 
                {
                    currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
                    currentEnumeration.Position++;

                    if (currentEnumeration.AtEnd) 
                    {
                        return false;
                    }

                }

                item = currentEnumeration.Source.Current;

                return true;
            }

            int refs = 0;

            // needed for dispose semantics 
            public void AddRef()
            {
                refs++;
            }

            public void RemoveRef()
            {
                refs--;
                if (refs == 0 && currentEnumeration != null)
                {
                    var copy = currentEnumeration;
                    currentEnumeration = null;
                    copy.Source.Dispose();
                }
            }
        }

        public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
        {
            if (chunksize < 1) throw new InvalidOperationException();

            var wrapper =  new EnumeratorWrapper<T>(source);

            int currentPos = 0;
            T ignore;
            try
            {
                wrapper.AddRef();
                while (wrapper.Get(currentPos, out ignore))
                {
                    yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
                    currentPos += chunksize;
                }
            }
            finally
            {
                wrapper.RemoveRef();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int i = 10;
            foreach (var group in Enumerable.Range(1, int.MaxValue).Skip(10000000).Chunk(3))
            {
                foreach (var n in group)
                {
                    Console.Write(n);
                    Console.Write(" ");
                }
                Console.WriteLine();
                if (i-- == 0) break;
            }


            var stuffs = Enumerable.Range(1, 10).Chunk(2).ToArray();

            foreach (var idx in new [] {3,2,1})
            {
                Console.Write("idx " + idx + " ");
                foreach (var n in stuffs[idx])
                {
                    Console.Write(n);
                    Console.Write(" ");
                }
                Console.WriteLine();
            }

            /*

10000001 10000002 10000003
10000004 10000005 10000006
10000007 10000008 10000009
10000010 10000011 10000012
10000013 10000014 10000015
10000016 10000017 10000018
10000019 10000020 10000021
10000022 10000023 10000024
10000025 10000026 10000027
10000028 10000029 10000030
10000031 10000032 10000033
idx 3 7 8
idx 2 5 6
idx 1 3 4
             */

            Console.ReadKey();


        }

    }
}

Существует также раунд оптимизаций, которые вы могли бы ввести для нестандартной итерации кусков, которая здесь отсутствует.

Каким образом вы должны выбрать? Это полностью зависит от проблемы, которую вы пытаетесь решить. Если вы не заинтересованы в первом недостатке, простой ответ невероятно привлекателен.

Обратите внимание, что как и в большинстве методов, это небезопасно для многопоточности, материал может стать странным, если вы хотите сделать его потокобезопасным необходимо будет исправить EnumeratorWrapper.

90
ответ дан Atif Aziz 17 August 2018 в 09:32
поделиться
  • 1
    Будет ли ошибка перечислимой.Range (0, 100) .Chunk (3) .Reverse (). ToArray () ошибочен или Enumerable.Range (0, 100) .ToArray (). Chunk (3) .Reverse () .ToArray () выбрасывает исключение? – Cameron MacFarland 3 May 2012 в 07:39
  • 2
    @SamSaffron Я обновил свой ответ и очень сильно упростил код, поскольку я считаю, что это важный пример использования (и признать оговорки). – casperOne 3 May 2012 в 16:45
  • 3
    Как насчет блокировки IQueryable & lt; & gt ;? Я предполагаю, что подход Take / Skip был бы оптимальным, если мы хотим делегировать максимум операций провайдеру – Guillaume86 3 May 2012 в 17:45
  • 4
    @ Guillaume86 Я согласен, если у вас есть IList или IQueryable, вы можете использовать всевозможные ярлыки, которые сделают это намного быстрее (Linq делает это внутренне для всех видов других методов) – Sam Saffron 3 May 2012 в 22:53
  • 5
    Это, безусловно, лучший ответ для эффективности. У меня возникла проблема с использованием SqlBulkCopy с IEnumerable, которая запускает дополнительные процессы в каждом столбце, поэтому она должна выполняться эффективно только за один проход. Это позволит мне разбить IEnumerable на куски управляемого размера. (Для тех, кто задавался вопросом, я включил режим потоковой передачи SqlBulkCopy, который, кажется, был сломан). – Brain2000 30 March 2016 в 19:23

Несколько лет назад я написал метод расширения Clump. Отлично работает и является самой быстрой реализацией здесь. : P

/// <summary>
/// Clumps items into same size lots.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source list of items.</param>
/// <param name="size">The maximum size of the clumps to make.</param>
/// <returns>A list of list of items, where each list of items is no bigger than the size given.</returns>
public static IEnumerable<IEnumerable<T>> Clump<T>(this IEnumerable<T> source, int size)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (size < 1)
        throw new ArgumentOutOfRangeException("size", "size must be greater than 0");

    return ClumpIterator<T>(source, size);
}

private static IEnumerable<IEnumerable<T>> ClumpIterator<T>(IEnumerable<T> source, int size)
{
    Debug.Assert(source != null, "source is null.");

    T[] items = new T[size];
    int count = 0;
    foreach (var item in source)
    {
        items[count] = item;
        count++;

        if (count == size)
        {
            yield return items;
            items = new T[size];
            count = 0;
        }
    }
    if (count > 0)
    {
        if (count == size)
            yield return items;
        else
        {
            T[] tempItems = new T[count];
            Array.Copy(items, tempItems, count);
            yield return tempItems;
        }
    }
}
6
ответ дан Cameron MacFarland 17 August 2018 в 09:32
поделиться
  • 1
    он должен работать, но он буферизирует 100% кусков, я пытался избежать этого ... но он оказался невероятно волосатым. – Sam Saffron 3 May 2012 в 08:03
  • 2
    @SamSaffron Yep. Особенно, если вы бросаете такие вещи, как plinq, в микс, из-за чего моя реализация была изначально. – Cameron MacFarland 3 May 2012 в 08:06
  • 3
    расширил мой ответ, дайте мне знать, что вы думаете – Sam Saffron 5 May 2012 в 01:59
  • 4
    @CameronMacFarland - можете ли вы объяснить, почему нужна вторая проверка для count == size? Благодарю. – dugas 26 September 2013 в 19:44

Этот вопрос немного устарел, но я просто написал это, и я думаю, что он немного более изящный, чем другие предлагаемые решения:

/// <summary>
/// Break a list of items into chunks of a specific size
/// </summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
    while (source.Any())
    {
        yield return source.Take(chunksize);
        source = source.Skip(chunksize);
    }
}
285
ответ дан CaseyB 17 August 2018 в 09:32
поделиться
  • 1
    Любите это решение. Я бы рекомендовал добавить эту проверку здравомыслия, чтобы предотвратить бесконечный цикл: if (chunksize <= 0) throw new ArgumentException("Chunk size must be greater than zero.", "chunksize"); – mroach 1 March 2012 в 18:34
  • 2
    Мне это нравится, но это не суперэффективно – Sam Saffron 3 May 2012 в 05:26
  • 3
    Мне нравится этот, но эффективность времени - O(n²). Вы можете перебирать список и получать время O(n). – hIpPy 30 August 2012 в 19:22
  • 4
    @hIpPy, как это n ^ 2? Выглядит линейно для меня – Vivek Maharajh 9 October 2013 в 22:02
  • 5
    @vivekmaharajh source заменяется на обертку IEnumerable каждый раз. Итак, взятие элементов из source проходит через слои Skip s – Lasse Espeholt 24 November 2013 в 14:04

Мы можем улучшить решение @ JaredPar, чтобы сделать истинную ленивую оценку. Мы используем метод GroupAdjacentBy , который дает группы последовательных элементов с одним и тем же ключом:

sequence
.Select((x, i) => new { Value = x, Index = i })
.GroupAdjacentBy(x=>x.Index/3)
.Select(g=>g.Select(x=>x.Value))

Поскольку группы получаются один за другим, это решение работает эффективно с длинными или бесконечными последовательностями.

8
ответ дан Colonel Panic 17 August 2018 в 09:32
поделиться

Вы могли использовать несколько запросов, которые используют Take и Skip , но это добавит слишком много итераций на Исходный список, я считаю.

Скорее, я думаю, что вы должны создать собственный итератор, например:

public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
  IEnumerable<T> enumerable, int groupSize)
{
   // The list to return.
   List<T> list = new List<T>(groupSize);

   // Cycle through all of the items.
   foreach (T item in enumerable)
   {
     // Add the item.
     list.Add(item);

     // If the list has the number of elements, return that.
     if (list.Count == groupSize)
     {
       // Return the list.
       yield return list;

       // Set the list to a new list.
       list = new List<T>(groupSize);
     }
   }

   // Return the remainder if there is any,
   if (list.Count != 0)
   {
     // Return the list.
     yield return list;
   }
}

Затем вы можете вызвать это, и LINQ включен так, вы можете выполнять другие операции над результирующими последовательностями.


В ответ на ответ Сэма , я чувствовал, что существует более простой способ сделать это без:

  • Итерирование по списку снова (что я не делал изначально)
  • Материализация элементов в группах перед выпуском фрагмента (для больших фрагментов элементов возникли проблемы с памятью)
  • Весь код, отправленный Сэм

Итак, вот еще один проход, который я кодировал в методе расширения до IEnumerable<T> Chunk:

public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, 
    int chunkSize)
{
    // Validate parameters.
    if (source == null) throw new ArgumentNullException("source");
    if (chunkSize <= 0) throw new ArgumentOutOfRangeException("chunkSize",
        "The chunkSize parameter must be a positive value.");

    // Call the internal implementation.
    return source.ChunkInternal(chunkSize);
}

Ничего удивительного там, просто базовая проверка ошибок.

Переход к ChunkInternal:

private static IEnumerable<IEnumerable<T>> ChunkInternal<T>(
    this IEnumerable<T> source, int chunkSize)
{
    // Validate parameters.
    Debug.Assert(source != null);
    Debug.Assert(chunkSize > 0);

    // Get the enumerator.  Dispose of when done.
    using (IEnumerator<T> enumerator = source.GetEnumerator())
    do
    {
        // Move to the next element.  If there's nothing left
        // then get out.
        if (!enumerator.MoveNext()) yield break;

        // Return the chunked sequence.
        yield return ChunkSequence(enumerator, chunkSize);
    } while (true);
}

В принципе , он получает IEnumerator<T> и вручную выполняет итерацию через каждый элемент. Он проверяет, есть ли какие-либо элементы, которые в настоящее время перечислены. После того, как каждый фрагмент перечислит, если не осталось никаких элементов, он выйдет из строя.

Как только он обнаруживает, что есть элементы в последовательности, он делегирует ответственность за внутреннюю реализацию IEnumerable<T> до ChunkSequence:

private static IEnumerable<T> ChunkSequence<T>(IEnumerator<T> enumerator, 
    int chunkSize)
{
    // Validate parameters.
    Debug.Assert(enumerator != null);
    Debug.Assert(chunkSize > 0);

    // The count.
    int count = 0;

    // There is at least one item.  Yield and then continue.
    do
    {
        // Yield the item.
        yield return enumerator.Current;
    } while (++count < chunkSize && enumerator.MoveNext());
}

Поскольку MoveNext уже вызывается на IEnumerator<T>, переданном в ChunkSequence, он возвращает элемент, возвращенный Current , а затем увеличивает счетчик, чтобы никогда не возвращать больше, чем chunkSize элементов и переходить к следующему элементу в последовательности после каждой итерации (но закорочен, если количество полученных элементов превышает размер куска).

Если никаких элементов не осталось, то метод InternalChunk сделает другой проход во внешнем цикле, но когда MoveNext вызывается второй раз, он все равно вернет false, в качестве в документации (выделенное мое):

Если MoveNext передает конец коллекции, перечислитель помещается после последнего элемента в коллекции, а MoveNext возвращает false. Когда перечислитель находится в этой позиции, последующие вызовы MoveNext также возвращают false до тех пор, пока не вызывается Reset.

В этот момент цикл прерывается, и последовательность последовательностей прекращается.

Это простой тест:

static void Main()
{
    string s = "agewpsqfxyimc";

    int count = 0;

    // Group by three.
    foreach (IEnumerable<char> g in s.Chunk(3))
    {
        // Print out the group.
        Console.Write("Group: {0} - ", ++count);

        // Print the items.
        foreach (char c in g)
        {
            // Print the item.
            Console.Write(c + ", ");
        }

        // Finish the line.
        Console.WriteLine();
    }
}

Выход:

Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,

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

Кроме того, он будет делать странные вещи, если вы играете с порядком, так же, как Сэм делал в какой-то момент .

61
ответ дан Community 17 August 2018 в 09:32
поделиться
  • 1
    Я думаю, что это лучшее решение ... единственная проблема заключается в том, что в списке нет Length ... у него есть Count. Но это легко изменить. Мы можем сделать это лучше, даже не создавая списки, но возвращая ienumerables, которые содержат ссылки на основной список со смещением / длиной. Итак, если группировка велика, мы не теряем память. Комментарий, если вы хотите, чтобы я это написал. – Amir 18 November 2009 в 05:39
  • 2
    @Amir я бы хотел, чтобы это записано – samandmoore 31 May 2011 в 16:38
  • 3
    Это хорошо и быстро - Cameron опубликовал очень похожего, а после вашего, только оговорка в том, что он буферизует куски, это может привести к нехватке памяти, если куски и размеры элементов большие. См. Мой ответ на альтернативу, хотя и очень волосатую, ответ. – Sam Saffron 3 May 2012 в 12:59
  • 4
    @SamSaffron Да, если у вас есть большое количество элементов в List<T>, у вас, очевидно, будут проблемы с памятью из-за буферизации. Оглядываясь назад, я должен был отметить это в ответе, но, похоже, в то время основное внимание уделялось слишком много итераций. Тем не менее, ваше решение действительно волосатое. Я не тестировал его, но теперь мне интересно, есть ли менее волосатое решение. – casperOne 3 May 2012 в 13:05
  • 5
    @casperOne да ... Google дал мне эту страницу, когда я искал способ разделить перечислимые числа, для моего конкретного случая использования я разделяю безумно большой список записей, которые возвращаются из db, если я материализую их на (в действительности у dapper есть опция buffer: false для этого варианта использования) – Sam Saffron 3 May 2012 в 13:11

System.Interactive предоставляет Buffer() для этой цели. Некоторые быстрые тесты показывают, что производительность похожа на решение Сэма.

8
ответ дан dahlbyk 17 August 2018 в 09:32
поделиться
  • 1
    знаете ли вы буферизующую семантику? например: если у вас есть счетчик, который выплескивает строки размером 300 КБ и пытается разбить его на 10 000 кусков размера, вы получите из памяти? – Sam Saffron 3 May 2012 в 12:52
  • 2
    Buffer() возвращает IEnumerable<IList<T>>, так что да, у вас, вероятно, возникнет проблема - он не течет, как ваш. – dahlbyk 3 May 2012 в 20:53

Если список имеет тип system.collections.generic, вы можете использовать метод CopyTo для копирования элементов вашего массива в другие вспомогательные массивы. Вы указываете начальный элемент и количество копируемых элементов.

Вы также можете сделать 3 клона исходного списка и использовать «RemoveRange» в каждом списке, чтобы уменьшить список до нужного размера.

Или просто создайте вспомогательный метод, чтобы сделать это для вас.

3
ответ дан Jobo 17 August 2018 в 09:32
поделиться

Для всех, кто интересуется решением с пакетом / поддержкой, библиотека MoreLINQ предоставляет метод расширения Batch , который соответствует вашему запросу:

IEnumerable<char> source = "Example string";
IEnumerable<IEnumerable<char>> chunksOfThreeChars = source.Batch(3);

Реализация Batch аналогична ответу Кэмерона МакФарланда с добавлением перегрузки для преобразования пакета / партии перед возвратом и выполняется достаточно хорошо.

0
ответ дан Kevinoid 17 August 2018 в 09:32
поделиться

Настолько, что это подход Сэм Шаффрон .

public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "Size must be greater than zero.");

    return BatchImpl(source, size).TakeWhile(x => x.Any());
}

static IEnumerable<IEnumerable<T>> BatchImpl<T>(this IEnumerable<T> source, int size)
{
    var values = new List<T>();
    var group = 1;
    var disposed = false;
    var e = source.GetEnumerator();

    try
    {
        while (!disposed)
        {
            yield return GetBatch(e, values, group, size, () => { e.Dispose(); disposed = true; });
            group++;
        }
    }
    finally
    {
        if (!disposed)
            e.Dispose();
    }
}

static IEnumerable<T> GetBatch<T>(IEnumerator<T> e, List<T> values, int group, int size, Action dispose)
{
    var min = (group - 1) * size + 1;
    var max = group * size;
    var hasValue = false;

    while (values.Count < min && e.MoveNext())
    {
        values.Add(e.Current);
    }

    for (var i = min; i <= max; i++)
    {
        if (i <= values.Count)
        {
            hasValue = true;
        }
        else if (hasValue = e.MoveNext())
        {
            values.Add(e.Current);
        }
        else
        {
            dispose();
        }

        if (hasValue)
            yield return values[i - 1];
        else
            yield break;
    }
}

}

0
ответ дан leandromoh 17 August 2018 в 09:32
поделиться

Это следующее решение является наиболее компактным, я мог бы придумать, что это O (n).

public static IEnumerable<T[]> Chunk<T>(IEnumerable<T> source, int chunksize)
{
    var list = source as IList<T> ?? source.ToList();
    for (int start = 0; start < list.Count; start += chunksize)
    {
        T[] chunk = new T[Math.Min(chunksize, list.Count - start)];
        for (int i = 0; i < chunk.Length; i++)
            chunk[i] = list[start + i];

        yield return chunk;
    }
}
12
ответ дан Marc-André Bertrand 17 August 2018 в 09:32
поделиться

Мы обнаружили, что решение Дэвида Б работает лучше всего. Но мы адаптировали его к более общему решению:

list.GroupBy(item => item.SomeProperty) 
   .Select(group => new List<T>(group)) 
   .ToArray();
4
ответ дан mwjackson 17 August 2018 в 09:32
поделиться
  • 1
    Это приятно, но сильно отличается от того, о чем просил первоначальный проситель. – Amy B 27 June 2011 в 15:50

Использование модульного разбиения:

public IEnumerable<IEnumerable<string>> Split(IEnumerable<string> input, int chunkSize)
{
    var chunks = (int)Math.Ceiling((double)input.Count() / (double)chunkSize);
    return Enumerable.Range(0, chunks).Select(id => input.Where(s => s.GetHashCode() % chunks == id));
}
1
ответ дан nawfal 17 August 2018 в 09:32
поделиться

Чтобы вставить мои два цента ...

Используя тип списка для источника, который должен быть помечен, я нашел еще одно очень компактное решение:

public static IEnumerable<IEnumerable<TSource>> Chunk<TSource>(this IEnumerable<TSource> source, int chunkSize)
{
    // copy the source into a list
    var chunkList = source.ToList();

    // return chunks of 'chunkSize' items
    while (chunkList.Count > chunkSize)
    {
        yield return chunkList.GetRange(0, chunkSize);
        chunkList.RemoveRange(0, chunkSize);
    }

    // return the rest
    yield return chunkList;
}
-1
ответ дан Patrick 17 August 2018 в 09:32
поделиться

Может работать с бесконечными генераторами:

a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1)))
 .Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1)))
 .Where((x, i) => i % 3 == 0)

Демо-код: https://ideone.com/GKmL7M

using System;
using System.Collections.Generic;
using System.Linq;

public class Test
{
  private static void DoIt(IEnumerable<int> a)
  {
    Console.WriteLine(String.Join(" ", a));

    foreach (var x in a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1))).Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1))).Where((x, i) => i % 3 == 0))
      Console.WriteLine(String.Join(" ", x));

    Console.WriteLine();
  }

  public static void Main()
  {
    DoIt(new int[] {1});
    DoIt(new int[] {1, 2});
    DoIt(new int[] {1, 2, 3});
    DoIt(new int[] {1, 2, 3, 4});
    DoIt(new int[] {1, 2, 3, 4, 5});
    DoIt(new int[] {1, 2, 3, 4, 5, 6});
  }
}
1

1 2

1 2 3
1 2 3

1 2 3 4
1 2 3

1 2 3 4 5
1 2 3

1 2 3 4 5 6
1 2 3
4 5 6

Но на самом деле я бы предпочел написать соответствующий метод без linq.

0
ответ дан Qwertiy 17 August 2018 в 09:32
поделиться

Старый код, но это то, что я использовал:

    public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
    {
        var toReturn = new List<T>(max);
        foreach (var item in source)
        {
            toReturn.Add(item);
            if (toReturn.Count == max)
            {
                yield return toReturn;
                toReturn = new List<T>(max);
            }
        }
        if (toReturn.Any())
        {
            yield return toReturn;
        }
    }
4
ответ дан Robert McKee 17 August 2018 в 09:32
поделиться
  • 1
    После публикации я понял, что это точно такой же код casperOne, опубликованный 6 лет назад, с заменой использования .Any () вместо .Count (), поскольку мне не нужен весь счет, просто нужно знать, существуют ли какие-либо , – Robert McKee 25 February 2015 в 10:47

Как насчет этого?

var input = new List<string> { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };
var k = 3

var res = Enumerable.Range(0, (input.Count - 1) / k + 1)
                    .Select(i => input.GetRange(i * k, Math.Min(k, input.Count - i * k)))
                    .ToList();

Насколько я знаю, GetRange () является линейным по количеству принятых элементов. Так что это должно хорошо работать.

3
ответ дан Roman Pekar 17 August 2018 в 09:32
поделиться

полностью ленив, нет счета или копирования:

public static class EnumerableExtensions
{

  public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int len)
  {
     if (len == 0)
        throw new ArgumentNullException();

     var enumer = source.GetEnumerator();
     while (enumer.MoveNext())
     {
        yield return Take(enumer.Current, enumer, len);
     }
  }

  private static IEnumerable<T> Take<T>(T head, IEnumerator<T> tail, int len)
  {
     while (true)
     {
        yield return head;
        if (--len == 0)
           break;
        if (tail.MoveNext())
           head = tail.Current;
        else
           break;
     }
  }
}
13
ответ дан user1583558 17 August 2018 в 09:32
поделиться
  • 1
    Это решение настолько изящно, что мне жаль, что я не могу выдвинуть этот ответ более одного раза. – Mark 10 April 2015 в 14:21
  • 2
    Я не думаю, что это никогда не потерпит неудачу. Но это, безусловно, может иметь какое-то странное поведение. Если у вас было 100 предметов, и вы разделились на партии по 10, и вы перечисляли все партии без перечисления каких-либо предметов этих партий, вы получили бы 100 партий по 1 штуке. – CaseyB 15 April 2015 в 20:58
  • 3
    Как упоминал @CaseyB, это страдает от того же неудачного 3dGrabber, что и здесь stackoverflow.com/a/20953521/1037948 , но человек быстро! – drzaus 10 June 2015 в 18:04
  • 4
    Это прекрасное решение. То, что он обещает. – Rod Hartzell 16 February 2016 в 23:59

Просто вставляем мои два цента. Если вы хотите «ведро» списка (визуализировать слева направо), вы можете сделать следующее:

 public static List<List<T>> Buckets<T>(this List<T> source, int numberOfBuckets)
    {
        List<List<T>> result = new List<List<T>>();
        for (int i = 0; i < numberOfBuckets; i++)
        {
            result.Add(new List<T>());
        }

        int count = 0;
        while (count < source.Count())
        {
            var mod = count % numberOfBuckets;
            result[mod].Add(source[count]);
            count++;
        }
        return result;
    }
1
ответ дан user1883961 17 August 2018 в 09:32
поделиться

Я считаю, что этот небольшой фрагмент делает работу довольно хорошо.

public static IEnumerable<List<T>> Chunked<T>(this List<T> source, int chunkSize)
{
    var offset = 0;

    while (offset < source.Count)
    {
        yield return source.GetRange(offset, Math.Min(source.Count - offset, chunkSize));
        offset += chunkSize;
    }
}
5
ответ дан Vivek Jain 17 August 2018 в 09:32
поделиться

Я взял основной ответ и сделал его контейнером МОК, чтобы определить, где разделить. ( Для тех, кто действительно ищет только разделение на 3 элемента, при чтении этого сообщения при поиске ответа? )

Этот метод позволяет разделить на любой тип элемента как

public static List<List<T>> SplitOn<T>(List<T> main, Func<T, bool> splitOn)
{
    int groupIndex = 0;

    return main.Select( item => new 
                             { 
                               Group = (splitOn.Invoke(item) ? ++groupIndex : groupIndex), 
                               Value = item 
                             })
                .GroupBy( it2 => it2.Group)
                .Select(x => x.Select(v => v.Value).ToList())
                .ToList();
}

Итак, для OP код будет

var it = new List<string>()
                       { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };

int index = 0; 
var result = SplitOn(it, (itm) => (index++ % 3) == 0 );
2
ответ дан ΩmegaMan 17 August 2018 в 09:32
поделиться
  • 1
    Очень похоже на подход, который я использовал, но я рекомендую, чтобы источник не был IEnumerable. Например, если источник является результатом запроса LINQ, Skip / Take инициирует nbChunk перечисления запроса. Может стоить дорого. Лучше было бы использовать IList или ICollection как тип источника. Это позволяет избежать проблемы в целом. – RB Davidson 27 April 2018 в 12:25
12
ответ дан Marc-André Bertrand 6 September 2018 в 07:12
поделиться
2
ответ дан ΩmegaMan 6 September 2018 в 07:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: