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ł...

Author: Kara, 2012-06-16

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 liczb Data.Array lub Data.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.

 17
Author: huon,
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.

 13
Author: Paul Johnson,
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