Ruby/Iteratory: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Szymon wro (dyskusja | edycje)
Nie podano opisu zmian
Szymon wro (dyskusja | edycje)
Nie podano opisu zmian
Linia 1:
== Iteratory ==
 
Iteratory nie są oryginalnym pojęciem Rubiego. Występują one powszechnie w językach programowania zorientowanych obiektowo. Używane są również w Lispie, choć nie są tam nazywane iteratorami. JednakżeW koncepcjatym iteratorówrozdziale jestszczegółowo wieluprzyjrzymy osobomsię obca,wszechobecnym więciteratorom zostanie ona tutaj wyjaśniona nieco bardziej szczegółowoRubiego.
 
Czasownik "iterować" oznacza wykonywać tę samą czynność wiele razy, tak więc iterator jest czymś co wykonuje tę samą rzecz wiele razy (przykładem może być metoda <tt>powtórz</tt> z [[Ruby/Domknięcia i obiekty procedurowe|rozdziału o domknięciach]]).
 
Podczas pisania kodu potrzebujemy pętli w wielu różnych sytuacjach. W C, kodujemy je używając <tt>for</tt> lub <tt>while</tt>. Na przykład,:
 
<source lang="cpp">
Linia 14:
</source>
 
Składnia pętli <tt>for (...)</tt> z języka C dostarcza pewnej abstrakcji, która pomaga w utworzeniu pętli, ale sprawdzenie czy <tt>*str</tt> nie wskazuje na znak pusty znak wymaga od programisty znajomości szczegółów o wewnętrznej strukturze łańcucha znakowego. SprawiaMiędzy to,innymi żedlatego, C jest odczuwanyodbierany jako język niskiego poziomu. Języki wyższego poziomu odznaczają się bardziej elastycznym wsparciem iteracji. Rozważ następujący skrypt ''sh'' powłoki systemowej:
 
<source lang="bash">
Linia 28:
Trzeba zauważyć jeszcze jedno: często język dostarcza iteratorów dla typów wbudowanych, ale budzi rozczarowanie gdy okazuje się, że musimy wracać z powrotem do pętli nisko poziomowych by iterować nasze własne typy danych. W programowaniu zorientowanym obiektowo (OOP - ang. ''Object-Oriented Programming''), użytkownicy zazwyczaj definiują dużo własnych typów danych, więc to może być całkiem poważny problem.
 
Każdy język wspierający OOP zawiera jakieś udogodnienia dotyczące iterowania. Niektóre języki dostarczają w tym celu specjalnych klas, natomiast Ruby pozwala na definiowanie iteratorów bezpośrednio, używając w tym celu znanych już nam domknięć.
 
Typ <tt>String</tt> Rubiego posiada kilka użytecznych iteratorów:
Linia 49:
</source>
 
... jednakże iterator <tt>each_byte</tt> jest koncepcyjnie prostszy, i wydaje się, że działałby nadal nawet gdyby klasa <tt>String</tt> uległa w przyszłości radykalnym modyfikacjom. Dużą zaletą iteratorów jest to, że zachowują one swoje poprawne działanie na przekór takim radykalnym zmianom. Jest to charakterystyczna cecha dobrego kodu w ogólności. (Tak, miej cierpliwość, niebawem będziemy mówić również o tym czym są klasy.)
 
Innym iteratorem klasy <tt>String</tt> jest <tt>each_line</tt>.
Linia 62:
Zadania które wymagałyby dużego wysiłku w C (wyszukiwanie ograniczników linii, generowanie podłańcuchów, itd.) z użyciem iteratorów można wykonać bardzo łatwo.
 
Instrukcja <tt>for</tt> pojawiająca się w [[Ruby/Struktury sterujące|poprzednim rozdziale o instrukcjach sterujących]] dokonywała iteracji przez użycie iteratora <tt>each</tt>. Iterator <tt>each</tt> klasy <tt>String</tt> działa w ten sam sposób jak <tt>each_line</tt>, więc przepiszmy powyższy przykład z <tt>for</tt>:
 
<source lang="ruby">
Linia 96:
</pre>
 
<tt>yield</tt> jak już wiemy, jest wyrażeniem, które pojawia się czasem w definicji iteratora. <tt>yield</tt> przenosi sterowanie do bloku kodu który został przekazany do iteratora. (wyjaśnimyUżywając toinstrukcji dogłębniej w [[Ruby<tt>yield</Obiektytt> procedurowe|rozdziale]] poświęconym obiektom procedurowym). Następujący przykład definiuje iteratori <tt>repeatretry</tt>, można zdefiniować iterator który powtarzabędzie blokdziałał kodumniej określonąwięcej ilośćjak razystandardowa przekazanąpętla jako argument<tt>while</tt>.
 
