হাস্কেল 3n + 1 সমস্যার উপায়


12

এখানে এসপিওজে-র একটি সাধারণ প্রোগ্রামিং সমস্যা: http://www.spoj.com/problems/PROBTRES/

মূলত, আপনাকে i এবং j এর মধ্যে সংখ্যার জন্য সবচেয়ে বড় কোলাটজ চক্র আউটপুট করতে বলা হয়। (একটি সংখ্যার কোলাটজ চক্র eventually n $ অবশেষে $ n $ থেকে ১ এ যাওয়ার পদক্ষেপের সংখ্যা)

আমি জাভা বা সি ++ এর তুলনায় তুলনামূলক পারফরম্যান্সের সাথে সমস্যার সমাধান করার জন্য একটি হাস্কেল পথের সন্ধান করছি (যাতে অনুমোদিত রান-টাইম সীমাতে ফিট করা যায়)। যদিও একটি সাধারণ জাভা সমাধান যা ইতিমধ্যে যে কোনও গণনা করা চক্রের দৈর্ঘ্যের স্মৃতিচারণ করে কাজ করবে, তবে আমি হাস্কেল সমাধান পেতে ধারণাটি প্রয়োগ করতে সফল হইনি।

আমি এই পোস্টটি থেকে ধারণাটি ব্যবহার করে ডেটা ফাংশন.মেমোজেস পাশাপাশি হোম-ব্রিউড লগ টাইম মেমোয়েজ করার কৌশলটি চেষ্টা করেছি: /programming/3208258/memoization-in-haskell । দুর্ভাগ্যক্রমে, স্মৃতিচারণ আসলে চক্রের গণনা (এন) আরও ধীর করে তোলে। আমি বিশ্বাস করি ধীরগতিটি হাস্কেল পথের ওভারহেড থেকে আসে। (আমি ব্যাখ্যা করার পরিবর্তে সংকলিত বাইনারি কোড দিয়ে চালানোর চেষ্টা করেছি।)

আমি আরও সন্দেহ করি যে কেবল i থেকে j পর্যন্ত সংখ্যার পুনরাবৃত্তি ব্যয়বহুল হতে পারে ($ i, j \ le10 ^ 6 $)। তাই আমি এমনকি http://blog.openendings.net/2013/10/range-trees-and- প্রোফিলিং-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
}

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ব্যবহার করা উচিত।
ওলাথে
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.