0% found this document useful (0 votes)
305 views50 pages

Python. Receptury

Skarbnica wiedzy dla programistów Pythona Python został opracowany na początku lat "90 i szybko zyskał uznanie programistów. Elastyczny i uniwersalny, pozwalał na stosowanie zasad programowania obiektowego, strukturalnego i funkcyjnego. Był i nadal jest wykorzystywany nie tylko do tworzenia skryptów, ale również przy dużych projektach, takich jak na przykład serwer aplikacji Zope. Decydując się na korzystanie z Pythona, stajemy się częścią niezwykłej społeczności programistów, chętnie pomagającej każdemu, kto chce doskonalić umiejętność posługiwania się tym językiem. Książka "Python. Receptury" to zbiór rozwiązań problemów, z jakimi w codziennej pracy borykają się programiści korzystający z tego języka. Materiały do niej przygotowało ponad 300 członków społeczności Pythona odpowiadających na pytania zadawane na forum internetowym. Rozwiązania zostały przetestowane w praktyce, co ułatwia ich zaimplementowanie we własnych projektach. W książce umówiono m.in.: Przetwarzanie tekstów Operacje na plikach Programowanie obiektowe Przeszukiwanie i sortowanie Łączenie skryptów z bazami danych Testowanie i usuwanie błędów Programowanie wielowątkowe Realizację zadań administracyjnych Obsługę interfejsów użytkownika Tworzenie aplikacji sieciowych Przetwarzanie dokumentów XML Każdy programista Pythona, niezależnie od umiejętności,znajdzie w tej książce coś dla siebie.

Uploaded by

helionsa
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
305 views50 pages

Python. Receptury

Skarbnica wiedzy dla programistów Pythona Python został opracowany na początku lat "90 i szybko zyskał uznanie programistów. Elastyczny i uniwersalny, pozwalał na stosowanie zasad programowania obiektowego, strukturalnego i funkcyjnego. Był i nadal jest wykorzystywany nie tylko do tworzenia skryptów, ale również przy dużych projektach, takich jak na przykład serwer aplikacji Zope. Decydując się na korzystanie z Pythona, stajemy się częścią niezwykłej społeczności programistów, chętnie pomagającej każdemu, kto chce doskonalić umiejętność posługiwania się tym językiem. Książka "Python. Receptury" to zbiór rozwiązań problemów, z jakimi w codziennej pracy borykają się programiści korzystający z tego języka. Materiały do niej przygotowało ponad 300 członków społeczności Pythona odpowiadających na pytania zadawane na forum internetowym. Rozwiązania zostały przetestowane w praktyce, co ułatwia ich zaimplementowanie we własnych projektach. W książce umówiono m.in.: Przetwarzanie tekstów Operacje na plikach Programowanie obiektowe Przeszukiwanie i sortowanie Łączenie skryptów z bazami danych Testowanie i usuwanie błędów Programowanie wielowątkowe Realizację zadań administracyjnych Obsługę interfejsów użytkownika Tworzenie aplikacji sieciowych Przetwarzanie dokumentów XML Każdy programista Pythona, niezależnie od umiejętności,znajdzie w tej książce coś dla siebie.

Uploaded by

helionsa
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 50

IDZ DO

PRZYKADOWY ROZDZIA
SPIS TRECI

KATALOG KSIEK
KATALOG ONLINE
ZAMW DRUKOWANY KATALOG

TWJ KOSZYK
DODAJ DO KOSZYKA

CENNIK I INFORMACJE
ZAMW INFORMACJE
O NOWOCIACH
ZAMW CENNIK

CZYTELNIA
FRAGMENTY KSIEK ONLINE

Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63
e-mail: [email protected]

Python. Receptury
Autorzy: Alex Martelli, Anna Martelli
Ravenscroft, David Ascher
Tumaczenie: Wojciech Moch, Marek Ptlicki
ISBN: 83-246-0214-3
Tytu oryginau: Python Cookbook
Format: B5, stron: 848

Python zosta opracowany na pocztku lat "90 i szybko zyska uznanie programistw.
Elastyczny i uniwersalny, pozwala na stosowanie zasad programowania obiektowego,
strukturalnego i funkcyjnego. By i nadal jest wykorzystywany nie tylko do tworzenia
skryptw, ale rwnie przy duych projektach, takich jak na przykad serwer aplikacji
Zope. Decydujc si na korzystanie z Pythona, stajemy si czci niezwykej
spoecznoci programistw, chtnie pomagajcej kademu, kto chce doskonali
umiejtno posugiwania si tym jzykiem.
Ksika Python. Receptury to zbir rozwiza problemw, z jakimi w codziennej pracy
borykaj si programici korzystajcy z tego jzyka. Materiay do niej przygotowao
ponad 300 czonkw spoecznoci Pythona odpowiadajcych na pytania zadawane
na forum internetowym. Rozwizania zostay przetestowane w praktyce, co uatwia ich
zaimplementowanie we wasnych projektach.
W ksice umwiono m.in.:
Przetwarzanie tekstw
Operacje na plikach
Programowanie obiektowe
Przeszukiwanie i sortowanie
czenie skryptw z bazami danych
Testowanie i usuwanie bdw
Programowanie wielowtkowe
Realizacj zada administracyjnych
Obsug interfejsw uytkownika
Tworzenie aplikacji sieciowych
Przetwarzanie dokumentw XML
Kady programista Pythona, niezalenie od umiejtnoci,
znajdzie w tej ksice co dla siebie

Wstp ........................................................................................................................................15
Rozdzia 1. Tekst .......................................................................................................................31
1.1. Przetwarzanie tekstu po jednym znaku
1.2. Konwersja pomidzy znakami a kodami numerycznymi
1.3. Sprawdzanie, czy obiekt jest podobny do cigu znakw
1.4. Wyrwnywanie cigw znakw
1.5. Usuwanie spacji z kocw cigu znakw
1.6. czenie cigw znakw
1.7. Odwracanie kolejnoci sw lub znakw w cigu znakw
1.8. Sprawdzanie, czy cig znakw zawiera pewien zestaw znakw
1.9. Upraszczanie uycia metody translate
1.10. Filtrowanie cigu znakw na podstawie znakw z okrelonego zbioru
1.11. Sprawdzanie, czy cig znakw jest tekstowy czy binarny
1.12. Kontrolowanie wielkoci znakw
1.13. Odczytywanie podcigw
1.14. Zmiany wci w wielowierszowym cigu znakw
1.15. Rozszerzanie i zawanie tabulacji
1.16. Wstawianie zmiennych do cigu znakw
1.17. Wstawianie zmiennych do cigu znakw w Pythonie 2.4
1.18. Podmiana wielu wzorcw w jednym przebiegu
1.19. Sprawdzanie kocwek w cigu znakw
1.20. Obsuga tekstw midzynarodowych za pomoc Unikodu
1.21. Konwertowanie pomidzy Unikodem i prostym tekstem
1.22. Wypisywanie znakw Unikodu na standardowe wyjcie
1.23. Kodowanie danych w formatach XML i HTML
1.24. Przygotowanie cigu znakw nierozrniajcego wielkoci liter
1.25. Konwertowanie dokumentw HTML na zwyky tekst na terminalu uniksowym

37
38
39
41
42
42
45
47
50
52
55
57
58
61
63
65
67
69
72
73
76
78
79
82
85

Rozdzia 2. Pliki .......................................................................................................................89


2.1. Czytanie z pliku
2.2. Zapisywanie do pliku
2.3. Wyszukiwanie i podmiany tekstu w pliku
2.4. Odczytanie z pliku okrelonego wiersza
2.5. Zliczanie wierszy w pliku
2.6. Przetwarzanie wszystkich sw z pliku
2.7. Wejcie i wyjcie o dostpie swobodnym
2.8. Aktualizowanie pliku o dostpie swobodnym
2.9. Odczytywanie danych z plikw .zip
2.10. Obsuga plikw .zip wewntrz cigu znakw
2.11. Archiwizowanie drzewa plikw w skompresowanym pliku .tar
2.12. Wysyanie danych binarnych na standardowe wyjcie w systemach Windows
2.13. Stosowanie skadni podobnej do skadni obiektw iostream z jzyka C++
2.14. Przewijanie pliku wejciowego do pocztku
2.15. Przystosowywanie obiektw plikopodobnych
do obiektw rzeczywistych plikw
2.16. Przegldanie drzew katalogw
2.17. Zamiana jednego rozszerzenia plikw na inne w caym drzewie katalogw
2.18. Wyszukiwanie pliku na podstawie cieki wyszukiwania
2.19. Wyszukiwanie plikw na postawie cieki wyszukiwania i wzorca
2.20. Wyszukiwanie plikw zgodnie ze ciek wyszukiwania Pythona
2.21. Dynamiczne modyfikowanie cieki wyszukiwania Pythona
2.22. Wyznaczanie cieki wzgldnej z jednego katalogu do drugiego
2.23. Odczytywanie znakw niebuforowanych w sposb niezaleny od platformy
2.24. Zliczanie stron dokumentw PDF w systemie Mac OS X
2.25. Modyfikowanie atrybutw plikw w systemach Windows
2.26. Pobieranie tekstu z dokumentw OpenOffice.org
2.27. Pobieranie tekstu z dokumentw Microsoft Word
2.28. Blokowanie plikw za pomoc midzyplatformowego API
2.29. Wersjonowanie nazw plikw
2.30. Wyliczanie sum kontrolnych CRC-64

93
97
98
99
100
103
105
106
108
110
111
113
114
115
118
119
121
122
123
124
125
127
129
130
131
132
133
134
136
138

Rozdzia 3. Czas i pienidz ....................................................................................................141


3.1. Wyliczanie dnia jutrzejszego i wczorajszego
3.2. Ktrego by ostatni pitek?
3.3. Wyliczanie przedziau czasu i zakresu dat
3.4. Sumowanie czasw trwania piosenek
3.5. Wyznaczanie liczby dni roboczych pomidzy dwoma datami
3.6. Automatyczne wyznaczanie dat wit
3.7. Elastyczne odczytywanie dat

Spis treci

147
149
151
152
153
155
158

3.8. Sprawdzanie, czy aktualnie mamy czas letni czy zimowy


3.9. Konwersja stref czasowych
3.10. Powtarzanie wywoania polecenia
3.11. Tworzenie terminarza polece
3.12. Arytmetyka dziesitna
3.13. Formatowanie liczb dziesitnych jako waluty
3.14. Python jako prosta maszyna sumujca
3.15. Sprawdzanie sumy kontrolnej karty kredytowej
3.16. Sprawdzanie kursw wymiany walut

159
160
162
163
165
167
170
173
174

Rozdzia 4. Skrty ...................................................................................................................177


4.1. Kopiowanie obiektu
4.2. Konstruowanie list za pomoc list skadanych
4.3. Zwracanie elementu listy, o ile istnieje
4.4. Przegldanie w ptli elementw sekwencji i ich indeksw
4.5. Tworzenie list bez wspdzielenia referencji
4.6. Spaszczanie zagniedonej sekwencji
4.7. Usuwanie lub przestawianie kolumn na licie wierszy
4.8. Transponowanie tablic dwuwymiarowych
4.9. Pobieranie wartoci ze sownika
4.10. Dodawanie pozycji do sownika
4.11. Budowanie sownika bez naduywania cudzysoww
4.12. Budowanie sownika na podstawie listy kluczy i wartoci
4.13. Wydobywanie podzbioru elementw sownika
4.14. Odwracanie sownika
4.15. Wizanie kilku wartoci z kluczami sownika
4.16. Stosowanie sownikw do wywoywania metod lub funkcji
4.17. Wyszukiwanie sum i czci wsplnych sownikw
4.18. Kolekcja elementw nazwanych
4.19. Przypisywanie i testowanie za pomoc jednej instrukcji
4.20. Stosowanie w Pythonie instrukcji printf
4.21. Losowe wybieranie elementw z zadanym prawdopodobiestwem
4.22. Obsugiwanie wyjtkw wewntrz wyrae
4.23. Sprawdzanie, czy nazwa jest zdefiniowana w danym module

179
182
184
185
186
188
191
192
194
196
197
199
201
203
204
206
208
210
212
214
215
217
219

Rozdzia 5. Szukanie i sortowanie ....................................................................................... 221


5.1. Sortowanie sownika
5.2. Sortowanie listy cigw znakw bez uwzgldniania wielkoci liter
5.3. Sortowanie listy obiektw na podstawie ich atrybutw
5.4. Sortowanie kluczy lub indeksw na podstawie zwizanych z nimi wartoci
5.5. Sortowanie cigw znakw zawierajcych liczby

Spis treci

226
227
229
231
234

5.6. Przetwarzanie wszystkich elementw listy w kolejnoci losowej


5.7. Utrzymywanie porzdku w sekwencji
w czasie dodawania do niej nowych elementw
5.8. Pobieranie kilku najmniejszych elementw sekwencji
5.9. Wyszukiwanie elementw w sekwencji posortowanej
5.10. Wybieranie n-tego najmniejszego elementu w sekwencji
5.11. Algorytm quicksort w trzech wierszach kodu
5.12. Wykonywanie czstych testw obecnoci elementw sekwencji
5.13. Wyszukiwanie podsekwencji
5.14. Wzbogacanie typu dict o moliwo wprowadzania ocen
5.15. Sortowanie nazwisk i rozdzielanie ich za pomoc inicjaw

235
237
239
241
243
246
249
251
253
257

Rozdzia 6. Programowanie obiektowe .............................................................................. 259


6.1. Konwertowanie midzy skalami temperatury
6.2. Definiowanie staych
6.3. Ograniczanie dodawania atrybutw
6.4. czenie wyszukiwa danych w sowniku
6.5. Automatyczne delegacje jako alternatywa dla dziedziczenia
6.6. Delegowanie metod specjalnych w obiektach proxy
6.7. Implementowanie krotek z nazywanymi elementami
6.8. Unikanie stosowania powtarzalnych metod dostpu do waciwoci
6.9. Tworzenie szybkiej kopii obiektu
6.10. Przechowywanie referencji metod powizanych
bez wstrzymywania mechanizmu oczyszczania pamici
6.11. Implementowanie bufora cyklicznego
6.12. Wykrywanie dowolnych zmian stanu egzemplarza
6.13. Sprawdzanie, czy obiekt ma wymagane atrybuty
6.14. Implementowanie wzorca Projektu Stanu
6.15. Implementowanie wzorca projektowego Singleton
6.16. Zastpowanie wzorca projektowego Singleton idiomem Borg
6.17. Implementowanie wzorca projektowego Obiektu Zerowego
6.18. Automatyczne inicjowanie zmiennych egzemplarzy
na podstawie argumentw metody __init__
6.19. Wywoywanie metody __init__ w klasie bazowej, jeli taka metoda istnieje
6.20. Spjne i bezpieczne kooperatywne wywoania metod w klasach nadrzdnych

266
268
270
272
274
277
280
282
284
286
289
292
295
299
301
302
307
310
312
315

Rozdzia 7. Trwao danych i bazy danych ..........................................................................317


7.1. Serializowanie danych za pomoc moduu marshal
7.2. Serializowanie danych za pomoc moduw pickle i cPickle
7.3. Stosowanie kompresji w poczeniu z serializacj
7.4. Wykorzystanie moduu cPickle wobec klas i ich egzemplarzy

Spis treci

320
322
325
326

7.5. Przechowywanie metod powizanych


w sposb pozwalajcy na ich serializacj
7.6. Serializacja obiektw kodu
7.7. Modyfikowanie obiektw za pomoc moduu shelve
7.8. Uytkowanie bazy danych Berkeley DB
7.9. Uzyskiwanie dostpu do bazy danych MySQL
7.10. Zapisywanie danych typu BLOB w bazie danych MySQL
7.11. Zapisywanie danych typu BLOB w bazie danych PostgreSQL
7.12. Zapisywanie danych typu BLOB w bazie danych SQLite
7.13. Generowanie sownika odwzorowujcego nazwy pl na numery kolumn
7.14. Wykorzystywanie moduu dtuple w celu uzyskania elastycznego dostpu
do wynikw zapytania
7.15. Wypisywanie zawartoci kursora bazy danych
7.16. Ujednolicenie stylu przekazywania parametrw w rnych moduach DB API
7.17. Wykorzystywanie Microsoft Jet poprzez interfejs ADO
7.18. Dostp do bazy danych JDBC z poziomu servletu Jythona
7.19. Wykorzystywanie w Jythonie interfejsu ODBC
do odczytywania danych z Excela

329
331
334
336
339
341
342
344
345
347
349
352
354
356
358

Rozdzia 8. Testy i debugowanie ......................................................................................... 361


8.1. Wyczanie wykonania niektrych instrukcji warunkowych i ptli
8.2. Pomiar wykorzystania pamici w Linuksie
8.3. Debugowanie procesu oczyszczania pamici
8.4. Przechwytywanie i zachowywanie wyjtkw
8.5. ledzenie wyrae i komentarzy w trybie debugowania
8.6. Pobieranie dokadniejszych informacji ze ladw
8.7. Automatyczne uruchamianie debugera po nieprzechwyconym wyjtku
8.8. Najprostsze uruchamianie testw moduowych
8.9. Automatyczne uruchamianie testw moduowych
8.10. Uywanie w Pythonie 2.4 moduu doctest w poczeniu z moduem unittest
8.11. Sprawdzanie w ramach testw moduowych,
czy warto mieci si w przedziale

362
363
365
366
368
371
374
375
377
378
380

Rozdzia 9. Procesy, wtki i synchronizacja ........................................................................383


9.1. Synchronizowanie wszystkich metod w obiekcie
9.2. Zatrzymywanie wtku
9.3. Uywanie klasy Queue.Queue jako kolejki priorytetowej
9.4. Praca ze zbiorem wtkw
9.5. Rwnolege wykonywanie jednej funkcji z wieloma zestawami argumentw
9.6. Koordynacja wtkw przez proste przekazywanie komunikatw
9.7. Zachowywanie informacji w poszczeglnych wtkach
9.8. Kooperatywna wielozadaniowo bez wtkw

Spis treci

387
390
392
394
397
399
402
405

9.9. Sprawdzanie, czy w systemach Windows dziaa ju inny egzemplarz skryptu


9.10. Przetwarzanie komunikatw systemu Windows za pomoc funkcji
MsgWaitForMultipleObjects
9.11. Uruchamianie zewntrznego procesu za pomoc funkcji popen
9.12. Przechwytywanie strumieni wyjciowego i bdw z polecenia uniksowego
9.13. Rozwidlanie procesu demona w Uniksie

407
409
412
413
416

Rozdzia 10. Administracja systemem .................................................................................. 419


10.1. Generowanie losowych hase
420
10.2. Generowanie hase atwych do zapamitania
422
10.3. Uwierzytelnianie uytkownikw za pomoc serwera POP
424
10.4. Obliczanie liczby odson stron serwera Apache z poszczeglnych adresw IP 426
10.5. Obliczanie wspczynnika zbuforowanych da klientw serwera Apache 428
10.6. Uruchomienie edytora tekstw ze skryptu
429
10.7. Kopie zapasowe plikw
431
10.8. Selektywne kopiowanie pliku skrzynki pocztowej
433
10.9. Budowanie biaej listy adresw e-mail w oparciu
o zawarto skrzynki pocztowej
434
10.10. Blokowanie duplikatw e-maili
435
10.11. Kontrola dziaania podsystemu dwikowego w Windows
437
10.12. Rejestrowanie i odrejestrowywanie bibliotek DLL w Windows
438
10.13. Sprawdzanie i modyfikacja listy zada
uruchamianych przez system Windows podczas rozruchu
440
10.14. Utworzenie udostpnianego zasobu sieciowego w systemie Windows
441
10.15. Podczenie do dziaajcego egzemplarza Internet Explorera
442
10.16. Odczyt listy kontaktw programu Microsoft Outlook
444
10.17. Pobieranie szczegowych informacji o systemie Mac OS X
446