<source lang="ruby">
def WHILE(warunek)
return if not warunek
yield
retry
end
 
i=0
WHILE(i < 3) { print i; i+=1 } #=> 012
</source>
 
Jak więc widzimy, iteratory w Rubim są metodami obsługującymi przekazane do nich domknięcia. Owszem, istnieją pewne ograniczenia, ale możesz pisać własne oryginalne iteratory. Szczególnie, gdy definiujemy nowy typ danych, wygodnie jest zdefiniować odpowiednie iteratory które będą na nim operować. W tym kontekście powyższe przykłady nie są szczególnie użyteczne. Poznajmy zatem bardziej praktyczne iteratory.
 
=== Przegląd iteratorów ===
 
;<tt>all?</tt>
 
:Przekazuje do bloku każdy element kolekcji. Zwraca <tt>true</tt>, jeśli blok nigdy nie zwróci <tt>false</tt> (lub <tt>nil</tt>).
 
<source lang="ruby">
[1, 2, 5].all? { |element| element <= 5 } #=> true
[1, 2, 5].all? { |element| element <= 4 } #=> false
%w{RAM CPU GPU DDR}.all? { |element| element.length == 3 } #=> true
 
[1, 2, 5].all? do |element|
puts "Sprawdzam #{element}; #{element} < 5: #{element < 5}"
element < 5
end #=> false
</source>
 
:Wyjście:
 
<pre>
Sprawdzam 1; 1 < 5: true
ruby> def repeat(num)
Sprawdzam 2; 2 < 5: true
| while num > 0
Sprawdzam 5; 5 < 5: false
| yield
| num -= 1
| end
| end
nil
ruby> repeat(3) { puts "foo" }
foo
foo
foo
nil
</pre>
 
 
Używając <tt>retry</tt> można zdefiniować iterator który będzie działał mniej więcej jak standardowa pętla <tt>while</tt>.
;any?
 
:Zwraca <tt>true</tt>, jeśli przekazany do bloku element kiedykolwiek zwróci <tt>true</tt>.
 
<source lang="ruby">
[1, 2, 5].any? { |element| element > 5 } # => false
[1, 2, 5].any? { |element| element == 2} # => true
</source>
 
 
;collect (map)
 
:Przekazuje do bloku każdy element kolekcji, następnie tworzy nową - z elementów zwracanych przez blok.
 
<source lang="ruby">
%w{kot tulipan parowka}.collect { |element| element.upcase }
# => ["KOT", "TULIPAN", "PAROWKA"]
[1, 2, 3].collect { |element| element + 1}
#=> [2, 3, 4]
</source>
 
 
;collect! (map!)
 
:Działa jak <tt>collect</tt>, z tą jednak różnicą, że operacji kolekcja dokonuje na sobie, w każdej iteracji zmieniając swoją zawartość.
 
<source lang="ruby">
a = [1, 2, 3] #=> [1, 2, 3]
a.collect! { |element| element + 1 } #=> [2, 3, 4]
a #=> [2, 3, 4]
</source>
 
 
;delete_if
 
:Usuwa z kolekcji elementy, dla których blok zwraca <tt>true</tt>
 
<source lang="ruby">
[1, 2, 3, 4, 5, 6].delete_if { |i| i%2 == 0 } # => [1, 3, 5]
</source>
 
 
;detect (find)
 
:Zwraca pierwszy element, dla którego blok zwróci <tt>true</tt>
 
<source lang="ruby">
(36..100).detect { |i| i%7 == 0 } # => 42
</source>
 
 
;downto
 
:Wykonuje blok, podając w kolejności malejącej liczby od siebie samej do podanej jako parametr.
 
<source lang="ruby">
9.downto(0) { |i| print i } #=> 9876543210
</source>
 
 
;each
 
:Przekazuje do bloku każdy z elementów kolekcji
 
<source lang="ruby">
['pies', 'kot', 'ryba'].each { |word| print word + " " }
(0..9).each { |i| print i }
#=> pies kot ryba 0123456789
</source>
 
 
;each_index
 
:Działa jak each, ale przekazuje sam indeks każdego elementu.
 
<source lang="ruby">
[3, 6, -5].each_index { |i| print i.to_s + " " }
#=> 0 1 2
</source>
 
 
;each_with_index
 
:Przekazuje jednocześnie element i jego indeks do bloku.
 
<source lang="ruby">
["jeden", 2, "trzy"].each_with_index do |element, index|
puts "Indeksowi #{index} przyporzadkowalem #{element}"
end
 
