Dzielenie tablicy JS na N tablic

82

Wyobraź sobie, że mam taką tablicę JS:

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

Chcę podzielić tę tablicę na N mniejszych tablic. Na przykład:

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

W przypadku Pythona mam to:

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

W przypadku JS najlepszym rozwiązaniem, jakie mogłem wymyślić, jest funkcja rekurencyjna, ale nie podoba mi się to, ponieważ jest skomplikowana i brzydka. Ta funkcja wewnętrzna zwraca tablicę taką jak ta [1, 2, 3, null, 4, 5, 6, null, 7, 8], a następnie muszę ponownie ją zapętlić i ręcznie podzielić. (Moją pierwszą próbą było zwrócenie tego: [1, 2, 3, [4, 5, 6, [7, 8, 9]]] i zdecydowałem się to zrobić z separatorem zerowym).

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

Oto jsfiddle tego: http://jsfiddle.net/uduhH/

Jak byś to zrobił? Dzięki!

Tiago
źródło
2
powiązane z - stackoverflow.com/q/40166199/104380
vsync
1
Twoja splitfunkcja nie jest daleko. Możesz usunąć nullfirmę, dodając dwa opakowania tablic: if (cols == 1) return [array]i return [array.slice(0, size)].concat(split(array.slice(size), cols-1)). Uważam, że ta rekurencyjna wersja jest znacznie bardziej czytelna niż większość odpowiedzi tutaj.
Scott Sauyet

Odpowiedzi:

134

Możesz sprawić, że plasterki będą „zbalansowane” (długości podtablic różnią się tak mniej, jak to możliwe) lub „równe” (wszystkie podtablice, ale ostatnia mają taką samą długość):

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
źródło
Twoje rozwiązanie jest zgrabne, robi to samo, co moje rozwiązanie rekurencyjne, ale bez całego tego bałaganu. Dziękuję Ci!
Tiago,
3
Działa jak urok. Niezłe rozwiązanie
Vardan
Cześć @georg, czy możesz wyjaśnić tę linię: var size = Math.ceil((len - i) / n--);
dpg5000,
@ dpg5000: podczas krojenia następnego kawałka jego rozmiar to liczba pozostałych elementów ( len - i) przez liczbę pozostałych kawałków ( n--)
georg
1
Cześć @georg, dziękuję. Jak zmodyfikowałbym ten kod, aby zapewnić, że wszystkie podtablice są równej długości z wyjątkiem ostatniej podtablicy (chyba że oczywiście dzielnik nie daje reszty, a wszystkie podtablice byłyby równe). Doceniam każdą pomoc.
dpg5000,
19

Myślę, że ten sposób użycia splotu jest najczystszy:

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

Na przykład parts = 3weźmiesz 1/3, potem 1/2 pozostałej części, a następnie resztę tablicy. Math.ceilzapewnia, że ​​w przypadku nieparzystej liczby elementów trafią one do najwcześniejszych fragmentów.

(Uwaga: to niszczy początkową tablicę).

Senthe
źródło
17

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

bagbee
źródło
2
Nie działa to zgodnie z oczekiwaniami dla n = 5 i arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].
Tiago,
1
nie dzieli się to na n podtabliców, a jedynie na podtablice o długości n.
dpg5000,
1
Dodaj wyjaśnienie, dlaczego ten kod pomaga PO. Pomoże to znaleźć odpowiedź, z której przyszli widzowie będą mogli się czegoś nauczyć. Aby uzyskać więcej informacji, zobacz Jak odpowiedzieć .
Heretic Monkey
12

Właśnie wykonałem iteracyjną implementację algorytmu: http://jsfiddle.net/ht22q/ . To przechodzi twoje przypadki testowe.

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
źródło
1
(Działa to zgodnie z oczekiwaniami, ale mogę wybrać tylko jedną poprawną odpowiedź) Cześć! Dzięki za pomoc. Miło pomyśleć o tym, jak wykorzystać resztę.
Tiago,
7

Możesz zredukować to do matrycy. Poniższy przykład podzieli tablicę array ( arr) na macierz tablic dwupozycyjnych. Jeśli chcesz inne rozmiary, po prostu zmień wartość 2 w drugiej linii:

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

Mam nadzieję, że to pomoże!

EDYCJA: Ponieważ niektórzy ludzie wciąż komentują, to nie odpowiada na pytanie, ponieważ ustalałem rozmiar każdego fragmentu zamiast liczby fragmentów, które chcę. Oto kod wyjaśniający, co próbuję wyjaśnić w sekcji komentarzy: Korzystanie z 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
źródło
2
Wow, ładny i zwięzły sposób na zrobienie tego! Kocham to! Dobra robota! :-)
Philippe Monnet
1
Uwielbiam tę technikę, ale nie odpowiada ona na pytanie. Zwraca dowolną liczbę fragmentów o rozmiarze x, podczas gdy pytania dotyczyły liczby x kawałków o równych rozmiarach.
Jodi Warren,
3
Kocham to !!! Refaktoryzowałem, aby zwrócić kawałki o równych rozmiarach 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; }, [[]]) }
davide andreazzini
3
zdecydowanie nie odpowiada na pytanie.
macdelacruz
1
Zwięzły i naprawdę sprytny, mój ulubiony sposób na rozwiązanie tego i wielu innych przypadków za pomocą tego prostego wzoru, dzięki!
Edmond Tamas
5

