La forma más inteligente de unir dos listas en una cadena formateada

Digamos que tengo dos listas de la misma longitud:

a = ['a1', 'a2', 'a3'] b = ['b1', 'b2', 'b3'] 

Y quiero producir la siguiente cadena:

 c = 'a1=b1, a2=b2, a3=b3' 

¿Cuál es la mejor manera de lograr esto?

Tengo las siguientes implementaciones:

 import timeit a = [str(f) for f in range(500)] b = [str(f) for f in range(500)] def func1(): return ', '.join([aa+'='+bb for aa in a for bb in b if a.index(aa) == b.index(bb)]) def func2(): list = [] for i in range(len(a)): list.append('%s=%s' % (a[i], b[i])) return ', '.join(list) t = timeit.Timer(setup='from __main__ import func1', stmt='func1()') print 'func1 = ' + t.timeit(10) t = timeit.Timer(setup='from __main__ import func2', stmt='func2()') print 'func2 = ' + t.timeit(10) 

y la salida es:

 func1 = 32.4704790115 func2 = 0.00529003143311 

¿Tienes alguna compensación?

Related of "La forma más inteligente de unir dos listas en una cadena formateada"

 a = ['a1', 'a2', 'a3'] b = ['b1', 'b2', 'b3'] pat = '%s=%%s, %s=%%s, %s=%%s' print pat % tuple(a) % tuple(b) 

da a1=b1, a2=b2, a3=b3

.

Entonces:

 from timeit import Timer from itertools import izip n = 300 a = [str(f) for f in range(n)] b = [str(f) for f in range(n)] def func1(): return ', '.join([aa+'='+bb for aa in a for bb in b if a.index(aa) == b.index(bb)]) def func2(): list = [] for i in range(len(a)): list.append('%s=%s' % (a[i], b[i])) return ', '.join(list) def func3(): return ', '.join('%s=%s' % t for t in zip(a, b)) def func4(): return ', '.join('%s=%s' % t for t in izip(a, b)) def func5(): pat = n * '%s=%%s, ' return pat % tuple(a) % tuple(b) d = dict(zip((1,2,3,4,5),('heavy','append','zip','izip','% formatting'))) for i in xrange(1,6): t = Timer(setup='from __main__ import func%d'%i, stmt='func%d()'%i) print 'func%d = %s %s' % (i,t.timeit(10),d[i]) 

resultado

 func1 = 16.2272833558 heavy func2 = 0.00410247671143 append func3 = 0.00349569568199 zip func4 = 0.00301686387516 izip func5 = 0.00157338432678 % formatting 

Esta implementación es, en mi sistema, más rápida que cualquiera de sus dos funciones y aún más compacta.

 c = ', '.join('%s=%s' % t for t in zip(a, b)) 

Gracias a @JBernardo por la mejora sugerida.

En una syntax más reciente, str.format es más apropiado:

 c = ', '.join('{}={}'.format(*t) for t in zip(a, b)) 

Esto produce en gran parte la misma salida, aunque puede aceptar cualquier objeto con un método __str__ , por lo que dos listas de enteros aún podrían funcionar aquí.

Esas dos soluciones hacen cosas muy diferentes. Los primeros bucles de forma anidada , luego list.index índices con list.index , haciendo de este un bucle doblemente nested y requiriendo lo que se podría considerar como 125,000,000 de operaciones. La segunda itera en lockstep, haciendo 500 pares sin hacer 250000 operaciones. No es de extrañar que sean tan diferentes!

¿Está familiarizado con la notación Big O para describir la complejidad de los algoritmos? Si es así, la primera solución es cúbica y la segunda solución es lineal . El costo de elegir el primero sobre el segundo va a crecer a un ritmo alarmante a medida que a y b hacen más largos, por lo que nadie usaría un algoritmo como ese.


Personalmente, casi con seguridad usaría código como

 ', '.join('%s=%s' % pair for pair in itertools.izip(a, b)) 

o si no estuviera demasiado preocupado por el tamaño de a y b simplemente escribiendo rápido, usaría zip lugar de itertools.izip . Este código tiene varias ventajas.

  • Es lineal Aunque la optimización prematura es un gran problema, es mejor no usar un algoritmo con un rendimiento asintótico innecesariamente malo.

  • Es simple e idiomático. Veo a otras personas escribir código como este con frecuencia.

  • Es la memoria eficiente. Al usar una expresión generadora en lugar de una comprensión de lista (e itertools.izip lugar de zip ), no construyo listas innecesarias en la memoria y convierto lo que podría ser una operación de memoria O (n) (lineal) en una O (1 ) (constante) – operación de memoria.


En cuanto a la sincronización para encontrar la solución más rápida, esto seguramente sería un ejemplo de optimización prematura. Para escribir progtwigs de rendimiento, usamos la teoría y la experiencia para escribir código de buena calidad y fácil de mantener. La experiencia demuestra que, en el mejor de los casos, es inútil y, en el peor de los casos, contraproducente detenerse en operaciones aleatorias y formular la pregunta “¿Cuál es la mejor manera de realizar esta operación en particular?”, Y tratar de determinar si es necesario adivinar o probar.

En realidad, los progtwigs con el mejor rendimiento son los que están escritos con código de la más alta calidad y optimizaciones muy selectivas. El código de alta calidad que valora la legibilidad y la simplicidad en comparación con las marcas de microbenclas termina siendo más fácil de probar, tiene menos errores y es más agradable de refactorizar; estos factores son clave para optimizar efectivamente su progtwig. El tiempo que pasas reparando errores innecesarios, comprendiendo códigos complicados y luchando con la re factorización se puede pasar optimizando.

Cuando llega el momento de optimizar un progtwig, después de que se haya probado y probablemente documentado, esto no se realiza en fragmentos aleatorios, sino en aquellos determinados por casos de uso reales y / o pruebas de rendimiento, con mediciones recostackdas por perfil . Si una pieza de código en particular solo toma el 0.1% del tiempo en el progtwig, ninguna cantidad de aceleración en esa pieza va a hacer ningún bien real.

 >>> ', '.join(i + '=' + j for i,j in zip(a,b)) 'a1=b1, a2=b2, a3=b3'