2010-05-07 32 views
11

मैं वर्तमान में मज़े के लिए एक परियोजना यूलर समस्या (www.projecteuler.net) पर काम कर रहा हूं लेकिन एक ठोकर खाई मार दी है। समस्या में से एक संख्या 20x20 ग्रिड प्रदान करती है और सीधी रेखा पर 4 संख्याओं का सबसे बड़ा उत्पाद मांगती है। यह रेखा या तो क्षैतिज, लंबवत, या विकर्ण हो सकती है।क्षैतिज, ऊर्ध्वाधर और विकर्ण रेखाओं पर गुणा संख्या

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

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

+0

कृपया यूलर समस्या संख्या भी निर्दिष्ट करें। कुछ लोगों ने पहले से ही इसे हल कर लिया है और शायद अपने स्वयं के समाधान को देखने की इच्छा रख सकते हैं, और शायद आपको – yairchu

+0

पर आधारित एक उपयोगी उत्तर दे सकते हैं। यह समस्या # 11 – MtnViewMark

उत्तर

10

मैं अनुमानित डॉन स्टीवर्ट से असहमत हूं। समस्या की संयोजी प्रकृति और तथ्य यह है कि समस्या का आकार केवल 20x20 है, सूचियों की सूची पर्याप्त तेज़ी से होने जा रही है। और अंतिम जो चीज आप चाहते हैं वह सरणी अनुक्रमण के साथ फ़ुटज़ करना है। इसके बजाय मैं सुझाव देता हूं कि आप रिचर्ड बर्ड द्वारा विकसित अपनी तकनीक को अपने उचित प्रसिद्ध sudoku solver में विकसित करें। अधिक विशिष्ट होना करने के लिए, मेरा सुझाव था निम्नलिखित:

  • एक समारोह है कि एक दृश्य दिया लिखें, लंबाई 4.

  • के सभी सन्निहित subsequences रिटर्न एक समारोह है कि एक ग्रिड दिया लिखें, सभी रिटर्न पंक्तियों।

  • एक ग्रिड दिया गया एक फ़ंक्शन लिखें, सभी कॉलम लौटाएं।

  • एक ग्रिड दिया गया एक समारोह लिखें, सभी विकर्ण लौटाता है।

हाथ में इन कार्यों के साथ, आपका समाधान आसान होगा। लेकिन जैसा कि आप विकर्ण का उल्लेख करते हैं, इतना स्पष्ट नहीं है। वैसे भी एक विकर्ण क्या है? एक उदाहरण पर नजर डालते हैं:

X . . . . . 
. X . . . . 
. . X . . . 
. . . X . . 
. . . . X . 
. . . . . X 

एक पल है कि आप drop समारोह का उपयोग करें और आप पंक्ति 0 से 0 तत्वों, पंक्ति 1 से 1 तत्व, और इतने पर ड्रॉप के लिए मान लीजिए।

X . . . . . 
X . . . . 
X . . . 
X . . 
X . 
X 

विकर्ण के तत्वों अब त्रिकोणीय बात आप छोड़ दिया है के पहले स्तंभ के रूप में: यहाँ क्या आप के साथ हवा है।इससे भी बेहतर, आपके द्वारा छोड़ी गई चीज़ों के प्रत्येक कॉलम मूल मैट्रिक्स का विकर्ण है। कुछ समरूपता परिवर्तनों में फेंको और आप सभी किसी भी आकार के वर्ग मैट्रिक्स के विकर्णों को आसानी से गणना करने में सक्षम होंगे। अपने "अपने लम्बे समय के अनुरूप 4" समारोह के साथ हर किसी को मारो, और बॉब का चाचा!


उन लोगों के लिए थोड़ा और विस्तार जो अटक जा सकता है:

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

  • ट्रांसपोज़ एक लाइन फ़ंक्शन है, और आपको कॉलम को साफ़ करने के लिए वैसे भी इसकी आवश्यकता है।

  • मिरर छवि — स्थानांतरित करने से भी सरल है, इस बारे में सोचें कि आप प्रीलूड से किस प्रकार का उपयोग कर सकते हैं।

समरूपता विधि प्रत्येक प्रमुख विकर्ण को दो बार देगी; सौभाग्य से समस्या के लिए कहा गया है कि एक विकर्ण दोहराना ठीक है।

+0

