Podziel tablicę na części

516

Powiedzmy, że mam tablicę JavaScript wyglądającą następująco:

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

Jakie podejście byłoby odpowiednie, aby podzielić (podzielić) tablicę na wiele mniejszych tablic zawierających, powiedzmy, maksymalnie 10 elementów?

Przemysłowy
źródło
Możliwy duplikat podziału tablicy JS na N tablic
TJ
24
Dla użytkowników lodash szukasz _.chunk .
Ulysse BN
2
Właśnie próbowałem, const chunk = (arr, n) => arr.length ? [arr.slice(0, n), ...chunk(arr.slice(n), n)] : []co jest miłe i krótkie, ale wydaje się, że zajmuje około 256 × tak długo, jak odpowiedź @ AymKdn dla 1000 elementów, i 1058 × tak długo dla 10 000 elementów!
Toph
jeśli potrzebujesz również minimalnego rozmiaru ostatniego fragmentu, oto opcje: stackoverflow.com/questions/57908133/…
Ahmet Cetin

Odpowiedzi:

640

Metoda array.slice może wyodrębnić plasterek z początku, środka lub końca tablicy dla dowolnych potrzeb, bez zmiany oryginalnej tablicy.

var i,j,temparray,chunk = 10;
for (i=0,j=array.length; i<j; i+=chunk) {
    temparray = array.slice(i,i+chunk);
    // do whatever
}
Blazemonger
źródło
19
Pamiętaj, że jeśli jest to funkcja util dochodzić od chunkbycia 0. (nieskończona pętla)
Steven Lu
19
Nie, ostatni fragment powinien być po prostu mniejszy od pozostałych.
Blazemonger,
7
@Blazemonger, rzeczywiście! Następnym razem spróbuję tego sam, zanim przejdę do wniosków. Założyłem (niepoprawnie), że przekazanie danych wejściowych do tablicy.slice, która przekroczyła granice tablicy, byłoby problemem, ale działa idealnie!
rysqui
55
Dla jednej wkładki (łańcuch-lovers) : const array_chunks = (array, chunk_size) => Array(Math.ceil(array.length / chunk_size)).fill().map((_, index) => index * chunk_size).map(begin => array.slice(begin, begin + chunk_size));.
Константин Ван
8
Dlaczego potrzebujesz j? Najpierw pomyślałem, że jest to optymalizacja, ale tak naprawdę jest wolniejsza niż dla (i = 0; i <array.length; i ++) {}
Alex
149

Zmodyfikowano na podstawie odpowiedzi 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]]


drobne uzupełnienie :

Powinienem zauważyć, że powyższe jest nie tak eleganckim (moim zdaniem) obejściem do użycia Array.map. Zasadniczo wykonuje następujące czynności, gdzie ~ jest konkatenacją:

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

Ma taki sam asymptotyczny czas działania, jak w poniższej metodzie, ale być może gorszy stały czynnik ze względu na tworzenie pustych list. Można to przepisać w następujący sposób (głównie taki sam jak metoda Blazemongera, dlatego pierwotnie nie przesłałem tej odpowiedzi):

Bardziej wydajna metoda:

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


Mój ulubiony sposób w dzisiejszych czasach to powyższe lub jedno z poniższych:

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

  }
});

Próbny:

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

Lub jeśli nie chcesz funkcji Array.range, w rzeczywistości jest to tylko jedna linijka (wyłączając puch):

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

lub

