Imagina que tengo una matriz JS como esta:
var a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
Lo que quiero es dividir esa matriz en N matrices más pequeñas. Por ejemplo:
split_list_in_n(a, 2)
[[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11]]
For N = 3:
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11]]
For N = 4:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11]]
For N = 5:
[[1, 2, 3], [4, 5], [6, 7], [8, 9], [10, 11]]
Para Python, tengo esto:
def split_list_in_n(l, cols):
""" Split up a list in n lists evenly size chuncks """
start = 0
for i in xrange(cols):
stop = start + len(l[i::cols])
yield l[start:stop]
start = stop
Para JS, la mejor solución correcta que se me ocurrió es una función recursiva, pero no me gusta porque es complicada y fea. Esta función interna devuelve una matriz como esta [1, 2, 3, nula, 4, 5, 6, nula, 7, 8], y luego tengo que repetirla y dividirla manualmente. (Mi primer intento fue devolver esto: [1, 2, 3, [4, 5, 6, [7, 8, 9]]], y decidí hacerlo con el separador nulo).
function split(array, cols) {
if (cols==1) return array;
var size = Math.ceil(array.length / cols);
return array.slice(0, size).concat([null]).concat(split(array.slice(size), cols-1));
}
Aquí hay un jsfiddle de eso: http://jsfiddle.net/uduhH/
¿Cómo lo harías tú? ¡Gracias!
javascript
arrays
Tiago
fuente
fuente
split
función no está lejos. Puede eliminar lanull
empresa agregando dos envoltorios de matriz:if (cols == 1) return [array]
yreturn [array.slice(0, size)].concat(split(array.slice(size), cols-1))
. Encuentro esta versión recursiva mucho más legible que la mayoría de las respuestas aquí.Respuestas:
Puede hacer que los cortes estén "equilibrados" (las longitudes de los subarreglos difieren lo menos posible) o "pares" (todos los subarreglos, pero el último tienen la misma longitud):
function chunkify(a, n, balanced) { if (n < 2) return [a]; var len = a.length, out = [], i = 0, size; if (len % n === 0) { size = Math.floor(len / n); while (i < len) { out.push(a.slice(i, i += size)); } } else if (balanced) { while (i < len) { size = Math.ceil((len - i) / n--); out.push(a.slice(i, i += size)); } } else { n--; size = Math.floor(len / n); if (len % size === 0) size--; while (i < size * n) { out.push(a.slice(i, i += size)); } out.push(a.slice(size * n)); } return out; } /////////////////////// onload = function () { function $(x) { return document.getElementById(x); } function calc() { var s = +$('s').value, a = []; while (s--) a.unshift(s); var n = +$('n').value; $('b').textContent = JSON.stringify(chunkify(a, n, true)) $('e').textContent = JSON.stringify(chunkify(a, n, false)) } $('s').addEventListener('input', calc); $('n').addEventListener('input', calc); calc(); }
<p>slice <input type="number" value="20" id="s"> items into <input type="number" value="6" id="n"> chunks:</p> <pre id="b"></pre> <pre id="e"></pre>
fuente
var size = Math.ceil((len - i) / n--);
len - i
) por el número de fragmentos restantes (n--
)Creo que de esta manera usar empalme es el más limpio:
splitToChunks(array, parts) { let result = []; for (let i = parts; i > 0; i--) { result.push(array.splice(0, Math.ceil(array.length / i))); } return result; }
Por ejemplo, para
parts = 3
, tomaría 1/3, luego 1/2 de la parte restante, luego el resto de la matriz.Math.ceil
asegura que en caso de un número impar de elementos, irán a los primeros fragmentos.(Nota: esto destruye la matriz inicial).
fuente
function split(array, n) { let [...arr] = array; var res = []; while (arr.length) { res.push(arr.splice(0, n)); } return res; }
fuente
Acabo de realizar una implementación iterativa del algoritmo: http://jsfiddle.net/ht22q/ . Pasa tus casos de prueba.
function splitUp(arr, n) { var rest = arr.length % n, // how much to divide restUsed = rest, // to keep track of the division over the elements partLength = Math.floor(arr.length / n), result = []; for(var i = 0; i < arr.length; i += partLength) { var end = partLength + i, add = false; if(rest !== 0 && restUsed) { // should add one element for the division end++; restUsed--; // we've used one division element now add = true; } result.push(arr.slice(i, end)); // part of the array if(add) { i++; // also increment i in the case we added an extra element for division } } return result; }
fuente
Puedes reducirlo a una matriz. El siguiente ejemplo divide la matriz (
arr
) en una matriz de matrices de dos posiciones. Si desea otros tamaños, simplemente cambie el valor 2 en la segunda línea:target.reduce((memo, value, index) => { if (index % 2 === 0 && index !== 0) memo.push([]) memo[memo.length - 1].push(value) return memo }, [[]])
¡Espero eso ayude!
EDITAR: Debido a que algunas personas todavía están comentando, esto no responde a la pregunta ya que estaba arreglando el tamaño de cada fragmento en lugar del número de fragmentos que quiero. Aquí viene el código que explica lo que trato de explicar en la sección de comentarios: Usando el
target.length
.// Chunk function const chunk = (target, size) => { return target.reduce((memo, value, index) => { // Here it comes the only difference if (index % (target.length / size) == 0 && index !== 0) memo.push([]) memo[memo.length - 1].push(value) return memo }, [[]]) } // Usage write(chunk([1, 2, 3, 4], 2)) write(chunk([1, 2, 3, 4], 4)) // For rendering pruposes. Ignore function write (content) { document.write(JSON.stringify(content), '</br>') }
fuente
function splitArr(arr, n) { return arr.reduce(function (a, i) { if (a[a.length - 1].length >= arr.length / n) { a.push([]) } a[a.length - 1].push(i) return a; }, [[]]) }
Actualización: 21/7/2020
La respuesta que di hace unos años solo funciona si
originalArray.length
<=numCols
. Alternativamente, podría usar algo como esta función a continuación, pero eso creará un diseño que no coincide con la pregunta en cuestión (clasificación horizontal en lugar de clasificación vertical). AKA:[1,2,3,4]
->[[1,4],[2],[3]]
. Entiendo que esto aún podría proporcionar valor, así que lo dejaré aquí, pero recomiendo la respuesta de Senthe .function splitArray(flatArray, numCols){ const newArray = [] for (let c = 0; c < numCols; c++) { newArray.push([]) } for (let i = 0; i < flatArray.length; i++) { const mod = i % numCols newArray[mod].push(flatArray[i]) } return newArray }
Respuesta original de 2017:
Pregunta antigua, pero dado que vanillaJS no es un requisito y muchos están tratando de resolver esto con lodash / chunk, y sin confundir lo que
_.chunk
realmente hace, aquí hay una solución concisa y precisa usandolodash
:(A diferencia de la respuesta aceptada, esto también garantiza n columnas incluso si
originalArray.length
<numCols
)import _chunk from 'lodash/chunk' /** * Split an array into n subarrays (or columns) * @param {Array} flatArray Doesn't necessarily have to be flat, but this func only works 1 level deep * @param {Number} numCols The desired number of columns * @return {Array} */ export function splitArray(flatArray, numCols){ const maxColLength = Math.ceil(flatArray.length/numCols) const nestedArray = _chunk(flatArray, maxColLength) let newArray = [] for (var i = 0; i < numCols; i++) { newArray[i] = nestedArray[i] || [] } return newArray }
El
for
bucle al final es lo que garantiza el número deseado de "columnas".fuente
Enfoque recursivo, no probado.
function splitArray(array, parts, out) { var len = array.length , partLen if (parts < len) { partLen = Math.ceil(len / parts); out.push(array.slice(0, partLen)); if (parts > 1) { splitArray(array.slice(partLen), parts - 1, out); } } else { out.push(array); } }
fuente
Otro recursivo funciona bastante bien, es menos feo
function nSmaller(num, arr, sliced) { var mySliced = sliced || []; if(num === 0) { return sliced; } var len = arr.length, point = Math.ceil(len/num), nextArr = arr.slice(point); mySliced.push(arr.slice(0, point)); nSmaller(num-1, nextArr, mySliced); return(mySliced); }
fuente
function parseToPages(elements, pageSize = 8) { var result = []; while (elements.length) { result.push(elements.splice(0, pageSize)); } return result; }
fuente
Si conoce el tamaño de los trozos que desea de antemano, hay una forma ES6 bastante elegante de hacer esto:
const groupsOfFour = ([a,b,c,d, ...etc]) => etc.length? [[a,b,c,d], ...groupsOfFour(etc)] : [[a,b,c,d]]; console.log(groupsOfFour([1,2,3,4,1,2,3,4,1,2,3,4]));
Encuentro esta notación bastante útil para, por ejemplo, analizar RGBA de un
Uint8ClampedArray
.fuente
groupsOfFour( [ 1 ] )
devuelve[ 1 , undefined, undefined, undefined ]
, en lugar de lo esperado (y deseado)[ [1] ]
.Probablemente, el enfoque más limpio sería el siguiente (sin usar ninguna otra biblioteca):
var myArray = []; for(var i=0; i<100; i++){ myArray.push(i+1); } console.log(myArray); function chunk(arr, size){ var chunkedArr = []; var noOfChunks = Math.ceil(arr.length/size); console.log(noOfChunks); for(var i=0; i<noOfChunks; i++){ chunkedArr.push(arr.slice(i*size, (i+1)*size)); } return chunkedArr; } var chunkedArr = chunk(myArray, 3); console.log(chunkedArr);
He creado mi propia matriz que se va a fragmentar. Puedes encontrar el código aquí
También tenemos un método "chunk" en la biblioteca lodash que es de gran utilidad. Espero que ayude
fuente
function splitArray(arr, numOfParts = 10){ const splitedArray = [] for (let i = 0; i < numOfParts;i++) { const numOfItemsToSplice = arr.length / 10; splitedArray.push(arr.splice(0, numOfItemsToSplice)) } return splitedArray; }
fuente
Lo hice de esta manera, funciona ...
function splitArray(array, parts) { if (parts< array.length && array.length > 1 && array != null) { var newArray = []; var counter1 = 0; var counter2 = 0; while (counter1 < parts) { newArray.push([]); counter1 += 1; } for (var i = 0; i < array.length; i++) { newArray[counter2++].push(array[i]); if (counter2 > parts - 1) counter2 = 0; } return newArray; } else return array; }
fuente
verifique mi versión de esta matriz dividida
// divide array Array.prototype.divideIt = function(d){ if(this.length <= d) return this; var arr = this, hold = [], ref = -1; for(var i = 0; i < arr.length; i++){ if(i % d === 0){ ref++; } if(typeof hold[ref] === 'undefined'){ hold[ref] = []; } hold[ref].push(arr[i]); } return hold; };
fuente
si sabes que quieres establecer child_arrays.length, creo que esta solución es la mejor:
function sp(size, arr){ //size - child_array.length var out = [],i = 0, n= Math.ceil((arr.length)/size); while(i < n) { out.push(arr.splice(0, (i==n-1) && size < arr.length ? arr.length: size)); i++;} return out; }
llamar a fn: sp (2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) // 2 - child_arrat.length
respuesta: [1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]
fuente
Si puede usar
lodash
y le gustaría un enfoque de programación funcional, esto es lo que se me ocurre:const _ = require('lodash') function splitArray(array, numChunks) { return _.reduce(_.range(numChunks), ({array, result, numChunks}, chunkIndex) => { const numItems = Math.ceil(array.length / numChunks) const items = _.take(array, numItems) result.push(items) return { array: _.drop(array, numItems), result, numChunks: numChunks - 1 } }, { array, result: [], numChunks }).result }
fuente
todo lo anterior podría funcionar bien, pero ¿qué pasa si tiene una
associative
matriz con cadenas como claves?objectKeys = Object.keys; arraySplit(arr, n) { let counter = 0; for (const a of this.objectKeys(arr)) { this.arr[(counter%n)][a] = arr[a]; counter++; } }
fuente
Tengo uno que no altera la matriz original.
function splitArray(array = [], nPieces = 1){ const splitArray = []; let atArrPos = 0; for(let i = 0; i < nPieces; i++){ const splitArrayLength = Math.ceil((array.length - atArrPos)/ (nPieces - i)); splitArray.push([]); splitArray[i] = array.slice(atArrPos, splitArrayLength + atArrPos); atArrPos += splitArrayLength; } return splitArray }
fuente
Puedes usar una función recursiva simple
const chunkify = (limit, completeArray, finalArray = [])=>{ if(!completeArray.length) return finalArray const a = completeArray.splice(0,limit); return chunkify(limit, completeArray, [...finalArray,a]) }
fuente
splitToChunks(arrayvar, parts) { let result = []; for (let i = parts; i > 0; i--) { result.push(arrayvar.splice(0, Math.ceil(arrayvar.length / i))); } return result; }
fuente
La mutación es, en general, algo malo ™.
Esto es agradable, limpio e idempotente.
function partition(list = [], n = 1) { const isPositiveInteger = Number.isSafeInteger(n) && n > 0; if (!isPositiveInteger) { throw new RangeError('n must be a positive integer'); } const partitions = []; const partitionLength = Math.ceil(list.length / n); for (let i = 0; i < list.length; i += partitionLength) { const partition = list.slice(i, i+partitionLength); partitions.push( partition ); } return partitions; }
fuente
Simplemente use la función chunk lodash 'para dividir la matriz en matrices más pequeñas https://lodash.com/docs#chunk ¡ Ya no es necesario jugar con los bucles!
fuente
Si está usando lodash, puede lograrlo con bastante facilidad, como se muestra a continuación:
import {chunk} from 'lodash'; // divides the array into 2 sections chunk([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 2); // => [[1,2,3,4,5,6], [7,8,9,10,11]]
fuente
_.chunk
crea matrices de N elementos, no de N matrices. Su ejemplo tendría una salida de 6 matrices con 2 elementos en cada una[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]