Rozdzia 11. Interfejsy uytkownika .....................................................................................449


11.1. Prezentacja wskanika postpu na konsoli tekstowej
11.2. Unikanie konstrukcji lambda przy tworzeniu funkcji zwrotnych
11.3. Wykorzystanie domylnych wartoci i ogranicze
przy korzystaniu z funkcji tkSimpleDialog
11.4. Umoliwienie zmiany kolejnoci pozycji w obiekcie klasy Listbox
za pomoc myszy
11.5. Wpisywanie specjalnych znakw w elementach sterujcych biblioteki Tkinter
11.6. Osadzanie obrazw GIF w kodzie skryptu
11.7. Przeksztacanie formatw graficznych
11.8. Implementacja stopera za pomoc biblioteki Tkinter
11.9. Wykorzystanie interfejsw graficznych
wraz z asynchroniczn obsug operacji wejcia-wyjcia za pomoc wtkw
11.10. Wykorzystanie elementu Tree z programu IDLE

Spis treci

451
453
454
455
457
459
460
463
465
469

11.11. Obsuga wielokrotnych wartoci w wierszu


w obiekcie Listbox moduu Tkinter
11.12. Kopiowanie metod i opcji wymiarowania
pomidzy kontrolkami biblioteki Tkinter
11.13. Implementacja kontrolki notatnika z zakadkami w bibliotece Tkinter
11.14. Wykorzystanie obiektw klasy Notebook biblioteki wxPython z panelami
11.15. Implementacja wtyczki do programu ImageJ w Jythonie
11.16. Przegldanie obrazw bezporednio z adresu URL
za pomoc Swinga i Jythona
11.17. Pobieranie danych od uytkownika w systemie Mac OS
11.18. Programowe generowanie interfejsu Cocoa GUI
11.19. Implementacja stopniowo pojawiajcych si okien z uyciem IronPythona

471
474
476
479
480
481
482
484
486

Rozdzia 12. Przetwarzanie formatu XML ...........................................................................489


12.1. Sprawdzanie poprawnoci struktury danych XML
12.2. Zliczanie znacznikw w dokumencie
12.3. Wydobywanie tekstu z dokumentu XML
12.4. Wykrywanie standardu kodowania dokumentu XML
12.5. Przeksztacanie dokumentu XML w drzewo obiektw Pythona
12.6. Usuwanie z drzewa DOM wzw zawierajcych
wycznie cigi biaych znakw
12.7. Parsowanie plikw XML zapisanych przez Microsoft Excel
12.8. Walidacja dokumentw XML
12.9. Filtrowanie elementw nalecych do okrelonej przestrzeni nazw
12.10. czenie wystpujcych po sobie zdarze tekstowych
w jedn cao za pomoc filtra SAX
12.11. Wykorzystanie MSHTML-a do parsowania formatu XML lub HTML

491
492
494
495
497
499
500
502
503
505
508

Rozdzia 13. Programowanie sieciowe ................................................................................. 511


13.1. Przekazywanie komunikatw za porednictwem gniazd datagramowych
13.2. Pobieranie dokumentacji z WWW
13.3. Filtrowanie listy serwerw FTP
13.4. Odczytywanie czasu z serwera za pomoc protokou SNTP
13.5. Wysyanie listw e-mail w formacie HTML
13.6. Wykorzystanie wiadomoci w formacie MIME do wysyki wielu plikw
13.7. Rozkadanie na czci wieloczciowej wiadomoci w formacie MIME
13.8. Usuwanie zacznikw z listw elektronicznych
13.9. Poprawianie bdnych obiektw email uzyskanych za pomoc parsera
email.FeedParser z Pythona 2.4
13.10. Interaktywne przegldanie skrzynki pocztowej POP3
13.11. Wykrywanie nieaktywnych komputerw
13.12. Monitorowanie sieci z uyciem HTTP

Spis treci

513
515
516
517
518
521
523
524
526
528
531
535

13.13. Przekazywanie i przeadresowywanie portw sieciowych


13.14. Tunelowanie pocze SSL przez serwer poredniczcy
13.15. Implementacja klienta usugi dynamicznego DNS
13.16. Poczenie z serwerem IRC i zapis dziennika rozmw na dysku
13.17. Wykorzystanie serwerw LDAP

537
540
543
546
547

Rozdzia 14. Programowanie WWW ....................................................................................549


14.1. Sprawdzanie, czy CGI dziaa poprawnie
14.2. Obsuga adresw URL w skryptach CGI
14.3. Przesyanie plikw na serwer WWW za pomoc CGI
14.4. Sprawdzanie istnienia strony WWW
14.5. Sprawdzanie typu zawartoci za pomoc HTTP
14.6. Wznawianie pobierania pliku za pomoc HTTP
14.7. Obsuga cookies przy pobieraniu stron WWW
14.8. Uwierzytelnianie poczenia HTTPS
nawizywanego za porednictwem porednika
14.9. Uruchamianie serwletw z uyciem Jythona
14.10. Wyszukiwanie cookie przegldarki Internet Explorer
14.11. Generowanie plikw OPML
14.12. Pobieranie wiadomoci RSS
14.13. Przeksztacanie danych w strony WWW z uyciem szablonw stron
14.14. Renderowanie dowolnych obiektw z uyciem Nevow

550
553
555
556
558
559
560
563
564
566
567
570
573
576

Rozdzia 15. Oprogramowanie rozproszone ....................................................................... 579


15.1. Wywoanie metody XML-RPC
15.2. Obsuga da XML-RPC
15.3. Serwer XML-RPC wykorzystujcy bibliotek Medusa
15.4. Zdalne zamykanie serwera XML-RPC
15.5. Implementowanie mechanizmw ulepszajcych na potrzeby klasy
SimpleXMLRPCServer
15.6. Implementacja interfejsu graficznego wxPython dla serwera XML-RPC
15.7. Wykorzystanie mechanizmu Twisted Perspective Broker
15.8. Implementacja serwera i klienta CORBA
15.9. Zdalne uruchamianie polece powoki z uyciem telnetlib
15.10. Zdalne uruchamianie polece powoki z uyciem SSH
15.11. Uwierzytelnianie z uyciem klienta SSL za pomoc protokou HTTPS

582
583
585
587
588
589
592
594
597
599
602

Rozdzia 16. Programy o programach ..................................................................................605


16.1. Sprawdzanie, czy cig znakw jest poprawn liczb
16.2. Importowanie dynamicznie wygenerowanego moduu
16.3. Importowanie moduw, ktrych nazwy s ustalane w trakcie wykonania
16.4. Wizanie parametrw z funkcjami (metoda Curryego)

10

Spis treci

611
612
613
615

16.5. Dynamiczne komponowanie funkcji


16.6. Kolorowanie kodu rdowego w Pythonie z uyciem wbudowanego
mechanizmu analizy leksykalnej
16.7. czenie i dzielenie tokenw
16.8. Sprawdzanie, czy cig znakw ma odpowiednio zrwnowaone nawiasy
16.9. Symulowanie typu wyliczeniowego w Pythonie
16.10. Odczyt zawartoci rozwinicia listy w trakcie jej budowania
16.11. Automatyczna kompilacja skryptw za pomoc py2exe
do postaci programw wykonywalnych systemu Windows
16.12. czenie skryptu gwnego i moduw
w jeden plik wykonywalny systemu Unix

618
619
622
624
627
629
631
633

Rozdzia 17. Rozszerzanie i osadzanie ................................................................................. 637


17.1. Implementacja prostego typu rozszerze
17.2. Implementacja prostego rozszerzenia za pomoc jzyka Pyrex
17.3. Wykorzystanie w Pythonie biblioteki napisanej w C++
17.4. Wywoywanie funkcji zdefiniowanych w bibliotekach DLL
systemu Windows
17.5. Wykorzystanie moduw wygenerowanych z uyciem SWIG
w rodowisku wielowtkowym
17.6. Przeksztacenie sekwencji Pythona na tablic jzyka C z uyciem protokou
PySequence_Fast
17.7. Odczyt elementw sekwencji Pythona
z wykorzystaniem protokou iteratorw
17.8. Zwracanie wartoci None w funkcji rozszerze w jzyku C
17.9. Debugowanie za pomoc gdb dynamicznie adowanych rozszerze Pythona
17.10. Debugowanie problemw z pamici

640
643
645
648
650
651
655
658
659
660

Rozdzia 18. Algorytmy .........................................................................................................663


18.1. Usuwanie duplikatw z sekwencji
18.2. Usuwanie duplikatw z sekwencji z zachowaniem kolejnoci
18.3. Generowanie losowych prbek z powtrzeniami
18.4. Generowanie losowych prbek bez powtrze
18.5. Zachowywanie wartoci zwracanych przez funkcje
18.6. Implementacja kontenera FIFO
18.7. Buforowanie obiektw w kolejce FIFO
18.8. Implementacja typu wielozbiorowego (kolekcji)
18.9. Zasymulowanie w Pythonie operatora trjargumentowego
18.10. Wyliczanie liczb pierwszych
18.11. Formatowanie liczb cakowitych w postaci dwjkowej
18.12. Formatowanie liczb cakowitych w notacji o dowolnej podstawie
18.13. Przeksztacanie liczb na notacj uamkow z uyciem cigw Fareya

Spis treci

667
669
673
674
675
677
679
681
684
687
690
692
694

11

18.14. Obliczenia arytmetyczne z propagacj bdu


18.15. Sumowanie liczb z jak najwiksz precyzj
18.16. Symulacja liczb zmiennoprzecinkowych
18.17. Wyliczanie powoki wypukej oraz rednicy zbioru punktw
w przestrzeni dwuwymiarowej

696
698
700
703

Rozdzia 19. Iteratory i generatory ....................................................................................... 707


19.1. Implementacja funkcji range() o przyrostach zmiennoprzecinkowych
19.2. Budowanie listy z dowolnego obiektu iterowalnego
19.3. Generowanie cigu Fibonacciego
19.4. Rozpakowanie kilku wartoci w operacji wielokrotnego przypisania
19.5. Automatyczne rozpakowanie odpowiedniej liczby elementw
19.6. Dzielenie obiektu iterowanego na rozszerzone wycinki o szerokoci n
19.7. Przegldanie sekwencji za pomoc czciowo pokrywajcych si okien
19.8. Rwnolege przegldanie wielu obiektw iterowalnych
19.9. Przegldanie iloczynu kartezjaskiego wielu obiektw iterowalnych
19.10. Odczytywanie zawartoci pliku po akapitach
19.11. Odczyt wierszy ze znakami kontynuacji
19.12. Przegldanie strumienia blokw danych
i interpretowanie go jako strumienia wierszy
19.13. Pobieranie duych zestaww wynikw z bazy danych
z wykorzystaniem generatora
19.14. czenie sekwencji posortowanych
19.15. Generowanie permutacji, kombinacji i selekcji
19.16. Generowanie rozkadw liczb cakowitych
19.17. Tworzenie duplikatu iteratora
19.18. Podgldanie wartoci iteratora w przd
19.19. Uproszczenie wtkw konsumentw kolejek
19.20. Uruchamianie iteratora w innym wtku
19.21. Obliczanie raportu podsumowujcego za pomoc itertools.groupby()

711
713
715
716
718
720
722
725
728
731
733
734
735
737
740
742
744
747
750
751
753

Rozdzia 20. Deskryptory, dekoratory i metaklasy ............................................................. 757


20.1. Przydzielanie nowych wartoci domylnych dla kadego wywoania funkcji
20.2. Kodowanie waciwoci za pomoc funkcji zagniedonych
20.3. Tworzenie aliasw wartoci atrybutw
20.4. Buforowanie wartoci atrybutw
20.5. Wykorzystanie jednej metody w charakterze akcesora wielu atrybutw
20.6. Dodawanie funkcjonalnoci klasie przez opakowanie metody
20.7. Wzbogacanie funkcjonalnoci klasy przez modyfikacj wszystkich metod
20.8. Dodawanie metod do egzemplarza klasy w czasie wykonania
20.9. Sprawdzanie, czy zostay zaimplementowane okrelone interfejsy

12

Spis treci

759
762
764
766
768
770
773
775
777

20.10. Odpowiednie wykorzystanie metod __new__ i __init__


we wasnych metaklasach
20.11. Zezwalanie na potokowe wykonywanie mutujcych metod obiektw list
20.12. Implementacja bardziej zwartej skadni wywoa metod klasy nadrzdnej
20.13. Inicjalizacja atrybutw egzemplarza bez uycia metody __init__()
20.14. Automatyczna inicjalizacja atrybutw egzemplarza
20.15. Automatyczna aktualizacja klas istniejcych obiektw
po ponownym zaadowaniu moduu
20.16. Wizanie staych w czasie kompilacji
20.17. Rozwizywanie konfliktw metaklas

779
781
782
784
786
789
793
797

Skorowidz .............................................................................................................................. 801

Spis treci

13

ROZDZIA 5.

5.0. Wprowadzenie
Pomysodawca: Tim Peters, PythonLabs
W latach 60. producenci komputerw szacowali, e 25% czasu pracy wszystkich sprzedanych
przez nich urzdze przeznaczane jest na zadania zwizane z sortowaniem. W rzeczywistoci byo wiele takich instalacji, w ktrych zadanie sortowania zajmowao ponad poow czasu pracy komputerw. Z tych danych mona wywnioskowa, e: a) istnieje wiele
bardzo wanych powodw sortowania, b) wielokrotnie sortuje si dane bez potrzeby lub c)
powszechnie stosowane byy nieefektywne algorytmy sortowania.
Donald Knuth
The Art of Computer Programming, tom 3,
Sorting and Searching, strona 3.
Wspaniaa praca profesora Knutha na temat sortowania i wyszukiwania ma niemal 800 stron
zoonego tekstu technicznego. W praktyce Pythona ca t prac redukuje si do dwch imperatyww (kto inny przeczyta to opase tomisko, dlatego Czytelnik zostanie zwolniony
z tego obowizku):
Jeeli musimy sortowa dane, to najlepiej bdzie znale sposb na wykorzystanie wbu-

dowanej w Pythona metody sort zajmujcej si sortowaniem list.


Jeeli musimy przeszukiwa dane, to najlepiej bdzie znale sposb na wykorzystanie

wbudowanego typu sownikw.


Wiele receptur z niniejszego rozdziau kieruje si wanie tymi dwoma zasadami. Najczciej
stosowanym w nich rozwizaniem jest implementacja wzorca DSU (ang. decorate-sort-undecorate
dekoruj-sortuj-usu dekoracj). Jest to najoglniejsze rozwizanie polegajce na utworzeniu
listy pomocniczej, ktr mona posortowa za pomoc domylnej, szybkiej metody sort. Ta
technika naley do najuyteczniejszych spord wszystkich prezentowanych w tym rozdziale.
Co wicej, wzorzec DSU jest tak dalece przydatny, e w Pythonie 2.4 wprowadzono kilka nowych funkcji uatwiajcych jego stosowanie. W efekcie w Pythonie 2.4 wiele z prezentowanych
receptur mona jeszcze bardziej uproci, dlatego analiza starszych receptur zostaa uzupeniona o odpowiednie instrukcje.

221

Wzorzec DSU polega na wykorzystaniu niezwykych waciwoci wbudowanych w Pythona


porwna: sekwencje s porwnywane leksykograficznie. Kolejno leksykograficzna jest uoglnieniem wszystkich znanych nam regu porwnywania cigw znakw (czyli kolejnoci alfabetycznej), ktre rozcignite zostay na krotki i listy. W przypadku, gdy zmienne s1 i s2
s sekwencjami, wbudowana funkcja cmp(s1, s2) jest rwnowana z nastpujcym kodem
Pythona:
def lexcmp(s1, s2):
# Znajd nierwn par po lewej stronie.
i = 0
while i < len(s1) and i < len(s2):
outcome = cmp(s1[i], s2[i])
if outcome:
return outcome
i += 1
# Wszystkie rwne do momentu wyczerpania przynajmniej jednej sekwencji.
return cmp(len(s1), len(s2))

Podany kod poszukuje pierwszej pary nierwnych sobie elementw. Po znalezieniu takiej pary
na jej podstawie okrelany jest wynik porwnania. W przeciwnym przypadku, jeeli jedna
z sekwencji jest dokadnym przedrostkiem drugiej, to taki przedrostek uznawany jest za mniejsz
sekwencj. W kocu, jeeli nie obowizuje aden z wymienionych wyej przypadkw, znaczy
to, e sekwencje s identyczne i w zwizku z tym uznawane s za rwne. Oto kilka przykadw:
>>>
0
>>>
1
>>>
-1
>>>
-1

cmp((1, 2, 3), (1, 2, 3))

# identyczne

cmp((1, 2, 3), (1, 2))

# pierwsza wiksza, poniewa druga jest przedrostkiem

cmp((1, 100), (2, 1))

# pierwsza jest mniejsza, poniewa 1<2

cmp((1, 2), (1, 3))

# pierwsza jest mniejsza, poniewa 1==1, ale 2 < 3

Bezporedni konsekwencj takich porwna leksykograficznych jest to, e w przypadku, gdy


chcielibymy posortowa list obiektw wedug klucza gwnego, a w przypadku rwnoci
wartoci tego klucza wedug klucza drugorzdnego, naley zbudowa list krotek, gdzie
kada krotka przechowuje klucz gwny, klucz drugorzdny i oryginalny obiekt, dokadnie
w tej kolejnoci. Krotki porwnywane s leksykograficznie, dlatego taka kolejno ich elementw automatycznie zaatwia spraw. W czasie porwnywania krotek najpierw porwnywane
s klucze gwne, a jeeli s one rwne, to (tylko w takim przypadku) porwnywane s klucze drugorzdne.
Podawane w tym rozdziale przykady wzorca DSU prezentuj wiele zastosowa takiego postpowania. Technik DSU mona stosowa z dowoln liczb kluczy. Krotki mona uzupenia
o kolejne klucze, umieszczajc je w takiej kolejnoci, w jakiej maj by porwnywane. W Pythonie 2.4 ten sam efekt mona uzyska, podajc do metody sort opcjonalny parametr key=, tak
jak robione jest to w niektrych recepturach. Stosowanie parametru key= metody sort jest prostsze, efektywniejsze i szybsze od rcznego tworzenia listy krotek.
Inn z nowoci wprowadzonych do Pythona 2.4 w zakresie sortowania jest bardzo wygodny
skrt: wbudowana funkcja sorted pozwalajca na sortowanie dowolnego elementu iterowalnego. Takie sortowanie nie odbywa si w miejscu, ale przez kopiowanie danych do nowej
listy. W Pythonie 2.3 (oprcz nowego opcjonalnego parametru nazywanego, ktry mona stosowa zarwno w funkcji sorted, jak i w metodzie list.sort) to samo dziaanie mona zaimplementowa bez wikszego wysiku:

222

Rozdzia 5. Szukanie i sortowanie

def sorted_2_3(iterable):
alist = list(iterable)
alist.sort()
return alist

Operacje kopiowania i sortowania listy nie s operacjami trywialnymi, a wbudowana funkcja


