Teilen Sie große Zeichenfolgen in JavaScript in n-großen Blöcken auf


208

Ich möchte eine sehr große Zeichenfolge (sagen wir 10.000 Zeichen) in N-große Teile aufteilen.

Was wäre in Bezug auf die Leistung der beste Weg, dies zu tun?

Zum Beispiel: "1234567890"geteilt durch 2 würde werden ["12", "34", "56", "78", "90"].

Wäre so etwas möglich String.prototype.matchund wenn ja, wäre dies der beste Weg, dies in Bezug auf die Leistung zu tun?

Antworten:


456

Sie können so etwas tun:

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

Die Methode funktioniert weiterhin mit Zeichenfolgen, deren Größe kein genaues Vielfaches der Blockgröße ist:

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

Im Allgemeinen würden Sie für jede Zeichenfolge, aus der Sie höchstens Teilzeichenfolgen mit n Größen extrahieren möchten, Folgendes tun:

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

Wenn Ihre Zeichenfolge Zeilenumbrüche oder Zeilenumbrüche enthalten kann, gehen Sie wie folgt vor:

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

Was die Leistung angeht, habe ich dies mit ungefähr 10.000 Zeichen ausprobiert und es hat in Chrome etwas mehr als eine Sekunde gedauert. YMMV.

Dies kann auch in einer wiederverwendbaren Funktion verwendet werden:

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

8
Da diese Antwort jetzt fast 3 Jahre alt ist, wollte ich den Leistungstest von @Vivin erneut versuchen. Zu Ihrer Information: In Chrome v33 erfolgt die sofortige Aufteilung von 100.000 Zeichen durch den angegebenen regulären Ausdruck sofort.
Aymericbeaumet

1
@Fmstrat Was meinst du mit "Wenn dein String Leerzeichen enthält, zählt er nicht in der Länge"? Ja, .stimmt überhaupt nicht mit Zeilenumbruch überein. Ich werde die Antwort aktualisieren , so dass es dauert \nund \rzu berücksichtigen.
Vivin Paliath

2
So etwas wie var chunks = str.split("").reverse().join().match(/.{1, 4}/).map(function(s) { return s.split("").reverse().join(); });. Dies geschieht in 4er-Stücken. Ich bin mir nicht sicher, was Sie unter "weniger oder mehr" verstehen. Beachten Sie, dass dies im Allgemeinen nicht funktioniert, insbesondere bei Zeichenfolgen, die kombinierte Zeichen enthalten und auch Unicode-Zeichenfolgen beschädigen können.
Vivin Paliath

2
Laut developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… können Sie jedes Zeichen, einschließlich neuer Zeilen, mit abgleichen [^]. Damit würde Ihr Beispiel zustr.match(/[^]{1,n}/g)
Francesc Rosas

1
Für alle, die auf der Suche nach wirklich schnellem String-Chunking mit Leistungsbenchmarks auf jsperf sind, siehe meine Antwort . Die Verwendung eines regulären Ausdrucks ist die langsamste Chunking-Methode von allen.
Justin Warkentin

34

Ich habe mehrere schnellere Varianten erstellt, die Sie auf jsPerf sehen können . Mein Favorit ist:

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
Das hat bei langen Saiten (ca. 800k - 9m Zeichen) hervorragend funktioniert, außer wenn ich aus irgendeinem Grund die Größe auf 20 eingestellt habe, wurde der letzte Block nicht zurückgegeben ... sehr seltsames Verhalten.
David

1
@ DavidAnderton Guter Fang. Ich habe es behoben und interessanterweise scheint es noch schneller zu laufen. Es wurde gerundet, wann es hätte sein sollen Math.ceil(), um die richtige Anzahl von Stücken zu bestimmen.
Justin Warkentin

1
Vielen Dank! Ich habe sein Modul als NPM-Modul mit optionaler Unicode-Unterstützung zusammengestellt - github.com/vladgolubev/fast-chunk-string
Vlad Holubiev

33

Endeffekt:

  • matchist sehr ineffizient, sliceist besser, auf Firefox substr/ substringist noch besser
  • match ist für kurze Zeichenfolgen noch ineffizienter (selbst bei zwischengespeicherten regulären Ausdrücken - wahrscheinlich aufgrund der Einrichtungszeit für die Analyse von regulären Ausdrücken)
  • match ist für große Chunks noch ineffizienter (wahrscheinlich aufgrund der Unfähigkeit zu "springen")
  • Bei längeren Zeichenfolgen mit sehr kleiner Blockgröße matchübertrifft es den sliceälteren IE, verliert jedoch auf allen anderen Systemen
  • jsperf rockt

