कार्यात्मक प्रोग्रामिंग और स्टेटफुल एल्गोरिदम


12

मैं हास्केल के साथ कार्यात्मक प्रोग्रामिंग सीख रहा हूं । इस बीच मैं ऑटोमेटा सिद्धांत का अध्ययन कर रहा हूं और जैसा कि दोनों एक साथ अच्छी तरह से फिट होते हैं, मैं ऑटोमेटा के साथ खेलने के लिए एक छोटी सी लाइब्रेरी लिख रहा हूं।

यहाँ समस्या है कि मुझे सवाल पूछना है। किसी राज्य की पुनर्विक्रयता का मूल्यांकन करने के तरीके का अध्ययन करते समय मुझे यह विचार आया कि एक सरल पुनरावर्ती एल्गोरिथ्म काफी अक्षम होगा, क्योंकि कुछ पथ कुछ राज्यों को साझा कर सकते हैं और मैं उनका मूल्यांकन एक से अधिक बार कर सकता हूं।

उदाहरण के लिए, यहाँ, a से g की अभिकर्मकता का मूल्यांकन करते हुए , मुझे d और c के माध्यम से पथ की जाँच करते समय f को बाहर करना होगा :

डिग्राफ एक ऑटोमेटन का प्रतिनिधित्व करता है

इसलिए मेरा विचार है कि कई रास्तों पर समानांतर में काम करने वाला एक एल्गोरिथ्म और बहिष्कृत राज्यों के साझा रिकॉर्ड को अपडेट करना महान हो सकता है, लेकिन यह मेरे लिए बहुत अधिक है।

मैंने देखा है कि कुछ सरल पुनरावृत्ति मामलों में, एक राज्य को एक तर्क के रूप में पारित किया जा सकता है, और यही मुझे यहां करना है, क्योंकि मैं उन राज्यों की सूची को आगे बढ़ाता हूं जिनसे मैं बचने के लिए गुजरता हूं। लेकिन क्या उस सूची को पीछे की तरफ से गुजारने का एक तरीका भी है, जैसे कि इसे मेरे canReachकार्य के बूलियन परिणाम के साथ एक ट्यूपल में वापस करना ? (हालांकि यह थोड़ा मजबूर लगता है)

मेरे उदाहरण के मामले की वैधता के अलावा , इस तरह की समस्याओं को हल करने के लिए कौन सी अन्य तकनीकें उपलब्ध हैं? मुझे ऐसा लगता है कि ये सामान्य रूप से पर्याप्त होना चाहिए कि क्या होना चाहिए fold*या इसके साथ क्या समाधान होना चाहिए map

अब तक, learnyouahaskell.com पढ़कर मुझे कोई भी नहीं मिला, लेकिन मुझे लगता है कि मैंने अभी तक भिक्षुओं को नहीं छुआ है।

( यदि रुचि है, तो मैंने अपना कोड कोडरेव्यू पर पोस्ट किया है )


3
मैं, एक के लिए, उस कोड को देखना पसंद करूंगा, जिसके साथ आप काम करना चाहते हैं। इसके अभाव में, मेरी सबसे अच्छी सलाह यह है कि हास्केल के आलस्य का अक्सर एक से अधिक बार चीजों की गणना न करने के लिए किया जा सकता है। तथाकथित "गांठ बांधना" और आलसी मूल्य पुनरावृत्ति को देखें, हालांकि आपकी समस्या काफी सरल है कि अधिक उन्नत तकनीकें जो अनंत मूल्यों और इसी तरह की चीजों का लाभ उठाती हैं, वे ओवरकिल हो जाएंगे, और शायद अभी आपको भ्रमित करेंगे।
पथरियन की लौ

1
@ Ptharien'sFlame आपकी रुचि के लिए धन्यवाद! यहाँ एक कोड है , पूरे प्रोजेक्ट का लिंक भी है। मैं पहले से ही उलझन में हूं कि मैंने अब तक क्या किया है, हां, बेहतर है कि उन्नत तकनीकों पर ध्यान न दें :)
bigstones

1
एक राज्य ऑटोमेटा कार्यात्मक प्रोग्रामिंग का बहुत अधिक विरोधी है। कार्यात्मक प्रोग्रामिंग आंतरिक स्थिति के बिना समस्याओं को हल करने के बारे में है, जबकि एक राज्य ऑटोमेटा अपने स्वयं के राज्य के प्रबंधन के बारे में है।
फिलिप