sorted i tak musi je wykona, dlatego przeksztacenie funkcji sorted w funkcj wbudowan
nie dao praktycznie adnego wzrostu prdkoci jej dziaania. Jedyn zalet jest tu po prostu
wygoda. Likwidacja koniecznoci powtarzalnego wpisywania nawet czterech prostych wierszy
kodu i wiadomo, e pewne elementy mamy zawsze pod rk, naprawd stanowi ogromn popraw wygody pracy. Z drugiej strony, zaledwie niewielka cz prostych funkcji uywana jest
na tyle powszechnie, eby usprawiedliwiao to rozbudow zbioru elementw wbudowanych.
W Pythonie 2.4 do tego zbioru dodane zostay funkcje sorted i reversed, poniewa w cigu
ostatnich lat czsto pojawiay si proby o dodanie ich do elementw wbudowanych.
Najwiksza zmiana w mechanizmach sortowania stosowanych w Pythonie od czasu pierwszego wydania tej ksiki polegaa na wprowadzeniu do Pythona 2.3 nowej implementacji algorytmu sortowania. Pierwsz widoczn konsekwencj tej zmiany by wzrost prdkoci w wielu
typowych przypadkach oraz fakt, e nowy algorytm jest stabilny, co oznacza, e dwa elementy,
ktre w oryginalnej licie s sobie rwne, w posortowanej licie zachowuj swoj wzgldn
kolejno. Nowa implementacja bya niezwykle udana, a szanse na przygotowanie lepszej byy
tak nike, e Guido da si przekona, e w Pythonie metoda list.sort ju zawsze bdzie
stabilna. Nowa funkcja sortujca pojawia si ju w wersji 2.3, ale gwarancja stabilnoci algorytmu sortowania wprowadzona zostaa dopiero w wersji 2.4. Mimo to historia algorytmw sortowania kae nam pamita, e zawsze mog zosta odkryte jeszcze lepsze algorytmy sortowania. W zwizku z tym naleaoby tutaj poda skrcon histori sortowania w Pythonie.

Krtka historia sortowania w Pythonie


We wczesnych wersjach Pythona metoda list.sort wykorzystywaa funkcj qsort pochodzc z biblioteki jzyka C. Takie rozwizanie nie sprawdzao si z wielu powodw, ale przede
wszystkim dlatego, e jako funkcji qsort nie bya jednolita na wszystkich komputerach. Niektre wersje dziaay wyjtkowo wolno w przypadkach, gdy miay posortowa list z wieloma jednakowymi wartociami albo uoon w odwrotnej kolejnoci sortowania. Zdarzay si
te wersje powodujce zawieszenie si procesu, poniewa nie pozwalay na stosowanie rekursji. Zdefiniowana przez uytkownika funkcja __cmp__ moe wywoywa metod list.sort,
dlatego w efekcie ubocznym jedno wywoanie list.sort moe powodowa kolejne takie wywoania w zwizku z wykonywanymi porwnaniami. Na niektrych platformach funkcja qsort
nie bya w stanie poradzi sobie z tak sytuacj. Zdefiniowana (w sposb gupi lub zoliwy)
przez uytkownika funkcja __cmp__ moe te zmutowa list w czasie jej sortowania i dlatego na wielu platformach funkcja qsort moe sobie nie radzi z takimi wanie sytuacjami.
W Pythonie przygotowana zostaa zatem specjalna implementacja algorytmu szybkiego sortowania (ang. quicksort algorithm). Bya ona zmieniana w kadej nastpnej wersji jzyka, poniewa znajdowane byy kolejne przypadki rzeczywistych zastosowa, w ktrych aktualna
w danym momencie implementacja okazywaa si niezwykle powolna. Jak si okazuje, quicksort to wyjtkowo delikatny algorytm!
W Pythonie 1.5.2 algorytm quicksort zosta zastpiony hybryd algorytmw sortowania przez
wybieranie (ang. samplesort) i sortowania przez wstawienia (ang. insertionsort). Ta implementacja nie ulega zmianie przez ponad cztery lata, a do momentu pojawienia si Pythona 2.3.
5.0. Wprowadzenie

223

Algorytm samplesort mona traktowa jak wariant algorytmu quicksort, w ktrym uywane s
bardzo due prbki do wybierania elementu rozdzielajcego (metoda ta rekursywnie sortuje
algorytmem samplesort duy losowy podzbir elementw i wybiera z nich median). Taki
wariant sprawia, e prawie nie jest moliwy wariant z czasem sortowania proporcjonalnym do
kwadratu liczby elementw, a liczba porwna w typowych przypadkach jest zdecydowanie
blisza teoretycznemu minimum.
Niestety, algorytm samplesort jest na tyle skomplikowany, e jego administracja danymi okazuje si zdecydowanie zbyt rozbudowana przy pracach z niewielkimi listami. Z tego wanie
powodu mae listy (a take niewielkie wykrojenia powstajce w wyniku podziaw dokonywanych przez ten algorytm) obsugiwane s za pomoc algorytmu insertionsort (jest to zwyczajny algorytm sortowania przez wstawianie, ale do okrelania pozycji kadego z elementw korzysta on z mechanizmw szukania binarnego). W wikszoci tekstw na temat sortowania
zaznaczane jest, e takie podziay nie s warte naszej uwagi, ale wynika to z faktu, e w tekstach
tych uznaje si, e operacja porwnania elementw jest mniej czasochonna od operacji zamiany
tych elementw w pamici, co nie jest prawd w algorytmach sortowania stosowanych w Pythonie. Przeniesienie obiektu jest operacj bardzo szybk, poniewa kopiowana jest tylko referencja tego obiektu. Z kolei porwnanie dwch obiektw jest operacj kosztown, poniewa
za kadym razem uruchamiany jest kod przeznaczony do wyszukiwania obiektw w pamici
oraz kod odpowiedni do wykonania porwnania danych obiektw. Jak si okazao, z tego wanie powodu w Pythonie najlepszym rozwizaniem jest sortowanie binarne.
To hybrydowe rozwizanie uzupenione zostao jeszcze o obsug kilku typowych przypadkw
ukierunkowan na popraw prdkoci dziaania. Po pierwsze, wykrywane s listy ju posortowane lub posortowane w odwrotnej kolejnoci i obsugiwane w czasie liniowym. W pewnych aplikacjach takie sytuacje zdarzaj si bardzo czsto. Po drugie, dla tablicy w wikszoci posortowanej, w ktrej nieposortowanych jest tylko kilka ostatnich elementw, ca prac wykonuje
algorytm sortowania binarnego. Takie rozwizanie jest znacznie szybsze od sortowania takich
list algorytmem samplesort, a przedstawiona sytuacja bardzo czsto pojawia si w aplikacjach,
ktre naprzemiennie sortuj list, dodaj do niej nowe elementy, znowu sortuj itd. W kocu
specjalny kod w algorytmie samplesort wyszukuje cigi jednakowych elementw i zajmowan
przez nie cz listy od razu oznacza jako posortowan.
W efekcie takie sortowanie w miejscu odbywa si z doskona wydajnoci we wszystkich znanych typowych przypadkach i osiga nienaturalnie dobr wydajno w pewnych typowych
przypadkach specjalnych. Caa implementacja zapisana zostaa w ponad 500 wierszach skomplikowanego kodu w jzyku C bardzo podobnego do tego prezentowanego w recepturze 5.11.
Przez lata, w ktrych uywany by algorytm samplesort, cay czas oferowaem obiad temu, kto
przygotuje szybszy algorytm sortujcy dla Pythona. Przez cay ten czas musiaem jada sam.
Mimo to cigle ledz pojawiajc si literatur, poniewa pewne aspekty stosowanej w Pythonie hybrydy algorytmw sortujcych s nieco irytujce:
Co prawda w rzeczywistych zastosowaniach nie pojawiaj si przypadki sortowania tablicy

w czasie proporcjonalnym do kwadratu iloci elementw, ale wiem, e takie przypadki


mona sobie wyobrazi, natomiast powstanie przypadkw, w ktrych sortowanie przebiega dwa do trzech razy wolniej od redniej, jest cakiem realne.

224 |

Rozdzia 5. Szukanie i sortowanie

Specjalne przypadki przyspieszajce sortowanie w sytuacjach wyjtkowych ukadw da-

nych z ca pewnoci byy nieocenion pomoc w normalnej praktyce, ale w czasie moich
prac czsto spotykaem si z innymi rodzajami losowych ukadw danych, ktre mona by
byo obsuy w podobny sposb. Doszedem do wniosku, e w przypadkach rzeczywistych
praktycznie nigdy nie wystpuj cakowicie losowo uoone elementy list wejciowych
(a szczeglnie poza rodowiskami przygotowanymi do testowania algorytmw sortujcych).
Nie istnieje praktyczny sposb przygotowania stabilnego algorytmu samplesort bez jed-

noczesnego znaczcego powikszenia wykorzystania pamici.


Kod by niezwykle zoony, a specjalne przypadki komplikoway go jeszcze bardziej.

Aktualny algorytm sortowania


Od zawsze byo wiadomo, e algorytm mergesort w pewnych przypadkach sprawuje si lepiej,
w tym rwnie w najgorszym przypadku zoonoci n log n, a dodatkowo atwo mona
przygotowa jego stabiln wersj. Jak si jednak okazao, p tuzina prb implementowania
tego algorytmu w Pythonie spezo na niczym, poniewa przygotowane procedury dziaay
wolniej (w algorytmie mergesort przenosi si znacznie wicej danych ni w algorytmie samplesort) i zajmoway wicej pamici.
Coraz wiksza cz literatury zaczyna opisywa adaptacyjne algorytmy sortowania, ktre prbuj wykrywa kolejno elementw w rnych danych wejciowych. Sam przygotowaem kilka
takich algorytmw, ale wszystkie okazay si by wolniejsze od pythonowej implementacji
algorytmu samplesort, z wyjtkiem tych przypadkw, na obsug ktrych byy specjalnie
przygotowywane. Teoretyczne podstawy tych algorytmw byy po prostu zbyt zoone, eby
na ich bazie mona byo w praktyce utworzy efektywny algorytm. Przeczytaem wtedy artyku, w ktrym wskazywano na fakt, e dziki sprawdzaniu liczby kolejnych zwycistw
poszczeglnych danych wejciowych czenie list w sposb naturalny wykazuje wiele cech porzdku czciowego. Ta informacja bya bardzo prosta i oglna, ale w momencie gdy uwiadomiem sobie, e mona by wykorzysta j w naturalnym algorytmie mergesort, ktry oczywicie
wykorzystaby wszystkie znane mi rodzaje porzdku czciowego, moj obsesj stao si takie
przygotowanie algorytmu, eby rozwiza problemy z prdkoci sortowania danych losowych
i zminimalizowa zajto pamici.
Przygotowana adaptacyjna, naturalna i stabilna implementacja algorytmu mergesort dla Pythona 2.3 bya wielkim sukcesem, ale zwizana bya rwnie z wielkim nakadem prac inynieryjnych po prostu diabe tkwi w szczegach. Implementacja ta zajmowaa mniej wicej
1200 wierszy kodu w jzyku C. Jednak w przeciwiestwie do hybrydowej implementacji algorytmu samplesort ten kod nie zawiera obsugi adnych przypadkw specjalnych, ale jego du
cz zajmuje pewna sztuczka pozwalajca na zmniejszenie o poow zajtoci pamici w najgorszym z moliwych przypadkw. Jestem bardzo dumny z tej implementacji. Niestety, niewielka ilo miejsca przeznaczona na to wprowadzenie nie pozwala mi na opisanie tu szczegw
tego rozwizania. Jeeli kto jest ciekaw, to odsyam go do opisu technicznego (ostrzegam, e
jest dugi), jaki przygotowaem i jaki dostpny jest wrd rde dystrybucji Pythona w pliku
Objects/listsort.txt, w katalogu, do ktrego zainstalowana zostaa dystrybucja Pythona. W poniszej licie podaj przykady porzdkw czciowych, jakie wykorzystuje implementacja algorytmu mergesort w Pythonie 2.3. Na licie tej sowo posortowany oznacza prawidow kolejno posortowanych elementw lub ich odwrotn kolejno:

5.0. Wprowadzenie

225

Dane wejciowe s ju posortowane.


Dane wejciowe s w wikszoci posortowane, ale maj dopisane losowe dane na pocztku

i (lub) na kocu albo wstawione w rodek.


Dane wejciowe s zoeniem dwch lub wicej list zoonych. Najszybszym sposobem na

poczenie kilku posortowanych list w Pythonie jest teraz zczenie ich w jedn wielk list
i wywoanie na jej rzecz funkcji list.sort.
Dane wejciowe s w wikszoci posortowane, ale pewne elementy nie s uoone w pra-

widowej kolejnoci. Typowym przykadem takiego stanu jest sytuacja, gdy uytkownicy
rcznie dopisuj nowe dane do bazy danych posortowanej wedug nazwisk. Jak wiemy,
ludzie nie najlepiej radz sobie z takim dopisywaniem danych i utrzymywaniem ich w porzdku alfabetycznym, ale czsto s bliscy wstawienia elementu we waciwe miejsce.
Wrd danych wejciowych znajduje si wiele kluczy o takich samych wartociach. Na

przykad w czasie sortowania bazy danych firm amerykaskich notowanych na giedzie


wikszo z takich firm powizana bdzie z indeksami NYSE lub NASDAQ. Taki fakt mona wykorzysta w bardzo ciekawy sposb: klucze o takiej samej wartoci s ju posortowane, co wynika z faktu, e stosowany jest stabilny algorytm sortowania.
Dane wejciowe byy posortowane, ale zostay rozbite na kawaki, ktre pniej poska-

dano w losowej kolejnoci, a dodatkowo elementy niektrych kawakw zostay skutecznie


przemieszane. Jest to oczywicie bardzo dziwny przykad, ale w jego wyniku powstaje porzdek czciowy danych, co wskazuje na wielk oglno prezentowanej metody.
Mwic krtko, w Pythonie 2.3 funkcja timsort (c, musiaa dosta jak krtk nazw) jest
rozwizaniem stabilnym, skutecznym i nienaturalnie szybkim w wielu rzeczywistych przypadkach, w zwizku z czym naley z niej korzysta jak najczciej!

5.1. Sortowanie sownika


Pomysodawca: Alex Martelli

Problem
Chcemy posortowa sownik. Najprawdopodobniej oznacza to, e chcemy posortowa klucze,
a nastpnie pobiera z niego wartoci w tej samej kolejnoci sortowania.

Rozwizanie
Najprostsze rozwizanie tego problemu zostao ju wyraone w opisie problemu: naley posortowa klucze i wybiera powizane z nimi wartoci:
def sortedDictValues(adict):
keys = adict.keys()
keys.sort()
return [adict[key] for key in keys]

226

Rozdzia 5. Szukanie i sortowanie

Analiza
Koncepcja sortowania dotyczy wycznie tych kolekcji, ktrych elementy maj jak kolejno
(czyli sekwencj). Odwzorowania takie jak sowniki nie maj kolejnoci, wobec czego nie mog
by sortowane. Mimo to na listach dyskusyjnych dotyczcych Pythona czsto pojawiaj si
cakowicie bezsensowne pytania Jak mog posortowa sownik?. Najczciej takie pytanie
oznacza jednak, e osoba pytajca chciaa posortowa pewn sekwencj skadajc si z kluczy
i (lub) ich wartoci pobranych ze sownika.
Jeeli chodzi o podan implementacj, to co prawda mona wymyli bardziej zoone rozwizania, ale jak si okazuje (w Pythonie nie powinno by to niespodziank), kod podany
w rozwizaniu jest rozwizaniem najprostszym, a jednoczenie najszybszym. Popraw prdkoci dziaania o mniej wicej 20% mona w Pythonie 2.3 uzyska przez zastpienie w instrukcji
return listy skadanej wywoaniem funkcji map, na przykad:
return map(adict.get, keys)

W Pythonie 2.4 wersja podawana w rozwizaniu jest jednak o wiele szybsza ni ta w Pythonie 2.3, dlatego z takiej zamiany nie zyskamy zbyt wiele. Inne warianty, takie jak na przykad zastpienie metody adict.get metod adict.__getitem__ nie powoduj ju poniesienia
prdkoci dziaania funkcji, a na dodatek mog spowodowa pogorszenie wydajnoci zarwno
w Pythonie 2.3, jak i w Pythonie 2.4.

Zobacz rwnie
Receptur 5.4, w ktrej opisywane s sposoby sortowania sownikw na podstawie przechowywanych wartoci, a nie kluczy.

5.2. Sortowanie listy cigw znakw


bez uwzgldniania wielkoci liter
Pomysodawcy: Kevin Altis, Robin Thomas, Guido van Rossum, Martin V. Lewis, Dave Cross

Problem
Chcemy posortowa list cigw znakw, ignorujc przy tym wszelkie rnice w wielkoci liter. Oznacza to, e chcemy, aby litera a, mimo e jest ma liter, znalaza si przed wielk
liter B. Niestety, domylne porwnywanie cigw znakw uwzgldnia rnice wielkoci liter,
co oznacza, e wszystkie wielkie litery umieszczane s przed maymi literami.

Rozwizanie
Wzorzec DSU (decorate-sort-undecorate) sprawdza si tu doskonale, tworzc szybkie i proste
rozwizanie:
def case_insensitive_sort(string_list):
auxiliary_list = [(x.lower(), x) for x in string_list]
auxiliary_list.sort()
return [x[1] for x in auxiliary_list]

# dekoracja
# sortowanie
# usunicie dekoracji

5.2. Sortowanie listy cigw znakw bez uwzgldniania wielkoci liter

227

W Pythonie 2.4 wzorzec DSU obsugiwany jest w samym jzyku, dlatego (zakadajc, e obiekty
listy string_list rzeczywicie s cigami znakw, a nie na przykad obiektami cigw znakw Unikodu) mona w nim zastosowa ponisze, jeszcze szybsze i prostsze rozwizanie:
def case_insensitive_sort(string_list):
return sorted(string_list, key=str.lower)

Analiza
Do oczywist alternatyw dla rozwizania podawanego w tej recepturze jest przygotowanie
funkcji porwnujcej i przekazanie jej do metody sort:
def case_insensitive_sort_1(string_list):
def compare(a, b): return cmp(a.lower(), b.lower())
string_list.sort(compare)

