Dividir uma coleção em partes `n` com o LINQ?

122

Existe uma boa maneira de dividir uma coleção em npartes com o LINQ? Não necessariamente igualmente, é claro.

Ou seja, quero dividir a coleção em sub-coleções, cada uma contendo um subconjunto dos elementos, onde a última coleção pode ser irregular.

Simon_Weaver
fonte
1
Retagged: A questão não tem nada a ver com asp.net. Marque suas perguntas adequadamente.
Como exatamente você deseja que eles se dividam, se não mesmo (permitindo o fim, é claro)?
Marc Gravell
1
quem ligou para esta pergunta? john era você? :-) de repente, todas essas respostas :-)
Simon_Weaver
Questão relacionada Dividir Lista em sublistas com LINQ
Gennady Vanin Геннадий Ванин
@ Simon_Weaver Tentei esclarecer o que você está perguntando com base na resposta aceita. De fato, existem muitas maneiras de 'dividir' uma lista, incluindo decompor cada elemento da lista em seus elementos e colocá-los nas chamadas listas 'paralelas'.
jpaugh

Respostas:

127

Um linq puro e a solução mais simples são mostrados abaixo.

static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
        int i = 0;
        var splits = from item in list
                     group item by i++ % parts into part
                     select part.AsEnumerable();
        return splits;
    }
}
Muhammad Hasan Khan
fonte
3
Você pode fazer: selecionar parte.AsEnumerable () em vez de selecionar (IEnumerable <T>) parte. Parece mais elegante.
tuinstoel 11/02/09
2
Fazer todas essas operações de módulo pode ficar um pouco caro em longas listas.
Jonathan Allen
8
Seria melhor usar a sobrecarga Select que inclui o índice.
Marc Gravell
1
Eu adicionei uma resposta que usa o seleto sobrecarga e método de encadeamento de sintaxe
reustmd
1
.AsEnumerable()não é necessário, IGrouping <T> já é um IEnumerable <T>.
22415 Alex
58

EDIT: Ok, parece que eu interpretei errado a pergunta. Eu li como "pedaços de comprimento n" em vez de "n pedaços". Doh! Considerando a exclusão da resposta ...

(Resposta original)

Não acredito que exista uma maneira interna de particionar, embora pretenda escrever uma no meu conjunto de adições ao LINQ to Objects. Marc Gravell tem uma implementação aqui, embora eu provavelmente a modifique para retornar uma exibição somente leitura:

public static IEnumerable<IEnumerable<T>> Partition<T>
    (this IEnumerable<T> source, int size)
{
    T[] array = null;
    int count = 0;
    foreach (T item in source)
    {
        if (array == null)
        {
            array = new T[size];
        }
        array[count] = item;
        count++;
        if (count == size)
        {
            yield return new ReadOnlyCollection<T>(array);
            array = null;
            count = 0;
        }
    }
    if (array != null)
    {             
        Array.Resize(ref array, count);
        yield return new ReadOnlyCollection<T>(array);
    }
}
Jon Skeet
fonte
Darn - beat me to it ;-p
Marc Gravell
3
Você realmente não fazer como os "array [count ++]", eh ;-p
Marc Gravell
18
Obrigado por não excluir, mesmo que não seja uma resposta para o OP, eu queria exatamente a mesma coisa - pedaços de comprimento n :).
Gishu
2
@Dejan: Não, não. Observe o uso de yield return. Requer que um lote esteja na memória de cada vez, mas é tudo.
Jon tiro ao prato
1
@Dejan: Direito - Eu não gostaria de adivinhar sobre como ele interage com o particionamento LINQ paralelo, para ser honesto :)
Jon Skeet
39
static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
            return list.Select((item, index) => new {index, item})
                       .GroupBy(x => x.index % parts)
                       .Select(x => x.Select(y => y.item));
    }
}
reustmd
fonte
28
Tenho uma aversão irracional ao Linq no estilo SQL, então essa é a minha resposta favorita.
Piedar
1
@ manu08, eu tentei o seu código, tenho uma lista var dept = {1,2,3,4,5}. Depois de dividir o resultado é como dept1 = {1,3,5}e dept2 = { 2,4 }onde parts = 2. Mas o resultado que eu preciso é dept1 = {1,2,3}edept2 = {4,5}
Karthik Arthik 13/01/16
3
Eu tive o mesmo problema com o módulo, então calculei o comprimento da coluna int columnLength = (int)Math.Ceiling((decimal)(list.Count()) / parts);e fiz a divisão com .GroupBy(x => x.index / columnLength). Uma desvantagem é Count () enumera a lista.
goodeye
24

