Tách chuỗi lớn thành các đoạn có kích thước n trong JavaScript


208

Tôi muốn chia một chuỗi rất lớn (giả sử, 10.000 ký tự) thành các khối cỡ N.

Điều gì sẽ là cách tốt nhất về hiệu suất để làm điều này?

Ví dụ: "1234567890"chia cho 2 sẽ trở thành ["12", "34", "56", "78", "90"].

Liệu một cái gì đó như thế này có thể được sử dụng String.prototype.matchvà nếu vậy, đó có phải là cách tốt nhất để làm điều đó về mặt hiệu suất?

Câu trả lời:


456

Bạn có thể làm một cái gì đó như thế này:

"1234567890".match(/.{1,2}/g);
// Results in:
["12", "34", "56", "78", "90"]

Phương thức vẫn sẽ hoạt động với các chuỗi có kích thước không phải là bội số chính xác của kích thước khối:

"123456789".match(/.{1,2}/g);
// Results in:
["12", "34", "56", "78", "9"]

Nói chung, đối với bất kỳ chuỗi nào trong đó bạn muốn trích xuất các chuỗi con có kích thước n lớn nhất , bạn sẽ làm:

str.match(/.{1,n}/g); // Replace n with the size of the substring

Nếu chuỗi của bạn có thể chứa dòng mới hoặc trả về vận chuyển, bạn sẽ làm:

str.match(/(.|[\r\n]){1,n}/g); // Replace n with the size of the substring

Về hiệu suất, tôi đã thử điều này với khoảng 10 nghìn ký tự và phải mất hơn một giây trên Chrome. YMMV.

Điều này cũng có thể được sử dụng trong một chức năng tái sử dụng:

function chunkString(str, length) {
  return str.match(new RegExp('.{1,' + length + '}', 'g'));
}

8
Vì câu trả lời này đã gần 3 tuổi, tôi muốn thử lại bài kiểm tra hiệu năng do @Vivin thực hiện. Vì vậy, FYI, việc chia 100 nghìn ký tự thành hai bằng cách sử dụng biểu thức chính quy đã cho là ngay lập tức trên Chrome v33.
aymericbeaumet

1
@Fmstrat Ý của bạn là gì nếu "nếu chuỗi của bạn chứa khoảng trắng, thì nó không được tính theo chiều dài"? Có, .hoàn toàn không phù hợp với dòng mới. Tôi sẽ cập nhật câu trả lời để nó mất \n\rvào tài khoản.
Vivin Paliath

2
Một cái gì đó như var chunks = str.split("").reverse().join().match(/.{1, 4}/).map(function(s) { return s.split("").reverse().join(); });. Điều này thực hiện trong khối 4. Tôi không chắc ý của bạn là "ít hay nhiều". Hãy nhớ rằng điều này sẽ không hoạt động nói chung, đặc biệt là với các chuỗi có chứa các ký tự kết hợp và cũng có thể phá vỡ các chuỗi Unicode.
Vivin Paliath 04/11/2015

2
Theo developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/, bạn có thể khớp bất kỳ ký tự nào, kể cả các dòng mới, với [^]. Với ví dụ này, ví dụ của bạn sẽ dẫn đếnstr.match(/[^]{1,n}/g)
Francesc Rosas

1
Đối với bất cứ ai đang tìm kiếm chuỗi chunk thực sự nhanh với điểm chuẩn hiệu suất trên jsperf, hãy xem câu trả lời của tôi . Sử dụng một regex là phương pháp chunk chậm nhất trong tất cả.
Justin Warkentin 4/2/2016

34

Tôi đã tạo ra một số biến thể nhanh hơn mà bạn có thể thấy trên jsPerf . Người tôi thích nhất là đây:

function chunkSubstr(str, size) {
  const numChunks = Math.ceil(str.length / size)
  const chunks = new Array(numChunks)

  for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
    chunks[i] = str.substr(o, size)
  }

  return chunks
}

