Aufteilen eines JS-Arrays in N Arrays

82

Stellen Sie sich vor, ich habe ein JS-Array wie dieses:

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];

Ich möchte dieses Array in N kleinere Arrays aufteilen. Zum Beispiel:

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]]

Für Python habe ich Folgendes:

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

Für JS ist die beste richtige Lösung, die ich finden könnte, eine rekursive Funktion, aber ich mag sie nicht, weil sie kompliziert und hässlich ist. Diese innere Funktion gibt ein Array wie dieses zurück [1, 2, 3, null, 4, 5, 6, null, 7, 8], und dann muss ich es erneut schleifen und manuell teilen. (Mein erster Versuch war, dies zurückzugeben: [1, 2, 3, [4, 5, 6, [7, 8, 9]]], und ich entschied mich, dies mit dem Nulltrennzeichen zu tun).

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));
}

Hier ist eine jsfiddle davon: http://jsfiddle.net/uduhH/

Wie würdest du das machen? Vielen Dank!

Tiago
quelle
2
im Zusammenhang mit - stackoverflow.com/q/40166199/104380
vsync
1
Ihre splitFunktion ist nicht weit weg. Sie können das nullUnternehmen entfernen, indem Sie zwei Array-Wrapper hinzufügen: if (cols == 1) return [array]und return [array.slice(0, size)].concat(split(array.slice(size), cols-1)). Ich finde diese rekursive Version viel lesbarer als die meisten Antworten hier.
Scott Sauyet

Antworten:

133

Sie können die Slices "ausgeglichen" (die Längen der Subarrays unterscheiden sich so gering wie möglich) oder "gerade" (alle Subarrays außer den letzten haben die gleiche Länge) machen:

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>

georg
quelle
Ihre Lösung ist ordentlich, sie macht dasselbe wie meine rekursive Lösung, aber ohne all dieses Durcheinander. Vielen Dank!
Tiago
3
Funktioniert wie ein Zauber. Schöne Lösung
Vardan
Hallo @georg, können Sie bitte diese Zeile erklären: var size = Math.ceil((len - i) / n--);
dpg5000
@ dpg5000: Wenn der nächste Block geschnitten wird, ist seine Größe die Anzahl der verbleibenden Elemente ( len - i) durch die Anzahl der verbleibenden Blöcke ( n--)
Georg
1
Hallo @georg, danke. Wie würde ich diesen Code ändern, um sicherzustellen, dass alle Sub-Arrays bis auf das letzte Subarray gleich lang sind (es sei denn, der Divisor ergibt natürlich keinen Rest, wobei alle Subarrays gleich wären). Ich freue mich über jede Hilfe.
dpg5000
19

Ich denke, diese Art der Verwendung von Spleiß ist die sauberste:

splitToChunks(array, parts) {
    let result = [];
    for (let i = parts; i > 0; i--) {
        result.push(array.splice(0, Math.ceil(array.length / i)));
    }
    return result;
}

Zum Beispiel parts = 3würden Sie 1/3, dann 1/2 des verbleibenden Teils und dann den Rest des Arrays nehmen. Math.ceilstellt sicher, dass bei ungleichmäßiger Anzahl von Elementen die frühesten Blöcke erreicht werden.

(Hinweis: Dadurch wird das ursprüngliche Array zerstört.)

Senthe
quelle
17

function split(array, n) {
  let [...arr]  = array;
  var res = [];
  while (arr.length) {
    res.push(arr.splice(0, n));
  }
  return res;
}

Bagbee
quelle
2
Dies funktioniert nicht wie erwartet für n = 5 und arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].
Tiago
1
Dies wird nicht in n Subarrays aufgeteilt, sondern nur in Subarrays mit n Länge.
dpg5000
1
Bitte fügen Sie eine Erklärung hinzu, warum dieser Code dem OP hilft. Dies wird dazu beitragen, eine Antwort zu geben, von der zukünftige Zuschauer lernen können. Siehe Wie Antwort für weitere Informationen.
Heretic Monkey
12