19

Dies ist eine schnelle und unkomplizierte Lösung -

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
Sie müssen substr()anstelle von verwenden substring().
Leif

2
Ich bin gespannt, warum die Unterstriche in den Variablennamen?
Felipe Valdes

@FelipeValdes Ich gehe davon aus, sie nicht mit globalen / Parametervariablen zu verwechseln oder sie als privat zu bezeichnen.
Mr. Polywhirl

15

Überraschung! Sie können split verwenden, um zu teilen.

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

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


Dies ist die beeindruckendste Antwort.
Galkin

2
Wofür ist filter (o=>o)?
Ben Carp

2
Der aktuelle reguläre Ausdruck erstellt leere Array-Elemente zwischen Chunks. filter(x=>x)wird verwendet, um diese leeren Elemente
herauszufiltern

Kurz und clever, aber wiederholt die Eingabe mehrmals. Diese Antwort ist mehr als viermal langsamer als andere Lösungen in diesem Thread.
Danke

6
@ BenCarp Es ist der Motorradfahrer. Es macht es schneller. ;)
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

Ich habe eine erweiterte Funktion geschrieben, daher kann die Blocklänge auch ein Array von Zahlen sein, wie [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;
};

Der Code

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

wird zurückkehren:

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

4

Es teilt die große Zeichenfolge in kleine Zeichenfolgen bestimmter Wörter auf .

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

Ich würde einen regulären Ausdruck verwenden ...

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

Nennen Sie es nach Bedarf

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

1

Hier ist eine Lösung, die ich nach ein wenig Experimentieren für Vorlagenzeichenfolgen gefunden habe:

Verwendung:

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

Sie können reduce()ohne regulären Ausdruck verwenden:

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

Ich denke, es wird sehr hilfreich sein, wenn Sie Beispiele für die Verwendung Ihrer reduceMethode liefern .
Kiatng

0

In Form einer Prototypfunktion:

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

0

Hier ist der Code, den ich verwende, er verwendet String.prototype.slice .

Ja, es dauert ziemlich lange, bis eine Antwort vorliegt, da versucht wird, den aktuellen Standards so nahe wie möglich zu kommen, und natürlich eine angemessene Anzahl von JSDOC- Kommentaren enthält. Einmal minimiert, beträgt der Code jedoch nur 828 Bytes und einmal für die Übertragung komprimiert sind es nur 497 Bytes.

Die 1-Methode, die hierdurch hinzugefügt wirdString.prototype (unter Verwendung von Object.defineProperty , sofern verfügbar), ist:

  1. toChunks

Eine Reihe von Tests wurde aufgenommen, um die Funktionalität zu überprüfen.

Befürchten Sie, dass die Länge des Codes die Leistung beeinträchtigt? Kein Grund zur Sorge, http://jsperf.com/chunk-string/3

Ein Großteil des zusätzlichen Codes dient dazu, sicherzustellen, dass der Code in mehreren Javascript-Umgebungen gleich reagiert.

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

Verwenden der Slice () -Methode:

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

Das gleiche kann mit der Methode substring () gemacht werden.

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

0

Mein Problem mit der obigen Lösung ist, dass sie die Zeichenfolge unabhängig von der Position in den Sätzen in Stücke von formaler Größe zerlegt.

Ich halte das Folgende für einen besseren Ansatz; obwohl es einige Leistungsverbesserungen erfordert:

 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

Sie können definitiv so etwas tun

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

um dies zu bekommen:

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

Wenn Sie die Blockgröße dynamisch eingeben / anpassen möchten, sodass die Blockgröße n beträgt, können Sie Folgendes tun:

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

Versuchen Sie Folgendes, um alle möglichen Chunks der Größe n in der Originalzeichenfolge zu finden:

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
}

Sie sollten am Ende haben mit:

[ '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

Was ist mit diesem kleinen Code:

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
Ihre Antwort fügt nichts Neues hinzu (im Vergleich zu den anderen Antworten) und enthält keine Beschreibung wie die anderen Antworten.
Flob
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.