मैं सप्ताहांत में इसे हल करने के बारे में सोच रहा था और आपका उदाहरण दिखाता है कि जिस दृष्टिकोण को मैं लेने की योजना बना रहा था :) –

+0

"आखिरी चीज जो आप चाहते हैं वह सरणी अनुक्रमण के साथ फ़ुटज़ करना है" - वेक्टर जैसे सरणी पुस्तकालय हैं संयोजकों के आधार पर, इसलिए उपयोग की आसानी में सूची एपीआई से इससे भी बदतर नहीं है। लेकिन मैं 20x20 पर अपना मुद्दा लेता हूं। –

+0

@ डॉन: ओह, अच्छा। मैंने आपके वेक्टर लिंक का पालन किया और उपलब्ध स्वादिष्ट वेक्टर कार्यों की संख्या से अभिभूत था। –

1

आप सूचकांक द्वारा सूची में तत्वों को पुनर्प्राप्त करने के लिए !! फ़ंक्शन का उपयोग कर सकते हैं। एक निश्चित चरण के साथ या तो सूचकांक में वृद्धि या कमी आपको एक विकर्ण हो जाता है।

+0

सूचियों पर यादृच्छिक पहुंच ओ (एन) है। बेशक यह शायद 20x20 ग्रिड के लिए कोई समस्या नहीं है। – sepp2k

+0

यह वही था जब मैंने इंडेक्सिंग और स्लाइसिंग का उपयोग करके इसे हल करने में सक्षम होने का उल्लेख किया था। शायद यह सबसे अच्छा समाधान है, मुझे बस एक अंतर्ज्ञान था कि मैं कोशिश कर सकता था कि कुछ और सुरुचिपूर्ण था। – untwisted

2

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

सरणी का उपयोग करने के बारे में कैसे? जैसे parallel vectors या regular vectors

+0

मैंने अभी तक वैकल्पिक डेटा संरचनाओं में बहुत कुछ नहीं देखा है, हालांकि यह कार्यान्वयन की आसानी या केवल दक्षता और गति के संदर्भ में मेरे लिए कुछ भी प्रदान करेगा? वैसे, महान पुस्तक के लिए धन्यवाद, यह मेरे बड़े पैमाने पर सीखने के कारण का एक बड़ा हिस्सा है जैसा कि मैं हूं :) – untwisted

+1

@ चेतावनी: एक सरणी का उपयोग करके अपने समाधान को कार्यान्वित करना आसान होना चाहिए टाइप करें, क्योंकि आप अपने सरणी को टुपल (एक्स, वाई) समन्वय के रूप में इंडेक्स कर सकते हैं, इसलिए यदि आप हैकेल की वेनिला 'डेटा.एरे' लाइब्रेरी का उपयोग कर रहे हैं, तो आपके पास 'ऐरे (इंट, इंट) इंट' का एक प्रकार होगा। यह तब तक बहुत तेज़ होगा जब तक कि आपके पास [[int]] का उपयोग करके वास्तव में चालाक एल्गोरिदम नहीं है। – jberryman

+0

स्पष्टीकरण के लिए धन्यवाद, जो [[Int]] के साथ काम करने के लिए इसे अधिक आसान बना देगा। – untwisted

2

ठीक है, इस विशेष समस्या के लिए, एक रैखिक सूची या सरणी वास्तव में सबसे आसान संरचना है! कुंजी को दिए गए कदम के साथ सूची के माध्यम से छोड़ने के रूप में इन रनों के बारे में सोचना है। ग्रिड डब्ल्यू × आकार में है, तो

  • है एक क्षैतिज रन
  • के एक कदम एक ऊर्ध्वाधर रन डब्ल्यू
  • एक विकर्ण रन का एक कदम है है w-1
  • एक विकर्ण दौड़ का डब्ल्यू + 1
  • का एक मार्ग है

अब, चार प्रकार के रनों में से प्रत्येक के लिए, आपको केवल संभावित शुरुआती बिंदुओं की गणना करने की आवश्यकता है। कुछ इस तरह:

allRuns :: Int -> Int -> Int -> [a] -> [[a]] 
allRuns n w h es = horiz ++ vert ++ acute ++ grave 
    where horiz = runs [0..w-n] [0..h-1] 1 
      vert = runs [0..w-1] [0..h-n] w 
      acute = runs [n-1..w-1] [0..h-n] (w-1) 
      grave = runs [0..w-n] [0..h-n] (w+1) 

      runs xs ys s = [run (x+y*w) s | x <- xs, y <- ys] 
      run i s = map (es!!) [i,i+s..i+(n-1)*s] 