#=> Indeksowi 0 przyporzadkowalem jeden
# Indeksowi 1 przyporzadkowalem 2
# Indeksowi 2 przyporzadkowalem trzy
</source>
 
 
;find_all
 
:Zwraca wszystkie elementy kolekcji, dla których blok zwróci <tt>true</tt>.
 
<source lang="ruby">
(0..30).find_all { |i| i%9 == 0 } #=> [0, 9, 18, 27]
</source>
 
;grep
 
:Zwraca elementy spełniające dopasowanie podane jako parametr. Jeśli podano blok, przekazuje do niego tylko te elementy i zwraca tablicę zbudowaną z wartości zwracanych przez blok.
 
<source lang="ruby">
# Zwraca wyrazy zawierajace litere 'r'
%w{ruby python perl php}.grep(/r/) do |w|
print "#{w.upcase} "
w.capitalize
end #=> ["Ruby", "Perl"]
 
#=> RUBY PERL
</source>
 
 
;inject
 
:Przekazuje do bloku każdy element kolekcji. Posiada dodatkowo pamięć, która początkowo jest równa pierwszemu elementowi (lub wartości podanej jako parametr). Po zakończeniu każdej iteracji pamięć jest aktualizowana do wartości zwracanej przez blok.
 
<source lang="ruby">
# Zwraca największą liczbę z tablicy
a = [-5, 2, 10, 17, -50]
a.inject a.first do |mem, element|
mem > element ? mem : element
end #=> 17
 
# Silnia
(1..5).inject do |mem, element|
mem *= element
end #=> 120
</source>
 
 
;partition
 
:Zwraca dwie tablice: jedną z elementami, dla których blok zwraca <tt>true</tt> i drugą - z resztą.
 
<source lang="ruby">
(1..6).partition { |i| i%2 == 0 } #=> [[2, 4, 6], [1, 3, 5]]
</source>
 
 
;reject
 
:Odrzuca z kolekcji wszystkie elementy, dla których blok zwróci <tt>true</tt>.
 
<source lang="ruby">
(1..10).reject { |i| i >= 3 and i <= 7 } #=> [1, 2, 8, 9, 10]
</source>
 
 
;reject!
 
:Wyrzuca z siebie elementy, dla których blok zwraca <tt>true</tt>.
 
<source lang="ruby">
a = (1..10).to_a # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a.reject! { |i| i >= 3 and i <= 7 } # => [1, 2, 8, 9, 10]
a # => [1, 2, 8, 9, 10]
</source>
 
 
;reverse_each
 
:Działa jak <tt>each</tt> tyle, że podaje elementy w odwrotnej kolejności.
 
<source lang="ruby">
(0..9).to_a.reverse_each { |i| print i }
#=> 9876543210
</source>
 
 
;step
 
:Przekazuje do bloku wartości od, do - z określonym krokiem.
 
<source lang="ruby">
# (1)
0.step(100, 10) { |i| puts i}
# (2)
(0..100).step(10) { |i| puts i }
</source>
 
W obu przypadkach wyjście będzie wyglądało tak:
 
<pre>
0
ruby> def WHILE(warunek)
10
| return if not warunek
20
| yield
30
| retry
40
| end
50
nil
60
ruby> i=0; WHILE(i < 3) { print i; i+=1 }
70
012 nil
80
90
100
</pre>
 
 
Czy już rozumiesz czym jest iterator? Owszem, istnieją pewne ograniczenia, ale możesz pisać własne oryginalne iteratory. Szczególnie, gdy definiujesz nowy typ danych, wygodnie jest zdefiniować odpowiednie iteratory które będą na nim operować. W tym kontekście powyższe przykłady nie są szczególnie użyteczne. O bardziej praktycznych iteratorach będziemy mogli powiedzieć, kiedy już lepiej zrozumiemy czym są klasy.
;times
 
:Wykonuje dany blok określoną ilość razy.
 
<source lang="ruby">
5.times { puts "Hej!" }
5.times { |i| print "#{i} "}
 
#=> Hej!
# Hej!
# Hej!
# Hej!
# Hej!
# 0 1 2 3 4
</source>
 
 
;upto
 
:Iteruje blok, przekazując liczby od, do.
 
<source lang="ruby">
1.upto(3) { |i| print i }
#=> 123
</source>
<noinclude>
{{ProstaNawigacja|spis=Ruby|poprzart=Ruby/Struktury sterujące|poprz=Struktury sterujące|nastart=Ruby/Myślenie zorientowane obiektowo|nast=Myślenie zorientowane obiektowo}}