@ खिल्ली मैं असहमत हूँ। एक ऑटोमेटन या स्टेट मशीन कभी-कभी किसी समस्या का प्रतिनिधित्व करने का सबसे स्वाभाविक और सटीक तरीका है, और कार्यात्मक ऑटोमेटा का अच्छी तरह से अध्ययन किया जाता है।
पथरियन की लौ

5
@Philipp: कार्यात्मक प्रोग्रामिंग राज्य को स्पष्ट करने के बारे में है, न कि इसे मना करने के बारे में। वास्तव में, पूंछ पुनरावृत्ति गोटो से भरे उन राज्य मशीनों को लागू करने के लिए वास्तव में एक महान उपकरण है।
hugomg

जवाबों:


16

कार्यात्मक प्रोग्रामिंग से छुटकारा नहीं मिलता है। यह केवल यह स्पष्ट करता है! हालांकि यह सच है कि मानचित्र जैसे कार्य अक्सर "साझा" किए गए डेटा संरचना को "अनवील" करते हैं, यदि आप जो करना चाहते हैं वह एक रीचबिलिटी एल्गोरिदम लिख रहा है तो यह केवल उन नोड्स का ट्रैक रखने की बात है जो आपने पहले से ही देखे हैं।

import qualified Data.Set as S
data Node = Node Int [Node] deriving (Show)

