17

मैंने हास्केल में एन-क्वींस समस्या के विभिन्न समाधानों के लिए वेब की खोज की लेकिन ओ (1) समय में असुरक्षित पदों की जांच करने वाले किसी भी व्यक्ति को नहीं मिला, जैसे कि आप/विकर्णों के लिए एक सरणी और \ diagonals के लिए एक रखें।सूची के बिना हास्केल में एन-क्वींस

अधिकांश समाधान मैंने पाया कि पिछले सभी के खिलाफ प्रत्येक नई रानी की जांच की गई है। कुछ इस तरह: http://www.reddit.com/r/programming/comments/62j4m/nqueens_in_haskell/

nqueens :: Int -> [[(Int,Int)]] 
nqueens n = foldr qu [[]] [1..n] 
    where qu k qss = [ ((j,k):qs) | qs <- qss, j <- [1..n], all (safe (j,k)) qs ] 
     safe (j,k) (l,m) = j /= l && k /= m && abs (j-l) /= abs (k-m) 

क्या होगा हास्केल में इस तरह के एक "हे (1) दृष्टिकोण" लागू करने के लिए सबसे अच्छा तरीका है? मैं कुछ भी "सुपर-अनुकूलित" की तलाश नहीं कर रहा हूं। "यह विकर्ण पहले से ही इस्तेमाल किया गया है" उत्पादन करने के लिए बस कुछ रास्ता? एक कार्यात्मक तरीके से सरणी।

अद्यतन:

सभी प्रश्नों के उत्तर के लिए धन्यवाद, लोग! मूल रूप से सवाल पूछने का कारण यह है कि मैं एक कठिन बैकट्रैकिंग समस्या को हल करना चाहता था। मुझे पता था कि इसे एक अनिवार्य भाषा में कैसे हल किया जाए लेकिन नौकरी करने के लिए पूरी तरह कार्यात्मक डेटा संरचना के बारे में आसानी से सोच नहीं सके। मुझे लगा कि रानी समस्या एक अच्छा मॉडल होगा ( बैकट्रैकिंग समस्या :)) समग्र डेटा-संरचना समस्या के लिए, लेकिन यह मेरी वास्तविक समस्या नहीं है।

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

मैं समझ है कि sth के समाधान के रूप में अच्छा है के रूप में आप हास्केल में अपरिवर्तनीय सरणियों और "मुख्य" समारोह मैं क्या यह बनना चाहता था की तरह लग रहा का उपयोग कर प्राप्त कर सकते हैं:

-- try all positions for a queen in row n-1 
place :: BoardState -> Int -> [[(Int, Int)]] 
place _ 0 = [[]] 
place b n = concatMap place_ (freefields b (n-1)) 
    where place_ p = map (p:) (place (occupy b p) (n-1)) 

मुख्य समस्या खोजने किया जा रहा है एक बेहतर डेटा संरचना हालांकि, जैसा कि हास्केल Arrays के पास ओ (एन) अद्यतन है।

  • DiffArrays करीब आ लेकिन गंदगी बैक ट्रैकिंग में: अन्य अच्छा सुझाव पौराणिक हे (1) ही सर्वोपरि बहुत कम होना। वे वास्तव में सुपर धीमी :(मिलता है।
  • STUArrays संघर्ष सुंदर कार्यात्मक बैक ट्रैकिंग दृष्टिकोण के साथ तो वे छोड़ दिए जाते हैं।
  • मैप्स और सेट केवल हे है (लॉग एन) अद्यतन।

मैं नहीं कर रहा हूँ वास्तव में यकीन है कि वहाँ एक समाधान समग्र है, लेकिन यह होनहार लगता

अद्यतन:।

ट्रेलर Arrays जहां मुझे सबसे आशाजनक डेटा संरचना मिली। मूल रूप से एक हास्केल डिफएरेय लेकिन जब आप बैकट्रैक करते हैं तो यह वापस बदल जाता है।

+0

क्या ट्रेलर सरणी कहीं भी हास्केल के लिए लागू की गई हैं? – is7s

उत्तर

4

आम तौर पर आप शायद एक कार्यात्मक गैर-विनाशकारी कार्यान्वयन के लिए O(log n) जटिलता कर का भुगतान करने जा रहे हैं या आपको (IO|ST|STM)UArray का उपयोग करना होगा और उपयोग करना होगा।

सख्त शुद्ध भाषाओं को एक अशुद्ध भाषा पर O(log n) कर का भुगतान करना पड़ सकता है जो मानचित्र-जैसी संरचना के माध्यम से संदर्भों को लागू करके संदर्भों को लिख सकता है; आलसी भाषाएं कभी-कभी इस कर को चकित कर सकती हैं, हालांकि इस बात का कोई प्रमाण नहीं है कि आलसी द्वारा दी गई अतिरिक्त शक्ति हमेशा इस कर को चकमा देने के लिए पर्याप्त है - भले ही यह दृढ़ता से संदेह हो कि आलस्य पर्याप्त शक्तिशाली नहीं है।

इस मामले में संदर्भ तंत्र से बचने के लिए एक तंत्र को देखना कठिन होता है जिसके द्वारा आलस्य का उपयोग किया जा सकता है। और, यही कारण है कि हमारे पास पहले स्थान पर ST मोनैड है। ;)

यह कहा गया है कि आप जांच कर सकते हैं कि किसी प्रकार के बोर्ड-विकर्ण जिपर का उपयोग अद्यतनों के इलाके का फायदा उठाने के लिए किया जा सकता है - एक जिपर में इलाके का शोषण करना एक लॉगरिदमिक शब्द छोड़ने का प्रयास करने का एक आम तरीका है।

6

शायद सुरक्षित/असुरक्षित बिट्स रिकॉर्ड करने के लिए UArray (Int, Int) Bool का उपयोग करने का सबसे सरल तरीका होगा। हालांकि इसकी प्रतिलिपि ओ (एन) है, एन के छोटे मूल्यों के लिए यह सबसे तेज़ तरीका उपलब्ध है।

  • Data.DiffArray हटा कॉपी भूमि के ऊपर जब तक आप उन्हें संशोधित करने के बाद फिर से पुराने मूल्यों का उपयोग कभी नहीं:

    एन के बड़े मान के लिए, वहाँ तीन प्रमुख विकल्प हैं। यही है, अगर आप इसे म्यूट करने के बाद सरणी के पुराने मान को हमेशा फेंक देते हैं, तो संशोधन ओ (1) है। यदि, हालांकि, आप बाद में सरणी के पुराने मान तक पहुंचते हैं (यहां तक ​​कि केवल पढ़ने के लिए), ओ (एन) का भुगतान पूरी तरह से किया जाता है।

  • Data.Map और Data.Set ओ (एलजी एन) संशोधन और लुकअप की अनुमति दें। यह एल्गोरिदमिक जटिलता को बदलता है, लेकिन अक्सर पर्याप्त तेज़ होता है।
  • Data.Array.ST का STUArray s (Int, Int) Bool आपको अनिवार्य सरणी देगा, जिससे आप क्लासिक (गैर-कार्यात्मक) तरीके से एल्गोरिदम लागू कर सकते हैं।
+0

मैंने सोचा कि ऐरे अपडेट ओ (एन) थे और एल्गोरिदम की जटिलता खराब कर देंगे। क्या मै गलत हु? – hugomg

+0

अपडेट किया गया - आप सही हैं, लेकिन छोटे एन के लिए यह अभी भी सस्ता है :) – bdonlan

3

इस दृष्टिकोण के साथ मूल संभावित समस्या यह है कि हर बार एक रानी रखी जाने वाली विकर्णों के लिए सरणी को संशोधित करने की आवश्यकता होती है। विकर्णों के लिए निरंतर लुकअप समय का छोटा सुधार आवश्यक रूप से नए संशोधित सरणी बनाने के अतिरिक्त काम के लायक नहीं हो सकता है।

