Pesquisa de site

javascript - Dividir array em pedaços


Vamos supor que eu tenha um array em Javascript da seguinte forma:

["Element 1","Element 2","Element 3",...]; // with close to a hundred elements.

Qual abordagem seria apropriada para dividir (split) o array em muitos arrays menores com, digamos, 10 elementos no máximo?

Resposta #1

O método array.slice() pode extrair uma fatia do início, meio ou fim de um array para quaisquer propósitos que você necessite, sem alterar o array original.

const chunkSize = 10;
for (let i = 0; i < array.length; i += chunkSize) {
    const chunk = array.slice(i, i + chunkSize);
    // do whatever
}

O último chunk pode ser menor que chunkSize. Por exemplo, ao fornecer um array de 12 elementos, o primeiro chunk terá 10 elementos, e o segundo chunk terá apenas 2.

Observe que um chunkSize de 0 causará um loop infinito.

Resposta #2

Aqui está uma versão ES6 usando reduce

const perChunk = 2 // items per chunk    

const inputArray = ['a','b','c','d','e']

const result = inputArray.reduce((resultArray, item, index) => { 
  const chunkIndex = Math.floor(index/perChunk)

  if(!resultArray[chunkIndex]) {
    resultArray[chunkIndex] = [] // start a new chunk
  }

  resultArray[chunkIndex].push(item)

  return resultArray
}, [])

console.log(result); // result: [['a','b'], ['c','d'], ['e']]

E você está pronto para encadear mais transformações map/reduce. Seu array de entrada permanece intacto


Se você preferir uma versão mais curta, mas menos legível, pode adicionar um pouco de concat à mistura para obter o mesmo resultado final:

inputArray.reduce((all,one,i) => {
   const ch = Math.floor(i/perChunk); 
   all[ch] = [].concat((all[ch]||[]),one); 
   return all
}, [])

Você pode usar o operador de resto para colocar itens consecutivos em diferentes blocos:

const ch = (i % perChunk); 

Resposta #3

Usando geradores

function* chunks(arr, n) {
  for (let i = 0; i < arr.length; i += n) {
    yield arr.slice(i, i + n);
  }
}

let someArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log([...chunks(someArray, 2)]) // [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]

Pode ser digitado com Typescript assim:

function* chunks<T>(arr: T[], n: number): Generator<T[], void> {
  for (let i = 0; i < arr.length; i += n) {
    yield arr.slice(i, i + n);
  }
}

Resposta #4

Modificado a partir de uma resposta de dbaseman: https://stackoverflow.com/a/10456344/711085

Object.defineProperty(Array.prototype, 'chunk_inefficient', {
  value: function(chunkSize) {
    var array = this;
    return [].concat.apply([],
      array.map(function(elem, i) {
        return i % chunkSize ? [] : [array.slice(i, i + chunkSize)];
      })
    );
  }
});

console.log(
  [1, 2, 3, 4, 5, 6, 7].chunk_inefficient(3)
)
// [[1, 2, 3], [4, 5, 6], [7]]

pequeno adendo:

Devo apontar que o acima é uma solução alternativa não muito elegante (na minha opinião) para usar Array.map. Basicamente, ele faz o seguinte, onde ~ é concatenação:

[[1,2,3]]~[]~[]~[] ~ [[4,5,6]]~[]~[]~[] ~ [[7]]

Ele possui o mesmo tempo de execução assintótico que o método abaixo, mas talvez um fator constante pior devido à criação de listas vazias. Poderia reescrever isso da seguinte forma (principalmente o mesmo que o método do Blazemonger, por isso não submeti esta resposta originalmente):

Método mais eficiente:

// refresh page if experimenting and you already defined Array.prototype.chunk

Object.defineProperty(Array.prototype, 'chunk', {
  value: function(chunkSize) {
    var R = [];
    for (var i = 0; i < this.length; i += chunkSize)
      R.push(this.slice(i, i + chunkSize));
    return R;
  }
});

console.log(
  [1, 2, 3, 4, 5, 6, 7].chunk(3)
)

Minha maneira preferida atualmente é a acima, ou uma das seguintes:

Array.range = function(n) {
  // Array.range(5) --> [0,1,2,3,4]
  return Array.apply(null,Array(n)).map((x,i) => i)
};

Object.defineProperty(Array.prototype, 'chunk', {
  value: function(n) {

    // ACTUAL CODE FOR CHUNKING ARRAY:
    return Array.range(Math.ceil(this.length/n)).map((x,i) => this.slice(i*n,i*n+n));

  }
});

Demonstração :

> JSON.stringify( Array.range(10).chunk(3) );
[[1,2,3],[4,5,6],[7,8,9],[10]]

Ou se você não quiser uma função Array.range, na verdade é apenas uma linha de código (excluindo os detalhes):

var ceil = Math.ceil;

Object.defineProperty(Array.prototype, 'chunk', {value: function(n) {
    return Array(ceil(this.length/n)).fill().map((_,i) => this.slice(i*n,i*n+n));
}});

ou

Object.defineProperty(Array.prototype, 'chunk', {value: function(n) {
    return Array.from(Array(ceil(this.length/n)), (_,i)=>this.slice(i*n,i*n+n));
}});

"Não modifique Object.prototype" --alguns comentários

Claro que está tudo bem, você pode colocar o acima em sua própria function chunk(arr, chunkSize); a forma como você empacota sua função chunk é irrelevante para responder à pergunta do OP (como codar tal função).

