una separar reemplazar palabra lista letras funcion especiales eliminar comparar como caracteres cadenas python string performance

separar - La cadena de Python ''join'' es más rápida(?) Que ''+'', pero ¿qué ocurre aquí?



separar palabra en letras python (11)

Pedí el método más eficiente para la concatenación de cadenas dinámicas masivas en una publicación anterior y me sugirieron usar el método de unión , el mejor, el más simple y el más rápido para hacerlo (como todos decían). Pero mientras jugaba con concatenaciones de cadenas, encontré algunos resultados extraños (?). Estoy seguro de que algo está sucediendo, pero no puedo entenderlo del todo. Aquí esta lo que hice:

Definí estas funciones:

import timeit def x(): s=[] for i in range(100): # Other codes here... s.append("abcdefg"[i%7]) return ''''.join(s) def y(): s='''' for i in range(100): # Other codes here... s+="abcdefg"[i%7] return s def z(): s='''' for i in range(100): # Other codes here... s=s+"abcdefg"[i%7] return s def p(): s=[] for i in range(100): # Other codes here... s+="abcdefg"[i%7] return ''''.join(s) def q(): s=[] for i in range(100): # Other codes here... s = s + ["abcdefg"[i%7]] return ''''.join(s)

He tratado de mantener otras cosas (excepto la concatenación) casi iguales en todas las funciones. Luego probé con lo siguiente con resultados en el comentario (usando Python 3.1.1 IDLE en Windows 32 bit machine):

timeit.timeit(x) # 31.54912480500002 timeit.timeit(y) # 23.533029429999942 timeit.timeit(z) # 22.116181330000018 timeit.timeit(p) # 37.718607439999914 timeit.timeit(q) # 108.60377576499991

Eso significa que muestra que strng = strng + dyn_strng es el más rápido. Aunque la diferencia en los tiempos no es tan significativa (excepto la última), pero quiero saber por qué sucede esto. ¿Eso es porque estoy usando Python 3.1.1 y eso proporciona ''+'' como el más eficiente? ¿Debo usar ''+'' como alternativa para unirme ? O, ¿he hecho algo extremadamente tonto? ¿O que? Por favor explique claramente.


Además de lo que dijeron los demás, 100 cadenas de 1 char es realmente pequeña . (Estoy sorprendido de que obtengas la separación de los resultados.) Ese es el tipo de conjunto de datos que cabe en tu caché de procesador. No verá el rendimiento asintótico en un microbenchmark.


Algunos de nosotros, los comentaristas de Python, creo que principalmente Rigo y Hettinger, salimos de su camino (en el camino a 2.5, creo) para optimizar algunos casos especiales del ay-demasiado-común- s += something tizón , argumentando que se demostró que los principiantes nunca estarán convencidos de que ''''.join es el camino correcto a seguir y la horrible lentitud de la += podría estar dándole a Python un mal nombre. Otros de nosotros no estábamos tan entusiasmados, porque simplemente no podían optimizar cada aparición (o incluso la mayoría de ellos) a un rendimiento decente; pero no nos sentimos lo suficientemente fuertes sobre el tema para tratar de bloquearlos activamente.

Creo que este hilo demuestra que deberíamos habernos opuesto más severamente. Tal como está ahora, optimizaron += en un cierto subconjunto difícil de predecir de casos hasta donde puede ser tal vez un 20% más rápido para casos estúpidos particulares que la forma correcta (que ES ''''.join ) - solo una forma perfecta de atrapar a los principiantes para perseguir esas ganancias irrelevantes del 20% usando el idioma equivocado ... a costa, de vez en cuando y de su POV de la nada, de ser golpeados con una pérdida de rendimiento del 200% (o más) , dado que el comportamiento no lineal todavía acecha justo afuera de las esquinas, que Hettinger y Rigo se embellecieron y pusieron flores ;-) - uno que IMPORTA, uno que los hará miserables. Esto va en contra de lo "idealmente, es una sola forma obvia de hacerlo" y me parece que, colectivamente, hemos tendido una trampa para los principiantes, el mejor tipo también ... aquellos que no solo aceptan lo que les dicen sus "mejores", pero inquisitivamente van a preguntar y explorar.

