Розбиття масиву JS на N масивів


82

Уявіть, у мене такий масив JS:

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

Я хочу поділити цей масив на N менших масивів. Наприклад:

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

Для Python я маю таке:

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

Для JS найкращим правильним рішенням, яке я міг би придумати, є рекурсивна функція, але мені це не подобається, оскільки це складно і негарно. Ця внутрішня функція повертає масив, подібний до цього [1, 2, 3, null, 4, 5, 6, null, 7, 8], а потім мені доведеться його повторно прокрутити та розділити вручну. (Моєю першою спробою було повернення цього: [1, 2, 3, [4, 5, 6, [7, 8, 9]]], і я вирішив зробити це за допомогою нульового роздільника).

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

Ось jsfiddle цього: http://jsfiddle.net/uduhH/

Як би ви це зробили? Дякую!


2
пов'язані з - stackoverflow.com/q/40166199/104380
VSync

1
Ваша splitфункція не за горами. Ви можете видалити nullкомпанію, додавши дві обгортки масивів: if (cols == 1) return [array]і return [array.slice(0, size)].concat(split(array.slice(size), cols-1)). Я вважаю цю рекурсивну версію набагато читабельнішою, ніж більшість відповідей тут.
Скотт Сауєт,

Відповіді:


134

Ви можете зробити зрізи "збалансованими" (довжини підмасивів різняться якомога менше) або "рівними" (всі підмасиви, але останні мають однакову довжину):

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>


Ваше рішення чудове, воно робить те саме, що робить моє рекурсивне рішення, але без усього цього безладу. Дякую!
Тіаго,

3
Працює як шарм. Гарне рішення
Вардан

Привіт @georg, ти можеш пояснити цей рядок: var size = Math.ceil((len - i) / n--);
dpg5000

@ dpg5000: при нарізуванні наступного шматка його розмір - це кількість решти елементів ( len - i) за кількістю решти шматків ( n--)
georg

1
Привіт @georg дякую. Як би я змінив цей код, щоб переконатися, що всі підмасиви мають однакову довжину, за винятком останнього підмасиву (якщо, звичайно, дільник не призводить до залишку, при цьому всі підмасиви будуть рівними). Я вдячний за будь-яку допомогу.
dpg5000

19

Я думаю, що такий спосіб використання сплайсингу є найчистішим:

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

Наприклад, для parts = 3, ви б взяли 1/3, потім 1/2 решти частини, а потім решту масиву. Math.ceilгарантує, що у випадку нерівномірної кількості елементів вони потраплять до найперших шматочків.

(Примітка: це знищує початковий масив.)


17

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


2
Це працює не так, як очікувалось для n = 5 та arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].
Тіаго,

1
це не ділиться на n підмасивів, лише на підмасиви n довжини.
dpg5000

1
Будь ласка, додайте пояснення, чому цей код допомагає операційній програмі. Це допоможе дати відповідь, на якій можуть повчитися майбутні глядачі. Докладніше див. У розділі Як відповісти .
Єретична мавпа

12

Я щойно здійснив ітеративну реалізацію алгоритму: http://jsfiddle.net/ht22q/ . Він здає ваші тестові кейси.

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

1
(Це працює, як очікувалось, але я можу вибрати лише одну відповідь як правильну) Привіт! Дякуємо за допомогу. Приємно подумати про те, як використати решту.
Тіаго,

7

Ви можете звести його до матриці. У наведеному нижче прикладі розділений масив ( arr) на матрицю двопозиційних масивів. Якщо ви хочете інших розмірів, просто змініть значення 2 у другому рядку:

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

Сподіваюся, це допоможе!

РЕДАГУВАТИ: Оскільки деякі люди все ще коментують, це не відповідає на запитання, оскільки я фіксував розмір кожного шматка замість кількості шматочків, який я хочу. Тут йде код, що пояснює, що я намагаюся пояснити в розділі коментарів: Використання 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>') }


2
Вау приємний і лаконічний спосіб зробити це! Любіть це! Чудова робота! :-)
Філіпп Монне

1
Я люблю цю техніку, але вона не відповідає на питання. Він повертає будь-яку кількість шматочків розміру x, тоді як запитання задавали х кількість шматочків рівномірного розміру.
Джоді Уоррен,

3
Люблю це !!! Я рефакторував повернути шматки рівномірного розміру 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; }, [[]]) }
Давід Андреацціні,

3
точно не відповідає на питання.
macdelacruz

