Tách một mảng JS thành N mảng


81

Hãy tưởng tượng tôi có một mảng JS như thế này:

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

Điều tôi muốn là chia mảng đó thành N mảng nhỏ hơn. Ví dụ:

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

Đối với Python, tôi có cái này:

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

Đối với JS, giải pháp tốt nhất mà tôi có thể đưa ra là một hàm đệ quy, nhưng tôi không thích nó vì nó phức tạp và xấu xí. Hàm bên trong này trả về một mảng như thế này [1, 2, 3, null, 4, 5, 6, null, 7, 8], sau đó tôi phải lặp lại nó và chia nó theo cách thủ công. (Nỗ lực đầu tiên của tôi là trả về điều này: [1, 2, 3, [4, 5, 6, [7, 8, 9]]] và tôi quyết định làm điều đó với dấu phân tách null).

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

Đây là một jsfiddle của điều đó: http://jsfiddle.net/uduhH/

Bạn làm điều đó như thế nào? Cảm ơn!



1
splitChức năng của bạn không còn xa nữa. Bạn có thể xóa nulldoanh nghiệp bằng cách thêm hai trình bao bọc mảng: if (cols == 1) return [array]return [array.slice(0, size)].concat(split(array.slice(size), cols-1)). Tôi thấy phiên bản đệ quy này dễ đọc hơn nhiều so với hầu hết các câu trả lời ở đây.
Scott Sauyet

Câu trả lời:


133

Bạn có thể làm cho các lát cắt "cân bằng" (độ dài của các mảng con càng ít khác nhau càng tốt) hoặc "thậm chí" (tất cả các mảng con nhưng cuối cùng có cùng độ dài):

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>


Giải pháp của bạn rất gọn gàng, nó làm giống như giải pháp đệ quy của tôi nhưng không có tất cả những thứ lộn xộn đó. Cảm ơn bạn!
Tiago

3
Hoạt động như một sự quyến rũ .. Giải pháp tốt đẹp
Vardan

Xin chào @georg, bạn có thể vui lòng giải thích dòng này không: var size = Math.ceil((len - i) / n--);
dpg5000

@ dpg5000: khi cắt đoạn tiếp theo, kích thước của nó là số phần tử còn lại ( len - i) bằng số đoạn còn lại ( n--)
georg 11/02/16

1
Xin chào @georg cảm ơn bạn. Tôi sẽ sửa đổi mã này như thế nào để đảm bảo rằng tất cả các mảng con có độ dài bằng nhau ngoại trừ mảng con cuối cùng (tất nhiên trừ khi số chia cho kết quả không có phần dư, theo đó tất cả các mảng con sẽ bằng nhau). Tôi đánh giá cao bất kỳ sự giúp đỡ nào.
dpg5000 17/02/16

18

Tôi nghĩ rằng cách này sử dụng mối nối là sạch nhất:

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

Ví dụ: parts = 3bạn sẽ lấy 1/3, rồi 1/2 phần còn lại, sau đó là phần còn lại của mảng. Math.ceilđảm bảo rằng trong trường hợp số lượng phần tử không đồng đều, chúng sẽ đi đến các phần sớm nhất.

(Lưu ý: điều này sẽ phá hủy mảng ban đầu.)


16

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


2
Điều này không hoạt động như mong đợi đối với n = 5 và arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].
Tiago

1
điều này không chia thành n mảng con, mà chỉ chia thành các mảng con có độ dài n.
dpg5000

1
Vui lòng thêm một số giải thích về lý do tại sao mã này giúp OP. Điều này sẽ giúp cung cấp câu trả lời mà người xem trong tương lai có thể học hỏi. Xem Cách trả lời để biết thêm thông tin.
Heretic Monkey

12

Tôi vừa thực hiện lặp đi lặp lại việc triển khai thuật toán: http://jsfiddle.net/ht22q/ . Nó vượt qua các trường hợp thử nghiệm của bạn.

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
(Điều này hoạt động như mong đợi, nhưng tôi chỉ có thể chọn một câu trả lời đúng) Xin chào! Cảm ơn vì đã giúp đỡ. Nghĩ tốt về cách sử dụng phần còn lại.
Tiago

7

Bạn có thể giảm nó thành một ma trận. Ví dụ dưới đây chia mảng ( arr) thành một ma trận mảng hai vị trí. Nếu bạn muốn các kích thước khác, chỉ cần thay đổi 2 giá trị trên dòng thứ hai:

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

Hy vọng nó giúp!

CHỈNH SỬA: Bởi vì một số người vẫn đang bình luận, điều này không trả lời câu hỏi vì tôi đã sửa kích thước của từng đoạn thay vì số lượng mà tôi muốn. Đây là mã giải thích những gì tôi đang cố gắng giải thích trong phần nhận xét: Sử dụng 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
Wow cách làm việc này thật hay và ngắn gọn! Yêu nó! Bạn đã làm rất tốt! :-)
Philippe Monnet

1
Tôi thích kỹ thuật này, nhưng nó không trả lời câu hỏi. Nó trả về bất kỳ số lượng khối kích thước x nào, trong khi các câu hỏi yêu cầu x số lượng khối có kích thước đồng đều.
Jodi Warren

3
Thích cái này !!! Tôi đã refactored trở về khối đều có kích thước 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
chắc chắn không trả lời câu hỏi.
macdelacruz