Object.defineProperty(Array.prototype, 'chunk', {value: function(n) {
    return Array.from(Array(ceil(this.length/n)), (_,i)=>this.slice(i*n,i*n+n));
}});
ninjagecko
źródło
41
Eh, unikałbym bałagania w prototypie, ponieważ poczucie chłodu wywołanego chunkfunkcją w tablicy nie naprawdę przewyższa dodatkowej złożoności, którą dodajesz, i subtelnych błędów, które mogą powodować bałagan z wbudowanymi prototypami.
Gordon Gustafson,
12
Nie zadziera z nimi, rozszerza je na tablice. Rozumiem, że nigdy nie dotykam prototypu Object.prototype, ponieważ spowoduje to bąbelkowanie do wszystkich obiektów (wszystkiego), ale dla tej funkcji specyficznej dla tablicy nie widzę żadnych problemów.
rlemon
Pewien, że powinna być array.map(function(i)nie array.map(function(elem,i)chociaż
Nigel Anioł
3
Na podstawie tabeli kompatybilności na stronie dewelopera mozilla, Array.map dla IE9 +. Bądź ostrożny.
Maikel D
Uważaj, aby podać liczbę zmiennoprzecinkową - 7,5 typu liczb - prowadzić do nieprzewidywalnych kawałków
Gal Bracha
103

Oto wersja ES6 wykorzystująca redukcję

var perChunk = 2 // items per chunk    

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

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

I jesteś gotowy do tworzenia kolejnych map / redukcji transformacji. Twoja tablica wejściowa pozostaje nienaruszona


Jeśli wolisz krótszą, ale mniej czytelną wersję, możesz dodać trochę concatdo miksu, aby uzyskać ten sam efekt końcowy:

inputArray.reduce((all,one,i) => {
   const ch = Math.floor(i/perChunk); 
   all[ch] = [].concat((all[ch]||[]),one); 
   return all
}, [])
Andrei R.
źródło
To wydaje się najbardziej skondensowanym rozwiązaniem. Co dostaje chunkIndex = Math.floor (index / perChunk)? Czy to średnia?
me-me
5/2 = 2.5i Math.floor(2.5) = 2tak przedmiot o indeksie 5 zostanie umieszczony w segmencie 2
Andrei R
Dzięki Andrei R. Ah, więc przechodzi przez 0 - 2. Jak to się nazywa w kategoriach matematycznych? Chyba chodzi mi o to, że nigdy nie pomyślałbym o podzieleniu indeksu / 2 każdego indeksu, aby uzyskać indeks każdego wycinka. Staram się więc owinąć głowę, ponieważ bardzo mi się podoba, ale nie do końca rozumiem w kategoriach matematycznych. Zwykle robię to, aby uzyskać średnie liczby całkowitej, ale wygląda to inaczej.
me-me
To rozwiązanie jest nieefektywne w porównaniu z innymi rozwiązaniami, ponieważ musisz iterować każdy element.
JP de la Torre,
masz rację @JPdelaTorre, prawdopodobnie nie jest tak skuteczny jak rozwiązania, które kroją tablice, ale tutaj dzielisz włosy. większość wymienionych odpowiedzi byłaby z tego powodu nieefektywna.
Andrei R
102

Staraj się unikać zmarnowania natywnych prototypów, w tym Array.prototype, jeśli nie wiesz, kto będzie konsumował Twój kod (osoby trzecie, współpracownicy, ty w późniejszym terminie itp.).

Istnieją sposoby bezpiecznego rozszerzania prototypów (ale nie we wszystkich przeglądarkach) i istnieją sposoby bezpiecznego konsumowania obiektów utworzonych z rozszerzonych prototypów, ale lepszą zasadą jest przestrzeganie zasady najmniejszej niespodzianki i całkowite unikanie tych praktyk.

Jeśli masz trochę czasu, obejrzyj wykład JSConf 2011 Andrew Duponta „Wszystko jest dozwolone: ​​rozszerzanie wbudowanych” , aby uzyskać dobrą dyskusję na ten temat.

Ale wracając do pytania, chociaż powyższe rozwiązania będą działać, są one zbyt złożone i wymagają niepotrzebnego narzutu obliczeniowego. Oto moje rozwiązanie:

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;
furf
źródło
25
„unikaj nawijania przy użyciu natywnych prototypów” nowi programiści js powinni uzyskać tymczasowy, jeśli nie stały, tatuaż tej frazy.
Jacob Dalton
2
Używam javascript od lat i prawie nigdy nie kłopoczę się prototypem, przy większości funkcji wywoływania, nigdy nie modyfikując tak, jak robią to niektórzy ludzie.
1984
2
najlepsza sugestia w moich oczach, najprostsza do zrozumienia i realizacji, bardzo dziękuję!
Olga Farber
1
@JacobDalton Myślę, że to wina wszystkich uniwersytetów. Ludzie myślą, że OOP musi być używane wszędzie. Dlatego boją się „tworzenia funkcji”. Chcą mieć pewność, że coś w to włoży. Nawet jeśli w ogóle nie jest to właściwe. Jeśli nie ma notacji kropkowej, nie ma „architektury”.
Gherman
51

Testowałem różne odpowiedzi na stronie jsperf.com. Wynik jest dostępny tam: https://web.archive.org/web/20150909134228/https://jsperf.com/chunk-mtds

A najszybszą funkcją (i działa z IE8) jest ta:

function chunk(arr, chunkSize) {
  var R = [];
  for (var i=0,len=arr.length; i<len; i+=chunkSize)
    R.push(arr.slice(i,i+chunkSize));
  return R;
}
AymKdn
źródło
1
Dzięki @AymKdn za dokonanie tego testu porównawczego: To było bardzo pomocne! I był przy użyciu podejścia spawów i rozbił moją przeglądarkę V70 Chrome w rozmiarze klocek 884432. ze swoimi sugerowanego podejścia „slice” w miejscu, mój kod nie crash „render” proces przeglądarkę już. :)
Benny Neugebauer,
34

Wolałbym użyć metody łączenia :

var chunks = function(array, size) {
  var results = [];
  while (array.length) {
    results.push(array.splice(0, size));
  }
  return results;
};
Arek Flinik
źródło
16
Należy zachować ostrożność przy tym rozwiązaniu łączenia, ponieważ modyfikuje on oryginalną tablicę, więc należy wyraźnie udokumentować skutki uboczne.
bdrx
3
Następnie użyj plasterka
mplungjan 24.04.15
@mplungjan Rezultatem byłaby ta sama tablica w kółko podczas używania plasterka. Więc to nie jest tak naprawdę zamiennik bez kilku modyfikacji.
nietonfir
Jedyne, co chciałbym dodać do tej odpowiedzi, to klon oryginalnej tablicy. Zrobiłbym to z operatorem rozprzestrzeniania ES6. var clone = [...array]następnie sprawdź długość i połącz w tej sklonowanej tablicy.
MauricioLeal,
1
Lub jeśli nie możesz korzystać z funkcji ES6, możesz po prostu array = array.slice()stworzyć płytką kopię.
3limin4t0r
34

Jednowarstwowy w ECMA 6

const [list,chuckSize] = [[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], 6]

new Array(Math.ceil(list.length / chuckSize)).fill().map(_ => list.splice(0,chuckSize))
Shairon Toledo
źródło
9
modyfikuje oryginalną listtablicę
Jacka
6
Łatwa naprawa za pomocą .slice () .. .map((_,i) => list.slice(i*chuckSize,i*chuckSize+chuckSize))
James Robey,
2
Na JSPerf jest to znacznie bardziej wydajne niż wiele innych odpowiedzi.
Micheasz Henning,
30

Stare pytanie: nowa odpowiedź! Właściwie pracowałem z odpowiedzią na to pytanie i mój przyjaciel poprawił to! Oto on:

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]]
Rlemon
źródło
14
fyi, wydajność tej metody wynosi O (N ^ 2), więc nie należy jej używać w krytycznych pod względem wydajności sekcjach kodu lub z długimi tablicami (szczególnie, gdy tablica .lengthjest znacznie większa niż wielkość porcji n). Gdyby to był leniwy język (w przeciwieństwie do javascript), algorytm ten nie cierpiałby z powodu czasu O (N ^ 2). To powiedziawszy, rekursywna implementacja jest elegancka. Prawdopodobnie możesz go zmodyfikować, aby poprawić wydajność, najpierw definiując funkcję pomocnika, która się powtarza array,position, a następnie wysyłając: Array.prototype.chunkzwraca [twoją funkcję pomocnika] (...)
ninjagecko
dzięki sensai za pobłogosławienie mnie waszym duchem, który musiałem dziś wieczorem intonować
ugryziony
1
lub ...var chunk = (arr, n) => { if ( !arr.length ) return []; return [ arr.slice( 0, n ) ].concat( chunk(arr.slice(n), n) ) }
Ed Williams
28