No entanto, vou reforçar levemente o ato 'errado' de estender o Array.prototype, e aqui está a justificativa: Isso não é algo horrível. O link compartilhado nos comentários faz um bom trabalho explicando os possíveis perigos, mas as pessoas podem ler o link e ver "estender prototype é ruim!" e não entender a nuance. Muitas linguagens têm essa funcionalidade, e a semântica é basicamente gerenciada pelo programador (no momento da importação/etc.):

  1. Talvez não estenda o protótipo se você estiver escrevendo uma biblioteca! Bibliotecas devem se comportar bem, não poluindo o namespace global. Ou, se fizer isso, deixe bem claro para os usuários que isso pode interagir de forma ruim com outras bibliotecas que fazem o mesmo, ou com seu próprio código. Você pode definir myLib.chunk=function() {...} ou exportá-lo como módulo ou algo assim, ou usar Object.defineProperty('myLib_chunk', ...
  2. Você pode usar um prefixo (defineProperty('myLib_chunk', ...) se você não for fazer isso com frequência. Mas, se você for dividir seus arrays em partes por todo o seu código, vá em frente e estenda o protótipo! Especialmente se seu código será usado apenas em um pequeno projeto downstream (não biblioteca). Isso é apenas o velho debate de "por que from LIBRARY import * [por exemplo, em Python] é ruim?" Bem, claramente é ruim se você exagerar, e às vezes é bom (quem quer escrever Math.floor em todo lugar no seu código JavaScript?! Funções matemáticas geralmente não dependem da implementação!). Em geral, você deve utilizar namespace para tudo myLib.yourExports, mas não necessariamente ao ponto de prejudicar a legibilidade.
  3. Obviamente, não torne isso enumerável (o que quebraria o for...in)... usar Object.defineProperty o tornará não enumerável por padrão.
  4. Se o ECMAScript decidir lançar um .chunk, eles deveriam tê-lo reservado ou desenvolvido uma funcionalidade melhor mais cedo. Novos padrões do ECMAScript sempre têm o risco de quebrar código existente. Se você quer tornar seu código à prova de futuro porque não vai mantê-lo ou não quer mantê-lo (uma consideração extremamente importante!), não programe usando este método, e também não adicione variáveis ao espaço de nomes global (sem alguma preparação extensiva para o futuro, talvez como Symbol, ou usando prefixos).

Então... obrigado aos comentaristas por apontarem essa consideração. Esta discussão está fora do escopo desta resposta porque, na verdade, poderia ser feita com qualquer resposta no StackOverflow, mas essa é a minha opinião.

Resposta #5

Tente evitar mexer com protótipos nativos, incluindo Array.prototype, se você não souber quem irá consumir seu código (terceiros, colegas de trabalho, você mesmo em uma data futura, etc.).

Existem maneiras de estender protótipos com segurança (mas não em todos os navegadores) e existem maneiras de consumir com segurança objetos criados a partir de protótipos estendidos, mas uma regra prática melhor é seguir o Princípio da Menor Surpresa e evitar essas práticas completamente.

Se você tiver algum tempo, assista à palestra de Andrew Dupont na JSConf 2011, "Tudo é Permitido: Estendendo Funcionalidades Nativas", para uma boa discussão sobre este tema.

Mas voltando à pergunta, embora as soluções acima funcionem, elas são excessivamente complexas e exigem uma sobrecarga computacional desnecessária. Aqui está a minha solução:

function chunk (arr, len) {

  var chunks = [],
      i = 0,
      n = arr.length;

  while (i < n) {
    chunks.push(arr.slice(i, i += len));
  }

  return chunks;
}

// Optionally, you can do the following to avoid cluttering the global namespace:
Array.chunk = chunk;

Resposta #6

Versão do Splice usando ES6

let  [list,chunkSize] = [[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], 6];
list = [...Array(Math.ceil(list.length / chunkSize))].map(_ => list.splice(0,chunkSize))
console.log(list);

Resposta #7

Eu testei as diferentes respostas no jsperf.com. O resultado está disponível lá: https://web.archive.org/web/20150909134228/https://jsperf.com/chunk-mtds

E a função mais rápida (e que funciona a partir do IE8) é esta:

function chunk(arr, chunkSize) {
  if (chunkSize <= 0) throw "Invalid chunk size";
  var R = [];
  for (var i=0,len=arr.length; i<len; i+=chunkSize)
    R.push(arr.slice(i,i+chunkSize));
  return R;
}

Resposta #8

Eu preferiria usar o método splice:

var chunks = function(array, size) {
  var results = [];
  while (array.length) {
    results.push(array.splice(0, size));
  }
  return results;
};

Resposta #9

Hoje em dia, você pode usar a função chunk do lodash para dividir o array em arrays menores https://lodash.com/docs#chunk Não há mais necessidade de mexer com os loops!

Resposta #10

Pergunta antiga: Nova resposta! Na verdade, eu estava trabalhando com uma resposta dessa pergunta e um amigo a melhorou! Então aqui está:

Array.prototype.chunk = function ( n ) {
    if ( !this.length ) {
        return [];
    }
    return [ this.slice( 0, n ) ].concat( this.slice(n).chunk(n) );
};

[1,2,3,4,5,6,7,8,9,0].chunk(3);
> [[1,2,3],[4,5,6],[7,8,9],[0]]

Resposta #11

Mais uma solução usando Array.prototype.reduce():

const chunk = (array, size) =>
  array.reduce((acc, _, i) => {
    if (i % size === 0) acc.push(array.slice(i, i + size))
    return acc
  }, [])

// Usage:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const chunked = chunk(numbers, 3)
console.log(chunked)

Esta solução é muito semelhante à solução de Steve Holgado. No entanto, porque esta solução não utiliza espalhamento de array e não cria novos arrays na função redutora, ela é mais rápida (veja o teste jsPerf) e subjetivamente mais legível (sintaxe mais simples) do que a outra solução.

A cada n-ésima iteração (onde n=size; começando na primeira iteração), o array acumulador (acc) é incrementado com um pedaço do array (array.slice(i, i + size)) e então retornado. Nas outras iterações, o array acumulador é retornado como está.

Se size for zero, o método retorna um array vazio. Se size for negativo, o método retorna resultados incorretos. Portanto, se necessário no seu caso, você pode querer fazer algo em relação aos valores de size negativos ou não positivos.


Se a velocidade for importante no seu caso, um simples loop for seria mais rápido do que usar reduce() (veja o teste jsPerf), e alguns podem achar esse estilo mais legível também:

function chunk(array, size) {
  // This prevents infinite loops
  if (size < 1) throw new Error('Size must be positive')

  const result = []
  for (let i = 0; i < array.length; i += size) {
    result.push(array.slice(i, i + size))
  }
  return result
}

Resposta #12

FRASE DE EFEITO

const chunk = (a,n)=>[...Array(Math.ceil(a.length/n))].map((_,i)=>a.slice(n*i,n+n*i));

Para TypeScript

const chunk = <T>(arr: T[], size: number): T[][] =>
  [...Array(Math.ceil(arr.length / size))].map((_, i) =>
    arr.slice(size * i, size + size * i)
  );

DEMO

const chunk = (a,n)=>[...Array(Math.ceil(a.length/n))].map((_,i)=>a.slice(n*i,n+n*i));
document.write(JSON.stringify(chunk([1, 2, 3, 4], 2)));

Dividir Por Número de Grupos

const part=(a,n)=>[...Array(n)].map((_,i)=>a.slice(i*Math.ceil(a.length/n),(i+1)*Math.ceil(a.length/n)));

Para TypeScript

const partitionArray = <T>(array: T[], parts: number): T[][] => {
  const itemsPerPart = Math.ceil(array.length / parts);
  return [...Array(parts)].map((_, index) =>
    array.slice(index * itemsPerPart, (index + 1) * itemsPerPart)
  );
};

DEMO

const part = (a, n) => {
    const b = Math.ceil(a.length / n);
    return [...Array(n)].map((_, i) => a.slice(i * b, (i + 1) * b));
};

document.write(JSON.stringify(part([1, 2, 3, 4, 5, 6], 2))+'<br/>');
document.write(JSON.stringify(part([1, 2, 3, 4, 5, 6, 7], 2)));

Resposta #13

Houveram muitas respostas, mas esta é a que eu uso:

const chunk = (arr, size) =>
  arr
    .reduce((acc, _, i) =>
      (i % size)
        ? acc
        : [...acc, arr.slice(i, i + size)]
    , [])

// USAGE
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
chunk(numbers, 3)

// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

Primeiro, verifique se há um resto ao dividir o índice pelo tamanho do bloco.

Se houver um resto, então apenas retorne o array acumulador.

Se não houver resto, então o índice é divisível pelo tamanho do bloco, então pegue uma fatia do array original (começando no índice atual) e adicione-a ao array acumulador.

Então, o array acumulador retornado para cada iteração do reduce se parece com isto:

// 0: [[1, 2, 3]]
// 1: [[1, 2, 3]]
// 2: [[1, 2, 3]]
// 3: [[1, 2, 3], [4, 5, 6]]
// 4: [[1, 2, 3], [4, 5, 6]]
// 5: [[1, 2, 3], [4, 5, 6]]
// 6: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
// 7: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
// 8: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
// 9: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

Resposta #14

Eu acho que esta é uma boa solução recursiva com a sintaxe do ES6:

const chunk = function(array, size) {
  if (!array.length) {
    return [];
  }
  const head = array.slice(0, size);
  const tail = array.slice(size);

  return [head, ...chunk(tail, size)];
};

console.log(chunk([1,2,3], 2));

Resposta #15

Ok, vamos começar com um bem apertado:

function chunk(arr, n) {
    return arr.slice(0,(arr.length+n-1)/n|0).
           map(function(c,i) { return arr.slice(n*i,n*i+n); });
}

Que é usado assim:

chunk([1,2,3,4,5,6,7], 2);

Então temos esta função redutora compacta:

function chunker(p, c, i) {
    (p[i/this|0] = p[i/this|0] || []).push(c);
    return p;
}

Que é usado assim:

[1,2,3,4,5,6,7].reduce(chunker.bind(3),[]);

Como um gatinho morre quando vinculamos this a um número, podemos fazer currying manual assim:

// Fluent alternative API without prototype hacks.
function chunker(n) {
   return function(p, c, i) {
       (p[i/n|0] = p[i/n|0] || []).push(c);
       return p;
   };
}

Que é usado assim:

[1,2,3,4,5,6,7].reduce(chunker(3),[]);

Então a função ainda bastante compacta que faz tudo de uma vez:

function chunk(arr, n) {
    return arr.reduce(function(p, cur, i) {
        (p[i/n|0] = p[i/n|0] || []).push(cur);
        return p;
    },[]);
}

chunk([1,2,3,4,5,6,7], 3);

Resposta #16

Eu tinha como objetivo criar uma solução simples que não mutasse em ES6 puro. Peculiaridades do JavaScript tornam necessário preencher o array vazio antes de mapear :-(

function chunk(a, l) { 
    return new Array(Math.ceil(a.length / l)).fill(0)
        .map((_, n) => a.slice(n*l, n*l + l)); 
}

Esta versão com recursão parece mais simples e atraente:

function chunk(a, l) { 
    if (a.length == 0) return []; 
    else return [a.slice(0, l)].concat(chunk(a.slice(l), l)); 
}

As funções de array ridiculamente fracas do ES6 rendem bons quebra-cabeças :-)

Resposta #17

Criei um pacote npm para isso https://www.npmjs.com/package/array.chunk

var result = [];

for (var i = 0; i < arr.length; i += size) {
  result.push(arr.slice(i, size + i));
}
return result;

Ao usar um TypedArray

var result = [];

for (var i = 0; i < arr.length; i += size) {
  result.push(arr.subarray(i, size + i));
}
return result;

Resposta #18

Recomendo usar o lodash. Chunking é uma das muitas funções úteis lá. Instruções:

npm i --save lodash

Inclua em seu projeto:

import * as _ from 'lodash';

Uso :

const arrayOfElements = ["Element 1","Element 2","Element 3", "Element 4", "Element 5","Element 6","Element 7","Element 8","Element 9","Element 10","Element 11","Element 12"]
const chunkedElements = _.chunk(arrayOfElements, 10)

Você pode encontrar meu exemplo aqui: https://playcode.io/659171/

Resposta #19

A seguinte abordagem ES2015 funciona sem precisar definir uma função e diretamente em arrays anônimos (exemplo com tamanho de bloco 2):

[11,22,33,44,55].map((_, i, all) => all.slice(2*i, 2*i+2)).filter(x=>x.length)

Se você quiser definir uma função para isso, você poderia fazer da seguinte forma (melhorando o comentário de K. sobre a resposta de Blazemonger):

const array_chunks = (array, chunk_size) => array
    .map((_, i, all) => all.slice(i*chunk_size, (i+1)*chunk_size))
    .filter(x => x.length)

Resposta #20

Usando Array.prototype.splice() e removendo elementos até que o array tenha elementos.

Array.prototype.chunk = function(size) {
    let result = [];
    
    while(this.length) {
        result.push(this.splice(0, size));
    }
        
    return result;
}

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(arr.chunk(2));

Atualizar

Array.prototype.splice() preenche o array original e, após executar o chunk(), o array original (arr) se torna [].

Então, se você quiser manter o array original intacto, copie e mantenha os dados de arr em outro array e faça a mesma coisa.

Array.prototype.chunk = function(size) {
  let data = [...this];  
  let result = [];
    
    while(data.length) {
        result.push(data.splice(0, size));
    }

    return result;
}

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log('chunked:', arr.chunk(2));
console.log('original', arr);

P.S: Obrigado ao @mts-knn por mencionar o assunto.

Resposta #21

Você pode pegar esta função ES6 chunk, que é fácil de usar:

const chunk = (array, size) =>
  Array.from({length: Math.ceil(array.length / size)}, (value, index) => array.slice(index * size, index * size + size));

const itemsPerChunk = 3;
const inputArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];

const newArray = chunk(inputArray, itemsPerChunk);
console.log(newArray.length); // 3,

document.write(JSON.stringify(newArray)); //  [ [ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ], [ 'g' ] ]

Resposta #22

js

function splitToBulks(arr, bulkSize = 20) {
    const bulks = [];
    for (let i = 0; i < Math.ceil(arr.length / bulkSize); i++) {
        bulks.push(arr.slice(i * bulkSize, (i + 1) * bulkSize));
    }
    return bulks;
}

console.log(splitToBulks([1, 2, 3, 4, 5, 6, 7], 3));

typescript

function splitToBulks<T>(arr: T[], bulkSize: number = 20): T[][] {
    const bulks: T[][] = [];
    for (let i = 0; i < Math.ceil(arr.length / bulkSize); i++) {
        bulks.push(arr.slice(i * bulkSize, (i + 1) * bulkSize));
    }
    return bulks;
}

Artigos relacionados