2
vì vậy, điều này đã làm việc tuyệt vời trên các chuỗi dài (khoảng 800 nghìn - 9 triệu ký tự) ngoại trừ khi tôi đặt kích thước thành 20 vì một số lý do, đoạn cuối cùng không được trả lại ... hành vi rất kỳ lạ.
David

1
@DavidAnderton Bắt tốt. Tôi đã sửa nó và thú vị là nó dường như còn chạy nhanh hơn nữa. Nó đã được làm tròn khi đáng lẽ phải làm Math.ceil()để xác định số lượng chính xác.
Justin Warkentin

1
Cảm ơn! Tôi kết hợp anh ấy làm mô-đun NPM với sự hỗ trợ Unicode tùy chọn - github.com/vladgolubev/fast-chunk-opes
Vlad Holubiev

33

Dòng dưới cùng:

  • matchlà rất không hiệu quả, slicelà tốt hơn, trên Firefox substr/ substringvẫn tốt hơn
  • match thậm chí còn kém hiệu quả hơn đối với các chuỗi ngắn (ngay cả với regex được lưu trong bộ nhớ cache - có thể do thời gian thiết lập phân tích cú pháp regex)
  • match thậm chí còn không hiệu quả hơn đối với kích thước khối lớn (có thể do không thể "nhảy")
  • đối với các chuỗi dài hơn với kích thước khối rất nhỏ, matchvượt trội hơn so slicevới IE cũ nhưng vẫn thua trên tất cả các hệ thống khác
  • đá jsperf

19

Đây là một giải pháp nhanh chóng và đơn giản -

function chunkString (str, len) {
  const size = Math.ceil(str.length/len)
  const r = Array(size)
  let offset = 0
  
  for (let i = 0; i < size; i++) {
    r[i] = str.substr(offset, len)
    offset += len
  }
  
  return r
}

console.log(chunkString("helloworld", 3))
// => [ "hel", "low", "orl", "d" ]

// 10,000 char string
const bigString = "helloworld".repeat(1000)
console.time("perf")
const result = chunkString(bigString, 3)
console.timeEnd("perf")
console.log(result)
// => perf: 0.385 ms
// => [ "hel", "low", "orl", "dhe", "llo", "wor", ... ]


1
Bạn phải sử dụng substr()thay vì substring().
Leif

2
Tôi tò mò, tại sao dấu gạch dưới trong tên biến?
Felipe Valdes

@FelipeValdes Tôi giả sử không nhầm lẫn chúng với các biến toàn cục / tham số hoặc biểu thị chúng là phạm vi riêng tư.
Ông Polywhirl

15

Sự ngạc nhiên! Bạn có thể sử dụng chia để tách.

var parts = "1234567890 ".split(/(.{2})/).filter(O=>O)

Kết quả trong [ '12', '34', '56', '78', '90', ' ' ]


Đây là câu trả lời awesomest.
galkin

2
Để làm gì filter (o=>o)?
Cá chép Ben

2
regex hiện tại tạo ra các phần tử mảng trống giữa các khối. filter(x=>x)được sử dụng để lọc các phần tử trống đó
artemdev

Ngắn và thông minh nhưng lặp đi lặp lại qua đầu vào nhiều lần. Câu trả lời này chậm hơn gấp 4 lần so với các giải pháp khác trong chủ đề này.
Cảm ơn bạn

6
@BenCarp Đó là người điều khiển xe máy. Nó làm cho nó đi nhanh hơn. ;)
Fozi

7
var str = "123456789";
var chunks = [];
var chunkSize = 2;

while (str) {
    if (str.length < chunkSize) {
        chunks.push(str);
        break;
    }
    else {
        chunks.push(str.substr(0, chunkSize));
        str = str.substr(chunkSize);
    }
}

alert(chunks); // chunks == 12,34,56,78,9

5

Tôi đã viết một hàm mở rộng, vì vậy độ dài đoạn cũng có thể là một mảng các số, như [1,3]

