3n + 1 समस्या के लिए हास्केल तरीके


12

यहाँ SPOJ से एक सरल प्रोग्रामिंग समस्या है: http://www.spoj.com/problems/PROBTRES/

मूल रूप से, आपको i और j के बीच की संख्या के लिए सबसे बड़े Collatz चक्र का उत्पादन करने के लिए कहा जाता है। (संख्या $ n $ का Collatz चक्र अंततः $ n $ 1. से $ पाने के लिए चरणों की संख्या है।)

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

मैंने इस पोस्ट से आईडिया का उपयोग करके Data.Function.Memoize और साथ ही होम-ब्रूएड लॉग टाइम मेमोलाइजेशन तकनीक की कोशिश की है: /programming/3208258/memoization-in-haskell । दुर्भाग्य से, संस्मरण वास्तव में चक्र (एन) की गणना को भी धीमा बनाता है। मेरा मानना ​​है कि धीमी गति हस्केल मार्ग के ऊपरी हिस्से से आती है। (मैंने व्याख्या करने के बजाय संकलित बाइनरी कोड के साथ चलने की कोशिश की।)

मुझे यह भी संदेह है कि केवल i से j तक संख्याओं को पुनरावृत्त करना महंगा हो सकता है ($ i, j \ le10 ^ 6 $)। इसलिए मैंने भी http://blog.openendings.net/2013/10/range-trees-and-profiling-in-haskell.html से विचार का उपयोग करते हुए, रेंज क्वेरी के लिए सब कुछ करने की कोशिश की । हालाँकि, यह अभी भी "समय सीमा से अधिक" त्रुटि देता है।

क्या आप इसके लिए एक स्वच्छ प्रतियोगी हास्केल कार्यक्रम को सूचित करने में मदद कर सकते हैं?


10
यह पोस्ट मुझे ठीक लगी। यह एक एल्गोरिथम समस्या है जिसे पर्याप्त प्रदर्शन प्राप्त करने के लिए एक उचित डिजाइन की आवश्यकता होती है। हम वास्तव में यहां नहीं चाहते हैं कि "मैं अपने टूटे हुए कोड को कैसे ठीक करूं" प्रश्न।
रॉबर्ट हार्वे

जवाबों:


7

मैं स्काला में उत्तर दूंगा, क्योंकि मेरा हास्केल उतना ताजा नहीं है, और इसलिए लोगों का मानना ​​है कि यह एक सामान्य कार्यात्मक प्रोग्रामिंग एल्गोरिथ्म है। मैं डेटा संरचनाओं और अवधारणाओं पर टिक जाऊंगा जो आसानी से हस्तांतरणीय हैं।

हम एक फ़ंक्शन के साथ शुरू कर सकते हैं जो एक कोलेजन अनुक्रम उत्पन्न करता है, जो अपेक्षाकृत सरल है, इसके अलावा परिणाम को पास करने के लिए एक तर्क के रूप में इसे पुनरावर्ती बनाने की आवश्यकता है:

def collatz(n: Int, result: List[Int] = List()): List[Int] = {
   if (n == 1) {
     1 :: result
   } else if ((n & 1) == 1) {
     collatz(3 * n + 1, n :: result)
   } else {
     collatz(n / 2, n :: result)
   }
 }

यह वास्तव में अनुक्रम को रिवर्स ऑर्डर में रखता है, लेकिन यह हमारे अगले कदम के लिए एकदम सही है, जो कि लंबाई को एक नक्शे में संग्रहीत करना है:

def calculateLengths(sequence: List[Int], length: Int,
  lengths: Map[Int, Int]): Map[Int, Int] = sequence match {
    case Nil     => lengths
    case x :: xs => calculateLengths(xs, length + 1, lengths + ((x, length)))
}

आप इसे पहले चरण, प्रारंभिक लंबाई, और एक खाली मानचित्र जैसे उत्तर के साथ कहेंगे calculateLengths(collatz(22), 1, Map.empty))। इस तरह आप परिणाम को याद करते हैं। अब हमें collatzइसका उपयोग करने में सक्षम होने के लिए संशोधित करने की आवश्यकता है :

def collatz(n: Int, lengths: Map[Int, Int], result: List[Int] = List()): (List[Int], Int) = {
  if (lengths contains n) {
     (result, lengths(n))
  } else if ((n & 1) == 1) {
    collatz(3 * n + 1, lengths, n :: result)
  } else {
    collatz(n / 2, lengths, n :: result)
  }
}