Ok, eu vou jogar meu chapéu no ringue. As vantagens do meu algoritmo:

  1. Não há operadores caros de multiplicação, divisão ou módulo
  2. Todas as operações são O (1) (veja nota abaixo)
  3. Funciona para a fonte IEnumerable <> (nenhuma propriedade Count é necessária)
  4. Simples

O código:

public static IEnumerable<IEnumerable<T>>
  Section<T>(this IEnumerable<T> source, int length)
{
  if (length <= 0)
    throw new ArgumentOutOfRangeException("length");

  var section = new List<T>(length);

  foreach (var item in source)
  {
    section.Add(item);

    if (section.Count == length)
    {
      yield return section.AsReadOnly();
      section = new List<T>(length);
    }
  }

  if (section.Count > 0)
    yield return section.AsReadOnly();
}

Conforme apontado nos comentários abaixo, essa abordagem não aborda a questão original, que solicitou um número fixo de seções de comprimento aproximadamente igual. Dito isso, você ainda pode usar minha abordagem para resolver a questão original chamando-a desta maneira:

myEnum.Section(myEnum.Count() / number_of_sections + 1)

Quando usada dessa maneira, a abordagem não é mais O (1), pois a operação Count () é O (N).

Mike
fonte
Brilhante - a melhor solução aqui! Algumas otimizações: * Limpe a lista vinculada em vez de criar uma nova para cada seção. Uma referência à lista vinculada nunca é retornada ao chamador, por isso é totalmente seguro. * Não crie a lista ligada até chegar ao primeiro item - dessa forma não há nenhuma alocação se a fonte está vazia
shadowchaser
3
@ShadowChaser De acordo com a limpeza do MSDN, a LinkedList é a complexidade O (N), por isso arruinaria meu objetivo de O (1). Obviamente, você poderia argumentar que o foreach é O (N) para começar ... :) #
Mike Mike
4
sua resposta está certa, mas a pergunta está errada. Sua resposta fornece um número desconhecido de pedaços com tamanho fixo para cada pedaço. Mas o OP quer uma funcionalidade Split, na qual fornece um número fixo de pedaços com qualquer tamanho por pedaço (provavelmente de tamanhos iguais ou próximos a iguais). Talvez seja mais adequado aqui stackoverflow.com/questions/3773403/…
nawfal 6/12/12
1
@ Mike você fez um benchmarking? Espero que você saiba que O (1) não significa mais rápido, apenas significa que o tempo necessário para o particionamento não aumenta. Só estou imaginando qual é a sua lógica de cegar o O (1) quando ele pode ser mais lento do que os outros O (n) para todos os cenários da vida real. Eu até testei para uma lista louca de força de 10 ^ 8 e a minha parecia ser mais rápida ainda. Eu espero que você saiba que não há até mesmo tipos padrão de cobrança que pode conter 10 ^ 12 itens ..
Nawfal
1
@nawfal - Obrigado por sua análise detalhada, isso ajuda a me manter alerta. As listas vinculadas em geral são conhecidas por inserções finais eficientes, razão pela qual eu a selecionei aqui. No entanto, eu apenas a comparei e, de fato, a Lista <> é muito mais rápida. Eu suspeito que isso seja algum tipo de detalhe da implementação do .NET, talvez merecendo uma pergunta separada do StackOverflow. Modifiquei minha resposta para usar a Lista <> conforme sua sugestão. A pré-alocação da capacidade da lista garante que a inserção final ainda seja O (1) e atenda ao meu objetivo de projeto original. Também mudei para o .AsReadOnly () interno no .NET 4.5.
Mike
16