String.prototype.chunkString = function(len) {
    var _ret;
    if (this.length < 1) {
        return [];
    }
    if (typeof len === 'number' && len > 0) {
        var _size = Math.ceil(this.length / len), _offset = 0;
        _ret = new Array(_size);
        for (var _i = 0; _i < _size; _i++) {
            _ret[_i] = this.substring(_offset, _offset = _offset + len);
        }
    }
    else if (typeof len === 'object' && len.length) {
        var n = 0, l = this.length, chunk, that = this;
        _ret = [];
        do {
            len.forEach(function(o) {
                chunk = that.substring(n, n + o);
                if (chunk !== '') {
                    _ret.push(chunk);
                    n += chunk.length;
                }
            });
            if (n === 0) {
                return undefined; // prevent an endless loop when len = [0]
            }
        } while (n < l);
    }
    return _ret;
};

Mật mã

"1234567890123".chunkString([1,3])

sẽ trở lại:

[ '1', '234', '5', '678', '9', '012', '3' ]

4

nó Chia chuỗi lớn thành chuỗi nhỏ của các từ đã cho .

function chunkSubstr(str, words) {
  var parts = str.split(" ") , values = [] , i = 0 , tmpVar = "";
  $.each(parts, function(index, value) {
      if(tmpVar.length < words){
          tmpVar += " " + value;
      }else{
          values[i] = tmpVar.replace(/\s+/g, " ");
          i++;
          tmpVar = value;
      }
  });
  if(values.length < 1 &&  parts.length > 0){
      values[0] = tmpVar;
  }
  return values;
}

3
var l = str.length, lc = 0, chunks = [], c = 0, chunkSize = 2;
for (; lc < l; c++) {
  chunks[c] = str.slice(lc, lc += chunkSize);
}

2

Tôi sẽ sử dụng regex ...

var chunkStr = function(str, chunkLength) {
    return str.match(new RegExp('[\\s\\S]{1,' + +chunkLength + '}', 'g'));
}

1
const getChunksFromString = (str, chunkSize) => {
    var regexChunk = new RegExp(`.{1,${chunkSize}}`, 'g')   // '.' represents any character
    return str.match(regexChunk)
}

Gọi nó khi cần thiết

console.log(getChunksFromString("Hello world", 3))   // ["Hel", "lo ", "wor", "ld"]

1

Đây là một giải pháp tôi đã đưa ra cho các chuỗi mẫu sau khi thử nghiệm một chút:

Sử dụng:

chunkString(5)`testing123`

function chunkString(nSize) {
    return (strToChunk) => {
        let result = [];
        let chars = String(strToChunk).split('');

        for(let i = 0; i < (String(strToChunk).length / nSize); i++) {
            result = result.concat(chars.slice(i*nSize,(i+1)*nSize).join(''));
        }
        return result
    }
}

document.write(chunkString(5)`testing123`);
// returns: testi,ng123

document.write(chunkString(3)`testing123`);
// returns: tes,tin,g12,3


1

Bạn có thể sử dụng reduce()mà không cần bất kỳ regex nào:

(str, n) => {
  return str.split('').reduce(
    (acc, rec, index) => {
      return ((index % n) || !(index)) ? acc.concat(rec) : acc.concat(',', rec)
    },
    ''
  ).split(',')
}

Tôi nghĩ rằng nó sẽ giúp ích rất nhiều nếu bạn cung cấp các ví dụ về cách sử dụng reducephương pháp của bạn .
kiatng

0

Ở dạng hàm nguyên mẫu:

String.prototype.lsplit = function(){
    return this.match(new RegExp('.{1,'+ ((arguments.length==1)?(isFinite(String(arguments[0]).trim())?arguments[0]:false):1) +'}', 'g'));
}

0

Đây là mã mà tôi đang sử dụng, nó sử dụng String.prototype.slice .

Có, nó khá dài khi một câu trả lời diễn ra khi nó cố gắng tuân theo các tiêu chuẩn hiện tại càng gần càng tốt và tất nhiên có chứa một lượng bình luận JSDOC hợp lý . Tuy nhiên, một khi được thu nhỏ, mã chỉ có 828 byte và một khi được nén để truyền, nó chỉ có 497 byte.

