Zanurkuj w Pythonie/Optymalizacja operacji na listach
Optymalizacja operacji na listach
edytujTrzecim krokiem algorytmu soundex
jest eliminacja kolejnych powtarzających się cyfr. Jak najlepiej to zrobić?
Taki kod otrzymaliśmy dotąd, znajduje się on w soundex/stage2/soundex2c.py:
digits2 = digits[0]
for d in digits[1:]:
if digits2[-1] != d:
digits2 += d
Takie wyniki wydajnościowe otrzymujemy dla soundex2c.py:
C:\samples\soundex\stage2>python soundex2c.py Woo W000 12.6070768771 Pilgrim P426 14.4033353401 Flingjingwaller F452 19.7774882003
Pierwszą rzeczą do rozważenia jest efektywność wywołań digits[-1]
w każdej iteracji pętli. Czy indeksowanie listy jest kosztowne? A może lepiej przechowywać ostatnią cyfrę w oddzielnej zmiennej i sprawdzać nią zamiast listy?
Odpowiedź na to pytanie pomoże nam znaleźć soundex/stage3/soundex3a.py:
digits2 = ''
last_digit = ''
for d in digits:
if d != last_digit:
digits2 += d
last_digit = d
soundex3a.py nie działa ani trochę szybciej niż soundex2c.py, a nawet może być troszeczkę wolniejsze (aczkolwiek jest to za mała różnica, aby coś powiedzieć z całą pewnością):
C:\samples\soundex\stage3>python soundex3a.py Woo W000 11.5346048171 Pilgrim P426 13.3950636184 Flingjingwaller F452 18.6108927252
Dlaczego soundex3a.py nie jest szybsze? Okazuje się, że indeksowanie list w Pythonie jest ekstremalnie efektywne. Powtarzanie dostępu do digits2[-1] nie stanowi w ogóle problemu. Z drugiej strony, kiedy manualnie zarządzamy ostatnią cyfrą w oddzielnej zmiennej, korzystamy z dwóch przypisań do zmiennych dla każdej przechowywanej cyfry, a te operacje zastępują mały koszt związany z korzystania z listy.
Spróbujemy teraz czegoś radykalnie innego. Jest możliwe, aby traktować dany napis jako listę znaków, zatem można byłoby wykorzystać wyrażenie listowe, aby przeiterować listę znaków. Jednak występuje problem związany z tym, że potrzebujemy dostępu do poprzedniego znaku w liście, a to nie jest łatwe w przypadku prostych wyrażeń listowych.
Jakkolwiek jest możliwe tworzenie listy liczbowych indeksów za pomocą wbudowanej funkcji range()
, aby następnie wykorzystać te indeksy do stopniowego przeszukiwania listy i wybierania każdego znaku różnego od znaku poprzedzającego. Dzięki temu otrzymamy listę znaków, a następnie możemy wykorzystać metodę łańcucha znaków join()
, aby zrekonstruować z tego listę.
Poniżej mamy soundex/stage3/soundex3b.py:
digits2 = "".join([digits[i] for i in range(len(digits))
if i == 0 or digits[i-1] != digits[i]])
Czy jest to szybsze? Jednym słowem, nie.
C:\samples\soundex\stage3>python soundex3b.py Woo W000 14.2245271396 Pilgrim P426 17.8337165757 Flingjingwaller F452 25.9954005327
Być może szybkie techniki powinny się skupiać wokół łańcuchów znaków. Python może konwertować łańcuch znaków na listę znaków za pomocą jednego polecenia: list('abc')
, które zwróci ['a', 'b', 'c']
. Ponadto listy mogą być bardzo szybko modyfikowane w miejscu. Zamiast zwiększać liczbę tworzonych nowych list (lub łańcuchów znaków) z naszego początkowego łańcucha, dlaczego nie przenieść wszystkich elementów do pojedynczej listy?
Poniżej przedstawiono soundex/stage3/soundex3c.py, który modyfikuje listę w miejscu i usuwa kolejno powtarzające się elementy:
digits = list(source[0].upper() + source[1:].translate(charToSoundex))
i=0
for item in digits:
if item==digits[i]: continue
i+=1
digits[i]=item
del digits[i+1:]
digits2 = "".join(digits)
Czy jest to szybsze od soundex3a.py lub soundex3b.py? Nie, w rzeczywistości działa to jeszcze wolniej:
C:\samples\soundex\stage3>python soundex3c.py Woo W000 14.1662554878 Pilgrim P426 16.0397885765 Flingjingwaller F452 22.1789341942
Ciągle nie wykonaliśmy tutaj żadnego podstępu, z wyjątkiem tego, że wykorzystaliśmy i wypróbowaliśmy kilka "mądrych" technik. Najszybszym kodem, który jak dotąd widzieliśmy, nadal pozostał oryginał, najbardziej prosta metoda (soundex2c.py). Czasami nie popłaca być mądrym.
import string, re
allChar = string.uppercase + string.lowercase
charToSoundex = string.maketrans(allChar, "91239129922455912623919292" * 2)
isOnlyChars = re.compile('^[A-Za-z]+$').search
def soundex(source):
if not isOnlyChars(source):
return "0000"
digits = source[0].upper() + source[1:].translate(charToSoundex)
digits2 = digits[0]
for d in digits[1:]:
if digits2[-1] != d:
digits2 += d
digits3 = re.sub('9', '', digits2)
while len(digits3) < 4:
digits3 += "0"
return digits3[:4]
if __name__ == '__main__':
from timeit import Timer
names = ('Woo', 'Pilgrim', 'Flingjingwaller')
for name in names:
statement = "soundex('%s')" % name
t = Timer(statement, "from __main__ import soundex")
print name.ljust(15), soundex(name), min(t.repeat())