É o mesmo que a resposta aceita, mas uma representação muito mais simples:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, 
                                                   int numOfParts)
{
    int i = 0;
    return items.GroupBy(x => i++ % numOfParts);
}

O método acima divide um IEnumerable<T>em N número de pedaços de tamanhos iguais ou quase iguais.

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    int i = 0;
    return items.GroupBy(x => i++ / partitionSize).ToArray();
}

O método acima divide um IEnumerable<T> em pedaços do tamanho fixo desejado com o número total de pedaços sem importância - e não é disso que se trata.

O problema com o Split método, além de ser mais lento, é que ele embaralha a saída no sentido de que o agrupamento será feito com base no i-ésimo múltiplo de N para cada posição ou, em outras palavras, você não recebe os pedaços na ordem original.

Quase todas as respostas aqui não preservam a ordem, ou são sobre particionamento e não são divididas, ou estão claramente erradas. Tente isso mais rápido, preserva a ordem, mas um pouco mais detalhado:

public static IEnumerable<IEnumerable<T>> Split<T>(this ICollection<T> items, 
                                                   int numberOfChunks)
{
    if (numberOfChunks <= 0 || numberOfChunks > items.Count)
        throw new ArgumentOutOfRangeException("numberOfChunks");

    int sizePerPacket = items.Count / numberOfChunks;
    int extra = items.Count % numberOfChunks;

    for (int i = 0; i < numberOfChunks - extra; i++)
        yield return items.Skip(i * sizePerPacket).Take(sizePerPacket);

    int alreadyReturnedCount = (numberOfChunks - extra) * sizePerPacket;
    int toReturnCount = extra == 0 ? 0 : (items.Count - numberOfChunks) / extra + 1;
    for (int i = 0; i < extra; i++)
        yield return items.Skip(alreadyReturnedCount + i * toReturnCount).Take(toReturnCount);
}

O método equivalente para uma Partitionoperação aqui

nawfal
fonte
6

Eu tenho usado a função Partition que publiquei anteriormente com bastante frequência. A única coisa ruim foi que não estava sendo transmitido completamente. Isso não é um problema se você trabalhar com poucos elementos em sua sequência. Eu precisava de uma nova solução quando comecei a trabalhar com mais de 100.000 elementos na minha sequência.

A solução a seguir é muito mais complexa (e mais código!), Mas é muito eficiente.

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

namespace LuvDaSun.Linq
{
    public static class EnumerableExtensions
    {
        public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int partitionSize)
        {
            /*
            return enumerable
                .Select((item, index) => new { Item = item, Index = index, })
                .GroupBy(item => item.Index / partitionSize)
                .Select(group => group.Select(item => item.Item)                )
                ;
            */

            return new PartitioningEnumerable<T>(enumerable, partitionSize);
        }

    }


    class PartitioningEnumerable<T> : IEnumerable<IEnumerable<T>>
    {
        IEnumerable<T> _enumerable;
        int _partitionSize;
        public PartitioningEnumerable(IEnumerable<T> enumerable, int partitionSize)
        {
            _enumerable = enumerable;
            _partitionSize = partitionSize;
        }

        public IEnumerator<IEnumerable<T>> GetEnumerator()
        {
            return new PartitioningEnumerator<T>(_enumerable.GetEnumerator(), _partitionSize);
        }

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


    class PartitioningEnumerator<T> : IEnumerator<IEnumerable<T>>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitioningEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
            _enumerator.Dispose();
        }

        IEnumerable<T> _current;
        public IEnumerable<T> Current
        {
            get { return _current; }
        }
        object IEnumerator.Current
        {
            get { return _current; }
        }

        public void Reset()
        {
            _current = null;
            _enumerator.Reset();
        }

        public bool MoveNext()
        {
            bool result;

            if (_enumerator.MoveNext())
            {
                _current = new PartitionEnumerable<T>(_enumerator, _partitionSize);
                result = true;
            }
            else
            {
                _current = null;
                result = false;
            }

            return result;
        }

    }



    class PartitionEnumerable<T> : IEnumerable<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitionEnumerable(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new PartitionEnumerator<T>(_enumerator, _partitionSize);
        }

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


    class PartitionEnumerator<T> : IEnumerator<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        int _count;
        public PartitionEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
        }

        public T Current
        {
            get { return _enumerator.Current; }
        }
        object IEnumerator.Current
        {
            get { return _enumerator.Current; }
        }
        public void Reset()
        {
            if (_count > 0) throw new InvalidOperationException();
        }

        public bool MoveNext()
        {
            bool result;

            if (_count < _partitionSize)
            {
                if (_count > 0)
                {
                    result = _enumerator.MoveNext();
                }
                else
                {
                    result = true;
                }
                _count++;
            }
            else
            {
                result = false;
            }

            return result;
        }

    }
}

