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

Usunięta treść Dodana treść
Szymon wro (dyskusja | edycje)
Szymon wro (dyskusja | edycje)
Nie podano opisu zmian
Linia 7:
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">
<pre>
char *str;
for (str = "abcdefg"; *str != '\0'; str++) {
/* tutaj przetwarzamy znak */
}
</presource>
 
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. Sprawia to, że C jest odczuwany 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">
<pre>
#!/bin/sh
 
Linia 22:
# ... tutaj byłby kod do wykonania dla każdego pliku
done
</presource>
 
Wszystkie pliki źródłowe i nagłówkowe języka C w bieżącym katalogu są przetwarzane i powłoka systemowa bierze na siebie detale dotyczące wskazywania i podstawiania po kolei wszystkich nazw plików, jedna po drugiej. To chyba działa na wyższym poziomie niż C, nie sądzisz?
Linia 32:
Typ <tt>String</tt> Rubiego posiada kilka użytecznych iteratorów:
 
<source lang="ruby">
<pre>
ruby> "abc".each_byte { |c| printf "<%c>", c }; print "\n"
#=> <a><b><c>
<source>
nil
</pre>
 
<tt>each_byte</tt> to iterator wskazujący każdy znak w łańcuchu. Każdy znak jest podstawiany do zmiennej lokalnej <tt>c</tt>. To samo można przełożyć na coś bardziej przypominającego kod C...
 
<source lang="ruby">
<pre>
ruby> s = "abc";i=0
i = 0
ruby> while i < s.length
| printf "<%c>", s[i];
i+=1
end
| end; print "\n"
#=> <a><b><c>
</source>
nil
</pre>
 
... 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.)
Linia 54 ⟶ 53:
Innym iteratorem klasy <tt>String</tt> jest <tt>each_line</tt>.
 
<source lang="ruby">
<pre>
ruby> "a\nb\nc\n".each_line { |l| print l }
#=>
a
# a
b
# b
c
# c
nil
</presource>
 
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.
Linia 66 ⟶ 65:
Instrukcja <tt>for</tt> pojawiająca się w [[Ruby/Struktury sterujące|poprzednim rozdziale]] 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">
<pre>
ruby> for l in "a\nb\nc\n"
| print l
| end
#=>
a
# a
b
# b
c
# c
nil
</presource>
 
Możemy używać struktury sterującej <tt>retry</tt> w połączeniu z iterowaną pętlą. Spowoduje ona rozpoczęcie iterowania pętli od początku.
 
<source lang="ruby">
<pre>
ruby> c = 0
for i in 0..4
print i
ruby> for i in 0..4
if i |== 2 and printc i== 0
| if i == 2 and c == 01
|print c = 1"\n"
| retry
| print "\n"
end
| retry
| end
#=>
| end; print "\n"
# 012
# 01234
</source>
nil
</pre>
 
Zamienienie <tt>retry</tt> na <tt>redo</tt> w powyższym przykładzie spowoduje, że tylko bieżąca iteracja będzie wykonana ponownie, z następującym wynikiem:
 
<pre>
012
234
</pre>
 
<tt>yield</tt> 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śnimy to dogłębniej w [[Ruby/Obiekty procedurowe|rozdziale]] poświęconym obiektom procedurowym). Następujący przykład definiuje iterator <tt>repeat</tt>, który powtarza blok kodu określoną ilość razy przekazaną jako argument.