Phương thức 1 mà phương thức này thêm vào String.prototype(sử dụng Object.defineProperty nếu có) là:

  1. toChunks

Một số thử nghiệm đã được đưa vào để kiểm tra chức năng.

Lo lắng rằng độ dài của mã sẽ ảnh hưởng đến hiệu suất? Không cần phải lo lắng, http://jsperf.com/chunk-opes/3

Phần lớn mã bổ sung là để chắc chắn rằng mã sẽ phản hồi giống nhau trên nhiều môi trường javascript.

/*jslint maxlen:80, browser:true, devel:true */

/*
 * Properties used by toChunks.
 */

/*property
    MAX_SAFE_INTEGER, abs, ceil, configurable, defineProperty, enumerable,
    floor, length, max, min, pow, prototype, slice, toChunks, value,
    writable
*/

/*
 * Properties used in the testing of toChunks implimentation.
 */

/*property
    appendChild, createTextNode, floor, fromCharCode, getElementById, length,
    log, pow, push, random, toChunks
*/

(function () {
    'use strict';

    var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1;

    /**
     * Defines a new property directly on an object, or modifies an existing
     * property on an object, and returns the object.
     *
     * @private
     * @function
     * @param {Object} object
     * @param {string} property
     * @param {Object} descriptor
     * @return {Object}
     * @see https://goo.gl/CZnEqg
     */
    function $defineProperty(object, property, descriptor) {
        if (Object.defineProperty) {
            Object.defineProperty(object, property, descriptor);
        } else {
            object[property] = descriptor.value;
        }

        return object;
    }

    /**
     * Returns true if the operands are strictly equal with no type conversion.
     *
     * @private
     * @function
     * @param {*} a
     * @param {*} b
     * @return {boolean}
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.4
     */
    function $strictEqual(a, b) {
        return a === b;
    }

    /**
     * Returns true if the operand inputArg is undefined.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @return {boolean}
     */
    function $isUndefined(inputArg) {
        return $strictEqual(typeof inputArg, 'undefined');
    }

    /**
     * The abstract operation throws an error if its argument is a value that
     * cannot be converted to an Object, otherwise returns the argument.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be tested.
     * @throws {TypeError} If inputArg is null or undefined.
     * @return {*} The inputArg if coercible.
     * @see https://goo.gl/5GcmVq
     */
    function $requireObjectCoercible(inputArg) {
        var errStr;

        if (inputArg === null || $isUndefined(inputArg)) {
            errStr = 'Cannot convert argument to object: ' + inputArg;
            throw new TypeError(errStr);
        }

        return inputArg;
    }

    /**
     * The abstract operation converts its argument to a value of type string
     *
     * @private
     * @function
     * @param {*} inputArg
     * @return {string}
     * @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tostring
     */
    function $toString(inputArg) {
        var type,
            val;

        if (inputArg === null) {
            val = 'null';
        } else {
            type = typeof inputArg;
            if (type === 'string') {
                val = inputArg;
            } else if (type === 'undefined') {
                val = type;
            } else {
                if (type === 'symbol') {
                    throw new TypeError('Cannot convert symbol to string');
                }

                val = String(inputArg);
            }
        }

        return val;
    }

    /**
     * Returns a string only if the arguments is coercible otherwise throws an
     * error.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @throws {TypeError} If inputArg is null or undefined.
     * @return {string}
     */
    function $onlyCoercibleToString(inputArg) {
        return $toString($requireObjectCoercible(inputArg));
    }

    /**
     * The function evaluates the passed value and converts it to an integer.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be converted to an integer.
     * @return {number} If the target value is NaN, null or undefined, 0 is
     *                   returned. If the target value is false, 0 is returned
     *                   and if true, 1 is returned.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
     */
    function $toInteger(inputArg) {
        var number = +inputArg,
            val = 0;

        if ($strictEqual(number, number)) {
            if (!number || number === Infinity || number === -Infinity) {
                val = number;
            } else {
                val = (number > 0 || -1) * Math.floor(Math.abs(number));
            }
        }

        return val;
    }

    /**
     * The abstract operation ToLength converts its argument to an integer
     * suitable for use as the length of an array-like object.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be converted to a length.
     * @return {number} If len <= +0 then +0 else if len is +INFINITY then
     *                   2^53-1 else min(len, 2^53-1).
     * @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
     */
    function $toLength(inputArg) {
        return Math.min(Math.max($toInteger(inputArg), 0), MAX_SAFE_INTEGER);
    }

    if (!String.prototype.toChunks) {
        /**
         * This method chunks a string into an array of strings of a specified
         * chunk size.
         *
         * @function
         * @this {string} The string to be chunked.
         * @param {Number} chunkSize The size of the chunks that the string will
         *                           be chunked into.
         * @returns {Array} Returns an array of the chunked string.
         */
        $defineProperty(String.prototype, 'toChunks', {
            enumerable: false,
            configurable: true,
            writable: true,
            value: function (chunkSize) {
                var str = $onlyCoercibleToString(this),
                    chunkLength = $toInteger(chunkSize),
                    chunked = [],
                    numChunks,
                    length,
                    index,
                    start,
                    end;

                if (chunkLength < 1) {
                    return chunked;
                }

                length = $toLength(str.length);
                numChunks = Math.ceil(length / chunkLength);
                index = 0;
                start = 0;
                end = chunkLength;
                chunked.length = numChunks;
                while (index < numChunks) {
                    chunked[index] = str.slice(start, end);
                    start = end;
                    end += chunkLength;
                    index += 1;
                }

                return chunked;
            }
        });
    }
}());