Ich habe gerade eine iterative Implementierung des Algorithmus vorgenommen: http://jsfiddle.net/ht22q/ . Es besteht Ihre Testfälle.

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;
}
pimvdb
quelle
1
(Dies funktioniert wie erwartet, aber ich kann nur eine Antwort als richtig auswählen.) Hi! Danke fürs Helfen. Schön darüber nachzudenken, wie man den Rest benutzt.
Tiago
7

Sie können es in eine Matrix reduzieren. Im folgenden Beispiel wird das Array ( arr) in eine Matrix aus Arrays mit zwei Positionen aufgeteilt. Wenn Sie andere Größen wünschen, ändern Sie einfach den Wert 2 in der zweiten Zeile:

target.reduce((memo, value, index) => {
  if (index % 2 === 0 && index !== 0) memo.push([])
  memo[memo.length - 1].push(value)
  return memo
}, [[]])

Ich hoffe es hilft!

BEARBEITEN: Da einige Leute immer noch Kommentare abgeben, beantwortet dies die Frage nicht, da ich die Größe jedes Blocks anstelle der Anzahl der gewünschten Blöcke festgelegt habe. Hier kommt der Code, der erklärt, was ich im Kommentarbereich zu erklären versuche: Verwenden von 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>') }

sospedra
quelle
2
Wow, nette und prägnante Art, dies zu tun! Liebe es! Gut gemacht! :-)
Philippe Monnet
1
Ich liebe diese Technik, aber sie beantwortet die Frage nicht. Es wird eine beliebige Anzahl von x-großen Chunks zurückgegeben, während bei den Fragen nach x-gleich großen Chunks gefragt wurde.
Jodi Warren
3
Ich liebe das !!! Ich habe überarbeitet, um gleichmäßig große Stücke zurückzugeben 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; }, [[]]) }
David Andreazzini
3
beantwortet die Frage definitiv nicht.
Macdelacruz
1
Prägnant und wirklich klug, mein bevorzugter Weg, diesen und einen ganzen anderen Fall mit diesem einfachen Muster zu lösen, danke!
Edmond Tamas
4

Update: 21.07.2020

Die Antwort, die ich vor einigen Jahren gegeben habe, funktioniert nur, wenn originalArray.length<= numCols. Alternativ können Sie auch eine der folgenden Funktionen verwenden. Dadurch wird jedoch ein Layout erstellt, das nicht ganz der jeweiligen Frage entspricht (horizontale Sortierung statt vertikale Sortierung). AKA: [1,2,3,4]-> [[1,4],[2],[3]]. Ich verstehe, dass dies immer noch einen Wert liefert, also lasse ich dies hier, aber ich empfehle Senthes Antwort .

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
}

Ursprüngliche Antwort von 2017:

Alte Frage, aber da VanilleJS keine Voraussetzung ist und so viele versuchen, dies mit lodash / chunk zu lösen, und ohne zu verwechseln, was _.chunktatsächlich geschieht, ist hier eine präzise und genaue Lösung mit lodash:

(Im Gegensatz zur akzeptierten Antwort garantiert dies auch n Spalten, selbst wenn 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
}

Die forSchleife am Ende garantiert die gewünschte Anzahl von "Spalten".

Joao
quelle
Dies schlägt fehl, wenn die Array-Länge 4 und numCols 3 beträgt. Mit splitArray ([1, 2, 3, 4], 3) versucht und [[1, 2], [3, 4], []] zurückgegeben.
Pratik Kulshreshth
Du hast absolut Recht @PratikKulshreshth. Ich werde die Antwort aktualisieren. Für alle Interessierten gefällt mir Senthes Antwort jetzt am besten: stackoverflow.com/a/51514813/1322810
Joao
2

Rekursiver Ansatz, nicht getestet.

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);
    }
}
12000
quelle
2

Ein anderes Rekursiv funktioniert ganz gut, es ist weniger hässlich

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);
}
Mcs
quelle
2
function parseToPages(elements, pageSize = 8) {
    var result = [];
    while (elements.length) {
        result.push(elements.splice(0, pageSize));
    }
    return result;
}
Vitalii Kulyk
quelle
3
Bitte geben Sie den Code nicht einfach als Antwort aus. Fügen Sie eine Erklärung hinzu, wie das Problem der Frage behoben wird.
Mark Rotteveel
2