Niestety, w ten sposb przy kadym porwnaniu dwukrotnie wywoywana jest metoda lower,
a liczba porwna koniecznych do posortowania listy n-elementowej zazwyczaj jest proporcjonalna do n log(n).
Wzorzec DSU tworzy list pomocnicz, ktrej elementami s krotki, w ktrych kady element
z oryginalnej listy poprzedzany jest specjalnym kluczem. Nastpnie sortowanie odbywa si
wedug tych wanie kluczy, poniewa Python porwnuje krotki leksykograficznie, czyli pierwsze elementy krotek porwnuje w pierwszej kolejnoci. Dziki zastosowaniu wzorca DSU metoda lower wywoywana jest tylko n razy w czasie sortowania listy n cigw znakw, co pozwala oszczdzi na tyle duo czasu, e cakowicie rekompensuje konieczno pocztkowego
udekorowania listy i kocowego zdjcia przygotowanych dekoracji.
Wzorzec DSU czasami znany jest te pod (nie do koca poprawn) nazw transformacji
Schwartza, ktra jest nieprecyzyjn analogi do znanego idiomu jzyka Perl. Poprzez zastosowanie takich porwna wzorzec DSU jest bardziej zbliony do transformacji Guttmana-Roslera
(wicej informacji na stronie http://www.sysarch.com/perl/sort_paper.html).
Wzorzec DSU jest tak wany, e w Pythonie 2.4 wprowadzono jego bezporedni obsug. Do
metody sort mona opcjonalnie przekaza nazywany argument key bdcy elementem wywoywalnym, uywanym w czasie sortowania do uzyskania klucza sortowania kadego elementu listy. Jeeli do funkcji sort zostanie przekazany taki argument, to automatycznie zacznie ona korzysta z wzorca DSU. Oznacza to, e w Pythonie 2.4 wywoanie string_list.sort
(key=str.lower) jest rwnowane z wywoaniem funkcji case_insensitive_sort. Jedyna
rnica polega na tym, e metoda sort sortuje list w miejscu (i zwraca warto None), a funkcja case_insensitive_sort zwraca posortowana kopi listy, nie modyfikujc przy tym oryginau. Jeeli chcielibymy, eby funkcja case_insensitive_sort rwnie sortowaa list w miejscu, to wystarczy wynik jej pracy przypisa do ciaa wejciowej listy:
string_list[:] = [x[1] for x in auxiliary_list]

Z drugiej strony, jeeli w Pythonie 2.4 chcielibymy uzyska posortowana kopi listy, bez modyfikowania oryginau, to moemy skorzysta z wbudowanej funkcji sorted. Na przykad zapis:
for s in sorted(string_list, key=str.lower): print s

w Pythonie 2.4 wypisuje wszystkie cigi znakw zapisane na licie string_list, ktra zostaje
posortowana bez uwzgldniania rnic w wielkoci liter, a jej orygina pozostaje bez zmian.

228

Rozdzia 5. Szukanie i sortowanie

Wykorzystanie w Pythonie 2.4 metody str.lower w argumencie key ogranicza nas do sortowania wycznie cigw znakw (nie da si tak posortowa na przykad cigw znakw
Unikodu). Jeeli wiemy, e bdziemy sortowa obiekty Unikodu, to naley posuy si parametrem key=unicode.lower. Jeeli chcielibymy uzyska funkcj, ktra dziaaaby tak samo
wobec prostych cigw znakw i cigw znakw Unikodu, to mona zaimportowa modu
string i posuy si argumentem key=string.lower. Ewentualnie mona te skorzysta z zapisu key=lambda s: s.lower().
Skoro musimy czasem sortowa listy cigw znakw w sposb nieuwzgldniajcy rnic wielkoci liter, to rwnie dobrze moemy potrzebowa sownikw lub zbiorw stosujcych klucze
nieuwzgldniajce rnic wielkoci liter, a take list, w ktrych podobnie zachowuj si metody
index oraz count i inne. W takich sytuacjach potrzebny jest nam typ wywiedziony z klasy str,
ktry nie uwzgldniaby rnic wielkoci liter w operacjach porwnywania i mieszania (ang.
hashing). Jest to zdecydowanie lepsze rozwizanie w porwnaniu z implementowaniem wielu
rnych typw kontenerowych i funkcji obejmujcych przedstawion funkcj. Sposb implementowania takiego typu podawany by ju w recepturze 1.24.

Zobacz rwnie
Zbir czsto zadawanych pyta (dostpny na stronie http://www.python.org/cgi-bin/faqw.py?req=
show&file=faq04.051.htp. Receptur 5.3. Podrcznik Library Reference Pythona 2.4 w czciach opisujcych wbudowan funkcj sorted oraz parametr key metod sort i sorted. Receptur 1.24.

5.3. Sortowanie listy obiektw


na podstawie ich atrybutw
Pomysodawcy: Yakov Markovitch, Nick Perkins

Problem
Musimy posortowa list obiektw wedug wartoci jednego z atrybutw tych obiektw.

Rozwizanie
Tutaj rwnie doskonale sprawdza si wzorzec DSU:
def sort_by_attr(seq, attr):
intermed = [ (getattr(x, attr), i, x) for i, x in enumerate(seq) ]
intermed.sort()
return [ x[-1] for x in intermed ]
def sort_by_attr_inplace(lst, attr):
lst[:] = sort_by_attr(lst, attr)

W Pythonie 2.4, w ktrym wzorzec DSU zosta wbudowany w jzyk, kod ten moe by jeszcze
krtszy i szybszy:
import operator
def sort_by_attr(seq, attr):
return sorted(seq, key=operator.attrgetter(attr))
def sort_by_attr_inplace(lst, attr):
lst.sort(key=operator.attrgetter(attr))

5.3. Sortowanie listy obiektw na podstawie ich atrybutw

229

Analiza
Sortowanie listy obiektw wedug okrelonego atrybutu najlepiej wykonuje si za pomoc wzorca DSU omawianego w poprzedniej recepturze 5.2. W Pythonie 2.3 i 2.4 nie jest on ju potrzebny do tworzenia stabilnego sortowania, co byo konieczne w poprzednich wersjach jzyka (od wersji 2.3 algorytm sortowania stosowany w Pythonie zawsze jest stabilny), a mimo
to inne zalety wzorca DSU nadal s niepodwaalne.
W oglnym przypadku i z wykorzystaniem najlepszego algorytmu sortowanie ma zoono
O(n log n) (w formuach matematycznych taki zapis oznacza, e wartoci n i log n s mnoone). Sia wzorca DSU polega na wykorzystaniu wycznie wbudowanych w Pythona (i przez
to najszybszych) mechanizmw porwnania, przez co maksymalnie przyspieszana jest ta cz
wyraenia O(n log n), ktra zabiera najwicej czasu w operacji sortowania sekwencji o bardzo
duej dugoci. Pocztkowy krok dekorowania, w ktrym przygotowywana jest pomocnicza lista
krotek, oraz kocowy krok usuwania dekoracji, w ktrym z posortowanej listy krotek wydobywane s waciwe informacje, oba maj zoono tylko O(n). Oznacza to, e drobne niedocignicia w tych dwch krokach bd miay niky wpyw na sortowanie list z wielk liczb
elementw, a w przypadku niewielkich list wpyw tych krokw rwnie bdzie wzgldnie
niewielki.

Notacja O( )
Najbardziej uyteczny sposb okrelania wydajnoci danego algorytmu polega na wykorzystaniu tak zwanej analizy lub notacji wielkiego O (litera O oznacza po angielsku order, czyli
rzd). Dokadne objanienia dotyczce tej notacji znale mona na stronie http://
pl.wikipedia.org/wiki/Notacja_du%C5%BCego_O. Tutaj podamy tylko krtkie podsumowanie.
Jeeli przyjrzymy si algorytmom stosowanym na danych wejciowych o rozmiarze N, to dla
odpowiednio duych wartoci N (due iloci danych wejciowych sprawiaj, e wydajno
danego rozwizania staje si spraw krytyczn) czas ich dziaania moe by okrelany jako
proporcjonalny do pewnej funkcji wartoci N. Oznacza si to za pomoc notacji takiej jak
O(N) (czas pracy algorytmu jest proporcjonalny do N; przetwarzanie dwukrotnie wikszego
zbioru danych zajmuje dwa razy wicej czasu, a przetwarzanie zbioru dziesiciokrotnie wikszego zajmuje dziesi razy wicej czasu; inna nazwa tej notacji to zoono liniowa), O(N2)
(czas pracy algorytmu jest proporcjonalny do kwadratu N; przetwarzanie dwukrotnie wikszego zbioru danych zajmuje cztery razy wicej czasu, a przetwarzanie zbioru dziesiciokrotnie wikszego zajmuje sto razy wicej czasu; inna nazwa tej notacji to zoono kwadratowa) itd.
Czsto bdziemy natyka si te na zapis O(N log N), ktry oznacza algorytm szybszy ni
O(N2), ale wolniejszy od algorytmu O(N).
Najczciej ignorowane s stae proporcji (przynajmniej w rozwaaniach teoretycznych), poniewa zazwyczaj zale one od takich czynnikw jak czstotliwo zegara w naszym komputerze, a nie od samego algorytmu. Jeeli zastosujemy dwa razy szybszy komputer, to cao bdzie trwaa o poow krcej, ale nie zmieni to wynikw porwna poszczeglnych
algorytmw.

W tej recepturze w kadej krotce bdcej elementem listy intermed umieszczamy indeks i
przed odpowiadajcym mu elementem x (x jest i-tym elementem sekwencji seq). W ten sposb upewniamy si, e dowolne dwa elementy sekwencji seq nie bd porwnywane bezporednio, nawet jeeli maj tak sam warto atrybutu attr, poniewa w takiej sytuacji ich
indeksy bd miay rne wartoci. W ten sposb wykonywane w Pythonie leksykograficzne
230

Rozdzia 5. Szukanie i sortowanie

porwnania krotek nigdy nie bd porwnywa ostatnich elementw poszczeglnych krotek


(czyli waciwych elementw sekwencji seq). Uniknicie porwnywania samych obiektw pozwala nam unikn wykonywania niezwykle powolnych porwna, a nawet prb wykonania
porwna niedozwolonych. Na przykad moglibymy sortowa liczby zespolone wedug ich
czci rzeczywistej (atrybut real); przy prbie bezporedniego porwnania takich liczb powstaby wyjtek, poniewa w liczbach zespolonych nie ma zdefiniowanej takiej operacji. Dziki
obostrzeniom opisywanym w tym akapicie takie porwnanie nigdy nie nastpi i w zwizku
z tym sortowanie przebiegnie bezproblemowo.
Jak ju wspominalimy wczeniej w recepturze 5.2, w Pythonie 2.4 wzorzec DSU obsugiwany
jest w samym jzyku. Do metody sort mona przekazywa opcjonalny argument nazywany
key, ktry jest wywoywany na rzecz kadego elementu sortowanej listy i ma zwrci klucz
sortowania. Modu operator bdcy czci biblioteki standardowej udostpnia dwie nowe
funkcje attrgetter i itemgetter przeznaczone do zwracania elementw wywoywalnych
nadajcych si do omwionych wyej zastosowa. Dziki temu w Pythonie 2.4 idealnym rozwizaniem naszego problemu staje si poniszy kod:
import operator
seq.sort(key=operator.attrgetter(attr))

Podany kod pozwala posortowa list w miejscu i to z niesamowit prdkoci na moim


komputerze sortowanie byo trzykrotnie szybsze ni ta sama operacja wykonana w Pythonie 2.3
za pomoc funkcji prezentowanej w rozwizaniu tej receptury. Jeeli potrzebna jest nam posortowana kopia listy, bez modyfikowania jej oryginau, to w Pythonie 2.4 moemy wykorzysta now, wbudowan funkcj sorted.
sorted_copy = sorted(seq, key=operator.attrgetter(attr))

Nie jest to a tak szybkie jak sortowanie w miejscu, ale ten ostatni kod jest nadal ponad 2,5 razy
szybszy od funkcji przedstawianej w rozwizaniu receptury. Przekazujc opcjonalny argument
nazywany key w Pythonie 2.4, uzyskujemy te pewno, e w czasie sortowania elementy listy
nie zostan porwnane bezporednio ze sob, wic nie musimy tworzy adnych dodatkowych
zabezpiecze. Co wicej, posortowana lista na pewno bdzie stabilna.

Zobacz rwnie
Receptur 5.2. Podrcznik Library Reference z Pythona 2.4 w czci opisujcej wbudowan funkcj sorted, funkcje attrgetter i itemgetter z moduu operator oraz parametr key funkcji
sort i sorted.

5.4. Sortowanie kluczy lub indeksw


na podstawie zwizanych z nimi wartoci
Pomysodawcy: John Jensen, Fred Bremmer, Nick Coghlan

Problem
Musimy zliczy wystpienia rnych elementw i zaprezentowa te elementy w kolejnoci czstotliwoci wystpowania na przykad w celu przygotowania histogramu.

5.4. Sortowanie kluczy lub indeksw na podstawie zwizanych z nimi wartoci

231

Rozwizanie
Histogramy niezalenie od swojego zastosowania przy tworzeniu grafiki tworzone s przez
zliczanie wystpie elementw (co nietrudno jest wykona w przypadku list lub sownikw
w Pythonie) i sortowanie list lub indeksw w kolejnoci odpowiadajcej wyznaczonym wartociom. Oto klasa wywiedziona z klasy dict, ktra uzupeniona zostaa o dwie metody:
class hist(dict):
def add(self, item, increment=1):
''' dodaje warto 'increment' do pozycji elementu 'item' '''
self[item] = increment + self.get(item, 0)
def counts(self, reverse=False):
''' zwraca list kluczy posortowan zgodnie z odpowiadajcymi im wartociami '''
aux = [ (self[k], k) for k in self ]
aux.sort()
if reverse: aux.reverse()
return [k for v, k in aux]

Jeeli zliczane elementy mog by modelowane jako niewielkie liczby cakowite z okrelonego zakresu i wyniki zliczania elementw chcemy przechowywa na licie, to mona zastosowa bardzo podobne rozwizanie:
class hist1(list):
def __init__(self, n):
''' inicjalizacja listy do zliczania wystpie n rnych elementw '''
list.__init__(self, n*[0])
def add(self, item, increment=1):
''' dodaje warto 'increment' do pozycji elementu 'item' '''
self[item] += increment
def counts(self, reverse=False):
''' zwraca list indeksw posortowan zgodnie z odpowiadajcymi im wartociami '''
aux = [ (v, k) for k, v in enumerate(self) ]
aux.sort()
if reverse: aux.reverse()
return [k for v, k in aux]

Analiza
Metoda add klasy hist jest przykadem wykorzystania typowego dla Pythona idiomu przeznaczonego do zliczania dowolnych (cho unikalnych) elementw. W klasie hist1, zbudowanej
na podstawie klasy list, stosowane jest inne rozwizanie, polegajce na wpisywaniu w specjalnej metodzie __init__ wartoci 0 do wszystkich elementw listy, dziki czemu metoda add
moe przyj prostsz posta.
Metoda counts tworzy list kluczy lub indeksw posortowanych w kolejnoci wyznaczanej przez
powizane z nimi wartoci. Problem jest bardzo podobny w obu klasach (hist i hist1), dlatego
podane rozwizania s niemal identyczne w obu wykorzystywany jest wzorzec DSU omawiany w recepturach 5.2 i 5.3. Jeeli w naszym programie chcielibymy skorzysta z obu klas,
to moemy wykorzysta ich podobiestwo i wydzieli czci wsplne do pomocniczej funkcji
_sorted_keys:
def _sorted_keys(container, keys, reverse):
''' zwraca list 'keys' posortowan zgodnie z wartociami z parametru 'container' '''
aux = [ (container[k], k) for k in keys ]
aux.sort()
if reverse: aux.reverse()
return [k for v, k in aux]

232

Rozdzia 5. Szukanie i sortowanie

Nastpnie metody counts obu klas mona zaimplementowa jako opakowania zaprezentowanej
funkcji _sorted_keys:
class hist(dict):
## ...
def counts(self, reverse=False):
return _sorted_keys(self, self, reverse)
class hist1(list):
## ...
def counts(self, reverse=False):
return _sorted_keys(self, xrange(len(self)), reverse)

W Pythonie 2.4 wzorzec DSU jest tak wany, e (jak pokazano w recepturach 5.2 i 5.3) metoda
sort z obiektw list oraz nowa, wbudowana funkcja sorted oferuj bardzo szybk implementacj tego wzorca. Dziki temu w Pythonie 2.4 funkcja _sorted_keys moe by jeszcze prostsza
i szybsza:
def _sorted_keys(container, keys, reverse):
return sorted(keys, key=container.__getitem__, reverse=reverse)

Metoda powizana container.__getitem__ wykonuje dokadnie te same operacje co indeksowanie container[k] stosowane w implementacji dla Pythona 2.3, ale jest ona elementem
wywoywalnym, ktry moe by wywoywany na rzecz kadego elementu k z sortowanej sekwencji dokadnie tak warto naley przekazywa w parametrze key do wywoywanej
funkcji sorted. W Pythonie 2.4 udostpniany jest te prosty i bezporedni sposb na odczytanie
listy elementw sownika posortowanych wedug wartoci:
from operator import itemgetter
def dict_items_sorted_by_value(d, reverse=False):
return sorted(d.iteritems(), key=itemgetter(1), reverse=reverse)

Funkcja wysokiego poziomu operator.itemgetter (rwnie wprowadzona zostaa w Pythonie 2.4) jest bardzo wygodnym sposobem dostarczania argumentu key w czasie sortowania
kontenera, ktrego elementy s podkontenerami, a kluczem sortowania ma by okrelony element podkontenera. Dokadnie tak sytuacj mamy w przedstawionym przypadku, poniewa
elementy sownika s sekwencj par (krotek dwuelementowych), a my chcemy posortowa t
sekwencj wedug drugiego elementu kadej krotki.
Wracajc do gwnego tematu tej receptury, oto przykad uycia klasy hist prezentowanej
w rozwizaniu receptury:
sentence = ''' Halo! To jest test. Halo! To by test,
ale ju nim nie jest. '''
words = sentence.split()
c = hist()
for word in words: c.add(word)
print "Rosnco:"
print c.counts()
print "Malejco:"
print c.counts(reverse=True)

Podany wycinek kodu tworzy nastpujce dane wyjciowe:


Rosnco:
[(1, 'ale'), (1, 'by'), (1, 'jest'), (1, 'jest.'), (1, 'ju'), (1, 'nie'), (1, 'nim'),
(1, 'test,'), (1, 'test.'), (2, 'Halo!'), (2, 'To')]
Malejco:
[(2, 'To'), (2, 'Halo!'), (1, 'test.'), (1, 'test,'), (1, 'nim'), (1, 'nie'), (1, 'ju'),
(1, 'jest.'), (1, 'jest'), (1, 'by'), (1, 'ale')]

5.4. Sortowanie kluczy lub indeksw na podstawie zwizanych z nimi wartoci

233

Zobacz rwnie
Receptur Special Method Names zamieszczon w podrczniku Library Reference oraz rozdzia o programowaniu obiektowym w podrczniku Python in a Nutshell, w czci opisujcej
metod __getitem__. Podrcznik Library Reference Pythona 2.4 w czci opisujcej wbudowan
funkcj sorted oraz parametr key funkcji sort i sorted.

5.5. Sortowanie cigw znakw zawierajcych liczby


Pomysodawcy: Sbastien Keim, Chui Tey, Alex Martelli

Problem
Musimy tak posortowa list cigw znakw zawierajcych sekwencje cyfr (na przykad list
kodw adresowych), eby wygldaa jak najlepiej. Na przykad tekst 'foo2.txt' powinien
znale si przed tekstem 'foo10.txt'. Niestety, w Pythonie domylnie stosowane jest porwnanie alfabetyczne, wic tekst 'foo10.txt' znajdzie si przed 'foo2.txt'.

Rozwizanie
Musimy podzieli kady cig znakw na sekwencje cyfr i niecyfr, a nastpnie kad sekwencj cyfr zamieni w liczb. W ten sposb uzyskamy list przechowujc waciwe klucze do
sortowania listy. Nastpnie mona skorzysta ze wzorca DSU do wykonania samego sortowania. Jak wida, wystarczy nam przygotowa dwie krciutkie funkcje:
import re
re_digits = re.compile(r'(\d+)')
def embedded_numbers(s):
pieces = re_digits.split(s)
# dzielenie na cyfry/niecyfry
pieces[1::2] = map(int, pieces[1::2])
# zamiana cyfr w liczby
return pieces
def sort_strings_with_embedded_numbers(alist):
aux = [ (embedded_numbers(s), s) for s in alist ]
aux.sort()
return [ s for __, s in aux ]
# konwencja: __ oznacza "ignoruj"

W Pythonie 2.4 mona skorzysta z wbudowanej obsugi wzorca DSU (przyda si te przedstawiona wyej funkcja embedded_numbers) i posortowa list za pomoc poniszej funkcji:
def sort_strings_with_embedded_numbers(alist):
return sorted(alist, key=embedded_numbers)

Analiza
Zamy, e mamy nieposortowan list nazw plikw podobn do poniszej:
files = 'plik3.txt plik11.txt plik7.txt plik4.txt plik15.txt'.split()

Jeeli w Pythonie 2.4 podan list posortujemy i wypiszemy za pomoc instrukcji print '
'.join(sorted(files)), to na ekranie zobaczymy nastpujcy tekst: plik11.txt plik15.txt
plik3.txt plik4.txt plik7.txt. Taka kolejno wynika z faktu, e domylnie cigi znakw
sortowane s alfabetycznie (a mwic bardziej wymylnie sortowane s leksykograficznie).

234 |

Rozdzia 5. Szukanie i sortowanie

Python nie moe si domyla, e chcemy inaczej traktowa cigi znakw, poniewa pewne
czci cigw znakw dziwnym trafem opisuj liczby. Musimy dokadnie okreli, co chcemy
zrobi, i w tej recepturze pokazujemy, jak mona tego dokona:
Korzystajc z tej receptury, mona uzyska znacznie lepiej wygldajce wyniki:
print ' '.join(sort_strings_with_embedded_numbers(files))

Podana instrukcja wypisuje teraz tekst: plik3.txt plik4.txt plik7.txt plik11.txt


plik15.txt i najprawdopodobniej o t kolejno plikw chodzio nam w tym przypadku.
Implementacja rozwizania opiera si na wzorcu DSU. Jeeli chcemy, eby takie sortowanie
dziaao w Pythonie 2.3, to wzorzec ten musimy jawnie zapisa w kodzie funkcji, natomiast kod
przygotowany dla Pythona 2.4 moe wykorzystywa wbudowan implementacj wzorca. Wykorzystanie wbudowanej implementacji wzorca DSU wymaga przekazania nazywanego argumentu key (argument ten okrela funkcj, ktra ma by wywoywana na rzecz kadego
elementu sortowanej listy w celu uzyskania klucza sortowania) do nowej wbudowanej funkcji sorted.
W tej recepturze waciwy klucz do porwnywania poszczeglnych elementw uzyskujemy
za pomoc funkcji embedded_numbers, ktra zwraca list czstkowych cigw znakw zawierajc zamiennie sekwencje niecyfr i wartoci typu int uzyskanych z kadej sekwencji cyfr.
Metoda re_digits.split(s) daje nam list naprzemiennych sekwencji niecyfr i cyfr, przy
czym sekwencje cyfr umieszczane s pod indeksami parzystymi. Nastpnie korzystamy z wbudowanych funkcji map i int (oraz rozbudowanych wykroje pobierajcych wszystkie elementy
i ustawiajcych je pod indeksami nieparzystymi), aby zamieni sekwencje cyfr w liczby. Leksykograficzne porwnania wykonywane w takiej licie skadajcej si z elementw rnych typw
generuj oczekiwany rezultat.

Zobacz rwnie
Podrczniki Library Reference i Python in a Nutshell w czciach opisujcych wykrojenia i modu re. Podrcznik Library Reference z Pythona 2.4 w czci opisujcej wbudowan funkcj
sorted i parametr key przekazywany do funkcji sort i sorted. Receptury 5.3 i 5.2.

5.6. Przetwarzanie wszystkich elementw listy


w kolejnoci losowej
Pomysodawcy: Iuri Wickert, Duncan Grisby, T. Warner, Steve Holden, Alex Martelli

Problem
Wszystkie elementy dugiej listy musimy obsuy w kolejnoci losowej.

Rozwizanie
Jak to zwykle bywa w Pythonie, najlepszym rozwizaniem jest rozwizanie najprostsze. Jeeli
wolno nam bdzie zmieni kolejno elementw w wejciowej licie, to ponisza funkcja bdzie
zdecydowanie najprostsza i najszybsza:
5.6. Przetwarzanie wszystkich elementw listy w kolejnoci losowej

235

def process_all_in_random_order(data, process):


# po pierwsze, list ukadamy w porzdku losowym
random.shuffle(data)
# po drugie, liniowo obsugujemy wszystkie jej elementy
for elem in data: process(elem)

Jeeli wejciowa lista musi zosta niezmieniona lub wejciowe dane zapisane s w elemencie
iterowalnym niebdcym list, to wystarczy dopisa do powyszej funkcji pierwsz instrukcj
w postaci przypisania data = list(data).

Analiza
Powszechnym bdem jest przywizywanie zbytniej wagi do prdkoci dziaania kodu,
ale z drugiej strony nie mona te popenia odwrotnego bdu polegajcego na ignorowaniu rnic wydajnoci poszczeglnych algorytmw. Zamy, e musimy w kolejnoci
losowej i bez powtrze obsuy wszystkie elementy dugiej listy (zakadamy te, e moemy zmodyfikowa, a nawet zniszczy list wejciow). Pierwszym pomysem, jaki zapewne przyszedby nam do gowy, jest losowe wybieranie jednego elementu (za pomoc funkcji
random.choice) i po jego obsueniu usuwanie z listy w celu zapobieenia powstawaniu
powtrze:
import random
def process_random_removing(data, process):
while data:
elem = random.choice(data)
data.remove(elem)
process(elem)

Taka funkcja jest niestety bardzo powolna, nawet w przypadku list z zaledwie kilkuset elementami. Kade wywoanie metody data.remove wymaga liniowego przejrzenia wszystkich
elementw listy w celu odnalezienia tego przeznaczonego do usunicia. Kady taki krok ma
zoono O(n), a zatem cay proces ma zoono O(n2) czas obsugi listy jest proporcjonalny do kwadratu jej dugoci (a zwykle listy nie nale do krtkich).
Kolejne usprawnienia tego pierwszego pomysu mog polega na: wybieraniu losowych indeksw oraz za pomoc metody pop pobieraniu samych elementw i jednoczesnym usuwaniu ich
z listy, niskopoziomowych zabawach z indeksami majcych na celu uniknicie kosztownego
usuwania elementw listy i zastpowaniu wybranego elementu ostatnim jeszcze niewybranym
elementem albo zastpowaniu listy sownikami lub zbiorami. Ten ostatni pomys moe wynika z nadziei, e uda si skorzysta z metody popitem obiektw sownikw (lub rwnowanej
jej metody pop z klasy sets.Set albo wbudowanego w Pythona 2.4 typu set), poniewa na
pierwszy rzut oka wyglda ona na przeznaczon do wybierania i usuwania losowych elementw, ale zgodnie z dokumentacj Pythona metoda dict.popitem suy ma do zwracania
i usuwania dowolnego elementu sownika, ktry zdecydowanie nie jest elementem losowym.
Prosz przyjrze si poniszemu kodowi:
>>> d=dict(enumerate('ciao'))
>>> while d: print d.popitem()

Niektrych moe to zaskoczy, ale w wikszoci implementacji Pythona podany kod wcale nie
wypisze elementw sownika d w kolejnoci losowej. Najczciej zobaczymy najpierw (0, 'c'),
nastpnie (1, 'i') itd. Jeeli w Pythonie potrzebne jest nam zachowanie pseudolosowe, to
obowizkowo musimy skorzysta z moduu random metoda popitem nie jest tutaj adnym
rozwizaniem.

236

Rozdzia 5. Szukanie i sortowanie

Jeeli kto myla o zamianie listy na sownik, to na pewno jest w stanie rozumowa w sposb
pythoniczny, mimo e w tym konkretnym problemie zastosowanie sownikw nie powoduje
znaczcego podniesienia prdkoci dziaania. Jak si okazuje, istnieje jeszcze bardziej pythoniczne rozwizanie od wybrania najwaciwszej struktury danych. Mona je podsumowa nastpujcym zdaniem: niech zrobi to biblioteka standardowa. Biblioteka standardowa Pythona jest
ogromnym, bogatym zbiorem przydatnych, skutecznych i szybkich funkcji oraz klas przeznaczonych do wykonywania najrniejszych operacji. W tym przypadku najwaniejsz rzecz jest
to, eby zaakceptowa fakt, e najprostszym sposobem na obsuenie wszystkich elementw
listy w losowej kolejnoci jest uoenie ich najpierw w kolejnoci losowej (proces ten nazywany
jest tasowaniem sekwencji, przez analogi do tasowania talii kart) i liniowe obsuenie elementw
listy. Takie przetasowanie elementw listy wykonuje funkcja random.shuffle i dlatego wykorzystana zostaa w rozwizaniu tej receptury.
Wydajno danego rozwizania zawsze musi zosta zmierzona; nigdy nie naley polega na
domysach. Do przeprowadzania takich pomiarw najlepiej bdzie wykorzysta modu timeit.
W poczeniu z pust funkcj process i list skadajc si z tysica elementw funkcja process_
all_in_random_order okazaa si by prawie dziesiciokrotnie szybsza od funkcji process_
random_removing. W przypadku listy skadajcej si z dwch tysicy elementw stosunek prdkoci tych dwch funkcji wynosi ju prawie 20. Zwykle popraw wydajnoci o, powiedzmy,
25% albo stay wspczynnik 2 mona uzna za niewart naszej uwagi, to jednak nie mona
tak samo traktowa algorytmu 10 lub 20 razy wolniejszego od swojego konkurenta. Tak wyjtkowo niska wydajno bardzo atwo moe spowodowa, e dana cz programu stanie si
wskim. Co wicej, takie ryzyko wzrasta jeszcze bardziej, gdy zaczynamy porwnywa algorytm o zoonoci O(n2) z algorytmem o zoonoci O(n). Przy takich rnicach zoonoci algorytmw stosunek prdkoci dziaania zego i dobrego algorytmu ronie bez adnych ogranicze wraz ze wzrostem liczby danych wejciowych.

Zobacz rwnie
Dokumentacj moduw random i timeit dostpn w podrcznikach Library Reference i Python
in a Nutshell.

5.7. Utrzymywanie porzdku w sekwencji


w czasie dodawania do niej nowych elementw
Pomysodawca: John Nielsen

Problem
Chcemy utrzyma w stanie posortowania sekwencj, do ktrej dodawane s nowe elementy,
tak eby w dowolnym momencie mona byo prosto sprawdzi lub usun najmniejszy z zapisanych aktualnie elementw.

Rozwizanie
Zamy, e pocztkowo mamy list nieuporzdkowan, tak jak ponisza:
the_list = [903, 10, 35, 69, 933, 485, 519, 379, 102, 402, 883, 1]

5.7. Utrzymywanie porzdku w sekwencji w czasie dodawania do niej nowych elementw

237

Mona teraz wywoa metod the_list.sort(), aby posortowa list, a nastpnie skorzysta
z metody the_list.pop(0), aby pobra i usun najmniejszy element listy. Niestety, pniej
po kadym dodaniu elementu do listy (na przykad metod the_list.append(0)) konieczne
jest ponownie wywoanie metody the_list.sort() w celu utrzymania porzdku na licie.
Inne rozwizanie polega na zastosowaniu moduu heapq pochodzcego ze standardowej biblioteki Pythona:
import heapq
heapq.heapify(the_list)

Tak przeksztacona lista nie musi by koniecznie w peni posortowana, ale spenia waciwo sterty (ang. heap property) (oznacza ona, e jeeli wszystkie indeksy s prawidowe, to
the_list[i]<=the_list[2*i+1] i the_list[i]<=the_list[2*i+2]), przez co w szczeglnoci
element the_list[0] jest zawsze elementem najmniejszym. W celu utrzymania na licie waciwoci sterty, naley uywa metody result=heapq.heappop(the_list), aby pobiera i usuwa najmniejszy element listy, natomiast nowe elementy do listy naley dodawa za pomoc
metody heapq.heappush(the_list, newitem). Jeeli zajdzie potrzeba wykonania obu tych operacji, czyli dodania nowego elementu z jednoczesnym pobraniem i usuniciem najmniejszego
elementu, naley skorzysta z wywoania result=heapq.heapreplace(the_list, newitem).

Analiza
Jeeli musimy odbiera dane w sposb uporzdkowany (przy kadym odczycie pobieramy
najmniejszy spord dostpnych aktualnie elementw), to koszt sortowania danych musimy
ponie albo w momencie odczytywania elementu albo w momencie jego dodawania. Jedno
z rozwiza polega na gromadzeniu danych w ramach listy i sortowaniu caej listy. Dziki
temu odczytywanie danych w kolejnoci od najmniejszego do najwikszego elementu jest bardzo proste. Niestety, jeeli w czasie odczytywania danych dodajemy do listy nowe elementy,
to zmuszeni jestemy do wywoywania metody sort, aby mie pewno, e po dodaniu nowego elementu odczytywa bdziemy najmniejszy element na licie. W Pythonie metoda sort
implementowana jest z wykorzystaniem mao znanego algorytmu Natural Mergesort, ktry minimalizuje koszta sortowania w takim rozwizaniu. Mimo to rozwizanie to moe by mocno
obciajce czas kadego dodania elementu (i sortowania listy), a take kadego odczytania
(i usunicia za pomoc metody pop) jest proporcjonalny do liczby elementw aktualnie znajdujcych si na licie (co oznacza, e algorytm ten ma zoono O(N)).
Rozwizanie alternatywne polega na wykorzystaniu sposobu organizacji danych znanego pod
nazw sterty (ang. heap). Jest to rodzaj kompaktowego drzewa binarnego, ktre zapewnia, e
kady wze-rodzic jest mniejszy od jego wzw-dzieci. Najlepszym sposobem na utworzenie
w Pythonie struktury sterty jest wykorzystanie listy i zarzdzanie ni przez modu heapq
z biblioteki standardowej. Taka lista nie jest sortowana dokadnie, a jedynie w takim stopniu,
eby zyska pewno, e wywoujc funkcj heappop, otrzymamy najmniejszy z dostpnych
aktualnie elementw, a wszystkie pozostae zostan uporzdkowane tak, aby utrzyma struktur sterty. Kade dodanie elementu funkcj heappush, jak rwnie kade usunicie elementu
funkcj heappop zajmuje bardzo mao czasu w stosunku do dugoci caej listy (w oglnym przypadku jest to O(log N)). Niewielkie koszta tych operacji ponosimy w trakcie pracy, a oglny
koszt pracy z tak zarzdzan list rwnie nie jest znaczcy.

238 |

Rozdzia 5. Szukanie i sortowanie

Dobr okazj do zastosowania sterty jest na przykad sytuacja, w ktrej mamy dug kolejk
cyklicznie dopisywanych danych, a chcemy z niej pobiera zawsze najwaniejszy w danym momencie element bez koniecznoci cigego sortowania listy lub wykonywania penego przeszukiwania. Takie rozwizanie nazywane jest kolejk priorytetow (ang. priority queue), a sterta jest
najlepsz metod na jej zaimplementowanie. Trzeba jednak zauway, e modu heapq przy
kadym wywoaniu funkcji heappop bdzie dostarcza nam zawsze najmniejszy element sterty,
dlatego naley uwzgldnia t cech przy ustalaniu priorytetw danych dodawanych do kolejki. Na przykad otrzymywane elementy powizane s z okrelonym kosztem i w zwizku
z tym najwaniejszym elementem jest ten najdroszy spord znajdujcych si aktualnie na
stercie. Co wicej, spord elementw o identycznym koszcie najwaniejszym ma by ten,
ktry zosta dopisany jako pierwszy. Oto sposb na przygotowanie klasy kolejki priorytetowej speniajcej te zaoenia, ktra korzysta z funkcji udostpnianych przez modu heapq:
class prioq(object):
def __init__(self):
self.q = []
self.i = 0
def push(self, item, cost):
heapq.heappush(self.q, (-cost, self.i, item))
self.i += 1
def pop(self):
return heapq.heappop(self.q)[-1]

Najwaniejsz czci powyszego kodu jest zapisywanie na stercie krotek, w ktrych pierwszym elementem jest koszt waciwego elementu ze zmienionym znakiem, dziki czemu elementy
o najwyszym koszcie tworzy bd najmniejsze krotki (tak porwnuje je Python). Zaraz za kosztem w krotce umieszczany jest postpujcy indeks dodawanych elementw, przez co wrd elementw o identycznym koszcie najmniejszym bdzie ten, ktry zosta dopisany jako pierwszy.
W Pythonie 2.4 modu heapq zosta napisany od nowa i poddany wielu optymalizacjom. Wicej informacji na jego temat podanych zostanie w recepturze 5.8.

Zobacz rwnie
Dokumentacj moduu heapq dostpn w podrcznikach Library Reference i Python in a Nutshell.
Wrd rde Pythona plik heapq.py zawiera bardzo ciekaw dyskusj na temat stert. Receptur 5.8, w ktrej podawanych jest wicej informacji o module heapq. Receptur 19.14, w ktrej
opisywany jest sposb czenia posortowanych sekwencji z wykorzystaniem moduu heapq.

5.8. Pobieranie kilku najmniejszych


elementw sekwencji
Pomysodawcy: Matteo Dell'Amico, Raymond Hettinger, George Yoshida, Daniel Harding

Problem
Musimy pobra kilka najmniejszych elementw sekwencji. Mona oczywicie posortowa sekwencj i uy wykrojenia seq[:n], ale moe istnieje jaki lepszy sposb?

5.8. Pobieranie kilku najmniejszych elementw sekwencji

239

Rozwizanie
Jeeli n, czyli liczba elementw wybieranych z sekwencji, jest maa w porwnaniu z L, czyli
cakowit dugoci sekwencji, to problem ten da si rozwiza lepiej. Metoda sort dziaa bardzo szybko, a mimo to zabiera O(L log L) czasu, natomiast pobranie z listy n najmniejszych
elementw zajmuje O(n) czasu dla maych n. Oto prosty i bardzo praktyczny generator rozwizujcy przedstawione zadanie, dziaajcy zarwno w Pythonie 2.3, jak i w Pythonie 2.4:
import heapq
def isorted(data):
data = list(data)
heapq.heapify(data)
while data:
yield heapq.heappop(data)

W Pythonie 2.4 mona skorzysta ze znacznie prostszej i szybszej metody pobierania n najmniejszych elementw sekwencji data:
import heapq
def smallest(n, data):
return heapq.nsmallest(n, data)

Analiza
Parametr data moe by dowolnym powizanym elementem iterowalnym. Funkcja isorted
podawana w rozwizaniu receptury rozpoczyna si wywoaniem funkcji list, dziki czemu
warunek ten jest zawsze speniony. Z funkcji tej instrukcj data = list(data) mona usun
dopiero wtedy, gdy spenione s nastpujce warunki: wiemy, e parametr data zawsze bdzie
list, nie przeszkadza nam to, e generator zmieni kolejno jej elementw, a dodatkowo chcemy usun z tej listy pobierane elementy.
Jak pokazalimy w recepturze 5.7, w bibliotece standardowej Pythona dostpny jest modu
heapq, ktry obsuguje struktur danych znan jako sterta. Generator isorted prezentowany
w tej recepturze na pocztku tworzy stert (wywoaniem funkcji heapq.heapify), a nastpnie
oddaje i usuwa w kadym kroku najmniejszy element sterty (za pomoc funkcji heapq.heappop).
W Pythonie 2.4 modu heapq uzupeniony zosta o dwie nowe funkcje. Funkcja heapq.nlargest
(n, data) zwraca list n najwikszych elementw parametru data, natomiast funkcja heapq.
nsmallest(n, data) zwraca list n najmniejszych elementw parametru data. Funkcje te nie
wymagaj, eby parametr data spenia warunki prawidowej sterty; co wicej, nie wymagaj
nawet, eby parametr data by list cakowicie wystarczy dowolny element iterowalny
z elementami pozwalajcymi na porwnywanie. Funkcja smallest prezentowana w rozwizaniu tej receptury cao prac przekazuje zatem do funkcji heapq.nsmallest.
Jeeli chcemy rozmawia na temat prdkoci dziaania funkcji, zawsze musimy j zmierzy
prby zgadywania wzgldnych prdkoci rnych kawakw kodu to naprawd niebezpieczna gra. Jak w takim razie wyglda wydajno funkcji isorted w porwnaniu z funkcj
sorted dostpn w Pythonie 2.4, jeeli interesuje nas tylko kilka (najmniejszych) elementw?
W celu zmierzenia czasw pracy obu tych funkcji napisaem funkcj top10, ktrej mona uy
w poczeniu z obydwoma funkcjami. Poza tym musiaem si upewni, e funkcja sorted dostpna bdzie rwnie w Pythonie 2.3, mimo e nie jest ona w tej wersji funkcj wbudowan:

240 |

Rozdzia 5. Szukanie i sortowanie

try:
sorted
except:
def sorted(data):
data = list(data)
data.sort()
return data
import itertools
def top10(data, howtosort):
return list(itertools.islice(howtosort(data), 10))

Na moim komputerze z Pythonem 2.4 obsuenie listy skadajcej si z tysica dobrze przemieszanych liczb cakowitych funkcji top10 wspomaganej przez funkcj isorted zajmuje 260 mikrosekund, natomiast po zmianie na wspprac z wbudowan funkcj sorted ta sama operacja
trwa 850 mikrosekund. Co wicej, w Pythonie 2.3 funkcje te s o wiele wolniejsze: w poczeniu
z funkcj isorted czas testu wynis 12 milisekund, a z funkcj sorted 2,7 milisekundy. Innymi sowy, funkcja sorted w Pythonie 2.3 jest trzy razy wolniejsza ni w Pythonie 2.4, ale
funkcja isorted jest 50 razy wolniejsza. Wynika z tego nastpujca nauka: przy okazji wprowadzania jakiejkolwiek optymalizacji naley dokonywa pomiarw. Nie powinno si wybiera
optymalizacji na podstawie oglnych przesanek, poniewa wydajno rozwiza moe rni
si znaczco nawet pomidzy bardzo podobnymi do siebie gwnymi wydaniami Pythona.
Trzeba tu zaznaczy jeszcze jedn rzecz: jeeli komu zaley na wydajnoci, powinien jak najszybciej przesi si na Pythona 2.4. Nowsza wersja zostaa w wielu miejscach przyspieszona
i zoptymalizowana w stosunku do wersji 2.3, szczeglnie w zakresie wyszukiwania i sortowania.
Jeeli mamy pewno, e nasz kod bdzie dziaa wycznie w Pythonie 2.4, to tak jak pokazano w rozwizaniu tej receptury, naley skorzysta z funkcji nsmallest z moduu heapq, poniewa jest ona szybsza, a take prostsza od jakiegokolwiek samodzielnie przygotowanego kodu.
W celu zaimplementowania funkcji top10 w Pythonie 2.4 wystarczy tak prosty zapis:
import heapq
def top10(data):
return heapq.nsmallest(10, data)

Podana tu wersja t sam list tysica dokadnie przemieszanych liczb cakowitych przetwarza
dwa razy szybciej od prezentowanej wczeniej wersji korzystajcej z funkcji isorted.

Zobacz rwnie
Podrczniki Library Reference i Python in a Nutshell w czciach opisujcych metod sort i typ
list, a take moduy heapq i timeit. Rozdzia 19., w ktrym podawanych jest wicej informacji na temat iterowania w Pythonie. Podrcznik Python in a Nutshell w czci powiconej
optymalizacji. Plik rdowy heapq.py, w ktrym znale mona interesujc analiz stert. Receptur 5.7, w ktrej podawanych jest wicej informacji na temat moduu heapq.

5.9. Wyszukiwanie elementw


w sekwencji posortowanej
Pomysodawca: Noah Spurrier

Problem
W podanej sekwencji musimy odnale wiele elementw.
5.9. Wyszukiwanie elementw w sekwencji posortowanej

241

Rozwizanie
Jeeli lista L jest posortowana, to najprostszym sposobem na sprawdzenie, czy element x znajduje si na licie L jest wykorzystanie moduu bisect bdcego czci standardowej biblioteki
Pythona:
import bisect
x_insert_point = bisect.bisect_right(L, x)
x_is_present = L[x_insert_point-1:x_insert_point] == [x]

Analiza
W Pythonie wyszukiwanie elementu x na licie L jest wyjtkowo proste. Sprawdzenie, czy dany
element jest czci tej listy, wymaga tylko zapisania if x in L, natomiast uzyskanie informacji o dokadnej lokalizacji tego elementu wymaga zastosowania wywoania L.index(x). Jeeli L ma dugo n, to operacje te trwaj proporcjonalnie do dugoci n, co oznacza, e sprawdzaj w ptli wszystkie elementy listy, porwnujc je z elementem x. Jeeli lista L jest
posortowana, to operacje te mona wykona zdecydowanie szybciej.
Klasyczny algorytm wyszukiwania elementu w posortowanej sekwencji znany jest pod nazw
szukania binarnego (ang. binary search). Nazwa ta wynika z tego, e w kadym kroku algorytm
zmniejsza zakres poszukiwa mniej wicej o poow, czyli w oglnym przypadku zajmuje on
log2n krokw. Warto o tym wiedzie szczeglnie wtedy, gdy musimy czsto wyszukiwa elementy w sekwencji, przez co koszt sortowania mona zamortyzowa w czasie wielu wyszukiwa danych. Jeeli zdecydujemy si binarnie szuka elementu x na licie L, to po wywoaniu
L.sort() reszt pracy bardzo uatwi nam modu bisect z biblioteki standardowej Pythona.
W szczeglnoci przyda si nam funkcja bisect.bisect_right, ktra nie zmienia zawartoci
listy, ale zwraca indeks pod ktrym dany element powinien by wstawiony, aby lista pozostaa
posortowana. Co wicej, jeeli szukany element znajduje si ju na licie, to funkcja bisect_
right zwraca indeks elementu znajdujcego si po prawej stronie elementw o tej samej wartoci. Oznacza to, e po uzyskaniu miejsca wstawienia elementu x musimy ju tylko skontrolowa element znajdujcy si tu przed tym miejscem, sprawdzajc, czy element ten jest rwny
elementowi x.
Sposb wyliczenia wartoci zmiennej x_is_present, jaki prezentowany jest w rozwizaniu, moe nie by cakiem zrozumiay. Jeeli wiemy, e lista L nie jest pusta, to moemy skorzysta
ze znacznie prostszego i czytelniejszego rozwizania:
x_is_present = L[x_insert_point-1] == x

Niestety, jeeli lista bdzie pusta, to tak uproszczone indeksowanie spowoduje wyjtek. Wykrawanie dziaa w sposb mniej cisy ni indeksowanie, poniewa w przypadku nieprawidowych granic wykrojenia tworzy tylko puste wykrojenie, ale nie wywouje wyjtkw. Mwic
oglnie, wyraenie somelist[i:i+1] tworzy tak sam list jednoelementow jak wyraenie
[somelist[i]], pod warunkiem, e i jest prawidowym indeksem na licie somelist. W przypadku, w ktrym indeksowanie powoduje wyjtek IndexError, wyrojenie zwraca tylko pust
list []. Przedstawiony w rozwizaniu sposb wyliczania wartoci zmiennej x_is_present
korzysta z tej wanej moliwoci uniknicia obsugi wyjtkw i w jednakowy sposb obsuguje
puste i niepuste listy L. Oto jeszcze inny sposb rozwizania problemu:

242 |

Rozdzia 5. Szukanie i sortowanie

x_is_present = L and L[x_insert_point-1] == x

Powyszy kod wykorzystuje zachowanie operatora and polegajce na skrconym wyznaczaniu


wyniku i w ten sposb zabezpiecza cae wyraenie przed ewentualnymi bdami indeksowania
bez koniecznoci uywania wykroje.
Jeeli elementy listy s unikalne (co oznacza, e mog by traktowane jak klucze sownika),
to pomocniczy sownik rwnie moe by bardzo ciekawym rozwizaniem, o czym mona si
przekona w recepturze 5.12. Mimo to rozwizanie przedstawione w tej recepturze jest niezastpione w przypadkach, w ktrych mona porwnywa poszczeglne elementy listy (inaczej
listy nie daoby si posortowa), ale nie s one unikalne (wobec czego nie mog by kluczami
sownika).
Jeeli lista jest ju posortowana, a my musimy wyszuka w niej niezbyt du liczb elementw,
to zastosowanie moduu bisect najprawdopodobniej bdzie o wiele szybsze od tworzenia pomocniczego sownika, poniewa czas potrzebny na jego przygotowanie moe zniweczy pozostae korzyci. Prawdopodobiestwo to wzrasta jeszcze bardziej w Pythonie 2.4, poniewa
modu bisect zosta w nim zoptymalizowany i jest duo szybszy ni w Pythonie 2.3. Na
przykad na moim komputerze funkcja bisect.bisect_right wybierajca element mniej wicej ze rodka listy dziesiciu tysicy liczb cakowitych w Pythonie 2.4 jest niemal czterokrotnie
szybsza ni w Pythonie 2.3.

Zobacz rwnie
Dokumentacj moduu bisect w podrcznikach Library Reference i Python in a Nutshell. Receptur 5.12.

5.10. Wybieranie n-tego najmniejszego elementu


w sekwencji
Pomysodawcy: Raymond Hettinger, David Eppstein, Shane Holloway, Chris Perkins

Problem
Musimy wybra z sekwencji n-ty element pod wzgldem wielkoci (na przykad element rodkowy zwany median). Jeeli lista byaby posortowana, to mona byoby uy zapisu seq[n],
ale nasza sekwencja nie jest posortowana, wiec zastanawiamy si, czy jest lepszy sposb od jej
posortowania.

Rozwizanie
Oczywicie jest lepsze rozwizanie, sprawdzajce si w sytuacji, gdy sekwencja jest dua, jej
elementy s mocno przemieszane, a porwnanie tych elementw jest kosztown operacj. Sortowanie jest bardzo szybkie, ale dla dobrze przemieszanych sekwencji o n elementach i tak
zajmuje ono O(n log n) czasu, natomiast dostpne s algorytmy pozwalajce na odszukanie

5.10. Wybieranie n-tego najmniejszego elementu w sekwencji

| 243

n-tego najmniejszego elementu w czasie O(n). Oto funkcja bdca solidn implementacj takiego algorytmu:
import random
def select(data, n):
" Wyszukuje n-ty element pod wzgldem wielkoci. "
# tworzy now list, sprawdza indeksy <0, szuka prawidowych indeksw
data = list(data)
if n<0:
n += len(data)
if not 0 <= n < len(data):
raise ValueError, "nie mog pobra elementu %d spord %d" % (n, len(data))
# ptla gwna, podobna do algorytmu quicksort, ale nie potrzebuje rekursji
while True:
pivot = random.choice(data)
pcount = 0
under, over = [], []
uappend, oappend = under.append, over.append
for elem in data:
if elem < pivot:
uappend(elem)
elif elem > pivot:
oappend(elem)
else:
pcount += 1
numunder = len(under)
if n < numunder:
data = under
elif n < numunder + pcount:
return pivot
else:
data = over
n -= numunder + pcount

Analiza
W prezentowanej recepturze chodzi nam o przypadki, w ktrych wane s powtrzenia. Na
przykad mediana z listy [1, 1, 1, 2, 3] wynosi 1, poniewa jest to trzeci element z piciu
w kolejnoci rosncej. Jeeli z jakiego dziwnego powodu chcielibymy nie uwzgldnia powtrze, to tak list trzeba by najpierw zredukowa do jej elementw unikalnych (na przykad
stosujc rozwizania podawane w recepturze 18.1), a dopiero potem wrci do kodu podawanego w tej recepturze.
Wejciowy parametr data moe by dowolnym elementem iterowalnym. Kod tej receptury
rozpoczyna si od wywoania na wszelki wypadek funkcji list. Nastpnie algorytm wchodzi
w ptl, w ktrej przy kadym kroku implementuje kilka wanych operacji: losowo wybiera
element rozdzielajcy (ang. pivot element), dzieli list na dwie czci skadajce si odpowiednio
z elementw poniej i ponad elementem rozdzielajcym, w nastpnym kroku kontynuuje
prac w jednej z czci listy, poniewa na tym etapie wiemy ju, w ktrej czci znajdzie si
szukany n-ty element. Pomys jest bardzo zbliony do klasycznego algorytmu znanego pod
nazw quicksort (rnica polega na tym, e algorytm quicksort musi obsuy obie czci listy
i w zwizku z tym zmuszony jest do korzystania z rekursji lub metod usuwania rekursji takich
jak utrzymywanie wasnego stosu zapewniajcego obsug caoci listy).

244 |

Rozdzia 5. Szukanie i sortowanie

Losowe wybranie elementu rozdzielajcego sprawia, e algorytm lepiej sprawdza si w przypadkach niekorzystnego uoenia danych (takich, ktre siej spustoszenie w naiwnych implementacjach algorytmu quicksort). Podana implementacja mniej wicej log2N razy wywouje
funkcj random.choice. Inn wart wymienienia cech implementacji prezentowanej w rozwizaniu tej receptury jest zliczanie liczby wystpie elementu rozdzielajcego. Takie dziaanie
zapewnia dobr wydajno nawet w niezwykych przypadkach, w ktrych parametr data zawiera wiele powtrze poszczeglnych wartoci.
Wydobywanie z list under oraz over powizanych metod .append i przypisywanie ich do
lokalnych zmiennych uappend i oappend na pierwszy rzut oka moe wydawa si niecelowe,
a na dodatek powodujce pewne komplikacje, ale w rzeczywistoci jest to jedna z najwaniejszych metod optymalizacji w Pythonie. W celu utrzymania prostej, solidnej i pozbawionej niespodzianek struktury kompilatora Python nie przenosi staych wylicze poza ptle, tak jak
i nie buforuje wynikw poszukiwania metod. Jeeli wywoujemy metody under.append
i over.append w ramach ptli, to przy kadej iteracji musimy ponie koszt wyszukania wywoywanej metody. Jeeli chcemy, eby co byo przechowywane, to sami musimy to przechowa. Jeeli zastanawiamy si nad pewn optymalizacj, to zawsze powinnimy zmierzy
wydajno kodu bez tej optymalizacji i z ni. Tylko w ten sposb mona oceni, czy wprowadzona optymalizacja rzeczywicie wprowadza zauwaaln rnic. Zgodnie z moimi pomiarami
usunicie tej jednej optymalizacji powoduje mniej wicej 50% spadek wydajnoci w typowym
zadaniu wybierania piciotysicznego elementu z zakresu range(10000). Uwzgldniajc t
niewielk komplikacj, jak wprowadza stosowany zapis, z ca pewnoci jest on wart dwukrotnego przyspieszenia dziaania tej funkcji.
Do naturalnym pomysem na optymalizacj, z ktrego zrezygnowaem dopiero po wykonaniu dokadnych pomiarw, jest wywoanie w ciele ptli funkcji cmp(elem, pivot), ktre miaoby zastpi osobne porwnania elem < pivot i elem > pivot. Niestety, pomiary wykazay,
e funkcja cmp nie przyspiesza dziaania ptli, ale spowalnia j, przynajmniej w przypadkach,
gdy elementami sekwencji data s typy podstawowe, takie jak liczby lub cigi znakw.
Jak w takim razie wyglda wydajno funkcji select w porwnaniu ze znacznie prostsz funkcj przedstawion poniej?
def selsor(data, n):
data = list(data)
data.sort()
return data[n]

Na moim komputerze wybranie mediany z dobrze przemieszanej listy skadajcej si z 3001


liczb cakowitych, zajmuje funkcji select mniej wicej 16 milisekund, podczas gdy funkcja
selsor t sam operacj przeprowadza w 13 milisekund. Biorc pod uwag fakt, e metoda
sort moe wykorzystywa dowoln posortowan ju cz danych, to w przypadku list podobnej dugoci skadajcych si z atwo porwnywalnych typw podstawowych wykorzystanie funkcji select nie jest najlepszym wyborem. W przypadku list o dugoci 30 001 elementw wydajno obu rozwiza jest bardzo podobna i wynosi okoo 170 milisekund. Dopiero
w momencie, gdy zaczniemy pracowa z listami o wielkoci zblionej do 300 001 elementw,
funkcja select zaczyna przewaa na funkcj selsor czasy ich pracy wynosz odpowiednio 2,2 sekundy i 2,5 sekundy.
Punkt zrwnania wydajnoci porwnywanych funkcji bdzie znacznie niszy, jeeli sekwencje
skada si bd z elementw o bardzo zoonych i kosztownych porwnaniach, poniewa
gwna rnica midzy tymi rozwizaniami polega na liczbie wykonywanych porwna

5.10. Wybieranie n-tego najmniejszego elementu w sekwencji

| 245

funkcja select wykonuje O(n) porwna, a funkcja selsor wykonuje ich O(n log n).
Zamy na przykad, e musimy porwna egzemplarze klasy, w ktrej operacje porwnania
s do kosztowne (symuluje ona punkt czterowymiarowy, w ktrym kilka pierwszych wymiarw moe si pokrywa):
class X(object):
def __init__(self):
self.a = self.b = self.c = 23.51
self.d = random.random()
def _dats(self):
return self.a, self.b, self.c, self.d
def __cmp__(self, oth):
return cmp(self._dats, oth._dats)

W takiej sytuacji funkcja select zaczyna dziaa szybciej od funkcji selsor ju w momencie
wyznaczania mediany z wektora skadajcego si z 201 takich egzemplarzy.
Innymi sowy, co prawda funkcja select wykonuje wicej oglnych operacji nadmiarowych
w porwnaniu z niezwykle efektywnym sposobem dziaania metody sort, to jednak w przypadku, gdy n jest odpowiednio due, a kade porwnanie jest wystarczajco kosztowne zastosowanie funkcji select jest warte rozwaenia.

Zobacz rwnie
Podrczniki Library Reference i Python in a Nutshell w czciach opisujcych metod sort,
typ list oraz modu random.

5.11. Algorytm quicksort w trzech wierszach kodu


Pomysodawcy: Nathaniel Gray, Raymond Hettinger, Christophe Delord, Jeremy Zucker

Problem
Musimy wykaza, e obsuga paradygmatu programowania funkcyjnego w Pythonie jest lepsza
ni mona si tego spodziewa na pierwszy rzut oka.

Rozwizanie
Jzyki programowania funkcyjnego, wrd ktrych prym wiedzie jzyk Haskell, to wyjtkowo
udane konstrukcje, ale nawet w takim towarzystwie Python prezentuje si zadziwiajco dobrze:
def qsort(L):
if len(L) <= 1: return L
return qsort([lt for lt in L[1:] if lt < L[0]]) + L[0:1] + \
qsort([ge for ge in L[1:] if ge >= L[0]])

Moim skromnym zdaniem podany kod jest niemal tak pikny jak wersja zapisana w jzyku
Haskell, pobrana ze strony http://www.haskell.org:
qsort [] = []
qsort (x:xs) = qsort elts_lt_x ++ [x] ++ qsort elts_greq_x
where
elts_lt_x = [y | y <- xs, y < x]
elts_greq_x = [y | y <- xs, y >= x]

246 |

Rozdzia 5. Szukanie i sortowanie

Oto funkcja testujca wersj przygotowan w Pythonie:


def qs_test(length):
import random
joe = range(length)
random.shuffle(joe)
qsJoe = qsort(joe)
for i in range(len(qsJoe)):
assert qsJoe[i] == i, 'qsort przesta dziaa na pozycji %d!' %i

Analiza
Ta raczej naiwna implementacja algorytmu quicksort doskonale ilustruje si list skadanych.
Takiego rozwizania nie naley jednak stosowa w kodzie produkcyjnym! W Pythonie listy
uzupeniane s o metod sort, ktra dziaa o wiele szybciej od prezentowanej w tej recepturze. W Pythonie 2.4 nowa wbudowana funkcja sorted dziaa z dowoln skoczon sekwencj i zwraca now, posortowan list elementw tej sekwencji. Jedynym waciwym zastosowaniem kodu z tej receptury jest pokazywanie go znajomym programistom, szczeglnie tym,
ktrzy (co zrozumiae) bardzo entuzjastycznie traktuj jzyki funkcyjne, a przede wszystkim
jzyk Haskell.
Podan funkcj przygotowaem po znalezieniu na stronie http://www.haskell.org/aboutHaskell.html
wspaniaej implementacji algorytmu quicksort w jzyku Haskell (podaem j rwnie w rozwizaniu tej receptury). Po okresie podziwiania elegancji znalezionego kodu uwiadomiem
sobie, e dokadnie takie samo rozwizanie moliwe jest w Pythonie przy zastosowaniu list
skadanych. Nie na darmo zostay one poyczone z jzyka Haskell i lekko spythonizowane,
tak eby moliwe byo wykorzystanie w nich sw kluczowych, a nie tylko operatorw.
Obie implementacje dziel list na jej pierwszym elemencie i dlatego ich wydajno w najgorszym przypadku, czyli w bardzo powszechnym przypadku sortowania posortowanej listy,
wynosi O(n). Z ca pewnoci nie chcielibymy stosowa takiego rozwizania w kodzie produkcyjnym! Omawiana procedura jest jednak czyst propagandwk, wic takie szczegy
nie maj znaczenia.
Mona te zapisa mniej kompaktow wersj o podobnej architekturze, stosujc w niej nazywane zmienne lokalne oraz funkcje poprawiajce czytelno kodu:
def qsort(L):
if not L: return L
pivot = L[0]
def lt(x): return x<pivot
def ge(x): return x>=pivot
return qsort(filter(lt, L[1:]))+[pivot]+qsort(filter(ge, L[1:]))

Skoro weszlimy ju na t ciek, to bardzo atwo moemy podan wersj przeksztaci w wersj nieco mniej naiwn, wykorzystujc losowe wybieranie elementu rozdzielajcego, przez co
zmniejsza si prawdopodobiestwo wystpienia najgorszego przypadku, oraz zliczajc elementy rozdzielajce, przez co poprawia si obsuga przypadkw z wieloma jednakowymi
elementami:
import random
def qsort(L):
if not L: return L
pivot = random.choice(L)
def lt(x): return x<pivot
def gt(x): return x>pivot
return qsort(filter(lt, L))+[pivot]*L.count(pivot)+qsort(filter(gt, L))

5.11. Algorytm quicksort w trzech wierszach kodu

247

Mimo takich modyfikacji wersja ta rwnie przeznaczona jest gwnie do zabawy i celw demonstracyjnych. Porzdny kod sortujcy musi wyglda zupenie inaczej: te pereki, nad ktrymi si tak rozwodzimy, nigdy nie osign wydajnoci i skutecznoci rozwiza sortowania
wbudowanych w Pythona.
Zamiast prbowa poprawia czytelno kodu, moemy zacz dziaa w przeciwnym kierunku i prbowa tworzy przede wszystkim skuteczne rozwizanie, pokazujc przy okazji, e
w Pythonie sowo kluczowe lambda pozwala na uzyskanie bardzo zwartego, ale i dziwnego zapisu:
q=lambda x:(lambda o=lambda s:[i for i in x if cmp(i,x[0])==s]:
len(x)>1 and q(o(-1))+o(0)+q(o(1)) or x)()

W przypadku tego potworka (pojedynczy wiersz kodu, ktry z powodu swojej dugoci musi
zosta podzielony na dwa wiersze) wida wyranie, e takie rozwizania nie powinny by stosowane w rzeczywistych programach. Nawet bardziej czytelna, rwnowana wersja stosujca
instrukcje def zamiast instrukcji lambda bdzie nie do koca zrozumiaa:
def q(x):
def o(s): return [i for i in x if cmp(i,x[0])==s]
return len(x)>1 and q(o(-1))+o(0)+q(o(1)) or x

Nieco czytelniejszy kod mona utworzy, rozbijajc bardzo zbit instrukcj len(x)>1 and ...
or x na instrukcje if/else i wprowadzajc odpowiednie nazwy zmiennych:
def q(x):
if len(x)>1:
lt = [i for i in x if cmp(i,x[0]) == -1 ]
eq = [i for i in x if cmp(i,x[0]) == 0 ]
gt = [i for i in x if cmp(i,x[0]) == 1 ]
return q(lt) + eq + q(gt)
else:
return x

Na szczcie prawdziwi Pythonianie s zbyt wraliwi, eby znosi i tworzy takie potworki
wypenione instrukcjami lambda, jak te prezentowane w tej recepturze. W rzeczywistoci wielu
z nas (cho,oczywicie nie wszyscy) czuje awersj do tej instrukcji (czciowo z powodu moliwoci jej naduywania) i zdecydowanie woli stosowa czytelniejsze konstrukcje z instrukcjami
def. W efekcie umiejtno odczytywania takich zamieconych wierszy nie jest wymagana
w wiecie Pythona, jak to bywa w innych jzykach programowania. W podobny sposb programista prbujcy zapisa sprytny kod moe naduy dowolnej funkcji jzyka, dlatego cz
Pythonian (zdecydowana mniejszo) podobn awersj darzy inne funkcje jzyka, takie jak listy
skadane (poniewa w wyraeniu listy skadanej mona umieci zbyt wiele niepotrzebnych
elementw, podczas gdy prosta ptla for byaby zdecydowanie czytelniejsza) lub wykorzystywanie warunkowoci operatorw logicznych and i or (poniewa mona w ten sposb tworzy
nieczytelne wyraenia, ktre daj si zastpi znacznie czytelniejsz instrukcj if).

Zobacz rwnie
Stron jzyka Haskell http://www.haskell.org.

248 |

Rozdzia 5. Szukanie i sortowanie

5.12. Wykonywanie czstych testw


obecnoci elementw sekwencji
Pomysodawca: Alex Martelli

Problem
Musimy wykonywa czste testy na obecno danego elementu w sekwencji. Wydajno O(n)
czsto wywoywanego operatora in moe bardzo negatywnie wpyn na prac programu,
ale nie moemy po prostu zastpi sekwencji sownikiem lub zbiorem, poniewa wana jest te
kolejno jej elementw.

Rozwizanie
Zamy, e musimy dodawa do sekwencji elementy, ale tylko wtedy, gdy nie zostay one
dodane do niej wczeniej. Ponisza funkcja jest doskonaym rozwizaniem tego zadania:
def addUnique(baseList, otherList):
auxDict = dict.fromkeys(baseList)
for item in otherList:
if item not in auxDict:
baseList.append(item)
auxDict[item] = None

Jeeli nasz kod ma dziaa wycznie w Pythonie 2.4, to dokadnie takie same efekty uzyskamy
za pomoc dodatkowego zbioru, a nie sownika.

Analiza
Najprostsze (naiwne?) rozwizanie problemu z tej receptury wyglda cakiem dobrze:
def addUnique_simple(baseList, otherList):
for item in otherList:
if item not in baseList:
baseList.append(item)

i nawet moe sprawowa si niele pod warunkiem, e listy bd bardzo krtkie.


Niestety, tak proste rozwizanie okae si bardzo powolne przy pracach z dugimi listami. Jeeli zastosujemy instrukcj if item not in baseList, to Python w tylko jeden sposb moe
zaimplementowa operator in za pomoc wewntrznej ptli iterujcej we wszystkich elementach listy baseList, ktra koczy si z wynikiem True, gdy tylko znaleziony zostanie element
listy identyczny z elementem item albo wynikiem False, jeeli aden z elementw listy nie
okaza si identyczny z elementem item. W typowych przypadkach zastosowanie operatora in
zajmuje czas proporcjonalny do liczby elementw listy baseList. Funkcja addUnique_simple
operator in wywouje len(otherList) razy, wic w sumie czas jej dziaania jest proporcjonalny do iloczynu dugoci obu tych list.
W funkcji addUnique przedstawionej w rozwizaniu tej receptury tworzymy najpierw pomocniczy sownik auxDict krok ten zajmuje czas proporcjonalny do dugoci listy baseList.
Nastpnie operator in umieszczony w ptli sprawdza, czy zadany element jest skadnikiem

5.12. Wykonywanie czstych testw obecnoci elementw sekwencji

| 249

sownika. To wanie ten krok stanowi gwn rnic midzy obiema funkcjami, poniewa
operator in zastosowany wobec sownika dziaa w czasie staym, niezalenie od liczby elementw w sowniku. Oznacza to, e ptla for dziaa bdzie w czasie proporcjonalnym do dugoci
listy otherList, a caa funkcja dziaa w czasie proporcjonalnym do sumy dugoci obu list.
Tak analiz czasw dziaania funkcji powinnimy przeprowadzi nieco gbiej, poniewa
w funkcji addUnique_simple dugo listy baseList nie jest staa. Lista baseList ronie za
kadym razem, gdy badany element nie jest czci tej listy. Wynik takiej (zaskakujco zoonej) analizy nie rniby si jednak wcale od wynikw analizy uproszczonej. Jeeli kada z list
skadaaby si z 10 liczb cakowitych, z ktrych tylko 50% byoby ze sob zgodnych, to prostsza
wersja funkcji byaby o mniej wicej 30% wolniejsza od funkcji prezentowanej w rozwizaniu
taki spadek wydajnoci mona z czystym sumieniem zignorowa. Niestety, ju w przypadku list zawierajcych sto elementw pokrywajcych si w 50%, prostsza wersja jest a dwanacie
razy wolniejsza od wersji oficjalnej. Takich wynikw na pewno nie mona zignorowa, tym
bardziej, e rnica ta powiksza si wraz ze wzrostem wielkoci list.
Czasami mona uzyska jeszcze lepsz wydajno caego programu, stosujc pomocniczy sownik rwnolegle z sam sekwencj i zamykajc je w ramach jednego obiektu. W takim jednak
przypadku musimy dba o aktualizowanie zawartoci sownika w czasie modyfikowania sekwencji, zapewniajc tym samym jego synchronizacj z sekwencj. Zadanie takiego synchronizowania sownika z ca pewnoci nie jest trywialne i moe by zrealizowane na wiele rnych sposobw. Oto jeden ze sposobw synchronizowania sownika w razie potrzeby, czyli
tylko w momencie testowania obecnoci elementu w sekwencji, gdy istnieje prawdopodobiestwo, e sownik nie jest zsynchronizowany z sekwencj. Koszt takiej operacji jest niewielki, dlatego podana niej klasa optymalizuje metod index, jak rwnie testy na obecno elementu:
class list_with_aux_dict(list):
def __init__(self, iterable=()):
list.__init__(self, iterable)
self._dict_ok = False
def _rebuild_dict(self):
self._dict = {}
for i, item in enumerate(self):
if item not in self._dict:
self._dict[item] = i
self._dict_ok = True
def __contains__(self, item):
if not self._dict_ok:
self._rebuild_dict()
return item in self._dict
def index(self, item):
if not self._dict_ok:
self._rebuild_dict()
try: return self._dict[item]
except KeyError: raise ValueError
def _wrapMutatorMethod(methname):
_method = getattr(list, methname)
def wrapper(self, *args):
# Kasowanie znacznika 'sownik OK' i delegowanie prawdziwej metody modyfikujcej
self._dict_ok = False
return _method(self, *args)
# tylko w Pythonie 2.4: wrapper.__name__ = _method.__name__
setattr(list_with_aux_dict, methname, wrapper)
for meth in 'setitem delitem setslice delslice iadd'.split():
_wrapMutatorMethod('__%s__' % meth)
for meth in 'append insert pop remove extend'.split():
_wrapMutatorMethod(meth)
del _wrapMutatorMethod
# usuwamy funkcj pomocnicz, nie bdzie ju nam potrzebna

250

Rozdzia 5. Szukanie i sortowanie

Klasa list_with_aux_dict rozbudowuje klas list i tworzy delegacj wszystkich jej metod
za wyjtkiem metod __contains__ i index. Kada z metod, ktra moe zmodyfikowa zawarto listy, opakowywana jest domkniciem kasujcym znacznik zgodnoci sownika z list.
W Pythonie operator in wywouje w obiekcie metod __contains__. Metoda ta w klasie list_
with_aux_dict powoduje przebudowanie pomocniczego sownika, chyba e znacznik zgodnoci jest ustawiony (bo wtedy przebudowa sownika nie jest ju konieczna). W podobny sposb
dziaa te metoda index.
Zamiast budowania i instalowania domkni opakowujcych wszystkie metody modyfikujce zawarto listy za pomoc funkcji pomocniczej klasy list_with_aux_dict, tak jak zrobiono
to w podanym kodzie, mona te napisa osobne opakowanie dla kadej z metod, korzystajc
przy tym z instrukcji def. Mimo to kod zaprezentowanej klasy ma nad takim rozwizaniem
niezaprzeczaln przewag, jako e minimalizuje potrzeb stosowania powtarzajcego si,
nudnego kodu o znacznej objtoci, w ktrym bardzo czsto ukrywaj si bdy. Moliwoci,
jakie Python daje nam w zakresie introspekcji i dynamicznej modyfikacji, pozwalaj nam zadecydowa: moemy budowa opakowania metod tak jak zostao to zrobione w prezentowanej
klasie, czyli spjnie i sprytnie, ale jeeli nie opanowalimy jeszcze czarnej magii introspekcji
i dynamicznej modyfikacji obiektw, to rwnie dobrze moe zdecydowa si na tworzenie powtarzajcego si kodu.
Architektura klasy list_with_aux_dict jest doskonaym przykadem bardzo powszechnego
wzorca uycia, stosowanego w sytuacjach, gdy operacje modyfikujce sekwencje zdarzaj si
w paczkach. Pomidzy takimi paczkami modyfikacji nastpuj okresy, w ktrych sekwencja nie jest poddawana adnym modyfikacjom, ale wykonywane s testy na obecno elementw. Niestety, prezentowana wczeniej funkcja addUnique_simple nie zyskaaby na prdkoci,
jeeli w parametrze baseList zamiast zwykego obiektu list otrzymaaby egzemplarz klasy
list_with_aux_dict, poniewa funkcja ta przeplata ze sob testy na obecno elementu i modyfikacje zawartoci listy. W takich warunkach zbyt wiele operacji przebudowywania pomocniczego sownika bardzo le wpywaoby na prdko dziaania funkcji (chyba e w znakomitej
wikszoci przypadkw elementy listy otherList s ju czci listy baseList, a zatem modyfikacji listy bdzie o wiele mniej ni operacji sprawdzania obecnoci).
Bardzo wan czci wszystkich takich optymalizacji testw obecnoci jest wymg, eby elementy sekwencji byy unikalne (jeeli tak nie bdzie, to nie mog one by oczywicie kluczami
sownika, ani elementami zbioru). Podane w tej recepturze funkcje mogyby by na przykad
wykorzystane do obsugi listy krotek, ale zupenie nie nadawayby si do obsugi listy list.

Zobacz rwnie
Podrczniki Library Reference i Python in a Nutshell w czciach opisujcych typy sekwencji
i typy odwzorowa.

5.13. Wyszukiwanie podsekwencji


Pomysodawcy: David Eppstein, Alexander Semenov

Problem
Musimy znale wystpienia pewnej podsekwencji w ramach wikszej sekwencji.
5.13. Wyszukiwanie podsekwencji

251

Rozwizanie
Jeeli sekwencjami s cigi znakw (proste lub Unikodu), to zdecydowanie najlepszym wyjciem jest metoda find oraz standardowy modu re. W przypadku innych sekwencji naley
posuy si algorytmem Knutha-Morrisa-Pratta (KMP):
def KnuthMorrisPratt(text, pattern):
''' Zwraca wszystkie pozycje pocztkw kopii podsekwencji 'pattern'
w ramach sekwencji 'text' -- kady z parametrw moe by dowolnym
elementem iterowalnym. Przy kadym zwrceniu elementu parametr 'text'
zostaje odczytany do samego koca znalezionej podsekwencji 'pattern'. '''
# musimy zapewni sobie moliwo indeksowania w parametrze pattern,
# a jednoczenie utworzy jego kopi na wypadek wprowadzenia do niego zmian
# w czasie, gdy funkcja jest wstrzymana przez instrukcj `yield'
pattern = list(pattern)
length = len(pattern)
# budujemy "tablic wartoci przesuni" i nazywamy j 'shifts'
shifts = [1] * (length + 1)
shift = 1
for pos, pat in enumerate(pattern):
while shift <= pos and pat != pattern[pos-shift]:
shift += shifts[pos-shift]
shifts[pos+1] = shift
# wykonanie waciwego wyszukiwania
startPos = 0
matchLen = 0
for c in text:
while matchLen == length or matchLen >= 0 and pattern[matchLen] != c:
startPos += shifts[matchLen]
matchLen -= shifts[matchLen]
matchLen += 1
if matchLen == length: yield startPos

Analiza
W niniejszej recepturze implementujemy algorytm Knutha-Morrisa-Pratta przeznaczony do wyszukiwania kopii danego wzorca w ramach cigej podsekwencji wikszego tekstu. Algorytm
KMP korzysta z tekstu w sposb sekwencyjny, dlatego bardzo naturalnym rozwizaniem jest
zezwolenie na stosowanie tekstu w postaci dowolnego elementu iterowalnego. Po zakoczeniu fazy przygotowa wstpnych, w ktrej budowana jest tabela wartoci przesuni i ktra
zajmuje czas proporcjonalny do dugoci szukanego wzorca, kady z symboli przetwarzany
jest w czasie staym. Wyjanienia dotyczce pracy algorytmu KMP mona znale w dowolnej
dobrej ksice opisujcej algorytmy (rekomendacje podajemy w punkcie Zobacz rwnie).
Jeeli parametry text i pattern s cigami znakw, to mona zastosowa zdecydowanie szybsze rozwizanie, wykorzystujc przy tym metody wbudowane Pythona:
def finditer(text, pattern):
pos = -1
while True:
pos = text.find(pattern, pos+1)
if pos < 0: break
yield pos

Na przykad, korzystajc z alfabetu o dugoci 4 ('ACGU'), odnalezienie wszystkich wystpie


danego wzorca o dugoci 8 w ramach tekstu o dugoci 100 000 na moim komputerze zajmuje
funkcji finditer mniej wicej 4,3 milisekundy, natomiast funkcja KnuthMorrisPratt to samo
252

Rozdzia 5. Szukanie i sortowanie

zadanie realizuje w cigu 540 milisekund (te wyniki dotycz Pythona 2.3, w Pythonie 2.4 algorytm KMP dziaa nieco szybciej, a wspomniane zadanie realizowane jest w cigu 480 milisekund, ale mimo to jest to wynik ponad stukrotnie gorszy od wyniku funkcji finditer).
Naley zatem pamita: kod podany w tej recepturze nadaje si do przeszukiwania dowolnych
sekwencji, wcznie z tymi, ktrych nie da si w caoci przechowywa w pamici, ale przy przeszukiwaniu cigw znakw zdecydowanie lepiej sprawdzaj si metody wbudowane Pythona.

Zobacz rwnie
Podstawy algorytmw, ktre s opisywane w wielu doskonaych ksikach. Wrd nich jedn
z najbardziej polecanych jest pozycja Thomasa H. Cormena, Charlesa E. Leisersona, Ronalda
L Rivesta i Clifforda Steina Introduction to Algorithms, wydanie drugie (MIT Press).

5.14. Wzbogacanie typu dict


o moliwo wprowadzania ocen
Pomysodawcy: Dmitry Vasiliev, Alex Martelli

Problem
Chcemy uy sownika do przechowywania odwzorowa kluczy i aktualnych wartoci ocen
tych kluczy. Niejednokrotnie musimy uzyska dostp do kluczy i ocen w kolejnoci naturalnej
(co oznacza malejce wartoci ocen) albo sprawdza aktualn pozycj danego klucza w takim
rankingu. To wszystko sugeruje, e samo zastosowanie sownika nie jest wystarczajce.

Rozwizanie
Moemy utworzy klas wywiedzion z klasy dict i doda do niej lub pokry potrzebne nam
metody. W ramach dziedziczenia wielobazowego moemy jako pierwsz klas bazow oznaczy klas UserDict.DictMixin i dopiero za ni dopisa klas dict, a w nowej klasie mona
ostronie przygotowa rne delegacje i pokrywania metod. W ten sposb uzyskamy rwnowag pomidzy niez wydajnoci klasy a koniecznoci tworzenia powtarzalnego kodu.
Wzbogacajc nasz klas o wiele przykadw zapisanych w jej dokumentacji, moemy skorzysta te z moduu biblioteki standardowej doctest, przez co klasa zostanie uzupeniona o funkcje testw moduowych, a my uzyskamy pewno, e przykady podane w dokumentacji bd
zgodne z prawd:
#!/usr/bin/env python
''' Wzbogacony sownik przechowujcy klucze powizane z ich ocenami '''
from bisect import bisect_left, insort_left
import UserDict
class Ratings(UserDict.DictMixin, dict):
""" klasa Ratings jest bardzo zbliona do sownika uzupenionego
o kilka dodatkowych funkcji: Warto powizana z kadym kluczem
jest traktowana jak jego 'ocena', a wszystkie klucze ukadane s wedug
tych ocen. Wartoci musz by porwnywalne, natomiast klucze, oprcz
tego, e musz by unikalne, musz te by porwnywalne na wypadek,

5.14. Wzbogacanie typu dict o moliwo wprowadzania ocen

253

gdyby przechowyway takie same wartoci (czyli miay t sam ocen).


Wszystkie zachowania zwizane z odwzorowywaniem s dokadnie takie,
jakich si spodziewamy, na przykad:
>>> r = Ratings({"piotr": 30, "pawe": 30})
>>> len(r)
2
>>> r.has_key("rafa"), "rafa" in r
(False, False)
>>> r["pawe"] = 20
>>> r.update({"wojtek": 20, "tomek": 10})
>>> len(r)
4
>>> r.has_key("pawe"), "pawe" in r
(True, True)
>>> [r[key] for key in ["piotr", "pawe", "wojtek", "tomek"]]
[30, 20, 20, 10]
>>> r.get("nikt"), r.get("nikt", 0)
(None, 0)
Oprcz interfejsu sownikowego klasa udostpnia metody zwizane
z ocenami. Metoda r.rating(key) zwraca pozycj danego klucza w
skali ocen, przy czym pozycja 0 oznacza ocen najnisz (jeeli
dwa klucze maj takie same oceny, to porwnywane s bezporednio
i mniejszy z nich otrzymuje nisz pozycj):
>>> [r.rating(key) for key in ["piotr", "wojtek", "pawe", "tomek"]]
[3, 2, 1, 0]
Metody getValueByRating(ranking) i getKeyByRating(ranking) zwracaj
ocen lub klucz odpowiedniej pozycji w rankingu:
>>> [r.getValueByRating(rating) for rating in range(4)]
[10, 20, 20, 30]
>>> [r.getKeyByRating(rating) for rating in range(4)]
['piotr', 'wojtek', 'pawe', 'tomek']
Metoda keys() zwraca klucze w kolejnoci malejcych pozycji,
a wszystkie pozostae metody zwracaj listy lub iteratory w peni
zgodne z t wanie kolejnoci:
>>> r.keys()
['piotr', 'wojtek', 'pawe', 'tomek']
>>> [key for key in r]
['piotr', 'wojtek', 'pawe', 'tomek']
>>> [key for key in r.iterkeys()]
['piotr', 'wojtek', 'pawe', 'tomek']
>>> r.values()
[10, 20, 20, 30]
>>> [value for value in r.itervalues()]
[10, 20, 20, 30]
>>> r.items()
[('tomek', 10), ('pawe', 20), ('wojtek', 20), ('piotr', 30)]
>>> [item for item in r.iteritems()]
[('tomek', 10), ('pawe', 20), ('wojtek', 20), ('piotr', 30)]
Egzemplarz klasy moe by dowolnie zmodyfikowany (mona dodawa,
zmienia i usuwa pary klucz-ocena), a kada metoda tego egzemplarza
zawsze bdzie odzwierciedla jego aktualny stan:
>>> r["tomek"] = 100
>>> r.items()
[('wojtek', 20), ('pawe', 20), ('piotr', 30), ('tomek', 100)]
>>> del r["pawe"]
>>> r.items()
[('wojtek', 20), ('piotr', 30), ('tomek', 100)]
>>> r["paul"] = 25
>>> r.items()
[('wojtek', 20), ('pawe', 25), ('piotr', 30), ('tomek', 100)]
>>> r.clear()
>>> r.items()
[]
"""

254 |

Rozdzia 5. Szukanie i sortowanie

''' Implementacja klasy miesza ze sob dziedziczenie z delegacjami w celu osignicia


zadowalajcej wydajnoci, minimalizacji powtarzalnego kodu i oczywicie
uzyskania poprawnej semantyki Wszystkie niezaimplementowane metody tego
odwzorowania s dziedziczone, przede wszystkim po klasie DictMixin, ale najwaniejsze
jest to, e metoda __getitem__ dziedziczona jest po klasie dict. '''
def __init__(self, *args, **kwds):
''' Egzemplarze tej klasy tworzone s tak jak egzemplarze klasy dict '''
dict.__init__(self, *args, **kwds)
# self._rating jest bardzo wan, pomocnicz struktur danych: lista
# wszystkich par (warto, klucz), utrzymywana w kolejnoci naturalnego
# posortowania
self._rating = [ (v, k) for k, v in dict.iteritems(self) ]
self._rating.sort()
def copy(self):
''' Tworzy identyczn, ale niezalen kopi '''
return Ratings(self)
def __setitem__(self, k, v):
''' oprcz delegowania do klasy dict zarzdzamy struktur self._rating '''
if k in self:
del self._rating[self.rating(k)]
dict.__setitem__(self, k, v)
insort_left(self._rating, (v, k))
def __delitem__(self, k):
''' oprcz delegowania do klasy dict zarzdzamy struktur self._rating '''
del self._rating[self.rating(k)]
dict.__delitem__(self, k)
''' jawnie delegujemy pewne metody do klasy dict, aby unikn
stosowania wolniejszej implementacji z klasy DictMixin '''
__len__ = dict.__len__
__contains__ = dict.__contains__
has_key = __contains__
''' Gwne poczenie semantyczne pomidzy self._rating i pozycj
w self.keys() -- DictMixin daje nam 'za darmo' wszystkie potrzebne metody, mimo e moglibymy
zaimplementowa je bezporednio z nieco lepsz wydajnoci. '''
def __iter__(self):
for v, k in self._rating:
yield k
iterkeys = __iter__
def keys(self):
return list(self)
''' trzy metody zwizane z ocenami '''
def rating(self, key):
item = self[key], key
i = bisect_left(self._rating, item)
if item == self._rating[i]:
return i
raise LookupError, "elementu nie znaleziono"
def getValueByRating(self, rating):
return self._rating[rating][0]
def getKeyByRating(self, rating):
return self._rating[rating][1]
def _test():
''' korzystamy z moduu doctest w celu przetestowania tego moduu, ktry naley nazwa
rating.py. Testy realizowane s przez wykonanie wszystkich przykadw z dokumentacji. '''
import doctest, rating
doctest.testmod(rating)
if __name__ == "__main__":
_test()

5.14. Wzbogacanie typu dict o moliwo wprowadzania ocen

255

Analiza
Pod wieloma wzgldami sownik jest najbardziej naturaln struktur danych w zakresie przechowywania zwizkw midzy kluczami (na przykad nazwiskami uczestnikw pewnego konkursu) i ich aktualnymi ocenami (na przykad liczby punktw, jakie poszczeglni uczestnicy
zdobyli do tej pory albo najwysze stawki zaproponowane przez poszczeglnych uczestnikw
aukcji). Jeeli ze sownika skorzystamy w takim wanie celu, to najprawdopodobniej bdziemy
chcieli te odczytywa jego elementy w kolejnoci naturalnej, czyli w kolejnoci rosncych wartoci ocen, a poza tym bdziemy chcieli szybko uzyska aktualn pozycj klucza (w takim rankingu) wynikajc z jego aktualnej oceny (na przykad pobra dane uczestnika znajdujcego
si na trzecim miejscu lub ocen uczestnika z drugiego miejsca).
W tej recepturze osigamy takie moliwoci dziki klasie wywiedzionej z klasy dict i uzupenionej o odpowiednie funkcje, ktrych brakuje w klasie dict (metody rating, getValueByRating, getKeyByRating). Znacznie waniejsze s jednak subtelne modyfikacje wprowadzane
do metody keys i do innych podobnych metod, dziki ktrym metody te zwracaj listy lub
iteratory o wymaganym porzdku (na przykad kolejno rosncych ocen, a jeeli dwa elementy maj identyczne oceny, to kolejno okrelana jest przez bezporednie porwnanie dwch
kluczy). Wikszo najwaniejszych informacji o klasie zapisanych zostao w jej dokumentacji
umieszczonej w kodzie. Jest to niezwykle istotne, poniewa zapisanie dokumentacji klasy
i przykadw jej uycia wewntrz jej kodu pozwala nam wykorzysta modu doctest ze
standardowej biblioteki Pythona w celu wprowadzenia funkcji testw moduowych i jednoczesnego zapewnienia poprawnoci podanych przykadw.
Najbardziej interesujcym aspektem podanej implementacji jest to, e bardzo zmniejszono w niej
ilo powtarzalnego i nudnego, a co za tym idzie podatnego na bdy kodu, bez jednoczesnego
znaczcego zredukowania wydajnoci. Klasa Ratings dziedziczy wielobazowo po klasach dict
i DictMixin, przy czym ta druga umieszczana jest jako pierwsza na licie klas bazowych, przez
co wszystkie niepokryte jawnie metody klasy Ratings pochodz wanie z klasy DictMixin,
o ile ona je udostpnia.
Klasa DictMixin przygotowana przez Raymonda Hettingera zostaa pierwotnie opublikowana
jako receptura w sieciowej wersji ksiki Python Receptury, a pniej staa si czci biblioteki standardowej Pythona 2.3. Klasa DictMixin udostpnia wszystkie metody sownikowe
z wyjtkiem metod __init__, copy i czterech metod podstawowych: __getitem__, __setitem__,
__delitem__ i keys. W czasie tworzenia klasy sownikowej, ktra ma udostpnia wszystkie
metody w peni funkcjonalnego sownika, powinnimy przygotowa klas wywiedzion z klasy DictMixin i doda do niej przynajmniej wymienione wczeniej metody podstawowe (zalenie od semantyki klasy na przykad jeeli klasa ma mie niezmienne egzemplarze, to nie
ma potrzeby udostpniania metody modyfikujcych __setitem__ i __delitem__). Mona te
zaimplementowa inne metody, pokrywajc implementacje udostpniane przez klas DictMixin, poprawiajc w ten sposb ich wydajno. Architektur klasy DictMixin mona uzna
za doskonay przykad klasycznego wzorca projektowego Szablonu Metody (ang. Template
Method) zastosowanego pasywnie w bardzo przydatnym wariancie.
W klasie prezentowanej w tej recepturze po drugiej klasie bazowej (czyli po wbudowanym
typie dict) dziedziczymy metod __getitem__, a wszystkie inne metody delegujemy jawnie
do klasy dict (te, ktre mona wydelegowa). Samodzielnie musimy zapisa podstawowe metody modyfikujce (__setitem__ i __delitem__), poniewa oprcz wydelegowania ich do
bazowej klasy dict musimy dodatkowo zaktualizowa w nich pomocnicz struktur self.

256

Rozdzia 5. Szukanie i sortowanie

_rating list par (ocena, klucz) utrzymywan w stanie posortowania za pomoc moduu
bisect z biblioteki standardowej. Metod keys implementujemy samodzielnie (a skoro ju
o tym wspominamy: implementujemy te metody __iter__ i iterkeys, poniewa najprostszym sposobem na implementowanie metody keys jest wykorzystanie metody __iter__), tak
aby wykorzysta w niej struktur self._rating i zwraca klucze sownika w potrzebnej nam

kolejnoci. W kocu, oprcz trzech standardowych metod obsugi ocen, dodajemy te oczywiste implementacje metod __init__ i copy.
Wynik okazuje si by ciekawym przykadem kodu o dobrze wywaonej spjnoci i czytelnoci, w ktrym bardzo szeroko wykorzystywane s funkcje udostpniane przez standardow
bibliotek Pythona. Jeeli z tego moduu skorzystamy w naszych aplikacjach, to dokadniejsze
badania mog wykaza, e niektre z metod prezentowanej klasy nie maj zadowalajcej wydajnoci. Wynika to z faktu, e natura klasy DictMixin wymusza stosowanie w niej bardzo
oglnych implementacji. W takiej sytuacji naley bezwzgldnie uzupeni klas wasnymi
implementacjami tych wszystkich metod, ktre s wymagane do osignicia lepszej wydajnoci. Na przykad, jeeli nasza aplikacja czsto w ptlach przeglda wyniki wywoania
r.iteritems(), gdzie r jest egzemplarzem klasy Ratings, to nieco lepsz wydajno osigniemy, dodajc do ciaa klasy bezporedni implementacj tej metody:
def iteritems(self):
for v, k in self._rating:
yield k, v

Zobacz rwnie
Podrczniki Library Reference i Python in a Nutshell w czciach opisujcych klas DictMixin
z moduu UserDict oraz modu bisect.

5.15. Sortowanie nazwisk i rozdzielanie ich


za pomoc inicjaw
Pomysodawcy: Brett Cannon, Amos Newcombe

Problem
Chcemy przygotowa spis osb, w ktrym poszczeglne osoby zapisane byyby w porzdku
alfabetycznym i pogrupowane wedug ich inicjaw utworzonych na podstawie nazwisk.

Rozwizanie
W Pythonie 2.4 nowa funkcja itertools.groupby bardzo uatwia realizacj tego zadania:
import itertools
def groupnames(name_iterable):
sorted_names = sorted(name_iterable, key=_sortkeyfunc)
name_dict = {}
for key, group in itertools.groupby(sorted_names, _groupkeyfunc):
name_dict[key] = tuple(group)
return name_dict
pieces_order = { 2: (-1, 0), 3: (-1, 0, 1) }
def _sortkeyfunc(name):

5.15. Sortowanie nazwisk i rozdzielanie ich za pomoc inicjaw

257

''' name jest cigiem znakw zawierajcym imi i nazwisko oraz opcjonalne
drugie imi lub inicja rozdzielane spacjami. Zwraca cig znakw w kolejnoci
nazwisko-imi-drugie_imi, ktra wymagana jest w zwizku z sortowaniem. '''
name_parts = name.split()
return ' '.join([name_parts[n] for n in pieces_order[len(name_parts)]])
def _groupkeyfunc(name):
''' zwraca klucz grupowania, na przykad inicja nazwiska '''
return name.split()[-1][0]

Analiza
W niniejszej recepturze parametr name_iterable musi by elementem iterowalnym, ktrego
elementy s cigami znakw zawierajcymi dane osb zapisane w formie: pierwsze_imi
drugie_imi nazwisko. W wyniku wywoania funkcji groupnames na rzecz takiego
elementu iterowalnego otrzymamy sownik, ktrego klucze s inicjaami nazwisk, a powizane z nimi wartoci s krotkami zawierajcymi wszystkie nazwiska, na podstawie ktrych
mona utworzy dany inicja.
Pomocnicza funkcja _sortkeyfunc dzieli dane osoby zapisane w ramach jednego cigu znakw zawierajcego albo imi nazwisko albo pierwsze_imi drugie_imi nazwisko i tworzy
na ich podstawie list, zawierajc najpierw nazwisko, a za nim imi, ewentualne drugie imi
i na kocu inicja. Nastpnie funkcja czy te dane w cig znakw i zwraca go funkcji wywoujcej. Zgodnie z problemem opisywanym w tej recepturze, wynikowy cig znakw jest kluczem,
z jakiego chcemy skorzysta w ramach sortowania danych. Wbudowana w Pythona 2.4 funkcja sorted przyjmuje omawian funkcje w swoim opcjonalnym parametrze key (wywouje j
na rzecz kadego elementu w celu uzyskania klucza sortowania).
Pomocnicza funkcja _groupkeyfunc pobiera dane osoby w takim samym formacie, z jakim
pracuje funkcja _sortkeyfunc, i zwraca inicja z nazwiska bdcy kluczem grupowania, tak
jak zostao to zapisane w opisie problemu.
W ramach rozwizywania tego problemu gwna funkcja z tej receptury groupnames wykorzystuje dwie opisane wczeniej funkcje pomocnicze, funkcj sorted z Pythona 2.4 oraz funkcj itertools.groupby. Z ich pomoc tworzy ona i zwraca opisywany wczeniej sownik.
Jeeli prezentowany kod ma by stosowany rwnie w Pythonie 2.3, to trzeba nieco przebudowa sam funkcj groupnames, przy czym obie funkcje pomocnicze mog pozosta bez zmian.
Ze wzgldu na to, e w bibliotece standardowej Pythona 2.3 nie ma funkcji groupby, wygodnej
jest w nim najpierw wykona grupowanie elementw i dopiero potem posortowa dane w ramach poszczeglnych grup.
def groupnames(name_iterable):
name_dict = {}
for name in name_iterable:
key = _groupkeyfunc(name)
name_dict.setdefault(key, []).append(name)
for k, v in name_dict.iteritems():
aux = [(_sortkeyfunc(name), name) for name in v]
aux.sort()
name_dict[k] = tuple([ n for __, n in aux ])
return name_dict

Zobacz rwnie
Receptur 19.21. Podrcznik Library Reference z Pythona 2.4 w czci opisujcej modu itertools.
258 |

Rozdzia 5. Szukanie i sortowanie

You might also like