Интерпретация скорости расстояния Хэмминга в Python

Я работал над тем, чтобы сделать свой питон более питоническим и экспериментировать со средами выполнения коротких фрагментов кода. Моя цель улучшить читаемость, но, кроме того, ускорить выполнение.

Этот пример противоречит лучшим практикам, о которых я читал, и мне интересно выяснить, в чем недостаток моего мыслительного процесса.

Проблема состоит в том, чтобы вычислить расстояние Хэмминга для двух строк равной длины. Например, расстояние хэмминга для строк «aaab» и «aaaa» равно 1.

Самая простая реализация, о которой я мог подумать, выглядит следующим образом:

def hamming_distance_1(s_1, s_2):
    dist = 0
    for x in range(len(s_1)):
        if s_1[x] != s_2[x]:  dist += 1
    return dist

Затем я написал две «питонические» реализации:

def hamming_distance_2(s_1, s_2): 
    return sum(i.imap(operator.countOf, s_1, s_2))

а также

def hamming_distance_3(s_1, s_2): 
    return sum(i.imap(lambda s: int(s[0]!=s[1]), i.izip(s_1, s_2)))  

В исполнении:

s_1 = (''.join(random.choice('ABCDEFG') for i in range(10000)))
s_2 = (''.join(random.choice('ABCDEFG') for i in range(10000)))
print 'ham_1  ',  timeit.timeit('hamming_distance_1(s_1, s_2)',  "from __main__ import s_1,s_2, hamming_distance_1",number=1000)
print 'ham_2  ',  timeit.timeit('hamming_distance_2(s_1, s_2)',  "from __main__ import s_1,s_2, hamming_distance_2",number=1000)
print 'ham_3  ',  timeit.timeit('hamming_distance_3(s_1, s_2)',  "from __main__ import s_1,s_2, hamming_distance_3",number=1000)

возвращение:

ham_1   1.84980392456
ham_2   3.26420593262
ham_3   3.98718094826

Я ожидал, что ham_3 будет работать медленнее, чем ham_2, из-за того, что вызов лямбды обрабатывается как вызов функции, что медленнее, чем вызов встроенного operator.countOf.

Я был удивлен, что не смог найти способ заставить более питоническую версию работать быстрее, чем ham_1. Мне трудно поверить, что ham_1 - это нижняя граница для чистого питона.

Кто-нибудь думает?


person Scrocco    schedule 04.02.2015    source источник
comment
Я бы сказал, что только ваша первая реализация - Pythonic   -  person YXD    schedule 04.02.2015
comment
В конечном итоге это решение с самым быстрым временем, sum (i.imap (operator.ne, s_1, s_2)) работает в 1.03.   -  person Scrocco    schedule 05.02.2015


Ответы (2)


Ключ в том, чтобы делать меньше поисков методов и вызовов функций:

def hamming_distance_4(s_1, s_2):
    return sum(i != j for i, j in i.izip(s_1, s_2))

работает на ham_4 1.10134792328 в моей системе.

ham_2 и ham_3 выполняет поиск внутри циклов, поэтому они работают медленнее.

person utdemir    schedule 04.02.2015
comment
Ага, вот и все. Спасибо. Ham_4 работает примерно на 1.67991399765 для сравнения. - person Scrocco; 05.02.2015
comment
Меня смутила медлительность ham_2, после дополнительных копаний я понял, что operator.countOf запускает цикл for по строкам (каждая длиной 1), и компилятор не оптимизирует это. Использование operator.ne не имеет цикла for и выполняется за 1/2 времени, как в приведенном выше примере. - person Scrocco; 05.02.2015

Интересно, может ли это быть немного более питоническим в более широком смысле. Что делать, если вы используете http://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.hamming.html ... модуль, который уже реализует то, что вы ищете?

person Jim Dennis    schedule 04.02.2015
comment
OP запрашивает расчет расстояния Хэмминга для массивов строк, а не int. Расчеты пространственного расстояния Scipy работают только для целых чисел. - person SummerEla; 28.12.2018