Ah bien, me rindo. OP, @mshsayem, adelante, use + = en todas partes, disfrute de sus irrelevantes aceleraciones del 20% en casos triviales, pequeños e irrelevantes, y será mejor que los disfrute al máximo, porque un día, cuando no pueda verlo viniendo, en una operación IMPORTANTE, GRANDE, será golpeado justo en el estómago por el camión remolque que viene de una ralentización del 200% (a menos que tenga mala suerte y sea un 2000% ;-). Solo recuerde: si alguna vez siente que "Python es terriblemente lento", RECUERDE, lo más probable es que sea uno de sus amados bucles de += dar la vuelta y morder la mano que lo alimenta.

Para el resto de nosotros, aquellos que entienden lo que significa decir Debemos olvidarnos de pequeñas eficiencias, digamos el 97% del tiempo , voy a seguir recomendando encarecidamente ''''.join , para que todos podamos dormir tranquilos y tranquilos. SABE que no seremos golpeados con una ralentización super lineal cuando menos lo esperemos y al menos podamos permitirnos. Pero para ti, Armin Rigo y Raymond Hettinger (los dos últimos, queridos amigos míos, por cierto, no solo co-commiters ;-) - ¡tu += sea ​​suave y tu gran O nunca peor que N! - )

Entonces, para el resto de nosotros, aquí hay un conjunto de medidas más significativo e interesante:

$ python -mtimeit -s''r=[str(x)*99 for x in xrange(100,1000)]'' ''s="".join(r)'' 1000 loops, best of 3: 319 usec per loop

900 cadenas de 297 caracteres cada una, uniéndose a la lista directamente es, por supuesto, el más rápido, pero al OP le aterroriza tener que hacer añadidos antes de esa fecha. Pero:

$ python -mtimeit -s''r=[str(x)*99 for x in xrange(100,1000)]'' ''s=""'' ''for x in r: s+=x'' 1000 loops, best of 3: 779 usec per loop $ python -mtimeit -s''r=[str(x)*99 for x in xrange(100,1000)]'' ''z=[]'' ''for x in r: z.append(x)'' ''"".join(z)'' 1000 loops, best of 3: 538 usec per loop

... con una cantidad de datos semi-importante (unos pocos .append de KB, tomando una fracción medible de un milisegundo en cada sentido), incluso el antiguo y bueno uso anterior .append es superior. Además, es obviamente y trivialmente fácil de optimizar:

$ python -mtimeit -s''r=[str(x)*99 for x in xrange(100,1000)]'' ''z=[]; zap=z.append'' ''for x in r: zap(x)'' ''"".join(z)'' 1000 loops, best of 3: 438 usec per loop

afeitarse otras décimas de milisegundo sobre el tiempo promedio de bucle. Todo el mundo (al menos todos los que están totalmente obsesionados abundan en el rendimiento) obviamente saben que HOISTING (sacar del ciclo interno un cálculo repetitivo que de otro modo se realizaría una y otra vez) es una técnica crucial en la optimización: Python no se lanza en su nombre , por lo que debe hacer su propio alzamiento en las raras ocasiones en que importa cada microsegundo.


En cuanto a por qué q es mucho más lento: cuando dices

l += "a"

está agregando la cadena "a" al final de l , pero cuando dice

l = l + ["a"]

está creando una nueva lista con los contenidos de l y ["a"] y luego reasignando los resultados a l . Por lo tanto, constantemente se generan nuevas listas.


Está midiendo dos operaciones distintas: la creación de una matriz de cadenas y la concatenación de cadenas.

import timeit def x(): s = [] for i in range(100): s.append("abcdefg"[i%7]) return ''''.join(s) def y(): s = '''' for i in range(100): s += "abcdefgh"[i%7] # timeit.timeit(x) returns about 32s # timeit.timeit(y) returns about 23s

De lo anterior, parece que ''+'' es una operación más rápida que join. Pero considera:

src = [] def c(): global src s = [] for i in range(100): s.append("abcdefg"[i%7]) src = s def x2(): return ''''.join(src) def y2(): s = '''' for i in range(len(src)): s += src[i] return s # timeit.timeit(c) returns about 30s # timeit.timeit(x2) returns about 1.5s # timeit.timeit(y2) returns about 14s

En otras palabras, al cronometrar x () vs y (), su resultado está contaminado por la construcción de su matriz fuente. Si lo desglosas, descubres que la unión es más rápida.

Además, estás trabajando con arreglos pequeños, y tus números de sincronización simplemente coinciden. Si aumenta significativamente el tamaño de la matriz y la longitud de cada cadena, las diferencias son más claras:

def c2(): global src s = [] for i in range(10000): s.append("abcdefghijklmnopqrstuvwxyz0123456789" src = s # timeit.timeit(x2, number=10000) returns about 1s # timeit.timeit(y2, number=10000) returns about 80s


Esta pregunta es realmente acerca de lo que cuestan las cosas. Jugaremos un poco rápido y flojo aquí, restando resultados en casos similares. Puede decidir por sí mismo si este es un método válido. Aquí hay algunos casos de prueba básicos:

import timeit def append_to_list_with_join(): s=[] for i in xrange(100): s.append("abcdefg"[i%7]) return ''''.join(s) def append_to_list_with_join_opt(): s=[] x = s.append for i in xrange(100): x("abcdefg"[i%7]) return ''''.join(s) def plus_equals_string(): s='''' for i in xrange(100): s+="abcdefg"[i%7] return s def plus_assign_string(): s='''' for i in xrange(100): s=s+"abcdefg"[i%7] return s def list_comp_join(): return ''''.join(["abcdefg"[i%7] for i in xrange(100)]) def list_comp(): return ["abcdefg"[i%7] for i in xrange(100)] def empty_loop(): for i in xrange(100): pass def loop_mod(): for i in xrange(100): a = "abcdefg"[i%7] def fast_list_join(): return "".join(["0"] * 100) for f in [append_to_list_with_join, append_to_list_with_join_opt, plus_equals_string,plus_assign_string,list_comp_join, list_comp, empty_loop,loop_mod, fast_list_join]: print f.func_name, timeit.timeit(f)