W dzisiejszych czasach możesz użyć funkcji fragmentu lodash, aby podzielić tablicę na mniejsze tablice https://lodash.com/docs#chunk Nie musisz już bawić się pętlami!

George Herolyants
źródło
3
Wydaje mi się, że powinno być zastrzeżenie dla SO pytań javascript: czy próbowałeś lodash? Prawie pierwsza rzecz, którą zawarłem w węźle lub przeglądarce.
Jacob Dalton,
20

Było wiele odpowiedzi, ale używam tego:

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

Najpierw sprawdź resztę przy dzieleniu indeksu przez wielkość porcji.

Jeśli pozostała resztka, po prostu zwróć zestaw akumulatorów.

Jeśli nie ma reszty, indeks dzieli się przez wielkość porcji, więc weź wycinek z oryginalnej tablicy (zaczynając od bieżącego indeksu) i dodaj go do tablicy akumulatorów.

Tak więc zwrócona tablica akumulatorów dla każdej iteracji redukcji wygląda mniej więcej tak:

// 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]]
Steve Holgado
źródło
Ładne rozwiązanie i ładne wizualne przedstawienie iteracji. Skończyłem z bardzo podobnym rozwiązaniem, które opublikowałem jako odpowiedź: stackoverflow.com/a/60779547
mts knn
15