बेशक

, एक कुशल कार्यान्वयन में, आप [a]Data.Array Int a और es!! की तरह कुछ के साथ es!

+0

इंडेक्स अंकगणित फॉरट्रान है, हास्केल नहीं। –

+1

दरअसल यह है, लेकिन इस मामले में, समस्या * अनिवार्य रूप से अनुक्रमण में से एक है। मुझे अभी तक कोई समाधान नहीं दिख रहा है जो इस छोटे और स्पष्ट सभी रनों को उत्पन्न करता है। मुझे लगता है, अंत में, यह व्यक्त करता है कि समस्या सीधे इसके बाद क्या है। और मुझे लगता है कि * वह * हास्केल का सार है! – MtnViewMark

1

तो साथ बदलें चाहते हैं आप एक NxN ग्रिड है और आप सभी क्षैतिज निकालना चाहते हैं , लंबाई एम की ऊर्ध्वाधर और विकर्ण रेखाएं, फिर अधिकतम उत्पाद खोजने के लिए। पर उदाहरण 4x4 ग्रिड कुछ हास्केल तकनीक को वर्णन करते हैं, लाइन की लंबाई जा रहा है 2 के साथ:

chunks 2 [1,2,3,4] == [[1,2],[2,3],[3,4]] 
:

[[ 1, 2, 3, 4], 
[ 5, 6, 7, 8], 
[ 9,10,11,12], 
[13,14,15,16]] 

क्षैतिज और ऊर्ध्वाधर आसान है, आप सभी की जरूरत एक समारोह है कि एक सूची से लंबाई एम का हिस्सा निकालने है

इस तरह का फ़ंक्शन [a] -> [[a]] है।यह एक सूची से संबंधित कार्य है, इसलिए पहिया को फिर से शुरू करने से पहले, देखते हैं कि Data.List में कुछ ऐसा है या नहीं। अहा, tails समान है, यह सूची की शुरुआत से हटाया से अधिक से अधिक तत्वों के साथ सूचियों रिटर्न:

tails [1,2,3,4] == [[1,2,3,4],[2,3,4],[3,4],[4],[]] 

केवल हम उन्हें लंबाई 2. बनाने के लिए उप-सूचियों को छोटा कर सकता है लेकिन हम, map का उपयोग करके कर सकते हैं समारोह, सूची के प्रत्येक तत्व के लिए एक समारोह लागू होता है और एक नई सूची देता है जो:

map (take n) (tails xs) -- [[1,2],[2,3],[3,4],[4],[]] 

मैं छोटे लाइनों के बारे में चिंता नहीं करता, के रूप में मूल कार्य सबसे बड़ी उत्पाद मिल रहा है, और [15, N] ≥ के उत्पाद [15], एन ≥ 1. का उत्पाद। लेकिन यदि आप उनसे छुटकारा पाना चाहते हैं, तो ऐसा लगता है लंबाई की सूची में एन में लंबाई एम के एन-एम + 1 भाग होते हैं, इसलिए आप परिणामी सूची में take (4-2+1) लागू कर सकते हैं। वैकल्पिक रूप से आप कर सकते थे बस filter सूची:

chunks n xs = filter ((==n) . length) $ map (take n) (tails xs) 
-- [[1,2],[2,3],[3,4]] 

ठीक है, हम एक सूची से हिस्सा की एक सूची निकाल सकते हैं, लेकिन हम एक 2 डी ग्रिड, नहीं एक फ्लैट सूची है! map फिर हमें बचाता है:

map (chunks 2) grid -- [[[1,2],[2,3],[3,4]],[[5,6],[6,7],[7,8]],...] 

लेकिन यहाँ बात है, जिसके परिणामस्वरूप कोड अलग-अलग सूचियों में हिस्सा रखती है, और बातें पेचीदा हो, जैसा कि हम वास्तव में, परवाह नहीं है जो लाइन से हिस्सा ही शुरू होता है। इसलिए हम concat . map या समकक्ष concatMap द्वारा एक स्तर है, जिसके परिणामस्वरूप सूची समतल करना चाहेंगे: अब

