Ruby. Receptury

Ruby. Receptury

-

Livres
888 pages

Description

<div align="center"><h4><b> Zbiór gotowych rozwišza? dla programistów u?ywajšcych j?zyka Ruby </b></h4></div> <ul><li> Jak przetwarza? pliki XML i HTML? </li><li> Jak wykorzystywa? ?rodowisko Ruby on Rails? </li><li> W jaki sposób ?šczy? Ruby z technologiš AJAX? </li></ul><p> 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. </p><p> 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.<ul><li> Przetwarzanie danych tekstowych i liczbowych </li><li> Operacje na tablicach </li><li> Praca z systemem plików </li><li> Programowanie obiektowe </li><li> Przetwarzanie dokumentów XML i HTML oraz plików graficznych </li><li> Generowanie plików PDF </li><li> Po?šczenie z bazami danych </li><li> Korzystanie z poczty elektronicznej, protoko?u telnet i po?šcze? Torrent </li><li> Projektowanie aplikacji internetowych za pomocš Ruby on Rails </li><li> Stosowanie us?ug sieciowych </li><li> Optymalizacja aplikacji </li><li> Tworzenie wersji dystrybucyjnych </li><li> Automatyzacja zada? z wykorzystaniem j?zyka Rake </li><li> Budowanie interfejsów u?ytkownika </li></ul><div align="center"><h4><b> Je?li chcesz rozwišza? problem, skorzystaj z gotowej receptury <br />-- ko?o ju? wynaleziono.</b></h4></div>


Sujets

Informations

Publié par
Ajouté le 26 novembre 2012
Nombre de lectures 24
EAN13 9781457177446
Langue Polish
Signaler un problème

Tytu ł orygina łu: Ruby Cookbook
T łumaczenie: Andrzej Gra ży ński (wst ęp, rozdz. 1 – 11),
Rados ław Meryk (rozdz. 12 – 23)
ISBN: 978-83-246-6173-2
© Helion S.A. 2007
Authorized translation of the English edition of Ruby Cookbook © 2006 O’Reilly Media, Inc.
This translation is published and sold by permission of O’Reilly Media, Inc., the owner of all
rights to publish and sell the same.
Polish language edition published by Helion S.A.
Copyright © 2007
All rights reserved. No part of this book may be reproduced or transmitted in any form or by
any means, electronic or mechanical, including photocopying, recording or by any information
storage retrieval system, without permission from the Publisher.
Wszelkie prawa zastrze żone. Nieautoryzowane rozpowszechnianie ca ło ści lub fragmentu
niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metod ą
kserograficzn ą, fotograficzn ą, a tak że kopiowanie książki na no śniku filmowym,
magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji.
Wszystkie znaki wyst ępuj ące w tek ście s ą zastrze żonymi znakami firmowymi
b ąd ź towarowymi ich w ła ścicieli.
Autor oraz Wydawnictwo HELION do ło żyli wszelkich stara ń, by zawarte w tej ksi ążce
informacje by ły kompletne i rzetelne. Nie bior ą jednak żadnej odpowiedzialno ści ani za ich
wykorzystanie, ani za zwi ązane z tym ewentualne naruszenie praw patentowych lub
autorskich. Autor oraz Wydawnictwo HELION nie ponosz ą równie ż żadnej odpowiedzialno ści
za ewentualne szkody wynik łe z wykorzystania informacji zawartych w ksi ążce.
Wydawnictwo HELION
ul. Ko ściuszki 1c, 44-100 GLIWICE
tel. 032 231 22 19, 032 230 98 63
e-mail: helion@helion.pl
WWW: http://helion.pl (ksi ęgarnia internetowa, katalog ksi ążek)
Drogi Czytelniku!
Je żeli chcesz oceni ć t ę ksi ążk ę, zajrzyj pod adres
http://helion.pl/user/opinie?rubyre_ebook
Mo żesz tam wpisa ć swoje uwagi, spostrze żenia, recenzj ę.
Printed in Poland.
• Poleć książkę na Facebook.com • Księgarnia internetowa
• Kup w wersji papierowej • Lubię to! » Nasza społeczność
• Oceń książkęDla Tess, która by ła przy mnie przez ca ły czas.
Dla Johna i Raela — najlepszych programistów, jakich znam.
— Lucas Carlson
Dla Sumany.
— Leonard RichardsonSpis 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
52.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
6 | Spis tre ści4.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
Spis tre ści | 76.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
8 | Spis tre ści8.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
Spis tre ści | 911.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
10 | Spis tre ści13.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 śunikacja z baz ą danych PostgreSQL 491
13.11. Mapowanie obiektowo-relacyjne z wykorzystaniem
biblioteki ActiveRecord 493
13.12. Mapowalacyjne 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
Spis tre ści | 1115. 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
12 | Spis tre ści16.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 ą 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
Spis tre ści | 1319. 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. Pobie ś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 apliwykorzystaniem biblioteki wxRuby 798
14 | Spis tre ści21.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
Spis tre ści | 1516 | Spis tre ściWprowadzenie
Życie jest krótkie
Niniejsza ksi ążka sk łada si ę z receptur — rozwi ąza ń powszechnie spotykanych problemów,
fragmentów kodu gotowych do skopiowania, obja śnie ń, przyk ładów i krótkich przewodników.
Pisz ąc t ę ksi ążk ę, zamierzali śmy zaoszcz ędzi ć czas naszym Czytelnikom. Czas to pieni ądz,
ale przedzia ł czasu to tak że fragment naszego życia. O wiele sensowniej jest prze żywać życie,
tworz ąc nowe, wspania łe rzeczy, ni ż zmagaj ąc si ę z konsekwencjami w łasnych b łędów b ąd ź
próbuj ąc rozwi ązywa ć problemy, które rozwi ązane zosta ły ju ż dawno. Miejmy nadziej ę, że
skumulowana oszcz ędno ść czasu wszystkich naszych Czytelników z nawi ązką zrekompensu-
je czas, jaki po świ ęcili śmy na napisanie ksi ążki.
J ęzyk Ruby sam w sobie jest wspania łym narz ędziem pozwalaj ącym zaoszcz ędzi ć du żo czasu.
Jest bardziej produktywny ni ż inne j ęzyki programowania, programista mo że bowiem wi ęcej
czasu przeznaczy ć na zmuszenie komputera do okre ślonego dzia łania ni ż na zmaganie si ę
z zawi ło ściami samego j ęzyka. Niestety, żaden j ęzyk programowania — nawet Ruby — nie
jest w stanie ca łkowicie uwolni ć programisty od rozmaitych czynno ści maj ących ma ło wspól-
nego z twórczym działaniem, w szczególno ści od:
• tworzenia implementacji powszechnie znanych i zaimplementowanych ju ż algorytmów,
• debugowania implementacji,
• odkrywania specyficznych elementów i osobliwo ści j ęzyka Ruby oraz radzenia sobie z ich
konsekwencjami,
• wielokrotnego powtarzania tych samych zada ń (tak że programistycznych!), łatwego do
unikni ęcia dzi ęki automatyzacji,
• ponownego wykonywania pracy wykonanej ju ż przez kogo ś innego, gdy rezultaty tej że
są publicznie dost ępne,
• poszukiwania biblioteki umo żliwiaj ącej rozwi ązanie problemu X,
• wyboru jednej spo śród dost ępnych bibliotek rozwi ązuj ących problem X,
• empirycznego dochodzenia do sposobu korzystania z okre ślonej biblioteki — na skutek
b łędnej, niekompletnej lub przestarza łej dokumentacji,
• skutków unikania nowoczesnych technologii, gdy te wydaj ą si ę niezrozumia łe lub prze-
rażaj ące.
17Doskonale pami ętamy, ile w łasnego czasu stracili śmy w taki w łaśnie sposób. Tego przykrego
do świadczenia chcieliby śmy oszcz ędzi ć naszym Czytelnikom, a przynajmniej sprawi ć, by swój
czas po świ ęcali oni problemom rzeczywi ście interesuj ącym.
Bo rozwijanie zainteresowa ń Czytelników jest drugim z celów, jakie przy świecały nam przy
pisaniu tej ksi ążki. Je żeli na przyk ład si ęgniesz po t ę ksi ążk ę z zamiarem nauczenia si ę, jak
w j ęzyku Ruby tworzy ć muzyk ę algorytmiczn ą, znacznie zwi ększysz swe szanse, studiuj ąc
Receptur ę 12.14 — mowa oczywi ście o oszcz ędno ści czasu w porównaniu z programowaniem
wszystkiego „od zera”. Generalnie obydwa wymienione cele — zaoszcz ędzenie czasu i rozwi-
janie zainteresowa ń — legły u podstaw wszystkich Receptur zawartych w niniejszej ksi ążce.
Do kogo ksi ążka jest adresowana?
Niniejsz ą książkę polecamy tym, którzy maj ą cho ć elementarne poj ęcie o j ęzyku Ruby lub po-
siadaj ą pewną doz ę do świadczenia w programowaniu w ogóle. Ksi ążka ta nie jest przewodni-
kiem po j ęzyku Ruby (kilka prawdziwych przewodników tego rodzaju wymienili śmy w sekcji
„Inne zasoby”), jednak Czytelnicy maj ący do świadczenie w programowaniu w innych j ęzy-
kach z pewno ści ą naucz ą si ę j ęzyka Ruby po przeczytaniu pierwszych 10 rozdzia łów i prze-
studiowaniu zamieszczonych w nich fragmentów przyk ładowego kodu.
Starali śmy si ę dobra ć tre ść ksi ążki tak, by sta ła się ona u żyteczna dla Czytelników o ró żnym
stopniu zaawansowania — od tych stawiaj ących w j ęzyku Ruby pierwsze kroki do ekspertów
szukaj ących jeszcze jednej pozycji w ramach literatury uzupe łniaj ącej. Koncentrowali śmy si ę
głównie na rodzimych technikach programistycznych, cho ć nie stronili śmy te ż od mechani-
zmów specyficznych (jak Ruby on Rails i biblioteki GUI) oraz dobrych praktyk „oko łoprogra-
mistycznych” (jak testowanie modu łów).
Nie polecamy wykorzystywania niniejszej ksi ążki wy łącznie jako materia łu referencyjnego;
je żeli masz taki zamiar, proponujemy przynajmniej pobie żne przejrzenie ca łej tre ści, gdy ż
pozwoli Ci to zorientowa ć si ę w repertuarze prezentowanych przez nas problemów i ich roz-
wi ązań. Jest to co prawda repertuar do ść obszerny, niemniej jednak nie sposób by ło zawrze ć
w nim wszystkich mo żliwych problemów. Wyrywkowe wertowanie ksi ążki bez szansy na zna-
lezienie upragnionego rozwi ązania czy cho ćby tylko u żytecznej wskazówki by łoby po prostu
strat ą czasu — a tego w łaśnie chcemy Ci oszcz ędzi ć.
Gdy natomiast zapoznasz si ę z tre ści ą w ca ło ści, łatwiej b ędziesz móg ł oceni ć, czy przypad-
kiem nie jest konieczne si ęgni ęcie do innych źróde ł — innych książek, internetu lub mo że wie-
dzy i do świadczenia Twoich znajomych.
Uk ład książki
Tre ść każdego z 23 rozdzia łów niniejszej książki koncentruje si ę na programowaniu zwi ąza-
nym z pewnym szczególnym typem danych. Poni żej prezentujemy ogólnie tematyk ę poszcze-
gólnych rozdzia łów, ponadto ka żdy rozdzia ł z osobna rozpoczyna si ę wst ępem, w którym
mi ędzy innymi omawiamy szczegó łowo charakter prezentowanych Receptur. Jako niezb ędne
minimum zalecamy przeczytanie co najmniej wspomnianych wst ępów oraz spisu tre ści.
Sze ść pierwszych rozdzia łów ksi ążki po świ ęconych jest wbudowanym strukturom danych
j ęzyka Ruby.
18 | Wprowadzenie• Rozdzia ł 1., „ Łańcuchy”, po świ ęcony jest łańcuchom tekstowym — ich budowaniu, prze-
twarzaniu i zarz ądzaniu nimi. Niektóre z Receptur rozdzia łu (1.17 – 1.19) zwi ązane s ą
z wyrażeniami regularnymi, jednak poniewa ż ksi ążka jako taka koncentruje si ę na specy-
fice samego j ęzyka Ruby, Czytelników zainteresowanych szczegó łami wyra że ń regular-
nych (jako mechanizmu na swój sposób ogólnego) odsy łamy do przewodnika Mastering
Regular Expressions autorstwa Jeffreya Friedla (który ukaza ł si ę nak ładem wydawnictwa
1O’Reilly) .
• W rozdziale 2., „Liczby”, omawiamy reprezentacje liczb ró żnych typów — rzeczywistych,
zespolonych oraz liczb dziesi ętnych o dowolnej precyzji. Prezentujemy tak że implemen-
tacj ę wybranych algorytmów matematycznych i statystycznych oraz omawiamy pewne
aspekty tworzenia w łasnych typów numerycznych w j ęzyku Ruby (Receptury 2.13 i 2.14).
• Tre ść rozdzia łu 3., „Data i czas”, koncentruje si ę wokó ł dwóch interfejsów j ęzyka Ruby
zwi ązanych z dat ą i czasem. Pierwszy z tych interfejsów oparty jest na bibliotece j ęzyka
C i jako taki powinien by ć znany wi ększo ści programistów, drugi, bazuj ący na czystym
j ęzyku Ruby, ma charakter bardziej idiomatyczny.
• Rozdzia ł 4., „Tablice”, po świ ęcony jest najprostszej strukturze z ło żonej j ęzyka Ruby. Wie-
le z metod tablic to metody modu łu Enumerable, dzi ęki czemu wiele z Receptur mo że
być stosowanych tak że w odniesieniu do innych typów danych, m.in. haszów. Niektóre
z cech modu łu Enumerable są przedmiotem Receptur 4.4 i 4.6, niektóre omawiane s ą tak-
że w rozdziale 7.
• Innej z ło żonej strukturze j ęzyka Ruby po świ ęcony jest rozdzia ł 5., „Hasze”. Hasze u ła-
twiaj ą kojarzenie nazw z obiektami i pó źniejsze wyszukiwanie obiektów na podstawie
tych że nazw. Hasze nazywane bywaj ą cz ęsto „tablicami przegl ądowymi” oraz „s łowni-
kami”, co jednak nie jest w pe łni poprawne. W połączeniu z tablicami hasze umo żliwiaj ą
łatwe budowanie skomplikowanych struktur danych.
• Przedmiotem rozwa ża ń rozdzia łu 6., „Pliki i katalogi”, jest odczytywanie, zapisywanie
i manipulowanie plikami i katalogami. Interfejs dost ępu do plików j ęzyka Ruby oparty
jest na (powszechnie znanych) standardowych bibliotekach plikowych j ęzyka C. W roz-
dziale opisujemy tak że standardowe biblioteki j ęzyka Ruby zwi ązane z wyszukiwaniem
i manipulacjami w systemie plików. Do wielu z Receptur tego rozdzia łu powracamy
w rozdziale 23.
Jak wida ć, tematyka pierwszych sze ściu rozdzia łów zwi ązana jest z konkretnymi problema-
mi algorytmicznymi. Cztery nast ępne rozdzia ły maj ą natomiast charakter nieco bardziej abs-
trakcyjny, po świ ęcone są bowiem filozofii i idiomatyce j ęzyka Ruby. Gdyby zdarzy ło si ę tak,
że mia łbyś kłopoty z wyra żeniem w j ęzyku Ruby swych algorytmicznych zamiarów b ąd ź te ż
doszed łbyś do wniosku, że napisany przez Ciebie kod nie wygl ąda tak, jak powinien wygl ą-
dać, tre ść tych rozdzia łów z pewno ści ą oka że się dla Ciebie wielce pomocna.
• W rozdziale 7., „Bloki kodowe i iteracje”, pokazujemy, jak w j ęzyku Ruby mo żna zamy-
kać fragmenty kodu w bloki (zwane tak że domkni ęciami — closures) i jak programowa ć
obliczenia iteracyjne.
• Programowanie zorientowane obiektowo jest przedmiotem tre ści rozdzia łu 8., „Obiekty
i klasy”. W kolejnych Recepturach prezentujemy tworzenie ró żnych typów klas i metod
oraz ilustrujemy zastosowanie specyficznych mechanizmów j ęzyka Ruby, jak zamra żanie
(freezing) i klonowanie (cloning) obiektów.