Korzystanie z generatorów

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

Ikechukwu Eze
źródło
3
Byłem zaskoczony wszystkimi odpowiedziami na to pytanie, które prawie używały generatorów lub korzystały z nich w bardziej skomplikowany sposób. Niezła zwięzłość i wydajność dzięki temu rozwiązaniu.
Keith
14

Ok, zacznijmy od dość ciasnego:

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

Który jest używany w ten sposób:

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

Następnie mamy tę funkcję ścisłego reduktora:

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

Który jest używany w ten sposób:

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

Ponieważ kotek umiera, gdy łączymy się thisz liczbą, możemy zamiast tego wykonać ręczne curry:

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

Który jest używany w ten sposób:

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

Następnie wciąż dość ścisła funkcja, która robi to wszystko za jednym razem:

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);
użytkownik239558
źródło
Nie działa w iE8.
Nadeemmnn Mohd
4
HA! uwielbiam komentarz kotka. przepraszam za brak konstruktywnego wkładu :)
29
Zrobiłbym to (p[i/n|0] || (p[i/n|0] = [])), abyś nie przypisał wartości, jeśli nie jest to konieczne ...
yckart
12

Chciałem stworzyć proste, niemutujące rozwiązanie w czystym ES6. Specyfika javascript wymaga wypełnienia pustej tablicy przed mapowaniem :-(

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

Ta wersja z rekurencją wydaje się prostsza i bardziej przekonująca:

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

Śmiesznie słabe funkcje tablicowe ES6 stanowią dobre łamigłówki :-)

thoredge
źródło
1
Tak też napisałem mój. Nadal działa, jeśli usuniesz 0z fill, co sprawia, że fillwygląd jest nieco bardziej sensowny, imho.
Coert Grobbelaar
12

Myślę, że to miłe rekurencyjne rozwiązanie ze składnią 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));

Gergely Fehérvári
źródło
9

Utworzono pakiet npm dla tego 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;

Podczas korzystania z TypedArray

var result = [];

for (var i = 0; i < arr.length; i += size) {
  result.push(arr.subarray(i, size + i));
}
return result;
zhiyelee
źródło
@ A1rPun Mój zły, nie dodałem tam komentarza. Tak, nie ma slicemetody TypedArray, możemy subarrayzamiast tego użyć developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
zhiyelee
8

Jeśli używasz EcmaScript w wersji> = 5.1, możesz zaimplementować funkcjonalną wersję za chunk()pomocą array.reduce () o złożoności O (N):

function chunk(chunkSize, array) {
    return array.reduce(function(previous, current) {
        var chunk;
        if (previous.length === 0 || 
                previous[previous.length -1].length === chunkSize) {
            chunk = [];   // 1
            previous.push(chunk);   // 2
        }
        else {
            chunk = previous[previous.length -1];   // 3
        }
        chunk.push(current);   // 4
        return previous;   // 5
    }, []);   // 6
}

console.log(chunk(2, ['a', 'b', 'c', 'd', 'e']));
// prints [ [ 'a', 'b' ], [ 'c', 'd' ], [ 'e' ] ]

Wyjaśnienie każdego z // nbrpowyższych:

  1. Utwórz nową porcję, jeśli poprzednia wartość, tj. Poprzednio zwrócona tablica porcji, jest pusta lub jeśli ostatnia poprzednia porcja zawiera chunkSizeelementy
  2. Dodaj nową porcję do tablicy istniejących porcji
  3. W przeciwnym razie bieżąca porcja jest ostatnią porcją w tablicy porcji
  4. Dodaj bieżącą wartość do porcji
  5. Zwraca zmodyfikowaną tablicę porcji
  6. Zainicjuj redukcję, przekazując pustą tablicę

Curry oparte na chunkSize:

var chunk3 = function(array) {
    return chunk(3, array);
};

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

Możesz dodać chunk()funkcję do Arrayobiektu globalnego :

