Séparer une grande chaîne en morceaux de taille n en JavaScript


210

Je voudrais diviser une très grosse chaîne (disons 10 000 caractères) en morceaux de taille N.

Quelle serait la meilleure façon en termes de performances de le faire?

Par exemple: "1234567890"divisé par 2 deviendrait ["12", "34", "56", "78", "90"].

Est-ce que quelque chose comme cela serait possible String.prototype.matchet si oui, serait-ce la meilleure façon de le faire en termes de performances?

Réponses:


458

Vous pouvez faire quelque chose comme ça:

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

La méthode fonctionnera toujours avec des chaînes dont la taille n'est pas un multiple exact de la taille de bloc:

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

En général, pour toute chaîne dont vous voulez extraire au plus n sous-chaînes, vous feriez:

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

Si votre chaîne peut contenir des retours à la ligne ou des retours chariot, vous feriez:

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

En ce qui concerne les performances, j'ai essayé cela avec environ 10 000 caractères et cela a pris un peu plus d'une seconde sur Chrome. YMMV.

Cela peut également être utilisé dans une fonction réutilisable:

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

8
Comme cette réponse a maintenant près de 3 ans, je voulais réessayer le test de performance réalisé par @Vivin. Donc, pour info, le fractionnement de 100k caractères deux à deux en utilisant l'expression régulière donnée est instantané sur Chrome v33.
aymericbeaumet

1
@Fmstrat Que voulez-vous dire par "si votre chaîne contient des espaces, elle ne compte pas dans la longueur"? Oui, .ne correspond pas du tout à la nouvelle ligne. Je vais mettre à jour la réponse pour qu'il prenne \net \ren compte.
Vivin Paliath

2
Quelque chose comme var chunks = str.split("").reverse().join().match(/.{1, 4}/).map(function(s) { return s.split("").reverse().join(); });. Cela se fait en morceaux de 4. Je ne sais pas trop ce que vous entendez par «moins ou plus». Gardez à l'esprit que cela ne fonctionnera pas en général, en particulier avec les chaînes qui contiennent des caractères de combinaison et peuvent également casser les chaînes Unicode.
Vivin Paliath

2
Selon developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… vous pouvez faire correspondre n'importe quel caractère, y compris les nouvelles lignes, avec [^]. Avec cela, votre exemple se traduirait parstr.match(/[^]{1,n}/g)
Francesc Rosas

1
Pour ceux qui recherchent un découpage de cordes très rapide avec des références de performance sur jsperf, voir ma réponse . L'utilisation d'une expression régulière est la méthode de découpage la plus lente de toutes.
Justin Warkentin

34

Conclusion:

  • matchest très inefficace, slicec'est mieux, sur Firefox substr/ substringc'est encore mieux
  • match est encore plus inefficace pour les chaînes courtes (même avec l'expression régulière mise en cache - probablement en raison du temps d'installation de l'analyse syntaxique de l'expression régulière)
  • match est encore plus inefficace pour les gros morceaux (probablement en raison de l'impossibilité de "sauter")
  • pour les chaînes plus longues avec une taille de bloc très petite, matchsurpasse slicesur les anciens IE mais perd toujours sur tous les autres systèmes
  • roches jsperf

34

J'ai créé plusieurs variantes plus rapides que vous pouvez voir sur jsPerf . Mon préféré est le suivant:

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
donc cela a fonctionné fabuleusement sur de longues cordes (environ 800k - 9m caractères) sauf quand j'ai mis la taille à 20 pour une raison quelconque, le dernier morceau n'a pas été retourné ... comportement très bizarre.
David

1
@DavidAnderton Bonne prise. Je l'ai corrigé et, curieusement, il semble fonctionner encore plus vite. Il arrondissait quand il aurait dû le faire Math.ceil()pour déterminer le nombre correct de morceaux.
Justin Warkentin

1
Merci! Je l'ai assemblé en tant que module NPM avec le support Unicode en option - github.com/vladgolubev/fast-chunk-string
Vlad Holubiev

19

Il s'agit d'une solution rapide et simple -

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
Vous devez utiliser à la substr()place de substring().
Leif

2
Je suis curieux, pourquoi les soulignements dans les noms de variables?
Felipe Valdes

@FelipeValdes Je suppose de ne pas les confondre avec des variables globales / paramètres ou de les désigner comme ayant une portée privée.
M. Polywhirl

15

Surprise! Vous pouvez utiliser le fractionnement pour diviser.

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

Résulte en [ '12', '34', '56', '78', '90', ' ' ]


C'est la réponse la plus géniale.
galkin

2
À quoi ça sert filter (o=>o)?
Ben Carp

2
l'expression régulière crée des éléments de tableau vides entre les morceaux. filter(x=>x)est utilisé pour filtrer ces éléments vides
artemdev

Court et intelligent mais itère plusieurs fois sur l'entrée. Cette réponse est plus de 4x plus lente que les autres solutions de ce fil.
Merci

6
@BenCarp C'est l'opérateur de la moto. Cela accélère. ;)
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

J'ai écrit une fonction étendue, donc la longueur du bloc peut également être un tableau de nombres, comme [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;
};

Le code

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

retournera:

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

4

il divise la grande chaîne en petites chaînes de mots donnés .

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

J'utiliserais une expression régulière ...

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

Appelez-le au besoin

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

1

Voici une solution que j'ai trouvée pour les chaînes de modèle après un peu d'expérimentation:

Usage:

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

Vous pouvez utiliser reduce()sans aucune expression régulière:

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

Je pense que cela vous aidera beaucoup si vous fournissez des exemples sur la façon d'utiliser votre reduceméthode.
kiatng

0

Sous la forme d'une fonction prototype:

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

0

Voici le code que j'utilise, il utilise String.prototype.slice .

Oui, la réponse est assez longue car elle essaie de suivre les normes actuelles aussi près que possible et contient bien sûr un nombre raisonnable de commentaires JSDOC . Cependant, une fois minifié, le code n'est que de 828 octets et une fois compressé pour la transmission, il n'est que de 497 octets.

La 1 méthode à laquelle cela s'ajoute String.prototype(en utilisant Object.defineProperty si disponible) est:

  1. toChunks

Un certain nombre de tests ont été inclus pour vérifier la fonctionnalité.

Vous craignez que la longueur du code n'affecte les performances? Pas besoin de vous inquiéter, http://jsperf.com/chunk-string/3

Une grande partie du code supplémentaire est là pour être sûr que le code répondra de la même manière dans plusieurs environnements 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

En utilisant la méthode slice ():

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

La même chose peut être faite en utilisant la méthode substring ().

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

0

Mon problème avec la solution ci-dessus est qu'elle porte la chaîne en morceaux de taille formelle quelle que soit la position dans les phrases.

Je pense que ce qui suit une meilleure approche; bien qu'il ait besoin d'un ajustement des performances:

 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

Vous pouvez certainement faire quelque chose comme

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

pour obtenir ceci:

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

Si vous souhaitez saisir / ajuster dynamiquement la taille de bloc de sorte que les blocs soient de taille n, vous pouvez le faire:

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

Pour trouver tous les morceaux de taille n possibles dans la chaîne d'origine, essayez ceci:

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
}

Vous devriez vous retrouver avec:

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

Qu'en est-il de ce petit morceau de 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
Votre réponse n'ajoute rien de nouveau (par rapport aux autres réponses) et manque de description comme les autres réponses.
flob
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.