Uczę się programowania funkcjonalnego w Haskell . W międzyczasie studiuję teorię automatów, a ponieważ wydaje się, że obie pasują do siebie, piszę małą bibliotekę do zabawy z automatami.
Oto problem, który zmusił mnie do zadania pytania. Badając sposób oceny osiągalności stanu, wpadłem na pomysł, że prosty algorytm rekurencyjny byłby dość nieefektywny, ponieważ niektóre ścieżki mogą dzielić niektóre stany, a ja mógłbym je oceniać więcej niż raz.
Na przykład tutaj, oceny osiągalność o g z , to, że ma wykluczyć F zarówno podczas sprawdzania drogę przez D i C :
Mój pomysł jest taki, że algorytm działający równolegle na wielu ścieżkach i aktualizujący wspólny zapis stanów wykluczonych może być świetny, ale to dla mnie za dużo.
Widziałem, że w niektórych prostych przypadkach rekurencji można podać stan jako argument, i to właśnie muszę zrobić, ponieważ przekazuję listę stanów, przez które przeszedłem, aby uniknąć pętli. Ale czy istnieje sposób na przekazanie tej listy również do tyłu, na przykład zwrócenie jej w krotce wraz z logicznym wynikiem mojej canReach
funkcji? (choć wydaje się to trochę wymuszone)
Oprócz ważności mojego przykładowego przypadku , jakie inne techniki są dostępne w celu rozwiązania tego rodzaju problemów? Wydaje mi się, że muszą być na tyle powszechne, że muszą istnieć rozwiązania takie jak to, co dzieje się z fold*
lub map
.
Jak dotąd, czytając learnyouahaskell.com , nie znalazłem żadnego, ale uważam, że nie dotknąłem jeszcze monad.
(w razie zainteresowania opublikowałem kod w widoku kodu )
źródło
Odpowiedzi:
Programowanie funkcjonalne nie pozbywa się stanu. Wyjaśnia to tylko! Chociaż prawdą jest, że funkcje takie jak mapa często „rozplątują” „udostępnioną” strukturę danych, jeśli wszystko, co chcesz zrobić, to napisać algorytm osiągalności, wystarczy jedynie śledzić, które węzły już odwiedziłeś:
Teraz muszę wyznać, że ręczne śledzenie tego stanu jest dość irytujące i podatne na błędy (łatwo użyć s zamiast s, łatwo przekazać te same s do więcej niż jednego obliczenia ...) . W tym momencie wkraczają monady: nie dodają niczego, czego wcześniej nie można było zrobić, ale pozwalają na niejawne przekazywanie zmiennej stanu, a interfejs gwarantuje, że dzieje się to w sposób jednowątkowy.
Edycja: Spróbuję podać bardziej uzasadnienie tego, co teraz zrobiłem: po pierwsze, zamiast po prostu testowania osiągalności, zakodowałem wyszukiwanie w pierwszej kolejności. Implementacja będzie wyglądać prawie tak samo, ale debugowanie wygląda trochę lepiej.
W języku stanowym DFS wyglądałby mniej więcej tak:
Teraz musimy znaleźć sposób na pozbycie się stanu zmiennego. Przede wszystkim pozbywamy się zmiennej „visitlist”, powodując, że dfs zwraca, że zamiast void:
A teraz przychodzi trudna część: pozbycie się zmiennej „odwiedzanej”. Podstawową sztuczką jest użycie konwencji, w której przekazujemy stan jako dodatkowy parametr do funkcji, które go potrzebują, i zwracają nową wersję stanu jako dodatkową wartość zwracaną, jeśli chcą ją zmodyfikować.
Aby zastosować ten wzorzec do dfs, musimy go zmienić, aby otrzymać zestaw „odwiedzonych” jako dodatkowy parametr i zwrócić zaktualizowaną wersję „odwiedzonych” jako dodatkową wartość zwracaną. Dodatkowo musimy przepisać kod, abyśmy zawsze przekazywali „najnowszą” wersję „odwiedzanej” tablicy:
Wersja Haskell robi prawie wszystko, co tutaj zrobiłem, z tym wyjątkiem, że działa całkowicie i wykorzystuje wewnętrzną funkcję rekurencyjną zamiast zmiennych zmiennych „curr_visited” i „childtrees”.
Jeśli chodzi o monady, to w zasadzie dokonują w sposób dorozumiany przekazywania „curr_visited”, zamiast zmuszania cię do zrobienia tego ręcznie. To nie tylko usuwa bałagan z kodu, ale także zapobiega popełnianiu błędów, takich jak rozwidlenie stanu (przekazanie tego samego „odwiedzonego” zestawu do dwóch kolejnych wywołań zamiast tworzenia łańcucha stanu).
źródło
Oto prosta odpowiedź polegająca na
mapConcat
.Gdzie
neighbors
zwraca stany bezpośrednio związane ze stanem. Zwraca to szereg ścieżek.źródło