Aproveitar!

Elmer
fonte
Esta versão quebra o contrato do IEnumerator. Não é válido lançar InvalidOperationException quando Reset é chamado - acredito que muitos dos métodos de extensão LINQ dependem desse comportamento.
ShadowChaser
1
@ShadowChaser Acho Reset () deve lançar um NotSupportedException e tudo ficaria bem. Na documentação do MSDN: "O método Reset é fornecido para interoperabilidade COM. Ele não precisa necessariamente ser implementado; em vez disso, o implementador pode simplesmente lançar uma NotSupportedException."
toong
@toong Wow, você está certo. Não tenho certeza de como eu perdi isso depois de todo esse tempo.
ShadowChaser
É buggy! Não me lembro exatamente, mas (tanto quanto me lembro), ele executa etapas indesejadas e pode levar a efeitos colaterais feios (com datareader para ex.). A melhor solução está aqui (Jeppe Stig Nielsen): stackoverflow.com/questions/13709626/…
SalientBrain
4

Tópico interessante. Para obter uma versão de streaming do Split / Partition, é possível usar enumeradores e gerar sequências do enumerador usando métodos de extensão. Converter código imperativo em código funcional usando yield é uma técnica muito poderosa.

Primeiro, uma extensão de enumerador que transforma uma contagem de elementos em uma sequência lenta:

public static IEnumerable<T> TakeFromCurrent<T>(this IEnumerator<T> enumerator, int count)
{
    while (count > 0)
    {
        yield return enumerator.Current;
        if (--count > 0 && !enumerator.MoveNext()) yield break;
    }
}

E então uma extensão enumerável que particiona uma sequência:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> seq, int partitionSize)
{
    var enumerator = seq.GetEnumerator();

    while (enumerator.MoveNext())
    {
        yield return enumerator.TakeFromCurrent(partitionSize);
    }
}

O resultado final é uma implementação altamente eficiente, de streaming e lenta, que depende de um código muito simples.

Aproveitar!

Martin Fredriksson
fonte
Inicialmente, programei a mesma coisa, mas o padrão é interrompido quando Redefinir é chamado em uma das instâncias IEnumerable <T> aninhadas.
ShadowChaser
1
Isso ainda funciona se você apenas enumerar a partição e não o enumerável interno? como o enumerador interno é adiado, nenhum código para o interno (extraído da corrente) será executado até que seja enumerado; portanto, o movenext () será chamado apenas pela função de partição externa, certo? Se minhas suposições são verdadeiras, então isso pode potencialmente produzir n partições com n elementos no enumeráveis original e os enumerables interior irá produzir resultados inesperados
Brad
@ Brad ele vai "falhar" como você espera, semelhante a algumas das questões neste tópico stackoverflow.com/questions/419019/... (especificamente stackoverflow.com/a/20953521/1037948 )
drzaus
4