1
Ngắn gọn và thực sự thông minh, cách ưa thích của tôi để giải quyết vấn đề này và toàn bộ các trường hợp khác với mẫu đơn giản này, cảm ơn!
Edmond Tamas

4

Cập nhật: 21/7/2020

Câu trả lời tôi đã đưa ra cách đây vài năm chỉ hoạt động nếu originalArray.length<= numCols. Ngoài ra, bạn có thể sử dụng một cái gì đó giống như chức năng này bên dưới, nhưng điều đó sẽ tạo ra một bố cục không hoàn toàn phù hợp với câu hỏi hiện tại (sắp xếp theo chiều ngang chứ không phải sắp xếp theo chiều dọc). AKA: [1,2,3,4]-> [[1,4],[2],[3]]. Tôi hiểu điều này có thể vẫn cung cấp giá trị vì vậy tôi sẽ để điều này ở đây, nhưng tôi đề xuất câu trả lời của 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
}

Câu trả lời gốc từ năm 2017:

Câu hỏi cũ, nhưng vì vanillaJS không phải là một yêu cầu và rất nhiều người đang cố gắng giải quyết vấn đề này bằng lodash / chunk, và không nhầm những gì _.chunkthực sự xảy ra, đây là một giải pháp ngắn gọn + chính xác bằng cách sử dụng lodash:

(Không giống như câu trả lời được chấp nhận, điều này cũng đảm bảo n cột ngay cả khi 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
}

Các forvòng lặp ở cuối là gì đảm bảo số lượng mong muốn của "cột".


Điều này không thành công khi độ dài mảng là 4 và numCols là 3. Đã thử với splitArray ([1, 2, 3, 4], 3) và nó trả về [[1, 2], [3, 4], []].
Pratik Kulshreshth

Bạn hoàn toàn đúng @PratikKulshreshth. Tôi sẽ cập nhật câu trả lời. Đối với bất kỳ ai quan tâm, tôi thích câu trả lời của Senthe nhất bây giờ: stackoverflow.com/a/51514813/1322810
Joao

2

Phương pháp đệ quy, không thử nghiệm.

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

Một đệ quy khác hoạt động khá tốt, nó ít xấu hơn

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
Vui lòng không chỉ kết xuất mã như một câu trả lời. Thêm lời giải thích cách này giải quyết vấn đề của câu hỏi.
Mark Rotteveel

2

Nếu bạn tình cờ biết trước kích thước của các khối bạn muốn, có một cách ES6 khá thanh lịch để làm điều này:

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

Tôi thấy ký hiệu này khá hữu ích, chẳng hạn như phân tích cú pháp RGBA ra khỏi a Uint8ClampedArray.


Ngoại trừ điều này không thành công đối với n <4: groupsOfFour( [ 1 ] )trả về [ 1 , undefined, undefined, undefined ], chứ không phải là mong đợi (và mong muốn) [ [1] ].
Nicholas Carey

1

Có thể cách tiếp cận rõ ràng hơn sẽ như sau (mà không sử dụng bất kỳ thư viện nào khác):

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

Tôi đã tạo mảng của riêng mình sẽ được chia nhỏ. Bạn có thể tìm thấy mã ở đây

Ngoài ra, chúng tôi có một phương thức "chunk" trong thư viện lodash rất được sử dụng. Hy vọng điều đó sẽ giúp


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

0

Tôi đã làm theo cách này, nó hoạt động ...

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

kiểm tra phiên bản tách mảng này của tôi

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

nếu bạn biết muốn đặt child_arrays.length thì tôi nghĩ giải pháp này tốt nhất:

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

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

câu trả lời: [1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]


0

Nếu bạn có thể sử dụng lodashvà muốn một cách tiếp cận lập trình chức năng, đây là những gì tôi đưa ra:

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

tất cả ở trên có thể hoạt động tốt, nhưng điều gì sẽ xảy ra nếu bạn có associativemảng với chuỗi làm khóa?

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

Tôi có một cái không làm thay đổi mảng ban đầu

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

Bạn có thể sử dụng một hàm đệ quy đơn giản

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

0

Nói chung, đột biến là một Điều Xấu.

Điều này là tốt đẹp, sạch sẽ và lý tưởng.

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

-1

Chỉ cần sử dụng hàm chunk của lodash để chia mảng thành các mảng nhỏ hơn https://lodash.com/docs#chunk Không cần phải loay hoay với các vòng lặp nữa!


1
Câu hỏi đặt ra cách giải quyết vấn đề này bằng cách sử dụng vannila js, không sử dụng thư viện js
TJ

3
Cảm ơn đã đưa ra vấn đề này. Tôi không biết lodash có cái này.
Tiago

9
Điều này cũng không trả lời câu hỏi. Anh ta muốn N mảng, không phải mảng N phần tử.
mAAdhaTTah

-3

Nếu bạn đang sử dụng lodash, bạn có thể đạt được nó khá dễ dàng như dưới đây:

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
Cái này sai. _.chunktạo mảng gồm N phần tử không phải N mảng. Ví dụ bạn sẽ có một sản lượng 6 mảng với 2 yếu tố trong mỗi exept người cuối cùng[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]
Vassilis Barzokas

đó là những gì câu hỏi ban đầu là. vui lòng đọc các hành vi mong đợi trong câu hỏi.
abhisekpaul
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.