самый умный способ объединить два списка в отформатированную строку - PullRequest
8 голосов
/ 02 сентября 2011

Допустим, у меня есть два списка одинаковой длины:

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

, и я хочу создать следующую строку:

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

Каков наилучший способ добиться этого?

У меня есть следующие реализации:

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)

и вывод:

func1 = 32.4704790115
func2 = 0.00529003143311

У вас есть какой-то компромисс?

Ответы [ 4 ]

25 голосов
/ 02 сентября 2011

В моей системе эта реализация быстрее, чем любая из ваших двух функций, и еще более компактна.

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

Спасибо @JBernardo за предложенное улучшение.

В более поздних версияхсинтаксис, str.format более уместен:

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

Это дает практически одинаковые выходные данные, хотя он может принимать любой объект с помощью метода __str__, поэтому два списка целых чисел все еще могут работать здесь.

12 голосов
/ 06 октября 2013
a = ['a1', 'a2', 'a3']
b = ['b1', 'b2', 'b3']

pat = '%s=%%s, %s=%%s, %s=%%s'

print pat % tuple(a) % tuple(b)

дает a1=b1, a2=b2, a3=b3

.

Тогда:

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

результат

func1 = 16.2272833558  heavy
func2 = 0.00410247671143  append
func3 = 0.00349569568199  zip
func4 = 0.00301686387516  izip
func5 = 0.00157338432678  % formatting
8 голосов
/ 02 сентября 2011

Эти два решения делают очень разные вещи. Первые циклы вложены , затем вычисляются индексы с list.index, что фактически делает этот цикл с двойным вложением и требует 125 000 000 операций. Вторая итерация выполняется на шаге, делая 500 пар без выполнения 250000 операций. Не удивительно, что они такие разные!

Вы знакомы с Big O нотацией для описания сложности алгоритмов? Если это так, первое решение - кубический , а второе решение - линейный . Стоимость выбора первого вместо второго будет расти с пугающей скоростью, так как a и b становятся длиннее, поэтому никто не будет использовать такой алгоритм.


Лично я почти наверняка использовал бы код вроде

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

или если бы меня не слишком волновали размеры a и b и просто быстрая запись, я бы использовал zip вместо itertools.izip. Этот код имеет несколько преимуществ

  • Это линейно. Хотя преждевременная оптимизация является огромной проблемой, лучше не использовать алгоритм с неоправданно плохой асимптотикой.

  • Это просто и идиоматично. Я вижу, как другие люди часто пишут такой код.

  • Это эффективная память. Используя выражение генератора вместо понимания списка (и itertools.izip вместо zip), я не строю ненужные списки в памяти и не превращаю то, что может быть операцией памяти O (n) (линейной) в O (1) (постоянная) операция памяти.


Что касается хронометража , чтобы найти самое быстрое решение, то это почти наверняка будет примером преждевременной оптимизации. Для написания эффективных программ мы используем теорию и опыт для написания качественного, удобного в обслуживании и хорошего кода Опыт показывает, что в лучшем случае бесполезно, а в худшем - контрпродуктивно останавливаться на случайных операциях и задавать вопрос: «Каков наилучший способ выполнить эту конкретную операцию» и пытаться определить ее по предположению или даже тестированию.

На самом деле программы с наилучшей производительностью написаны с использованием кода самого высокого качества и очень выборочной оптимизации. Высококачественный код, который оценивает удобочитаемость и простоту по сравнению с микробенчмарками, становится проще для тестирования, меньше ошибок и приятнее для рефакторинга - эти факторы являются ключевыми для эффективной оптимизации вашей программы. Время, потраченное на исправление ненужных ошибок, понимание сложного кода и борьбу с рефакторингом, можно потратить на оптимизацию.

Когда приходит время оптимизировать программу - после того, как она протестирована и, вероятно, задокументирована, - это делается не на случайных фрагментах, а на тех, которые определены в реальных случаях использования и / или тестах производительности, с измерениями, собранными с помощью профилирования . Если конкретный фрагмент кода занимает всего 0,1% времени в программе, никакое ускорение этого фрагмента не принесет никакой пользы.

3 голосов
/ 02 сентября 2011
>>> ', '.join(i + '=' + j for i,j in zip(a,b))
'a1=b1, a2=b2, a3=b3'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...