Eu uso isso:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> instance, int partitionSize)
{
    return instance
        .Select((value, index) => new { Index = index, Value = value })
        .GroupBy(i => i.Index / partitionSize)
        .Select(i => i.Select(i2 => i2.Value));
}
Elmer
fonte
Por favor, explique o porquê. Eu tenho usado esta função sem problemas!
Elmer
ler a pergunta novamente e veja se você obter n (quase) peças de comprimento iguais com a sua função
Muhammad Hasan Khan
@ Elmer, sua resposta está certa, mas a pergunta está errada. Sua resposta fornece um número desconhecido de partes com tamanho fixo para cada parte (exatamente como Partição, o nome que você deu para ela). Mas o OP quer uma funcionalidade Split, na qual fornece um número fixo de pedaços com qualquer tamanho por pedaço (provavelmente de tamanhos iguais ou próximos a iguais). Talvez seja mais adequado aqui stackoverflow.com/questions/3773403/…
nawfal 6/12/12
Eu acho que você pode simplesmente alterar i.Index / partitionSize para i.Index% partitionSize e obter o resultado solicitado. Também prefiro isso à resposta aceita, pois é mais compacta e legível.
Jake Drew
2

Isso é eficiente em memória e adia a execução o máximo possível (por lote) e opera em tempo linear O (n)

    public static IEnumerable<IEnumerable<T>> InBatchesOf<T>(this IEnumerable<T> items, int batchSize)
    {
        List<T> batch = new List<T>(batchSize);
        foreach (var item in items)
        {
            batch.Add(item);

            if (batch.Count >= batchSize)
            {
                yield return batch;
                batch = new List<T>();
            }
        }

        if (batch.Count != 0)
        {
            //can't be batch size or would've yielded above
            batch.TrimExcess();
            yield return batch;
        }
    }
Brad
fonte
2

Há muitas ótimas respostas para essa pergunta (e seus primos). Eu mesmo precisava disso e havia criado uma solução projetada para ser eficiente e tolerante a erros em um cenário em que a coleção de fontes pode ser tratada como uma lista. Ele não usa nenhuma iteração lenta, por isso pode não ser adequado para coleções de tamanho desconhecido que possam aplicar pressão de memória.

static public IList<T[]> GetChunks<T>(this IEnumerable<T> source, int batchsize)
{
    IList<T[]> result = null;
    if (source != null && batchsize > 0)
    {
        var list = source as List<T> ?? source.ToList();
        if (list.Count > 0)
        {
            result = new List<T[]>();
            for (var index = 0; index < list.Count; index += batchsize)
            {
                var rangesize = Math.Min(batchsize, list.Count - index);
                result.Add(list.GetRange(index, rangesize).ToArray());
            }
        }
    }
    return result ?? Enumerable.Empty<T[]>().ToList();
}

static public void TestGetChunks()
{
    var ids = Enumerable.Range(1, 163).Select(i => i.ToString());
    foreach (var chunk in ids.GetChunks(20))
    {
        Console.WriteLine("[{0}]", String.Join(",", chunk));
    }
}

Vi algumas respostas em toda essa família de perguntas que usam GetRange e Math.Min. Mas acredito que, no geral, essa é uma solução mais completa em termos de verificação de erros e eficiência.

rhaben
fonte
1
   protected List<List<int>> MySplit(int MaxNumber, int Divider)
        {
            List<List<int>> lst = new List<List<int>>();
            int ListCount = 0;
            int d = MaxNumber / Divider;
            lst.Add(new List<int>());
            for (int i = 1; i <= MaxNumber; i++)
            {
                lst[ListCount].Add(i);
                if (i != 0 && i % d == 0)
                {
                    ListCount++;
                    d += MaxNumber / Divider;
                    lst.Add(new List<int>());
                }
            }
            return lst;
        }
Amit Sengar
fonte
1

Ótimas respostas, para o meu cenário, testei a resposta aceita e parece que ela não mantém a ordem. também há uma ótima resposta de Nawfal que mantém a ordem. Mas, no meu cenário, eu queria dividir o restante de uma maneira normalizada, todas as respostas que vi espalharam o restante ou no início ou no final.

