Przedziały logiczne (przerzutniki) w Rubim
Posted by Jacek Galanciak on Apr 22 2007
Przeglądając książki i zasoby sieci natrafiłem na bardzo ciekawą konstrukcję w Rubim:
if wyr1 .. wyr2
while wyr1 .. wyr2
Normalne wyrażenia logiczne mają charakter kombinacyjny - ich wartość zależy jedynie od danych wejściowych. Wyrażenie jest wykonywane, zwraca wartość i jest zapominane. Inaczej jest z przedziałami logicznymi - te działają podobnie jak przerzutniki. Wykazują działanie bistabilne - nie tylko wykonują i zwracają wyrażenia, ale także pamiętają swój poprzedni stan.
Szczypta kodu
Wypróbujmy poniższy kod:
for i in 1..10 do
if (i == 3)..(i == 7)
print 1
else
print 0
end
end
Na wyjściu otrzymamy:
0011111000
Jak to działa?
Ogólny schemat działania wyrażenia przedstawia poniższy graf (podziękowania dla Riddle'a):
Innymi słowy:
- wyróżniamy 2 wewnętrzne stany: ustawiony i nieustawiony
- rozpoczynamy stanem nieustawionym i sprawdzamy
wyr1
- jeśli jest
false
, nie zmieniamy stanu wewnętrznego, przerzutnik zwracafalse
- jeśli jest
true
, przeskakujemy dowyr2
, przerzutnik zwracatrue
- jeśli jest
false
, nie zmieniamy stanu wewnętrznego, przerzutnik zwracatrue
- jeśli jest
true
, przeskakujemy dowyr1
, przerzutnik zwracatrue
- i tak dalej…
Można powiedzieć, że nasz automat zaczął zwracać jedynki, gdy natrafił na spełnienie pierwszego warunku (i == 3
), a skończył zwracać po tym, jak tylko drugi warunek (i == 7
) zostanie spełniony. Program ponownie czeka na spełnienie warunku pierwszego. Biedaczek ;-).
Coś bardziej praktycznego
Użyjmy przerzutnika w pętli. Napiszemy prosty program, który czyta plik tekstowy i wypisuje bloki zawarte pomiędzy słowami BEGIN i END (wraz z nimi - dla uproszczenia):
file = File.open("test.txt")
while a = file.gets
puts a if a.chomp == "BEGIN" .. a.chomp == "END"
end
Dla pliku o zawartości
BEGIN
Ta informacja
pojawi się
na wyjściu
END
Ale ta
już nie
BEGIN
A ta już tak
END
program zwróci:
BEGIN
Ta informacja
pojawi się
na wyjściu
END
BEGIN
A ta już tak
END
Prześledźmy to dokładniej
Napiszmy kolejny prosty skrypcik, ale tym razem skupimy się na samych wyrażeniach i ich wartościach:
# Sprawdza podzielność przez 5
def check5(i)
puts "check5 zwraca " + (i%5 == 0).to_s
i%5 == 0
end
# Sprawdza podzielność przez 4
def check4(i)
puts "check4 zwraca " + (i%4 == 0).to_s
i%4 == 0
end
for i in 9..22 do
puts "BEGIN: " + i.to_s
if check5(i)..check4(i)
puts "END: true"
else
puts "END: false"
end
puts
end
Program pokazuje, co zwraca każde z wyrażeń, co zwraca cały przerzutnik, oraz czy dane wyrażenie jest w ogóle wykonywane.
Czym możemy być zaskoczeni?
Zanim zaczniemy poważniej używać przedziałów logicznych, musimy pamiętać o dwóch sprawach, by na przyszłość uniknąć męczących błędów.
- jeśli
wyr1
jest fałszywe,wyr2
nie będzie w ogóle wykonywane; i vice versa - w jednym cyklu nasz automat może dwukrotnie zmienić stan - widzimy to dla liczby 20 - pierwszy warunek jest spełniony, przeskakuje więc na drugi stan, którego warunek również jest spełniony; automat wraca więc do swojego pierwszego stanu
Podsumowanie
Może i operator przerzutnika nie należy do najprostszych, ale może mieć potężne zastosowania o miażdżącej zwięzłości. Warto jednak śledzić newsy dotyczące rozwoju Rubiego - część społeczności domaga się usunięcia tej konstrukcji, która dla nich jest perlowym dziwactwem. Ciekawe jak się sprawa dalej potoczy…
PS. Zna ktoś inne ciekawe zastosowania? :-)