Automat skończony w Haskell
Jaki jest dobry sposób na reprezentowanie skończonego automatu w Haskell? Jak wyglądałby jego typ danych?
W naszej szkole automaty były definiowane jako 5-krotka
(Q, X, delta, q_0, F)
Gdzie Q jest zbiorem Stanów automatu, X jest alfabetem (czy ta część jest nawet potrzebna), delta jest funkcją przejściową przyjmującą 2-krotkę z (Q, X)i zwracającą stan / -s (w wersji niedeterministycznej), A F jest zbiorem Stanów akceptujących/końcowych.
Co najważniejsze, nie jestem pewien jaki typ delta
powinienem był...
2 answers
Istnieją dwie podstawowe opcje:
- funkcja Jawna
delta :: Q -> X -> Q
(lub[Q]
odpowiednio), jak sugeruje Sven Hager. - mapa
delta :: Map (Q, X) Q
np. za pomocąData.Map
, lub jeśli państwa/alfabet mogą być indeksowane przez rosnących liczbData.Array
lubData.Vector
.
Zauważ, że te dwa podejścia są zasadniczo równoważne, można przekonwertować z wersji mapy na wersję funkcji (jest to nieco inne ze względu na dodatkowe Maybe
z wywołania lookup
) stosunkowo łatwo
delta_func q x = Data.Map.lookup (q,x) delta_map
(lub odpowiednio skrócona wersja funkcji wyszukiwania dla dowolnego typu mapowania, którego używasz.)
Jeśli konstruujesz automaty w czasie kompilacji (a więc znasz możliwe stany i możesz je zakodować jako typ danych), użycie wersji funkcji daje większe bezpieczeństwo typu, ponieważ kompilator może sprawdzić, czy uwzględniłeś wszystkie przypadki.
Jeśli budujesz automaty w czasie wykonywania (np. z wejścia użytkownika), to przechowywanie delta
jako mapy (i ewentualnie wykonywanie konwersji funkcji jak wyżej) i posiadanie odpowiedniej walidacji danych wejściowych, która gwarantuje poprawność, aby fromJust
była bezpieczna (tzn. zawsze jest wpis na mapie dla dowolnej możliwej krotki (Q,X)
, a więc wyszukiwanie nigdy nie zawodzi (nigdy nie zwraca Nothing
)).
Automaty niedeterministyczne działają dobrze z opcją map, ponieważ nieudane wyszukiwanie jest tym samym, co brak stanu do którego należy przejść, tzn. pusta lista [Q]
, a więc nie musi być każda Specjalna obsługa Maybe
poza wywołaniem do join . maybeToList
(join
jest z Data.Monad
i maybeToList
jest z Data.Maybe
).
Z innej strony, alfabet jest zdecydowanie niezbędny: to sposób, w jaki automat odbiera dane wejściowe.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-16 14:29:06
Sprawdź kontrolkę./ Align = "left" / Transformator.Moduł automatu w pakiecie "strzałki". Typ wygląda tak
newtype Automaton a b c = Automaton (a b (c, Automaton a b c))
Jest to nieco mylące, ponieważ jest to transformator strzałkowy. W najprostszym przypadku możesz napisać
type Auto = Automaton (->)
Który używa funkcji jako podstawowej strzałki. Podstawianie ( - > ) dla "a" w definicji automatu i używając notacji infiksowej widać, że jest to mniej więcej równoważne:
newtype Auto b c = Automaton (b -> (c, Auto b c))
Innymi słowy automat jest funkcją, która pobiera dane wejściowe i zwraca wynik i nowy automat.
Możesz użyć tego bezpośrednio, pisząc funkcję dla każdego stanu, który pobiera argument i zwraca wynik i następną funkcję. Na przykład, oto maszyna stanowa do rozpoznawania wyrażeń regularnych "A+b" (to znaczy serii co najmniej jednego "a", po której następuje "b"). (Uwaga: kod nieprzetestowany)
state1, state2 :: Auto Char Bool
state1 c = if c == 'a' then (False, state2) else (False, state1)
state2 c = case c of
'a' -> (False, state2)
'b' -> (True, state1)
otherwise -> (False, state1)
Jeśli chodzi o Twoje pierwotne pytanie, Q = {state1, state2}, X = Char, delta to zastosowanie funkcji, A F to przejście stanu zwracające True (zamiast "akceptującego stanu" użyłem przejścia wyjściowego z akceptującą wartością).
Alternatywnie można użyć notacji strzałek. Automaton jest instancją wszystkich interesujących klas strzałek, w tym pętli i obwodu, dzięki czemu można uzyskać dostęp do poprzednich wartości za pomocą opóźnienia. (Uwaga: ponownie, kod nieprzetestowany)
recognise :: Auto Char Bool
recognise = proc c -> do
prev <- delay 'x' -< c -- Doesn't matter what 'x' is, as long as its not 'a'.
returnA -< (prev == 'a' && c == 'b')
Strzałka "delay" oznacza, że "prev" jest równa poprzedniej wartości "c", a nie bieżącej wartości. Możesz również uzyskać dostęp do poprzedniego wyjścia używając "rec". Na przykład, tutaj jest strzałka, która daje rozkładający się razem w czasie. (Faktycznie Przetestowane w tym przypadku)
-- | Inputs are accumulated, but decay over time. Input is a (time, value) pair.
-- Output is a pair consisting
-- of the previous output decayed, and the current output.
decay :: (ArrowCircuit a) => NominalDiffTime -> a (UTCTime, Double) (Double, Double)
decay tau = proc (t2,v2) -> do
rec
(t1, v1) <- delay (t0, 0) -< (t2, v)
let
dt = fromRational $ toRational $ diffUTCTime t2 t1
v1a = v1 * exp (negate dt / tau1)
v = v1a + v2
returnA -< (v1a, v)
where
t0 = UTCTime (ModifiedJulianDay 0) (secondsToDiffTime 0)
tau1 = fromRational $ toRational tau
Zauważ, że wejście do " delay "zawiera" v", wartość pochodzącą z jego wyjścia. Umożliwia to klauzula "rec", dzięki czemu możemy zbudować pętlę sprzężenia zwrotnego.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-22 14:30:00