Aktualizacja: 21.07.2020

Odpowiedź, której udzieliłem kilka lat temu, działa tylko wtedy, gdy originalArray.length<= numCols. Możesz alternatywnie użyć czegoś podobnego do tej funkcji poniżej, ale stworzy to układ, który nie do końca pasuje do pytania (sortowanie poziome zamiast sortowania pionowego). AKA: [1,2,3,4]-> [[1,4],[2],[3]]. Rozumiem, że to może nadal mieć wartość, więc zostawię to tutaj, ale polecam odpowiedź 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
}

Oryginalna odpowiedź z 2017 r .:

Stare pytanie, ale ponieważ vanillaJS nie jest wymaganiem, a tak wielu próbuje rozwiązać ten problem za pomocą lodash / chunk, i nie myląc się, co _.chunktak naprawdę robi, oto zwięzłe + dokładne rozwiązanie wykorzystujące lodash:

(W przeciwieństwie do zaakceptowanej odpowiedzi, gwarantuje to również n kolumn, nawet jeśli 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
}

forPętla na końcu jest co gwarantuje wymaganą liczbę „kolumny”.

Joao
źródło
To kończy się niepowodzeniem, gdy długość tablicy wynosi 4, a numCols wynosi 3. Próbowano z splitArray ([1, 2, 3, 4], 3) i zwraca [[1, 2], [3, 4], []].
Pratik Kulshreshth
Masz całkowitą rację @PratikKulshreshth. Zaktualizuję odpowiedź. Dla wszystkich zainteresowanych najbardziej podoba mi się teraz odpowiedź Senthe: stackoverflow.com/a/51514813/1322810
Joao
2

Podejście rekurencyjne, nie testowane.

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
źródło
2

Inna rekurencyjna działa całkiem dobrze, jest mniej brzydka

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
źródło
2
function parseToPages(elements, pageSize = 8) {
    var result = [];
    while (elements.length) {
        result.push(elements.splice(0, pageSize));
    }
    return result;
}
Vitalii Kulyk
źródło
3
Proszę, nie zrzucaj kodu jako odpowiedzi. Dodaj wyjaśnienie, w jaki sposób rozwiązuje to problem pytania.
Mark Rotteveel
2

Jeśli zdarzy się, że znasz wcześniej rozmiar kawałków, które chcesz, jest całkiem elegancki sposób na zrobienie tego w ES6:

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

Uważam, że ta notacja jest całkiem przydatna, na przykład do parsowania RGBA z pliku Uint8ClampedArray.

user1034533
źródło
Tyle tylko, że nie powiedzie się to dla n <4: groupsOfFour( [ 1 ] )zwraca [ 1 , undefined, undefined, undefined ], a nie oczekiwane (i pożądane) [ [1] ].
Nicholas Carey,
1

Prawdopodobnie czystsze podejście byłoby następujące (bez korzystania z żadnej innej biblioteki):

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

Stworzyłem własną tablicę, która ma być podzielona. Możesz znaleźć kod tutaj

W bibliotece lodash mamy również metodę „chunk”, która jest bardzo przydatna. Mam nadzieję, że to pomoże

Vaibhav Pachauri
źródło
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
źródło
1

Ogólnie mówiąc, mutacja jest złą rzeczą ™.

To jest ładne, czyste i idempotentne.

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
źródło
0

Zrobiłem to w ten sposób, działa ...

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
źródło
0

sprawdź moją wersję tego podziału tablicy

// 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
źródło
0

jeśli wiesz, że chcesz ustawić child_arrays.length, to myślę, że to rozwiązanie jest najlepsze:

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

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

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

Anja Ishmukhametova
źródło
0

Jeśli możesz używać lodashi chciałbyś mieć podejście do programowania funkcjonalnego, oto co wymyśliłem:

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
źródło
0

wszystko powyżej może działać dobrze, ale co, jeśli masz associativetablicę z ciągami znaków jako kluczami?

objectKeys = Object.keys;

arraySplit(arr, n) {
    let counter = 0;
    for (const a of this.objectKeys(arr)) {
        this.arr[(counter%n)][a] = arr[a];
        counter++;
    }
}
lokers
źródło
0

Mam taki, który nie zmienia oryginalnej tablicy

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
źródło
0

Możesz użyć prostej funkcji rekurencyjnej

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
źródło
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
źródło
-1

Po prostu użyj funkcji chunk lodash, aby podzielić tablicę na mniejsze tablice https://lodash.com/docs#chunk Nie musisz już majstrować przy pętlach!

George Herolyants
źródło
1
Pytanie brzmi, jak rozwiązać ten problem za pomocą vannila js, a nie bibliotek js
TJ
3
Dzięki za poruszenie tego. Nie wiedziałem, że ma to lodash.
Tiago
9
To również nie odpowiada na pytanie. Chce mieć N tablic, a nie tablice N elementów.
mAAdhaTTah
-3

Jeśli używasz lodash, możesz to dość łatwo osiągnąć, jak poniżej:

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
źródło
3
To jest źle. _.chunktworzy tablice N elementów, a nie N tablic. Twój przykład miałby wynik 6 tablic z 2 elementami w każdym z [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]
wyjątkiem
to właśnie jest pierwotne pytanie. przeczytaj oczekiwane zachowanie w pytaniu.
abhisekpaul