Dividir una cadena grande en trozos de tamaño n en JavaScript

209

Me gustaría dividir una cadena muy grande (digamos, 10,000 caracteres) en trozos de tamaño N.

¿Cuál sería la mejor manera en términos de rendimiento para hacer esto?

Por ejemplo: "1234567890"dividir entre 2 se convertiría en["12", "34", "56", "78", "90"] .

¿Sería posible usar algo así String.prototype.matchy, de ser así, sería la mejor manera de hacerlo en términos de rendimiento?

tribu84
fuente

Respuestas:

457

Puedes hacer algo como esto:

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

El método seguirá funcionando con cadenas cuyo tamaño no es un múltiplo exacto del tamaño del fragmento:

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

En general, para cualquier cadena de la que desee extraer a lo sumo n subcadenas de tamaño n , haría lo siguiente:

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

Si su cadena puede contener nuevas líneas o retornos de carro, haría lo siguiente:

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

En cuanto al rendimiento, probé esto con aproximadamente 10k caracteres y me tomó un poco más de un segundo en Chrome. YMMV.

Esto también se puede usar en una función reutilizable:

function chunkString(str, length) {
  return str.match(new RegExp('.{1,' + length + '}', 'g'));
}
Vivin Paliath
fuente
8
Como esta respuesta ahora tiene casi 3 años, quería probar la prueba de rendimiento realizada por @Vivin nuevamente. Para su información, dividir 100k caracteres de dos en dos usando la expresión regular dada es instantáneo en Chrome v33.
aymericbeaumet
1
@Fmstrat ¿Qué quiere decir con "si su cadena contiene espacios, no cuenta en la longitud"? Sí, .no coincide con la nueva línea en absoluto. Voy a actualizar la respuesta por lo que se necesita \ny \ren cuenta.
Vivin Paliath
2
Algo así como var chunks = str.split("").reverse().join().match(/.{1, 4}/).map(function(s) { return s.split("").reverse().join(); });. Esto lo hace en trozos de 4. No estoy seguro de lo que quiere decir con "menos o más". Tenga en cuenta que esto no funcionará en general, especialmente con cadenas que contienen caracteres combinados y también pueden romper cadenas Unicode.
Vivin Paliath el
2
De acuerdo con developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , puede hacer coincidir cualquier carácter, incluidas las nuevas líneas, con [^]. Con esto su ejemplo resultaría enstr.match(/[^]{1,n}/g)
Francesc Rosas
1
Para cualquiera que busque un trozo de cadena realmente rápido con puntos de referencia de rendimiento en jsperf, vea mi respuesta . Usar una expresión regular es el método de fragmentación más lento de todos.
Justin Warkentin
34

Creé varias variantes más rápidas que puedes ver en jsPerf . Mi favorito es este:

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
fuente
2
así que esto funcionó fabulosamente en cadenas largas (alrededor de 800k - 9m caracteres) excepto cuando configuré el tamaño en 20 por alguna razón, el último fragmento no fue devuelto ... comportamiento muy extraño.
David
1
@DavidAnderton Buena captura. Lo arreglé y, curiosamente, parece correr aún más rápido. Estaba redondeando cuando debería haber estado haciendo Math.ceil()para determinar la cantidad correcta de fragmentos.
Justin Warkentin
1
¡Gracias! Lo armé
Vlad Holubiev
33

Línea de fondo:

  • matches muy ineficiente, slicees mejor, en Firefox substr/ substringes aún mejor
  • match es aún más ineficiente para cadenas cortas (incluso con expresiones regulares en caché, probablemente debido al tiempo de configuración del análisis de expresiones regulares)
  • match es aún más ineficiente para un gran tamaño de fragmento (probablemente debido a la incapacidad de "saltar")
  • para cadenas más largas con un tamaño de fragmento muy pequeño, matchsupera sliceel IE anterior pero aún pierde en todos los demás sistemas
  • rocas jsperf
Tgr
fuente
19

Esta es una solución rápida y sencilla:

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

Gracias
fuente
1
Tienes que usar en substr()lugar de substring().
Leif
2
Tengo curiosidad, ¿por qué los guiones bajos en los nombres de las variables?
Felipe Valdés
@FelipeValdes Supongo que no los confundiré con variables globales / de parámetros o denotarlos como de ámbito privado.
Sr. Polywhirl
15

¡Sorpresa! Puedes usar split para dividir.

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

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

Fozi
fuente
Esta es la respuesta más asombrosa.
galkin
2
Lo que es filter (o=>o)para?
Ben Carp
2
La expresión regular actual crea elementos de matriz vacíos entre fragmentos. filter(x=>x)se usa para filtrar esos elementos vacíos
artemdev
Corto e inteligente, pero itera sobre la entrada varias veces. Esta respuesta es más de 4 veces más lenta que otras soluciones en este hilo.
Gracias
66
@BenCarp Es el operador de motocicletas. Lo hace ir más rápido. ;)
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
PescadoCanasta
fuente
5

He escrito una función extendida, por lo que la longitud del fragmento también puede ser una matriz de números, como [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;
};

El código

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

volverá:

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

divide la cadena grande en cadenas pequeñas de palabras dadas .

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

Yo usaría una expresión regular ...

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

Llámalo según sea necesario

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

Aquí hay una solución que se me ocurrió para las cadenas de plantillas después de un poco de experimentación:

Uso:

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
fuente
1

Puede usar reduce()sin ninguna expresión regular:

(str, n) => {
  return str.split('').reduce(
    (acc, rec, index) => {
      return ((index % n) || !(index)) ? acc.concat(rec) : acc.concat(',', rec)
    },
    ''
  ).split(',')
}
código amargo
fuente
Creo que ayudaría mucho si proporcionara ejemplos sobre cómo usar su reducemétodo.
Jueves
0

En forma de una función prototipo:

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

Aquí está el código que estoy usando, usa String.prototype.slice .

Sí, la respuesta es bastante larga, ya que trata de seguir los estándares actuales lo más cerca posible y, por supuesto, contiene una cantidad razonable de JSDOC comentarios . Sin embargo, una vez minimizado, el código tiene solo 828 bytes y una vez comprimido para la transmisión, solo tiene 497 bytes.

El 1 método al que esto se agrega String.prototype(usando Object.defineProperty donde esté disponible) es:

  1. toChunks

Se han incluido varias pruebas para verificar la funcionalidad.

¿Le preocupa que la longitud del código afecte el rendimiento? No hay que preocuparse, http://jsperf.com/chunk-string/3

Gran parte del código adicional está ahí para asegurarse de que el código responderá de la misma manera en múltiples entornos 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
fuente
0

Usando el método slice ():

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

Lo mismo se puede hacer usando el método substring ().

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

Mi problema con la solución anterior es que lleva la cadena en trozos de tamaño formal independientemente de la posición en las oraciones.

Creo que el siguiente es un mejor enfoque; aunque necesita algunos ajustes de rendimiento:

 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
fuente
0

Definitivamente puedes hacer algo como

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

para obtener esto:

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

Si desea ingresar / ajustar dinámicamente el tamaño del fragmento para que los fragmentos sean de tamaño n, puede hacer esto:

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

Para encontrar todos los trozos de tamaño n posibles en la cadena original, intente esto:

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
}

Deberías terminar con:

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

¿Qué pasa con este pequeño código?

function splitME(str, size) {
    let subStr = new RegExp('.{1,' + size + '}', 'g');
    return str.match(subStr);
};
Hari
fuente
-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
fuente
44
Su respuesta no agrega nada nuevo (en comparación con las otras respuestas) y carece de una descripción como las otras respuestas.
flob