1
Wydanie polskie: Wyra żenia regularne, wyd. Helion 2001 (http://helion.pl/ksiazki/wyrare.htm) — przyp. t łum.
Wprowadzenie | 19• Modu łom i przestrzeniom nazw po świ ęcony jest rozdzia ł 9., „Modu ły i przestrzenie nazw”.
S ą to mechanizmy umo żliwiaj ące programowanie nowych zachowa ń w ramach istniej ą-
cych klas oraz segregowanie elementów funkcjonalno ści w roz łączne obszary.
• Rozdzia ł 10., „Odzwierciedlenia i metaprogramowanie”, po świ ęcony jest technikom pro-
gramowej eksploracji i modyfikowania definicji klas.
Opisywane w rozdziale 6. mechanizmy dost ępu do plików maj ą charakter raczej ogólny; ich
uzupe łnieniem jest bardziej szczegó łowe omówienie zarz ądzania danymi przechowywanymi
w plikach o specyficznych formatach.
• Rozdzia ł 11., „XML i HTML”, po świ ęcony jest dwóm najbardziej popularnym formatom
wymiany danych. Jego tre ść dotyczy w wi ększo ści analizy istniej ących plików w tych
formatach — patrz jednak Receptura 11.9.
• W rozdziale 12., „Formaty plików graficznych i innych”, prezentujemy przetwarzanie
danych w innych popularnych formatach, ze szczególnym uwzgl ędnieniem tworzenia
i przetwarzania grafiki.
• Tre ść rozdzia łu 13., „Bazy danych i trwa ło ść obiektów”, koncentruje si ę wokó ł mechani-
zmów trwa łego (persistent) przechowywania danych drog ą ich serializacji lub strukturalnej
organizacji w postaci baz danych. Prezentujemy rozmaite techniki serializacji i indekso-
wania, od bibliotek klienckich j ęzyka Ruby dla popularnych baz SQL do pe łnokrwistych
warstw abstrakcji w rodzaju ActiveRecord, uwalniaj ących programist ę od bezpo średnie-
go operowania kwerendami SQL.
Obecnie najbardziej popularnym zastosowaniem j ęzyka Ruby jest tworzenie aplikacji siecio-
wych (g łównie w oparciu o Ruby on Rails). Kolejne trzy rozdzia ły po świ ęcone s ą ró żnym ty-
pom takich aplikacji.
• W rozdziale 14., „Us ługi internetowe”, po świ ęconym us ługom internetowym, przedsta-
wiamy ogólne zagadnienia zwi ązane z aplikacjami sieciowymi, ilustruj ąc je ró żnymi typa-
mi klientów i serwerów stworzonych za pomoc ą bibliotek j ęzyka Ruby.
• Środowisko do tworzenia aplikacji sieciowych — Ruby on Rails — któremu Ruby zawdzi ę-
cza sw ą popularno ść, opisywane jest w rozdziale 15., „Projektowanie aplikacji interneto-
wych: Ruby on Rails”.
• Rozdzia ł 16., „Us ługi sieciowe i programowanie rozproszone”, po świ ęcony jest dwóm
technikom wymiany informacji za pomoc ą programów w j ęzyku Ruby: us ługom webo-
wym (Web Services) i programowaniu rozproszonemu. Żądanie us ługi webowej wyra żane
jest w kategoriach protoko łu HTTP i kierowane jest do komputera pozostaj ącego zazwy-
DRb j ęzyka Ruby umo żliwia natomiastczaj poza kontrol ą programu żądaj ącego; biblioteka
wspó łdzielenie struktur danych mi ędzy programy dzia łaj ące na wielu komputerach, po-
zostaj ących pod ca łkowit ą kontrol ą tych że programów.
Trzy nast ępne rozdzia ły zwi ązane s ą z czynno ściami pomocniczymi towarzysz ącymi realiza-
cji ka żdego projektu programistycznego.
• W rozdziale 17., „Testowanie, debugowanie, optymalizacja i tworzenie dokumentacji”,
omawiamy g łównie techniki post ępowania w sytuacjach wyj ątkowych oraz tworzenia
zestawów testowych na potrzeby testowania modu łów tworzonego kodu. Kilka Receptur
po świ ęconych jest procesom debugowania i optymalizowania kodu.
• Rozdzia ł 18., „Tworzenie pakietów oprogramowania i ich dystrybucja”, po świ ęcony jest
problematyce dystrybuowania gotowych aplikacji. Jego treść koncentruje si ę g łównie
wokó ł systemu pakietowego Gem j ęzyka Ruby oraz serwera RubyForge kolekcjonuj ącego
wiele plików tego systemu. Wiele Receptur zawartych w innych rozdzia łach wymaga za-
20 | Wprowadzenieinstalowania konkretnego gema, je żeli wi ęc tematyka ta nie jest Ci znajoma, powiniene ś
przestudiowa ć dok ładnie Receptur ę 18.2. W rozdziale pokazujemy tak że, jak mo żna two-
rzyć i dystrybuowa ć gemy dla swych w łasnych projektów.
• W rozdziale 19., „Automatyzacja zada ń z wykorzystaniem j ęzyka Rake”, prezentujemy
Rake — najpopularniejsze narz ędzie do automatyzacji tworzenia aplikacji w j ęzyku Ruby.
Narz ędzie to umo żliwia skryptowe programowanie najbardziej typowych czynno ści w ro-
dzaju testowania modu łów czy pakowania gotowych projektów do postaci gemów. Mimo
i ż Rake przeznaczone jest g łównie dla aplikacji tworzonych w j ęzyku Ruby, jego funkcjo-
nalno ść umo żliwia tak że wykorzystywanie go w roli j ęzyka ogólnego przeznaczenia, na
wzór mechanizmu Make znanego z j ęzyka C.
Trzy ostatnie rozdzia ły obejmuj ą ró żne zagadnienia o charakterze uzupe łniaj ącym.
• W rozdziale 20., „Wielozadaniowość i wielow ątkowo ść”, pokazujemy, jak za pomoc ą wie-
low ątkowo ści mo żna wykonywa ć kilka rzeczy równocze śnie oraz jak za pomoc ą unikso-
wych podprocesów uruchamia ć polecenia zewn ętrzne.
• Rozdzia ł 21., „Interfejs u żytkownika”, po świ ęcony jest interfejsom u żytkownika (nieza-
le żnie od omawianego w rozdziale 15. interfejsu webowego). Opisujemy interfejs wiersza
polece ń, interfejs GUI wykorzystuj ący Curses i HighLine, zestawy narz ędziowe GUI dla
ró żnych platform oraz rozmaite subtelno ści zwi ązane z interfejsami u żytkownika (Recep-
tura 21.11).
• Wspó łpraca j ęzyka Ruby z innymi j ęzykami programowania — w celu uzyskania lepszej
wydajno ści lub dost ępu do pewnych bibliotek — jest tre ści ą rozdzia łu 22., „Rozszerzenia
j ęzyka Ruby z wykorzystaniem innych j ęzyków”. Koncentrujemy si ę tu g łównie na j ęzy-
ku C i jego bibliotekach, cho ć jedna z Receptur (22.5) po świ ęcona jest JRuby — implemen-
tacji j ęzyka uruchamianej w ramach wirtualnej maszyny Javy (JVM).
• Rozdzia ł 23., „Administrowanie systemem”, obfituje w kompletne programy wykonuj ące
ró żne zadania administracyjne; wi ększość z tych programów wykorzystuje techniki opisy-
wane w poprzednich rozdzia łach. Zawarte w rozdziale Receptury koncentruj ą si ę g łów-
nie na administracji uniksowej, cho ć nie brakuje te ż zasobów dla u żytkowników Windows
(Receptura 23.2) i kilku skryptów mi ędzyplatformowych.
Jak wykorzystywa ć fragmenty kodu?
Studiowanie książki kucharskiej polega przede wszystkim na praktycznym wypróbowywa-
niu zawartych w niej receptur. Niektóre z prezentowanych w niniejszej ksi ążce fragmentów
kodu s ą do ść obszerne i nadaj ą si ę do bezpo średniego wykorzystania bez konieczno ści ro-
zumienia ich dzia łania (czego dobrym przyk ładem jest chocia żby Receptura 19.8). Wi ększo ść
receptur stanowi jednak ilustracj ę okre ślonych technik, a najlepszym sposobem nauczenia si ę
konkretnej techniki jest wypróbowanie jej w praktyce.
Z tak ą w łaśnie intencj ą tworzyli śmy poszczególne Receptury, a znakomita wi ększo ść przy-
k ładowego kodu mo że by ć potraktowana jako testy dla koncepcji przedstawianych w tych
recepturach: bierze si ę konkretne obiekty, dokonuje na nich pewnych dzia łań i porównuje si ę
otrzymane wyniki z opisywanymi.
Jednym z elementów zestawu instalacyjnego j ęzyka Ruby jest interaktywny interpreter o na-
zwie irb. W ramach sesji tego interpretera mo żna wpisywa ć pojedyncze wiersze kodu i obser-
wowa ć „na gor ąco” efekty ich wykonywania. Nie jest konieczne uprzednie tworzenie kom-
pletnych plików z kodem programu.
Wprowadzenie | 21Wi ększość przyk ładowego kodu towarzysz ącego poszczególnym Recepturom ma posta ć na-
daj ącą si ę do bezpo średniego wpisywania lub wklejania do sesji irb. Dla lepszego zrozumie-
nia dzia łania poszczególnych fragmentów kodu zach ęcamy do ich uruchamiania w ramach
irb i obserwowania kolejnych rezultatów tego uruchomienia. Praktyczne prze śledzenie wy-
konywania kodu z pewno ści ą oka że si ę bardziej po żyteczne ni ż tylko przeczytanie niefor-
malnego opisu tego ż kodu, a ponadto stanowi ć mo że dobry punkt wyj ścia dla w łasnych eks-
perymentów.
W niektórych przypadkach zwracamy uwag ę Czytelnika na (oczekiwany) wynik okre ślonego
wyra żenia. Czynimy to za pomoc ą komentarzy, zawieraj ących strza łki skomponowane ze
znaków ASCII, wskazuj ące odno śn ą warto ść — s ą to takie same strza łki, jakich u żywa irb
do wskazywania rezultatów obliczania wpisywanych wyra że ń. Wykorzystujemy tak że zwy-
kłe komentarze tekstowe do obja śniania wybranych fragmentów. Oto przyk ład kodu sforma-
towanego zgodnie z opisanymi konwencjami:
1 + 2 # => 3
# W przypadku d ługiego wiersza oczekiwan ą warto ść prezentujemy w nowym wierszu
Math.sqrt(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10)
# => 7.41619848709566
Aby wy świetli ć wynik wykonania wyra żenia, pos ługujemy si ę komentarzami niezawieraj ący-
mi strzałek, pisanymi zawsze w nowym wierszu:
puts "Ten łańcuch nie wymaga żadnych wyja śnie ń."
# Ten łańż śnie ń.
Je żeli wpiszesz obydwa powy ższe fragmenty do sesji irb (pomijaj ąc komentarze), przekonasz
si ę, że otrzymane wyniki istotnie s ą zgodne z oczekiwanymi:
$ irb
irb(main):001:0> 1 + 2
=> 3
irb(main):002:0> Math.sqrt(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10)
=> 7.41619848709566
irb(main):003:0> puts "Ten łańcuch nie wymaga żadnych wyja śnie ń."
Ten łańcuch nie wymaga żadnych wyja śnie ń.
=> nil
Je żeli czytasz t ę ksi ążkę w formie elektronicznej, mo żesz bezpo średnio wkleja ć poszczególne
fragmenty do sesji irb. Interpreter ignoruje komentarze, warto je jednak zachowa ć w celu
przekonania si ę, że otrzymane wyniki s ą zgodne z oczekiwanymi — bez potrzeby zagl ądania
do tekstu oryginalnego. (Mimo i ż wklejanie kodu jest operacj ą wygodn ą i efektywn ą, naszym
zdaniem r ęczne jego przepisywanie jest bardziej pouczaj ące i dlatego zalecamy je stosowa ć
przynajmniej na pocz ątku nauki).
$ irb
irb(main):001:0> 1 + 2 # => 3
=> 3
irb(main):002:0>
irb(main):003:0* # W przypadku d ługiego wiersza oczekiwan ą warto ść prezentujemy
# w nowym wierszu
irb(main):004:0* Math.sqrt(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10)
=> 7.41619848709566
irb(main):005:0> # => 7.41619848709566
irb(main):006:0*
irb(main):007:0* puts "Ten łańcuch nie wymaga żadnych wyja śnie ń."
Ten łańcuch nie wymaga żadnych wyja śnie ń.
=> nil
irb(main):008:0> # Ten łańżadnych wyja śnie ń.
22 | WprowadzenieGeneralnie nie stosujemy żadnych skrótów, prezentuj ąc kompletne przebiegi sesji irb, od po-
czątku do ko ńca, wraz z ewentualnymi importami i czynno ściami inicjacyjnymi. Umo żliwi to
Czytelnikom łatwiejsze zrozumienie otrzymywanych rezultatów oraz szczegó łow ą konfron-
2
tacj ę prezentowanych sesji z rezultatami w łasnych poczyna ń . Odzwierciedla to nasz ą filozo-
fi ę, zgodnie z któr ą przykładowy kod powinien by ć swoistym testem na prawdziwo ść opisy-
wanych koncepcji (o czym ju ż wcze śniej wspominali śmy). Sami zreszt ą przetestowali śmy ów
kod w ramach testu modu łów, u żywaj ąc skryptu wy łuskuj ącego przyk łady z tre ści ksi ążki
i uruchamiaj ącego je.
W pewnych sytuacjach interpreter irb okazuje si ę jednak nieprzydatny — mi ędzy innymi
Receptury zwi ązane ze środowiskiem Ruby on Rails musz ą by ć uruchamiane w tym że śro-
dowisku, a Receptury wykorzystuj ące Curses wykorzystuj ą całą powierzchni ę ekranu i jako
takie nie daj ą si ę pogodzi ć z wynikami wypisywanymi przez irb. Z konieczno ści wi ęc pre-
zentujemy wówczas zawarto ść konkretnych plików, zapisuj ąc ów fakt w nast ępuj ącej postaci:
#!/usr/bin/ruby –w
# sample_ruby_file.rb: przyk ładowy plik
1 + 2
Math.sqrt(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10)
puts "Ten łańcuch nie wymaga żadnych wyja śnie ń."
Je śli to mo żliwe, staramy si ę wówczas prezentowa ć tak że wyniki dzia łania programu, w for-
mie zrzutu ekranowego interfejsu GUI albo w postaci produkowanego przez program tekstu:
$ ruby sample_ruby_file.rb
Ten łańcuch nie wymaga żadnych wyja śnie ń.
Zwró ć uwag ę, i ż wynik produkowany na podstawie przyk ładowego pliku ró żni si ę od wy-
niku generowanego w sesji irb (na podstawie takich samych danych wej ściowych): nie ma
śladu ani po dodawaniu, ani po obliczaniu pierwiastka, bowiem operacje te nie generuj ą tek-
stu wyj ściowego.
Instalowanie oprogramowania
W systemie Mac OS X i w wi ększo ści instalacji Linuksa środowisko j ęzyka Ruby jest preinsta-
lowanym sk ładnikiem. U żytkownicy Windows mog ą pobrać pakiet instalacyjny środowiska
(„one-click installer”) ze strony http://rubyforge.org/projects/rubyinstaller/.
Je śli w swojej instalacji Linuksa nie posiadasz j ęzyka Ruby b ąd ź chcia łbyś posiadan ą wersj ę
uaktualni ć, sprawd ź dost ępno ść pakietu Ruby dla Twojej dystrybucji Linuksa: dla Debiana
pakiety te posiadaj ą nazw ę o postaci ruby-<wersja> — na przyk ład ruby-1.8 lub ruby-1.9,
natomiast dla dystrybucji Red Hat Linux (a tak że dla podsystemu DarwinParts na Mac OS X)
pakiet nazywa si ę (po prostu) ruby.
W razie niedost ępno ści gotowego pakietu mo żna pobra ć i skompilowa ć kod źród łowy j ęzyka
Ruby — kod ten dost ępny jest do pobrania (poprzez HTTP lub FTP) pod adresem http://www.
ruby-lang.org/.

2
W sytuacji, gdy dzia łanie programu zale żne jest od bie żącego wskazania czasu, generowanych liczb pseudo-
losowych lub obecno ści pewnych plików, otrzymywane przez Ciebie wyniki mog ą ró żni ć si ę od prezentowa-
nych w ksi ążce, cho ć powinny by ć do nich podobne.
Wprowadzenie | 23Wiele z zawartych w tej książce Receptur wykorzystuje niezale żne biblioteki w postaci gemów
j ęzyka Ruby. Generalnie zamiast gemów wolimy niezale żne rozwi ązania bazuj ące na standar-
dowych bibliotekach j ęzyka Ruby, dajemy jednak gemom pierwsze ństwo przed niezale żnymi
rozwi ązaniami w innej formie.
Je żeli chcesz dowiedzie ć si ę czego ś wi ęcej o gemach, przeczytaj rozdzia ł 18. Na pocz ątek wy-
starczy, że pobierzesz bibliotek ę Rubygems spod adresu http://rubyforge.org/projects/rubygems/
(wybierz najnowsz ą wersj ę), rozpakujesz archiwum do katalogu Rubygems-<wersja> i po za-
logowaniu si ę jako superuser uruchomisz instalacj ę za pomoc ą polecenia
$ ruby setup.rb
U żytkownicy Windows maj ą u łatwione zadanie, bowiem biblioteka Rubygems jest integraln ą
części ą pakietu „one-click installer”.
Gdy zainstalujesz bibliotek ę Rubygems, instalowanie pozosta łych elementów j ęzyka nie b ę-
dzie trudnym zadaniem. Je żeli na przykład przeczytasz w którym ś z Receptur co ś w rodzaju
„Ruby on Rails dost ępny jest w gemie rails”, w celu zainstalowania gemu rails wydaj
nast ępuj ące polecenie (po zalogowaniu si ę jako superuser):
$ gem install rails –include-dependencies
Spowoduje to pobranie gemu rails (wraz ze wszystkimi innymi gemami, od których jest on
zale żny) i automatyczne jego zainstalowanie. Potem mo żesz ju ż uruchomi ć kod zawarty
w Recepturze — bez żadnych jego modyfikacji.
rails, do najbardziej u żytecznych niew ątpliwie zaliczy ć nale ży dwa gemy sk ła-Oprócz gemu
daj ące si ę na projekt Ruby Facets: facets_core i facets_more. Pierwszy z nich rozszerza
standardow ą bibliotek ę klas j ęzyka Ruby o nowe, u żyteczne metody, drugi dostarcza ca łkiem
nowych klas i modu łów. Wi ęcej informacji na ten temat znale źć mo żna na stronie projektu
http://facets.rubyforge.org.
Niektóre biblioteki j ęzyka Ruby (szczególnie te starsze) nie maj ą postaci gemów. Wi ększo ść
bibliotek tego typu, do których odwo łujemy si ę w niniejszej ksi ążce, reprezentowana jest
w Ruby Application Archive (RAA) pod adresem http://raa.ruby-lang.org. Jest to katalog, z któ-
rego mo żna pobiera ć potrzebne archiwa i instalowa ć je zgodnie z opisem zawartym w Recep-
turze 18.8.
Ró żne platformy, ró żne wersje i inne k łopoty…
Z wyj ątkiem przypadków, gdy wyra źnie to zaznaczamy, prezentowane Receptury nie s ą uza-
le żnione od konkretnej platformy i zawarty w nich kod powinien funkcjonowa ć identycznie
w Windows, Linuksie i Mac OS X. Receptury specyficzne dla konkretnych platform pojawiaj ą
si ę głównie w rozdzia łach ko ńcowych — 20., 21. i 23., a we wst ępie do rozdzia łu 6. znajduje
si ę uwaga na temat nazewnictwa plików w systemie Windows.
Prezentowane przyk łady zosta ły napisane i przetestowane przy u życiu wersji 1.8.4 j ęzyka
Ruby i wersji 1.1.2 Rails — gdy pisali śmy t ę ksi ążk ę, by ły to najnowsze stabilne wersje.
W niektórych miejscach kodu informujemy Czytelników o tym, jakie zmiany powinni poczy-
nić, przenosz ąc ów kod do wy ższych wersji Ruby — 1.9 (najnowsza, niestabilna w czasie pi-
sania ksi ążki) oraz 2.0.
Mimo do ło żenia wszelkich stara ń, nie mo żemy zagwarantowa ć, że w prezentowanym kodzie nie
znajduj ą si ę (mimo wszystko) fragmenty zale żne od konkretnych platform, bez wskazania tego
faktu explicite. Z góry przepraszamy, przy okazji proponuj ąc zajrze ć do erraty (patrz ni żej).
24 | WprowadzenieW niektórych Recepturach dokonali śmy modyfikacji standardowej klasy Array, dodaj ąc do
niej nowe metody (czego przyk ładem mo że być Receptura 1.10, w której definiujemy metod ę
String#capitalize_first_letter). Nowe metody staj ą si ę tym samym dost ępne dla wszyst-
kich instancji klasy Array w danym programie. Technika ta stosowana jest zreszt ą powszechnie
w j ęzyku Ruby, wykorzystuj ą j ą m.in. wspominane wcze śniej biblioteki Rails i Facets Core.
Jest jednak technik ą nieco kontrowersyjn ą, bo mo że niekiedy przysporzy ć niema łych k łopo-
tów (patrz na przyk ład dyskusja w ramach Receptury 8.4), czujemy si ę zatem w obowi ązku
wyra źnie zasygnalizowa ć ów fakt w niniejszej przedmowie, nawet je śli dla nowicjuszy mo że
si ę on wydawa ć kwesti ą czysto techniczn ą.
Je śli więc nie chcesz modyfikowa ć istniej ących klas standardowych, mo żesz wprowadzi ć no-
w ą metod ę do subklasy albo zdefiniowa ć j ą w przestrzeni nazw Kernel — przyk ładowo, de-
finiuj ąc now ą metod ę capitalize_first_letter_of_string zamiast dodawania nowej me-
tody capitalize_first_letter w ramach istniej ącej klasy String.
Inne zasoby
Podstawow ą pozycj ą dla nowicjuszy, stawiaj ących pierwsze kroki na gruncie j ęzyka Ruby,
mo że być ksi ążka Dave’a Thomasa, Chada Fowlera i Andy’ego Hunta Programming Ruby: The
Pragmatic Programmer’s Guide. Jej pierwsze wydanie dost ępne jest w formacie HTML pod ad-
resem http://www.rubycentral.com/book, jest ono jednak nieco przestarza łe. Znacznie lepsze wy-
danie drugie dost ępne jest zarówno w formie drukowanej, jak i w formacie PDF pod adresem
http://www.pragmaticprogrammer.com/titles/ruby/. Zalecamy zakup wydania drugiego i ograni-
czenie roli pierwszego do podr ęcznego poradnika.
Wspaniałą pozycj ą dla pocz ątkuj ących mo że być ilustrowany, utrzymany w groteskowej for-
mie Why’s (Poignant) Guide to Ruby autorstwa why the lucky stiff, dost ępny pod adresem http://
3
poignantguide.net/ruby.
Standardow ą pozycj ą o Rails jest ksi ążka Agile Web Development with Rails autorstwa Dave’a
Thomasa, Davida Hanssona, Leona Breedta i Mike’a Clarka (wyd. Pragmatic Programmers)
oraz dwie podobne ksi ążki traktuj ące wy łącznie o Rails: Rails Cookbook Roba Orsini (wyd.
O’Reilly) i Rails Recipes Chada Fowlera (wyd. Pragmatic Programmers).
Niektóre z najcz ęściej napotykanych problemów zwi ązanych z j ęzykiem Ruby dyskutowane s ą
na forum Ruby FAQ (http://www.rubycentral.com/faq/) oraz na forum „Things That Newcomers
to Ruby Should Know” (http://www.glue.umd.edu/~billtj/ruby.html).
Wielu adeptów Ruby ma ju ż do świadczenie (by ć mo że niema łe) w programowaniu w innych
j ęzykach; frustruj ącym mo że wi ęc by ć dla nich fakt, że ta czy inna (obszerna) ksi ążka stwo-
rzona zosta ła po to, by uczy ć Czytelników j ęzyka Ruby i jednocze śnie programowania. Jednym
z wyj ątków w tym wzgl ędzie jest na pewno przewodnik autorstwa twórcy Ruby, Yukihiro
Matsumoto, „Ruby User’s Guide” (http://www.ruby-doc.org/docs/UsersGuide/rg/) — krótki i kon-
centruj ący si ę na tym, co odró żnia j ęzyk Ruby od innych j ęzyków programowania. Co praw-
da terminologia, jak ą posługuje si ę autor, jest po trosze nieaktualna, a przyk ładowy kod pre-
zentowany jest przy u życiu przestarza łego narz ędzia eval.rb (zamiast irb), ale i tak jest to
najlepszy z krótkich przewodników, jakie dane nam by ło czyta ć.

3
Polskie t łumaczenie pocz ątku tego przewodnika jest tre ści ą rozdzia łu 29. ksi ążki J. Spolsky’ego „Sztuka pisa-
nia oprogramowania” (http://helion.pl/ksiazki/artopr.htm), wyd. Helion 2006 — przyp. t łum.
Wprowadzenie | 25Kilka artyku łów napisanych zosta ło specjalnie dla znawców Javy, którzy chc ą nauczy ć si ę j ę-
zyka Ruby: w tej grupie wymieni ć nale ży między innymi „10 Things Every Java Programmer
Should Know About Ruby” Jima Weiricha (http://onestepback.org/articles/10things/), „Coming to
Ruby From Java” Francisa Hwanga (jeden z zapisów w blogu — http://hwang.net/blog/40.html)
oraz „From Java to Ruby (With Love)” Chrisa Williamsa (http://cwilliams.textdriven.com/pages/
java_to_ruby). Mimo tytu łów sugeruj ących wyra źny zwi ązek z j ęzykiem Java, artyku ły te mo-
gą okaza ć si ę wielce u żyteczne tak że dla programistów korzystaj ących z j ęzyka C++.
Na witrynie Ruby Bookshelf (http://books.rubyveil.com/books/Bookshelf/Introduction/Bookshelf) zna-
le źć mo żna kilka (darmowych) ksi ążek i artyku łów w formacie HTML.
Wreszcie, wbudowane modu ły, klasy i metody Ruby wyposa żone s ą w doskonałą dokumen-
tacj ę (z której wi ększo ść napisana zosta ła na potrzeby Programming Ruby). Dokumentacja ta
dost ępna jest on-line pod adresami http://www.ruby-doc.org/core/ i http://www.ruby-doc.org/stdlib/.
Informacj ę na temat wybranej klasy lub metody uzyska ć mo żna tak że za pomoc ą polecenia
ri (w lokalnej instalacji j ęzyka):
$ ri Array # A class
$ ri Array.new # A class method
$ ri Array#compact # An instance method
Konwencje typograficzne stosowane w ksi ążce
W niniejszej książce przyj ęli śmy nast ępuj ące konwencje zapisywania tre ści poszczególnych
kategorii:
Kursywa
Stosowana do oznaczenia nowych terminów, odsy łaczy URL, adresów e-mailowych i nazw
programów uniksowych. W ten sposób zapisujemy tytu ły menu, opcje menu, przyciski
menu i akceleratory klawiaturowe (jak Alt lub Ctrl).
Czcionka o sta łej szeroko ści
Oznacza polecenia, opcje, prze łączniki, zmienne, atrybuty, klawisze, funkcje, typy, klasy,
przestrzenie nazw, metody, modu ły, w łaściwo ści, parametry, warto ści, obiekty, zdarzenia,
procedury obs ługi zdarze ń, znaczniki XML, znaczniki HTML, makra, programy, bibliote-
ki, pliki, ście żki, katalogi, zawarto ści plików i wyniki realizacji polece ń.
Pogrubiona czcionka o sta łej szeroko ści
Oznacza polecenia lub inny tekst wpisywany (dos łownie) przez u żytkownika.
Pochy ła czcionka o sta łej szeroko ści
Oznacza fragmenty tekstu, które podlegaj ą zamianie na warto ści dostarczane przez u żyt-
kownika.
Wykorzystywanie fragmentów kodu
na w łasne potrzeby
Zadaniem niniejszej książki jest pomoc w rozwi ązywaniu pewnych problemów. Generalnie
nie mamy nic przeciwko temu, by ś wykorzystywa ł prezentowany w ksi ążce kod w swoich
programach i dokumentacjach, je żeli jednak zamierzasz cytowa ć znacz ąco du że fragmenty,
powiniene ś zwróci ć si ę do nas o pozwolenie. W szczególno ści wymagamy tego w przypadku
26 | Wprowadzenienp. rozpowszechniania CD-ROM-u z przyk ładowym kodem pochodz ącym z wydawnictwa
O’Reilly, natomiast nie jest wymagane nasze pozwolenie w przypadku cytowania niewielkich
fragmentów kodu jako np. argumentów w dyskusji czy w odpowiedziach na pytania.
Byliby śmy tak że wdzi ęczni za opatrywanie cytowanego kodu notatk ą o jego pochodzeniu (na
przyk ład „Ruby Cookbook Lucasa Carlsona i Leonarda Richardsona, Copyright 2006 O’Reilly
Media inc.”), nie wymagamy tego jednak bezwzgl ędnie.
W razie jakichkolwiek w ątpliwo ści w powy ższym temacie mo żesz skontaktowa ć si ę z nami,
wysy łaj ąc list na adres permissions@oreilly.com.
Podzi ękowania
Na pocz ątek chcieliby śmy podzi ękowa ć naszemu redaktorowi, Michaelowi Loukidesowi,
za pomoc oraz zgod ę na wykorzystanie jego nazwiska w przyk ładowym kodzie — nawet
wówczas, gdy obsadzamy go w roli gadaj ącej żaby. Bardzo pomocny okaza ł si ę tak że redak-
tor techniczny, Colleen Gorman.
Ksi ążka nie ukaza łaby si ę tak szybko i zapewne nie byłaby tak interesuj ąca, gdyby nie wk ład
wspó łautorów, którzy stworzyli w sumie ponad 60 receptur. S ą to mi ędzy innymi: Steve Arniel,
Ben Bleything, Antonio Cangiano, Mauro Cicio, Maurice Codik, Thomas Enebo, Pat Eyler, Bill
Froelich, Rod Gaither, Ben Giddings, Michael Granger, James Edward Gray II, Stefan Lang,
Kevin Marshall, Matthew Palmer, Chetan Patil, Alun ap Rhisiart, Garrett Rooney, John-Mason
Shackelford, Phil Tomson i John Wells. Zaoszcz ędzili oni mnóstwo naszego czasu, s łu żąc nam
sw ą wiedz ą na temat rozmaitych aspektów j ęzyka Ruby i wzbogacaj ąc tre ść ksi ążki swymi
cennymi pomys łami.
Ksi ążka straci łaby znacznie na jako ści, gdyby nie wnikliwe uwagi recenzentów i korektorów,
którzy wykryli tuziny b łędów programistycznych, zale żno ści od konkretnej platformy oraz
b łędów koncepcyjnych. Na szczególne podzi ękowanie w tej grupie zas ługuj ą: John N. Alegre,
Dave Burt, Bill Dolinar, Simen Edvardsen, Shane Emmons, Edward Faulkner, Dan Fitzpatrick,
Bill Guindon, Stephen Hildrey, Meador Inge, Eric Jacoboni, Julian I. Kamil, Randy Cramer,
Alex LeDonne, Steven Lumos, Keith Rosenblatt, Gene Tani i R. Vrajmohan.
Na zako ńczenie winni jeste śmy podzi ękowanie programistom i autorom z kr ęgów spo łecz-
no ści Ruby — zarówno takim znakomito ściom jak Yukihiro Matsumoto, Dave Thomas, Chad
Fowler i why the lucky stiff, jak równie ż setkom tych bezimiennych bohaterów, których pracy
owoce (w postaci bibliotek) demonstrujemy w niniejszej ksi ążce i których umiejętno ści oraz
cierpliwo ść przyczyniaj ą si ę do nieustannego rozwoju tej spo łeczno ści.
Wprowadzenie | 2728 | WprowadzenieROZDZIA Ł 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
29— 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
1wed ług UTF-8 :
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.
30 | Rozdzia ł 1. Ła ńcuchyputs '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
1.1. Budowanie ła ńcucha z cz ęści | 31przede 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 ł:
• 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!
• 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.
• 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
32 | Rozdzia ł 1. Ła ńcuchyDyskusja
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. 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"
1.1. Budowanie ła ńcucha z cz ęści | 33Aby 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 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
34 | Rozdzia ł 1. Ła ńcuchy 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
• 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 ń.
1.3. Zast ępowanie zmiennych w istniej ącym ła ńcuchu | 35Rozwią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 ą' ł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"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)
36 | Rozdzia ł 1. Ła ńcuchy# 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'=> "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
• 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.
• Receptura 1.2, „Zast ępowanie zmiennych w tworzonym łańcuchu”.
• 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.
1.4. Odwracanie kolejno ści s łów lub znaków w ła ńcuchu | 37Rozwią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 n
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
2
oddzielone „bia łymi” znakami (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
• Receptura 1.9, „Przetwarzanie poszczególnych s łów ła ńcucha”, ilustruje kilka wyra że ń
regularnych wyznaczaj ących alternatywn ą definicj ę „s łowa”.
• Receptura 1.11, „Zarz ądzanie bia łymi znakami”.
• 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.
38 | Rozdzia ł 1. Ła ńcuchy1.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.
1.5. Reprezentowanie znaków niedrukowalnych | 39Znaki 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 ć
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?
40 | Rozdzia ł 1. Ła ńcuchy# Nowy wiersz.
# Alt+X: uruchomi ć Emacs?
# 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}
puts %Q{foo\tbar}
# foo bar
puts 'foo\tbar'
# foo\tbar
puts %q{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
1.6. Konwersja mi ędzy znakami a kodami | 41Konwersj ę 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
• 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.
42 | Rozdzia ł 1. Ła ńcuchySymbole 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
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:
• U żyj łańcucha, je śli istotna jest zawartość obiektu (sekwencja tworz ących go znaków).
• U żyj symbolu, je śli istotna jest to żsamo ść obiektu.
Patrz tak że
• Receptura 5.1, „Wykorzystywanie symboli jako kluczy”.
• Receptura 8.12, „Symulowanie argumentów zawieraj ących s łowa kluczowe”.
• Rozdzia ł 10., a szczególnie receptura 10.4, „Uzyskiwanie referencji do metody”, i receptu-
ra 10.10, „Oszcz ędne kodowanie dzi ęki metaprogramowaniu”.
• 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
# 98 = b
# 97 = a
# 114 = r
1.8. Przetwarzanie kolejnych znaków ła ńcucha | 43Za 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
#
44 | Rozdzia ł 1. Ła ńcuchy# 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
• 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}
1.9. Przetwarzanie poszczególnych s łów ła ńcucha | 45Dyskusja
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]+/ ł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}
46 | Rozdzia ł 1. Ła ńcuchyJe ś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
• Receptura 1.4, „Odwracanie kolejno ści s łów lub znaków w łańcuchu”.
• 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, j
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."
1.10. Zmiana wielko ści liter w ła ńcuchu | 47W 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 jeste
s.capitalize_first_letter!
s # => "Teraz jeste
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
• Receptura 1.18, „Zast ępowanie wielu wzorców w pojedynczym przebiegu”.
• 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:
48 | Rozdzia ł 1. Ła ńcuchys = "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?
1.12. Czy mo żna potraktowa ć dany obiekt jak ła ńcuch? | 49Rozwią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:łań
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.
50 | Rozdzia ł 1. Ła ńcuchyWarto 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
• 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"
1.13. Wyodr ębnianie cz ęści ła ńcucha | 51Je ż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
• Receptura 1.9, „Przetwarzanie poszczególnych s łów łańcucha”.
• 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
52 | Rozdzia ł 1. Ła ńcuchyMetoda 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
• 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”.
• 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 z ć. \n"
puts wrap("Ten tekst zostanie zawini ęty.", 15)
1.15. Zawijanie wierszy tekstu | 53# 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
54 | Rozdzia ł 1. Ła ńcuchyPrzetwarzanie 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?
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
• 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
1.16. Generowanie nast ępnika ła ńcucha | 55Metod ą 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-
3gą pojawia ć si ę znaki tylko jednego rodzaju: cyfry, ma łe litery albo wielkie litery .
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.
56 | Rozdzia ł 1. Ła ńcuchy'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
• Receptura 2.15, „Generowanie sekwencji liczb”.
• Receptura 3.4, „Iterowanie po datach”.
1.16. Generowanie nast ępnika ła ńcucha | 571.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}
58 | Rozdzia ł 1. Ła ńcuchyW 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
4
• Książka Jeffreya Friedla Mastering Regular Expressions dostarcza eleganckiego i zwięzłe-
go wprowadzenia w tematyk ę wyra że ń regularnych, ilustrowanego wieloma praktyczny-
mi przyk ładami.
• Witryna RegExLib.com (http://regexlib.com/default.aspx) jest obszern ą baz ą wyra że ń regu-
larnych, wyposa żon ą w wyszukiwark ę.
• 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.
• Informacje na temat klasy Regexp mo żesz uzyska ć za pomoc ą polecenia ri Regexp.
• 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.
1.17. Dopasowywanie ła ńcuchów za pomoc ą wyra że ń regularnych | 591.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
60 | Rozdzia ł 1. Ła ńcuchyjej 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
• Receptura 1.17, „Dopasowywanie łańcuchów za pomoc ą wyra że ń regularnych”.
• 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' ]
1.19. Weryfikacja poprawno ści adresów e-mailowych | 61Oto 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}$/e_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 Mail
::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.
62 | Rozdzia ł 1. Ła ńcuchyJedynym 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' # =>
probably_valid? 'joe.bloggs@mail.example.com' # => true
probably_valid? 'joe@examplecom' # => false 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
1.19. Weryfikacja poprawno ści adresów e-mailowych | 63 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
• Receptura 14.5, „Wysy łanie poczty elektronicznej”.
• Receptura 15.19, „Przesy łanie wiadomo ści pocztowych za pomoc ą aplikacji Rails”.
• 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.
64 | Rozdzia ł 1. Ła ńcuchyRozwiązanie
Mo żna w tym celu skorzysta ć w biblioteki Classifier Lucasa Carlsona, dost ępnej w gemie
classifier. W bibliotece tej znajduje si ę naiwny klasyfikator bayesowski oraz klasyfikator
wykorzystuj ący bardziej zaawansowan ą technik ę ukrytego indeksowania semantycznego
(LSI — Latent Semantic Indexing).
Interfejs naiwnego klasyfikatora bayesowskiego jest elementarny: tworzy si ę obiekt Classi-
fier::Bayes z okre śleniem rodzaju klasyfikacji jako parametrem, po czym dokonuje si ę „ucze-
nia” tego ż obiektu za pomoc ą fragmentów tekstu o znanym wyniku klasyfikacji.
require 'rubygems'
require 'classifier'
classifier = Classifier::Bayes.new('Spam', 'Not spam')
classifier.train_spam 'are you in the market for viagra? we sell viagra'
classifier.train_not_spam 'hi there, are we still on for lunch?'
Nast ępnie mo żna przekaza ć do obiektu nieznany tekst i zaobserwowa ć wynik klasyfikacji:
classifier.classify "we sell the cheapest viagra on the market"
# => "Spam"
classifier.classify "lunch sounds great"
# => "Not spam"
Dyskusja
Bayesowska analiza tekstu opiera si ę na rachunku prawdopodobie ństwa. Klasyfikator w pro-
cesie uczenia si ę analizuje wzorcowy tekst w rozbiciu na s łowa, zapami ętuj ąc prawdopodo-
bie ństwo wyst ępowania ka żdego z tych s łów w podanej kategorii. W prostym przyk ładzie
podanym w Rozwi ązaniu rozkład tego prawdopodobie ństwa mo że być opisany przez nast ę-
puj ące hasze:
classifier
# => #<Classifier::Bayes:0xb7cec7c8
# @categories={:"Not spam"=>
# { :lunch=>1, :for=>1, :there=>1,
# :"?"=>1, :still=>1, :","=>1 },
# :Spam=>
# { :market=>1, :for=>1, :viagra=>2, :"?"=>1, :sell=>1 }
# },
# @total_words=12>
Hasze te wykorzystywane s ą nast ępnie do budowania statystyki analizowanego (nieznanego)
tekstu. Zwró ćmy uwag ę, że s łowo „viagra” dwukrotnie wyst ąpi ło we wzorcowym tek ście
zaliczonym do kategorii „Spam”, s łowo „sell” — jednokrotnie w kategorii „Spam”, za ś s łowo
„for” — jednokrotnie w obydwu kategoriach, „Spam’ i „Not spam”. Oznacza to, że wyst ąpie-
nie s łowa „for” w analizowanym (nieznanym) tek ście nie daje żadnej przes łanki klasyfikacyj-
nej, wyst ąpienie s łowa „sell” daje pewn ą przes łank ę w kierunku kategorii „Spam”, za ś wy-
st ąpienie s łowa „viagra” stanowi dwukrotnie silniejsz ą przes łank ę w tym samym kierunku.
Im wi ększa obj ęto ść tekstu wzorcowego przeanalizowana zostanie na etapie uczenia si ę kla-
syfikatora, tym generalnie trafniejszych rezultatów mo żna si ę spodziewa ć w procesie rozpo-
znawania kategorii nieznanego tekstu. Wynik tej klasyfikacji — zaproponowany przez klasy-
fikator b ąd ź skorygowany przez u żytkownika — mo że być wykorzystany jako kolejna porcja
danych „ucz ących”.
1.20. Klasyfikacja tekstu za pomoc ą analizatora bayesowskiego | 65Bie żący „dorobek” klasyfikatora w procesie uczenia si ę mo żna zapisa ć na dysku do pó źniej-
szego u żytku, za pomoc ą Madeleine (patrz receptura 13.3).
Klasyfikator bayesowski mo że rozpoznawa ć dowoln ą liczb ę kategorii. Kategorie „Spam”
i „Not spam” należą do najczęściej wykorzystywanych, ale liczba kategorii nie jest bynajm-
niej ograniczona do dwóch. Mo żna tak że wykorzysta ć rodzim ą metod ę train zamiast specy-
ficznych metod train_<kategoria>. Klasyfikator u żyty w poni ższym przyk ładzie wykorzy-
stuje t ę rodzimą metod ę i dokonuje klasyfikacji tekstu do jednej z trzech kategorii:
classifier = Classifier::Bayes.new('Interesting', 'Funny', 'Dramatic')
classifier.train 'Interesting', "Leaving reminds us of what we can part
with and what we can't, then offers us something new to look forward
to, to dream about."
classifier.train 'Funny', "Knock knock. Who's there? Boo boo. Boo boo
who? Don't cry, it is only a joke."
classifier.train 'Dramatic', 'I love you! I hate you! Get out right
now.'
classifier.classify 'what!'
# => "Dramatic"
classifier.classify "who's on first?"
# => "Funny"
classifier.classify 'perchance to dream'
# => "Interesting"
Za pomocą metody untrain mo żna anulowa ć efekt obecno ści danego s łowa we wzorcowym
tek ście okre ślonej kategorii, co okazuje si ę nieodzowne w przypadku niereprezentatywnego
tekstu wzorcowego b ąd ź b łędnego typowania:
classifier.untrain_funny "boo"
classifier.untrain "Dramatic", "out"
Patrz tak że
• Receptura 13.3, „Utrwalanie obiektów z wykorzystaniem biblioteki Madeleine”.
• Plik README biblioteki Classifier zawiera przyk ład klasyfikatora LSI.
• Bishop (http://bishop.rubyforge.org/) jest innym klasyfikatorem bayesowskim, przeniesionym
z Python Reverend i dost ępnym w gemie bishop.
• http://pl.wikipedia.org/wiki/Naiwny_klasyfikator_bayesowski.
• http://en.wikipedia.org/wiki/Latent_Semantic_Analysis.
66 | Rozdzia ł 1. Ła ńcuchyROZDZIA Ł 2.
Liczby
Liczby maj ą tak fundamentalne znaczenie dla oblicze ń komputerowych, jak oddychanie dla
ludzkiego życia. Nawet programy zdaj ące si ę nie mie ć nic wspólnego z matematyk ą i tak mu-
szą wykonywa ć rozmaite operacje „liczbowe”, jak zliczanie elementów struktury, wy świetla-
nie średniego czasu wykonania czy symulowanie losowego zachowania. Ruby znacznie u ła-
twia programi ście operowanie liczbami, pozwalaj ąc mu w wi ększym stopniu skoncentrowa ć
si ę na istocie rozwi ązywanego problemu.
Odwiecznym problemem programistycznym s ą ró żne implementacje „liczb” w j ęzykach pro-
gramowania, optymalizowane pod k ątem ró żnych zastosowa ń: 32-bitowe liczby ca łkowite,
liczby zmiennopozycyjne itp. Ruby w du żym stopniu skrywa te subtelno ści przed programi-
st ą, jednak że nale ży by ć ich świadomym, jako że często manifestuj ą si ę pod postaci ą dziwacz-
1
nych wyników oblicze ń .
Jedna ze wspomnianych ró żnic implementacyjnych zwi ązana jest z odmiennym reprezento-
waniem (w pami ęci maszyny) ma łych liczb i du żych liczb. W tradycyjnych j ęzykach progra-
mowania ró żnica ta by ła dla programisty ewidentna, gdy ż zmuszony by ł on w sposób jawny
u żywać dwóch ró żnych reprezentacji liczb (oczywi ście przy za ło żeniu, że j ęzyk programowa-
nia w ogóle zapewnia ł obs ług ę liczb o warto ści znacznie wykraczaj ącej poza pojemno ść s ło-
wa maszynowego). W j ęzyku Ruby tak że istniej ą dwie ró żne klasy dla liczb ma łych (Fixnum)
i du żych (Bignum), jednak że Ruby stara si ę w maksymalnym stopniu uwalnia ć programist ę
od konsekwencji tej ró żnicy: przyk ładowo, wyra żenia sta łe (czyli niezawieraj ące zmiennych)
2
automatycznie zaliczane s ą do odpowiedniej klasy zależnie od swej warto ści :
1000.class # => Fixnum
10000000000.class # => Bignum
(2**30 - 1).class # => Fixnum
(2**30).class # => Bignum
Podczas wykonywania oblicze ń Ruby automatycznie dokonuje wszelkich niezb ędnych kon-
wersji, skrywaj ąc po raz kolejny ró żnice mi ędzy obydwiema kategoriami liczb:
small = 1000
big = small ** 5 # => 1000000000000000
big.class # => Bignum
smaller = big / big # => 1
smaller.class # => Fixnum

1
Przyk ładem takich nieoczekiwanych wyników mo że by ć odwracanie macierzy liczb ca łkowitych (patrz re-
ceptura 2.11), poniewa ż dzielenie liczb ca łkowitych realizowane jest inaczej ni ż dzielenie liczb zmiennopo-
zycyjnych.
2
Podobnie jest w j ęzyku Python.
67Kolejna ró żnica implementacyjna zwi ązana jest z odmiennym reprezentowaniem liczb ca ł-
kowitych (integer) i liczb u łamkowych (fractional). Podobnie jak wszystkie nowoczesne j ęzyki
programowania, dla reprezentowania liczb u łamkowych j ęzyk Ruby stosuje implementacj ę
liczb zmiennopozycyjnych zgodn ą ze standardem IEEE. Sta łe zawieraj ące kropk ę dziesi ętn ą
reprezentowane s ą w postaci obiektów klasy Float:
0.01.class # => Float
1.0.class # => Float
10000000000.00000000001.class # => Float
Poniewa ż jednak liczby zmiennopozycyjne posiadaj ą ograniczon ą a priori dok ładno ść, j ęzyk
Ruby dostarcza odr ębn ą klas ę umo żliwiaj ąc ą reprezentowanie sko ńczonych u łamków dzie-
si ętnych o dowolnej precyzji (patrz receptura 2.3). Co wi ęcej, dostarcza on tak że klasy do re-
2
prezentowania liczb w rodzaju posiadaj ących niesko ńczon ą reprezentacj ę dziesi ętn ą (recep-3
tura 2.4) oraz liczb zespolonych i niewymiernych (receptura 2.12).
Wszystkie klasy wykorzystywane do reprezentowania liczb (Integer, Bignum, Complex itd.)
wywodz ą si ę ze wspólnej klasy bazowej Numeric. Wszystkie one implementuj ą podstawowe
operacje arytmetyczne i w wi ększo ści przypadków mo żna miesza ć ze sob ą — w operacjach
arytmetycznych i porównaniach — liczby ró żnych typów, co mo żna szczegó łowo prze śledzić
w tre ści receptury 8.9. Mo żliwe jest tak że wzbogacanie wymienionych klas o w łasne metody
(patrz na przyk ład receptura 2.17), nie da si ę natomiast (w u żyteczny sposób) definiowa ć na
ich bazie subklas.
Ruby umo żliwia łatwe generowanie liczb pseudolosowych (patrz receptura 2.5) i sekwencji
liczbowych (receptura 2.15). W niniejszym rozdziale przedstawiamy tak że implementacj ę kil-
ku algorytmów numerycznych (receptury 2.7 i 2.11) i statystycznych (receptura 2.8).
2.1. Przekszta łcanie ła ńcucha w liczb ę
Problem
Maj ąc łańcuch przedstawiaj ący reprezentacj ę jakiej ś liczby, nale ży stworzy ć t ę liczb ę — ca łko-
wit ą lub zmiennopozycyjn ą.
Rozwiązanie
Do przekszta łcenia ła ńcucha w równowa żn ą posta ć ca łkowitoliczbow ą mo żna wykorzysta ć
metod ę String#to_i, za ś żn ą liczb ę zmiennopozycyjn ą — metody String#to_f.
'400'.to_i # => 400
'3.14'.to_f # => 3.14
'1.602e-19'.to_f # => 1.602e-19
Dyskusja
W przeciwie ństwie do Perla i PHP, j ęzyk Ruby nie dokonuje automatycznej konwersji ła ńcu-
cha na równowa żn ą mu liczb ę. Konwersj ę t ę trzeba wykona ć w sposób jawny — nale ży bo-
wiem okre śli ć sposób jej przeprowadzenia.
Metody to_i oraz to_f nie s ą jedynymi metodami konwertuj ącymi ła ńcuch na liczb ę. Gdy
łańcuch zawiera (na przyk ład) ósemkow ą lub szesnastkow ą posta ć liczby, jego konwersj ę do
68 | Rozdzia ł 2. Liczbyliczby (ca łkowitej) wykona ć mo żna za pomoc ą metod (odpowiednio) oct i hex. Identyczny
efekt mo żna uzyska ć, podaj ąc w wywo łaniu metody to_i parametr jawnie okre ślaj ący pod-
staw ę, wzgl ędem której utworzono znakow ą postać liczby:
'405'.oct # => 261
'405'.to_i(8) # => 261
'405'.hex # => 1029
'405'.to_i(16) # => 1029
'fed'.hex # => 4077
'fed'.to_i(16) # => 4077
Je żeli w trakcie dokonywania konwersji metoda to_i, oct, hex lub to_f napotka znak, który
nie daje si ę zakwalifikowa ć jako wchodz ący w sk ład reprezentacji liczby (odpowiednio ca łko-
witej lub zmiennopozycyjnej), konwersja zostaje zatrzymana na tym znaku, a jako wynik me-
tody zwracana jest liczba utworzona ze znaków dotychczas przetworzonych. Je żeli wspomnia-
nym znakiem jest pierwszy znak łańcucha, wynikiem konwersji jest zero.
"13: tuzin piekarzy".to_i # => 13
'1001 nocy'.to_i # => 1001
'Bajki z 1000 i 1 nocy'.to_i # => 0
'60.50 nasiona i sadzonki'.to_f # => 60.5
'$60.50'.to_f # => 0.0
'Feed the monster!'.hex # => 65261 # FEED
'I fed the monster at Canoga Park Waterslides'.hex # => 0
'0xA2Z'.hex # => 162
'-10'.oct # => -8
'-109'.oct # => -8 # zatrzymanie na 9
'3.14'.to_i # => 3 # zatrzymanie na .
W ostatnim przyk ładzie znakiem uniemo żliwiaj ącym konwersj ę na liczb ę całkowit ą jest krop-
ka dziesi ętna.
Mo żna za żąda ć od j ęzyka Ruby generowania wyj ątku w sytuacji napotkania „koliduj ącego”
znaku w trakcie konwersji — nale ży w tym celu pos łu ży ć si ę metod ą Integer() lub Float():
Integer('1001') # => 1001
Integer('1001 nocy')
# ArgumentError: invalid value for Integer: "1001 nocy"
Float('94.54') # => 94.54
Float('94.54% alkoholu')
# ArgumentError: invalid value for Float(): "94.54% alkoholu"
W celu dokonania ekstrakcji liczby z wi ększego ła ńcucha nale ży pos łu ży ć si ę wyra żeniami
regularnymi. Prezentowana poni żej klasa NumberParser zawiera wyra żenia regularne do
ekstrakcji pod ła ńcuchów reprezentuj ących liczby zmiennopozycyjne oraz liczby ca łkowite
w postaci dziesi ętnej, ósemkowej i szesnastkowej. Metoda extract_numbers wykorzystuje
metod ę String#scan do znajdowania w łańcuchu wszystkich liczb okre ślonego typu.
class NumberParser
@@number_regexps = {
:to_i => /([+-]?[0-9]+)/,
:to_f => /([+-]?([0-9]*\.)?[0-9]+(e[+-]?[0-9]+)?)/i,
:oct => /([+-]?[0-7]+)/,
:hex => /\b([+-]?(0x)?[0-9a-f]+)\b/i
# Znaki \b zapobiegaj ą potraktowaniu liter A-F jako cyfr
# sk ładaj ących si ę na liczb ę szesnastkow ą
}
def NumberParser.re(parsing_method=:to_i)
re = @@number_regexps[parsing_method]
raise ArgumentError, "Brak wyra żenia regularnego dla #{parsing_method.inspect}!"
unless re
return re
2.1. Przekszta łcanie ła ńcucha w liczb ę | 69 end
def extract(s, parsing_method=:to_i)
numbers = []
s.scan(NumberParser.re(parsing_method)) do |match|
numbers << match[0].send(parsing_method)
end
numbers
end
end
Oto kilka przyk ładów funkcjonowania powy ższej klasy:
p = NumberParser.new
pw = "Dzisiaj wylosowano numery 104 and 391."
NumberParser.re(:to_i).match(pw).captures # => ["104"]
p.extract(pw, :to_i) # => [104, 391]
p.extract('Bajki z 1000 i jednej nocy') # => [1000]
p.extract('$60.50', :to_f) # => [60.5]
p.extract('I fed the monster at Canoga Park Waterslides', :hex) # => [4077]
p.extract('W zapisie ósemkowym liczba pi ętna ście to 017.', :oct) # => [15]
p.extract('Od 0 do 10e60 w -2.4 sekundy', :to_f)
# => [0.0, 1.0e+61, -2.4]
p.extract('Od 0 do 10e60 w -2.4 sekundy')
# => [0, 10, 60, -2, 4]
Aby wyodr ębni ć z łańcucha kilka rodzajów liczb, nale ży zamiast wyra że ń regularnych u żyć
niezale żnego (darmowego) modu łu scanf, zawieraj ącego parser podobny do wykorzystywa-
nego przez funkcj ę scanf j ęzyka C.
require 'scanf'
s = '0x10 4.44 10'.scanf('%x %f %d') # => [16, 4.44, 10]
Patrz tak że
• Receptura 2.6, „Konwersje mi ędzy ró żnymi podstawami liczenia”.
• Receptura 8.9, „Konwersja i koercja typów obiektów”.
• Modu ł scanf (http://www.rubyhacker.com/code/scanf).
2.2. Porównywanie liczb zmiennopozycyjnych
Problem
Liczby zmiennopozycyjne nie nadaj ą si ę z natury do bezwzgl ędnie dok ładnych porówna ń.
Często zdarza si ę tak, że dwie liczby, które „powinny” by ć sobie równe, ró żni ą się od siebie
o bardzo niewielk ą warto ść. T łumaczy to dziwne na pozór zachowanie interpretera j ęzyka
Ruby w niektórych sytuacjach, gdy w gr ę wchodz ą liczby zmiennopozycyjne.
1.8 + 0.1 # => 1.9
1.8 + 0.1 == 1.9 # => false
1.8 + 0.1 > 1.9 # => true
Konieczne jest wi ęc porównywanie liczb zmiennopozycyjnych w sposób mniej rygorystyczny
— tak, by liczby ró żni ące si ę „infinitezymalnie” traktowane by ły jako równe.
70 | Rozdzia ł 2. LiczbyRozwiązanie
Najprostszym sposobem unikni ęcia opisanego problemu jest rezygnacja z u żywania liczb
zmiennopozycyjnych na rzecz liczb klasy BigDecimal (patrz receptura 2.3). Liczby BigDecimal
charakteryzuj ą si ę bezwzgl ędn ą dokładno ści ą i równie dobrze jak liczby zmiennopozycyjne
nadaj ą si ę do reprezentowania niewielkich warto ści z małą liczb ą miejsc po kropce dziesi ętnej
(na przyk ład cen owoców na straganie). Obliczenia na liczbach BigDecimal s ą jednak wol-
niejsze w porównaniu z arytmetyk ą zmiennopozycyjn ą, wi ększość systemów baz danych nie
zapewnia ich rodzimej obs ługi (w przeciwie ństwie do liczb zmiennopozycyjnych), no i same
obiekty typu Float tworzy si ę w j ęzyku Ruby znacznie łatwiej (na przyk ład wpisuj ąc w wier-
szu interpretera warto ść 10.2) . Liczby BigDecimal nie mog ą wi ęc w pe łni zast ąpi ć liczb typu
Float, konieczne jest wi ęc wypracowanie w stosunku do tych ostatnich takiej strategii ich
porównywania, by nie trzeba by ło troszczy ć si ę o niewielkie ró żnice mi ędzy nimi w procesieania.
Co to jednak znaczy „niewielkie”? Jak du ża musi by ć ró żnica mi ędzy dwiema liczbami zmien-
nopozycyjnymi, by liczby te mo żna by ło uzna ć za istotnie ró żne? Jest intuicyjnie jasne, że ró ż-
nica ta powinna zwi ększać si ę wraz ze wzrostem rz ędu wielko ści porównywanych liczb: licz-
20by 1,1 i 1,2 trudno uzna ć za równe nawet w przybli żeniu, lecz ju ż na przyk ład 10 +0,1 jest
20w przybli żeniu równe 10 +0,2.
Najlepszym rozwi ązaniem w tej sytuacji wydaje si ę nast ępuj ąca strategia: przy porównywa-
niu du żych liczb nale ży kierowa ć si ę ich wzgl ędn ą ró żnic ą, zaś przy porównywaniu ma łych
3
— ich ró żnic ą bezwzgl ędn ą . W prezentowanym poni żej kodzie wykorzystuje si ę warto ści
progowe obydwu wymienionych ró żnic (odpowiednio epsilon i relative_epsilon), obie
domyślnie równe Float::EPSILON, czyli najmniejszej mo żliwej warto ści dodatniej, o jak ą mo-
gą si ę od siebie ró żni ć dwa obiekty typu Float. Dwie liczby uznawane s ą za równe w przy-
bli żeniu, je śli zachodzi którykolwiek z poni ższych warunków:
• bezwzgl ędna wartość ró żnicy mi ędzy liczbami nie przekracza epsilon,
• ęość ró żnicy mi ędzy liczbami jest nie większa ni ż bezwzgl ędna warto ść
iloczynu relative_epsilon i wi ększej z liczb.
class Float
def approx(other, relative_epsilon=Float::EPSILON, epsilon=Float::EPSILON)
difference = other - self
return true if difference.abs <= epsilon
relative_error = (difference / (self > other ? self : other)).abs
return relative_error <= relative_epsilon
end
end
100.2.approx(100.1 + 0.1) # => true
10e10.approx(10e10+1e-5) # => true
100.0.approx(100+1e-5) # => false
Dyskusja
Arytmetyka zmiennopozycyjna cechuje si ę do ść du żą dok ładno ści ą, jednak ze wzgl ędu na
sposób implementacji obiektów typu Float nie jest to dok ładno ść bezwzględna. Wiele liczb

3
Przy porównywaniu ma łej i du żej liczby opisany problem nie istnieje, jako że liczby te s ą ewidentnie ró żne
— przyp. t łum.
2.2. Porównywanie liczb zmiennopozycyjnych | 71rzeczywistych — jak 1.9 — nie da si ę reprezentowa ć bezwzgl ędnie dok ładnie w tej implemen-
4
tacji . W efekcie warto ść przechowywana przez obiekt Float jest mo żliwie jak najbardziej bli-
ska warto ści dok ładnej, nie zawsze jest jej jednak równa.
Co wi ęcej, owa niedok ładno ść nie jest zauwa żalna na pierwszy rzut oka — nie sposób jej za-
uwa żyć w przypadku wyra że ń 1.9 i 1.8+0.1, poniewa ż obydwa przekszta łcane s ą przez me-
tod ę Float#to_s w ła ńcuch "1.9". Wystarczy jednak zwi ększy ć odpowiednio liczb ę żądanych
miejsc dziesi ętnych, u żywaj ąc metody Kernel#printf, i wszystko staje si ę jasne:
printf("%.55f", 1.9)
# 1.8999999999999999111821580299874767661094665527343750000
printf("%.55f", 1.8 + 0.1)
# 1.9000000000000001332267629550187848508358001708984375000
Obydwie warto ści bardzo dobrze przybli żaj ą warto ść 1,9 — pierwsza z niedomiarem, druga
z nadmiarem — nie s ą jej jednak równe. Warto zwróci ć uwag ę na fakt, i ż ró żnica mi ędzy ty-
mi przybli żeniami wynosi dok ładnie Float::EPSILON:
Float::EPSILON # => 2.22044604925031e-16
(1.8 + 0.1) - 1.9 # => 2.2204
Tak niewielk ą ró żnic ą mo żna si ę nie przejmowa ć, z wyj ątkiem przypadku, gdy chodzi o po-
równania. 1.9+Float::EPSILON nie jest równe 1.9-Float:EPSILON mimo i ż obydwie te war-
to ści znakomicie przybli żaj ą warto ść 1.9. Jest to jedna z przyczyn, dla których problem po-
równywania liczb zmiennopozycyjnych musi by ć rozpatrywany w szerszym kontek ście.
Jednym ze sposobów „rozlu źnienia rygorów” w tym wzgl ędzie jest uznanie dwóch liczb za
równe, je śli nie ró żni ą si ę one od siebie o wi ęcej ni ż przyj ęta a priori warto ść progowa:
class Float
def absolute_approx(other, epsilon=Float::EPSILON)
puts((other-self).abs)
return (other-self).abs <= epsilon
end
end
(1.8 + 0.1).absolute_approx(1.9) # => true
10e10.absolute_approx(10e10+1e-5) # => false
Jak jednak wida ć na powy ższym przyk ładzie, filozofia ta sprawdza si ę jedynie w stosunku do
liczb bliskich zera — dla liczb o du żej warto ści bezwzgl ędnej za ło żona warto ść epsilon oka-
zuje si ę zbyt ma ła. Jej zwi ększenie te ż niewiele pomo że, poniewa ż w dalszym ci ągu b ędzie od-
powiednia jedynie dla liczb mieszcz ących się w pewnym zakresie warto ści bezwzgl ędnych.
Skoro wi ęc wspomniana warto ść progowa wzrasta wraz z zakresem porównywanych liczb, to
zadowalaj ącym rozwi ązaniem jest przyj ęcie jej warto ści proporcjonalnej do tej wartości bez-
wzgl ędnej (a dok ładniej — do warto ści bezwzgl ędnej większej z porównywanych liczb) przy
jednoczesnym za ło żeniu, że ostatecznie nie mo że być ona mniejsza ni ż ustalona a priori war-
to ść progowa. W prezentowanej w Rozwi ązaniu metodzie Float#approx zarówno owa war-
to ść progowa, jak i wspomniany wspó łczynnik proporcjonalno ści ustalone zosta ły domy ślnie
jako Float:EPSILON. Ta ostatnia warto ść jest obowi ązuj ącym progiem przy porównywaniu
liczb o warto ści bezwzgl ędnej nieprzekraczaj ącej 1.0; dla liczb z przedzia łu 1.0 – 2.0 zwi ęk-
sza si ę ona nawet dwukrotnie, dla liczb z przedzia łu 2.0 – 3.0 nawet trzykrotnie itd.

4
Poniewa ż reprezentacje tych liczb w uk ładzie dwójkowym s ą niesko ńczonymi u łamkami okresowymi — przyp.
t łum.
72 | Rozdzia ł 2. LiczbyOpisana strategia jest ca łkowicie odpowiednia dla operacji matematycznych, jednak że w przy-
padku danych pochodz ących ze świata rzeczywistego wspomniany próg nale ży odpowied-
nio zwi ększy ć, stosownie do warunków, w jakich warto ści te s ą generowane. Je żeli mianowi-
cie dokonujemy ci ągłego pomiaru temperatury (i przetwarzamy otrzymane dane za pomoc ą
skryptu zapisanego w j ęzyku Ruby), a u żywany termometr cechuje si ę dok ładno ści ą wskaza ń
na poziomie 99,9%, mo żemy przyj ąć za warto ść progow ą iloczyn 0,001 i wi ększej z porów-
nywanych warto ści — mniejsza ró żnica mie ści si ę bowiem w zakresie b łędu pomiarowego:
98.6.approx(98.66) # => false
98.6.approx(98.66, 0.001) # => true
Patrz tak że
• Receptura 2.3, „Reprezentowanie liczb z dowoln ą dokładno ści ą”, ilustruje zastosowanie
liczb klasy BigDecimal.
• Dla dokładnego reprezentowania liczb niemaj ących dok ładnej reprezentacji zmiennopo-
zycyjnej mo żna wykorzysta ć liczby klasy Rational (patrz receptura 2.4).
• Pod adresem http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
znajduje si ę doskonały (cho ć o tre ści zorientowanej na j ęzyk C) artyku ł Bruce’a Dawsona
na temat zalet i niedostatków ró żnych strategii porównywania liczb zmiennopozycyjnych.
2.3. Reprezentowanie liczb z dowoln ą dok ładno ści ą
Problem
Chcemy wykona ć obliczenia z du żą dokładno ści ą, wykraczaj ącą poza mo żliwo ści arytmetyki
obiektów typu Float.
Rozwiązanie
Za pomoc ą obiektów typu BigDecimal mo żemy reprezentowa ć liczby rzeczywiste z dowoln ą
dokładno ści ą.
require 'bigdecimal'
BigDecimal("10").to_s # => "0.1E2"
BigDecimal("1000").to_s # => "0.1E4"
BigDecimal("1000").to_s("F") # => "1000.0"
BigDecimal("0.123456789").to_s # => "0.123456789E0"
Porównajmy to z dok ładno ści ą dostarczan ą przez typ Float:
nm = "0.123456789012345678901234567890123456789"
nm.to_f # => 0.123456789012346
BigDecimal(nm).to_s # => "0.123456789012345678901234567890123456789E0"
Dyskusja
Obiekty BigDecimal przechowuj ą warto ści zgodnie z tzw. notacj ą naukow ą. W notacji tej
liczba dzieli si ę na trzy części: znak (dodatni albo ujemny), cz ęść u łamkow ą (o dowolnie du-
żej precyzji) oraz wyk ładnik (dowolnie du ży). W podobny sposób reprezentowane s ą liczby
2.3. Reprezentowanie liczb z dowoln ą dok ładno ści ą | 73zmiennopozycyjne, te jednak charakteryzuj ą si ę ograniczonym zakresem i ograniczon ą do-
kładno ści ą — przyk ładowo, liczba zmiennopozycyjna podwójnej dok ładno ści umo żliwia re-
prezentowanie warto ści o wyk ładniku (dwójkowym) z zakresu Float::MIN_EXP (-1021) do
Float::MAX_EXP (1024), a graniczna dok ładno ść tej reprezentacji wynosi nie mniej ni ż Float#
-16EPSILON, czyli oko ło 2,2*10 .
Poszczególne komponenty obiektu BigDecimal mo żna uzyska ć za pomoc ą metody split.
Zwraca ona tablic ę czterech warto ści, którymi s ą kolejno: znak (1 albo –1), część u łamkowa
(jako łańcuch), podstawa wyk ładnika (zawsze 10) i sam wyk ładnik.
BigDecimal("105000").split
# => [1, "105", 10, 6]
# 105000 = 0.105*(10**6)
BigDecimal("-0.005").split
# => [-1, "5", 10, -2]
# -0.005 = (-1) * (0.5*(10**-2))
Dobrym sposobem przetestowania ró żnej dok ładno ści reprezentowania liczb rzeczywistych
jest u życie dowolnej liczby posiadaj ącej niesko ńczon ą reprezentacj ę dziesi ętn ą (na przyk ład
2
). Domy ślnie klasa BigDecimal zapewnia dok ładno ść 16 cyfr znacz ących, co w przybli żeniu
3
odpowiada dok ładno ści liczb zmiennopozycyjnych podwójnej dok ładno ści.
(BigDecimal("2") / BigDecimal("3")).to_s # => "0.6666666666666667E0"
2.0/3 # => 0.666666666666667
Wi ększ ą dok ładno ść mo żna uzyska ć, przekazuj ąc żądan ą liczb ę cyfr znacz ących jako parametr
konstruktora obiektu BigDecimal. Dok ładno ść zwi ększana jest w porcjach co 4 — i tak argu-
ment z zakresu 1 – 4 oznacza 16 cyfr znacz ących, argument z zakresu 5 – 8 — 20 cyfr znacz ą-
cych, argument z zakresu 9 – 12 — 24 cyfry znacz ące itd.
def dwie_trzecie(precision)
(BigDecimal("2", precision) / BigDecimal("3")).to_s
end
dwie_trzecie(1) # => "0.6666666666666667E0"
dwie_trzecie(4) # => "0.666
dwie_trzecie(5) # => "0.66666666666666666667E0"
dwie_trzecie(9) # => "0.666666666666666666666667E0"
dwie_trzecie(13) # => "0.6666666666666666666666666667E0"
Nie zawsze wykorzystywane s ą wszystkie cyfry znacz ące. Przyk ładowo, liczby BigDecimal
("2") i BigDecimal("2.000000000000") uwa żane s ą za równe, mimo i ż druga reprezento-
wana jest z wi ększ ą liczb ą cyfr znacz ących.
Precyzj ę, z jak ą reprezentowana jest dana liczba, mo żna uzyska ć za pomoc ą metody BigDe-
cimal#precs. Metoda ta zwraca tablic ę dwóch warto ści, którymi s ą (kolejno): liczba aktualnie
wykorzystywanych cyfr znacz ących i liczba wszystkich cyfr znacz ących. Poniewa ż (jak wcze-
śniej wspominali śmy) cyfry znacz ące przydzielane s ą w blokach po cztery pozycje, obydwie
te warto ści zawsze s ą wielokrotno ści ą 4:
BigDecimal("2").precs # => [4, 8]
BigDecimal("2.000000000000").precs # => [4, 20]
BigDecimal("2.000000000001").precs # => [16, 20]
Wynik standardowych operacji arytmetycznych na liczbach typu BigDecimal reprezentowa-
ny jest z mo żliwie najwi ększ ą dok ładno ści ą, w szczególno ści wynik mno żenia lub dzielenia
posiada wi ęcej cyfr znacz ących ni ż ka żdy z argumentów:
74 | Rozdzia ł 2. Liczby(a = BigDecimal("2.01")).precs # => [8, 8]
(b = BigDecimal("3.01")).precs # => [8, 8]
(product = a * b).to_s("F") # => "6.0501"
product.precs # => [8, 24]
Mo żna za żąda ć wykonania danej operacji arytmetycznej ze wskazan ą dok ładno ści ą; w tym
celu zamiast operatora arytmetycznego nale ży u żyć metody add, sub, mul lub div:
dwie_trzecie = (BigDecimal("2", 13) / 3)
dwie_trzecie.to_s
# => "0.666666666666666666666666666666666667E0"
(dwie_trzecie + 1).to_s
# => "0.1666666666666666666666666666666666667E1"
dwie_trzecie.add(1, 1).to_s # => "0.2E1"
dwie_trzecie.add(1, 4).to_s # => "0.1667E1"
Obliczenia z udzia łem liczb BigDecimal przebiegaj ą znacz ąco wolniej ni ż obliczenia na licz-
bach zmiennopozycyjnych. Przyczyn ą tego jest nie tylko wi ększa (na ogó ł) liczba cyfr znacz ą-
cych, lecz tak że brak bezpo średniego wsparcia sprz ętowego — arytmetyka zmiennopozycyj-
na realizowana jest za pomoc ą rodzimych instrukcji procesora, natomiast liczby BigDecimal
przechowywane s ą jako tablice cyfr dziesi ętnych.
W module BigMath standardowej biblioteki j ęzyka Ruby zdefiniowane s ą metody wykonuj ą-
ce operacje matematyczne na obiektach BigDecimal z dowolną dok ładno ści ą. W sk ład tych
operacji wchodz ą mi ędzy innymi pierwiastek kwadratowy (sqrt), logarytm naturalny (log)
i funkcja wyk ładnicza (exp), oraz funkcje trygonometryczne, m.in. sinus (sin), cosinus (cos)
i arcus tangens (atan).
Wszystkie te metody posiadaj ą parametr okre ślaj ący żądan ą liczb ę cyfr znacz ących wyniku;
faktyczna liczba cyfr znacz ących wyniku mo że być wi ększa, lecz „nadmiarowe” cyfry mog ą
nie by ć wiarygodne.
require 'bigdecimal/math'
include BigMath
dwa = BigDecimal("2")
BigMath::sqrt(dwa, 10).to_s("F")
# => "1.4142135623730950488016883515"
W powy ższym przyk ładzie wynik ma 28 cyfr znacz ących, mimo i ż za żądali śmy tylko 10.
W rzeczywisto ści warto ść pierwiastka z 2 z dok ładno ści ą do 28 cyfr znacz ących wynosi
1,4142135623730950488016887242, a wi ęc cyfry znacz ące pocz ąwszy od 25. s ą bezwarto ściowe.
Skoro jednak żądali śmy dok ładno ści 10-cyfrowej, mo żemy odrzuci ć pozostałe cyfry, zaokr ą-
glaj ąc liczb ę do tej że dok ładno ści za pomoc ą metody BigDecimal#round:
BigMath::sqrt(two, 10).round(10).to_s("F") # => "1.4142135624"
Oczywi ście nic nie stoi na przeszkodzie uzyskaniu wi ększej dok ładno ści:
BigMath::sqrt(two, 28).round(28).to_s("F")
# => "1.4142135623730950488016887242"
W module BigMath definiowane s ą tak że metody klasowe BigDecimal.PI i BigDecimal.E
zwracaj ące warto ść liczby przest ępnej (odpowiednio) π i e z dowoln ą dok ładno ści ą:
Math::PI # => 3.14159265358979
Math::PI.class # => Float
BigDecimal.PI(1).to_s # => "0.31415926535897932364198143965603E1"
BigDecimal.PI(20).to_s # => "0.3141592653589793238462643383279502883919859293521427E1"
2.3. Reprezentowanie liczb z dowoln ą dok ładno ści ą | 75Patrz tak że
• Na razie metoda BigMath::log obliczaj ąca logarytm naturalny z liczby BigDecimal o do-
kładno ści wi ększej ni ż 10 cyfr znacz ących jest bardzo wolna. Jeden ze sposobów łagodze-
nia skutków tego mankamentu opisany jest w recepturze 2.7.
• W recepturze 2.4, „Reprezentowanie liczb wymiernych”, prezentujemy sposób bezwzgl ęd-
nie dok ładnego reprezentowania liczb, które w rozwini ęciu dziesi ętnym s ą niesko ńczony-
2
mi u łamkami okresowymi (jak liczba ).
3
• Gdy przegl ąda si ę dokument RDoc wygenerowany dla standardowej biblioteki j ęzyka
Ruby, klasa BigDecimal wydaje si ę niemal nieudokumentowana. W rzeczywisto ści ist-
nieje jej obszerny opis (po angielsku i japo ńsku) dost ępny w pakiecie źród łowym j ęzyka
Ruby oraz w internecie — pod has łem „BigDecimal: An extension library for Ruby”.
2.4. Reprezentowanie liczb wymiernych
Problem
2Chcemy u żywa ć bezwzgl ędnie dok ładnych reprezentacji liczb wymiernych (takich jak ), na-
3
wet je żeli ich dziesi ętna reprezentacja jest niesko ńczona.
Rozwiązanie
Liczb ą wymiern ą (rational number) nazywamy liczb ę, któr ą mo żna przedstawi ć w postaci ilo-
razu (u łamka) dwóch liczb ca łkowitych. W j ęzyku Ruby liczby wymierne reprezentowane s ą
w postaci klasy Rational.
float = 2.0/3.0 # => 0.666666666666667
float * 100 # => 66.6666666666667
float * 100 / 42 # => 1.58730158730159
require 'rational'
rational = Rational(2, 3) # => Rational(2, 3)
rational.to_f # => 0.666666666666667
rational * 100 # => Rational(200, 3)
rational * 100 / 42 # => Rational(100, 63)
Dyskusja
Obiekty klasy Rational nadaj ą si ę do reprezentowania liczb, które nie mog ą by ć reprezen-
towane w żaden inny sposób. Arytmetyka liczb typu Rational jest arytmetyk ą bezwzgl ędnie
dokładn ą.
Poniewa ż zarówno licznik, jak i mianownik u łamka reprezentuj ącego liczb ę typu Rational
mo że być obiektem klasy Bignum, liczby typu Rational nadaj ą si ę do reprezentowania war-
to ści wi ększych oraz mniejszych od tych, które mog ą by ć reprezentowane w postaci liczb
zmiennopozycyjnych. Efektywność arytmetyki obiektów Rational jest jednak jeszcze mniej-
sza ni ż w przypadku obiektów BigDecimal; te ostatnie wydaj ą si ę te ż bardziej intuicyjne ni ż
liczby typu Rational, bowiem zwykli śmy postrzega ć liczby w kategoriach ich rozwini ęcia
dziesi ętnego.
76 | Rozdzia ł 2. LiczbyZatem obiektów Rational nale ży u żywać tylko wtedy, gdy konieczne jest zachowanie abso-
lutnej dok ładno ści oblicze ń. Nale ży pami ętać, i ż mo żna je łączyć tylko z obiektami klasy Ra-
tional, Fixnum i Bignum — „mieszanie” ich z obiektami klasy BigDecimal lub Float daje
w rezultacie wynik typu zmiennopozycyjnego, bezpowrotnie tracimy wi ęc wówczas dok ład-
no ść, na której tak bardzo nam zale ży.
10 + Rational(2,3) # => Rational(32, 3)
require 'bigdecimal'
BigDecimal('10') + Rational(2,3) # => 10.6666666666667
Metody zdefiniowane w module Math implementuj ą operacje w rodzaju pierwiastka kwadra-
towego (sqrt), a wi ęc operacje, które dla wymiernego argumentu mog ą dawa ć wynik niewy-
mierny (czyli niedaj ący si ę reprezentowa ć w postaci obiektu Rational). Z tego wzgl ędu wy-
nik ten jest liczb ą zmiennopozycyjn ą:
Math::sqrt(Rational(2,3)) # => 0.816496580927726
Math::sqrt(Rational(25,1)) # => 5.0
Math::log10(Rational(100, 1)) # => 2.0
Biblioteka mathn wzbogaca matematyczn ą funkcjonalno ść Ruby o nowe elementy, mi ędzy
innymi przedefiniowuj ąc metod ę Math::sqrt tak, że dla argumentu b ęd ącego kwadratem ja-
kiej ś liczby ca łkowitej zwraca t ę liczb ę jako obiekt typu Fixnum, nie Float. W wielu przypad-
kach przyczynia si ę to do poprawy dok ładno ści oblicze ń.
require 'mathn'
Math::sqrt(Rational(2,3)) # => 0.816496580927726
Math::sqrt(Rational(25,1)) # => 5
Math::sqrt(25) # => 5
Math::sqrt(25.0) # => 5.0
Patrz tak że
• Niezale żna biblioteka rfloat (http://blade.nagaokaut.ac.jp/~sinara/ruby/rfloat) definiuje klas ę
liczb zmiennopozycyjnych, zrealizowanych na bazie obiektów Rational. W efekcie daje
to po łączenie wygody operowania liczbami zmiennopozycyjnymi z dok ładno ści ą w łaści-
w ą liczbom wymiernym:
puts 1.0 / 3 #=> 0.333333333333333
puts RFloat("1") / 3 #=> 0.333333333333333
puts RFloat("61.1") - RFloat("60.0") == RFloat("1.1") #=> true
puts 61.1 - 60.0 == 1.1 #=> false
• W dokumencie RCR 320 (http://www.rcrchive.net/rcr/show/320) zawarte s ą propozycje zmie-
rzaj ące do lepszego wspó łdzia łania klas Rational i Float, mi ędzy innymi prezentowana
jest metoda Rational#approximate dokonująca konwersji liczby zmiennopozycyjnej na
liczb ę wymiern ą, na przyk ład liczby 0.1 na Rational(1, 10).
2.5. Generowanie liczb pseudolosowych
Problem
Chcemy generowa ć liczby pseudolosowe, wybiera ć losowo elementy z pewnej struktury, a tak-
że generowa ć w sposób powtarzalny takie same ci ągi liczb „losowych” na potrzeby testowa-
nia aplikacji.
2.5. Generowanie liczb pseudolosowych | 77Rozwiązanie
Metoda Kernel#rand wywo łana bez argumentów zwraca (w postaci zmiennopozycyjnej) losow ą
liczb ę o rozk ładzie jednostajnym z zakresu od 0 do 1.
rand # => 0.517297883846589
rand # => 0.946962603814814
Gdy wywo łamy t ę metod ę z liczb ą ca łkowit ą n jako pojedynczym argumentem, otrzymamy
w wyniku losow ą liczb ę ca łkowit ą z zakresu od 0 do n-1.
rand(5) # => 0
rand(5) # => 4
rand(5) # => 3
rand(1000) # => 39
Dyskusja
Wywo ływanie metody Kernel#rand z pojedynczym argumentem jest u żytecznym sposobem
budowania rozmaitych zada ń bazuj ących na zjawiskach losowych. W poni ższym fragmencie
wybierany jest losowo element z tablicy trzyelementowej:
a = ['item1', 'item2', 'item3']
a[rand(a.size)] # => "item3"
Aby losowo wybra ć klucz lub warto ść z hasza, nale ży utworzy ć tablicę (kluczy lub warto ści)
i wybra ć z niej losowo element:
m = { :key1 => 'value1',
:key2 => 'value2',
:key3 => 'value3' }
values = m.values
values[rand(values.size)] # => "value1"
Efektem wykonania poni ższego kodu jest wygenerowanie słowa mo żliwego do wymówienia,
lecz na ogó ł bezsensownego:
def random_word
letters = { ?v => 'aeiou',
?c => 'bcdfghjklmnprstvwyz' }
word = ''
'cvcvcvc'.each_byte do |x|
source = letters[x]
word << source[rand(source.length)].chr
end
return word
end
random_word # => "rutonid"
random_word # => "wumuwiz"
random_word # => "mekerah"
random_word # => "guwenuh"
random_word # => "civocok"
Interpreter j ęzyka Ruby inicjuje generator liczb pseudolosowych warto ści ą startow ą utworzo-
n ą na podstawie bie żącego wskazania czasu i numeru procesu. Aby uzyska ć powtarzalno ść
generowanych ci ągów liczb, nale ży inicjacj ę t ę przeprowadzi ć samodzielnie, ka żdorazowo
przy u życiu tej samej warto ści. S łu ży do tego metoda Kernel#srand:
#Kilka liczb pseudolosowych zale żnych od bie żącego czasu i numeru procesu
rand(1000) # => 187
rand(1000) # => 551
rand(1000) # => 911
78 | Rozdzia ł 2. Liczby#Zainicjowanie generatora warto ści ą startow ą 1
srand 1
rand(1000) # => 37
rand(1000) # => 235
rand(1000) # => 908
#Ponowne zainicjowanie generatora warto ści ą startow ą 1
srand 1
Patrz tak że
• Receptura 4.10, „Tasowanie tablicy”.
• Receptura 5.11, „Losowy wybór z listy zdarze ń o ró żnych prawdopodobie ństwach”.
• Receptura 6.9, „Losowy wybór wiersza z pliku”.
• Biblioteka Facets implementuje wiele metod umo żliwiaj ących losowy wybór elementów
ze struktur danych: Array#pick, Array#rand_subset, Hash#rand_pair itp., a tak że meto-
d ę String#random generuj ącą losowe łańcuchy.
• Biblioteka rand.rb Christiana Neukirchena (http://chneukirchen.org/blog/static/projects/rand.
html) tak że implementuje wiele metod losowego wyboru elementów.
2.6. Konwersje mi ędzy ró żnymi podstawami liczenia
Problem
Chcemy wyra ża ć warto ści liczbowe w ró żnych uk ładach liczenia.
Rozwiązanie
Stałe binarne, ósemkowe i szesnastkowe zapisuje si ę, rozpoczynaj ąc od prefiksu (odpowied-
nio) 0b, 0o i 0x:
0b100 # => 4
0o100 # => 64
0x100 # => 256
Mo żliwa jest tak że konwersja warto ści liczbowej do równowa żnego jej łańcucha, wyra żonego
w systemie liczenia o dowolnej podstawie z zakresu od 2 do 36 — nale ży podstaw ę t ę poda ć
jako argument wywo łania metody Integer#to_s.
42.to_s(10) # => "42"
-100.to_s(2) # => "-1100100"
255.to_s(16) # => "ff"
1442151747.to_s(36) # => "number"
Konwersj ę odwrotn ą wykonuje si ę za pomoc ą metody String#to_i wywo ływanej z podsta-
w ą liczenia jako argumentem:
"1045".to_i(10) # => 1045
"-1001001".to_i(2) # => -73
"abc".to_i(16) # => 2748
"abc".to_i(20) # => 4232
"number".to_i(36) # => 1442151747
2.6. Konwersje mi ędzy ró żnymi podstawami liczenia | 79"zz1z".to_i(36) # => 1678391
"abcdef".to_i(16) # => 11259375
"AbCdEf".to_i(16) # => 11259375
Oto kilka przyk ładów tego typu „odwrotnych” konwersji — liczb dziesi ętnych na łańcuchy,
przy ró żnych podstawach liczenia:
42.to_s(10) # => "42"
-100.to_s(2) # => "-1100100"
255.to_s(16) # => "ff"
1442151747.to_s(36) # => "number"
A oto kilka przyk ładów konwersji niewykonalnych:
"6".to_i(2) # => 0 #Niedozwolona cyfra dwójkowa (6)
"0".to_i(1) # ArgumentError: illegal radix 1 #Niedozwolona podstawa (1)
40.to_s(37) # ArgumentError: illegal radix 37 #Niedozwolona podstawa (37)
Dyskusja
Za pomoc ą metody Integer#to_s mo żemy tworzy ć łańcuchy stanowi ące reprezentacje liczb
w układzie liczenia o dowolnej podstawie od 2 do 36. Za pomoc ą metody String#to_i mo że-
my łańcuchy takie przetwarza ć na liczby ca łkowite. W dobrze znanym uk ładzie o podstawie
2 (binarnym) mamy tylko dwie cyfry, 0 i 1. W uk ładzie o podstawie 36 (heksatridecymalnym)
mamy 36 cyfr, reprezentowanych przez cyfry dziesi ętne (0 .. 9) i litery alfabetu angielskiego
(a .. z); uk ład ten wykorzystywany jest zwykle do generowania mnemonicznych odpowied-
ników du żych liczb ca łkowitych:
2569066195565326.to_s(36) # "panoptikum"
1052443607.to_s(36) # "helion"
Spo śród podstaw liczenia wi ększych ni ż 36 jedyne praktyczne znaczenie ma podstawa 64,
u żywana do kodowania np. za łączników MIME do poczty elektronicznej. Kodowaniu podle-
gaj ą jednak łańcuchy, nie liczby; metody wykonuj ące to kodowanie znale źć mo żna w biblio-
tece base64.
Patrz tak że
• Receptura 12.5, „Wprowadzanie graficznego kontekstu za pomoc ą wykresów typu Spar-
kline”, oraz receptura 14.5, „Wysy łanie poczty elektronicznej”, ilustruj ą zastosowanie bi-
blioteki base64.
2.7. Logarytmy
Problem
Nale ży policzy ć logarytm pewnej liczby, by ć mo że bardzo du żej.
Rozwiązanie
Metoda Math.log oblicza logarytm naturalny, czyli logarytm o podstawie e:
Math.log(1) # => 0.0
Math.log(Math::E) # => 1.0
Math.log(10) # => 2.30258509299405
Math::E ** Math.log(25) # => 25.0
80 | Rozdzia ł 2. LiczbyMetoda Math.log10 oblicza natomiast logarytm dziesi ętny:
Math.log10(1) # => 0.0
Math.log10(10) # => 1.0
Math.log10(10.1) # => 1.00432137378264
Math.log10(1000) # => 3.0
10 ** Math.log10(25) # => 25.0
Zmiana podstawy logarytmu równowa żna jest pomno żeniu jego warto ści przez sta łą warto ść,
zale żn ą jedynie od obydwu podstaw. Maj ąc dany logarytm pewnej warto ści x przy podstawie
b , mo żemy z łatwo ści ą otrzymać logarytm tej warto ści przy innej podstawie b :1 2
log ()xb1log ()x =b2 log ()bb 21
module Math
def Math.logb(num, base)
log(num) / log(base)
end
end
Dyskusja
Logarytm jest odwrotno ści ą funkcji wykładniczej. Warto ść logarytmu z liczby x przy podsta-
wie k jest warto ści ą pot ęgi, do której nale ży podnie ść k, aby otrzyma ć x:
wlog x = w ⇒ k = xk
Zatem na przyk ład Math.log10(1000) == 3.0, poniewa ż 10 podniesione do trzeciej pot ęgi
równa si ę 1000, natomiast Math.log(Math::E) == 1, poniewa ż logarytmowanie podstawy
zawsze daje w wyniku 1.
Warto ści logarytmów tej samej warto ści o ró żnych podstawach s ą ze sob ą powi ązane w spo-
sób wy żej pokazany; ró żne podstawy logarytmowania wykorzystywane bywaj ą do ró żnych
celów. Aplikacje naukowe najczęściej wykorzystuj ą logarytmy naturalne — w j ęzyku Ruby
logarytmy naturalne maj ą najefektywniejsz ą implementacj ę. Logarytmy dziesi ętne u żywane
s ą zwykle do wizualizacji zjawisk charakteryzuj ących si ę parametrami mog ącymi zmienia ć
si ę w zakresie kilku rz ędów wielko ści, czego przyk ładem mo że by ć skala kwasowo ści pH,
skala Richtera wyra żaj ąca nasilenie trz ęsienia ziemi czy te ż poziom nat ężenia d źwi ęku wyra-
żany w decybelach.
Je żeli trzeba policzy ć du żą liczb ę logarytmów przy podstawie nieobs ługiwanej w sposób ro-
dzimy w j ęzyku Ruby, mo żna uczyni ć obliczenia bardziej efektywnymi, obliczaj ąc a priori
mianownik u łamka prezentowanego wcze śniej jako zale żno ść logarytmów przy ró żnych pod-
stawach. W poni ższym przyk ładzie wyliczany jest ci ąg logarytmów przy podstawie 2, po śred-
nio za pomoc ą logarytmów naturalnych:
mianownik = Math.log(2)
(1..6).collect { |x| Math.log(x) / mianownik}
# => [0.0, 1.0, 1.58496250072116, 2.0, 2.32192809488736, 2.58496250072116]
Korzystamy tu z zale żno ści
ln()x
log x =2 ln()2
przy czym ln(2) wyliczany jest tylko raz.
2.7. Logarytmy | 81Metody logarytmiczne zdefiniowane w module Math dopuszczaj ą w roli argumentów liczby
całkowite i liczby zmiennopozycyjne, ale nie obiekty BigDecimal czy BigNum. Nie jest to do-
bra wiadomo ść, bowiem logarytmy cz ęsto obliczane s ą dla argumentów ekstremalnie du-
żych. W module BigMath zdefiniowana jest funkcja obliczaj ąca logarytm naturalny z liczby
BigDecimal, jest ona jednak bardzo wolna.
T ę nieefektywno ść mo żna jednak z łagodzi ć, wykorzystuj ąc fakt, że liczby BigDecimal repre-
zentowane s ą w formie trzech komponentów — części u łamkowej, podstawy i wyk ładnika —
yoraz korzystaj ąc ze znanych zale żno ści log (x *y ) = log (x ) + log (y ) i .log (x ) = y *log()x
require 'bigdecimal'
require 'bigdecimal/math'
require 'bigdecimal/util'
module BigMath
alias :log_slow :log
def log(x, prec)
if x <= 0 || prec <= 0
raise ArgumentError, "Zerowy lub ujemny argument funkcji logarytmicznej"
end
return x if x.infinite? || x.nan?
sign, fraction, power, exponent = x.split
fraction = BigDecimal(".#{fraction}")
power = power.to_s.to_d
log_slow(fraction, prec) + (log_slow(power, prec) * exponent)
end
end
Podobnie jak BigMath:Log, powy ższa funkcja zwraca wartość logarytmu z co najmniej prec
cyframi znacz ącymi — „co najmniej”, bo cyfr tych mo że by ć wi ęcej, jednak nie musz ą by ć one
wiarygodne. Aby wyeliminowa ć z łudny efekt zwi ększonej dok ładno ści, mo żna otrzyman ą
warto ść zaokr ąglić, wykorzystuj ąc metod ę BigDecimal#round.
include BigMath
number = BigDecimal("1234.5678")
Math.log(number) # => 7.11847622829779
prec = 50
BigMath.log_slow(number, prec).round(prec).to_s("F")
# => "7.11847622829778629250879253638708184134073214145175"
BigMath.log(number, prec).round(prec).to_s("F")45175"
BigMath.log(number ** 1000, prec).round(prec).to_s("F")
# => "7118.47622829778629250879253638708184134073214145175161"
Oczywi ście, maj ąc dany logarytm naturalny danej liczby, mo żemy łatwo obliczy ć warto ść jej
logarytmu przy dowolnej innej podstawie:
# Logarytm dziesi ętny z warto ści 1000 ** 1000
# To jedynka i 3000 zer
huge_number = BigDecimal("1000") ** 1000
base = BigDecimal("10")
(BigMath.log(huge_number, 100) / BigMath.log(base, 100)).to_f
# => 3000.0
Jak to dzia ła? Liczby BigDecimal reprezentowane s ą wewn ętrznie w tzw. notacji naukowej,
wczyli w postaci . Zgodnie z prezentowanymi wcze śniej zale żno ściami, mo żemy napisa ćf *10
wlog (f *10 ) = log()f +w*log (10)
Tym samym obliczanie jednego logarytmu dla du żego argumentu sprowadza si ę do oblicze-
nia dwóch logarytmów dla znacznie mniejszych argumentów f i 10.
82 | Rozdzia ł 2. LiczbyPatrz tak że
• Kilkaset lat temu matematycy po świ ęcali wiele lat swego życia na konstruowanie tablic
logarytmicznych przydatnych naukowcom i in żynierom (patrz http://en.wikipedia.org/wiki/
Logarithm#tables_of_logarithms). Ma ło kto docenia dzi ś fakt, że dzi ęki komputerom mo że
swe życie sp ędza ć bardziej produktywnie.
2.8. Średnia, mediana i moda
Problem
Maj ąc tablic ę liczb, nale ży obliczy ć ich średni ą, median ę i mod ę.
Rozwiązanie
Najbardziej bodaj znan ą miar ą statystyczn ą zbioru liczb jest ich średnia arytmetyczna (arithme-
tic mean), czyli iloraz sumy elementów przez ich ilo ść:
def mean(array)
array.inject(0) { |sum, x| sum += x } / array.size.to_f
end
mean([1,2,3,4]) # => 2.5
mean([100,100,100,100.1]) # => 100.025
mean([-100, 100]) # => 0.0
mean([3,3,3,3]) # => 3.0
Median ą nazywamy element środkowy co do warto ści — liczba elementów nie wi ększych od
niego jest taka sama jak liczba elementów od niego nie mniejszych. W tablicy posortowanej
(rosn ąco lub malej ąco) jest to element środkowy. Oczywi ście element o tej w łasno ści istnieje
tylko wtedy, gdy liczba elementów tablicy jest nieparzysta; w tablicy o parzystej liczbie ele-
mentów jako median ę przyjmuje si ę średni ą arytmetyczn ą obydwu środkowych elementów.
def median(array, already_sorted=false)
return nil if array.empty?
array = array.sort unless already_sorted
m_pos = array.size / 2
return array.size % 2 == 1 ? array[m_pos] : mean(array[m_pos-1..m_pos])
end
median([1,2,3,4,5]) # => 3
median([5,3,2,1,4]) # => 3
median([1,2,3,4]) # => 2.5
median([1,1,2,3,4]) # => 2
median([2,3,-100,100]) # => 2.5
median([1, 1, 10, 100, 1000]) # => 10
Mod ą nazywamy warto ść, która w tablicy powtarza si ę najczęściej. Je żeli ka żdy element tablicy
ma unikaln ą warto ść, moda nie jest okre ślona; je śli kilka elementów wyst ępuje z równ ą czę-
stotliwo ści ą, mamy do czynienia z tablic ą wielomodaln ą. Zale żnie od konkretnego przypadku,
mo żna w charakterze mody wybra ć dowolny ze wspomnianych elementów, lub te ż uwzględ-
ni ć wszystkie te elementy.
def modes(array, find_all=true)
histogram = array.inject(Hash.new(0)) { |h, n| h[n] += 1; h }
modes = nil
histogram.each_pair do |item, times|
2.8. Średnia, mediana i moda | 83 modes << item if modes && times == modes[0] and find_all
modes = [times, item] if (!modes && times>1) or (modes && times>modes[0])
end
return modes ? modes[1...modes.size] : modes
end
modes([1,2,3,4]) # => nil # moda nieokre ślona
modes([1,1,2,3,4]) # => [1]
modes([1,1,2,2,3,4]) # => [1, 2]
modes([1,1,2,2,3,4,4]) # => [1, 2, 4]
modes([1,1,2,2,3,4,4], false) # => [1]
modes([1,1,2,2,3,4,4,4,4,4]) # => [4]
Dyskusja
Średnia arytmetyczna jest zarówno prosta koncepcyjnie, jak i łatwa do obliczenia. W przed-
stawionej powy żej implementacji średnia arytmetyczna jest zawsze liczb ą zmiennopozycyjn ą,
nawet je żeli elementy tablicy s ą liczbami ca łkowitymi (Fixnum). Aby obliczy ć średni ą liczb
BigDecimal lub Rational, nale ży usun ąć z implementacji ko ńcowe wywo łanie metody to_f:
def mean_without_float_conversion(array)
array.inject(0) { |x, sum| sum += x } / array.size
end
require 'rational'
numbers = [Rational(2,3), Rational(3,4), Rational(6,7)]
mean(numbers)
# => 0.757936507936508
mean_without_float_conversion(numbers)
# => Rational(191, 252)
Mediana bywa niekiedy miar ą bardziej reprezentatywn ą ni ż średnia arytmetyczna — na t ę
ostatni ą maj ą bowiem wp ływ wszystkie elementy, nawet te znacznie odbiegaj ące warto ści ą
od pozosta łych. Przyk ładowo, ró żne agencje rz ądowe publikuj ą co jaki ś czas statystyk ę w ro-
dzaju przeci ętnego (median) dochodu gospodarstwa domowego, zamiast dochodu średniego
(mean). Gdyby za miar ę zamo żno ści spo łecze ństwa przyj ąć średni ą arytmetyczn ą dochodów,
bogatsze gospodarstwa w nieuzasadniony sposób zawy żałyby t ę statystyk ę. Spójrzmy na po-
ni ższy przyk ład:
mean([1, 100, 100000]) # => 33367.0
median([1, 100, 100000]) # => 100
mean([1, 100, -1000000]) # => -333299.666666667
median([1, 100, -1000000]) # => 1
W przeciwie ństwie do średniej arytmetycznej, mediana zdefiniowana jest tak że dla tablicy
zło żonej z elementów innych ni ż liczby — obliczanie średniej arytmetycznej wymaga doda-
wania i dzielenia, znajdowanie mediany wymaga tylko porówna ń. Wyj ątkiem w tym wzgl ę-
dzie jest jednak tablica o parzystej liczbie elementów, bo wtedy mediana jest obliczana jako
średnia arytmetyczna dwóch elementów — chyba że zmienimy ad hoc definicj ę mediany
w tym przypadku.
W poni ższym przyk ładzie podejmowana jest próba znalezienia mediany w dwóch tablicach
ła ńcuchów; druga tablica zawiera parzyst ą liczb ę elementów i próba „u średnienia” ła ńcuchów
"b" i "c" ko ńczy si ę wyj ątkiem („ łańcuch nie mo że by ć skonwertowany na liczb ę”):
median(["a", "z", "b", "l", "m", "j", "b"])
# => "j"
median(["a", "b", "c", "d"])
# TypeError: String can't be coerced into Fixnum
84 | Rozdzia ł 2. LiczbyOdchylenie standardowe
Wielko ści ą pokrewn ą średniej arytmetycznej jest odchylenie standardowe (standard deviation).
Stanowi ono miar ę „rozrzutu” warto ści elementów zbioru: im bardziej elementy te zbli żone
s ą do swej średniej arytmetycznej, tym ich odchylenie standardowe jest mniejsze; odchyle-
nie standardowe zbioru elementów identycznych równe jest zero. Informacja dostarczana
przez średni ą arytmetyczn ą jest niepe łna, je śli nie towarzyszy jej informacja o odchyleniu
standardowym.
def mean_and_standard_deviation(array)
m = mean(array)
variance = array.inject(0) { |variance, x| variance += (x - m) ** 2 }
return m, Math.sqrt(variance/(array.size-1))
end
#Wszystkie elementy listy s ą bliskie swojej średniej arytmetycznej,
#wi ęc ich odchylenie standardowe jest niewielkie
mean_and_standard_deviation([1,2,3,1,1,2,1])
# => [1.57142857142857, 0.786795792469443]
#Element 1000 ma nie tylko wp ływ na średni ą arytmetyczn ą, lecz tak że
#zwi ększa odchylenie standardowe
mean_and_standard_deviation([1,2,3,1,1,2,1000])
# => [144.285714285714, 377.33526837801]
Je żeli warto ści elementów zbioru podlegaj ą rozk ładowi normalnemu (Gaussa) — bo na przy-
kład stanowi ą wyniki ró żnych pomiarów tej samej wielko ści — oko ło 68% procent tych ele-
mentów nie ró żni si ę od średniej arytmetycznej o wi ęcej ni ż odchylenie standardowe, za ś
oko ło 95% procent nie odbiega od średniej o wi ęcej ni ż podwójne odchylenie standardowe.
Regu ła ta przejawia si ę tym wyra źniej, im wi ększa jest liczba elementów.
Patrz tak że
• Kilka implementacji ró żnych miar statystycznych w j ęzyku Ruby (http://dada.perl.it/shootout/
moments.ruby.html).
• Aby wykonywa ć za pomoc ą j ęzyka Ruby bardziej z ło żone analizy statystyczne, spróbuj
po łączy ć go z bibliotek ą GNU Scientific Library (http://ruby-sgl.sourceforge.net/).
• Klasa Stats na serwerze WWW Mongrel (http://mongrel.rubyforge.org) implementuje inne
algorytmy obliczania średniej i odchylenia standardowego, szybsze i bardziej przydatne
do powtarzalnego obliczania średniej rozrastaj ącej si ę serii danych.
2.9. Konwersja stopni na radiany i odwrotnie
Problem
Funkcje trygonometryczne modu łu Math wymagaj ą argumentów wyra żonych w radianach
(kąt pe łny to 2 π radianów), tymczasem bardziej naturaln ą konwencj ą w komunikacji z u żyt-
kownikiem wydaje si ę wyra żanie wielko ści k ątów w stopniach (k ąt pe łny to 360 °).
Rozwiązanie
Najbardziej oczywisty sposób rozwi ązania powy ższego problemu polega na dodaniu do kla-
sy Numeric metody dokonuj ącej konwersji stopni na radiany:
2.9. Konwersja stopni na radiany i odwrotnie | 85class Numeric
def degrees
self * Math::PI / 180 #PI radianów to 180 stopni
end
end
Odt ąd mo żna b ędzie traktowa ć ka żdy obiekt numeryczny jako miar ę k ąta wyrażon ą w stop-
niach i za pomoc ą metody degrees dokonywa ć jej konwersji na radiany, gdy obiekt ten u ży-
ty zostanie jako argument funkcji trygonometrycznej:
90.degrees # => 1.5707963267949
Math::tan(45.degrees) # => 1.0
Math::cos(90.degrees) # => 6.12303176911189e-17
Math::sin(90.degrees) # => 1.0
Math::sin(89.9.degrees) # => 0.999998476913288
Math::sin(45.degrees) # => 0.707106781186547
Math::cos(45.degrees) # => 0.707106781186548
Dyskusja
Nazwa łem metod ę konwersyjn ą degrees przez analogi ę do nazewnictwa metod definiowa-
nych przez Rails (np. hours). Przyczynia si ę to do poprawy czytelno ści kodu, jednak że mo ż-
na si ę niekiedy zastanawia ć, dlaczego 45.degrees ma by ć równe liczbie zmiennopozycyjnej
0.785398163397448.
Zawsze mo żna zastosowa ć nazewnictwo bardziej intuicyjne, jak degrees_to_radians, b ąd ź
te ż posłu żyć si ę gemem units autorstwa Lucasa Carlsona, który to gem pozwala u żytkowni-
kowi na definiowanie w łasnych regu ł konwersji jednostek i śledzenie, w jakich jednostkach
wyra żane s ą poszczególne warto ści liczbowe:
require 'rubygems'
require 'units/base'
class Numeric
remove_method(:degrees) # Usuni ęcie implementacji metody degrees zdefiniowanej
# w Rozwi ązaniu
add_unit_conversions(:angle => { :radians => 1, :degrees => Math::PI/180 })
add_unit_aliases(:angle => { :degrees => [:degree], :radians => [:radian] })
end
90.degrees # => 90.0
90.degrees.unit # => :degrees
90.degrees.to_radians # => 1.5707963267949
90.degrees.to_radians.unit # => :radians
1.degree.to_radians # => 0.0174532925199433
1.radian.to_degrees # => 57.2957795130823
Nale ży jednak mie ć świadomo ść, że jedynym efektem powy ższych zabiegów jest poprawie-
nie czytelno ści kodu; metody trygonometryczne nie s ą świadome zdefiniowanych jednostek
i w dalszym ci ągu argumenty ich wywo ła ń nale ży wyrażać w radianach
5# Nie tak:
Math::sin(90.degrees) # => 0.893996663600558
# Ale tak:
Math::sin(90.degrees.to_radians) # => 1.0

5
Nazwa degrees oznacza tu jednostk ę zdefiniowan ą w przyk ładzie ilustruj ącym u życie gemu units, a nie me-
tod ę degrees prezentowan ą w „Rozwi ązaniu” — przyp. t łum.
86 | Rozdzia ł 2. LiczbyOczywi ście mo żliwe jest takie przedefiniowanie metod trygonometrycznych, by sta ły si ę one
świadome u żywanych jednostek:
class << Math
alias old_sin sin
def sin(x)
old_sin(x.unit == :degrees ? x.to_radians : x)
end
end
90.degrees # => 90.0
Math::sin(90.degrees) # => 1.0
Math::sin(Math::PI/2.radians) # =>
Math::sin(Math::PI/2) # => 1.0
Nie wydaje si ę to jednak zbyt interesuj ącym sposobem sp ędzania d ługich jesiennych wie-
czorów…
Patrz tak że
• Receptura 8.9, „Konwersja i koercja typów obiektów”.
• W bibliotece Facets More (dost ępnej w postaci gemu facets_more) tak że znajduje si ę
modu ł Units.
2.10. Mno żenie macierzy
Problem
Chcemy przekszta łcać tablice tablic w macierze i dokonywa ć mno żenia tych macierzy.
Rozwiązanie
Macierz reprezentowana jest w j ęzyku Ruby przez obiekt Matrix, który mo żna utworzy ć na
6podstawie tablicy tablic, za ś mno żenie obiektów Matrix wykonywane jest przez operator *.
require 'matrix'
require 'mathn'
a1 = [[1, 1, 0, 1],
[2, 0, 1, 2],
[3, 1, 1, 2]]
m1 = Matrix[*a1]
# => Matrix[[1, 1, 0, 1], [2, 0, 1, 2], [3, 1, 1, 2]]
a2 = [[1, 0],
[3, 1],
[1, 0],
[2, 2.5]]

6
Je żeli A jest macierz ą o wymiarach m × n, a B jest macierz ą o wymiarach n × k, to iloczynem macierzy A ∗ B
nazywamy macierz C o wymiarach m × k, której elementy okre ślone s ą nast ępuj ąco:
n
C = A B i = 1, K,m, j = 1, K,ki, j ∑ i, p p, j
p =1
— przyp. t łum.
2.10. Mno żenie macierzy | 87m2 = Matrix[*a2]
# => Matrix[[1, 0], [3, 1], [1, 0], [2, 2.5]]
m1 * m2
# => Matrix[[6, 3.5], [7, 5.0], [11, 6.0]]
Zwró ć uwag ę na specyficzn ą sk ładni ę zwi ązan ą z tworzeniem obiektu Matrix: poszczególne
wiersze macierzy wyst ępuj ą w roli argumentów operatora indeksowania, nie jako argumenty
konstruktora Matrix#new (który jest prywatny).
Dyskusja
Klasa Matrix dokonuje przeci ążenia podstawowych operatorów arytmetycznych tak, by ła-
two mo żna by ło zapisywa ć podstawowe operacje na macierzach, mi ędzy innymi mno żenie
7macierzy o kompatybilnych wymiarach . Próba pomno żenia macierzy o wymiarach niekom-
patybilnych spowoduje wyst ąpienie wyj ątku ExceptionForMatrix::ErrDImensionMismatch.
Mno żenie dwóch macierzy nie wymaga zbyt wielu komentarzy, jednak że mno żenie ca łego
ich łańcucha mo że trwa ć d łu żej lub krócej, w zale żno ści od tego, w jakiej kolejno ści poszcze-
gólne mno żenia zostan ą wykonane. Co prawda mno żenie macierzy nie jest przemienne (A ∗B
na ogó ł nie równa si ę B ∗A), ale jest ono łączne — przyk ładowo, obliczaj ąc iloczyn czterech ma-
cierzy A, B, C i D mo żemy wykonywa ć poszczególne mno żenia w ró żnej kolejno ści, zgodnie
z to żsamo ści ą
(A*B)*C)*D = (A*B)*(C*D) = A*((B*C)*D)
Obliczenie iloczynu macierzy o wymiarach K × M przez macierz o wymiarach M × N wymaga
wykonania K ∗ M ∗ N mno że ń elementów. Rozpatrzmy trzy macierze A, B i C o wymiarach (od-
powiednio) 100 × 20, 20 × 10 i 10 × 1. Obliczenie iloczynu A ∗ B wymaga 100 ∗20 ∗10 = 20 000
mno że ń elementów i daje (jako wynik cz ąstkowy) macierz o wymiarach 100 × 10. Obliczenie
iloczynu tego wyniku cz ąstkowego przez macierz C wymaga 100 ∗ 10 ∗ 1 = 1 000 mno że ń
elementów. Zatem w celu obliczenia iloczynu (A ∗B) ∗C musimy wykona ć 21 000 mno że ń ele-
mentów. Z kolei w celu obliczenia iloczynu B ∗C musimy mno żyć elementy 20 ∗10 ∗1 = 200 ra-
zy, a obliczenie iloczynu macierzy A przez iloczyn cz ąstkowy B ∗C (o wymiarach 20 × 1) wyma-
ga jedynie 100 ∗20 ∗1 = 2 000 mno że ń elementów. Obliczenie A ∗ (B ∗C) wymaga zatem 2 200
mno że ń elementów, ergo — zmieniaj ąc kolejno ść obliczania iloczynów cz ąstkowych, zmniej-
szyli śmy niemal dziesi ęciokrotnie liczb ę mno że ń elementów.
Opisane zjawisko — i jego konsekwencje praktyczne — stanowi wystarczaj ącą motywacj ę do
tego, by przed przyst ąpieniem do obliczania iloczynu d ługiego łańcucha macierzy ustali ć za-
wczasu kolejno ść obliczania poszczególnych iloczynów cz ąstkowych. Prezentowana poni żej
metoda mno żenia macierzy (przekazanych w postaci listy) najpierw wywo łuje metod ę best_
split, ustalaj ącą optymaln ą kolejno ść wykonywania mno że ń, a nast ępnie metod ę multiply_
following_cache, wykonuj ącą fizyczne mno żenie macierzy zgodnie z t ą kolejno ści ą. Wymie-
nione metody komunikuj ą si ę ze sob ą za po średnictwem wspó łdzielonej tablicy cache.
class Matrix
def Matrix.multiply(*matrices)
cache = []
matrices.size.times { cache << [nil] * matrices.size }

7
Liczba kolumn pierwszej macierzy musi by ć równa liczbie wierszy drugiej, by mo żna by ło pomno ży ć pierw-
sz ą macierz przez drug ą — przyp. t łum.
88 | Rozdzia ł 2. Liczby