हम n == 1चेक को समाप्त कर देते हैं, क्योंकि हम केवल नक्शे के साथ आरंभ कर सकते हैं 1 -> 1, लेकिन हमें उन मानचित्रों में जोड़ने की आवश्यकता 1है जो हम मानचित्र में डालते हैं calculateLengths। अब यह याद की गई लंबाई भी लौटा देता है, जहाँ इसकी पुनरावृत्ति रुक ​​जाती है, जिसे हम आरंभ करने के लिए उपयोग कर सकते हैं calculateLengths, जैसे:

val initialMap = Map(1 -> 1)
val (result, length) = collatz(22, initialMap)
val newMap = calculateLengths(result, lengths, initialMap)

अब हमारे पास टुकड़ों के अपेक्षाकृत कुशल कार्यान्वयन हैं, हमें पिछली गणना के परिणामों को अगले गणना के इनपुट में फीड करने का तरीका खोजने की आवश्यकता है। इसे कहा जाता है fold, और जैसा दिखता है:

def iteration(lengths: Map[Int, Int], n: Int): Map[Int, Int] = {
  val (result, length) = collatz(n, lengths)
  calculateLengths(result, length, lengths)
}

val lengths = (1 to 10).foldLeft(Map(1 -> 1))(iteration)

अब वास्तविक उत्तर खोजने के लिए, हमें बस दी गई सीमा के बीच के नक्शे में कुंजियों को फ़िल्टर करना होगा, और अंतिम मूल्य देना होगा:

def answer(start: Int, finish: Int): Int = {
  val lengths = (start to finish).foldLeft(Map(1 -> 1))(iteration)
  lengths.filterKeys(x => x >= start && x <= finish).values.max
}

आकार की श्रेणियों के लिए मेरे REPL में 1000 या तो, उदाहरण इनपुट की तरह, जवाब बहुत ही तुरंत देता है।


3

कार्ल बेवफेल्ड ने पहले ही सवाल का अच्छी तरह से जवाब दिया है, मैं सिर्फ हास्केल संस्करण जोड़ूंगा।

कुशल पुनरावर्तन दिखाने के लिए बुनियादी एल्गोरिथ्म का पहला सरल, गैर-संस्मरण संस्करण:

simpleCollatz :: Int -> Int -> Int
simpleCollatz count 1 = count + 1
simpleCollatz count n | odd n     = simpleCollatz (count + 1) (3 * n + 1)
                      | otherwise = simpleCollatz (count + 1) (n `div` 2)

यह लगभग आत्म-व्याख्यात्मक होना चाहिए।

मैं भी Mapपरिणामों को संग्रहीत करने के लिए एक सरल का उपयोग करूंगा ।

-- double imports to make the namespace pretty
import           Data.Map  ( Map )
import qualified Data.Map as Map

-- a new name for the memoizer
type Store = Map Int Int

हम हमेशा स्टोर में अपने अंतिम परिणामों को देख सकते हैं, इसलिए एक ही मूल्य के लिए हस्ताक्षर है

memoCollatz :: Int -> Store -> Store

चलो अंत के मामले से शुरू करते हैं

memoCollatz 1 store = Map.insert 1 1 store

हाँ हम पहले से जोड़ सकते हैं, लेकिन मुझे परवाह नहीं है। अगला साधारण मामला कृपया।

memoCollatz n store | Just _ <- Map.lookup n store = store

यदि मूल्य है, तो यह है। फिर भी कुछ नहीं कर रहा।

                    | odd n     = processNext store (3 * n + 1)
                    | otherwise = processNext store (n `div` 2)

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

  where processNext store'' next | Just count <- Map.lookup next store''
                                 = Map.insert n (count + 1) store''

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

                                | otherwise
                                = processNext (memoCollatz next store'') next

और अब हम किसी एकल मान की कुशलता से गणना कर सकते हैं। यदि हम कई गणना करना चाहते हैं, तो हम स्टोर पर गुना से गुजरते हैं।

collatzRange :: Int -> Int -> Store
collatzRange lower higher = foldr memoCollatz Map.empty [lower..higher]

(यह यहाँ है कि आप 1/1 मामले को शुरू कर सकते हैं।)

अब हमें केवल इतना करना है कि अधिकतम निकालना है। अभी के लिए स्टोर में एक मूल्य नहीं हो सकता है जो सीमा में एक से अधिक है, इसलिए यह कहने के लिए पर्याप्त है

collatzRangeMax :: Int -> Int -> Int
collatzRangeMax lower higher = maximum $ collatzRange lower higher

बेशक, यदि आप कई रेंज की गणना करना चाहते हैं और उन संगणनाओं के बीच स्टोर को साझा करना चाहते हैं (सिलवटों में आपका दोस्त है) तो आपको एक फिल्टर की आवश्यकता होगी, लेकिन यहां मुख्य ध्यान केंद्रित नहीं है।


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