Wydawnictwo Helion
ul. Koœciuszki 1c
44-100 Gliwice
tel. 032 230 98 63
e-mail: helion@helion.pl
Ruby. Receptury
Zbiór gotowych rozwi¹zañ dla programistów u¿ywaj¹cych jêzyka Ruby
• Jak przetwarzaæ pliki XML i HTML?
• Jak wykorzystywaæ œrodowisko Ruby on Rails?
• W jaki sposób ³¹czyæ Ruby z technologi¹ AJAX?
Korzystasz w pracy z jêzyka Ruby i zastanawiasz siê, czy niektóre zadania
programistyczne mo¿na wykonaæ szybciej? Chcesz poznaæ zasady programowania
obiektowego w Ruby? A mo¿e interesuje Ciê framework Ruby on Rails? Jêzyk Ruby
zdobywa coraz wiêksz¹ popularnoœæ, jest wykorzystywany do tworzenia aplikacji
sieciowych i sta³ siê podstaw¹ œrodowiska Ruby on Rails. Jednak nawet najlepszy
jêzyk programowania nie uwalnia programistów od ¿mudnego realizowania zadañ,
które nie maj¹ zbyt wiele wspólnego z tworzeniem aplikacji, czyli usuwania b³êdów,
implementowania typowych algorytmów, poszukiwania rozwi¹zañ mniej lub bardziej
typowych problemów i wielu innych.
Ksi¹¿ka „Ruby. Receptury” znacznie przyspieszy Twoj¹ pracê. Znajdziesz tu kilkaset
praktycznych rozwi¹zañ problemów wraz z przejrzystym komentarzem oraz tysi¹ce
wierszy proponowanego kodu, który bêdziesz móg³ wykorzystaæ w swoich projektach.
Przeczytasz o strukturach danych, algorytmach, przetwarzaniu plików XML i HTML,
tworzeniu interfejsów u¿ytkownika dla aplikacji i po³¹czeniach z bazami danych.
Nauczysz siê generowaæ i obrabiaæ pliki graficzne, korzystaæ z us³ug sieciowych,
wyszukiwaæ i usuwaæ b³êdy w aplikacjach, a tak¿e pisaæ skrypty niezwykle pomocne
w administrowaniu systemem operacyjnym Linux.
• Przetwarzanie danych tekstowych i liczbowych
• Operacje na tablicach
• Praca z systemem plików
• Programowanie obiektowe
• Przetwarzanie dokumentów XML i HTML oraz plików graficznych
• Generowanie plików PDF
• Po³¹czenie z bazami danych
• Korzystanie z poczty elektronicznej, protoko³u telnet i po³¹czeñ Torrent
• Projektowanie aplikacji internetowych za pomoc¹ Ruby on Rails
• Stosowanie us³ug sieciowych
• Optymalizacja aplikacji
• Tworzenie wersji dystrybucyjnych
• Automatyzacja zadañ z wykorzystaniem jêzyka Rake
• Budowanie interfejsów u¿ytkownika
Jeœli chcesz rozwi¹zaæ problem, skorzystaj z gotowej receptury — ko³o ju¿ wynaleziono
Autorzy: Lucas Carlson, Leonard Richardson
T³umaczenie: Andrzej Gra¿yñski, Rados³aw Meryk
ISBN: 83-246-0768-4
Tytu³ orygina³u:
Ruby Cookbook
Format: B5, stron: 888
5
Spis tre
ļci
Wprowadzenie .............................................................................................................. 17
1.
Ĥaħcuchy ........................................................................................................................29
1.1. Budowanie äaþcucha z czöĈci
32
1.2. Zastöpowanie zmiennych w tworzonym äaþcuchu
34
1.3. Zastöpowanie zmiennych w istniejñcym äaþcuchu
35
1.4. Odwracanie kolejnoĈci säów lub znaków w äaþcuchu
37
1.5. Reprezentowanie znaków niedrukowalnych
39
1.6. Konwersja miödzy znakami a kodami
41
1.7. Konwersja miödzy äaþcuchami a symbolami
42
1.8. Przetwarzanie kolejnych znaków äaþcucha
43
1.9. Przetwarzanie poszczególnych säów äaþcucha
45
1.10. Zmiana wielkoĈci liter w äaþcuchu
47
1.11. Zarzñdzanie biaäymi znakami
48
1.12. Czy moĔna potraktowaè dany obiekt jak äaþcuch?
49
1.13. Wyodröbnianie czöĈci äaþcucha
51
1.14. Obsäuga miödzynarodowego kodowania
52
1.15. Zawijanie wierszy tekstu
53
1.16. Generowanie nastöpnika äaþcucha
55
1.17. Dopasowywanie äaþcuchów za pomoc
ñ wyraĔeþ regularnych
58
1.18. Zastöpowanie wielu wzorców w pojedynczym przebiegu
60
1.19. Weryfikacja poprawnoĈci adresów e-mailowych
61
1.20. Klasyfikacja tekstu za pomocñ analizatora bayesowskiego
64
2. Liczby .............................................................................................................................67
2.1. Przeksztaäcanie äaþcucha w liczbö
68
2.2. Porównywanie liczb zmiennopozycyjnych
70
2.3. Reprezentowanie liczb z dowolnñ dokäadnoĈciñ
73
6
_
Spis tre
ļci
2.4. Reprezentowanie liczb wymiernych
76
2.5. Generowanie liczb pseudolosowych
77
2.6. Konwersje miödzy róĔnymi podstawami liczenia
79
2.7. Logarytmy
80
2.8. ćrednia, mediana i moda
83
2.9. Konwersja stopni na radiany i odwrotnie
85
2.10. MnoĔenie macierzy
87
2.11. Rozwiñzywanie ukäadu równaþ liniowych
91
2.12. Liczby zespolone
94
2.13. Symulowanie subklasingu klasy Fixnum
96
2.14. Arytmetyka liczb w zapisie rzymskim
100
2.15. Generowanie sekwencji liczb
105
2.16. Generowanie liczb pierwszych
107
2.17. Weryfikacja sumy kontrolnej w numerze karty kredytowej
111
3. Data i czas .....................................................................................................................113
3.1. Odczyt dzisiejszej daty
115
3.2. Dekodowanie daty, dokäadne i przybliĔone
119
3.3. Drukowanie dat
122
3.4. Iterowanie po datach
126
3.5. Arytmetyka dat
127
3.6. Obliczanie dystansu miödzy datami
129
3.7. Konwersja czasu miödzy strefami czasowymi
131
3.8. Czas letni
134
3.9. Konwersje miödzy obiektami Time i DateTime
135
3.10. Jaki to dzieþ tygodnia?
138
3.11. Obsäuga dat biznesowych
139
3.12. Periodyczne wykonywanie bloku kodu
140
3.13. Oczekiwanie przez zadany odcinek czasu
142
3.14. Przeterminowanie wykonania
145
4. Tablice .......................................................................................................................... 147
4.1. Iterowanie po elementach tablicy
149
4.2. Wymiana zawartoĈci bez uĔywania zmiennych pomocniczych
152
4.3. Eliminowanie zdublowanych wartoĈci
154
4.4. Odwracanie kolejnoĈci elementów w tablicy
155
4.5. Sortowanie tablicy
156
4.6. Sortowanie äaþcuchów bez rozróĔniania wielkoĈci liter
158
4.7. Zabezpieczanie tablic przed utratñ posortowania
159
Spis tre
ļci
_
7
4.8. Sumowanie elementów tablicy
164
4.9. Sortowanie elementów tablicy wedäug czöstoĈci wystöpowania
165
4.10. Tasowanie tablicy
167
4.11. Znajdowanie N najmniejszych elementów tablicy
168
4.12. Tworzenie hasza za pomocñ iteratora inject
170
4.13. Ekstrahowanie wybranych elementów z tablicy
172
4.14. Operacje teoriomnogoĈciowe na tablicach
175
4.15. Partycjonowanie i klasyfikacja elementów zbioru
177
5. Hasze ........................................................................................................................... 183
5.1. Wykorzystywanie symboli jako kluczy
186
5.2. WartoĈci domyĈlne w haszach
187
5.3. Dodawanie elementów do hasza
189
5.4. Usuwanie elementów z hasza
191
5.5. Tablice i inne modyfikowalne obiekty w roli kluczy
193
5.6. Kojarzenie wielu wartoĈci z tym samym kluczem
195
5.7. Iterowanie po zawartoĈci hasza
196
5.8. Iterowanie po elementach hasza w kolejnoĈci ich wstawiania
200
5.9. Drukowanie hasza
201
5.10. Inwersja elementów hasza
203
5.11. Losowy wybór z listy zdarzeþ o róĔnych prawdopodobieþstwach
204
5.12. Tworzenie histogramu
207
5.13. Odwzorowanie zawartoĈci dwóch haszów
209
5.14. Ekstrakcja fragmentów zawartoĈci haszów
210
5.15. Przeszukiwanie hasza przy uĔyciu wyraĔeþ regularnych
211
6. Pliki i katalogi .............................................................................................................. 213
6.1. Czy taki plik istnieje?
216
6.2. Sprawdzanie uprawnieþ dostöpu do plików
218
6.3. Zmiana uprawnieþ dostöpu do plików
220
6.4. Sprawdzanie, kiedy plik byä ostatnio uĔywany
223
6.5. Przetwarzanie zawartoĈci katalogu
224
6.6. Odczytywanie zawartoĈci pliku
227
6.7. Zapis do pliku
230
6.8. Zapis do pliku tymczasowego
232
6.9. Losowy wybór wiersza z pliku
233
6.10. Porównywanie dwóch plików
234
6.11. Swobodne nawigowanie po „jednokrotnie odczytywalnych”
strumieniach wejĈciowych
238
8
_
Spis tre
ļci
6.12. Wödrówka po drzewie katalogów
240
6.13. Szeregowanie dostöpu do pliku
242
6.14. Tworzenie wersjonowanych kopii pliku
245
6.15. ãaþcuchy udajñce pliki
248
6.16. Przekierowywanie standardowego wejĈcia i standardowego wyjĈcia
250
6.17. Przetwarzanie plików binarnych
252
6.18. Usuwanie pliku
255
6.19. Obcinanie pliku
257
6.20. Znajdowanie plików o okreĈlonej wäasnoĈci
258
6.21. Odczytywanie i zmiana bieĔñcego katalogu roboczego
260
7. Bloki kodowe i iteracje ...............................................................................................263
7.1. Tworzenie i wywoäywanie bloku kodowego
265
7.2. Tworzenie metod wykorzystujñcych bloki kodowe
267
7.3. Przypisywanie bloku kodowego do zmiennej
269
7.4. Bloki kodowe jako domkniöcia: odwoäania do zmiennych zewnötrznych
w treĈci bloku kodowego
272
7.5. Definiowanie iteratora dla struktury danych
273
7.6. Zmiana sposobu iterowania po strukturze danych
276
7.7. Nietypowe metody klasyfikujñce i kolekcjonujñce
278
7.8. Zatrzymywanie iteracji
279
7.9. Iterowanie równolegäe
281
7.10. Kod inicjujñcy i koþczñcy dla bloku kodowego
285
7.11. Tworzenie systemów luĒno powiñzanych przy uĔyciu odwoäaþ zwrotnych
287
8. Obiekty i klasy ............................................................................................................. 291
8.1. Zarzñdzanie danymi instancyjnymi
294
8.2. Zarzñdzanie danymi klasowymi
296
8.3. Weryfikacja funkcjonalnoĈci obiektu
299
8.4. Tworzenie klasy pochodnej
301
8.5. PrzeciñĔanie metod
303
8.6. Weryfikacja i modyfikowanie wartoĈci atrybutów
305
8.7. Definiowanie wirtualnych atrybutów
307
8.8. Delegowanie wywoäaþ metod do innego obiektu
308
8.9. Konwersja i koercja typów obiektów
311
8.10. Prezentowanie obiektu w postaci czytelnej dla czäowieka
315
8.11. Metody wywoäywane ze zmiennñ liczbñ argumentów
317
8.12. Symulowanie argumentów zawierajñcych säowa kluczowe
319
8.13. Wywoäywanie metod superklasy
321
Spis tre
ļci
_
9
8.14. Definiowanie metod abstrakcyjnych
323
8.15. ZamraĔanie obiektów w celu ich ochrony przed modyfikacjñ
325
8.16. Tworzenie kopii obiektu
327
8.17. Deklarowanie staäych
330
8.18. Implementowanie metod klasowych i metod-singletonów
332
8.19. Kontrolowanie dostöpu — metody prywatne, publiczne i chronione
334
9. Modu
ĥy i przestrzenie nazw .......................................................................................339
9.1. Symulowanie wielokrotnego dziedziczenia za pomocñ moduäów-domieszek
339
9.2. Rozszerzanie wybranych obiektów za pomocñ moduäów
343
9.3. Rozszerzanie repertuaru metod klasowych za pomocñ moduäów
345
9.4. Moduä Enumerable — zaimplementuj jednñ metodö, dostaniesz 22 za darmo
346
9.5. Unikanie kolizji nazw dziöki ich kwalifikowaniu
348
9.6. Automatyczne äadowanie bibliotek na Ĕñdanie
350
9.7. Importowanie przestrzeni nazw
352
9.8. Inicjowanie zmiennych instancyjnych doäñczanego moduäu
353
9.9. Automatyczne inicjowanie moduäów-domieszek
354
10. Odzwierciedlenia i metaprogramowanie .................................................................357
10.1. Identyfikacja klasy obiektu i jej superklasy
358
10.2. Zestaw metod obiektu
359
10.3. Lista metod unikalnych dla obiektu
363
10.4. Uzyskiwanie referencji do metody
364
10.5. Poprawianie bäödów w „obcych” klasach
366
10.6. ćledzenie zmian dokonywanych w danej klasie
368
10.7. Weryfikacja atrybutów obiektu
370
10.8. Reagowanie na wywoäania niezdefiniowanych metod
372
10.9. Automatyczne inicjowanie zmiennych instancyjnych
375
10.10. Oszczödne kodowanie dziöki metaprogramowaniu
377
10.11. Metaprogramowanie z uĔyciem ewaluacji äaþcuchów
380
10.12. Ewaluacja kodu we wczeĈniejszym kontekĈcie
382
10.13. Anulowanie definicji metody
383
10.14. Aliasowanie metod
386
10.15. Programowanie zorientowane aspektowo
389
10.16. Wywoäania kontraktowane
391
11. XML i HTML .................................................................................................................395
11.1. Sprawdzanie poprawnoĈci dokumentu XML
396
11.2. Ekstrakcja informacji z drzewa dokumentu
398
10
_
Spis tre
ļci
11.3. Ekstrakcja informacji w trakcie analizy dokumentu XML
400
11.4. Nawigowanie po dokumencie za pomocñ XPath
401
11.5. Parsowanie bäödnych dokumentów
404
11.6. Konwertowanie dokumentu XML na hasz
406
11.7. Walidacja dokumentu XML
409
11.8. Zastöpowanie encji XML
411
11.9. Tworzenie i modyfikowanie dokumentów XML
414
11.10. Kompresowanie biaäych znaków w dokumencie XML
417
11.11. Autodetekcja standardu kodowania znaków w dokumencie
418
11.12. Konwersja dokumentu miödzy róĔnymi standardami kodowania
419
11.13. Ekstrakcja wszystkich adresów URL z dokumentu HTML
420
11.14. Transformacja tekstu otwartego na format HTML
423
11.15. Konwertowanie Ĉciñgniötego z internetu dokumentu HTML na tekst
425
11.16. Prosty czytnik kanaäów
428
12. Formaty plików graficznych i innych ........................................................................ 433
12.1. Tworzenie miniaturek
433
12.2. Dodawanie tekstu do grafiki
436
12.3. Konwersja formatów plików graficznych
439
12.4. Tworzenie wykresów
441
12.5. Wprowadzanie graficznego kontekstu za pomocñ wykresów typu Sparkline
444
12.6. Silne algorytmy szyfrowania danych
447
12.7. Przetwarzanie danych rozdzielonych przecinkami
449
12.8. Przetwarzanie plików tekstowych nie w peäni zgodnych z formatem CSV
451
12.9. Generowanie i przetwarzanie arkuszy Excela
453
12.10. Kompresowanie i archiwizowanie plików za pomocñ narzödzi Gzip i Tar
455
12.11. Czytanie i zapisywanie plików ZIP
458
12.12. Czytanie i zapisywanie plików konfiguracyjnych
460
12.13. Generowanie plików PDF
461
12.14. Reprezentowanie danych za pomocñ plików muzycznych MIDI
465
13. Bazy danych i trwa
ĥoļë obiektów ............................................................................. 469
13.1. Serializacja danych za pomocñ biblioteki YAML
472
13.2. Serializacja danych z wykorzystaniem moduäu Marshal
475
13.3. Utrwalanie obiektów z wykorzystaniem biblioteki Madeleine
476
13.4. Indeksowanie niestrukturalnego tekstu z wykorzystaniem
biblioteki SimpleSearch
479
13.5. Indeksowanie tekstu o okreĈlonej strukturze z wykorzystaniem
biblioteki Ferret
481
Spis tre
ļci
_
11
13.6. Wykorzystywanie baz danych Berkeley DB
484
13.7. Zarzñdzanie bazñ danych MySQL w systemie Unix
486
13.8. Zliczanie wierszy zwracanych przez zapytanie
487
13.9. BezpoĈrednia komunikacja z bazñ danych MySQL
489
13.10. BezpoĈrednia komunikacja z bazñ danych PostgreSQL
491
13.11. Mapowanie obiektowo-relacyjne z wykorzystaniem
biblioteki ActiveRecord
493
13.12. Mapowanie obiektowo-relacyjne z wykorzystaniem
biblioteki Og
497
13.13. Programowe tworzenie zapytaþ
501
13.14. Sprawdzanie poprawnoĈci danych z wykorzystaniem
biblioteki ActiveRecord
504
13.15. Zapobieganie atakom typu SQL Injection
507
13.16. Obsäuga transakcji z wykorzystaniem biblioteki ActiveRecord
510
13.17. Definiowanie haków dotyczñcych zdarzeþ zwiñzanych z tabelami
511
13.18. Oznaczanie tabel bazy danych z wykorzystaniem moduäów-domieszek
514
14. Us
ĥugi internetowe ..................................................................................................... 519
14.1. Pobieranie zawartoĈci strony WWW
520
14.2. Obsäuga Ĕñdaþ HTTPS
522
14.3. Dostosowywanie nagäówków Ĕñdaþ HTTP
524
14.4. Wykonywanie zapytaþ DNS
526
14.5. Wysyäanie poczty elektronicznej
528
14.6. Czytanie poczty z serwera IMAP
531
14.7. Czytanie poczty z wykorzystaniem protokoäu POP3
535
14.8. Implementacja klienta FTP
538
14.9. Implementacja klienta telnet
540
14.10. Implementacja klienta SSH
543
14.11. Kopiowanie plików do innego komputera
546
14.12. Implementacja klienta BitTorrent
547
14.13. Wysyäanie sygnaäu ping do zdalnego komputera
549
14.14. Implementacja wäasnego serwera internetowego
550
14.15. Przetwarzanie adresów URL
552
14.16. Pisanie skryptów CGI
555
14.17. Ustawianie plików cookie i innych nagäówków odpowiedzi HTTP
557
14.18. Obsäuga przesyäania plików na serwer z wykorzystaniem CGI
559
14.19. Uruchamianie serwletów WEBrick
562
14.20. Wäasny klient HTTP
567
12
_
Spis tre
ļci
15. Projektowanie aplikacji internetowych: Ruby on Rails ............................................ 571
15.1. Prosta aplikacja Rails wyĈwietlajñca informacje o systemie
573
15.2. Przekazywanie danych ze sterownika do widoku
576
15.3. Tworzenie ukäadu nagäówka i stopki
578
15.4. Przekierowania do innych lokalizacji
581
15.5. WyĈwietlanie szablonów za pomocñ metody render
582
15.6. Integracja baz danych z aplikacjami Rails
585
15.7. Reguäy pluralizacji
588
15.8. Tworzenie systemu logowania
590
15.9. Zapisywanie haseä uĔytkowników w bazie danych w postaci skrótów
594
15.10. Unieszkodliwianie kodu HTML i JavaScript przed wyĈwietlaniem
595
15.11. Ustawianie i odczytywanie informacji o sesji
596
15.12. Ustawianie i odczytywanie plików cookie
599
15.13. Wyodröbnianie kodu do moduäów pomocniczych
601
15.14. Rozdzielenie widoku na kilka czöĈci
602
15.15. Dodawanie efektów DHTML z wykorzystaniem
biblioteki script.aculo.us
605
15.16. Generowanie formularzy do modyfikowania obiektów modelu
607
15.17. Tworzenie formularzy Ajax
611
15.18. Udostöpnianie usäug sieciowych w witrynie WWW
614
15.19. Przesyäanie wiadomoĈci pocztowych za pomocñ aplikacji Rails
616
15.20. Automatyczne wysyäanie komunikatów o bäödach pocztñ elektronicznñ
618
15.21. Tworzenie dokumentacji witryny WWW
620
15.22. Testy moduäowe witryny WWW
621
15.23. Wykorzystywanie puäapek w aplikacjach internetowych
624
16. Us
ĥugi sieciowe i programowanie rozproszone .......................................................627
16.1. Wyszukiwanie ksiñĔek w serwisie Amazon
628
16.2. Wyszukiwanie zdjöè w serwisie Flickr
631
16.3. Jak napisaè klienta XML-RPC?
634
16.4. Jak napisaè klienta SOAP?
636
16.5. Jak napisaè serwer SOAP?
637
16.6. Wyszukiwanie w internecie z wykorzystaniem usäugi sieciowej
serwisu Google
638
16.7. Wykorzystanie pliku WSDL w celu uäatwienia wywoäaþ SOAP
640
16.8. PäatnoĈci kartami kredytowymi
642
16.9. Odczytywanie kosztów przesyäki w serwisie UPS lub FedEx
644
16.10. Wspóädzielenie haszów przez dowolnñ liczbö komputerów
645
16.11. Implementacja rozproszonej kolejki
649
Spis tre
ļci
_
13
16.12. Tworzenie wspóädzielonej „tablicy ogäoszeþ”
650
16.13. Zabezpieczanie usäug DRb za pomocñ list kontroli dostöpu
653
16.14. Automatyczne wykrywanie usäug DRb z wykorzystaniem
biblioteki Rinda
654
16.15. Wykorzystanie obiektów poĈredniczñcych
656
16.16. Zapisywanie danych w rozproszonej pamiöci RAM z wykorzystaniem
systemu MemCached
659
16.17. Buforowanie kosztownych obliczeniowo wyników za pomocñ
systemu MemCached
661
16.18. Zdalnie sterowana „szafa grajñca”
664
17. Testowanie, debugowanie, optymalizacja i tworzenie dokumentacji ...................669
17.1. Uruchamianie kodu wyäñcznie w trybie debugowania
670
17.2. Generowanie wyjñtków
672
17.3. Obsäuga wyjñtków
673
17.4. Ponawianie próby wykonania kodu po wystñpieniu wyjñtku
676
17.5. Mechanizmy rejestrowania zdarzeþ w aplikacji
677
17.6. Tworzenie i interpretowanie stosu wywoäaþ
679
17.7. Jak pisaè testy moduäowe?
681
17.8. Uruchamianie testów moduäowych
684
17.9. Testowanie kodu korzystajñcego z zewnötrznych zasobów
686
17.10. Wykorzystanie puäapek do kontroli i modyfikacji stanu aplikacji
690
17.11. Tworzenie dokumentacji aplikacji
692
17.12. Profilowanie aplikacji
696
17.13. Pomiar wydajnoĈci alternatywnych rozwiñzaþ
699
17.14. Wykorzystywanie wielu narzödzi analitycznych jednoczeĈnie
701
17.15. Co wywoäuje tö metodö? Graficzny analizator wywoäaþ
702
18. Tworzenie pakietów oprogramowania i ich dystrybucja ........................................705
18.1. Wyszukiwanie bibliotek poprzez kierowanie zapytaþ
do repozytoriów gemów
706
18.2. Instalacja i korzystanie z gemów
709
18.3. Wymaganie okreĈlonej wersji gemu
711
18.4. Odinstalowywanie gemów
714
18.5. Czytanie dokumentacji zainstalowanych gemów
715
18.6. Tworzenie pakietów kodu w formacie gemów
717
18.7. Dystrybucja gemów
719
18.8. Instalacja i tworzenie samodzielnych pakietów z wykorzystaniem
skryptu setup.rb
722
14
_
Spis tre
ļci
19. Automatyzacja zada
ħ z wykorzystaniem jýzyka Rake ............................................725
19.1. Automatyczne uruchamianie testów moduäowych
727
19.2. Automatyczne generowanie dokumentacji
729
19.3. Porzñdkowanie wygenerowanych plików
731
19.4. Automatyczne tworzenie gemów
733
19.5. Pobieranie informacji statystycznych dotyczñcych kodu
734
19.6. Publikowanie dokumentacji
737
19.7. Równolegäe uruchamianie wielu zadaþ
738
19.8. Uniwersalny plik Rakefile
740
20. Wielozadaniowo
ļë i wielowétkowoļë ......................................................................747
20.1. Uruchamianie procesu-demona w systemie Unix
748
20.2. Tworzenie usäug systemu Windows
751
20.3. Wykonywanie dwóch operacji jednoczeĈnie z wykorzystaniem wñtków
754
20.4. Synchronizacja dostöpu do obiektu
756
20.5. Niszczenie wñtków
758
20.6. Równolegäe uruchamianie bloku kodu dla wielu obiektów
760
20.7. Ograniczanie liczby wñtków z wykorzystaniem ich puli
763
20.8. Sterowanie zewnötrznym procesem za pomocñ metody popen
766
20.9. Przechwytywanie strumienia wyjĈciowego i informacji o bäödach
z polecenia powäoki w systemie Unix
767
20.10. Zarzñdzanie procesami w innym komputerze
768
20.11. Unikanie zakleszczeþ
770
21. Interfejs u
żytkownika .................................................................................................773
21.1. Pobieranie danych wejĈciowych wiersz po wierszu
774
21.2. Pobieranie danych wejĈciowych znak po znaku
776
21.3. Przetwarzanie argumentów wiersza polecenia
778
21.4. Sprawdzenie, czy program dziaäa w trybie interaktywnym
781
21.5. Konfiguracja i porzñdkowanie po programie wykorzystujñcym
bibliotekö Curses
782
21.6. Czyszczenie ekranu
784
21.7. OkreĈlenie rozmiaru terminala
785
21.8. Zmiana koloru tekstu
787
21.9. Odczytywanie haseä
790
21.10. Edycja danych wejĈciowych z wykorzystaniem biblioteki Readline
791
21.11. Sterowanie migotaniem diod na klawiaturze
792
21.12. Tworzenie aplikacji GUI z wykorzystaniem biblioteki Tk
795
21.13. Tworzenie aplikacji GUI z wykorzystaniem biblioteki wxRuby
798
Spis tre
ļci
_
15
21.14. Tworzenie aplikacji GUI z wykorzystaniem biblioteki Ruby/GTK
802
21.15. Tworzenie aplikacji Mac OS X z wykorzystaniem biblioteki RubyCocoa
805
21.16. Wykorzystanie AppleScript do pobierania danych wejĈciowych
od uĔytkownika
812
22. Rozszerzenia j
ýzyka Ruby z wykorzystaniem innych jýzyków ...............................815
22.1. Pisanie rozszerzeþ w jözyku C dla jözyka Ruby
816
22.2. Korzystanie z bibliotek jözyka C z poziomu kodu Ruby
819
22.3. Wywoäywanie bibliotek jözyka C za pomocñ narzödzia SWIG
822
22.4. Kod w jözyku C wstawiany w kodzie Ruby
825
22.5. Korzystanie z bibliotek Javy za poĈrednictwem interpretera JRuby
827
23. Administrowanie systemem ...................................................................................... 831
23.1. Pisanie skryptów zarzñdzajñcych zewnötrznymi programami
832
23.2. Zarzñdzanie usäugami systemu Windows
833
23.3. Uruchamianie kodu w imieniu innego uĔytkownika
835
23.4. Okresowe uruchamianie zadaþ bez uĔywania mechanizmu cron lub at
836
23.5. Usuwanie plików, których nazwy speäniajñ kryteria okreĈlone
przez wyraĔenie regularne
838
23.6. Zmiana nazw grupy plików
840
23.7. Wyszukiwanie plików zdublowanych
842
23.8. Automatyczne wykonywanie kopii zapasowych
845
23.9. Ujednolicanie wäasnoĈci i uprawnieþ w katalogach uĔytkowników
846
23.10. Niszczenie wszystkich procesów wybranego uĔytkownika
849
Skorowidz ................................................................................................................... 853
29
ROZDZIA
Ĥ 1.
Ĥaħcuchy
Ruby jest jözykiem przyjaznym programiĈcie. Przed programistami hoädujñcymi filozofii pro-
gramowania zorientowanego obiektowo odkryje on drugñ jego naturö; programiĈci stroniñcy
od obiektów nie powinni mieè natomiast wiökszych trudnoĈci, bowiem — w odróĔnieniu od
wielu innych jözyków — w jözyku Ruby stosuje siö zwiözäe i konsekwentne nazewnictwo me-
tod, które generalnie zachowujñ siö tak, jak (intuicyjnie) moĔna by tego oczekiwaè.
ãaþcuchy znakomicie nadajñ siö na obszar „pierwszego kontaktu” z jözykiem Ruby: sñ uĔy-
teczne, äatwo siö je tworzy i wykorzystuje, wystöpujñ w wiökszoĈci jözyków, a wiöc säuĔyè
mogñ zarówno jako materiaä porównawczy, jak i okazja do przedstawienia koncepcyjnych
nowoĈci jözyka Ruby w rodzaju duck typing (receptura 1.12), otwartych klas (receptura 1.10),
symboli (receptura 1.7), a nawet gemów (receptura 1.20).
Omawiane koncepcje ilustrujemy konsekwentnie interaktywnymi sesjami jözyka Ruby. W
Ĉro-
dowisku Uniksa i Mac OS X säuĔy do tego program
irb
, uruchamiany z wiersza poleceþ. UĔyt-
kownicy Windows mogñ takĔe wykorzystywaè w tym celu program
fxri
, dostöpny za pomo-
cñ menu Start (po zainstalowaniu Ĉrodowiska Ruby za pomocñ pakietu „one-click installer”,
który pobraè moĔna spod adresu http:/rubyforge.org/projects/rubyinstaller). Program
irb
jest rów-
nieĔ dostöpny w Windows. Wspomniane programy tworzñ po uruchomieniu interaktywnñ po-
wäokö jözyka Ruby, pod kontrolñ której wykonywaè moĔna fragmenty przykäadowego kodu.
ãaþcuchy jözyka Ruby podobne sñ do äaþcuchów w innych „dynamicznych” jözykach — Perlu,
Pythonie czy PHP. Nie róĔniñ siö zbytnio od äaþcuchów znanych z jözyków C i Java. Sñ dyna-
miczne, elastyczne i modyfikowalne.
Rozpocznijmy wiöc naszñ sesjö, wpisujñc do wiersza poleceþ powäoki nastöpujñcy tekst:
string = "To jest napis"
Spowoduje to wyĈwietlenie rezultatu wykonania polecenia:
=> "To jest napis"
Polecenie to powoduje utworzenie äaþcucha
"To jest napis"
i przypisanie go zmiennej o na-
zwie
string
. ãaþcuch ten staje siö wiöc wartoĈciñ zmiennej i jednoczeĈnie wartoĈciñ caäego wy-
raĔenia, co uwidocznione zostaje w postaci wyniku wypisywanego (po strzaäce
=>
) w ramach
interaktywnej sesji. W treĈci ksiñĔki zapisywaè bödziemy ten rodzaj interakcji w postaci
string "To jest napis" => "To jest napis"
W jözyku Ruby wszystko, co moĔna przypisaè zmiennej, jest obiektem. W powyĔszym przy-
käadzie zmienna
string
wskazuje na obiekt klasy
String
. Klasa ta definiuje ponad sto metod
30
_
Rozdzia
ĥ 1. Ĥaħcuchy
— nazwanych fragmentów kodu säuĔñcych do wykonywania rozmaitych operacji na äaþcu-
chach. Wiele z tych metod wykorzystywaè bödziemy w naszej ksiñĔce, takĔe w niniejszym
rozdziale. Jedna z tych metod —
String#length
— zwraca rozmiar äaþcucha, czyli liczbö skäa-
dajñcych siö na niego bajtów:
string.length => 13
W wielu jözykach programowania wymagana (lub dopuszczalna) jest para nawiasów po na-
zwie wywoäywanej metody:
string.length() => 13
W jözyku Ruby nawiasy te sñ niemal zawsze nieobowiñzkowe, szczególnie w sytuacji, gdy
do wywoäywanej metody nie sñ przekazywane Ĕadne parametry (jak w powyĔszym przykäa-
dzie). Gdy parametry takie sñ przekazywane, uĔycie nawiasów moĔe uczyniè caäñ konstruk-
cjö bardziej czytelnñ:
string.count 's' => 2 # s wyst
Ăpuje dwukrotnie
string.count('s') => 2
WartoĈè zwracana przez metodö sama z siebie jest obiektem. W przypadku metody
String#
length
, wywoäywanej w powyĔszym przykäadzie, obiekt ten jest liczbñ
20
, czyli egzempla-
rzem (instancjñ) klasy
Fixnum
. PoniewaĔ jest obiektem, moĔna na jego rzecz takĔe wywoäy-
waè metody:
string.length.next => 14
WeĒmy teraz pod uwagö bardziej ciekawy przypadek — äaþcuch zawierajñcy znaki spoza
kodu ASCII. PoniĔszy äaþcuch reprezentuje francuskie zdanie „il était une fois” zakodowane
wedäug UTF-8
1
:
french_string = "il \xc3\xa9tait une fois" # => "il \303\251tait une fois"
Wiele jözyków programowania, miödzy innymi Java, traktuje äaþcuchy jako ciñgi znaków.
W jözyku Ruby äaþcuch postrzegany jest jako ciñg bajtów. PoniewaĔ powyĔszy äaþcuch za-
wiera 14 liter i 3 spacje, moĔna by domniemywaè, Ĕe jego däugoĈè wynosi 17; poniewaĔ jedna
z liter jest znakiem dwubajtowym, äaþcuch skäada siö z 18, nie 17 bajtów:
french_string.length # => 18
Do róĔnorodnego kodowania znaków powrócimy w recepturach 1.14 i 11.12; specyfikñ äaþ-
cuchów zawierajñcych znaki wielobajtowe zajmiemy siö takĔe w recepturze 1.8.
Znaki specjalne (tak jak binarne dane w äaþcuchu
french_string
) mogñ byè reprezentowane
za pomocñ tzw. sekwencji unikowych (escaping). Ruby udostöpnia kilka rodzajów takich se-
kwencji w zaleĔnoĈci od tego, w jaki sposób tworzony jest dany äaþcuch. JeĔeli mianowicie
äaþcuch ujöty jest w cudzysäów (
" ... "
), moĔna w nim kodowaè zarówno znaki binarne (jak
w przykäadowym äaþcuchu francuskim), jak i znak nowego wiersza
\n
znany z wielu innych
jözyków:
puts "Ten
ĪaĬcuch\nzawiera znak nowego wiersza"
# Ten
ĪaĬcuch
# zawiera znak nowego wiersza
W äaþcuchu zamkniötym znakami apostrofu (
' ... '
) jedynym dopuszczalnym znakiem
specjalnym jest odwrotny ukoĈnik
\
(backslash), umoĔliwiajñcy reprezentowanie pojedyncze-
go znaku specjalnego; para
\\
reprezentuje pojedynczy backslash.
1
"\xc3\xa9"
jest zapisem w jözyku Ruby unikodowego znaku é w reprezentacji UTF-8.
1.1. Budowanie
ĥaħcucha z czýļci
_
31
puts 'Ten
ĪaĬcuch wbrew pozorom \nniezawiera znaku nowego wiersza'
# Ten
ĪaĬcuch wbrew pozorom \nniezawiera znaku nowego wiersza
puts 'To jest odwrotny uko
Łnik: \\'
# To jest odwrotny uko
Łnik: \
Do kwestii tej powrócimy w recepturze 1.5, a w recepturach 1.2 i 1.3 zajmiemy siö bardziej
spektakularnymi moĔliwoĈciami äaþcuchów ograniczonych apostrofami.
Oto inna uĔyteczna moĔliwoĈè inicjowania äaþcucha, nazywana w jözyku Ruby here documents:
long_string = <<EOF
To jest d
Īugi ĪaĬcuch
Sk
Īadajîcy siĂ z kilku akapitów
EOF
# => "To jest d
Īugi ĪaĬcuch\nSkĪadajîcy siĂ z kilku akapitów\n"
puts long_string
# To jest d
Īugi ĪaĬcuch
# Sk
Īadajîcy siĂ z kilku akapitów
Podobnie jak w przypadku wiökszoĈci wbudowanych klas jözyka Ruby, takĔe w przypadku
äaþcuchów moĔna kodowaè tö samñ funkcjonalnoĈè na wiele róĔnych sposobów („idiomów”)
i programista moĔe dokonaè wyboru tego, który odpowiada mu najbardziej. WeĒmy jako przy-
käad ekstrakcjö podäaþcucha z däugiego äaþcucha: programiĈci preferujñcy podejĈcie obiekto-
we zapewne uĔyliby do tego celu metody
String#slice
:
string # "To jest napis"
string.slice(3,4) # "jest"
ProgramiĈci wywodzñcy swe nawyki z jözyka C skäonni sñ jednak do traktowania äaþcuchów
jako tablic bajtów — im takĔe Ruby wychodzi naprzeciw, umoĔliwiajñc ekstrakcjö poszczegól-
nych bajtów äaþcucha:
string[3].chr + string[4].chr + string[5].chr + string[6].chr
# => "jest"
Podobnie proste zadanie majñ programiĈci przywykli do Pythona:
string[3, 4] # => "jest"
W przeciwieþstwie do wielu innych jözyków programowania, äaþcuchy Ruby sñ modyfiko-
walne (mutable) — moĔna je zmieniaè juĔ po zadeklarowaniu. Oto wynik dziaäania dwóch me-
tod:
String#upcase
i
String#upcase!
— zwróè uwagö na istotnñ róĔnicö miödzy nimi:
string.upcase # => "TO JEST NAPIS"
string # => "To jest napis"
string.upcase! # => "TO JEST NAPIS"
string # => "TO JEST NAPIS"
Przy okazji widoczna staje siö jedna z konwencji skäadniowych jözyka Ruby: metody „nie-
bezpieczne” — czyli gäównie te modyfikujñce obiekty „w miejscu” — opatrywane sñ nazwami
koþczñcymi siö wykrzyknikiem. Inna konwencja syntaktyczna zwiñzana jest z predykatami,
czyli metodami zwracajñcymi wartoĈè
true
albo
false
— ich nazwy koþczñ siö znakiem za-
pytania.
string.empty? # => false
string.include? "To" # => true
UĔycie znaków przestankowych charakterystycznych dla jözyka potocznego, w celu uczynie-
nia kodu bardziej czytelnym dla programisty, jest odzwierciedleniem filozofii twórcy jözyka
Ruby, Yukihiro „Matza” Matsumoto, zgodnie z którñ to filozofiñ Ruby powinien byè czytelny
32
_
Rozdzia
ĥ 1. Ĥaħcuchy
przede wszystkim dla ludzi, zaĈ moĔliwoĈè wykonywania zapisanych w nim programów przez
interpreter jest kwestiñ wtórnñ.
Interaktywne sesje jözyka Ruby sñ niezastñpionym narzödziem umoĔliwiajñcym poznawanie
metod jözyka i praktyczne eksperymentowanie z nimi. Ponownie zachöcamy do osobistego
sprawdzania prezentowanego kodu w ramach sesji programu
irb
lub
fxri
, a z biegiem cza-
su tworzenia i testowania takĔe wäasnych przykäadów.
Dodatkowe informacje na temat äaþcuchów Ruby moĔna uzyskaè z nastöpujñcych Ēródeä:
x
Informacjö o dowolnej wbudowanej metodzie jözyka moĔna otrzymaè wprost w oknie
programu
fxri
, wybierajñc odnoĈnñ pozycjö w lewym panelu. W programie
irb
moĔna
uĔyè w tym celu polecenia
ri
— na przykäad informacjö o metodzie
String#upcase!
uzy-
skamy za pomocñ polecenia
ri String#upcase!
x
why the lucky stiff napisaä wspaniaäe wprowadzenie do instalacji jözyka Ruby oraz wyko-
rzystywania poleceþ
ir
i
irb
. Jest ono dostöpne pod adresem http://poignantguide.net/ruby/
expansion-pak-1.html.
x
Filozofiö projektowñ jözyka Ruby przedstawia jego autor, Yukihiro „Matz” Matsumoto,
w wywiadzie dostöpnym pod adresem http://www.artima.com/intv/ruby.html.
1.1. Budowanie
ĥaħcucha z czýļci
Problem
Iterujñc po strukturze danych, naleĔy zbudowaè äaþcuch reprezentujñcy kolejne kroki tej
iteracji.
Rozwi
ézanie
Istniejñ dwa efektywne rozwiñzania tego problemu. W najprostszym przypadku rozpoczy-
namy od äaþcucha pustego, sukcesywnie doäñczajñc do niego podäaþcuchy za pomocñ ope-
ratora
<<
:
hash = { "key1" => "val1", "key2" => "val2" }
string = ""
hash.each { |k,v| string << "#{k} is #{v}\n" }
puts string
# key1 is val1
# key2 is val2
PoniĔsza odmiana tego prostego rozwiñzania jest nieco efektywniejsza, chociaĔ mniej czytelna:
string = ""
hash.each { |k,v| string << k << " is " << v << "\n" }
JeĈli wspomnianñ strukturñ danych jest tablica, bñdĒ teĔ struktura ta daje siö äatwo przetrans-
formowaè na tablicö, zwykle bardziej efektywne rozwiñzanie moĔna uzyskaè za pomocñ me-
tody
Array#join
:
puts hash.keys.join("\n") + "\n"
# key1
# key2
1.1. Budowanie
ĥaħcucha z czýļci
_
33
Dyskusja
W jözykach takich jak Pyton czy Java sukcesywne doäñczanie podäaþcuchów do pustego po-
czñtkowo äaþcucha jest rozwiñzaniem bardzo nieefektywnym. ãaþcuchy w tych jözykach sñ
niemodyfikowalne (immutable), a wiöc kaĔdorazowe doäñczenie podäaþcucha wiñĔe siö ze
stworzeniem nowego obiektu. Doäñczanie serii podäaþcuchów oznacza wiöc tworzenie duĔej
liczby obiektów poĈrednich, z których kaĔdy stanowi jedynie „pomost” do nastöpnego etapu.
W praktyce przekäada siö to na marnotrawstwo czasu i pamiöci.
W tych warunkach rozwiñzaniem najbardziej efektywnym byäoby zapisanie poszczególnych
podäaþcuchów w tablicy (lub innej modyfikowalnej strukturze) zdolnej do dynamicznego roz-
szerzania siö. Gdy wszystkie podäaþcuchy zostanñ juĔ zmagazynowane we wspomnianej ta-
blicy, moĔna poäñczyè je w pojedynczy äaþcuch za pomocñ operatora stanowiñcego odpowied-
nik metody
Array#join
jözyka Ruby. W jözyku Java zadanie to speänia klasa
StringBuffer
.
Unikamy w ten sposób tworzenia wspomnianych obiektów poĈrednich.
W jözyku Ruby sprawa ma siö zgoäa inaczej, bo äaþcuchy sñ tu modyfikowalne, podobnie jak
tablice. Mogñ wiöc byè rozszerzane w miarö potrzeby, bez zbytniego obciñĔania pamiöci lub
procesora. W najszybszym wariancie rozwiñzania moĔemy wiöc w ogóle zapomnieè o tabli-
cy poĈredniczñcej i umieszczaè poszczególne podäaþcuchy bezpoĈrednio wewnñtrz äaþcucha
docelowego. Niekiedy skorzystanie z
Array#join
okazuje siö szybsze, lecz zwykle niewiele
szybsze, ponadto konstrukcja oparta na
<<
jest generalnie äatwiejsza do zrozumienia.
W sytuacji, gdy efektywnoĈè jest czynnikiem krytycznym, nie naleĔy tworzyè nowych äaþcu-
chów, jeĔeli moĔliwe jest doäñczanie podäaþcuchów do äaþcucha istniejñcego. Konstrukcje
w rodzaju
str << 'a' + 'b'
czy
str << "#{var1} #{var2}"
powodujñ tworzenie nowych äaþcuchów, które natychmiast „podäñczane” sñ do wiökszego
äaþcucha — a tego wäaĈnie chcielibyĈmy unikaè. UmoĔliwia nam to konstrukcja
str << var1 << ' ' << var2
Z drugiej jednak strony, nie powinno siö modyfikowaè äaþcuchów nietworzonych przez sie-
bie i wzglödy bezpieczeþstwa przemawiajñ za tworzeniem nowego äaþcucha. Gdy definiujesz
metodö otrzymujñcñ äaþcuch jako parametr, metoda ta nie powinna modyfikowaè owego äaþ-
cucha przez doäñczanie podäaþcuchów na jego koþcu — chyba Ĕe wäaĈnie to jest celem meto-
dy (której nazwa powinna tym samym koþczyè siö wykrzyknikiem, dla zwrócenia szczegól-
nej uwagi osoby studiujñcej kod programu).
Przy okazji waĔna uwaga: dziaäanie metody
Array#join
nie jest dokäadnie równowaĔne do-
äñczaniu kolejnych podäaþcuchów do äaþcucha.
Array#join
akceptuje separator, który wsta-
wiany jest miödzy kaĔde dwa sñsiednie elementy tablicy; w przeciwieþstwie do sukcesywne-
go doäñczania podäaþcuchów, separator ten nie jest umieszczany po ostatnim elemencie. RóĔnicö
tö ilustruje poniĔszy przykäad:
data = ['1', '2', '3']
s = ''
data.each { |x| s << x << ' oraz '}
s # => "1 oraz 2 oraz 3 oraz "
data.join(' oraz ') # => "1 oraz 2 oraz 3"
34
_
Rozdzia
ĥ 1. Ĥaħcuchy
Aby zasymulowaè dziaäanie
Array#join
za pomocñ iteracji, moĔna wykorzystaè metodö
Enumerable#each_with_index
, opuszczajñc separator dla ostatniego indeksu. Da siö to jed-
nak zrobiè tylko wówczas, gdy liczba elementów objötych enumeracjñ znana jest a priori:
s = ""
data.each_with_index { |x, i| s << x; s << "|" if i < data.length-1 }
s # => "1|2|3"
1.2. Zast
ýpowanie zmiennych w tworzonym ĥaħcuchu
Problem
NaleĔy stworzyè äaþcuch zawierajñcy reprezentacjö zmiennej lub wyraĔenia jözyka Ruby.
Rozwi
ézanie
NaleĔy wewnñtrz äaþcucha zamknñè zmiennñ lub wyraĔenie w nawiasy klamrowe i poprze-
dziè tö konstrukcjö znakiem
#
(hash).
liczba = 5
"Liczba jest równa #{liczba}." # => "Liczba jest równa 5."
"Liczba jest równa #{5}." # => "Liczba jest równa 5."
"Liczba nast
Ăpna po #{liczba} równa jest #{liczba.next}."
# => "Liczba nast
Ăpna po 5 równa jest 6."
"Liczba poprzedzaj
îca #{liczba} równa jest #{liczba-1}."
# => "Liczba poprzedzaj
îca 5 równa jest 4."
"To jest ##{number}!" # => "To jest #5!"
Dyskusja
ãaþcuch ujöty w cudzysäów (
" ... "
) jest przez interpreter skanowany pod kñtem obecnoĈci
specjalnych kodów substytucyjnych. Jednym z najbardziej elementarnych i najczöĈciej uĔywa-
nych kodów tego typu jest znak
\n
, zastöpowany znakiem nowego wiersza.
OczywiĈcie istniejñ bardziej skomplikowane kody substytucyjne. W szczególnoĈci dowolny
tekst zamkniöty w nawiasy klamrowe poprzedzone znakiem
#
(czyli konstrukcja
#{tekst}
)
interpretowany jest jako wyraĔenie jözyka Ruby, a caäa konstrukcja zastöpowana jest w äaþ-
cuchu wartoĈciñ tego wyraĔenia. JeĔeli wartoĈè ta nie jest äaþcuchem, Ruby dokonuje jej kon-
wersji na äaþcuch za pomocñ metody
to_s
. Proces ten nosi nazwö interpolacji.
Tak powstaäy äaþcuch staje siö nieodróĔnialny od äaþcucha, w którym interpolacji nie zasto-
sowano:
"#{liczba}" == '5' # => true
Za pomocñ interpolacji moĔna umieszczaè w äaþcuchu nawet spore porcje tekstu. Przypad-
kiem ekstremalnym jest definiowanie klasy wewnñtrz äaþcucha i wykorzystanie podäaþcucha
stanowiñcego wynik wykonania okreĈlonej metody tej klasy. Mimo ograniczonej raczej uĔy-
tecznoĈci tego mechanizmu, warto go zapamiötaè jako dowód wspaniaäych moĔliwoĈci jözy-
ka Ruby.
%{Tutaj jest #{class InstantClass
def bar
"pewien tekst"
end
end
1.3. Zast
ýpowanie zmiennych w istniejécym ĥaħcuchu
_
35
InstantClass.new.bar
}.}
# => "Tutaj jest pewien tekst."
Kod wykonywany w ramach interpolacji funkcjonuje dokäadnie tak samo, jak kaĔdy inny kod
Ruby w tej samej lokalizacji. Definiowana w powyĔszym przykäadzie klasa
InstantClass
nie
róĔni siö od innych klas i moĔe byè uĔywana takĔe na zewnñtrz äaþcucha.
Gdy w ramach interpolacji wywoäywana jest metoda powodujñca efekty uboczne, efekty te
widoczne sñ na zewnñtrz äaþcucha. W szczególnoĈci, jeĔeli efektem ubocznym jest nadanie war-
toĈci jakiejĈ zmiennej, zmienna ta zachowuje tö wartoĈè na zewnñtrz äaþcucha. Mimo iĔ nie po-
lecamy celowego polegania na tej wäasnoĈci, naleĔy koniecznie mieè ĈwiadomoĈè jej istnienia.
"Zmiennej x nadano warto
Łð #{x = 5; x += 1}." # => "Zmiennej x nadano wartoŁð 6."
x # => 6
JeĔeli chcielibyĈmy potraktowaè wystöpujñcñ w äaþcuchu sekwencjö
#{tekst}
w sposób lite-
ralny, nie jako polecenie interpolacji, wystarczy poprzedziè jñ znakiem odwrotnego ukoĈnika
(
\
) albo zamknñè äaþcuch znakami apostrofu zamiast cudzysäowu:
"\#{foo}" # => "\#{foo}"
'#{foo}' # => "\#{foo}"
Alternatywnym dla
%{}
kodem substytucyjnym jest konstrukcja here document. Pozwala ona
na zdefiniowanie wielowierszowego äaþcucha, którego ogranicznikiem jest wiersz o wyróĔ-
nionej postaci.
name = "Mr. Lorum"
email = <<END
Szanowny #{name},
Niestety, nie mo
šemy pozytywnie rozpatrzyð PaĬskiej reklamacji w zwiîzku
z oszacowaniem szkody, gdy
š jesteŁmy piekarniî, nie firmî ubezpieczeniowî.
Podpisano,
Bu
Īa, Rogal i Precel
Piekarze Jej Królewskiej Wysoko
Łci
END
Jözyk Ruby pozostawia programiĈcie duĔñ swobodö w zakresie wyboru postaci wiersza ogra-
niczajñcego:
<<koniec_wiersza
Pewien poeta z Afryki
Pisa
Ī dwuwierszowe limeryki
koniec_wiersza
# => "Pewien poeta z Afryki\nPisa
Ī dwuwierszowe limeryki\n"
Patrz tak
że
x
Za pomocñ techniki opisanej w recepturze 1.3 moĔna definiowaè äaþcuchy i obiekty sza-
blonowe, umoĔliwiajñce „odroczonñ” interpolacjö.
1.3. Zast
ýpowanie zmiennych w istniejécym ĥaħcuchu
Problem
NaleĔy utworzyè äaþcuch umoĔliwiajñcy interpolacjö wyraĔenia jözyka Ruby, jednakĔe bez
wykonywania tej interpolacji — ta wykonana zostanie póĒniej, prawdopodobnie wtedy, gdy
bödñ znane wartoĈci zastöpowanych wyraĔeþ.
36
_
Rozdzia
ĥ 1. Ĥaħcuchy
Rozwi
ézanie
Problem moĔna rozwiñzaè za pomocñ dwojakiego rodzaju Ĉrodków: äaþcuchów typu
printf
oraz szablonów ERB.
Ruby zapewnia wsparcie dla znanych z C i Pythona äaþcuchów formatujñcych typu
printf
.
Kody substytucyjne w ramach tych äaþcuchów majñ postaè dyrektyw rozpoczynajñcych siö
od znaku
%
(modulo):
template = 'Oceania zawsze by
Īa w stanie wojny z %s.'
template % 'Eurazj
î'
# => "Oceania zawsze by
Īa w stanie wojny z Eurazjî."
template % 'Antarktyd
î'
# => "Oceania zawsze by
Īa w stanie wojny z Antarktydî."
'Z dwoma miejscami dziesi
Ătnymi: %.2f' % Math::PI
# => " Z dwoma miejscami dziesi
Ătnymi: 3.14"
'Dope
Īnione zerami: %.5d' % Math::PI # => "DopeĪnione zerami: 00003"
Szablony ERB przypominajñ swñ postaciñ kod w jözyku JSP lub PHP. Zasadniczo szablon
ERB traktowany jest jako „normalny” äaþcuch, jednak pewne sekwencje sterujñce traktowane
sñ jako kod w jözyku Ruby lub aktualne wartoĈci wyraĔeþ:
template = ERB.new %q{Pyszne <%= food %>!}
food = "kie
Ībaski"
template.result(binding) # => "Pyszne kie
Ībaski!"
food = "mas
Īo orzechowe"
template.result(binding) # => "Pyszne mas
Īo orzechowe!"
Poza sesjñ
irb
moĔna pominñè wywoäania metody
Kernel#binding
:
puts template.result
# Pyszne mas
Īo orzechowe!
Szablony ERB wykorzystywane sñ wewnötrznie przez widoki Rails i äatwo moĔna rozpoznaè
je w plikach
.rhtml
.
Dyskusja
W szablonach ERB moĔna odwoäywaè siö do zmiennych (jak
food
w powyĔszym przykäa-
dzie), zanim zmienne te zostanñ zdefiniowane. Wskutek wywoäania metody
ERB#result
lub
ERB#run
szablon jest wartoĈciowany zgodnie z bieĔñcymi wartoĈciami tych zmiennych.
Podobnie jak kod w jözyku JSP i PHP, szablony ERB mogñ zawieraè pötle i rozgaäözienia wa-
runkowe. Oto przykäad rozbudowanego szablonu ERB:
template = %q{
<% if problems.empty? %>
Wygl
îda na to, še w kodzie nie ma bĪĂdów!
<% else %>
W kodzie kryj
î siĂ nastepujîce potencjalne problemy:
<% problems.each do |problem, line| %>
* <%= problem %> w wierszu <%= line %>
<% end %>
<% end %>}.gsub(/^\s+/, '')
template = ERB.new(template, nil, '<>')
problems = [["U
šyj is_a? zamiast duck typing", 23],
["eval() jest potencjalnie niebezpieczne", 44]]
template.run(binding)
1.4. Odwracanie kolejno
ļci sĥów lub znaków w ĥaħcuchu
_
37
# W kodzie kryj
î siĂ nastepujîce potencjalne problemy:
# * U
šyj is_a? zamiast duck typing w wierszu 23
# * eval() jest potencjalnie niebezpieczne w wierszu 44
problems = []
template.run(binding)
# Wygl
îda na to, še w kodzie nie ma bĪĂdów!
ERB jest wyrafinowanym mechanizmem, jednak ani szablony ERB, ani äaþcuchy typu
printf
nie przypominajñ w niczym prostych podstawieþ prezentowanych w recepturze 1.2. Podsta-
wienia te nie sñ aktywowane, jeĈli äaþcuch ujöty jest w apostrofy (
' ... '
) zamiast w cudzy-
säów (
" ... "
). MoĔna wykorzystaè ten fakt do zbudowania szablonu zawierajñcego meto-
dö
eval
:
class String
def substitute(binding=TOPLEVEL_BINDING)
eval(%{"#{self}"}, binding)
end
end
template = %q{Pyszne #{food}!} # => "Pyszne \#{food}!"
food = 'kie
Ībaski'
template.substitute(binding) # => "Pyszne kie
Ībaski!"
food = 'mas
Īo orzechowe'
template.substitute(binding) # => "Pyszne mas
Īo orzechowe!"
NaleĔy zachowaè szczególnñ ostroĔnoĈè, uĔywajñc metody
eval
, bowiem potencjalnie stwarza
ona moĔliwoĈè wykonania dowolnego kodu, z czego skwapliwie skorzystaè moĔe ewentual-
ny wäamywacz. Nie zdarzy siö to jednak w poniĔszym przykäadzie, jako Ĕe dowolna wartoĈè
zmiennej
food
wstawiona zostaje do äaþcucha jeszcze przed jego interpolacjñ:
food = '#{system("dir")}'
puts template.substitute(binding)
# Pyszne #{system("dir")}!
Patrz tak
że
x
PowyĔej prezentowaliĈmy proste przykäady szablonów ERB; przykäady bardziej skom-
plikowane znaleĒè moĔna w dokumentacji klas ERB pod adresem http://www.ruby-doc.org/
stdlib/libdoc/erb/rdoc/classes/ERB.html.
x
Receptura 1.2, „Zastöpowanie zmiennych w tworzonym äaþcuchu”.
x
Receptura 10.12, „Ewaluacja kodu we wczeĈniejszym kontekĈcie”, zawiera informacje na
temat obiektów
Binding
.
1.4. Odwracanie kolejno
ļci sĥów lub znaków
w
ĥaħcuchu
Problem
Znaki lub säowa wystöpujñ w äaþcuchu w niewäaĈciwej kolejnoĈci.
38
_
Rozdzia
ĥ 1. Ĥaħcuchy
Rozwi
ézanie
Do stworzenia nowego äaþcucha, zawierajñcego znaki äaþcucha oryginalnego w odwrotnej
kolejnoĈci, moĔna posäuĔyè siö metodñ
reverse
:
s = ".kapo an sipan tsej oT"
s.reverse # => "To jest napis na opak."
s # => ".kapo an sipan tsej oT"
s.reverse! # => "To jest napis na opak."
s # => "To jest napis na opak."
W celu odwrócenia kolejnoĈci säów w äaþcuchu, naleĔy podzieliè go najpierw na podäaþcuchy
oddzielone „biaäymi” znakami
2
(czyli poszczególne säowa), po czym wäñczyè listö tych säów
z powrotem do äaþcucha, w odwrotnej kolejnoĈci.
s = "kolei. po nie Wyrazy "
s.split(/(\s+)/).reverse!.join('') # => "Wyrazy nie po kolei."
s.split(/\b/).reverse!.join('') # => "Wyrazy nie po. kolei"
Dyskusja
Metoda
String#split
wykorzystuje wyraĔenie regularne w roli separatora. KaĔdorazowo
gdy element äaþcucha udaje siö dopasowaè do tego wyraĔenia, poprzedzajñca go czöĈè äaþcu-
cha wäñczona zostaje do listy, a metoda
split
przechodzi do skanowania dalszej czöĈci äaþ-
cucha. Efektem przeskalowania caäego äaþcucha jest lista podäaþcuchów znajdujñcych siö miö-
dzy wystñpieniami separatora. UĔyte w naszym przykäadzie wyraĔenie regularne
/(\s+)/
reprezentuje dowolny ciñg „biaäych” znaków, zatem metoda
split
dokonuje podziaäu äaþcu-
cha na poszczególne säowa (w potocznym rozumieniu).
WyraĔenie regularne
/b
reprezentuje granicö säowa; to nie to samo co „biaäy” znak, bowiem
granicö säowa moĔe takĔe wyznaczaè znak interpunkcyjny. Zwróè uwagö na konsekwencje
tej róĔnicy w powyĔszym przykäadzie.
PoniewaĔ wyraĔenie regularne
/(\s+)/
zawiera parö nawiasów, separatory takĔe sñ wäñcza-
ne do listy wynikowej. JeĔeli zatem zestawimy elementy tej listy w kolejnoĈci odwrotnej, se-
paratory oddzielajñce säowa bödñ juĔ obecne na swych miejscach. PoniĔszy przykäad ilustruje
róĔnicö miödzy zachowywaniem a ignorowaniem separatorów:
"Trzy banalne wyrazy".split(/\s+/) # => ["Trzy", "banalne", "wyrazy"]
"Trzy banalne wyrazy".split(/(\s+)/)
# => ["Trzy", " ", "banalne", " ", "wyrazy"]
Patrz tak
że
x
Receptura 1.9, „Przetwarzanie poszczególnych säów äaþcucha”, ilustruje kilka wyraĔeþ
regularnych wyznaczajñcych alternatywnñ definicjö „säowa”.
x
Receptura 1.11, „Zarzñdzanie biaäymi znakami”.
x
Receptura 1.17, „Dopasowywanie äaþcuchów za pomocñ wyraĔeþ regularnych”.
2
Pojöcie „biaäego znaku” wyjaĈnione jest w recepturze 1.11 — przyp. täum.
1.5. Reprezentowanie znaków niedrukowalnych
_
39
1.5. Reprezentowanie znaków niedrukowalnych
Problem
NaleĔy stworzyè äaþcuch zawierajñcy znaki sterujñce, znaki w kodzie UTF-8 lub dowolne znaki
niedostöpne z klawiatury.
Rozwi
ézanie
Ruby udostöpnia kilka mechanizmów unikowych (escape) w celu reprezentowania znaków
niedrukowalnych. W äaþcuchach ujötych w cudzysäowy mechanizmy te umoĔliwiajñ repre-
zentowanie dowolnych znaków.
Dowolny znak moĔna zakodowaè w äaþcuchu, podajñc jego kod ósemkowy (octal) w formie
\ooo
lub kod szesnastkowy (hexadecimal) w formie
\xhh
.
octal = "\000\001\010\020"
octal.each_byte { |x| puts x }
# 0
# 1
# 8
# 16
hexadecimal = "\x00\x01\x10\x20"
hexadecimal.each_byte { |x| puts x }
# 0
# 1
# 16
# 32
W ten sposób umieszczaè moĔna w äaþcuchach znaki, których nie moĔna wprowadziè bez-
poĈrednio z klawiatury czy nawet wyĈwietliè na ekranie terminala. Uruchom poniĔszy pro-
gram, po czym otwórz wygenerowany plik smiley.html w przeglñdarce WWW.
open('smiley.html', 'wb') do |f|
f << '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">'
f << "\xe2\x98\xBA"
end
Niektóre z niedrukowalnych znaków — te wykorzystywane najczöĈciej — posiadajñ specjal-
ne, skrócone kody unikowe:
"\a" == "\x07" # => true #ASCII 0x07 = BEL (D
şwiĂk systemowy)
"\b" == "\x08" # => true #ASCII 0x08 = BS (Cofanie)
"\e" == "\x1b" # => true #ASCII 0x1B = ESC (Escape)
"\f" == "\x0c" # => true #ASCII 0x0C = FF (Nowa strona)
"\n" == "\x0a" # => true #ASCII 0x0A = LF (Nowy wiersz)
"\r" == "\x0d" # => true #ASCII 0x0D = CR (Pocz
îtek wiersza)
"\t" == "\x09" # => true #ASCII 0x09 = HT (Tabulacja pozioma)
"\v" == "\x0b" # => true #ASCII 0x0B = VT (Tabulacja pionowa)
Dyskusja
W jözyku Ruby äaþcuchy sñ ciñgami bajtów. Nie ma znaczenia, czy bajty te sñ drukowalny-
mi znakami ASCII, niedrukowalnymi znakami binarnymi, czy teĔ mieszankñ obydwu tych
kategorii.
40
_
Rozdzia
ĥ 1. Ĥaħcuchy
Znaki niedrukowalne wyĈwietlane sñ w jözyku Ruby w czytelnej dla czäowieka reprezentacji
\ooo
, gdzie
ooo
jest kodem znaku w reprezentacji ósemkowej; znaki posiadajñce reprezenta-
cjö mnemonicznñ w postaci
\<znak>
wyĈwietlane sñ jednak w tej wäaĈnie postaci. Znaki dru-
kowalne wyĈwietlane sñ zawsze w swej naturalnej postaci, nawet jeĔeli w tworzonym äaþcu-
chu zakodowane zostaäy w inny sposób.
"\x10\x11\xfe\xff" # => "\020\021\376\377"
"\x48\145\x6c\x6c\157\x0a" # => "Hello\n"
Znak odwrotnego ukoĈnika (
\
) reprezentowany jest przez parö takich ukoĈników (
\\
) — jest
to konieczne dla odróĔnienia literalnego uĔycia znaku
\
od mnemonicznej sekwencji uniko-
wej rozpoczynajñcej siö od takiego znaku. Przykäadowo, äaþcuch
"\\n"
skäada siö z dwóch
znaków: odwrotnego ukoĈnika i litery
n
.
"\\".size # => 1
"\\" == "\x5c" # => true
"\\n"[0] == ?\\ # => true
"\\n"[1] == ?n # => true
"\\n" =~ /\n/ # => nil
Ruby udostöpnia takĔe kilka wygodnych skrótów dla reprezentowania kombinacji klawiszy
w rodzaju Ctrl+C. Sekwencja
\C-<znak>
oznacza rezultat naciĈniöcia klawisza
<znak>
z jed-
noczesnym przytrzymaniem klawisza Ctrl; analogicznie sekwencja
\M-<znak>
oznacza rezul-
tat naciĈniöcia klawisza
<znak>
z jednoczesnym przytrzymaniem klawisza Alt (lub Meta):
"\C-a\C-b\C-c" # => "\001\002\003" # Ctrl+A Ctrl+B Ctrl+C
"\M-a\M-b\M-c" # => "\341\342\343" # Alt+A Alt+B Alt+C
Dowolna z opisywanych sekwencji moĔe pojawiè siö wszödzie tam, gdzie Ruby spodziewa
siö znaku. W szczególnoĈci moĔliwe jest wyĈwietlenie kodu znaku w postaci dziesiötnej — na-
leĔy w tym celu poprzedziè ów znak znakiem zapytania (
?
).
?\C-a # => 1
?\M-z # => 250
W podobny sposób moĔna uĔywaè rozmaitych reprezentacji znaków do specyfikowania za-
kresu znaków w wyraĔeniach regularnych:
contains_control_chars = /[\C-a-\C-^]/
'Foobar' =~ contains_control_chars # => nil
"Foo\C-zbar" =~ contains_control_chars # => 3
contains_upper_chars = /[\x80-\xff]/
'Foobar' =~ contains_upper_chars # => nil
"Foo\212bar" =~ contains_upper_chars # => 3
PoniĔsza aplikacja Ĉledzi („szpieguje”) naciĈniöcia klawiszy, reagujñc na niektóre kombinacje
specjalne:
def snoop_on_keylog(input)
input.each_byte do |b|
case b
when ?\C-c; puts 'Ctrl+C: zatrzyma
ð proces?'
when ?\C-z; puts 'Ctrl+Z: zawiesi
ð proces?'
when ?\n; puts 'Nowy wiersz.'
when ?\M-x; puts 'Alt+X: uruchomi
ð Emacs?'
end
end
end
snoop_on_keylog("ls -ltR\003emacsHello\012\370rot13-other-window\012\032")
# Ctrl+C: zatrzyma
ð proces?
1.6. Konwersja mi
ýdzy znakami a kodami
_
41
# Nowy wiersz.
# Alt+X: uruchomi
ð Emacs?
# Nowy wiersz.
# Ctrl+Z: zawiesi
ð proces?
Sekwencje reprezentujñce znaki specjalne interpretowane sñ tylko w äaþcuchach ujötych
w cudzysäów oraz äaþcuchach tworzonych za pomocñ konstrukcji
%{}
lub
%Q{}
. Nie sñ one
interpretowane w äaþcuchach zamkniötych znakami apostrofu oraz äaþcuchach tworzonych
za pomocñ konstrukcji
%q{}
. Fakt ten moĔna wykorzystaè w przypadku potrzeby literalnego
wyĈwietlenia sekwencji reprezentujñcych znaki specjalne oraz w przypadku tworzenia äaþcu-
chów zawierajñcych duĔñ liczbö odwrotnych ukoĈników.
puts "foo\tbar"
# foo bar
puts %{foo\tbar}
# foo bar
puts %Q{foo\tbar}
# foo bar
puts 'foo\tbar'
# foo\tbar
puts %q{foo\tbar}
# foo\tbar
Nieinterpretowanie sekwencji reprezentujñcych znaki specjalne w äaþcuchach zamkniötych
znakami apostrofu moĔe wydaè siö dziwne — i niekiedy nieco käopotliwe — programistom
przyzwyczajonym do jözyka Python. JeĔeli äaþcuch ujöty w cudzysäów sam zawiera znaki
cudzysäowu (
"
), jedynym sposobem reprezentowania tychĔe jest uĔycie sekwencji unikowej
\"
,
\042
lub
\x22
. W przypadku äaþcuchów obfitujñcych w znaki cudzysäowu moĔe siö to
wydaè käopotliwe i najwygodniejszym rozwiñzaniem jest zamkniöcie äaþcucha apostrofami
— znaków cudzysäowu moĔna wówczas uĔywaè literalnie, tracimy jednak moĔliwoĈè inter-
pretowania znaków specjalnych. Na szczöĈcie istnieje zäoty Ĉrodek pozwalajñcy na pogodze-
nie tych sprzecznych racji: jeĈli chcesz zachowaè moĔliwoĈè interpretowania znaków specjal-
nych w äaþcuchach najeĔonych znakami cudzysäowu, uĔyj konstrukcji
%{}
.
1.6. Konwersja mi
ýdzy znakami a kodami
Problem
Chcemy otrzymaè kod ASCII danego znaku lub przetransformowaè kod ASCII znaku w sam
znak.
Rozwi
ézanie
Kod ASCII znaku moĔemy poznaè za pomocñ operatora
?
:
?a # => 97
?! # => 33
?\n # => 10
W podobny sposób moĔemy poznaè kod ASCII znaku wchodzñcego w skäad äaþcucha — na-
leĔy wówczas wyäuskaè ów znak z äaþcucha za pomocñ indeksu:
'a'[0] # => 97
'kakofonia'[1] # => 97
42
_
Rozdzia
ĥ 1. Ĥaħcuchy
Konwersjö odwrotnñ — kodu ASCII na znak o tym kodzie — realizuje metoda
chr
, zwracajñ-
ca jednoznakowy äaþcuch:
97.chr # => "a"
33.chr # => "!"
10.chr # => "\n"
0.chr # => "\000"
256.chr # RangeError: 256 out of char range
Dyskusja
Mimo iĔ äaþcuch jako taki nie jest tablicñ, moĔe byè utoĔsamiany z tablicñ obiektów
Fixnum
— po jednym obiekcie dla kaĔdego bajta. Za pomocñ odpowiedniego indeksu moĔna wyäu-
skaè obiekt
Fixnum
reprezentujñcy konkretny bajt äaþcucha, czyli kod ASCII tego bajta. Za po-
mocñ metody
String#each_byte
moĔna iterowaè po wszystkich obiektach
Fixnum
tworzñcych
dany äaþcuch.
Patrz tak
że
x
Receptura 1.8, „Przetwarzanie kolejnych znaków äaþcucha”.
1.7. Konwersja mi
ýdzy ĥaħcuchami a symbolami
Problem
Majñc symbol jözyka Ruby, naleĔy uzyskaè reprezentujñcy go äaþcuch, lub vice versa — ziden-
tyfikowaè symbol odpowiadajñcy danemu äaþcuchowi.
Rozwi
ézanie
Konwersjö symbolu na odpowiadajñcy mu äaþcuch realizuje metoda
Symbol#to_s
lub metoda
Symbol#id2name
, dla której
to_s
jest aliasem.
:a_symbol.to_s # => "a_symbol"
:InnySymbol.id2name # => "InnySymbol"
:"Jeszcze jeden symbol!".to_s # => "Jeszcze jeden symbol!"
Odwoäanie do symbolu nastöpuje zwykle przez jego nazwö. Aby uzyskaè symbol reprezento-
wany przez äaþcuch w kodzie programu, naleĔy posäuĔyè siö metodñ
String.intern
:
:dodecahedron.object_id # => 4565262
symbol_name = "dodecahedron"
symbol_name.intern # => :dodecahedron
symbol_name.intern.object_id # => 4565262
Dyskusja
Symbol
jest najbardziej podstawowym obiektem jözyka Ruby. KaĔdy symbol posiada nazwö
i wewnötrzny identyfikator (internal ID). UĔytecznoĈè symboli wynika z faktu, Ĕe wielokrot-
ne wystñpienie tej samej nazwy w kodzie programu oznacza kaĔdorazowo odwoäanie do te-
go samego symbolu.
1.8. Przetwarzanie kolejnych znaków
ĥaħcucha
_
43
Symbole sñ czösto bardziej uĔyteczne niĔ äaþcuchy. Dwa äaþcuchy o tej samej zawartoĈci
sñ dwoma róĔnymi obiektami — moĔna jeden z nich zmodyfikowaè bez wpäywu na drugi.
Dwie identyczne nazwy odnoszñ siö do tego samego symbolu, co oczywiĈcie przekäada siö na
oszczödnoĈè czasu i pamiöci.
"string".object_id # => 1503030
"string".object_id # => 1500330
:symbol.object_id # => 4569358
:symbol.object_id # => 4569358
Tak wiöc n wystñpieþ tej samej nazwy odnosi siö do tego samego symbolu, przechowywanego
w pamiöci w jednym egzemplarzu. n identycznych äaþcuchów to n róĔnych obiektów o iden-
tycznej zawartoĈci. TakĔe porównywanie symboli jest szybsze niĔ porównywanie äaþcuchów,
bowiem sprowadza siö jedynie do porównywania identyfikatorów.
"string1" == "string2" # => false
:symbol1 == :symbol2 # => false
Na koniec zacytujmy hakera od jözyka Ruby, Jima Wericha:
x
UĔyj äaþcucha, jeĈli istotna jest zawartoĈè obiektu (sekwencja tworzñcych go znaków).
x
UĔyj symbolu, jeĈli istotna jest toĔsamoĈè obiektu.
Patrz tak
że
x
Receptura 5.1, „Wykorzystywanie symboli jako kluczy”.
x
Receptura 8.12, „Symulowanie argumentów zawierajñcych säowa kluczowe”.
x
Rozdziaä 10., a szczególnie receptura 10.4, „Uzyskiwanie referencji do metody”, i receptu-
ra 10.10, „Oszczödne kodowanie dziöki metaprogramowaniu”.
x
http://glu.ttono.us/articles/2005/08/19/understanding-ruby-symbols — interesujñcy artykuä o sym-
bolach jözyka Ruby.
1.8. Przetwarzanie kolejnych znaków
ĥaħcucha
Problem
NaleĔy wykonaè pewnñ czynnoĈè w stosunku do kaĔdego znaku äaþcucha z osobna.
Rozwi
ézanie
W dokumencie zäoĔonym wyäñcznie ze znaków ASCII kaĔdy bajt äaþcucha odpowiada jedne-
mu znakowi. Za pomocñ metody
String#each_byte
moĔna wyodröbniè poszczególne bajty
jako liczby, które nastöpnie mogñ byè skonwertowane na znaki.
'foobar'.each_byte { |x| puts "#{x} = #{x.chr}" }
# 102 = f
# 111 = o
# 111 = o
# 98 = b
# 97 = a
# 114 = r
44
_
Rozdzia
ĥ 1. Ĥaħcuchy
Za pomocñ metody
String#scan
moĔna wyodröbniè poszczególne znaki äaþcucha jako jedno-
znakowe äaþcuchy:
'foobar'.scan( /./ ) { |c| puts c }
# f
# o
# o
# b
# a
# r
Dyskusja
PoniewaĔ äaþcuch jest sekwencjñ bajtów, moĔna by oczekiwaè, Ĕe metoda
String#each
umoĔ-
liwia iterowanie po tej sekwencji, podobnie jak metoda
Array#each
. Jest jednak inaczej — me-
toda
String#each
dokonuje podziaäu äaþcucha na podäaþcuchy wzglödem pewnego separa-
tora (którym domyĈlnie jest znak nowego wiersza):
"foo\nbar".each { |x| puts x }
# foo
# bar
Odpowiednikiem metody
Array#each
w odniesieniu do äaþcuchów jest metoda
each_byte
.
KaĔdy element äaþcucha moĔe byè traktowany jako obiekt
Fixnum
, a metoda
each_byte
umoĔ-
liwia iterowanie po sekwencji tych obiektów.
Metoda
String#each_byte
jest szybsza niĔ
String#scan
i jako taka zalecana jest w przypad-
ku przetwarzania plików ASCII — kaĔdy wyodröbniony obiekt
Fixnum
moĔe byè äatwo prze-
ksztaäcony w znak (jak pokazano w Rozwiñzaniu).
Metoda
String#scan
dokonuje sukcesywnego dopasowywania podanego wyraĔenia regu-
larnego do kolejnych porcji äaþcucha i wyodröbnia kaĔdñ z tych opcji. JeĔeli wyraĔeniem tym
jest
/./
, wyodröbniane sñ poszczególne znaki äaþcucha.
JeĈli zmienna
$KCODE
jest odpowiednio ustawiona, metoda
scan
moĔe byè stosowana takĔe
do äaþcuchów zawierajñcych znaki w kodzie UTF-8. Jest to najprostsza metoda przeniesienia
koncepcji „znaku” na grunt äaþcuchów jözyka Ruby, które z definicji sñ ciñgami bajtów, nie
znaków.
PoniĔszy äaþcuch zawiera zakodowanñ w UTF-8 francuskñ frazö „ça va”:
french = "\xc3\xa7a va"
Nawet jeĔeli znaku
ç
nie sposób poprawnie wyĈwietliè na terminalu, poniĔszy przykäad ilu-
struje zmianö zachowania metody
String#scan
w sytuacji, gdy okreĈli siö wyraĔenie regu-
larne stosownie do standardów Unicode lub ustawi zmiennñ
$KCODE
tak, by Ruby traktowaä
wszystkie äaþcuchy jako kodowane wedäug UTF-8:
french.scan(/./) { |c| puts c }
#
ë
# §
# a
#
# v
# a
french.scan(/./u) { |c| puts c }
# ç
# a
#
1.9. Przetwarzanie poszczególnych s
ĥów ĥaħcucha
_
45
# v
# a
$KCODE = 'u'
french.scan(/./) { |c| puts c }
# ç
# a
#
# v
# a
Gdy Ruby traktuje äaþcuchy jako sekwencje znaków UTF-8, a nie ASCII, dwa bajty reprezen-
tujñce znak
ç
traktowane sñ äñcznie, jako pojedynczy znak. Nawet jeĈli niektórych znaków
UTF-8 nie moĔna wyĈwietliè na ekranie terminala, moĔna stworzyè programy, które zajmñ
siö ich obsäugñ.
Patrz tak
że
x
Receptura 11.12, „Konwersja dokumentu miödzy róĔnymi standardami kodowania”.
1.9. Przetwarzanie poszczególnych s
ĥów ĥaħcucha
Problem
NaleĔy wydzieliè z äaþcucha jego kolejne säowa i dla kaĔdego z tych säów wykonaè pewnñ
czynnoĈè.
Rozwi
ézanie
Najpierw naleĔy zastanowiè siö nad tym, co rozumiemy pod pojöciem „säowa” w äaþcuchu.
Co oddziela od siebie sñsiednie säowa? Tylko biaäe znaki, czy moĔe takĔe znaki interpunkcyj-
ne? Czy „taki-to-a-taki” to pojedyncze säowo, czy moĔe cztery säowa? Te i inne kwestie roz-
strzyga siö jednoznacznie, definiujñc wyraĔenie regularne reprezentujñce pojedyncze säowo
(kilka przykäadów takich wyraĔeþ podajemy poniĔej w Dyskusji).
Wspomniane wyraĔenie regularne naleĔy przekazaè jako parametr metody
String#scan
, któ-
ra tym samym dokona podzielenia äaþcucha na poszczególne säowa. Prezentowana poniĔej
metoda
word_count
zlicza wystñpienia poszczególnych säów w analizowanym tekĈcie; zgod-
nie z uĔytym wyraĔeniem regularnym „säowo” ma skäadniö identycznñ z identyfikatorem jö-
zyka Ruby, jest wiöc ciñgiem liter, cyfr i znaków podkreĈlenia:
class String
def word_count
frequencies = Hash.new(0)
downcase.scan(/\w+/) { |word| frequencies[word] += 1 }
return frequencies
end
end
%{Dogs dogs dog dog dogs.}.word_count
# => {"dogs"=>3, "dog"=>2}
%{"I have no shame," I said.}.word_count
# => {"no"=>1, "shame"=>1, "have"=>1, "said"=>1, "i"=>2}
46
_
Rozdzia
ĥ 1. Ĥaħcuchy
Dyskusja
WyraĔenie regularne
/\w+/
jest co prawda proste i eleganckie, jednakĔe ucieleĈniana przezeþ
definicja „säowa” z pewnoĈciñ pozostawia wiele do Ĕyczenia. Przykäadowo, rzadko kto skäon-
ny byäby uwaĔaè za pojedyncze säowo dwa säowa (w rozumieniu potocznym) poäñczone zna-
kiem podkreĈlenia, ponadto niektóre ze säów angielskich — jak „pan-fried” czy „foc’s’le” —
zawierajñ znaki interpunkcyjne. Warto wiöc byè moĔe rozwaĔyè kilka alternatywnych wyra-
Ĕeþ regularnych, opartych na bardziej wyszukanych koncepcjach säowa:
# Podobne do /\w+/, lecz nie dopuszcza podkresle
Ĭ wewnîtrz sĪowa.
/[0-9A-Za-z]/
# Dopuszcza w s
Īowie dowolne znaki oprócz biaĪych znaków.
/[^\S]+/
# Dopuszcza w s
Īowie litery, cyfry, apostrofy i Īîczniki
/[-'\w]+/
# Zadowalaj
îca heurystyka reprezentowania sĪów angielskich
/(\w+([-'.]\w+)*)/
Ostatnie z prezentowanych wyraĔeþ regularnych wymaga krótkiego wyjaĈnienia. Reprezen-
towana przezeþ koncepcja dopuszcza znaki interpunkcyjne wewnñtrz säowa, lecz nie na jego
kraþcach — i tak na przykäad „Work-in-progress” zostanie w Ĉwietle tej koncepcji uznane
za pojedyncze säowo, lecz juĔ äaþcuch „--never--” rozpoznany zostanie jako säowo „never”
otoczone znakami interpunkcyjnymi. Co wiöcej, poprawnie rozpoznane zostanñ akronimy
w rodzaju „U.N.C.L.E.” czy „Ph.D.” — no, moĔe nie do koþca poprawnie, poniewaĔ ostatnia
z kropek, równouprawniona z poprzednimi, nie zostanie zaliczona w poczet säowa i pierw-
szy z wymienionych akronimów zostanie rozpoznany jako säowo „U.N.C.L.E”, po którym
nastöpuje kropka.
Napiszmy teraz na nowo naszñ metodö
word_count
, wykorzystujñc ostatnie z prezentowa-
nych wyraĔeþ regularnych. RóĔni siö ono od wersji poprzedniej pewnym istotnym szczegó-
äem: otóĔ wykorzystywane wyraĔenie regularne skäada siö tym razem z dwóch grup. Metoda
String#scan
wyodröbni wiöc kaĔdorazowo dwa podäaþcuchy i przekaĔe je jako dwa argu-
menty do swego bloku kodowego. PoniewaĔ tylko pierwszy z tych argumentów reprezento-
waè bödzie rzeczywiste säowo, drugi z nich musimy zwyczajnie zignorowaè.
class String
def word_count
frequencies = Hash.new(0)
downcase.scan(/(\w+([-'.]\w+)*)/) { |word, ignore| frequencies[word] += 1 }
return frequencies
end
end
%{"That F.B.I. fella--he's quite the man-about-town."}.word_count
# => {"quite"=>1, "f.b.i"=>1, "the"=>1, "fella"=>1, "that"=>1,
# "man-about-town"=>1, "he's"=>1}
Zwróèmy uwagö, iĔ fraza
\w
reprezentowaè moĔe róĔne rzeczy w zaleĔnoĈci od wartoĈci
zmiennej
$KCODE
. DomyĈlnie reprezentuje ona jedynie säowa skäadajñce siö wyäñcznie ze zna-
ków ASCII:
french = "il \xc3\xa9tait une fois"
french.word_count
# => {"fois"=>1, "une"=>1, "tait"=>1, "il"=>1}
1.10. Zmiana wielko
ļci liter w ĥaħcuchu
_
47
JeĈli jednak wäñczymy obsäugö kodu UTF-8, reprezentowaè bödzie ona takĔe säowa zawiera-
jñce znaki w tymĔe kodzie:
$KCODE='u'
french.word_count
# => {"fois"=>1, "une"=>1, "était"=>1, "il"=>1}
Grupa
/b
w wyraĔeniu regularnym reprezentuje granicö säowa, czyli ostatnie säowo poprze-
dzajñce biaäy znak lub znak interpunkcyjny. Fakt ten bywa uĔyteczny w odniesieniu do me-
tody
String#split
(patrz receptura 1.4), lecz juĔ nie tak uĔyteczny w stosunku do metody
String#scan
.
Patrz tak
że
x
Receptura 1.4, „Odwracanie kolejnoĈci säów lub znaków w äaþcuchu”.
x
W bibliotece
Facets core
zdefiniowana jest metoda
String#each_word
, wykorzystujñca
wyraĔenie regularne
/([-'\w]+)/
.
1.10. Zmiana wielko
ļci liter w ĥaħcuchu
Problem
Wielkie/maäe litery sñ niewäaĈciwie uĔyte w äaþcuchu.
Rozwi
ézanie
Klasa
String
definiuje kilka metod zmieniajñcych wielkoĈè liter w äaþcuchu:
s = 'WITAM, nie ma Mnie W Domu, JesTeM W kaWIArNi.'
s.upcase # => "WITAM, NIE MA MNIE W DOMU, JESTEM W KAWIARNI."
s.downcase # => "witam, nie ma mnie w domu, jestem w kawiarni."
s.swapcase # => "witam, NIE MA mNIE w dOMU, jEStEm w KAwiaRnI."
s.capitalize # => "Witam, nie ma mnie w domu, jestem w kawiarni."
Dyskusja
Metody
upcase
i
downcase
wymuszajñ zmianö wszystkich liter w äaþcuchu na (odpowiednio)
wielkie i maäe. Metoda
swapcase
dokonuje zamiany maäych liter na wielkie i vice versa. Me-
toda
capitalize
dokonuje zamiany pierwszego znaku äaþcucha na wielkñ literö pod warun-
kiem,
Ĕe znak ten jest literñ; wszystkie nastöpne litery w äaþcuchu zamieniane sñ na maäe.
KaĔda z czterech wymienionych metod posiada swój odpowiednik dokonujñcy stosownej
zamiany liter w miejscu —
upcase!
,
downcase!
,
swapcase!
i
capitalize!
. Przy zaäoĔeniu, Ĕe
oryginalny äaþcuch nie jest däuĔej potrzebny, uĔycie tych metod moĔe zmniejszyè zajötoĈè
pamiöci, szczególnie w przypadku däugich äaþcuchów:
un_banged = 'Hello world.'
un_banged.upcase # => "HELLO WORLD."
un_banged # => "Hello world."
banged = 'Hello world.'
banged.upcase! # => "HELLO WORLD."
banged # => "HELLO WORLD."
48
_
Rozdzia
ĥ 1. Ĥaħcuchy
W niektórych przypadkach istnieje potrzeba zamiany pierwszego znaku äaþcucha na wielkñ
literö (jeĈli w ogóle jest literñ) bez zmiany wielkoĈci pozostaäych liter — w äaþcuchu mogñ
bowiem wystöpowaè nazwy wäasne. CzynnoĈè tö realizujñ dwie poniĔsze metody — druga
oczywiĈcie dokonuje stosownej zamiany „w miejscu”:
class String
def capitalize_first_letter
self[0].chr.capitalize + self[1, size]
end
def capitalize_first_letter!
unless self[0] == (c = self[0,1].upcase[0])
self[0] = c
self
end
# Zwraca nil, je
Łli nie dokonano šadnych zmian, podobnie jak np. upcase!.
end
end
s = 'teraz jestem w Warszawie. Jutro w Sopocie.'
s.capitalize_first_letter # => "Teraz jestem w Warszawie. Jutro w Sopocie."
s # => "teraz jestem w Warszawie. Jutro w Sopocie."
s.capitalize_first_letter!
s # => "Teraz jestem w Warszawie. Jutro w Sopocie."
Do zmiany wielkoĈci wybranej litery w äaþcuchu, bez zmiany wielkoĈci pozostaäych liter, moĔ-
na wykorzystaè metodö
tr
lub
tr!
, dokonujñcñ translacji jednego znaku na inny:
'LOWERCASE ALL VOWELS'.tr('AEIOU', 'aeiou')
# => "LoWeRCaSe aLL VoWeLS"
'Swap case of ALL VOWELS'.tr('AEIOUaeiou', 'aeiouAEIOU')
# => "SwAp cAsE Of aLL VoWeLS"
Patrz tak
że
x
Receptura 1.18, „Zastöpowanie wielu wzorców w pojedynczym przebiegu”.
x
W bibliotece
Facets core
zdefiniowana jest metoda
String#camelcase
oraz metody pre-
dykatowe
String#lowercase?
i
String#uppercase?
.
1.11. Zarz
édzanie biaĥymi znakami
Problem
ãaþcuch zawiera zbyt duĔo lub zbyt maäo biaäych znaków, bñdĒ uĔyto w nim niewäaĈciwych
biaäych znaków.
Rozwi
ézanie
Za pomocñ metody
strip
moĔna usunñè biaäe znaki z poczñtku i koþca äaþcucha.
" \tWhitespace at beginning and end. \t\n\n".strip
# => "Whitespace at beginning and end."
Metody
ljust
,
rjust
i
center
dokonujñ (odpowiednio) wyrównania äaþcucha do lewej stro-
ny, wyrównania do prawej oraz wyĈrodkowania:
1.12. Czy mo
żna potraktowaë dany obiekt jak ĥaħcuch?
_
49
s = "To jest napis." # => "To jest napis."
s.center(30) => # => " To jest napis. "
s.ljust(30) => # => "To jest napis. "
s.rjust(30) => # => " To jest napis."
Za pomocñ metody
gsub
, w poäñczeniu z wyraĔeniami regularnymi, moĔna dokonywaè zmian
bardziej zaawansowanych, na przykäad zastöpowaè jeden typ biaäych znaków innym:
# Normalizacja kodu przez zast
Ăpowanie kašdego tabulatora ciîgiem dwóch spacji
rubyCode.gsub("\t", " ")
# Zamiana ograniczników wiersza z windowsowych na uniksowe
"Line one\n\rLine two\n\r".gsub("\n\r", "\n")
# => "Line one\nLine two\n"
# Zamiana ka
šdego ciîgu biaĪych znaków na pojedynczî spacjĂ
"\n\rThis string\t\t\tuses\n all\tsorts\nof whitespace.".gsub(/\s+/, " ")
# => " This string uses all sorts of whitespace."
Dyskusja
Biaäym znakiem (whitespace) jest kaĔdy z piöciu nastöpujñcych znaków: spacja, tabulator (
\t
),
znak nowego wiersza (
\n
), znak powrotu do poczñtku wiersza (
\r
) i znak nowej strony (
\f
).
WyraĔenie regularne
/\s/
reprezentuje dowolny znak z tego zbioru. Metoda
strip
dokonuje
usuniöcia dowolnej kombinacji tych znaków z poczñtku i koþca äaþcucha.
Niekiedy konieczne jest przetwarzanie innych niedrukowalnych znaków w rodzaju backspace
(
\b
lub
\010
) czy tabulatora pionowego (
\v
lub
\012
). Znaki te nie naleĔñ do grupy znaków
reprezentowanych przez
/s
w wyraĔeniu regularnym i trzeba je reprezentowaè explicite:
" \bIt's whitespace, Jim,\vbut not as we know it.\n".gsub(/[\s\b\v]+/, " ")
# => " It's whitespace, Jim, but not as we know it. "
Do usuniöcia biaäych znaków tylko z poczñtku lub tylko z koþca äaþcucha moĔna wykorzy-
staè metody (odpowiednio)
lstrip
i
rstrip
:
s = " Whitespace madness! "
s.lstrip # => "Whitespace madness! "
s.rstrip # => " Whitespace madness!"
Metody dopeäniajñce spacjami do Ĕñdanej däugoĈci (
ljust
,
rjust
i
center
) posiadajñ jeden
argument wywoäania — tö wäaĈnie däugoĈè. JeĔeli wyĈrodkowanie äaþcucha nie moĔe byè
wykonane idealnie, bo liczba doäñczanych spacji jest nieparzysta, z prawej strony doäñczana
jest jedna spacja wiöcej niĔ z lewej.
"napis".center(9) # => " napis "
"napis".center(10) # => " napis "
Podobnie jak wiökszoĈè metod modyfikujñcych äaþcuchy, metody
strip
,
gsub
,
lstrip
i
rstrip
posiadajñ swe odpowiedniki operujñce „w miejscu” —
strip!
,
gsub!
,
lstrip!
i
rstrip!
.
1.12. Czy mo
żna potraktowaë dany obiekt jak ĥaħcuch?
Problem
Czy dany obiekt przejawia elementy funkcjonalnoĈci charakterystyczne dla äaþcuchów?
50
_
Rozdzia
ĥ 1. Ĥaħcuchy
Rozwi
ézanie
SprawdĒ, czy obiekt definiuje metodö
to_str
.
'To jest napis'.respond_to? :to_str # => true
Exception.new.respond_to? :to_str # => true
4.respond_to? :to_str # => false
Sformuäowany powyĔej problem moĔemy jednak rozwaĔaè w postaci bardziej ogólnej: czy
mianowicie dany obiekt definiuje pewnñ konkretnñ metodö klasy
String
, z której to metody
chcielibyĈmy skorzystaè. Oto przykäad konkatenacji obiektu z jego nastöpnikiem i konwersji
wyniku do postaci äaþcucha — to wszystko wykonalne jest jednak tylko wtedy, gdy obiekt
definiuje metodö
succ
wyznaczajñcñ nastöpnik:
def join_to_successor(s)
raise ArgumentError, 'Obiekt nie definiuje metody succ!' unless s.respond_to? :succ
return "#{s}#{s.succ}"
end
join_to_successor('a') # => "ab"
join_to_successor(4) # => "45"
join_to_successor(4.01) # ArgumentError: Obiekt nie definiuje metody succ!
GdybyĈmy zamiast predykatu
s.respond_to? :succ
uĔyli predykatu
s.is_a? String
, oka-
zaäoby siö, Ĕe nie jest moĔliwe wyznaczenie nastöpnika dla liczby caäkowitej:
def join_to_successor(s)
raise ArgumentError, 'Obiekt nie jest
ĪaĬcuchem!' unless s.is_a? String
return "#{s}#{s.succ}"
end
join_to_successor('a') # => "ab"
join_to_successor(4) # => ArgumentError: 'Obiekt nie jest
ĪaĬcuchem!'
join_to_successor(4.01) # => ArgumentError: 'Obiekt nie jest
ĪaĬcuchem!'
Dyskusja
To, co widzimy powyĔej, jest najprostszym przykäadem pewnego aspektu filozofii jözyka
Ruby, zwanego „kaczym typowaniem” (duck typing): jeĈli mianowicie chcemy przekonaè siö,
Ĕe dane zwierzö jest kaczkñ, moĔemy skäoniè je do wydania gäosu — powinniĈmy wówczas
usäyszeè kwakanie. Na podobnej zasadzie moĔemy badaè rozmaite aspekty funkcjonalnoĈci
obiektu, sprawdzajñc, czy obiekt ów definiuje metody o okreĈlonych nazwach, realizujñce tö
wäaĈnie funkcjonalnoĈè.
Jak przekonaliĈmy siö przed chwilñ, predykat
obj.is_a? String
nie jest najlepszym sposo-
bem badania, czy mamy do czynienia z äaþcuchem. Owszem, jeĈli predykat ten jest speäniony,
obiekt äaþcuchem jest niewñtpliwie, jego klasa wywodzi siö bowiem z klasy
String
; zaleĔnoĈè
odwrotna nie zawsze jest jednak prawdziwa — pewne zachowania typowe dla äaþcuchów
mogñ byè przejawiane przez obiekty niewywodzñce siö z klasy
String
.
Jako przykäad posäuĔyè moĔe klasa
Exceptions
, której obiekty sñ koncepcyjnie äaþcuchami
wzbogaconymi o pewne dodatkowe informacje. Klasa
Exceptions
nie jest jednak subklasñ
klasy
String
i uĔycie w stosunku do niej predykatu
is_a? String
moĔe spowodowaè prze-
oczenie jej „äaþcuchowoĈci”. Wiele moduäów jözyka Ruby definiuje inne rozmaite klasy o tej-
Ĕe wäasnoĈci.
1.13. Wyodr
ýbnianie czýļci ĥaħcucha
_
51
Warto wiöc zapamiötaè (i stosowaè) opisanñ filozofiö: jeĈli chcemy badaè pewien aspekt funk-
cjonalny obiektu, powinniĈmy czyniè to, sprawdzajñc (za pomocñ predykatu
respond_to?
),
czy obiekt ten definiuje okreĈlonñ metodö, zamiast badaè jego genealogiö za pomocñ predy-
katu
is_a?
. Pozwoli to w przyszäoĈci na definiowanie nowych klas oferujñcych te same moĔ-
liwoĈci, bez kröpujñcego uzaleĔniania ich od istniejñcej hierarchii klas. Jedynym uzaleĔnie-
niem bödzie wówczas uzaleĔnienie od konkretnych nazw metod.
Patrz tak
że
x
Rozdziaä 8., szczególnie wstöp oraz receptura 8.3, „Weryfikacja funkcjonalnoĈci obiektu”.
1.13. Wyodr
ýbnianie czýļci ĥaħcucha
Problem
Majñc dany äaþcuch, naleĔy wyodröbniè okreĈlone jego fragmenty.
Rozwi
ézanie
W celu wyodröbnienia podäaþcucha moĔemy posäuĔyè siö metodñ
slice
lub wykorzystaè
operator indeksowania tablicy (czyli de facto wywoäaè metodö
[]
). W obydwu przypadkach
moĔemy okreĈliè bñdĒ to zakres (obiekt
Range
) wyodröbnianych znaków, bñdĒ parö liczb caä-
kowitych (obiektów
Fixnum
) okreĈlajñcych (kolejno) indeks pierwszego wyodröbnianego zna-
ku oraz liczbö wyodröbnianych znaków:
s = "To jest napis"
s.slice(0,2) # => "To"
s[3,4] # => "jest"
s[8,5] # => "napis"
s[8,0] # => ""
Aby wyodröbniè pierwszñ porcjö äaþcucha pasujñcñ do danego wyraĔenia regularnego, naleĔy
wyraĔenia tego uĔyè jako argumentu wywoäania metody
slice
lub operatora indeksowego:
s[/.pis/] # => "apis"
s[/na.*/] # => "napis"
Dyskusja
Dla uzyskania pojedynczego bajta äaþcucha (jako obiektu
Fixnum
) wystarczy podaè jeden ar-
gument — indeks tego bajta (pierwszy bajt ma indeks 0). Aby otrzymaè znakowñ postaè owe-
go bajta, naleĔy podaè dwa argumenty: jego indeks oraz
1
:
s.slice(3) # => 106
s[3] # => 106
106.chr # => "j"
s.slice(3,1) # => "j"
s[3,1] # => "j"
Ujemna wartoĈè pierwszego argumentu oznacza indeks liczony wzglödem koþca äaþcucha:
s.slice(-1,1) # => "s"
s.slice(-5,5) # => "napis"
s[-5,5] # => "napis"
52
_
Rozdzia
ĥ 1. Ĥaħcuchy
JeĔeli specyfikowana däugoĈè podäaþcucha przekracza däugoĈè caäego äaþcucha liczonñ od
miejsca okreĈlonego przez pierwszy argument, zwracana jest caäa reszta äaþcucha poczñwszy
od tego miejsca. UmoĔliwia to wygodne specyfikowanie „koþcówek” äaþcuchów:
s[8,s.length] # => "napis"
s[-5,s.length] # => "napis"
s[-5, 65535] # => "napis"
Patrz tak
że
x
Receptura 1.9, „Przetwarzanie poszczególnych säów äaþcucha”.
x
Receptura 1.17, „Dopasowywanie äaþcuchów za pomocñ wyraĔeþ regularnych”.
1.14. Obs
ĥuga miýdzynarodowego kodowania
Problem
W äaþcuchu znajdujñ siö znaki niewchodzñce w skäad kodu ASCII — na przykäad znaki Uni-
code kodowane wedäug UTF-8.
Rozwi
ézanie
Aby zapewniè poprawnñ obsäugö znaków Unicode, naleĔy na poczñtku kodu umieĈciè nastö-
pujñcñ sekwencjö:
$KCODE='u'
require 'jcode'
Identyczny efekt moĔna osiñgnñè, uruchamiajñc interpreter jözyka Ruby w nastöpujñcy sposób:
$ ruby -Ku –rjcode
W Ĉrodowisku Uniksa moĔna okreĈliè powyĔsze parametry w poleceniu uruchamiajñcym
skrypt (shebang line):
#!/usr/bin/ruby -Ku –rjcode
W bibliotece
jcode
wiökszoĈè metod klasy
String
zostaäa przedefiniowana tak, by metody
te zapewniaäy obsäugö znaków wielobajtowych. Nie przedefiniowano metod
String#length
,
String#count
i
String#size
, definiujñc w zamian trzy nowe metody,
String#jlength
,
String#jcount
i
String#jsize
.
Dyskusja
Rozpatrzmy przykäadowy äaþcuch zawierajñcy szeĈè znaków Unicode:
efbca1
(
A
),
efbca2
(
B
),
efbca3
(
C
),
efbca4
(
D
),
efbca5
(
E
) i
efbca6
(
F
):
string = "\xef\xbc\xa1" + "\xef\xbc\xa2" + "\xef\xbc\xa3" +
"\xef\xbc\xa4" + "\xef\xbc\xa5" + "\xef\xbc\xa6"
ãaþcuch ten skäada siö z 18 bajtów, kodujñcych 6 znaków:
string.size # => 18
string.jsize # => 6
1.15. Zawijanie wierszy tekstu
_
53
Metoda
String#count
zlicza wystñpienia okreĈlonych bajtów w äaþcuchu, podczas gdy me-
toda
String#jcount
dokonuje zliczania okreĈlonych znaków:
string.count "\xef\xbc\xa2" # => 13
string.jcount "\xef\xbc\xa2" # => 1
W powyĔszym przykäadzie metoda
count
traktuje argument
"\xef\xbc\xa2"
jak trzy od-
dzielne bajty
\xef
,
\xbc
i
\xa2
, zwracajñc sumö liczby ich wystñpieþ w äaþcuchu (6+6+1). Me-
toda
jcount
traktuje natomiast swój argument jako pojedynczy znak, zwracajñc liczbö jego wy-
stñpieþ w äaþcuchu (w tym przypadku znak wystöpuje tylko raz).
"\xef\xbc\xa2".length # => 3
"\xef\xbc\xa2".jlength # => 1
Metoda
String#length
zwraca, jak wiadomo, liczbö bajtów äaþcucha niezaleĔnie od tego, ja-
kie znaki sñ za pomocñ tych bajtów kodowane. Metoda
String#jlength
zwraca natomiast
liczbö kodowanych znaków.
Mimo tych wyraĒnych róĔnic obsäuga znaków Unicode odbywa siö w jözyku Ruby w wiök-
szoĈci „pod podszewkñ” — przetwarzanie äaþcuchów zawierajñcych znaki kodowane wedäug
UTF-8 odbywa siö w sposób elegancki i naturalny, bez jakiejĈ szczególnej troski ze strony pro-
gramisty. Stanie siö to caäkowicie zrozumiaäe, gdy uĈwiadomimy sobie, Ĕe twórca Ruby —
Yukihiro Matsumoto — jest Japoþczykiem.
Patrz tak
że
x
Tekst zäoĔony ze znaków kodowanych w systemie innym niĔ UTF-8 moĔe byè äatwo
przekodowany do UTF-8 za pomocñ biblioteki
iconv
, o czym piszemy w recepturze 11.2,
„Ekstrakcja informacji z drzewa dokumentu”.
x
Istnieje kilka wyszukiwarek on-line obsäugujñcych znaki Unicode; dwiema godnymi pole-
cenia wydajñ siö naszym zdaniem http://isthisthingon.org/unicode/ oraz http://www.fileformat.
info/info/unicode/char/search.htm.
1.15. Zawijanie wierszy tekstu
Problem
ãaþcuch zawierajñcy duĔñ liczbö biaäych znaków naleĔy sformatowaè, dzielñc go na wiersze,
tak aby moĔliwe byäo jego wyĈwietlenie w oknie lub wysäanie e-mailem.
Rozwi
ézanie
Najprostszym sposobem wstawienia do äaþcucha znaków nowego wiersza jest uĔycie wyra-
Ĕenia regularnego podobnego do poniĔszego:
def wrap(s, width=78)
s.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n")
end
wrap("Ten tekst jest zbyt krótki, by trzeba go by
Īo zawijað.")
# => "Ten tekst jest zbyt krótki, by trzeba go by
Īo zawijað. \n"
puts wrap("Ten tekst zostanie zawini
Ăty.", 15)
54
_
Rozdzia
ĥ 1. Ĥaħcuchy
# Ten tekst
# zostanie
# zawini
Ăty.
puts wrap("Ten tekst zostanie zawini
Ăty.", 20)
# Ten tekst zostanie
# zawini
Ăty.
puts wrap("By
ð albo nie byð – oto jest pytanie!",5)
# By
ð
# albo
# nie
# by
ð –
# oto
# jest
# pytanie!
Dyskusja
W prezentowanym przykäadzie zachowane zostaäo oryginalne formatowanie äaþcucha, jedno-
czeĈnie w kilku jego miejscach wstawione zostaäy znaki nowego wiersza. W efekcie uzyskali-
Ĉmy äaþcuch zdatny do wyĈwietlenia w stosunkowo niewielkim obszarze ekranu.
poetry = %q{It is an ancient Mariner,
And he stoppeth one of three.
"By thy long beard and glittering eye,
Now wherefore stopp'st thou me?}
puts wrap(poetry, 20)
# It is an ancient
# Mariner,
# And he stoppeth one
# of three.
# "By thy long beard
# and glittering eye,
# Now wherefore
# stopp'st thou me?
Niekiedy jednak biaäe znaki nie sñ istotne, co wiöcej — zachowanie ich w äaþcuchu powoduje
pogorszenie koþcowego rezultatu formatowania:
prose = %q{Czu
Īem siĂ tak samotny tego dnia, jak rzadko kiedy,
spogl
îdajîc apatycznie na deszcz padajîcy za oknem. Jak dĪugo jeszcze bĂdzie
pada
ð? W gazecie byĪa prognoza pogody, ale któš w ogóle zadaje sobie trud
jej czytania?}
puts wrap(prose, 50)
# Czu
Īem siĂ tak samotny tego dnia, jak rzadko
# kiedy,
# spogl
îdajîc apatycznie na deszcz padajîcy za
# oknem. Jak d
Īugo jeszcze bĂdzie
# pada
ð? W gazecie byĪa prognoza pogody, ale któš w
# ogóle zadaje sobie trud
# jej czytania?
By zniwelowaè efekt „postrzöpienia” tekstu, naleĔaäoby najpierw usunñè z niego istniejñce
znaki nowego wiersza. NaleĔy w tym celu uĔyè innego wyraĔenia regularnego:
def reformat_wrapped(s, width=78)
s.gsub(/\s+/, " ").gsub(/(.{1,#{width}})( |\Z)/, "\\1\n")
end
1.16. Generowanie nast
ýpnika ĥaħcucha
_
55
Przetwarzanie sterowane wyraĔeniami regularnymi jest jednak stosunkowo powolne; znacz-
nie efektywniejszym rozwiñzaniem byäoby podzielenie äaþcucha na poszczególne säowa i zäo-
Ĕenie z nich nowego äaþcucha, podzielonego na wiersze nieprzekraczajñce okreĈlonej däugoĈci:
def reformat_wrapped(s, width=78)
lines = []
line = ""
s.split(/\s+/).each do |word|
if line.size + word.size >= width
lines << line
line = word
elsif line.empty?
line = word
else
line << " " << word
end
end
lines << line if line
return lines.join "\n"
end
puts reformat_wrapped(prose, 50)
# Czu
Īem siĂ tak samotny tego dnia, jak rzadko
# kiedy, spogl
îdajîc apatycznie na deszcz padajîcy
# za oknem. Jak d
Īugo jeszcze bĂdzie padað? W
# gazecie by
Īa prognoza pogody, ale któš w ogóle
# zadaje sobie trud jej czytania?
Patrz tak
że
x
W bibliotece
Facets Core
zdefiniowane sñ metody
String#word_wrap
i
String#word_
wrap!
.
1.16. Generowanie nast
ýpnika ĥaħcucha
Problem
NaleĔy wykonaè iteracjö po ciñgu äaþcuchów zwiökszajñcych siö alfabetycznie — w sposób
podobny do iterowania po ciñgu kolejnych liczb.
Rozwi
ézanie
JeĈli znany jest poczñtkowy i koþcowy äaþcuch z zakresu objötego iteracjñ, moĔna do tego za-
kresu (reprezentowanego jako obiekt
Range
) zastosowaè metodö
Range#each
:
('aa'..'ag').each { |x| puts x }
# aa
# ab
# ac
# ad
# ae
# af
# ag
56
_
Rozdzia
ĥ 1. Ĥaħcuchy
Metodñ generujñcñ nastöpnik danego äaþcucha jest
String#succ
. JeĈli nie jest znany äaþcuch,
na którym naleĔy skoþczyè iterowanie, moĔna na bazie tej metody zdefiniowaè iteracjö nie-
skoþczonñ, którñ przerwie siö w momencie speänienia okreĈlonego warunku:
def endless_string_succession(start)
while true
yield start
start = start.succ
end
end
W poniĔszym przykäadzie iteracja jest koþczona w momencie, gdy dwa ostatnie znaki äaþcu-
cha sñ identyczne:
endless_string_succession('fol') do |x|
puts x
break if x[-1] == x[-2]
end
# fol
# fom
# fon
# foo
Dyskusja
WyobraĒmy sobie, Ĕe äaþcuch jest czymĈ na ksztaät (uogólnionego) licznika przejechanych ki-
lometrów — kaĔdy znak äaþcucha jest osobnñ pozycjñ tego licznika. Na kaĔdej z pozycji mo-
gñ pojawiaè siö znaki tylko jednego rodzaju: cyfry, maäe litery albo wielkie litery
3
.
Nastöpnikiem (successor) äaþcucha jest äaþcuch powstajñcy w wyniku zwiökszenia o 1 (inkre-
mentacji) wskazania wspomnianego licznika. Rozpoczynamy od zwiökszenia prawej skrajnej
pozycji; jeĈli spowoduje to jej „przekröcenie” na wartoĈè poczñtkowñ, zwiökszamy o 1 sñ-
siedniñ pozycjö z lewej strony — która teĔ moĔe siö przekröciè, wiöc opisanñ zasadö stosuje-
my rekurencyjnie:
'89999'.succ # => "90000"
'nzzzz'.succ # => "oaaaa"
JeĈli „przekröci” siö skrajna lewa pozycja, doäñczamy z lewej strony äaþcucha nowñ pozycjö
tego samego rodzaju co ona i ustawiamy tö dodanñ pozycjö na wartoĈè poczñtkowñ:
'Zzz'.succ # => "AAaa"
W powyĔszym przykäadzie skrajna lewa pozycja wyĈwietla wielkie litery; jej inkrementacja
powoduje „przekröcenie” z wartoĈci
Z
do wartoĈci
A
, dodajemy wiöc z lewej strony äaþcucha
nowñ pozycjö, takĔe wyĈwietlajñcñ wielkie litery, ustawiajñc jñ na wartoĈè poczñtkowñ
A
.
Oto przykäady inkrementacji äaþcuchów zawierajñcych wyäñcznie maäe litery:
'z'.succ # => "aa"
'aa'.succ # => "ab"
'zz'.succ # => "aaa"
W przypadku wielkich liter sprawa ma siö podobnie — naleĔy pamiötaè, Ĕe wielkie i maäe
litery nigdy nie wystöpujñ razem na tej samej pozycji:
'AA'.succ # => "AB"
'AZ'.succ # => "BA"
3
Ograniczamy siö tylko do liter alfabetu angielskiego
a
..
z
i
A
..
Z
— przyp. täum.
1.16. Generowanie nast
ýpnika ĥaħcucha
_
57
'ZZ'.succ # => "AAA"
'aZ'.succ # => "bA"
'Zz'.succ # => "AAa"
Inkrementowanie cyfr odbywa siö w sposób naturalny — inkrementacja cyfry
9
oznacza jej
„przekröcenie” na wartoĈè
0
:
'foo19'.succ # => "foo20"
'foo99'.succ # => "fop00"
'99'.succ # => "100"
'9Z99'.succ # => "10A00"
Znaki niealfanumeryczne — czyli inne niĔ cyfry, maäe litery i wielkie litery — sñ przy inkre-
mentowaniu äaþcucha ignorowane — wyjñtkiem jest jednak sytuacja, gdy äaþcuch skäada siö wy-
äñcznie ze znaków tej kategorii. UmoĔliwia to inkrementowanie äaþcuchów sformatowanych:
'10-99'.succ # => "11-00"
JeĈli äaþcuch skäada siö wyäñcznie ze znaków niealfanumerycznych, jego pozycje inkremento-
wane sñ zgodnie z uporzñdkowaniem znaków w kodzie ASCII; oczywiĈcie w wyniku inkre-
mentacji mogñ pojawiè siö w äaþcuchu znaki alfanumeryczne, wówczas kolejna jego inkremen-
tacja odbywa siö wedäug reguä wczeĈniej opisanych.
'a-a'.succ # => "a-b"
'z-z'.succ # => "aa-a"
'Hello!'.succ # => "Hellp!"
%q{'zz'}.succ # => "'aaa'"
%q{z'zz'}.succ # => "aa'aa'"
'$$$$'.succ # => "$$$%"
s = '!@-'
13.times { puts s = s.succ }
# !@.
# !@/
# !@0
# !@1
# !@2
# ...
# !@8
# !@9
# !@10
Nie istnieje metoda realizujñca funkcjö odwrotnñ do metody
String#succ
. Zarówno twórca
jözyka Ruby, jak i caäa wspólnota jego uĔytkowników zgodni sñ co do tego, Ĕe wobec ogra-
niczonego zapotrzebowania na takñ metodö nie warto wkäadaè wysiäku w jej tworzenie,
a zwäaszcza poprawnñ obsäugö róĔnych warunków granicznych. Iterowanie po zakresie äaþ-
cuchów w kierunku malejñcym najlepiej jest wykonywaè, transformujñc ów zakres na tablicö
i organizujñc iteracjö po tejĔe w kierunku malejñcych indeksów:
("a".."e").to_a.reverse_each { |x| puts x }
# e
# d
# c
# b
# a
Patrz tak
że
x
Receptura 2.15, „Generowanie sekwencji liczb”.
x
Receptura 3.4, „Iterowanie po datach”.
58
_
Rozdzia
ĥ 1. Ĥaħcuchy
1.17. Dopasowywanie
ĥaħcuchów
za pomoc
é wyrażeħ regularnych
Problem
Chcemy sprawdziè, czy dany äaþcuch zgodny jest z pewnym wzorcem.
Rozwi
ézanie
Wzorce sñ zwykle definiowane za pomocñ wyraĔeþ regularnych. ZgodnoĈè („pasowanie”)
äaþcucha z wyraĔeniem regularnym testowane jest przez operator
=~
.
string = 'To jest
ĪaĬcuch 27-znakowy.'
if string =~ /([0-9]+)-character/ and $1.to_i == string.length
"Tak, to jest
ĪaĬcuch #$1-znakowy."
end
# "Tak, to jest
ĪaĬcuch 27-znakowy."
MoĔna takĔe uĔyè metody
Regexp#match
:
match = Regexp.compile('([0-9]+)-znakowy').match(string)
if match && match[1].to_i == string.length
"Tak, to jest
ĪaĬcuch #{match[1]}-znakowy."
end
# "Tak, to jest
ĪaĬcuch 27-znakowy."
Za pomocñ instrukcji
case
moĔna sprawdziè zgodnoĈè äaþcucha z caäym ciñgiem wyraĔeþ
regularnych:
string = "123"
case string
when /^[a-zA-Z]+$/
"Litery"
when /^[0-9]+$/
"Cyfry"
else
"Zawarto
Łð mieszana"
end
# => "Cyfry"
Dyskusja
WyraĔenia regularne stanowiñ maäo czytelny, lecz uĔyteczny minijözyk umoĔliwiajñcy dopaso-
wywanie äaþcuchów do wzorców oraz ekstrakcjö podäaþcuchów. WyraĔenia regularne wykorzy-
stywane sñ od dawna przez wiele narzödzi uniksowych (jak
sed
), lecz to Perl byä pierwszym
uniwersalnym jözykiem zapewniajñcym ich obsäugö. Obecnie wyraĔenia regularne w stylu
zbliĔonym do wersji z Perla obecne sñ w wiökszoĈci nowoczesnych jözyków programowania.
W jözyku Ruby wyraĔenia regularne inicjowaè moĔna na wiele sposobów. KaĔda z poniĔszych
konstrukcji daje w rezultacie taki sam obiekt klasy
Regexp
:
/cokolwiek/
Regexp.new("cokolwiek")
Regexp.compile("cokolwiek")
%r{ cokolwiek}
1.17. Dopasowywanie
ĥaħcuchów za pomocé wyrażeħ regularnych
_
59
W wyraĔeniach regularnych moĔna uĔywaè nastöpujñcych modyfikatorów:
Regexp::IGNORECASE
i
Przy dopasowywaniu nieistotna jest wielko
ļë liter — maĥe litery utożsamiane sé z ich
wielkimi odpowiednikami.
Regexp:MULTILINE
m
Domy
ļlnie dopasowywanie realizowane jest w odniesieniu do ĥaħcucha mieszczécego
si
ý w jednym wierszu. Gdy użyty zostanie ten modyfikator, znaki nowego wiersza
traktowane s
é na równi z innymi znakami ĥaħcucha.
Regexp::EXTENDED
x
U
życie tego modyfikatora daje możliwoļë bardziej czytelnego zapisu wyrażenia
regularnego, przez wype
ĥnienie go biaĥymi znakami i komentarzami.
Oto przykäad wykorzystania wymienionych powyĔej modyfikatorów w definicji wyraĔenia
regularnego:
/something/mxi
Regexp.new('something',
Regexp::EXTENDED + Regexp::IGNORECASE + Regexp::MULTILINE)
%r{something}mxi
A oto efekt dziaäania tychĔe modyfikatorów:
case_insensitive = /mangy/i
case_insensitive =~ "I'm mangy!" # => 4
case_insensitive =~ "Mangy Jones, at your service." # => 0
multiline = /a.b/m
multiline =~ "banana\nbanana" # => 5
/a.b/ =~ "banana\nbanana" # => nil
# Ale zwróc uwag
Ă na to:
/a\nb/ =~ "banana\nbanana" # => 5
extended = %r{ \ was # Dopasowano " was"
\s # Dopasowano jeden bia
Īy znak
a # Dopasowano "a" }xi
extended =~ "What was Alfred doing here?" # => 4
extended =~ "My, that was a yummy mango." # => 8
extended =~ "It was\n\n\na fool's errand" # => nil
Patrz tak
że
x
KsiñĔka Jeffreya Friedla Mastering Regular Expressions
4
dostarcza eleganckiego i zwiözäe-
go wprowadzenia w tematykö wyraĔeþ regularnych, ilustrowanego wieloma praktyczny-
mi przykäadami.
x
Witryna RegExLib.com (http://regexlib.com/default.aspx) jest obszernñ bazñ wyraĔeþ regu-
larnych, wyposaĔonñ w wyszukiwarkö.
x
Przewodnik po wyraĔeniach regularnych i ich wykorzystywaniu w jözyku Ruby dostöp-
ny jest pod adresem http://www.regular-expressions.info/ruby.html.
x
Informacje na temat klasy
Regexp
moĔesz uzyskaè za pomocñ polecenia
ri Regexp
.
x
Receptura 1.19, „Weryfikacja poprawnoĈci adresów e-mailowych”.
4
Wydanie polskie: WyraĔenia regularne, wyd. Helion 2001 (http://helion.pl/ksiazki/wyrare.htm) — przyp. täum.
60
_
Rozdzia
ĥ 1. Ĥaħcuchy
1.18. Zast
ýpowanie wielu wzorców
w pojedynczym przebiegu
Problem
Chcemy wykonaè kilka operacji typu „znajdĒ i zamieþ”, sterowanych oddzielnymi wyraĔe-
niami regularnymi — równolegle, w pojedynczym przejĈciu przez äaþcuch.
Rozwi
ézanie
Musimy uĔyè metody
Regexp.union
do zagregowania poszczególnych wyraĔeþ regularnych
w pojedyncze wyraĔenie, pasujñce do kaĔdego z wyraĔeþ czñstkowych. Zagregowane wyra-
Ĕenie musimy nastöpnie przekazaè jako parametr metody
String#gsub
wraz z blokiem kodo-
wym bazujñcym na obiekcie
MatchData
. Wiedzñc, do którego z wyraĔeþ czñstkowych przy-
porzñdkowaè moĔna znalezionñ frazö, moĔemy wybraè odpowiedniñ frazö zastöpujñcñ:
class String
def mgsub(key_value_pairs=[].freeze)
regexp_fragments = key_value_pairs.collect { |k,v| k }
gsub(Regexp.union(*regexp_fragments)) do |match|
key_value_pairs.detect{|k,v| k =~ match}[1]
end
end
end
Oto prosty przykäad uĔycia metody
mgsub
:
"GO HOME!".mgsub([[/.*GO/i, 'Home'], [/home/i, 'is where the heart is']])
# => "Home is where the heart is!"
W powyĔszym przykäadzie Ĕñdamy zamiany dowolnego ciñgu koþczñcego siö na
GO
(bez
wzglödu na wielkoĈè liter) na ciñg
Home
, zaĈ ciñgu
Home
(bez wzglödu na wielkoĈè liter) na
ciñg
is where the heart is
.
W poniĔszym przykäadzie zamieniamy wszystkie litery na znak #, a kaĔdy znak
#
na literö
P
:
"To jest liczba #123".mgsub([[/[a-z]/i, '#'], [/#/, 'P']])
# => "#### ## ###### P123"
Dyskusja
Wydawaäoby siö, Ĕe naiwne podejĈcie polegajñce na sukcesywnym wywoäaniu metody
gsub
dla kaĔdej operacji „znajdĒ i zamieþ” da identyczny efekt i tylko efektywnoĈciñ ustöpowaè
bödzie rozwiñzaniu wyĔej opisanemu. Jest jednak inaczej, o czym moĔemy siö przekonaè, spo-
glñdajñc na poniĔsze przykäady:
"GO HOME!".gsub(/.*GO/i, 'Home').gsub(/home/i, 'is where the heart is')
# => "is where the heart is is where the heart is!"
"To jest liczba #123".gsub(/[a-z]/i, '#').gsub(/#/, 'P')
# => "PP PPPP PPPPPP P123"
Przyczyna rozbieĔnoĈci z rozwiñzaniem „równolegäym” nie jest Ĕadnñ tajemnicñ: otóĔ w oby-
dwu przypadkach materiaäem wejĈciowym dla drugiego wywoäania metody
gsub
jest wynik
1.19. Weryfikacja poprawno
ļci adresów e-mailowych
_
61
jej pierwszego wywo
äania. W wariancie równolegäym natomiast obydwa wywoäania metody
gsub
operujñ na äaþcuchu oryginalnym. W pierwszym przypadku moĔna zniwelowaè owñ in-
terferencjö, zamieniajñc kolejnoĈè operacji, w drugim jednak nawet i to nie pomoĔe.
Do metody
mgsub
moĔna przekazaè takĔe hasz, w którym poszukiwane frazy sñ kluczami,
a frazy zastöpujñce — wartoĈciami. Nie jest to jednak rozwiñzanie bezpieczne, bowiem ele-
menty hasza sñ z natury nieuporzñdkowane i w zwiñzku z tym kolejnoĈè zastöpowania fraz
wymyka siö spod kontroli. Znacznie lepszym wyjĈciem byäoby uĔycie tablicy elementów ty-
pu „klucz-wartoĈè”. PoniĔszy przykäad z pewnoĈciñ uäatwi zrozumienie tego problemu:
"between".mgsub(/ee/ => 'AA', /e/ => 'E') # Z
Īy kod
# => "bEtwEEn"
"between".mgsub([[/ee/, 'AA'], [/e/, 'E']]) # Dobry kod
# => "bEtwAAn"
W drugim przypadku najpierw wykonywane jest pierwsze zastöpowanie. W pierwszym przy-
padku jest ono wykonywane jako drugie i szukana fraza nie zostaje znaleziona — to jedna
z osobliwoĈci implementacji haszów w jözyku Ruby.
JeĈli efektywnoĈè programu jest czynnikiem krytycznym, naleĔy zastanowiè siö nad innñ im-
plementacjñ metody
mgsub
. Im wiöcej bowiem fraz do znalezienia i zastñpienia, tym däuĔej
trwaè bödzie caäa operacja, poniewaĔ metoda
detect
wykonuje sprawdzenie dla kaĔdego wy-
raĔenia regularnego i dla kaĔdej znalezionej frazy.
Patrz tak
że
x
Receptura 1.17, „Dopasowywanie äaþcuchów za pomocñ wyraĔeþ regularnych”.
x
Czytelnikom, którym zagadkowa wydaje siö skäadnia
Regexp.union(*regexp_fragments)
,
polecamy przestudiowanie receptury 8.11, „Metody wywoäywane ze zmiennñ liczbñ ar-
gumentów”.
1.19. Weryfikacja poprawno
ļci adresów e-mailowych
Problem
Chcemy sprawdziè, czy podany adres e-mailowy jest poprawny.
Rozwi
ézanie
Oto kilka przykäadowych adresów e-mail — poprawnych
test_addresses = [ # Poni
šsze adresy czyniî zadoŁð specyfikacji RFC822.
'joe@example.com', 'joe.bloggs@mail.example.com',
'joe+ruby-mail@example.com', 'joe(and-mary)@example.museum',
'joe@localhost',
i niepoprawnych
# Poni
šsze adresy sî niezgodne ze specyfikacjî RFC822
'joe', 'joe@', '@example.com',
'joe@example@example.com',
'joe and mary@example.com' ]
62
_
Rozdzia
ĥ 1. Ĥaħcuchy
Oto kilka przykäadowych wyraĔeþ regularnych filtrujñcych bäödne adresy e-mailowe. Pierw-
sze z nich ogranicza siö do bardzo elementarnej kontroli.
valid = '[^ @]+' # Wyeliminowanie znaków bezwzgl
Ădnie niedopuszczalnych w adresie e-mail
username_and_machine = /^#{valid}@#{valid}$/
test_addresses.collect { |i| i =~ username_and_machine }
# => [0, 0, 0, 0, 0, nil, nil, nil, nil, nil]
Drugie z wyraĔeþ eliminuje adresy typowe dla sieci lokalnej, w rodzaju
joe@localhost
—
wiökszoĈè aplikacji nie zezwala na ich uĔywanie.
username_and_machine_with_tld = /^#{valid}@#{valid}\.#{valid}$/
test_addresses.collect { |i| i =~ username_and_machine_with_tld }
# => [0, 0, 0, 0, nil, nil, nil, nil, nil, nil]
Niestety, jak za chwilö zobaczymy, prawdopodobnie poszukujemy rozwiñzania nie tego pro-
blemu.
Dyskusja
WiökszoĈè systemów weryfikacji adresów e-mailowych opiera swe funkcjonowanie na naiw-
nych wyraĔeniach regularnych, podobnych do prezentowanych powyĔej. Niestety, wyraĔe-
nia takie bywajñ czösto zbyt rygorystyczne, wskutek czego zdarza siö, Ĕe poprawny adres zo-
staje odrzucony. Jest to powszechna przyczyna frustracji uĔytkowników posäugujñcych siö
nietypowymi adresami w rodzaju joe(and-mary)@example.museum oraz uĔytkowników wyko-
rzystujñcych w swych adresach specyficzne cechy systemu e-mail (joe+ruby-mail@example.com).
Prezentowane powyĔej wyraĔenia regularne cierpiñ na dokäadnie odwrotnñ przypadäoĈè —
nie kwestionujñc nigdy adresów poprawnych, akceptujñ niektóre niepoprawne.
Dlaczego wiöc nie stworzyè (publicznie znanego) wyraĔenia regularnego, które z zadaniem
weryfikacji adresów e-mailowych poradzi sobie zawsze? OtóĔ dlatego, Ĕe byè moĔe wyraĔe-
nie takie wcale nie istnieje — definicji skäadni adresu e-mailowego zarzuciè moĔna wszystko,
tylko nie prostotö. Haker jözyka Perl, Paul Warren, w stworzonym przez siebie module
::RFC822:Address
zdefiniowaä wyraĔenie regularne skäadajñce siö z 6343 znaków, lecz na-
wet ono wymaga przetwarzania wstöpnego dla absolutnie (w zamierzeniu) bezbäödnej wery-
fikacji adresu. WyraĔenia tego moĔna uĔyè bez zmian w jözyku Ruby — zainteresowani Czy-
telnicy mogñ znaleĒè je w katalogu
Mail-RFC822-Address-0.3
na CD-ROM-ie doäñczonym
do niniejszej ksiñĔki.
Weryfikuj prawdziwo
ļë, nie poprawnoļë
Jednak najbardziej nawet wyszukane wyraĔenie regularne nie potrafi zapewniè nic wiöcej niĔ
tylko weryfikacjö skäadniowej poprawnoĈci adresu. PoprawnoĈè skäadniowa nie oznacza wcale,
Ĕe dany adres jest istniejñcym adresem.
Przy wpisywaniu adresu äatwo moĔna siö pomyliè, wskutek czego poprawny adres zamienia
siö w (takĔe poprawny) adres kogo innego (joe@example.com). Adres !@ jest skäadniowo popraw-
ny, lecz nikt na Ĉwiecie go nie uĔywa. Nawet zbiór domen najwyĔszego poziomu (top-level
domains) teĔ nie jest ustalony i jako taki nie moĔe byè przedmiotem weryfikacji w oparciu o sta-
tycznñ listö. Reasumujñc — weryfikacja poprawnoĈci skäadniowej adresu e-mail jest tylko ma-
äñ czöĈciñ rozwiñzania rzeczywistego problemu.
1.19. Weryfikacja poprawno
ļci adresów e-mailowych
_
63
Jedynym sposobem stwierdzenia poprawnoĈci adresu jest udane wysäanie listu na ów adres.
O tym, czy adres ten jest wäaĈciwy, moĔemy przekonaè siö dopiero po otrzymaniu odpowie-
dzi od adresata. Jak widaè, nietrudny na pozór problem wymaga wcale niemaäo zachodu przy
tworzeniu aplikacji.
Nie tak dawno jeszcze adres e-mailowy uĔytkownika zwiñzany byä nierozerwalnie w jego
toĔsamoĈciñ w sieci, bo przydzielany byä przez dostawcö internetowego (ISP). Przestaäo tak
byè w dobie poczty webowej, gdzie kaĔdy uĔytkownik moĔe sobie przydzieliè tyle adresów,
ile tylko zechce. W efekcie weryfikacja poprawnoĈci adresów nie jest w stanie zapobiec ani
dublowaniu kont, ani teĔ antyspoäecznym zachowaniom w sieci (i wñtpliwe jest, czy kiedy-
kolwiek mogäa).
Nie oznacza to bynajmniej, Ĕe weryfikacja skäadni adresu e-mailowego jest caäkowicie bezu-
Ĕyteczna, albo Ĕe nie jest problemem niezamierzone znieksztaäcenie wpisywanego adresu
(„literówka”). Aby usprawniè pracö uĔytkownika aplikacji wpisujñcego adres e-mailowy, bez
obawy o kwestionowanie poprawnych adresów, moĔesz zrobiè trzy nastöpujñce rzeczy oprócz
weryfikacji adresu w oparciu o prezentowane wcze
Ĉniej wyraĔenia regularne:
1.
UĔyj drugiego, naiwnego i bardziej restrykcyjnego wyraĔenia regularnego, lecz w przypad-
ku stwierdzenia niepoprawnoĈci adresu ogranicz siö do wypisania komunikatu ostrze-
gawczego, nie blokujñc uĔytkownikowi moĔliwoĈci uĔycia tego adresu. Nie jest to tak
uĔyteczne, jak mogäoby siö wydawaè, bo adres bödñcy wynikiem pomyäki literowej jest
czösto takĔe adresem poprawnym skäadniowo (po prostu jedna litera zamieniona zostaje
na innñ).
def probably_valid?(email)
valid = '[A-Za-z\d.+-]+' # Znaki powszechnie spotykane w adresach
(email =~ /#{valid}@#{valid}\.#{valid}/) == 0
end
# Wyniki weryfikacji zgodne z oczekiwaniami
probably_valid? 'joe@example.com' # => true
probably_valid? 'joe+ruby-mail@example.com' # => true
probably_valid? 'joe.bloggs@mail.example.com' # => true
probably_valid? 'joe@examplecom' # => false
probably_valid? 'joe+ruby-mail@example.com' # => true
probably_valid? 'joe@localhost' # => false
# Adres poprawny, lecz kwestionowany przez metod
Ă probably_valid?
probably_valid? 'joe(and-mary)@example.museum' # => false
# Adres sk
Īadniowo poprawny, lecz ewidentnie bĪĂdny
probably_valid? 'joe@example.cpm' # => true
2.
Wydziel adres serwera z adresu e-mailowego (np. example.com) i sprawdĒ (za pomocñ
DNS), czy serwer ten zapewnia obsäugö poczty (tzn. czy da siö z niego odczytaè rekord
MX DNS). PoniĔszy fragment kodu zdolny jest wychwyciè wiökszoĈè pomyäek w zapisie
adresu serwera, co jednak nie chroni przed podaniem nazwy nieistniejñcego uĔytkowni-
ka. Ponadto ze wzglödu na zäoĔonoĈè samego dokumentu RFC822 nie moĔna zagwaran-
towaè, Ĕe analiza adresu serwera zawsze bödzie przeprowadzona bezbäödnie:
require 'resolv'
def valid_email_host?(email)
hostname = email[(email =~ /@/)+1..email.length]
valid = true
64
_
Rozdzia
ĥ 1. Ĥaħcuchy
begin
Resolv::DNS.new.getresource(hostname, Resolv::DNS::Resource::IN::MX)
rescue Resolv::ResolvError
valid = false
end
return valid
end
# example.com jest adresem rzeczywistej domeny, lecz jej serwer
# nie obs
Īuguje poczty.
valid_email_host?('joe@example.com') # => false
# lcqkxjvoem.mil nie jest adresem istniej
îcej domeny.
valid_email_host?('joe@lcqkxjvoem.mil') # => false
# domena oreilly.com istnieje i jej serwer zapewnia obs
ĪugĂ poczty, jednakše
# uzytkownik 'joe' mo
še nie byð zdefiniowany na tym serwerze.
valid_email_host?('joe@oreilly.com') # => true
3.
WyĈlij list na adres wpisany przez uĔytkownika aplikacji, z proĈbñ do adresata o potwier-
dzenie poprawnoĈci adresu. Aby uäatwiè adresatowi zadanie, moĔna w treĈci listu umie-
Ĉciè stosowny URL (gotowy do klikniöcia) z odpowiednim komentarzem. Jest to jedyny
sposób upewnienia siö, Ĕe uĔyto wäaĈciwego adresu. Powrócimy do tej kwestii w recep-
turach 14.5 i 15.19.
Mimo iĔ rozwiñzanie to stanowczo podnosi poprzeczkö wymagaþ wobec programisty
tworzñcego aplikacjö, moĔe okazaè siö nieskuteczne z bardzo prostej przyczyny — roz-
maitych sposobów walki z niechcianñ pocztñ. UĔytkownik moĔe zdefiniowaè filtr, który
zaklasyfikuje wspomnianñ wiadomoĈè jako niechcianñ (junk), bñdĒ teĔ generalnie odrzu-
caè wszelkñ pocztö pochodzñcñ z nieznanego Ēródäa. JeĔeli jednak weryfikacja adresów
e-mail nie jest dla aplikacji zagadnieniem krytycznym, opisywane sposoby tej weryfikacji
powinny okazaè siö wystarczajñce.
Patrz tak
że
x
Receptura 14.5, „Wysyäanie poczty elektronicznej”.
x
Receptura 15.19, „Przesyäanie wiadomoĈci pocztowych za pomocñ aplikacji Rails”.
x
Wspomniane wczeĈniej kolosalne wyraĔenie regularne autorstwa Paula Warrena dostöp-
ne jest do pobrania pod adresem http://search.cpan.org/~pdwarren/Mail-RFC822-Address-0.3/
Address.pm.
1.20. Klasyfikacja tekstu
za pomoc
é analizatora bayesowskiego
Problem
Majñc dany fragment tekstu, chcemy dokonaè jego klasyfikacji — na przykäad zdecydowaè,
czy otrzymany list moĔna potraktowaè jako spam, bñdĒ czy zawarty w liĈcie dowcip jest na-
prawdö Ĉmieszny.