/*
 * Some tests
 */

(function () {
    'use strict';

    var pre = document.getElementById('out'),
        chunkSizes = [],
        maxChunkSize = 512,
        testString = '',
        maxTestString = 100000,
        chunkSize = 0,
        index = 1;

    while (chunkSize < maxChunkSize) {
        chunkSize = Math.pow(2, index);
        chunkSizes.push(chunkSize);
        index += 1;
    }

    index = 0;
    while (index < maxTestString) {
        testString += String.fromCharCode(Math.floor(Math.random() * 95) + 32);
        index += 1;
    }

    function log(result) {
        pre.appendChild(document.createTextNode(result + '\n'));
    }

    function test() {
        var strLength = testString.length,
            czLength = chunkSizes.length,
            czIndex = 0,
            czValue,
            result,
            numChunks,
            pass;

        while (czIndex < czLength) {
            czValue = chunkSizes[czIndex];
            numChunks = Math.ceil(strLength / czValue);
            result = testString.toChunks(czValue);
            czIndex += 1;
            log('chunksize: ' + czValue);
            log(' Number of chunks:');
            log('  Calculated: ' + numChunks);
            log('  Actual:' + result.length);
            pass = result.length === numChunks;
            log(' First chunk size: ' + result[0].length);
            pass = pass && result[0].length === czValue;
            log(' Passed: ' + pass);
            log('');
        }
    }

    test();
    log('');
    log('Simple test result');
    log('abcdefghijklmnopqrstuvwxyz'.toChunks(3));
}());
<pre id="out"></pre>


0

Sử dụng phương thức lát ():

function returnChunksArray(str, chunkSize) {
  var arr = [];
  while(str !== '') {
    arr.push(str.slice(0, chunkSize));
    str = str.slice(chunkSize);
  }
  return arr;
}

Điều tương tự có thể được thực hiện bằng cách sử dụng phương thức chuỗi con ().

function returnChunksArray(str, chunkSize) {
  var arr = [];
  while(str !== '') {
    arr.push(str.substring(0, chunkSize));
    str = str.substring(chunkSize);
  }
  return arr;
}

0

