Zanurkuj w Pythonie/Optymalizacja operacji na napisach

Ostatnim krokiem algorytmu Soundex jest dopełnienie krótkich napisów wynikowych zerami oraz przycięcie napisów zbyt długich. W jaki sposób można to zrobić najlepiej?

Dotychczasowy kod w programie soundex/stage2/soundex2c.py wygląda następująco:

    digits3 = re.sub('9', '', digits2)
    while len(digits3) < 4:
        digits3 += "0"
    return digits3[:4]

Oto rezultaty pomiarów wydajności w soundex2c.py:

C:\samples\soundex\stage2>python soundex2c.py
Woo             W000 12.6070768771
Pilgrim         P426 14.4033353401
Flingjingwaller F452 19.7774882003

Pierwszą rzeczą, jaką należy rozważyć, jest zastąpienie wyrażenia regularnego pętlą. Oto kod programu soundex/stage4/soundex4a.py:

    digits3 = ''
    for d in digits2:
        if d != '9':
            digits3 += d

Czy soundex4a.py jest szybszy? Oczywiście:

C:\samples\soundex\stage4>python soundex4a.py
Woo             W000 6.62865531792
Pilgrim         P426 9.02247576158
Flingjingwaller F452 13.6328416042

Czekajcie chwilę. Pętla, która usuwa znaki z napisu? Możemy użyć do tego prostej metody z klasy string. Oto soundex/stage4/soundex4b.py:

    digits3 = digits2.replace('9', '')

Czy soundex4b.py jest szybszy? To interesujące pytanie. Zależy to od danych wejściowych:

C:\samples\soundex\stage4>python soundex4b.py
Woo             W000 6.75477414029
Pilgrim         P426 7.56652144337
Flingjingwaller F452 10.8727729362

Dla większości nazw z programu soundex4b.py metoda z klasy string jest szybsza niż pętla, jest jednak odrobinę wolniejsza niż soundex4a.py dla przypadku trywialnego (dla bardzo krótkiej nazwy). Optymalizacje wydajnościowe nie zawsze są jednorodne; poprawki, które sprawią, że w pewnych przypadkach program będzie działał szybciej, mogą sprawić, że w innych przypadkach ten sam program będzie działał wolniej. W naszym programie uzyskujemy poprawę dla większości przypadków, więc zostawimy tę poprawkę, warto jednak na przyszłość mieć tę ważną zasadę na uwadze.

Na koniec, choć to wcale nie jest sprawa najmniejszej wagi, prześledźmy dwa ostatnie kroki algorytmu: uzupełnianie zerami krótkich napisów wynikowych oraz przycinanie napisów zbyt długich do czterech znaków. Kod, który widzicie w soundex4b.py robi dokładnie to, co trzeba, jednak robi to w bardzo niewydajny sposób. Spójrzmy na soundex/stage4/soundex4c.py, aby przekonać się, dlaczego tak się dzieje.

    digits3 += '000'
    return digits3[:4]

Dlaczego potrzebujemy pętli while do wyrównania napisu wynikowego? Wiemy przecież z góry, że zamierzamy obciąć napis do czterech znaków; wiemy również, że mamy juz przynajmniej jeden znak (pierwszą literę zmiennej source, która w wyniku tego algorytmu nie zmienia się). Oznacza to, że możemy po prostu dodać trzy zera do napisu wyjściowego, a następnie go przyciąć. Nie dajcie się nigdy zbić z tropu przez dosłowne sformułowanie problemu; czasami wystarczy spojrzeć na ten problem z trochę innej perspektywy, a już pojawia się prostsze rozwiązanie.

Czy usuwając pętlę while zyskaliśmy na prędkości programu soundex4c.py? Nawet znacznie:

C:\samples\soundex\stage4>python soundex4c.py
Woo             W000 4.89129791636
Pilgrim         P426 7.30642134685
Flingjingwaller F452 10.689832367

Na koniec warto zauważyć, że jest jeszcze jedna rzecz, jaką można zrobić, aby przyspieszyć te trzy linijki kodu: można przekształcić je do jednej linii. Spójrzmy na soundex/stage4/soundex4d.py:

    return (digits2.replace('9', '') + '000')[:4]

Umieszczenie tego kodu w jednej linii sprawiło, że stał się on zaledwie odrobinę szybszy, niż soundex4c.py:

C:\samples\soundex\stage4>python soundex4d.py
Woo             W000 4.93624105857
Pilgrim         P426 7.19747593619
Flingjingwaller F452 10.5490700634

Za cenę tego niewielkiego wzrostu wydajności stał się przy okazji znacznie mniej czytelny. Czy warto było to zrobić? Mam nadzieję, że potraficie to dobrze skomentować. Wydajność to nie wszystko. Wysiłki związane z optymalizacją kodu muszą być zawsze równoważone dążeniem do tego, aby kod był czytelny i łatwy w utrzymaniu.