ultimo - Repetir cadena-Javascript
split javascript (28)
¿Cuál es el método mejor o más conciso para devolver una cadena repetida una cantidad arbitraria de veces?
El siguiente es mi mejor tiro hasta ahora:
function repeat(s, n){
var a = [];
while(a.length < n){
a.push(s);
}
return a.join('''');
}
Nota para los nuevos lectores: esta respuesta es antigua y no es terriblemente práctica, es simplemente "inteligente" porque utiliza cosas de Array para hacer cosas de String. Cuando escribí "menos proceso", definitivamente quise decir "menos código" porque, como otros han señalado en las respuestas posteriores, se comporta como un cerdo. Así que no lo uses si la velocidad te importa.
Yo pondría esta función en el objeto String directamente. En lugar de crear una matriz, rellenarla y unirla con un carácter vacío, simplemente cree una matriz de la longitud adecuada y únala a la cadena deseada. ¡El mismo resultado, menos proceso!
String.prototype.repeat = function( num )
{
return new Array( num + 1 ).join( this );
}
alert( "string to repeat/n".repeat( 4 ) );
ES2015
Se ha realizado este repeat()
método!
http://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.repeat
String.prototype.repeat
http://www.w3schools.com/jsref/jsref_repeat.asp
/**
* str: String
* count: Number
*/
const str = `hello repeat!/n`, count = 3;
let resultString = str.repeat(count);
console.log(`resultString = /n${resultString}`);
/*
resultString =
hello repeat!
hello repeat!
hello repeat!
*/
({ toString: () => ''abc'', repeat: String.prototype.repeat }).repeat(2);
// ''abcabc'' (repeat() is a generic method)
// Examples
''abc''.repeat(0); // ''''
''abc''.repeat(1); // ''abc''
''abc''.repeat(2); // ''abcabc''
''abc''.repeat(3.5); // ''abcabcabc'' (count will be converted to integer)
// ''abc''.repeat(1/0); // RangeError
// ''abc''.repeat(-1); // RangeError
Concatenación recursiva simple.
Solo quería darle un golpe, e hice esto:
function ditto( s, r, c ) {
return c-- ? ditto( s, r += s, c ) : r;
}
ditto( "foo", "", 128 );
No puedo decir que lo pensé mucho, y probablemente muestra :-)
Esto es posiblemente mejor
String.prototype.ditto = function( c ) {
return --c ? this + this.ditto( c ) : this;
};
"foo".ditto( 128 );
Y se parece mucho a una respuesta ya publicada, lo sé.
¿Pero por qué ser recursivo en absoluto?
¿Y qué tal un poco de comportamiento por defecto también?
String.prototype.ditto = function() {
var c = Number( arguments[ 0 ] ) || 2,
r = this.valueOf();
while ( --c ) {
r += this;
}
return r;
}
"foo".ditto();
Porque , aunque el método no recursivo manejará repeticiones arbitrariamente grandes sin alcanzar los límites de la pila de llamadas, es mucho más lento.
¿Por qué me molesté en agregar más métodos que no son tan inteligentes como los que ya se publicaron?
En parte para mi propia diversión, y en parte para señalar de la manera más simple que sé que hay muchas maneras de pelear a un gato, y dependiendo de la situación, es muy posible que el mejor método aparentemente no sea el ideal.
Un método relativamente rápido y sofisticado puede bloquearse y quemarse de manera efectiva en ciertas circunstancias, mientras que un método más lento y simple puede hacer el trabajo, eventualmente.
Algunos métodos pueden ser poco más que explota, y como tal propenso a ser fijo fuera de la existencia, y otros métodos pueden funcionar muy bien en todas las condiciones, pero están construidos de manera que uno simplemente tiene ni idea de cómo funciona.
"¿Y qué si no sé cómo funciona?"
¿Seriamente?
JavaScript sufre de una de sus mayores fortalezas; es muy tolerante con el mal comportamiento, y es tan flexible que se esforzará al máximo para obtener resultados, ¡cuando podría haber sido mejor para todos si se hubiera roto!
"Con gran poder, viene gran responsabilidad" ;-)
Pero más seriamente y más importante, aunque preguntas generales como esta conducen a una genialidad en forma de respuestas inteligentes que, si no más, amplían el conocimiento y los horizontes, al final, la tarea en cuestión, el guión práctico que utiliza el método resultante. Puede requerir un poco menos, o un poco más inteligente de lo que se sugiere.
Estos algoritmos "perfectos" son divertidos y todo, pero "una talla para todos" rara vez será mejor que hecho a medida.
Este sermón fue presentado por cortesía de la falta de sueño y de un interés pasajero. ¡Adelante y codifica!
Para todos los navegadores
Esto es tan conciso como es posible:
function repeat(s, n) { return new Array(n+1).join(s); }
Si también te importa el rendimiento, este es un enfoque mucho mejor:
function repeat(s, n) { var a=[],i=0;for(;i<n;)a[i++]=s;return a.join(''''); }
Si desea comparar el rendimiento de ambas opciones, vea este Fiddle y este Fiddle para las pruebas de referencia. Durante mis propias pruebas, la segunda opción fue aproximadamente 2 veces más rápida en Firefox y aproximadamente 4 veces más rápida en Chrome.
Solo para los navegadores modernos:
En los navegadores modernos, ahora también puedes hacer esto:
function repeat(s,n) { return s.repeat(n) };
Esta opción no solo es más corta que las otras dos opciones, sino que es incluso más rápida que la segunda opción.
Desafortunadamente, no funciona en ninguna versión de Internet Explorer. Los números en la tabla especifican la primera versión del navegador que soporta totalmente este método :
¡Buenas noticias! Se acepta String.prototype.repeat
para Harmony (ECMAScript 6) .
> "yo".repeat(2)
"yoyo"
El método está disponible en versiones recientes de V8, utilizadas por Node.js, Chrome ( String.repeat
compatible desde la versión 41) y Opera. Las nuevas versiones de Safari y Firefox también parecen tener soporte, pero Internet Explorer no. Para obtener una lista actualizada, consulte MDN: String.prototype.repeat> Compatibilidad del navegador .
String.prototype.repeat el siguiente polyfill:
if (!String.prototype.repeat) {
String.prototype.repeat = function(count) {
''use strict'';
if (this == null) {
throw new TypeError(''can/'t convert '' + this + '' to object'');
}
var str = '''' + this;
count = +count;
if (count != count) {
count = 0;
}
if (count < 0) {
throw new RangeError(''repeat count must be non-negative'');
}
if (count == Infinity) {
throw new RangeError(''repeat count must be less than infinity'');
}
count = Math.floor(count);
if (str.length == 0 || count == 0) {
return '''';
}
// Ensuring count is a 31-bit integer allows us to heavily optimize the
// main part. But anyway, most current (August 2014) browsers can''t handle
// strings 1 << 28 chars or longer, so:
if (str.length * count >= 1 << 28) {
throw new RangeError(''repeat count must not overflow maximum string size'');
}
var rpt = '''';
for (;;) {
if ((count & 1) == 1) {
rpt += str;
}
count >>>= 1;
if (count == 0) {
break;
}
str += str;
}
return rpt;
}
}
Ampliando la solución de P.Bailey :
String.prototype.repeat = function(num) {
return new Array(isNaN(num)? 1 : ++num).join(this);
}
De esta manera usted debe estar a salvo de tipos de argumentos inesperados
var foo = ''bar'';
alert(foo.repeat(3)); // Will work, "barbarbar"
alert(foo.repeat(''3'')); // Same as above
alert(foo.repeat(true)); // Same as foo.repeat(1)
alert(foo.repeat(0)); // This and all the following return an empty
alert(foo.repeat(false)); // string while not causing an exception
alert(foo.repeat(null));
alert(foo.repeat(undefined));
alert(foo.repeat({})); // Object
alert(foo.repeat(function () {})); // Function
EDITAR: Créditos a jerone por su idea elegante de ++num
!
Aquí hay una mejora del 5-7% en la respuesta de desfasada.
Desenrolle el bucle deteniéndose en count > 1
y realice un result += pattnern
adicional result += pattnern
concat después del bucle. Esto evitará que el pattern += pattern
final sin usar de los bucles pattern += pattern
sin tener que usar un costoso if-check El resultado final se vería así:
String.prototype.repeat = function(count) {
if (count < 1) return '''';
var result = '''', pattern = this.valueOf();
while (count > 1) {
if (count & 1) result += pattern;
count >>= 1, pattern += pattern;
}
result += pattern;
return result;
};
Y aquí está el violín desfigurado para la versión desenrollada: http://jsfiddle.net/wsdfg/
Este es bastante eficiente
String.prototype.repeat = function(times){
var result="";
var pattern=this;
while (times > 0) {
if (times&1)
result+=pattern;
times>>=1;
pattern+=pattern;
}
return result;
};
Este problema es un problema de optimización bien conocido / "clásico" para JavaScript, causado por el hecho de que las cadenas de JavaScript son "inmutables" y la adición por concatenación de incluso un solo carácter a una cadena requiere creación, incluida la asignación de memoria para y la copia a , toda una nueva cadena.
Desafortunadamente, la respuesta aceptada en esta página es incorrecta, donde "incorrecto" significa un factor de rendimiento de 3x para cadenas simples de un carácter, y 8x-97x para cadenas cortas repetidas más veces, hasta 300x para oraciones repetidas, e infinitamente incorrecto cuando tomando el límite de las relaciones de complejidad de los algoritmos a medida que n
va al infinito. Además, hay otra respuesta en esta página que es casi correcta (basada en una de las muchas generaciones y variaciones de la solución correcta que circula por Internet en los últimos 13 años). Sin embargo, esta solución "casi correcta" pierde un punto clave del algoritmo correcto que causa una degradación del rendimiento del 50%.
~ Octubre de 2000, publiqué un algoritmo para este problema exacto que fue ampliamente adaptado, modificado y, finalmente, poco conocido y olvidado. Para remediar este problema, en agosto de 2008 publiqué un artículo webreference.com/programming/javascript/jkm3/3.html explica el algoritmo y lo uso como un ejemplo de optimizaciones simples de JavaScript de propósito general. Por ahora, Web Reference ha borrado mi información de contacto e incluso mi nombre de este artículo. Y una vez más, el algoritmo ha sido ampliamente adaptado, modificado, luego poco comprendido y en gran parte olvidado.
Algoritmo original de repetición / multiplicación de cadenas por Joseph Myers, circa Y2K como función de multiplicación de texto dentro de Text.js; publicado en agosto de 2008 en esta forma por Referencia web: webreference.com/programming/javascript/jkm3/3.html (El artículo usó la función como un ejemplo de optimizaciones de JavaScript, que es la única para lo extraño nombre "stringFill3".
/*
* Usage: stringFill3("abc", 2) == "abcabc"
*/
function stringFill3(x, n) {
var s = '''';
for (;;) {
if (n & 1) s += x;
n >>= 1;
if (n) x += x;
else break;
}
return s;
}
Dos meses después de la publicación de ese artículo, esta misma pregunta se publicó en y voló bajo mi radar hasta ahora, cuando aparentemente el algoritmo original para este problema se ha olvidado una vez más. La mejor solución disponible en esta página de desbordamiento de pila es una versión modificada de mi solución, posiblemente separada por varias generaciones. Desafortunadamente, las modificaciones arruinaron la optimalidad de la solución. De hecho, al cambiar la estructura del bucle de mi original, la solución modificada realiza un paso adicional completamente innecesario de duplicación exponencial (uniendo así la cadena más grande utilizada en la respuesta adecuada un tiempo adicional y luego descartándola).
A continuación se presenta una discusión de algunas optimizaciones de JavaScript relacionadas con todas las respuestas a este problema y para el beneficio de todos.
Técnica: Evitar referencias a objetos o propiedades de objetos.
Para ilustrar cómo funciona esta técnica, usamos una función de JavaScript de la vida real que crea cadenas de cualquier longitud que sea necesaria. Y como veremos, se pueden agregar más optimizaciones!
Una función como la que se usa aquí es crear un relleno para alinear las columnas de texto, para dar formato al dinero o para rellenar los datos del bloque hasta el límite. Una función de generación de texto también permite el ingreso de longitud variable para probar cualquier otra función que funcione con texto. Esta función es uno de los componentes importantes del módulo de procesamiento de texto JavaScript.
A medida que avancemos, cubriremos dos más de las técnicas de optimización más importantes mientras desarrollamos el código original en un algoritmo optimizado para crear cadenas. El resultado final es una función de alto rendimiento y potencia industrial que he usado en todas partes: alineación de precios y totales de artículos en formularios de pedido de JavaScript, formato de datos y formato de mensajes de correo electrónico / texto y muchos otros usos.
Código original para crear cadenas stringFill1()
function stringFill1(x, n) {
var s = '''';
while (s.length < n) s += x;
return s;
}
/* Example of output: stringFill1(''x'', 3) == ''xxx'' */
La sintaxis está aquí es clara. Como puede ver, ya hemos usado variables de función local, antes de pasar a más optimizaciones.
Tenga en cuenta que hay una referencia inocente a una propiedad de objeto en el código que s.length
su rendimiento. Peor aún, el uso de esta propiedad de objeto reduce la simplicidad del programa al suponer que el lector conoce las propiedades de los objetos de cadena de JavaScript.
El uso de esta propiedad de objeto destruye la generalidad del programa de computadora. El programa asume que x
debe ser una cadena de longitud uno. Esto limita la aplicación de la función stringFill1()
a cualquier cosa, excepto a la repetición de caracteres individuales. Incluso no se pueden usar caracteres individuales si contienen varios bytes, como la entidad HTML
.
El peor problema causado por este uso innecesario de una propiedad de objeto es que la función crea un bucle infinito si se prueba en una cadena de entrada vacía x
. Para verificar la generalidad, aplique un programa a la menor cantidad posible de entrada. Un programa que falla cuando se le pide que exceda la cantidad de memoria disponible tiene una excusa. Un programa como este que falla cuando se le pide que produzca nada es inaceptable. A veces el código bonito es un código venenoso.
La simplicidad puede ser un objetivo ambiguo de la programación de computadoras, pero en general no lo es. Cuando un programa carece de un nivel razonable de generalidad, no es válido decir: "El programa es suficientemente bueno en la medida de lo posible". Como puede ver, el uso de la propiedad string.length
evita que este programa funcione en una configuración general y, de hecho, el programa incorrecto está listo para provocar un bloqueo del navegador o del sistema.
¿Hay alguna forma de mejorar el rendimiento de este JavaScript y de resolver estos dos problemas graves?
Por supuesto. Solo usa números enteros.
Código optimizado para crear cadenas stringFill2()
function stringFill2(x, n) {
var s = '''';
while (n-- > 0) s += x;
return s;
}
Código de tiempo para comparar stringFill1()
y stringFill2()
function testFill(functionToBeTested, outputSize) {
var i = 0, t0 = new Date();
do {
functionToBeTested(''x'', outputSize);
t = new Date() - t0;
i++;
} while (t < 2000);
return t/i/1000;
}
seconds1 = testFill(stringFill1, 100);
seconds2 = testFill(stringFill2, 100);
El éxito hasta ahora de stringFill2()
stringFill1()
toma 47.297 microsegundos (millonésimas de segundo) para completar una cadena de 100 bytes, y stringFill2()
toma 27.68 microsegundos para hacer lo mismo. Eso es casi una duplicación en el rendimiento al evitar una referencia a una propiedad de objeto.
Técnica: Evite agregar cuerdas cortas a cuerdas largas
Nuestro resultado anterior se veía bien - muy bien, de hecho. La función mejorada stringFill2()
es mucho más rápida debido al uso de nuestras dos primeras optimizaciones. ¿Lo creerías si te dijera que se puede mejorar para que sea mucho más rápido de lo que es ahora?
Sí, podemos lograr ese objetivo. En este momento necesitamos explicar cómo evitamos añadir cadenas cortas a cadenas largas.
El comportamiento a corto plazo parece ser bastante bueno, en comparación con nuestra función original. A los científicos informáticos les gusta analizar el "comportamiento asintótico" de una función o un algoritmo de programa informático, lo que significa estudiar su comportamiento a largo plazo probándolo con entradas más grandes. A veces, sin hacer más pruebas, uno nunca se da cuenta de las formas en que se podría mejorar un programa de computadora. Para ver qué sucederá, vamos a crear una cadena de 200 bytes.
El problema que aparece con stringFill2()
Usando nuestra función de temporización, encontramos que el tiempo aumenta a 62.54 microsegundos para una cadena de 200 bytes, en comparación con 27.68 para una cadena de 100 bytes. Parece que el tiempo se debe duplicar para hacer el doble de trabajo, pero en cambio se triplicó o cuadruplicó. Según la experiencia de programación, este resultado parece extraño, porque en todo caso, la función debería ser un poco más rápida, ya que el trabajo se realiza de manera más eficiente (200 bytes por llamada de función en lugar de 100 bytes por llamada de función). Este problema tiene que ver con una propiedad insidiosa de las cadenas de JavaScript: las cadenas de JavaScript son "inmutables".
Inmutable significa que no puede cambiar una cadena una vez que se crea. Al agregar un byte a la vez, no estamos usando un byte más de esfuerzo. En realidad estamos recreando toda la cadena más un byte más.
En efecto, para agregar un byte más a una cadena de 100 bytes, se requieren 101 bytes de trabajo. Analicemos brevemente el costo computacional para crear una cadena de N
bytes. El costo de agregar el primer byte es 1 unidad de esfuerzo computacional. El costo de agregar el segundo byte no es una unidad sino 2 unidades (copiar el primer byte en un nuevo objeto de cadena y agregar el segundo byte). El tercer byte requiere un costo de 3 unidades, etc.
C(N) = 1 + 2 + 3 + ... + N = N(N+1)/2 = O(N^2)
. El símbolo O(N^2)
se pronuncia Big O de N al cuadrado, y significa que el costo computacional en el largo plazo es proporcional al cuadrado de la longitud de la cadena. Para crear 100 caracteres se requieren 10.000 unidades de trabajo, y para crear 200 caracteres se requieren 40.000 unidades de trabajo.
Por esta razón, se tardó más del doble en crear 200 caracteres que 100 caracteres. De hecho, debería haber tomado cuatro veces más tiempo. Nuestra experiencia en programación fue correcta en el sentido de que el trabajo se realiza de una manera un poco más eficiente para cadenas más largas, y por lo tanto, tomó solo tres veces más tiempo. Una vez que la sobrecarga de la llamada a la función se vuelve despreciable en cuanto al tiempo de creación de una cadena, en realidad llevará cuatro veces más tiempo crear una cadena el doble de larga.
(Nota histórica: este análisis no se aplica necesariamente a las cadenas en el código fuente, como html = ''abcd/n'' + ''efgh/n'' + ... + ''xyz./n''
, ya que el compilador del código fuente de JavaScript puede unir las cadenas antes de convertirlas en un objeto de cadena de JavaScript. Hace apenas unos años, la implementación KJS de JavaScript se congelaría o se bloquearía al cargar cadenas largas de código fuente unidas por signos más. Dado que el tiempo de cálculo era O(N^2)
no fue difícil hacer páginas web que sobrecargaron el navegador web Konqueror o Safari, que utilizaba el núcleo del motor KJS JavaScript. Primero me encontré con este problema cuando estaba desarrollando un lenguaje de marcado y un analizador de lenguaje de marcado de JavaScript, y luego descubrí lo que estaba causando el problema cuando escribí mi script para JavaScript Incluye.)
Claramente, esta rápida degradación del rendimiento es un gran problema. ¿Cómo podemos lidiar con eso, dado que no podemos cambiar la forma de JavaScript de manejar cadenas como objetos inmutables? La solución es usar un algoritmo que recrea la cadena tan pocas veces como sea posible.
Para aclarar, nuestro objetivo es evitar agregar cadenas cortas a cadenas largas, ya que para agregar la cadena corta, toda la cadena larga también debe estar duplicada.
Cómo funciona el algoritmo para evitar agregar cadenas cortas a cadenas largas
Esta es una buena manera de reducir el número de veces que se crean nuevos objetos de cadena. Concatene longitudes de cadena más largas para que se agregue más de un byte a la vez a la salida.
Por ejemplo, para hacer una cadena de longitud N = 9
:
x = ''x'';
s = '''';
s += x; /* Now s = ''x'' */
x += x; /* Now x = ''xx'' */
x += x; /* Now x = ''xxxx'' */
x += x; /* Now x = ''xxxxxxxx'' */
s += x; /* Now s = ''xxxxxxxxx'' as desired */
Para ello fue necesario crear una cadena de longitud 1, crear una cadena de longitud 2, crear una cadena de longitud 4, crear una cadena de longitud 8 y, finalmente, crear una cadena de longitud 9. ¿Cuánto costo hemos ahorrado?
Costo anterior C(9) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 9 = 45
.
Nuevo costo C(9) = 1 + 2 + 4 + 8 + 9 = 24
.
Tenga en cuenta que tuvimos que agregar una cadena de longitud 1 a una cadena de longitud 0, luego una cadena de longitud 1 a una cadena de longitud 1, luego una cadena de longitud 2 a una cadena de longitud 2, luego una cadena de longitud 4 a una cadena de longitud 4, luego una cadena de longitud 8 a una cadena de longitud 1, para obtener una cadena de longitud 9. Lo que estamos haciendo puede resumirse como evitar agregar cadenas cortas a cadenas largas, o en otras palabras, tratando de concatenar cadenas que sean de igual o casi igual longitud.
Para el costo computacional anterior encontramos una fórmula N(N+1)/2
. ¿Hay una fórmula para el nuevo costo? Sí, pero es complicado. Lo importante es que es O(N)
, por lo que duplicar la longitud de la cadena duplicará aproximadamente la cantidad de trabajo en lugar de cuadruplicarla.
El código que implementa esta nueva idea es casi tan complicado como la fórmula para el costo computacional. Cuando lo lea, recuerde que >>= 1
significa desplazarse hacia la derecha en 1 byte. Entonces, si n = 10011
es un número binario, entonces n >>= 1
da como resultado el valor n = 1001
.
La otra parte del código que tal vez no reconozca es el bitwise y el operador, escrito &
. La expresión n & 1
evalúa verdadero si el último dígito binario de n
es 1, y falso si el último dígito binario de n
es 0.
Nueva función stringFill3()
altamente eficiente
function stringFill3(x, n) {
var s = '''';
for (;;) {
if (n & 1) s += x;
n >>= 1;
if (n) x += x;
else break;
}
return s;
}
Parece feo para el ojo inexperto, pero su rendimiento no es más que encantador.
Veamos qué tan bien realiza esta función. Después de ver los resultados, es probable que nunca olvide la diferencia entre un algoritmo O(N^2)
y un algoritmo O(N)
.
stringFill1()
toma 88.7 microsegundos (millonésimas de segundo) para crear una cadena de 200 bytes, stringFill2()
toma 62.54, y stringFill3()
toma solo 4.608. ¿Qué hizo que este algoritmo fuera mucho mejor? Todas las funciones aprovecharon el uso de variables de función locales, pero al aprovechar la segunda y tercera técnicas de optimización se agregó una mejora de veinte veces en el rendimiento de stringFill3()
.
Análisis más profundo
¿Qué hace que esta función particular saque a la competencia del agua?
Como he mencionado, la razón por la que ambas funciones, stringFill1()
y stringFill2()
, se ejecutan tan lentamente es que las cadenas de JavaScript son inmutables. La memoria no se puede reasignar para permitir que se agregue un byte más a la vez a los datos de cadena almacenados por JavaScript. Cada vez que se agrega un byte más al final de la cadena, la cadena completa se regenera de principio a fin.
Por lo tanto, para mejorar el rendimiento del script, uno debe calcular previamente cadenas de longitud más largas mediante la concatenación de dos cadenas antes de tiempo, y luego construir de forma recursiva la longitud de cadena deseada.
Por ejemplo, para crear una cadena de bytes de 16 letras, primero se precomputa una cadena de dos bytes. Luego, la cadena de dos bytes se reutilizaría para calcular previamente una cadena de cuatro bytes. Luego, la cadena de cuatro bytes se reutilizaría para calcular previamente una cadena de ocho bytes. Finalmente, dos cadenas de ocho bytes se reutilizarán para crear la nueva cadena deseada de 16 bytes. En total, se tuvieron que crear cuatro nuevas cadenas, una de longitud 2, una de longitud 4, una de longitud 8 y otra de longitud 16. El costo total es 2 + 4 + 8 + 16 = 30.
A largo plazo, esta eficiencia se puede calcular sumando en orden inverso y utilizando una serie geométrica que comienza con un primer término a1 = N y que tiene una relación común de r = 1/2. La suma de una serie geométrica viene dada por a_1 / (1-r) = 2N
.
Esto es más eficiente que agregar un carácter para crear una nueva cadena de longitud 2, creando una nueva cadena de longitud 3, 4, 5 y así sucesivamente, hasta 16. El algoritmo anterior usó ese proceso de agregar un solo byte a la vez y el costo total sería n (n + 1) / 2 = 16 (17) / 2 = 8 (17) = 136
.
Obviamente, 136 es un número mucho mayor que 30, por lo que el algoritmo anterior toma mucho, mucho más tiempo para construir una cadena.
Para comparar los dos métodos, puede ver cuánto más rápido está el algoritmo recursivo (también llamado "dividir y conquistar") en una cadena de longitud 123,457. En mi computadora FreeBSD, este algoritmo, implementado en la función stringFill3()
, crea la cadena en 0.001058 segundos, mientras que la función stringFill1()
original crea la cadena en 0.0808 segundos. La nueva función es 76 veces más rápida.
La diferencia en el rendimiento aumenta a medida que aumenta la longitud de la cadena. En el límite a medida que se crean cadenas más grandes y más grandes, la función original se comporta aproximadamente como C1
(constante) veces N^2
, y la nueva función se comporta como C2
(constante) veces N
De nuestro experimento podemos determinar que el valor de C1
sea C1 = 0.0808 / (123457)2 = .00000000000530126997
, y el valor de C2
que sea C2 = 0.001058 / 123457 = .00000000856978543136
. En 10 segundos, la nueva función podría crear una cadena que contenga 1.166.890.359 caracteres. Para crear esta misma cadena, la función antigua necesitaría 7,218,384 segundos de tiempo.
¡Esto es casi tres meses comparado con diez segundos!
Solo respondo (varios años tarde) porque mi solución original a este problema ha estado flotando en Internet durante más de 10 años, y al parecer aún es poco conocida por los pocos que sí lo recuerdan. Pensé que al escribir un artículo sobre esto aquí ayudaría:
webreference.com/programming/javascript/jkm3/3.html
Desafortunadamente, algunas de las otras soluciones que se presentan aquí son algunas de las que demorarían tres meses en producir la misma cantidad de salida que crea una solución adecuada en 10 segundos.
Quiero tomarme el tiempo para reproducir parte del artículo aquí como una respuesta canónica en .
Tenga en cuenta que el algoritmo de mejor rendimiento aquí se basa claramente en mi algoritmo y probablemente se heredó de la adaptación de tercera o cuarta generación de otra persona. Desafortunadamente, las modificaciones resultaron en la reducción de su rendimiento. La variación de mi solución presentada aquí tal vez no entendió mi expresión confusa for (;;)
que se parece al bucle infinito principal de un servidor escrito en C, y que fue simplemente diseñada para permitir una declaración de interrupción cuidadosamente posicionada para el control de bucle, la forma más compacta de evitar la replicación exponencial de la cadena una vez más innecesaria.
He probado el rendimiento de todos los enfoques propuestos.
Aquí está la variante más rápida que tengo.
String.prototype.repeat = function(count) {
if (count < 1) return '''';
var result = '''', pattern = this.valueOf();
while (count > 1) {
if (count & 1) result += pattern;
count >>= 1, pattern += pattern;
}
return result + pattern;
};
O como función independiente :
function repeat(pattern, count) {
if (count < 1) return '''';
var result = '''';
while (count > 1) {
if (count & 1) result += pattern;
count >>= 1, pattern += pattern;
}
return result + pattern;
}
Se basa en el algoritmo artistoex . Es realmente rápido. Y cuanto más grande sea el count
, más rápido irá comparado con el new Array(count + 1).join(string)
enfoque tradicional de new Array(count + 1).join(string)
.
Solo he cambiado 2 cosas:
-
pattern = this
reemplazadopattern = this
conpattern = this.valueOf()
(borra una conversión de tipo obvia); - agregado
if (count < 1)
verifique de prototypejs a la parte superior de la función para excluir acciones innecesarias en ese caso. - optimización aplicada de la answer Dennis (5-7% de aceleración)
UPD
Creé aquí un pequeño parque de pruebas de rendimiento para los interesados.
count
variable ~ 0 .. 100:
Prueba de rendimiento de diferentes variantes de String.repeat () http://tinyurl.com/kb3raxr
count
constante = 1024:
Probando el rendimiento de diferentes variantes de String.repeat () http://tinyurl.com/k527auo
Úsalo y hazlo aún más rápido si puedes :)
Use Array(N+1).join("string_to_repeat")
String.prototype.repeat ahora es estándar ES6.
''abc''.repeat(3); //abcabcabc
Aquí está la versión segura de JSLint
String.prototype.repeat = function (num) {
var a = [];
a.length = num << 0 + 1;
return a.join(this);
};
Concatenando cadenas basadas en un número.
function concatStr(str, num) {
var arr = [];
//Construct an array
for (var i = 0; i < num; i++)
arr[i] = str;
//Join all elements
str = arr.join('''');
return str;
}
console.log(concatStr("abc", 3));
¡Espero que ayude!
Este puede ser el más pequeño recursivo:
String.prototype.repeat = function(n,s) {
s = s || ""
if(n>0) {
s += this
s = this.repeat(--n,s)
}
return s}
Método simple:
String.prototype.repeat = function(num) {
num = parseInt(num);
if (num < 0) return '''';
return new Array(num + 1).join(this);
}
Pruebas de los distintos métodos:
var repeatMethods = {
control: function (n,s) {
/* all of these lines are common to all methods */
if (n==0) return '''';
if (n==1 || isNaN(n)) return s;
return '''';
},
divideAndConquer: function (n, s) {
if (n==0) return '''';
if (n==1 || isNaN(n)) return s;
with(Math) { return arguments.callee(floor(n/2), s)+arguments.callee(ceil(n/2), s); }
},
linearRecurse: function (n,s) {
if (n==0) return '''';
if (n==1 || isNaN(n)) return s;
return s+arguments.callee(--n, s);
},
newArray: function (n, s) {
if (n==0) return '''';
if (n==1 || isNaN(n)) return s;
return (new Array(isNaN(n) ? 1 : ++n)).join(s);
},
fillAndJoin: function (n, s) {
if (n==0) return '''';
if (n==1 || isNaN(n)) return s;
var ret = [];
for (var i=0; i<n; i++)
ret.push(s);
return ret.join('''');
},
concat: function (n,s) {
if (n==0) return '''';
if (n==1 || isNaN(n)) return s;
var ret = '''';
for (var i=0; i<n; i++)
ret+=s;
return ret;
},
artistoex: function (n,s) {
var result = '''';
while (n>0) {
if (n&1) result+=s;
n>>=1, s+=s;
};
return result;
}
};
function testNum(len, dev) {
with(Math) { return round(len+1+dev*(random()-0.5)); }
}
function testString(len, dev) {
return (new Array(testNum(len, dev))).join('' '');
}
var testTime = 1000,
tests = {
biggie: { str: { len: 25, dev: 12 }, rep: {len: 200, dev: 50 } },
smalls: { str: { len: 5, dev: 5}, rep: { len: 5, dev: 5 } }
};
var testCount = 0;
var winnar = null;
var inflight = 0;
for (var methodName in repeatMethods) {
var method = repeatMethods[methodName];
for (var testName in tests) {
testCount++;
var test = tests[testName];
var testId = methodName+'':''+testName;
var result = {
id: testId,
testParams: test
}
result.count=0;
(function (result) {
inflight++;
setTimeout(function () {
result.start = +new Date();
while ((new Date() - result.start) < testTime) {
method(testNum(test.rep.len, test.rep.dev), testString(test.str.len, test.str.dev));
result.count++;
}
result.end = +new Date();
result.rate = 1000*result.count/(result.end-result.start)
console.log(result);
if (winnar === null || winnar.rate < result.rate) winnar = result;
inflight--;
if (inflight==0) {
console.log(''The winner: '');
console.log(winnar);
}
}, (100+testTime)*testCount);
}(result));
}
}
Solo otra función de repetición:
function repeat(s, n) {
var str = '''';
for (var i = 0; i < n; i++) {
str += s;
}
return str;
}
Solución recursiva utilizando divide y vencerás:
function repeat(n, s) {
if (n==0) return '''';
if (n==1 || isNaN(n)) return s;
with(Math) { return repeat(floor(n/2), s)+repeat(ceil(n/2), s); }
}
Use Lodash para la funcionalidad de utilidad de Javascript, como repetir cadenas.
Lodash proporciona un buen rendimiento y compatibilidad ECMAScript.
Lo recomiendo encarecidamente para el desarrollo de UI y también funciona bien en el lado del servidor.
Aquí se explica cómo repetir la cadena "yo" 2 veces utilizando Lodash:
> _.repeat(''yo'', 2)
"yoyo"
Vine aquí al azar y nunca tuve una razón para repetir un char en javascript antes.
Me impresionó la manera en que Artistoex lo hizo y los resultados se desvalorizaron. Noté que el último concat de cuerdas era innecesario, como Dennis también señaló.
Noté algunas cosas más al jugar con la muestra desfasada en conjunto.
Los resultados variaron una cantidad justa a menudo favoreciendo la última ejecución y algoritmos similares a menudo compiten por la posición. Una de las cosas que cambié fue en lugar de usar el recuento generado por JSLitmus como semilla para las llamadas; Como la cuenta se generó diferente para los distintos métodos, puse en un índice. Esto hizo que la cosa fuera mucho más confiable. Luego miré asegurándome de que pasaran cadenas de diferentes tamaños a las funciones. Esto impidió algunas de las variaciones que vi, donde algunos algoritmos funcionaron mejor en caracteres individuales o cadenas más pequeñas. Sin embargo, los 3 métodos principales funcionaron bien independientemente del tamaño de la cadena.
Equipo de prueba bifurcado
http://jsfiddle.net/schmide/fCqp3/134/
// repeated string
var string = ''0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'';
// count paremeter is changed on every test iteration, limit it''s maximum value here
var maxCount = 200;
var n = 0;
$.each(tests, function (name) {
var fn = tests[name];
JSLitmus.test(++n + ''. '' + name, function (count) {
var index = 0;
while (count--) {
fn.call(string.slice(0, index % string.length), index % maxCount);
index++;
}
});
if (fn.call(''>'', 10).length !== 10) $(''body'').prepend(''<h1>Error in "'' + name + ''"</h1>'');
});
JSLitmus.runAll();
Luego incluí la corrección de Dennis y decidí ver si podía encontrar una manera de descubrir un poco más.
Dado que javascript realmente no puede optimizar las cosas, la mejor manera de mejorar el rendimiento es evitarlas manualmente. Si sacara los primeros 4 resultados triviales del bucle, podría evitar 2-4 cadenas de cadenas y escribir la tienda final directamente al resultado.
// final: growing pattern + prototypejs check (count < 1)
''final avoid'': function (count) {
if (!count) return '''';
if (count == 1) return this.valueOf();
var pattern = this.valueOf();
if (count == 2) return pattern + pattern;
if (count == 3) return pattern + pattern + pattern;
var result;
if (count & 1) result = pattern;
else result = '''';
count >>= 1;
do {
pattern += pattern;
if (count & 1) result += pattern;
count >>= 1;
} while (count > 1);
return result + pattern + pattern;
}
Esto resultó en una mejora del 1-2% en promedio sobre la corrección de Dennis. Sin embargo, diferentes ejecuciones y diferentes navegadores mostrarían una variación lo suficientemente justa como para que este código adicional probablemente no valga la pena en comparación con los 2 algoritmos anteriores.
Editar: Hice esto principalmente en cromo. Firefox e IE a menudo favorecerán a Dennis en un par%.
En primer lugar, las preguntas del OP parecen ser sobre concisión, lo que entiendo que significa "simple y fácil de leer", mientras que la mayoría de las respuestas parecen ser sobre eficiencia, que obviamente no es lo mismo y también creo que a menos que se implementen algunas los algoritmos específicos de manipulación de datos grandes, no deberían preocuparle cuando llegue a implementar funciones de JavaScript básicas para la manipulación de datos. La concisión es mucho más importante.
En segundo lugar, como señaló André Laszlo, String.repeat es parte de ECMAScript 6 y ya está disponible en varias implementaciones populares, por lo que la implementación más concisa String.repeat
es no implementarlo ;-)
Por último, si necesita admitir hosts que no ofrecen la implementación de ECMAScript 6, el polyfill de MDN mencionado por André Laszlo es todo menos conciso.
Así que, sin más preámbulos, aquí está mi polyfill conciso :
String.prototype.repeat = String.prototype.repeat || function(n){
return n<=1 ? this : this.concat(this.repeat(n-1));
}
Sí, esto es una recursión. Me gustan las recursiones, son simples y, si se hacen correctamente, son fáciles de entender. En cuanto a la eficiencia, si el lenguaje lo admite, pueden ser muy eficientes si se escriben correctamente.
Según mis pruebas, este método es ~ 60% más rápido que el Array.join
enfoque. A pesar de que obviamente no llega a ninguna parte cerca de la implementación, es mucho más simple que ambas.
Mi configuración de prueba es el nodo v0.10, que utiliza el "modo estricto" (creo que habilita algún tipo de TCO ), invocando repeat(1000)
una cadena de 10 caracteres un millón de veces.
Fiddle: http://jsfiddle.net/3Y9v2/
function repeat(s, n){
return ((new Array(n+1)).join(s));
}
alert(repeat(''R'', 10));
La gente lo complica en exceso o desperdicia el rendimiento. ¿Arrays? Recursion Tienes que estar bromeando.
function repeat (string, times) {
var result = ''''
while (times-- > 0) result += string
return result
}
Editar. Realicé algunas pruebas simples para comparar con la versión bitwise publicada por artistoex / desfated y un grupo de otras personas. Este último fue solo ligeramente más rápido, pero órdenes de magnitud más eficientes en memoria. Para 1000000 repeticiones de la palabra ''blah'', el proceso del nodo subió a 46 megabytes con el algoritmo de concatenación simple (arriba), pero solo 5.5 megabytes con el algoritmo logarítmico. Este último es definitivamente el camino a seguir. Repostándolo por claridad:
function repeat (string, times) {
var result = ''''
while (times > 0) {
if (times & 1) result += string
times >>= 1
string += string
}
return result
}
Si cree que todas las definiciones de prototipos, creaciones de matrices y operaciones de unión son excesivas, solo use un código de línea donde lo necesite. Cadena S repitiendo N veces:
for (var i = 0, result = ''''; i < N; i++) result += S;
/**
@desc: repeat string
@param: n - times
@param: d - delimiter
*/
String.prototype.repeat = function (n, d) {
return --n ? this + (d || '''') + this.repeat(n, d) : '''' + this
};
así es como repetir la cadena varias veces usando delimitador.
function repeat(pattern, count) {
for (var result = '''';;) {
if (count & 1) {
result += pattern;
}
if (count >>= 1) {
pattern += pattern;
} else {
return result;
}
}
}
Puedes probarlo en JSFiddle . Comparado con el hacky Array.join
y el mío es, aproximadamente, 10 (Chrome) a 100 (Safari) a 200 (Firefox) veces más rápido (según el navegador).
function repeat(s, n) { var r=""; for (var a=0;a<n;a++) r+=s; return r;}