Wenn Sie die Größe der gewünschten Chunks im Voraus kennen, gibt es eine ziemlich elegante ES6-Methode:

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]));

Ich finde diese Notation ziemlich nützlich, um beispielsweise RGBA aus a zu analysieren Uint8ClampedArray.

user1034533
quelle
Abgesehen davon, dass dies für n <4 fehlschlägt: groupsOfFour( [ 1 ] )gibt [ 1 , undefined, undefined, undefined ]statt der erwarteten (und gewünschten) zurück [ [1] ].
Nicholas Carey
1

Wahrscheinlich wäre der sauberere Ansatz der folgende (ohne Verwendung einer anderen Bibliothek):

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);

Ich habe mein eigenes Array erstellt, das aufgeteilt werden soll. Den Code finden Sie hier

Wir haben auch eine Methode "Chunk" in der Lodash-Bibliothek, die von großem Nutzen ist. hoffentlich hilft das

Vaibhav Pachauri
quelle
1
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;
    }
Stefdelec
quelle
1

Mutation ist im Allgemeinen eine schlechte Sache.

Das ist schön, sauber und idempotent.

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;
}
Nicholas Carey
quelle
0

Ich habe es so gemacht, es funktioniert ...

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;
}
Juanmorschrott
quelle
0

Überprüfen Sie meine Version dieses Array Split

// 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;
};
hitesh upadhyay
quelle
0

Wenn du weißt, dass du child_arrays.length setzen willst, dann denke ich, dass diese Lösung am besten ist:

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;
}

Rufen Sie fn: sp (2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) // 2 - child_arrat.length auf

Antwort: [1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]

Anja Ishmukhametova
quelle
0

Wenn Sie lodasheinen funktionalen Programmieransatz verwenden können und möchten, habe ich Folgendes:

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
} 
Wormie
quelle
0

Alles oben könnte gut funktionieren, aber was ist, wenn Sie ein associativeArray mit Zeichenfolgen als Schlüssel haben?

objectKeys = Object.keys;

arraySplit(arr, n) {
    let counter = 0;
    for (const a of this.objectKeys(arr)) {
        this.arr[(counter%n)][a] = arr[a];
        counter++;
    }
}
Loker
quelle
0

Ich habe eine, die das ursprüngliche Array nicht verändert

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
}

Ihsan Müjdeci
quelle
0

Sie können eine einfache rekursive Funktion verwenden

const chunkify = (limit, completeArray, finalArray = [])=>{
    if(!completeArray.length) return finalArray
    const a = completeArray.splice(0,limit);
    return chunkify(limit, completeArray, [...finalArray,a])
}

Matías Sánchez Rivas
quelle
0
splitToChunks(arrayvar, parts) {
    let result = [];
    for (let i = parts; i > 0; i--) {
        result.push(arrayvar.splice(0, Math.ceil(arrayvar.length / i)));
    }
    return result;
}
Abhi Savadiya
quelle
-1

Verwenden Sie einfach die Chunk-Funktion von lodash, um das Array in kleinere Arrays aufzuteilen. Https://lodash.com/docs#chunk Sie müssen nicht mehr mit den Schleifen herumspielen!

George Herolyants
quelle
1
Die Frage fragt, wie man dies mit vannila js löst, nicht mit js Bibliotheken
TJ
3
Vielen Dank, dass Sie dies angesprochen haben. Ich wusste nicht, dass lodash das hat.
Tiago
9
Dies beantwortet auch nicht die Frage. Er will N Arrays, keine Arrays von N Elementen.
mAAdhaTTah
-3

Wenn Sie lodash verwenden, können Sie es ziemlich einfach wie folgt erreichen:

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]]
abhisekpaul
quelle
3
Das ist falsch. _.chunkErstellt Arrays mit N Elementen, nicht mit N Arrays. Ihr Beispiel würde eine Ausgabe von 6 Arrays mit 2 Elementen in jedem haben, außer dem letzten[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]
Vassilis Barzokas
Das ist die ursprüngliche Frage. Bitte lesen Sie das erwartete Verhalten in der Frage.
Abhisekpaul