Vấn đề của tôi với giải pháp trên là nó mang chuỗi thành các khối kích thước chính thức bất kể vị trí trong các câu.

Tôi nghĩ rằng sau đây một cách tiếp cận tốt hơn; mặc dù nó cần một số điều chỉnh hiệu năng:

 static chunkString(str, length, size,delimiter='\n' ) {
        const result = [];
        for (let i = 0; i < str.length; i++) {
            const lastIndex = _.lastIndexOf(str, delimiter,size + i);
            result.push(str.substr(i, lastIndex - i));
            i = lastIndex;
        }
        return result;
    }

0

Bạn chắc chắn có thể làm một cái gì đó như

let pieces = "1234567890 ".split(/(.{2})/).filter(x => x.length == 2);

để có được điều này:

[ '12', '34', '56', '78', '90' ]

Nếu bạn muốn tự động nhập / điều chỉnh kích thước khối để các khối có kích thước n, bạn có thể làm điều này:

n = 2;
let pieces = "1234567890 ".split(new RegExp("(.{"+n.toString()+"})")).filter(x => x.length == n);

Để tìm tất cả các khối n kích thước có thể có trong chuỗi gốc, hãy thử điều này:

let subs = new Set();
let n = 2;
let str = "1234567890 ";
let regex = new RegExp("(.{"+n.toString()+"})");     //set up regex expression dynamically encoded with n

for (let i = 0; i < n; i++){               //starting from all possible offsets from position 0 in the string
    let pieces = str.split(regex).filter(x => x.length == n);    //divide the string into chunks of size n...
    for (let p of pieces)                 //...and add the chunks to the set
        subs.add(p);
    str = str.substr(1);    //shift the string reading frame
}

Bạn nên kết thúc với:

[ '12', '23', '34', '45', '56', '67', '78', '89', '90', '0 ' ]

-1
    window.format = function(b, a) {
        if (!b || isNaN(+a)) return a;
        var a = b.charAt(0) == "-" ? -a : +a,
            j = a < 0 ? a = -a : 0,
            e = b.match(/[^\d\-\+#]/g),
            h = e && e[e.length - 1] || ".",
            e = e && e[1] && e[0] || ",",
            b = b.split(h),
            a = a.toFixed(b[1] && b[1].length),
            a = +a + "",
            d = b[1] && b[1].lastIndexOf("0"),
            c = a.split(".");
        if (!c[1] || c[1] && c[1].length <= d) a = (+a).toFixed(d + 1);
        d = b[0].split(e);
        b[0] = d.join("");
        var f = b[0] && b[0].indexOf("0");
        if (f > -1)
            for (; c[0].length < b[0].length - f;) c[0] = "0" + c[0];
        else +c[0] == 0 && (c[0] = "");
        a = a.split(".");
        a[0] = c[0];
        if (c = d[1] && d[d.length -
                1].length) {
            for (var d = a[0], f = "", k = d.length % c, g = 0, i = d.length; g < i; g++) f += d.charAt(g), !((g - k + 1) % c) && g < i - c && (f += e);
            a[0] = f
        }
        a[1] = b[1] && a[1] ? h + a[1] : "";
        return (j ? "-" : "") + a[0] + a[1]
    };

var str="1234567890";
var formatstr=format( "##,###.", str);
alert(formatstr);


This will split the string in reverse order with comma separated after 3 char's. If you want you can change the position.

-1

Điều gì về đoạn mã nhỏ này:

function splitME(str, size) {
    let subStr = new RegExp('.{1,' + size + '}', 'g');
    return str.match(subStr);
};

-2
function chunkString(str, length = 10) {
    let result = [],
        offset = 0;
    if (str.length <= length) return result.push(str) && result;
    while (offset < str.length) {
        result.push(str.substr(offset, length));
        offset += length;
    }
    return result;
}

4
Câu trả lời của bạn không thêm bất cứ điều gì mới (so với các câu trả lời khác) và thiếu bất kỳ mô tả nào như các câu trả lời khác.
nổi
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.