-- Receives a root node, returns a list of the node keyss visited in a depth-first search
dfs :: Node -> [Int]
dfs x = fst (dfs' (x, S.empty))

-- This worker function keeps track of a set of already-visited nodes to ignore.
dfs' :: (Node, S.Set Int) -> ([Int], S.Set Int)
dfs' (node@(Node k ns), s )
  | k  `S.member` s = ([], s)
  | otherwise =
    let (childtrees, s') = loopChildren ns (S.insert k s) in
    (k:(concat childtrees), s')

--This function could probably be implemented as just a fold but Im lazy today...
loopChildren :: [Node] -> S.Set Int -> ([[Int]], S.Set Int)
loopChildren []  s = ([], s)
loopChildren (n:ns) s =
  let (xs, s') = dfs' (n, s) in
  let (xss, s'') = loopChildren ns s' in
  (xs:xss, s'')

na = Node 1 [nb, nc, nd]
nb = Node 2 [ne]
nc = Node 3 [ne, nf]
nd = Node 4 [nf]
ne = Node 5 [ng]
nf = Node 6 []
ng = Node 7 []

main = print $ dfs na -- [1,2,5,7,3,6,4]

अब, मुझे यह स्वीकार करना चाहिए कि इस सारे राज्य पर नज़र रखना बहुत कष्टप्रद और त्रुटि प्रवण है (s 'के बजाय s' का उपयोग करना आसान है, एक ही s 'को एक से अधिक संगणना के लिए पास करना आसान है ...) । यह वह जगह है जहां सन्यासी आते हैं: वे कुछ भी नहीं जोड़ते हैं जो आप पहले से ही नहीं कर सकते थे, लेकिन वे आपको अनुमानित रूप से राज्य चर पास करते हैं और इंटरफ़ेस गारंटी देता है कि यह एकल-थ्रेडेड तरीके से होता है।


संपादित करें: मैं अब जो कुछ भी किया था, उसके बारे में अधिक तर्क देने का प्रयास करूंगा: सबसे पहले, केवल रीचैबिलिटी के परीक्षण के बजाय मैंने एक गहराई-पहली खोज को कोडित किया। कार्यान्वयन बहुत ज्यादा दिखने वाला है लेकिन डिबगिंग थोड़ा बेहतर है।

एक राज्य भाषा में, DFS इस तरह दिखाई देगा:

visited = set()  #mutable state
visitlist = []   #mutable state
def dfs(node):
   if isMember(node, visited):
       //do nothing
   else:
       visited[node.key] = true           
       visitlist.append(node.key)
       for child in node.children:
         dfs(child)

अब हमें उत्परिवर्तित स्थिति से छुटकारा पाने का एक रास्ता खोजने की जरूरत है। सबसे पहले हम "विज़िटसूची" चर से छुटकारा पाने के लिए dfs रिटर्न बनाते हैं जो शून्य के बजाय:

visited = set()  #mutable state
def dfs(node):
   if isMember(node, visited):
       return []
   else:
       visited[node.key] = true
       return [node.key] + concat(map(dfs, node.children))

और अब मुश्किल हिस्सा आता है: "विज़िट किए गए" चर से छुटकारा। मूल चाल एक सम्मेलन का उपयोग करना है जहां हम राज्य को उन कार्यों के लिए एक अतिरिक्त पैरामीटर के रूप में पास करते हैं जिनकी आवश्यकता होती है और उन कार्यों को राज्य के नए संस्करण को अतिरिक्त रिटर्न मान के रूप में वापस करना होता है यदि वे इसे संशोधित करना चाहते हैं।

let increment_state s = s+1 in
let extract_state s = (s, 0) in

let s0 = 0 in
let s1 = increment_state s0 in
let s2 = increment_state s1 in
let (x, s3) = extract_state s2 in
-- and so on...

इस पैटर्न को dfs में लागू करने के लिए, हमें अतिरिक्त पैरामीटर के रूप में "विज़िट किए गए" सेट को प्राप्त करने के लिए और अतिरिक्त रिटर्न मान के रूप में "विज़िट" के अपडेट किए गए संस्करण को वापस करने के लिए इसे बदलना होगा। इसके अतिरिक्त, हमें कोड को फिर से लिखना होगा ताकि हम हमेशा "विज़िट किए गए" सरणी के "सबसे हाल के" संस्करण को आगे बढ़ाएं:

def dfs(node, visited1):
   if isMember(node, visited1):
       return ([], visited1) #return the old state because we dont want to  change it
   else:
       curr_visited = insert(node.key, visited1) #immutable update, with a new variable for the new value
       childtrees = []
       for child in node.children:
          (ct, curr_visited) = dfs(child, curr_visited)
          child_trees.append(ct)
       return ([node.key] + concat(childTrees), curr_visited)

हास्केल संस्करण बहुत कुछ करता है जो मैंने यहां किया था, सिवाय इसके कि यह सभी तरह से चला जाता है और उत्परिवर्तित "कर्वस" और "चाइल्डट्रीज़" चर के बजाय एक आंतरिक पुनरावर्ती फ़ंक्शन का उपयोग करता है।


भिक्षुओं के लिए, जो वे मूल रूप से पूरा करते हैं, वह "हाथ में लेने के लिए मजबूर करने के बजाय," वक्र_विचारित "होता है। यह न केवल कोड से अव्यवस्था को हटाता है, बल्कि यह आपको गलतियों को करने से भी रोकता है, जैसे कि फोर्किंग स्टेट (राज्य का पीछा करने के बजाय दो बाद की कॉल पर सेट "समान" का दौरा किया)।


मुझे पता था कि इसे कम दर्दनाक बनाने का एक तरीका होना चाहिए, और शायद अधिक पठनीय, क्योंकि मुझे आपके उदाहरण को समझने में कठिन समय हो रहा है। क्या मुझे आपके जैसे कोड को समझने के लिए भिक्षुओं या बेहतर अभ्यास के लिए जाना चाहिए?
बिगस्टोन्स

@bigstones: मुझे लगता है कि आपको यह समझने की कोशिश करनी चाहिए कि भिक्षुओं से निपटने से पहले मेरा कोड कैसे काम करता है - वे मूल रूप से वही काम करेंगे जो मैंने किया था लेकिन आपको भ्रमित करने के लिए अमूर्त की अतिरिक्त परतों के साथ। वैसे भी, मैंने चीजों को थोड़ा स्पष्ट करने की कोशिश करने के लिए कुछ अतिरिक्त स्पष्टीकरण जोड़ा
hugomg

1
"कार्यात्मक प्रोग्रामिंग राज्य से छुटकारा नहीं दिलाता है। यह केवल इसे स्पष्ट करता है!": यह वास्तव में स्पष्ट कर रहा है!
जियोर्जियो

"[मोनाड्स] आपको अनुमानित रूप से राज्य चर पास करने देते हैं और इंटरफ़ेस गारंटी देता है कि यह एकल-थ्रेडेड तरीके से होता है" <- यह भिक्षुओं का एक शानदार वर्णन है; इस प्रश्न के संदर्भ के बाहर, मैं 'स्टेट वेरिएबल' को 'क्लोजर' से बदल सकता हूं
एन्थ्रोपिक एंड्रॉइड

2

यहाँ एक सरल जवाब है mapConcat

 mapConcat :: (a -> [b]) -> [a] -> [b]
 -- mapConcat is in the std libs, mapConcat = concat . map
 type Path = []

 isReachable :: a -> Auto a -> a -> [Path a]
 isReachable to auto from | to == from = [[]]
 isReachable to auto from | otherwise = 
    map (from:) . mapConcat (isReachable to auto) $ neighbors auto from

जहां neighborsराज्यों को तुरंत एक राज्य से जोड़ा जाता है। यह रास्तों की एक श्रृंखला देता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.