Tìm kiếm trang web

javascript - Chia một mảng JS thành N mảng


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 đúng và tốt nhất mà tôi có thể nghĩ 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], và sau đó tôi phải lặp lại nó và tách thủ công. (Lần thử đầu tiên của tôi là trả về như sau: [1, 2, 3, [4, 5, 6, [7, 8, 9]]], và tôi đã quyết định làm nó với bộ 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 cái đó: http://jsfiddle.net/uduhH/

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

Câu trả lời #1

Bạn có thể làm cho các lát cắt trở nên "cân bằng" (độ dài của các mảng con khác nhau càng ít càng tốt) hoặc "đều" (tất cả các mảng con ngoại trừ mả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>

Câu trả lời số 2

Tôi nghĩ cách sử dụng splice theo cách này là gọn gàng nhất:

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

// Example:

const example = [0,1,2,3,4,5,6,7,8,9,10,11,12]

console.log(splitToNChunks([...example], 3))
console.log(splitToNChunks([...example], 5))

Ví dụ, với n=3, bạn sẽ lấy 1/3, sau đó lấy 1/2 phần còn lại, rồi lấy 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 đều, chúng sẽ được chia vào các phần đầu tiên.

(Lưu ý: gọi .splice trên một mảng sẽ trực tiếp thay đổi độ dài của nó. Để tránh làm hỏng mảng ban đầu của bạn, bạn có thể sử dụng bản sao nông tạm thời của mảng thay vì vậy: const copiedArray=[ ...originalArray ])

Câu trả lời #3

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

Câu trả lời #4

Tôi vừa thực hiện một phiên bản lặp của thuật toán: http://jsfiddle.net/ht22q/. Nó vượt qua các trường hợp kiểm tra 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;
}

Câu trả lời số 5

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 các mảng hai phần tử. Nếu bạn muốn các kích thước khác, chỉ cần thay đổi giá trị 2 ở 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 điều này hữu ích!

EDIT: Bởi vì một số người vẫn đang bình luận nên điều này không trả lời được câu hỏi vì tôi đang sửa kích thước của mỗi đoạn thay vì số lượng đoạn mà tôi muốn. Ở đây có mã giải thích điều 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>') }

Câu trả lời số 6

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

Câu trả lời mà tôi đã đưa vài năm trước chỉ hoạt động nếu originalArray.length <= numCols. Bạn có thể thay vào đó sử dụng một hàm như bên dưới, nhưng điều đó sẽ tạo ra một bố cục không hoàn toàn khớp với câu hỏi hiện tại (sắp xếp theo hàng thay vì theo cột). Ví dụ: [1,2,3,4] -> [[1,4],[2],[3]]. Tôi hiểu rằng điều này vẫn có thể có giá trị, vì vậy tôi sẽ để nó ở đây, nhưng tôi khuyến nghị 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à yêu cầu và 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 lẫn với những gì _.chunk thực sự làm, đây là một giải pháp ngắn gọn + chính xác 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
}

Vòng lặp for ở cuối là thứ đảm bảo số lượng "cột" mong muốn.

Bài viết liên quan