Y aquí está lo que cuestan:

append_to_list_with_join 25.4540209021 append_to_list_with_join_opt 19.9999782794 plus_equals_string 16.7842428996 plus_assign_string 14.8312124167 list_comp_join 16.329590353 list_comp 14.6934344309 empty_loop 2.3819276612 loop_mod 10.1424356308 fast_list_join 2.58149394686

En primer lugar, muchas cosas tienen costos inesperados en Python. append_to_list_with_join versus append_to_list_with_join_opt muestra que incluso buscar un método en un objeto tiene un costo no despreciable. En este caso, buscar s.append es una cuarta parte del tiempo.

A continuación, list_comp_join versus list_comp muestra que join () es bastante rápido: toma alrededor de 1.7 o solo el 10% del tiempo de list_comp_join.

loop_mod muestra que la mayor parte de esta prueba está en realidad configurando los datos, independientemente del método de construcción de la secuencia que se utilice. Por inferencia, el tiempo tomado para "cadena = cadena +", "cadena + =" y lista de comprensión son:

plus_equals_string = 16.78 - 10.14 = 6.64 plus_assign_string = 14.83 - 10.14 = 4.69 list_comp = 14.69 - 10.14 = 4.55

En cuanto a la pregunta del OP, join () es rápido, pero el momento de crear la lista subyacente, ya sea con primitivas de lista o comprensión de lista, es comparable a la creación de la cadena con primitivas de cadena. Si ya tiene una lista, conviértala en una cadena con join (); será rápida.

Los tiempos que presenta el OP indican que construir listas usando operadores de concatenación es lento. Por el contrario, el uso de listas de comprensión es rápido. Si tiene que compilar una lista, use una lista de comprensión.

Finalmente, tomemos tres de las funciones más cercanas del OP: ¿cuál es la diferencia entre x, p y q? Simplifiquemos un poco:

import timeit def x(): s=[] for i in range(100): s.append("c") def p(): s=[] for i in range(100): s += "c" def q(): s=[] for i in range(100): s = s + ["c"] for f in [x,p,q]: print f.func_name, timeit.timeit(f)

Aquí están los resultados:

x 16.0757342064 p 87.1533697719 q 85.0999698984

Y aquí está el disassembly :

>>> import dis >>> dis.dis(x) 2 0 BUILD_LIST 0 3 STORE_FAST 0 (s) 3 6 SETUP_LOOP 33 (to 42) 9 LOAD_GLOBAL 0 (range) 12 LOAD_CONST 1 (100) 15 CALL_FUNCTION 1 18 GET_ITER >> 19 FOR_ITER 19 (to 41) 22 STORE_FAST 1 (i) 4 25 LOAD_FAST 0 (s) 28 LOAD_ATTR 1 (append) 31 LOAD_CONST 2 (''c'') 34 CALL_FUNCTION 1 37 POP_TOP 38 JUMP_ABSOLUTE 19 >> 41 POP_BLOCK >> 42 LOAD_CONST 0 (None) 45 RETURN_VALUE >>> dis.dis(p) 2 0 BUILD_LIST 0 3 STORE_FAST 0 (s) 3 6 SETUP_LOOP 30 (to 39) 9 LOAD_GLOBAL 0 (range) 12 LOAD_CONST 1 (100) 15 CALL_FUNCTION 1 18 GET_ITER >> 19 FOR_ITER 16 (to 38) 22 STORE_FAST 1 (i) 4 25 LOAD_FAST 0 (s) 28 LOAD_CONST 2 (''c'') 31 INPLACE_ADD 32 STORE_FAST 0 (s) 35 JUMP_ABSOLUTE 19 >> 38 POP_BLOCK >> 39 LOAD_CONST 0 (None) 42 RETURN_VALUE >>> dis.dis(q) 2 0 BUILD_LIST 0 3 STORE_FAST 0 (s) 3 6 SETUP_LOOP 33 (to 42) 9 LOAD_GLOBAL 0 (range) 12 LOAD_CONST 1 (100) 15 CALL_FUNCTION 1 18 GET_ITER >> 19 FOR_ITER 19 (to 41) 22 STORE_FAST 1 (i) 4 25 LOAD_FAST 0 (s) 28 LOAD_CONST 2 (''c'') 31 BUILD_LIST 1 34 BINARY_ADD 35 STORE_FAST 0 (s) 38 JUMP_ABSOLUTE 19 >> 41 POP_BLOCK >> 42 LOAD_CONST 0 (None) 45 RETURN_VALUE

Los bucles son casi idénticos. La comparación asciende a CALL_FUNCTION + POP_TOP frente a INPLACE_ADD + STORE_FAST frente a BUILD_LIST + BINARY_ADD + STORE_FAST. Sin embargo, no puedo dar una explicación de bajo nivel más que eso: simplemente no puedo encontrar los costos de los códigos de bytes de Python en la red. Sin embargo, puede obtener algo de inspiración al mirar el módulo Python de la semana de Doug Hellmann en dis .


Hay una diferencia entre + = y + con cadenas: si no hay otras referencias a "x", x + = y solo puede agregarse a x, en lugar de tener que tomar una copia de la cadena para anexarla, que es la misma beneficio que obtienes al usar "" .join ().

El principal beneficio de "" .join () sobre + o + = es que join () siempre debe dar un rendimiento lineal, mientras que en muchos casos + / + = dará rendimiento cuadrático (es decir, cuando duplicas la cantidad de texto, cuadruplica la cantidad de tiempo tomado). Pero esto solo importará con una gran cantidad de texto, no solo 100 bytes, y creo que no se activará si solo tiene una referencia a la cadena que está agregando.

En detalle:

El mejor rendimiento de su caso para la concatenación de cadenas es mirar cada carácter en la cadena final una vez. "" .join () lo hace naturalmente, tiene toda la información que necesita desde el principio.