Object.defineProperty(Array.prototype, 'chunk', {
    value: function(chunkSize) {
        return this.reduce(function(previous, current) {
            var chunk;
            if (previous.length === 0 || 
                    previous[previous.length -1].length === chunkSize) {
                chunk = [];
                previous.push(chunk);
            }
            else {
                chunk = previous[previous.length -1];
            }
            chunk.push(current);
            return previous;
        }, []);
    }
});

console.log(['a', 'b', 'c', 'd', 'e'].chunk(4));
// prints [ [ 'a', 'b', 'c' 'd' ], [ 'e' ] ]

matsev
źródło
7
in coffeescript:

b = (a.splice(0, len) while a.length)

demo 
a = [1, 2, 3, 4, 5, 6, 7]

b = (a.splice(0, 2) while a.length)
[ [ 1, 2 ],
  [ 3, 4 ],
  [ 5, 6 ],
  [ 7 ] ]
Arpit Jain
źródło
a.splice (0, 2) usuwa podtablicę [0..1] zi zwraca tablicę podrzędną a [0..1].
Tworzę
2
Polecam użycie nieniszczącej metody slice () zamiast splice ()
Ash Blue
7
results = []
chunk_size = 10
while(array.length > 0){
   results.push(array.splice(0, chunk_size))
}
Jon
źródło
1
Nie jestem pewien, dlaczego zostało to obniżone, ale kod mógłby posłużyć się pewnym wyjaśnieniem.
jpaugh
2
Ponieważ połączenie jest destrukcyjne w stosunku do oryginalnej tablicy.
metalim
7

Poniższe podejście ES2015 działa bez konieczności definiowania funkcji i bezpośrednio na anonimowych tablicach (przykład z wielkością porcji 2):

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

Jeśli chcesz zdefiniować dla tego funkcję, możesz to zrobić w następujący sposób (poprawa komentarza K. do odpowiedzi Blazemongera ):

const array_chunks = (array, chunk_size) => array
    .map((_, i, all) => all.slice(i*chunk_size, (i+1)*chunk_size))
    .filter(x => x.length)
użytkownik 1460043
źródło
6

I to byłby mój wkład w ten temat. Myślę, że .reduce()to najlepszy sposób.

var segment = (arr, n) => arr.reduce((r,e,i) => i%n ? (r[r.length-1].push(e), r)
                                                    : (r.push([e]), r), []),
        arr = Array.from({length: 31}).map((_,i) => i+1);
        res = segment(arr,7);
console.log(JSON.stringify(res));

Ale powyższa implementacja nie jest zbyt wydajna, ponieważ .reduce()przebiega przez wszystkie arrfunkcje. Bardziej wydajnym podejściem (bardzo zbliżonym do najszybszego imperatywnego rozwiązania) byłoby iterowanie po zredukowanej (do porcji) tablicy, ponieważ możemy wcześniej obliczyć jej rozmiar Math.ceil(arr/n);. Gdy mamy już pustą tablicę wyników, podobnie jak Array(Math.ceil(arr.length/n)).fill();reszta, należy zmapować na nią jej plasterki arr.

function chunk(arr,n){
  var r = Array(Math.ceil(arr.length/n)).fill();
  return r.map((e,i) => arr.slice(i*n, i*n+n));
}

arr = Array.from({length: 31},(_,i) => i+1);
res = chunk(arr,7);
console.log(JSON.stringify(res));

Redu
źródło
5

Użyj fragmentu z lodash

lodash.chunk(arr,<size>).forEach(chunk=>{
  console.log(chunk);
})
Milind Chaudhary
źródło
4

Aby uzyskać funkcjonalne rozwiązanie, używając Ramdy :

Gdzie popularProductsjest twoja tablica wejściowa, 5to wielkość porcji

import splitEvery from 'ramda/src/splitEvery'

splitEvery(5, popularProducts).map((chunk, i) => {
// do something with chunk

})

Damian Green
źródło
4

ES6 podejście jedna linia na podstawie Array.prototype reducei pushmetod:

const doChunk = (list, size) => list.reduce((r, v) =>
  (!r.length || r[r.length - 1].length === size ?
    r.push([v]) : r[r.length - 1].push(v)) && r
, []);

console.log(doChunk([0,1,2,3,4,5,6,7,8,9,10,11,12], 5));
// [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12]]
dilt
źródło
4

Wersja generatora ES6

