Skip to content

Instantly share code, notes, and snippets.

@suissa
Last active June 5, 2018 14:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save suissa/596c74a336a825efbc73addb3df5ef9f to your computer and use it in GitHub Desktop.
Save suissa/596c74a336a825efbc73addb3df5ef9f to your computer and use it in GitHub Desktop.

chunk

Como eu NÃO ESTOU LENDO NADA DA DOCUMENTAÇÃO para fazer esse estudo pois eu simplesmente peço um exemplo do uso da função o qual foi me passado esse exemplo:

const _ = require('lodash')

const data = [1, 3, 8, 1, 9, 11, 25, 63, 75, 45, 85]
const chunkedData = _.chunk(data, 3)
// [ [ 1, 3, 8 ], [ 1, 9, 11 ], [ 25, 63, 75 ], [ 45, 85 ] ]

Bom fica claro de entendermos que o primeiro parâmetro é a lista de valores, enquanto que o segundo é o tamanho de cada sub-array. Pois pelo que percebi essa função chunk retorna um Array com Arrays do tamanho definido onde serão agrupados os elementos em ordem.

Pelo menos eu, quando vejo uma transformação de estrutural da entrada para a saída logo me remete ao reduce.

Sabendo disso podemos criar nossa função principal dessa forma:

const chunk = (list, length) => list.reduce(toChunk(length),[])

Perceba como separando o callback do reduce você consegue escrever a função diretamente sem precisar pensar nesse momento como será sua implementação.

Depois disso vamos para a função toChunk que deverá receber o tamanho definido para os grupos, agora que vem a pergunta derradeira:

Como agrupar os elementos da lista em um Array menor com o tamanho definido e depois colocar todos juntos em um só Array?

Vou mostrar aqui a solução mais simples que foi como eu pensei, mas já sabendo o que eu precisarei refatorar.

Pense comigo:

E se eu tiver um Array onde eu possa ir adicionando os elementos até o seu tamanho desejado?

Pois foi assim que pensei e para conseguirmos fazer isso ANTES DE TUDO você precisará saber o mínimo de Matemática e sobre o conceito de Divisibilidade.

TÁ LOCO JÁ SUISSA? O que que isso tem a ver com Matemática?

É exatamente isso que quero lhe explicar, na minha cabeça já veio automaticamente a ideia de eu testar o índice que está sendo iterado e caso ele for divisível pelo tamanho definido eu devo pegar o Array menor criado com esses elementos e adicionar esse Array no resultado final do reduce.

Porém se eu testar dessa forma:

(i % length === 0)

Estará errado pois o i inicia em 0, logo precisamos fazer:

((i + 1) % length === 0)

E como TODO MUNDO SABE o 0 tem valor de false, então se eu quiser eliminar a comparação === 0 podemos fazer assim:

!((i + 1) % length) 

Dessa maneira quando o resultado do % for 0 nós invertemos seu valor lógico de false para true.

Bom e o que faremos com essa informação? Iremos implementar a função que será executada pelo reduce:

const toChunk = (length) => (acc, cur, i) => {}

Coloquei apenas sua definição nesse momento pois preciso lhe explicar o porquê estou usando (length) => (acc, cur, i) =>, isso se chama Closure e eu PRECISO utilizar dessa forma pois preciso injetar o valor de length na função que será executada pelo reduce pois a mesma não possui essa informação e é exatamente por isso que nós chamamos ela dessa forma:

list.reduce(toChunk(length),[])

Agora que você já manja dos paranaues vamos ao que interessa!

Quando entrarmos na função nós devemos adicionar o elemento iterado no Array de agrupamento para depois adicioná-lo ao acumulador(acc) do reduce para ser retornado.

E logo após nós devemos testar em qual índice da lista nós estamos, caso esse índice seja divisível pelo length significa que devo adicionar o Array de agrupamento ao acc que será nossa resposta final.

A forma mais "comum" de se fazer isso é colocando um Array por fora e usando o push para adicionar os elementos:

const arr = []
const toChunk = (length, r = []) => (acc, cur, i, self) => {
  arr.push(cur)
  if (!((i + 1) % length)) {
    acc.push(arr)
    arr = []
  }
  return acc
}

Porém essa forma fere o conceito de Funções Puras e Efeitos Colaterais da Programação Funcional, para resolver isso basta que nosso arr não exista fora do reduce, para isso iremos usar uma malandragem relativamente nova: o Default Parameter.

E além disso iremos utilizar o [Spread Operator] para emular a funcionalidade do Array#concat, desse jeito: arr = [...arr, cur].

Então veja como ficará nossa função toChunk:

const data = [1, 3, 8, 1, 9, 11, 25, 63, 75, 45, 85]

const toChunk = (length, arr = []) => (acc, cur, i, self) => {
  arr = [...arr, cur]
  if (!((i + 1) % length)) {
    acc = [...acc, arr]
    arr = []
  }
  return acc
}

const chunk = (list, length) => list.reduce(toChunk(length),[])

console.log(
  chunk(data, 3)
)

// [ [ 1, 3, 8 ], [ 1, 9, 11 ], [ 25, 63, 75 ], [ 45, 85 ] ]

Refatorada

Para dar aquela alegria dei uma "leve" refatorada na solução deixando ela mais "funcional":

const denyThe = (exp) => !exp
const concat = (list, el) => [...list, el]
const cleanArray = (arr) => { arr = []; return arr }
const RestOfDivision = (x, y) => x % y
const groupElements = (acc, r) => [concat(acc, r),cleanArray(r)]
const testLength = (i, length, self) => 
  ( denyThe(RestOfDivision((i + 1), length)) ||
    i === self.length - 1
)
const toChunk = (length, r = []) => (acc, cur, i, self) => {
  r = concat(r, cur)
  
  if (testLength(i, length, self)) 
    [acc, r] = groupElements(acc, r)
  
  return acc
}

const chunk = (list, length) => list.reduce(toChunk(length), [])
const _ = require('lodash')
const data = [1, 3, 8, 1, 9, 11, 25, 63, 75, 45, 85]
const chunkedData = _.chunk(data, 3)
console.log(chunkedData)
const toChunk = (length, r = []) => (acc, cur, i, self) => {
r = [...r, cur]
if (!((i + 1) % length) || i === self.length - 1) {
acc = [...acc, r]
r = []
}
return acc
}
const chunk = (list, length) =>
list.reduce(toChunk(length), [])
console.log(
chunk(data, 3)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment