Pisahkan string besar dalam potongan ukuran-n dalam JavaScript

208

Saya ingin membagi string yang sangat besar (katakanlah, 10.000 karakter) menjadi potongan N-size.

Apa yang akan menjadi cara terbaik dalam hal kinerja untuk melakukan ini?

Misalnya: "1234567890"dibagi 2 akan menjadi ["12", "34", "56", "78", "90"].

Apakah hal seperti ini dimungkinkan menggunakan String.prototype.matchdan jika demikian, apakah itu cara terbaik untuk melakukannya dalam hal kinerja?

suku84
sumber

Jawaban:

456

Anda dapat melakukan sesuatu seperti ini:

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

Metode ini masih akan bekerja dengan string yang ukurannya bukan kelipatan pasti dari ukuran chunk:

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

Secara umum, untuk string apa pun yang ingin Anda ekstrak paling banyak substring n- berukuran, Anda akan melakukan:

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

Jika string Anda dapat berisi baris baru atau carriage return, Anda akan melakukannya:

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

Sejauh kinerja, saya mencoba ini dengan sekitar 10 ribu karakter dan butuh sedikit lebih dari satu detik di Chrome. YMMV.

Ini juga dapat digunakan dalam fungsi yang dapat digunakan kembali:

function chunkString(str, length) {
  return str.match(new RegExp('.{1,' + length + '}', 'g'));
}
Vivin Paliath
sumber
8
Karena jawaban ini sekarang hampir 3 tahun, saya ingin mencoba tes kinerja yang dibuat oleh @Vivin lagi. Jadi FYI, membagi 100k karakter dua demi dua menggunakan regex yang diberikan adalah instan pada Chrome v33.
aymericbeaumet
1
@Fmstrat Apa yang Anda maksud dengan "jika string Anda berisi spasi, panjangnya tidak dihitung"? Ya, sama .sekali tidak cocok dengan baris baru. Saya akan memperbarui jawabannya sehingga perlu \ndan \rdiperhitungkan.
Vivin Paliath
2
Sesuatu seperti var chunks = str.split("").reverse().join().match(/.{1, 4}/).map(function(s) { return s.split("").reverse().join(); });. Ini melakukannya dalam potongan 4. Saya tidak yakin apa yang Anda maksud dengan "kurang atau lebih". Ingatlah bahwa ini tidak akan berfungsi secara umum, terutama dengan string yang berisi kombinasi karakter dan juga dapat merusak string Unicode.
Vivin Paliath
2
Menurut developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Anda dapat mencocokkan karakter apa pun, termasuk baris baru, dengan [^]. Dengan ini contoh Anda akan menghasilkanstr.match(/[^]{1,n}/g)
Francesc Rosas
1
Bagi siapa pun yang mencari chunking string yang sangat cepat dengan tolok ukur kinerja di jsperf, lihat jawaban saya . Menggunakan regex adalah metode chunking yang paling lambat.
Justin Warkentin
34

Saya membuat beberapa varian lebih cepat yang dapat Anda lihat di jsPerf . Yang favorit saya adalah ini:

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
}
Justin Warkentin
sumber
2
jadi ini bekerja sangat baik pada string panjang (sekitar 800r - 9m karakter) kecuali ketika saya mengatur ukuran ke 20 untuk beberapa alasan potongan terakhir tidak dikembalikan ... perilaku yang sangat aneh.
David
1
@ David Jelas tangkapan yang bagus. Saya memperbaikinya dan menariknya tampaknya berjalan lebih cepat. Itu pembulatan ketika seharusnya dilakukan Math.ceil()untuk menentukan jumlah potongan yang benar.
Justin Warkentin
1
Terima kasih! Saya menyatukannya sebagai modul NPM dengan dukungan Unicode opsional - github.com/vladgolubev/fast-chunk-string
Vlad Holubiev
33

Intinya:

  • matchsangat tidak efisien, slicelebih baik, di Firefox substr/ substringmasih lebih baik
  • match bahkan lebih tidak efisien untuk string pendek (bahkan dengan regex di-cache - mungkin karena waktu setup parsing regex)
  • match bahkan lebih tidak efisien untuk ukuran bongkahan besar (mungkin karena ketidakmampuan untuk "melompat")
  • untuk string yang lebih panjang dengan ukuran chunk yang sangat kecil, matchmengungguli sliceIE yang lebih lama tetapi masih kalah pada semua sistem lainnya
  • batu jsperf
Tgr
sumber
19

Ini adalah solusi yang cepat dan mudah -

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", ... ]

Terima kasih
sumber
1
Anda harus menggunakan substr()sebagai gantinya substring().
Leif
2
Saya ingin tahu, mengapa garis bawah pada nama variabel?
Felipe Valdes
@FelipeValdes Saya berasumsi untuk tidak membingungkan mereka dengan variabel global / parameter atau untuk menyatakannya sebagai lingkup pribadi.
Tn. Polywhirl
15