Sin embargo, a + = b puede funcionar de dos maneras, puede simplemente agregar "b" a una cadena existente, en cuyo caso solo necesita mirar los caracteres en "b", o puede tener que mirar a los caracteres en " un "también".

En C, strcat () siempre mira a todos los caracteres en ambas cadenas, por lo que funciona mal siempre. En Python, sin embargo, la longitud de la cadena se almacena, por lo que la cadena se puede extender siempre y cuando no se haga referencia en otra parte, y se obtiene un buen rendimiento copiando solo los caracteres en "b". Si se hace referencia en otro lugar, Python hará una copia de "a" primero, luego agregará "b" al final, lo que le dará un mal rendimiento. Si agrega cinco cadenas de esta manera, su tiempo será:

ab = a+b # Time is a + b abc = ab+c # Time is (a+b) + c abcd = abc+d # Time is (a+b+c) + d abcde = abcd+e # Time is (a+b+c+d) + e

que si a, b, c, d, e son más o menos del mismo tamaño, es decir, n, son n * (n-1) / 2-1 operaciones, o esencialmente n-cuadrado.

Para obtener el mal comportamiento para x + = y, intente:

def a(n=100): res = "" for k in xrange(n): v=res res += "foobar" return res

Aunque v no se utiliza en realidad, es suficiente para activar la ruta más lenta para + = y obtener el mal comportamiento que preocupa a las personas.

Creo que + = no se introdujo hasta Python 2.0, por lo que no fue posible agregar eficientemente sin usar algo como "" .join () en Python 1.6 y versiones anteriores.


He descubierto la respuesta de las respuestas publicadas aquí por expertos. La concatenación de cadenas de Python (y las mediciones de tiempos) depende de estas (por lo que he visto):

  • Número de concatenaciones
  • Longitud promedio de cuerdas
  • Número de llamadas a funciones

He creado un nuevo código que relaciona estos. Gracias a Peter S Magnusson, sepp2k, hughdbrown, David Wolever y otros por indicar puntos importantes que me había perdido antes. Además, en este código podría haberme perdido algo. Por lo tanto, agradezco cualquier respuesta que indique nuestros errores, sugerencias, críticas, etc. Después de todo, estoy aquí para aprender. Aquí está mi nuevo código:

from timeit import timeit noc = 100 tocat = "a" def f_call(): pass def loop_only(): for i in range(noc): pass def concat_method(): s = '''' for i in range(noc): s = s + tocat def list_append(): s=[] for i in range(noc): s.append(tocat) ''''.join(s) def list_append_opt(): s = [] zap = s.append for i in range(noc): zap(tocat) ''''.join(s) def list_comp(): ''''.join(tocat for i in range(noc)) def concat_method_buildup(): s='''' def list_append_buildup(): s=[] def list_append_opt_buildup(): s=[] zap = s.append def function_time(f): return timeit(f,number=1000)*1000 f_callt = function_time(f_call) def measure(ftuple,n,tc): global noc,tocat noc = n tocat = tc loopt = function_time(loop_only) - f_callt buildup_time = function_time(ftuple[1]) -f_callt if ftuple[1] else 0 total_time = function_time(ftuple[0]) return total_time, total_time - f_callt - buildup_time - loopt*ftuple[2] functions ={''Concat Method/t/t'':(concat_method,concat_method_buildup,True), ''List append/t/t/t'':(list_append,list_append_buildup,True), ''Optimized list append'':(list_append_opt,list_append_opt_buildup,True), ''List comp/t/t/t'':(list_comp,0,False)} for i in range(5): print("/n/n%d concatenation/t/t/t/t10''a''/t/t/t/t 100''a''/t/t/t1000''a''"%10**i) print(''-''*80) for (f,ft) in functions.items(): print(f,"/t|",end="/t") for j in range(3): t = measure(ft,10**i,''a''*10**j) print("%.3f %.3f |" % t,end="/t") print()

Y aquí está lo que tengo. [En la columna de tiempo se muestran dos veces (en escala): la primera es el tiempo de ejecución de la función total, y la segunda es el tiempo de concatenación real (?). He deducido el tiempo de llamada a la función, el tiempo de creación de la función (tiempo de inicialización) y el tiempo de iteración. Aquí estoy considerando un caso en el que no se puede hacer sin un bucle (digamos más enunciados adentro).]

1 concatenation 1''a'' 10''a'' 100''a'' ------------------- ---------------------- ------------------- ---------------- List comp | 2.310 2.168 | 2.298 2.156 | 2.304 2.162 Optimized list append | 1.069 0.439 | 1.098 0.456 | 1.071 0.413 Concat Method | 0.552 0.034 | 0.541 0.025 | 0.565 0.048 List append | 1.099 0.557 | 1.099 0.552 | 1.094 0.552 10 concatenations 1''a'' 10''a'' 100''a'' ------------------- ---------------------- ------------------- ---------------- List comp | 3.366 3.224 | 3.473 3.331 | 4.058 3.916 Optimized list append | 2.778 2.003 | 2.956 2.186 | 3.417 2.639 Concat Method | 1.602 0.943 | 1.910 1.259 | 3.381 2.724 List append | 3.290 2.612 | 3.378 2.699 | 3.959 3.282 100 concatenations 1''a'' 10''a'' 100''a'' ------------------- ---------------------- ------------------- ---------------- List comp | 15.900 15.758 | 17.086 16.944 | 20.260 20.118 Optimized list append | 15.178 12.585 | 16.203 13.527 | 19.336 16.703 Concat Method | 10.937 8.482 | 25.731 23.263 | 29.390 26.934 List append | 20.515 18.031 | 21.599 19.115 | 24.487 22.003 1000 concatenations 1''a'' 10''a'' 100''a'' ------------------- ---------------------- ------------------- ---------------- List comp | 134.507 134.365 | 143.913 143.771 | 201.062 200.920 Optimized list append | 112.018 77.525 | 121.487 87.419 | 151.063 117.059 Concat Method | 214.329 180.093 | 290.380 256.515 | 324.572 290.720 List append | 167.625 133.619 | 176.241 142.267 | 205.259 171.313 10000 concatenations 1''a'' 10''a'' 100''a'' ------------------- ---------------------- ------------------- ---------------- List comp | 1309.702 1309.560 | 1404.191 1404.049 | 2912.483 2912.341 Optimized list append | 1042.271 668.696 | 1134.404 761.036 | 2628.882 2255.804 Concat Method | 2310.204 1941.096 | 2923.805 2550.803 | STUCK STUCK List append | 1624.795 1251.589 | 1717.501 1345.137 | 3182.347 2809.233

Para resumir todo esto, he tomado esta decisión por mí:

  1. Si tiene disponible una lista de cadenas, el método de "juntar" es mejor y más rápido.
  2. Si puede usar la comprensión de la lista, también es la más fácil y rápida.
  3. Si necesita una concatenación de 1 a 10 (promedio) con una longitud de 1 a 100, list append, ''+'' toma el mismo tiempo (casi, tenga en cuenta que los tiempos son escala).
  4. El apéndice de la lista optimizada parece muy bueno en la mayoría de las situaciones.
  5. Cuando #concatenación o longitud de cadena aumenta, ''+'' comienza a tomar significativamente más y más tiempo. Tenga en cuenta que, para 10000 concatenaciones con 100''a ''¡mi PC está atascada!
  6. Si usa list append y ''join'' siempre, estará seguro todo el tiempo (señalado por Alex Martelli).
  7. Pero en alguna situación, por ejemplo, donde se necesita tomar la entrada del usuario e imprimir ''¡Hola mundo de usuarios!'', Es más simple usar ''+''. Creo que compilar una lista y unirme para este caso como x = input ("Enter user name:") y luego x.join (["Hello", "''s world!"]) Es más feo que "Hello% s''s world! "% x o" Hello "+ x +" ''s world "
  8. Python 3.1 ha mejorado el rendimiento de concatenación. Pero, en algunas implementaciones como Jython, ''+'' es menos eficiente.
  9. La optimización prematura es la raíz de todo mal (refrán de los expertos). La mayoría de las veces no necesita optimización. Por lo tanto, no pierda el tiempo aspirando a la optimización (a menos que esté escribiendo un proyecto grande o computacional donde cada micro / mili segundo cuenta.
  10. Use esta información y escriba de la forma que desee tomando las circunstancias que se consideran.
  11. Si realmente necesita una optimización, use un generador de perfiles, encuentre los cuellos de botella y trate de optimizarlos.

Finalmente, estoy tratando de aprender Python más profundamente. Por lo tanto, no es inusual que haya errores (error) en mis observaciones. Por lo tanto, comente sobre esto y sugiérame si estoy tomando una ruta incorrecta. Gracias a todos por participar.


Interesante: he hecho algunas pruebas donde cambia el tamaño de la cuerda, y esto es lo que encontré:

def x(): x = "a" * 100 s=[] for i in range(100): # Other codes here... s.append(x) return ''''.join(s) def z(): x = "a" * 100 s='''' for i in xrange(100): # Other codes here... s=s+x return s from timeit import timeit print "x:", timeit(x, number=1000000) print "z:", timeit(z, number=1000000)

Para cadenas de longitud 1 ( x = "a" * 1 ):

x: 27.2318270206 z: 14.4046051502

Para cadenas de longitud 100:

x: 30.0796670914 z: 21.5891489983

Y para cadenas de longitud 1000, ejecutando timeit 100,000 veces en vez de 1,000,000

x: 14.1769361496 z: 31.4864079952

Lo cual, si mi lectura de Objects/stringobject.c es correcta, tiene sentido.

Parece, en una primera lectura, que el algoritmo String.join (edge-cases al lado) es:

def join(sep, sequence): size = 0 for string in sequence: size += len(string) + len(sep) result = malloc(size) for string in sequence: copy string into result copy sep into result return result

Por lo tanto, esto requerirá más o menos O(S) pasos (donde S es la suma de las longitudes de todas las cadenas que se unen).


La concatenación de cadenas fue mucho más lenta antes de Python 2.5, cuando aún creaba una nueva copia para cada concatenación de cadenas en lugar de agregarla al original, lo que hizo que join () se convirtiera en una solución popular.

Aquí hay un viejo punto de referencia que demuestra el viejo problema: http://www.skymind.com/~ocrow/python_string/


Supongo que x () es más lento porque primero estás construyendo la matriz y luego uniéndote a ella. Por lo tanto, no solo está midiendo el tiempo que lleva la unión, sino también el tiempo que tarda en crear la matriz.

En un escenario donde ya tiene una matriz y quiere crear una cadena de sus elementos, la unión debe ser más rápida que iterar a través de la matriz y construir la cadena paso a paso.


Ya hay muchos buenos resúmenes aquí, pero solo para obtener más pruebas.

Fuente: ¡Observé el código fuente de Python durante una hora y calculé las complejidades!

Mis hallazgos

Para 2 cuerdas. (Supongamos que n es la longitud de ambas cadenas)

Concat (+) - O(n) Join - O(n+k) effectively O(n) Format - O(2n+k) effectively O(n)

Para más de 2 cadenas. (Supongamos que n es la longitud de todas las cadenas)

Concat (+) - O(n^2) Join - O(n+k) effectively O(n) Format - O(2n+k) effectively O(n)

RESULTADO:

Si tiene dos cadenas técnicamente, la concatenación (+) es mejor, aunque es exactamente lo mismo que unirse y formatear.

Si tiene más de dos cadenas, el concat se vuelve horrible y unirse y formatear son efectivamente los mismos, aunque técnicamente unirse es un poco mejor.

RESUMEN:

Si no le interesa la eficiencia, use cualquiera de los anteriores. (Aunque como hiciste la pregunta, supongo que te importa)

Por lo tanto -

Si tiene 2 cadenas use concat (cuando no esté en un bucle)

Si tiene más de dos cadenas (todas las cadenas) (o en un bucle) use join

Si tiene algo, no cadenas, use formato, porque duh.

¡Espero que esto ayude!