Minha resposta também leva o restante a se espalhar de maneira mais normalizada.

 static class Program
{          
    static void Main(string[] args)
    {
        var input = new List<String>();
        for (int k = 0; k < 18; ++k)
        {
            input.Add(k.ToString());
        }
        var result = splitListIntoSmallerLists(input, 15);            
        int i = 0;
        foreach(var resul in result){
            Console.WriteLine("------Segment:" + i.ToString() + "--------");
            foreach(var res in resul){
                Console.WriteLine(res);
            }
            i++;
        }
        Console.ReadLine();
    }

    private static List<List<T>> splitListIntoSmallerLists<T>(List<T> i_bigList,int i_numberOfSmallerLists)
    {
        if (i_numberOfSmallerLists <= 0)
            throw new ArgumentOutOfRangeException("Illegal value of numberOfSmallLists");

        int normalizedSpreadRemainderCounter = 0;
        int normalizedSpreadNumber = 0;
        //e.g 7 /5 > 0 ==> output size is 5 , 2 /5 < 0 ==> output is 2          
        int minimumNumberOfPartsInEachSmallerList = i_bigList.Count / i_numberOfSmallerLists;                        
        int remainder = i_bigList.Count % i_numberOfSmallerLists;
        int outputSize = minimumNumberOfPartsInEachSmallerList > 0 ? i_numberOfSmallerLists : remainder;
        //In case remainder > 0 we want to spread the remainder equally between the others         
        if (remainder > 0)
        {
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                normalizedSpreadNumber = (int)Math.Floor((double)i_numberOfSmallerLists / remainder);    
            }
            else
            {
                normalizedSpreadNumber = 1;
            }   
        }
        List<List<T>> retVal = new List<List<T>>(outputSize);
        int inputIndex = 0;            
        for (int i = 0; i < outputSize; ++i)
        {
            retVal.Add(new List<T>());
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                retVal[i].AddRange(i_bigList.GetRange(inputIndex, minimumNumberOfPartsInEachSmallerList));
                inputIndex += minimumNumberOfPartsInEachSmallerList;
            }
            //If we have remainder take one from it, if our counter is equal to normalizedSpreadNumber.
            if (remainder > 0)
            {
                if (normalizedSpreadRemainderCounter == normalizedSpreadNumber-1)
                {
                    retVal[i].Add(i_bigList[inputIndex]);
                    remainder--;
                    inputIndex++;
                    normalizedSpreadRemainderCounter=0;
                }
                else
                {
                    normalizedSpreadRemainderCounter++;
                }
            }
        }
        return retVal;
    }      

}
Robocide
fonte
0

Se a ordem nessas partes não for muito importante, você pode tentar o seguinte:

int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int n = 3;

var result =
   array.Select((value, index) => new { Value = value, Index = index }).GroupBy(i => i.Index % n, i => i.Value);

// or
var result2 =
   from i in array.Select((value, index) => new { Value = value, Index = index })
   group i.Value by i.Index % n into g
   select g;

No entanto, estes não podem ser convertidos em IEnumerable <IEnumerable <int>> por algum motivo ...

okutane
fonte
Pode ser feito. Em vez de fundição direta apenas fazer a função genérica e, em seguida, chamá-lo para a matriz int
Nawfal
0

Este é o meu código, agradável e curto.

 <Extension()> Public Function Chunk(Of T)(ByVal this As IList(Of T), ByVal size As Integer) As List(Of List(Of T))
     Dim result As New List(Of List(Of T))
     For i = 0 To CInt(Math.Ceiling(this.Count / size)) - 1
         result.Add(New List(Of T)(this.GetRange(i * size, Math.Min(size, this.Count - (i * size)))))
     Next
     Return result
 End Function
Jonathan Allen
fonte
0

Este é o meu caminho, listando itens e quebrando linhas por colunas

  int repat_count=4;

  arrItems.ForEach((x, i) => {
    if (i % repat_count == 0) 
        row = tbo.NewElement(el_tr, cls_min_height);
    var td = row.NewElement(el_td);
    td.innerHTML = x.Name;
  });