Mengherankan! Anda dapat menggunakan split untuk membagi.

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

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

Fozi
sumber
Ini adalah jawaban yang paling menakjubkan.
Galkin
2
Untuk apa filter (o=>o)?
Ben Carp
2
regex saat ini membuat elemen array kosong antara potongan. filter(x=>x)digunakan untuk menyaring elemen
artemdev
Pendek dan pintar tapi berulang kali input. Jawaban ini lebih dari 4x lebih lambat dari solusi lain di utas ini.
Terima kasih
6
@ BenCarp Ini adalah operator sepeda motor. Itu membuatnya lebih cepat. ;)
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
FishBasketGordo
sumber
5

Saya telah menulis fungsi yang diperluas, jadi panjang chunk juga bisa berupa array angka, seperti [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;
};

Kode

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

akan kembali:

[ '1', '234', '5', '678', '9', '012', '3' ]
Egon Schmid
sumber
4

itu string besar Split ke string kecil kata-kata yang diberikan .

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;
}
Haseeb
sumber
3
var l = str.length, lc = 0, chunks = [], c = 0, chunkSize = 2;
for (; lc < l; c++) {
  chunks[c] = str.slice(lc, lc += chunkSize);
}
Poetro
sumber
2

Saya akan menggunakan regex ...

var chunkStr = function(str, chunkLength) {
    return str.match(new RegExp('[\\s\\S]{1,' + +chunkLength + '}', 'g'));
}
alex
sumber
1
const getChunksFromString = (str, chunkSize) => {
    var regexChunk = new RegExp(`.{1,${chunkSize}}`, 'g')   // '.' represents any character
    return str.match(regexChunk)
}

Sebut saja sesuai kebutuhan

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

Inilah solusi yang saya buat untuk string templat setelah sedikit bereksperimen:

Pemakaian:

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

Alex W
sumber
1

Anda dapat menggunakan reduce()tanpa regex apa pun:

(str, n) => {
  return str.split('').reduce(
    (acc, rec, index) => {
      return ((index % n) || !(index)) ? acc.concat(rec) : acc.concat(',', rec)
    },
    ''
  ).split(',')
}
kode bitter
sumber
Saya pikir itu akan banyak membantu jika Anda akan memberikan contoh tentang cara menggunakan reducemetode Anda .
kiat
0

Dalam bentuk fungsi prototipe:

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

Berikut adalah kode yang saya gunakan, menggunakan String.prototype.slice .

Ya itu cukup lama karena jawabannya mencoba untuk mengikuti standar saat ini sedekat mungkin dan tentu saja mengandung jumlah JSDOC yang masuk akal komentar . Namun, setelah diperkecil, kodenya hanya 828 byte dan sekali di-gzip untuk transmisi hanya 497 byte.

Metode 1 yang ditambahkan ini String.prototype(menggunakan Object.defineProperty jika tersedia) adalah:

  1. toChunks

Sejumlah tes telah disertakan untuk memeriksa fungsionalitasnya.

Khawatir panjang kode akan memengaruhi kinerja? Tidak perlu khawatir, http://jsperf.com/chunk-string/3

Sebagian besar kode tambahan ada untuk memastikan bahwa kode akan merespons yang sama di beberapa lingkungan 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>

Xotic750
sumber
0

Menggunakan metode slice ():

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

Hal yang sama dapat dilakukan dengan menggunakan metode substring ().

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

Masalah saya dengan solusi di atas adalah bahwa ia memasukkan string ke dalam potongan ukuran formal terlepas dari posisi dalam kalimat.

Saya pikir berikut ini pendekatan yang lebih baik; meskipun perlu beberapa penyesuaian kinerja:

 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;
    }
Ahmad Alhyari
sumber
0

Anda pasti dapat melakukan sesuatu seperti

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

untuk mendapatkan ini:

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

Jika Anda ingin secara dinamis memasukkan / menyesuaikan ukuran chunk sehingga chunks berukuran n, Anda dapat melakukan ini:

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

Untuk menemukan semua ukuran yang mungkin dan potongan dalam string asli, coba ini:

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
}

Anda harus berakhir dengan:

[ '12', '23', '34', '45', '56', '67', '78', '89', '90', '0 ' ]
Adrian
sumber
-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.
Murali Krish
sumber
-1

Bagaimana dengan sepotong kecil kode ini:

function splitME(str, size) {
    let subStr = new RegExp('.{1,' + size + '}', 'g');
    return str.match(subStr);
};
Hari
sumber
-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;
}
Alexandr Stepanov
sumber
4
Jawaban Anda tidak menambahkan sesuatu yang baru (dibandingkan dengan jawaban lain) dan tidak memiliki deskripsi seperti jawaban lainnya.
flob