concatMap (chunks 2) grid -- [[1,2],[2,3],[3,4],[5,6],[6,7],[7,8],...] 

, मैं कैसे एक ग्रिड से खड़ी हिस्सा मिलता है? पहली बार में डरावना लगता है, जब तक आप महसूस करते हैं कि आप पूरे ग्रिड transpose कर सकते हैं, जैसे कि पंक्तियों में स्तंभों और स्तंभों में पंक्तियों की बारी है, और फिर एक ही कोड लागू करें:

concatMap (chunks 2) (transpose grid) -- [[1,5],[5,9],[9,13],[2,6],[6,10],...] 

अब कठिन हिस्सा: विकर्ण लाइनों। Norman Ramsey एक विचार देता है: क्या होगा यदि आप लाइन 0 से 0 तत्वों को छोड़ सकते हैं, लाइन 1 से 1 तत्व इत्यादि? विकर्ण रेखा एक ऊर्ध्वाधर रेखा बन जाएगी, जो निकालना आसान है। आपको याद है कि सूची के प्रत्येक तत्व में एक फ़ंक्शन लागू करने के लिए आप map का उपयोग करते हैं, लेकिन यहां आपको प्रत्येक तत्व, drop 0, drop 1, drop 2, आदि map के अनुरूप विभिन्न कार्यों को लागू करने की आवश्यकता नहीं है। लेकिन देखो, drop का पहला तर्क लगातार संख्याओं का एक पैटर्न बनाता है, जिसे अनंत सूची [0..] के रूप में प्रदर्शित किया जा सकता है। अब क्या होगा यदि हम [0..] से एक तत्व ले सकते हैं तो हमें एक ऐसी फ़ंक्शन है जो एक अनंत सूची [0..] और ग्रिड से एक पंक्ति से एक संख्या लेती है, और इस संख्या के साथ drop लागू होती है। zipWith तुम क्या जरूरत है:

zipWith drop [0..] grid -- [[1,2,3,4],[6,7,8],[11,12],[16]] 
map head $ zipWith drop [0..] grid -- [1,6,11,16] 

लेकिन मैं लंबाई 2 के सभी विकर्ण, न सिर्फ सबसे बड़ी विकर्ण चाहते हैं। तो ग्रिड को देखो और सोचें, पंक्ति 0 पर तत्वों के साथ आप कौन सी विकर्ण रेखाएं देखते हैं? [1,6],[2,7],[3,8]। कैसे मैं अन्य पंक्तियों से शुरू के रूप में अच्छी विकर्णों मिलता है

transpose $ zipWith drop [0,1] grid -- [[1,6],[2,7],[3,8],[4]] 

अब: तो यह स्पष्ट है कि आप केवल पहले 2 पंक्तियाँ लेने के लिए और तत्वों स्थानांतरित करने के लिए की जरूरत है? हमारी tails चाल याद रखें?हम एक concatMap के लिए हमारी नई समारोह प्रदान करने और tails grid पर लागू करके सभी विकर्णों प्राप्त कर सकते हैं:

concatMap (transpose . zipWith drop [0,1]) (tails g) 
-- [[1,6],[2,7],[3,8],[5,10],[6,11],...] 

लेकिन इन केवल विकर्णों कि से जाना जाता है ऊपर-बाईं ओर निचले-दाएं कोने। उन लोगों के बारे में क्या जो ऊपर-दाएं से नीचे-बाएं जाते हैं? बस ग्रिड की पंक्तियों को उल्टा करने के यह सबसे आसान है:

concatMap (transpose . zipWith drop [0,1]) (tails $ reverse g) 
-- [[13,10],[14,11],[15,12],[9,6],[10,7],...] 

अंत में, आप सभी लाइनों के उत्पादों को खोजने और सबसे बड़ी चयन करने के लिए की जरूरत है। अंतिम कोड इस तरह दिखता है:

grid = [[1..4],[5..8],[9..12],[13..16]] 
chunks n xs = map (take n) (tails xs) 
horizontal = concatMap (chunks 2) grid 
vertical = concatMap (chunks 2) (transpose grid) 
grave = concatMap (transpose . zipWith drop [0,1]) (tails grid) 
acute = concatMap (transpose . zipWith drop [0,1]) (tails $ reverse grid) 
maxProduct = maximum $ map product $ horizontal ++ vertical ++ grave ++ acute 
-- answer: 240 

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

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