लेकिन सबसे अच्छा तरीका है पता करने के लिए असली जवाब यह कोशिश करने के लिए है, इसलिए मैं थोड़ा के आसपास खेला और के साथ आया था निम्नलिखित:

import Data.Array.IArray (array, (//), (!)) 
import Data.Array.Unboxed (UArray) 
import Data.Set (Set, fromList, toList, delete) 

-- contains sets of unoccupied columns and lookup arrays for both diagonals 
data BoardState = BoardState (Set Int) (UArray Int Bool) (UArray Int Bool) 

-- an empty board 
board :: Int -> BoardState 
board n 
    = BoardState (fromList [0..n-1]) (truearr 0 (2*(n-1))) (truearr (1-n) (n-1)) 
    where truearr a b = array (a,b) [(i,True) | i <- [a..b]] 

-- modify board state if queen gets placed 
occupy :: BoardState -> (Int, Int) -> BoardState 
occupy (BoardState c s d) (a,b) 
    = BoardState (delete b c) (tofalse s (a+b)) (tofalse d (a-b)) 
    where tofalse arr i = arr // [(i, False)] 

-- get free fields in a row 
freefields :: BoardState -> Int -> [(Int, Int)] 
freefields (BoardState c s d) a = filter freediag candidates 
    where candidates = [(a,b) | b <- toList c] 
     freediag (a,b) = (s ! (a+b)) && (d ! (a-b)) 

-- try all positions for a queen in row n-1 
place :: BoardState -> Int -> [[(Int, Int)]] 
place _ 0 = [[]] 
place b n = concatMap place_ (freefields b (n-1)) 
    where place_ p = map (p:) (place (occupy b p) (n-1)) 

-- all possibilities to place n queens on a n*n board 
queens :: Int -> [[(Int, Int)]] 
queens n = place (board n) n 

यह काम करता है और तेजी से एन = 14 मोटे तौर पर 25% के लिए है आपके द्वारा वर्णित संस्करण की तुलना में। मुख्य स्पीडअप अनबॉक्स किए गए सरणी bdonian अनुशंसित करने से आता है। सामान्य Data.Array के साथ यह प्रश्न में संस्करण के समान रनटाइम है।

यह मानक लाइब्रेरी से अन्य सरणी प्रकारों को आजमाने के लिए भी लायक हो सकता है यह देखने के लिए कि उनका उपयोग प्रदर्शन को और बेहतर कर सकता है या नहीं।

3

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

मुझे लगता है कि ओ (1) एन-क्वींस एल्गोरिदम के शुद्ध कार्यात्मक संस्करण में सैद्धांतिक रूप से संभव है, डिफएरे के लिए एक पूर्ववत विधि जोड़कर, जो डुप्लिकेट को हटाने और उन्हें फिर से चलाने से बचने के लिए मतभेदों में एक नजर डालने का अनुरोध करता है।

यदि बैकट्रैकिंग एन-क्वींस एल्गोरिदम संचालित करने के तरीके की मेरी समझ में सही है, तो डिफएरे के कारण होने वाली मंदी इसलिए है क्योंकि अनावश्यक अंतर बनाए रखा जा रहा है।

अमूर्त में, "डिफएरे" (आवश्यक रूप से हास्केल) के पास एक सेट तत्व विधि नहीं है (या हो सकती है) जो सरणी की एक नई प्रतिलिपि देता है और मूल प्रतिलिपि के साथ एक अंतर रिकॉर्ड स्टोर करता है, जिसमें पॉइंटर शामिल है नई बदली गई प्रति। जब मूल प्रति को किसी तत्व तक पहुंचने की आवश्यकता होती है, तो वर्तमान प्रतिलिपि की प्रतिलिपि में बदलावों को पूर्ववत करने के लिए अंतरों की इस सूची को फिर से चलाया जाना चाहिए। ध्यान दें कि यहां तक ​​कि ओवरहेड भी है कि इसे सिंगल-लिंक्ड सूची को अंत तक चलाना होगा, इसे दोबारा चलाने से पहले।

कल्पना कीजिए कि इन्हें डबल-लिंक्ड सूची के रूप में संग्रहीत किया गया था, और निम्नानुसार एक पूर्ववत ऑपरेशन था।

एक अमूर्त वैचारिक स्तर से, बैकट्रैकिंग एन-क्वींस एल्गोरिदम क्या करता है जो बूलियन के कुछ सरणी पर फिर से चल रहा है, जो कि प्रत्येक रिकर्सिव स्तर पर उन सरणी में रानी की स्थिति को आगे बढ़ाता है। this animation देखें।

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

बैकट्रैकिंग पर पूरे सरणी की अनावश्यक प्रतिलिपि को हटाने के लिए यह क्या होता है। अंतर रिकॉर्ड के जोड़ को जोड़ने और पूर्ववत करने के लिए एल्गोरिदम के अनिवार्य संस्करण की तुलना में कुछ अतिरिक्त ओवरहेड अभी भी है, लेकिन यह निरंतर समय के करीब हो सकता है, यानी ओ (1)।

यदि मैं एन-क्वीन एल्गोरिदम को सही ढंग से समझता हूं, तो पूर्ववत ऑपरेशन के लिए लुकबैक केवल एक है, इसलिए कोई पैदल चलना नहीं है। इस प्रकार रानी की स्थिति को स्थानांतरित करते समय सेट तत्व के अंतर को स्टोर करना भी आवश्यक नहीं है, क्योंकि पुराने प्रतिलिपि तक पहुंचने से पहले इसे पूर्ववत कर दिया जाएगा। हमें इस प्रकार को सुरक्षित रूप से व्यक्त करने का एक तरीका चाहिए, जो करना आसान है, लेकिन मैं इसे पाठक के लिए एक अभ्यास के रूप में छोड़ दूंगा, क्योंकि यह पोस्ट पहले से ही बहुत लंबा है।


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

यहां डेटा संरचना के लिए इंटरफेस का पालन करता है I कल्पना। नीचे दिए गए वाक्यविन्यास कोप्यूट है, लेकिन मुझे लगता है कि यह स्कैला के करीब है, कि आप समझ सकते हैं कि क्या इरादा है।

ध्यान दें कि यदि यह बहुप्रचारित है तो डिफएरे के किसी भी कार्यान्वयन को अनुचित रूप से धीमा कर दिया जाएगा, लेकिन एन-क्वींस बैकट्रैकिंग एल्गोरिदम को डिफएरे को बहुप्रचारित करने की आवश्यकता नहीं है। इस जवाब के लिए टिप्पणियों में इंगित करने के लिए एडवर्ड Kmett के लिए धन्यवाद।

interface Array[T] 
{ 
    setElement : Int -> T -> Array[T]  // Return copy with changed element. 
    setElement : Int -> Maybe[T] -> Array[T] 
    array  :() -> Maybe[DiffArray[T]]// Return copy with the DiffArray interface, or None if first called setElement() before array(). 
} 
// An immutable array, typically constructed with Array(). 
// 
// If first called setElement() before array(), setElement doesn't store differences, 
// array will return None, and thus setElement is as fast as a mutable imperative array. 
// 
// Else setElement stores differences, thus setElement is O(1) but with a constant extra overhead. 
// And if setElement has been called, getElement incurs an up to O(n) sequential time complexity, 
// because a copy must be made and the differences must be applied to the copy. 
// The algorithm is described here: 
// http://stackoverflow.com/questions/1255018/n-queens-in-haskell-without-list-traversal/7194832#7194832 
// Similar to Haskell's implementation: 
// http://www.haskell.org/haskellwiki/Arrays#DiffArray_.28module_Data.Array.Diff.29 
// http://www.haskell.org/pipermail/glasgow-haskell-users/2003-November/005939.html 
// 
// If a multithreaded implementation is used, it can be extremely slow, 
// because there is a race condition on every method, which requires internal critical sections. 

interface DiffArray[T] inherits Array[T] 
{ 
    unset  :() -> Array[T]  // Return copy with the previous setElement() undone, and its difference removed. 
    getElement : Int -> Maybe[T]  // Return the the element, or None if element is not set. 
} 
// An immutable array, typically constructed with Array(...) or Array().array. 

अद्यतन: मैं Scala implementation है, जो एक बेहतर interface है क्या मैं ऊपर का सुझाव दिया था की तुलना में पर काम कर रहा हूँ। मैंने यह भी समझाया है कि फ़ोल्ड्स के लिए अनुकूलन एक म्यूटेबल सरणी के समान स्थिर ओवरहेड तक कैसे पहुंचता है।

+0

यह थोड़ी देर पहले से ही मैंने इस समस्या को देखा है, लेकिन मुझे यह आसान नहीं लगता ... – hugomg

+0

मैं एक कार्यान्वयन पर काम कर रहा हूं , और मैं बाद में यहां रिपोर्ट करूंगा। –

+0

बैकट्रैकिंग एन-क्वीन एल्गोरिदम एक रिकर्सिव फ़ंक्शन है जो 3 पैरामीटर लेता है, प्रत्येक विकर्ण दिशा (\ और /), और पंक्ति गणना के लिए एक सरणी। यह उस पंक्ति पर कॉलम पर फिर से चलाता है, उस पंक्ति पर रानी की स्थिति को सरणी में स्थानांतरित करता है, और प्रत्येक कॉलम स्थिति पर cur_row + 1 के साथ खुद को पुन: स्थापित करता है। इसलिए मुझे लगता है कि सरणी में रानी की स्थिति का आंदोलन पूर्ववत है जैसा कि मैंने वर्णन किया है मेरे जवाब में ऐसा लगता है कि यह बहुत आसान नहीं है। तो कृपया कोई मुझे बताएं क्यों, या जब मैं कोड में कार्यान्वयन लिखता हूं तो मुझे पता चल जाएगा। –

1

मेरे पास एक समाधान है। हालांकि, निरंतर बड़ा हो सकता है, इसलिए मुझे वास्तव में कुछ भी मारने की उम्मीद नहीं है।

-- | Zipper over a list of integers 
type Zipper = (Bool, -- does the zipper point to an item? 
       [Int], -- previous items 
         -- (positive numbers representing 
         -- negative offsets relative to the previous list item) 
       [Int] -- next items (positive relative offsets) 
       ) 

type State = 
    (Zipper, -- Free columns zipper 
    Zipper, -- Free diagonal1 zipper 
    Zipper -- Free diagonal2 zipper 
    ) 

यह आवश्यक कार्यों के सभी हे (1) में किया जा करने के लिए अनुमति देता है:

यहाँ मेरी डेटा संरचना है।

कोड यहाँ पाया जा सकता है: http://hpaste.org/50707

गति बुरा है - यह संदर्भ समाधान सबसे आदानों पर सवाल में तैनात की तुलना में धीमी है। मैंने उन्हें [1,3 .. 15] इनपुट पर एक दूसरे के खिलाफ बेंचमार्क किया है और निम्नलिखित समय अनुपात ((संदर्भ समाधान समय/मेरा समाधान समय)% में मिला है:

[24.66%, 1 9 .8 9%, 23.74%, 41.22%, 42.54% , 66.1 9%, 84.13%, 106.30%]

एसिम्प्टोटिक जटिलता में अंतर दिखाते हुए, मेरे सापेक्ष संदर्भ समाधान के लगभग रैखिक धीमी गति से ध्यान दें।

मेरा समाधान शायद सख्तता और इस तरह की चीजों के मामले में भयानक है, और बेहतर परिणाम प्राप्त करने के लिए कुछ अच्छे अनुकूलन कंपाइलर (उदाहरण के लिए डॉन स्टीवर्ट जैसे) को खिलाया जाना चाहिए।

वैसे भी, मुझे लगता है कि इस समस्या में ओ (1) और ओ (लॉग (एन)) अलग-अलग हैं क्योंकि लॉग (8) केवल 3 है और इस तरह के स्थिरांक एल्गोरिदम की बजाय माइक्रो-ऑप्टिमाइज़ेशन के अधीन हैं।

संबंधित मुद्दे