function* chunkArray(array,size=1){
    var clone = array.slice(0);
    while (clone.length>0) 
      yield clone.splice(0,size); 
};
var a = new Array(100).fill().map((x,index)=>index);
for(const c of chunkArray(a,10)) 
    console.log(c);
Rm558
źródło
Użyj constzamiast tego.
Константин Ван
4

To najbardziej wydajne i proste rozwiązanie, o jakim mogłem myśleć:

function chunk(array, chunkSize) {
    let chunkCount = Math.ceil(array.length / chunkSize);
    let chunks = new Array(chunkCount);
    for(let i = 0, j = 0, k = chunkSize; i < chunkCount; ++i) {
        chunks[i] = array.slice(j, k);
        j = k;
        k += chunkSize;
    }
    return chunks;
}
mpen
źródło
4

ES6 rozprzestrzenia funkcjonalne #ohmy #ftw

const chunk =
  (size, xs) => 
    xs.reduce(
      (segments, _, index) =>
        index % size === 0 
          ? [...segments, xs.slice(index, index + size)] 
          : segments, 
      []
    );

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

goofballLogic
źródło
4

Oto rekurencyjne rozwiązanie, które polega na optymalizacji ogonów.

const splitEvery = (n, xs, y=[]) =>
  xs.length===0 ? y : splitEvery(n, xs.slice(n), y.concat([xs.slice(0, n)])) 

console.log(splitEvery(2, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))

Chris Vouga
źródło
4

Jeszcze jedno rozwiązanie wykorzystujące arr.reduce():

const chunk = (arr, size) => (
  arr.reduce((acc, _, i) => {
    if (i % size === 0) acc.push(arr.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)

To rozwiązanie jest bardzo podobne do rozwiązania Steve'a Holgado . Ponieważ jednak to rozwiązanie nie wykorzystuje rozkładania tablic i nie tworzy nowych tablic w funkcji reduktora, jest szybsze (patrz test jsPerf ) i subiektywnie bardziej czytelne (prostsza składnia) niż inne rozwiązanie.

Przy każdej n-tej iteracji (gdzie n = size; zaczynając od pierwszej iteracji) do tablicy akumulatorów ( acc) dołączany jest fragment tablicy ( arr.slice(i, i + size)), a następnie zwracany. W innych iteracjach tablica akumulatorów jest zwracana w niezmienionej postaci.

Jeśli sizewynosi zero, metoda zwraca pustą tablicę. Jeśli sizejest ujemny, metoda zwraca zepsute wyniki. Tak więc, jeśli zajdzie taka potrzeba, możesz zrobić coś z wartościami ujemnymi lub dodatnimi size.


Jeśli w twoim przypadku ważna jest prędkość, prosta forpętla byłaby szybsza niż używanie arr.reduce()(patrz test jsPerf ), a niektórzy mogą uważać ten styl za bardziej czytelny:

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

  const result = []
  for (let i = 0; i < arr.length; i += size) {
    result.push(arr.slice(i, i + size))
  }
  return result
}
mts knn
źródło
2

EDYCJA: @ mblase75 dodał bardziej zwięzły kod do wcześniejszej odpowiedzi, kiedy pisałem mój, więc zalecam skorzystanie z jego rozwiązania.

Możesz użyć takiego kodu:

var longArray = ["Element 1","Element 2","Element 3", /*...*/];
var smallerArrays = []; // will contain the sub-arrays of 10 elements each
var arraySize = 10;
for (var i=0;i<Math.ceil(longArray.length/arraySize);i++) {
    smallerArrays.push(longArray.slice(i*arraySize,i*arraySize+arraySize));
}

Zmień wartość, arraySizeaby zmienić maksymalną długość mniejszych tablic.

dentaku
źródło
2

Oto niemutujące rozwiązanie wykorzystujące tylko rekurencję i slice ().

const splitToChunks = (arr, chunkSize, acc = []) => (
    arr.length > chunkSize ?
        splitToChunks(
            arr.slice(chunkSize),
            chunkSize,
            [...acc, arr.slice(0, chunkSize)]
        ) :
        [...acc, arr]
);

Następnie po prostu użyj go tak, jak splitToChunks([1, 2, 3, 4, 5], 3)chcesz [[1, 2, 3], [4, 5]].

Oto skrzypce do wypróbowania: https://jsfiddle.net/6wtrbx6k/2/

Suhair Zain
źródło