IlPADlI
fonte
0

Eu estava procurando por uma divisão como a de string, então a lista inteira é dividida de acordo com alguma regra, não apenas a primeira parte, esta é a minha solução

List<int> sequence = new List<int>();
for (int i = 0; i < 2000; i++)
{
     sequence.Add(i);
}
int splitIndex = 900;
List<List<int>> splitted = new List<List<int>>();
while (sequence.Count != 0)
{
    splitted.Add(sequence.Take(splitIndex).ToList() );
    sequence.RemoveRange(0, Math.Min(splitIndex, sequence.Count));
}
Adel
fonte
Da próxima vez, tente: var nrs = Enumerable.Range (1,2000) .ToList ();
MBoros
0

Aqui está um pequeno ajuste para o número de itens em vez do número de peças:

public static class MiscExctensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int nbItems)
    {
        return (
            list
            .Select((o, n) => new { o, n })
            .GroupBy(g => (int)(g.n / nbItems))
            .Select(g => g.Select(x => x.o))
        );
    }
}
JB
fonte
-1
int[] items = new int[] { 0,1,2,3,4,5,6,7,8,9, 10 };

int itemIndex = 0;
int groupSize = 2;
int nextGroup = groupSize;

var seqItems = from aItem in items
               group aItem by 
                            (itemIndex++ < nextGroup) 
                            ? 
                            nextGroup / groupSize
                            :
                            (nextGroup += groupSize) / groupSize
                            into itemGroup
               select itemGroup.AsEnumerable();

fonte
-1

Acabei de encontrar esse segmento, e a maioria das soluções aqui envolve adicionar itens às coleções, materializando efetivamente cada página antes de devolvê-la. Isso é ruim por dois motivos: primeiro, se suas páginas são grandes, há uma sobrecarga de memória para preencher a página; em segundo lugar, existem iteradores que invalidam os registros anteriores quando você avança para o próximo (por exemplo, se você agrupar um DataReader em um método de enumerador) .

Esta solução usa dois métodos de enumerador aninhado para evitar a necessidade de armazenar itens em cache em coleções temporárias. Como os iteradores externos e internos estão percorrendo o mesmo enumerável, eles necessariamente compartilham o mesmo enumerador, portanto, é importante não avançar o externo até que você termine o processamento da página atual. Dito isto, se você decidir não percorrer toda a página atual, quando você passar para a próxima página, esta solução irá percorrer o limite da página automaticamente.

using System.Collections.Generic;

public static class EnumerableExtensions
{
    /// <summary>
    /// Partitions an enumerable into individual pages of a specified size, still scanning the source enumerable just once
    /// </summary>
    /// <typeparam name="T">The element type</typeparam>
    /// <param name="enumerable">The source enumerable</param>
    /// <param name="pageSize">The number of elements to return in each page</param>
    /// <returns></returns>
    public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int pageSize)
    {
        var enumerator = enumerable.GetEnumerator();

        while (enumerator.MoveNext())
        {
            var indexWithinPage = new IntByRef { Value = 0 };

            yield return SubPartition(enumerator, pageSize, indexWithinPage);

            // Continue iterating through any remaining items in the page, to align with the start of the next page
            for (; indexWithinPage.Value < pageSize; indexWithinPage.Value++)
            {
                if (!enumerator.MoveNext())
                {
                    yield break;
                }
            }
        }
    }

    private static IEnumerable<T> SubPartition<T>(IEnumerator<T> enumerator, int pageSize, IntByRef index)
    {
        for (; index.Value < pageSize; index.Value++)
        {
            yield return enumerator.Current;

            if (!enumerator.MoveNext())
            {
                yield break;
            }
        }
    }

    private class IntByRef
    {
        public int Value { get; set; }
    }
}
Jon G
fonte
Isso não está funcionando! O melhor possível é aqui stackoverflow.com/questions/13709626/… ! Ver comentários.
precisa saber é o seguinte