1
Лаконічний і дуже розумний, мій улюблений спосіб вирішити цю та цілі інші справи за допомогою цієї простої моделі, дякую!
Едмонд Тамаш

5

Оновлення: 21.07.2020

Відповідь, яку я дав кілька років тому, працює, лише якщо originalArray.length<= numCols. Ви можете використати щось на зразок цієї функції нижче, але це створить макет, який не зовсім відповідає розглядуваному питанню (горизонтальне сортування, а не вертикальне сортування). АКА: [1,2,3,4]-> [[1,4],[2],[3]]. Я розумію, що це все одно може надати користь, тому я залишу це тут, але я рекомендую відповідь Сенте .

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
}

Оригінальна відповідь від 2017 року:

Старе питання, але оскільки vanillaJS не є вимогою, і тому багато хто намагається вирішити це за допомогою lodash / chunk, і, не помиляючись, що _.chunkнасправді робить, ось коротке + точне рішення з використанням lodash:

(На відміну від прийнятої відповіді, це також гарантує n стовпців, навіть якщо 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
}

forПетлі в кінці кінців , це те , що гарантує необхідну кількість «стовпчики».


Це не вдається, коли довжина масиву дорівнює 4, а numCols - 3. Пробується splitArray ([1, 2, 3, 4], 3), і він повертає [[1, 2], [3, 4], []].
Пратік Кульшрешт

Ви абсолютно праві @PratikKulshreshth. Я оновлю відповідь. Для тих, хто цікавиться, мені зараз найбільше подобається відповідь Сенте: stackoverflow.com/a/51514813/1322810
Joao

2

Рекурсивний підхід, не перевірений.

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

2

Інший рекурсив працює досить добре, він менш потворний

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

2
function parseToPages(elements, pageSize = 8) {
    var result = [];
    while (elements.length) {
        result.push(elements.splice(0, pageSize));
    }
    return result;
}

3
Будь ласка, не просто дамп код як відповідь. Додайте пояснення, як це вирішує проблему питання.
Mark Rotteveel

2

Якщо вам заздалегідь відомо, який розмір потрібних фрагментів вам відомий, існує досить елегантний спосіб 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]));

Я вважаю це позначення досить корисним для, наприклад, синтаксичного аналізу RGBA з Uint8ClampedArray.


За винятком того, що це не вдається для n <4: groupsOfFour( [ 1 ] )повертає [ 1 , undefined, undefined, undefined ], а не очікуване (і бажане) [ [1] ].
Ніколас Кері,

1

Ймовірно, чистіший підхід буде таким (без використання будь-якої іншої бібліотеки):

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

Я створив свій власний масив, який слід поділити. Ви можете знайти код тут

Також у нас є метод "шматок" у бібліотеці lodash, який дуже корисний. Сподіваюся, що це допомагає


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

1

Мутація - це, загалом кажучи, погана річ ™.

Це приємно, чисто та ідемпотентно.

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

0

Я зробив це таким чином, це працює ...

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

0

перевірити мою версію цього розділу масиву

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

0

якщо ви знаєте, хочете встановити child_arrays.length, тоді я думаю, що це рішення найкраще:

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

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

відповідь: [1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]


0

Якщо ви можете використовувати lodashі хочете підхід до функціонального програмування, ось що я придумав:

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
} 

0

все вище може працювати нормально, але що, якщо у вас є associativeмасив із рядками в якості ключів?

objectKeys = Object.keys;

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

0

У мене є такий, який не змінює оригінальний масив

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
}


0

Ви можете використовувати просту рекурсивну функцію

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


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

-1

Просто використовуйте функцію chunk lodash, щоб розділити масив на менші масиви https://lodash.com/docs#chunk Не потрібно більше возитися з циклами!


1
Питання задає питання, як це вирішити за допомогою vannila js, а не використання бібліотек js
TJ

3
Дякуємо, що підняли це питання. Я не знав, що у лодаша таке.
Тіаго

9
Це також не відповідає на питання. Він хоче N масивів, а не масиви з N елементів.
mAAdhaTTah

-3

Якщо ви використовуєте lodash, ви можете досягти цього досить легко, як показано нижче:

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

3
Це неправильно. _.chunkстворює масиви з N елементів, а не N масивів. У вашому прикладі буде виведено 6 масивів з 2 елементами в кожному, окрім останнього[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]
Василіса Барзокаса

ось вихідне питання. будь ласка, прочитайте очікувану поведінку у питанні.
абхісекпаул
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.