हास्केल में संस्मरण?


136

हास्केल में निम्नलिखित फ़ंक्शन को कुशलता से हल करने के लिए कोई भी संकेत, बड़ी संख्या के लिए (n > 108)

f(n) = max(n, f(n/2) + f(n/3) + f(n/4))

मैंने हॅस्केल में संस्मरणों के उदाहरणों को देखा है, जिसमें संख्याओं को हल करने के लिए, जिसमें आवश्यक संख्या तक सभी संख्याओं की गणना (lazily) शामिल है। लेकिन इस मामले में, किसी दिए गए n के लिए, हमें केवल बहुत कम मध्यवर्ती परिणामों की गणना करने की आवश्यकता है।

धन्यवाद


110
केवल इस अर्थ में कि यह कुछ काम है जो मैं घर पर कर रहा हूं :-)
एंजेल डे विसेंट

जवाबों:


256

हम इसे एक संरचना बनाकर बहुत कुशलता से कर सकते हैं जिसे हम उप-रैखिक समय में अनुक्रमित कर सकते हैं।

लेकिन पहले,

{-# LANGUAGE BangPatterns #-}

import Data.Function (fix)

चलो परिभाषित करते हैं f, लेकिन इसे सीधे कॉल करने के बजाय 'खुली पुनरावृत्ति' का उपयोग करते हैं।

f :: (Int -> Int) -> Int -> Int
f mf 0 = 0
f mf n = max n $ mf (n `div` 2) +
                 mf (n `div` 3) +
                 mf (n `div` 4)

आप fका उपयोग करके एक unmemoized प्राप्त कर सकते हैंfix f

यह आपको यह परखने देगा कि fछोटे मूल्यों के लिए आपका क्या मतलब हैf कॉलिंग के , उदाहरण के लिए:fix f 123 = 144

हम इसे परिभाषित करके याद कर सकते हैं:

f_list :: [Int]
f_list = map (f faster_f) [0..]

faster_f :: Int -> Int
faster_f n = f_list !! n

जो कि अच्छा प्रदर्शन करता है, और बदलता है कि O (n ^ 3) लेने जा रहा है है कि मध्यवर्ती परिणामों को याद रखने वाली चीज़ के साथ समय ।

लेकिन यह अभी भी रैखिक समय लेता है सूचकांक के लिए बस के लिए ज्ञापन जवाब खोजने के लिए mf। इसका मतलब है कि परिणाम:

*Main Data.List> faster_f 123801
248604

सहनीय हैं, लेकिन परिणाम इससे बेहतर नहीं है। हम बेहतर कर सकते हैं!

सबसे पहले, एक अनंत पेड़ को परिभाषित करते हैं:

data Tree a = Tree (Tree a) a (Tree a)
instance Functor Tree where
    fmap f (Tree l m r) = Tree (fmap f l) (f m) (fmap f r)

और फिर हम इसमें अनुक्रमित करने का एक तरीका परिभाषित करेंगे, इसलिए हम इसके बजाय O (लॉग एन) समय nमें सूचकांक के साथ एक नोड पा सकते हैं :

index :: Tree a -> Int -> a
index (Tree _ m _) 0 = m
index (Tree l _ r) n = case (n - 1) `divMod` 2 of
    (q,0) -> index l q
    (q,1) -> index r q

... और हमें प्राकृतिक संख्याओं से भरा एक पेड़ मिल सकता है जो सुविधाजनक हो ताकि हमें उन सूचकांकों के साथ इधर-उधर न भटकना पड़े:

nats :: Tree Int
nats = go 0 1
    where
        go !n !s = Tree (go l s') n (go r s')
            where
                l = n + s
                r = l + s
                s' = s * 2

चूंकि हम इंडेक्स कर सकते हैं, आप बस एक पेड़ को सूची में बदल सकते हैं:

toList :: Tree a -> [a]
toList as = map (index as) [0..]

आप जो सत्यापन करते हैं, toList natsउसे सत्यापित करके आप अब तक के काम को देख सकते हैं[0..]

अभी,

f_tree :: Tree Int
f_tree = fmap (f fastest_f) nats

fastest_f :: Int -> Int
fastest_f = index f_tree

ऊपर दी गई सूची के साथ ही काम करता है, लेकिन प्रत्येक नोड को खोजने के लिए रैखिक समय लेने के बजाय, यह लघुगणक समय में पीछा कर सकता है।

परिणाम काफी तेज है:

*Main> fastest_f 12380192300
67652175206

*Main> fastest_f 12793129379123
120695231674999

वास्तव में यह तो बहुत तेजी से है कि आप के माध्यम से जाने के लिए और जगह ले सकता है Intके साथ Integerऊपर और लगभग तुरंत हास्यास्पद बड़ा जवाब पाने

*Main> fastest_f' 1230891823091823018203123
93721573993600178112200489

*Main> fastest_f' 12308918230918230182031231231293810923
11097012733777002208302545289166620866358

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

5
अनंत सूची के मामले में 111111111 से जुड़ी वस्तुओं की लंबी सूची है। पेड़ का मामला लॉग एन के साथ काम कर रहा है * नोड्स की संख्या तक पहुंच गई।
एडवर्ड KMETT

2
यानी सूची संस्करण को सूची में सभी नोड्स के लिए थ्रक्स बनाना पड़ता है, जबकि ट्री संस्करण उनमें से बहुत सारे बनाने से बचता है।
टॉम एलिस

7
मुझे पता है कि यह एक पुरानी पोस्ट है, लेकिन कॉल के दौरान पेड़ में अनावश्यक रास्ते को बचाने से बचने के लिए इसे f_treeएक whereखंड में परिभाषित नहीं किया जाना चाहिए ?
dfeuer

17
इसे CAF में स्टफ करने का कारण यह था कि आप कॉल भर में मेमोइज़ेशन प्राप्त कर सकते थे। यदि मेरे पास एक महंगा कॉल था जिसे मैं याद कर रहा था, तो मैं शायद इसे सीएएफ में छोड़ दूंगा, इसलिए यहां तकनीक दिखाई गई है। एक वास्तविक आवेदन में निश्चित रूप से स्थायी संस्मरण के लाभ और लागत के बीच एक व्यापार-बंद है। यद्यपि, यह प्रश्न दिया गया था कि संस्मरण कैसे प्राप्त किया जाए, मुझे लगता है कि यह एक ऐसी तकनीक के साथ जवाब देने के लिए भ्रामक होगा जो जानबूझकर कॉल के दौरान संस्मरण से बचा जाता है, और अगर कुछ और नहीं तो यह टिप्पणी यहां लोगों को इस तथ्य की ओर इशारा करेगी कि सूक्ष्मताएं हैं। ;)
एडवर्ड KMETT

17

एडवर्ड के जवाब इस तरह के एक अद्भुत रत्न कि मैं इसे दोहराया और के कार्यान्वयन उपलब्ध करा चुके हैं memoListऔर memoTreecombinators कि खुले पुनरावर्ती रूप में एक समारोह memoize।

{-# LANGUAGE BangPatterns #-}

import Data.Function (fix)

f :: (Integer -> Integer) -> Integer -> Integer
f mf 0 = 0
f mf n = max n $ mf (div n 2) +
                 mf (div n 3) +
                 mf (div n 4)


-- Memoizing using a list

-- The memoizing functionality depends on this being in eta reduced form!
memoList :: ((Integer -> Integer) -> Integer -> Integer) -> Integer -> Integer
memoList f = memoList_f
  where memoList_f = (memo !!) . fromInteger
        memo = map (f memoList_f) [0..]

faster_f :: Integer -> Integer
faster_f = memoList f


-- Memoizing using a tree

data Tree a = Tree (Tree a) a (Tree a)
instance Functor Tree where
    fmap f (Tree l m r) = Tree (fmap f l) (f m) (fmap f r)

index :: Tree a -> Integer -> a
index (Tree _ m _) 0 = m
index (Tree l _ r) n = case (n - 1) `divMod` 2 of
    (q,0) -> index l q
    (q,1) -> index r q

nats :: Tree Integer
nats = go 0 1
    where
        go !n !s = Tree (go l s') n (go r s')
            where
                l = n + s
                r = l + s
                s' = s * 2

toList :: Tree a -> [a]
toList as = map (index as) [0..]

-- The memoizing functionality depends on this being in eta reduced form!
memoTree :: ((Integer -> Integer) -> Integer -> Integer) -> Integer -> Integer
memoTree f = memoTree_f
  where memoTree_f = index memo
        memo = fmap (f memoTree_f) nats

fastest_f :: Integer -> Integer
fastest_f = memoTree f

12

सबसे कुशल तरीका नहीं है, लेकिन याद रखना है:

f = 0 : [ g n | n <- [1..] ]
    where g n = max n $ f!!(n `div` 2) + f!!(n `div` 3) + f!!(n `div` 4)

अनुरोध करते समय f !! 144, यह जाँच की जाती है कि f !! 143मौजूद है, लेकिन इसकी सही कीमत की गणना नहीं की गई है। यह अभी भी गणना के कुछ अज्ञात परिणाम के रूप में सेट है। गणना किए गए एकमात्र सटीक मान आवश्यक हैं।

इसलिए शुरू में, जहां तक ​​गणना की गई है, कार्यक्रम को कुछ भी नहीं पता है।

f = .... 

जब हम अनुरोध करते हैं f !! 12, तो यह कुछ पैटर्न से मेल खाता है:

f = 0 : g 1 : g 2 : g 3 : g 4 : g 5 : g 6 : g 7 : g 8 : g 9 : g 10 : g 11 : g 12 : ...

अब इसकी गणना शुरू होती है

f !! 12 = g 12 = max 12 $ f!!6 + f!!4 + f!!3

यह पुनरावर्ती रूप से f पर एक और मांग करता है, इसलिए हम गणना करते हैं

f !! 6 = g 6 = max 6 $ f !! 3 + f !! 2 + f !! 1
f !! 3 = g 3 = max 3 $ f !! 1 + f !! 1 + f !! 0
f !! 1 = g 1 = max 1 $ f !! 0 + f !! 0 + f !! 0
f !! 0 = 0

अब हम कुछ को पीछे कर सकते हैं

f !! 1 = g 1 = max 1 $ 0 + 0 + 0 = 1

जिसका मतलब है कि कार्यक्रम अब जानता है:

f = 0 : 1 : g 2 : g 3 : g 4 : g 5 : g 6 : g 7 : g 8 : g 9 : g 10 : g 11 : g 12 : ...

जारी रखने के लिए:

f !! 3 = g 3 = max 3 $ 1 + 1 + 0 = 3

जिसका मतलब है कि कार्यक्रम अब जानता है:

f = 0 : 1 : g 2 : 3 : g 4 : g 5 : g 6 : g 7 : g 8 : g 9 : g 10 : g 11 : g 12 : ...

अब हम अपनी गणना जारी रखते हैं f!!6:

f !! 6 = g 6 = max 6 $ 3 + f !! 2 + 1
f !! 2 = g 2 = max 2 $ f !! 1 + f !! 0 + f !! 0 = max 2 $ 1 + 0 + 0 = 2
f !! 6 = g 6 = max 6 $ 3 + 2 + 1 = 6

जिसका मतलब है कि कार्यक्रम अब जानता है:

f = 0 : 1 : 2 : 3 : g 4 : g 5 : 6 : g 7 : g 8 : g 9 : g 10 : g 11 : g 12 : ...

अब हम अपनी गणना जारी रखते हैं f!!12:

f !! 12 = g 12 = max 12 $ 6 + f!!4 + 3
f !! 4 = g 4 = max 4 $ f !! 2 + f !! 1 + f !! 1 = max 4 $ 2 + 1 + 1 = 4
f !! 12 = g 12 = max 12 $ 6 + 4 + 3 = 13

जिसका मतलब है कि कार्यक्रम अब जानता है:

f = 0 : 1 : 2 : 3 : 4 : g 5 : 6 : g 7 : g 8 : g 9 : g 10 : g 11 : 13 : ...

तो गणना काफी आलस्य से की जाती है। कार्यक्रम जानता है कि इसके लिए कुछ मूल्य f !! 8मौजूद है, कि यह बराबर है g 8, लेकिन यह पता नहीं है कि क्या g 8है।


इसके लिए आपको धन्यवाद। आप एक 2 आयामी समाधान स्थान का निर्माण और उपयोग कैसे करेंगे? क्या वह सूचियों की सूची होगी? औरg n m = (something with) f!!a!!b
vikingsteve

1
ज़रूर, आप कर सकते हैं। एक वास्तविक समाधान के लिए, हालांकि, मैं शायद एक ज्ञापन पुस्तकालय का उपयोग करूंगा
रैंपियन

यह दुर्भाग्य से हे (एन ^ 2) है।
क्यूमरिक

8

यह एडवर्ड केमट के उत्कृष्ट उत्तर का एक परिशिष्ट है।

जब मैंने उनके कोड की कोशिश की, की परिभाषाएँ natsऔरindex , सुंदर रहस्यमय लग रहा था तो मुझे लगा कि मैं समझना आसान पाया एक वैकल्पिक संस्करण लिखें।

मैं परिभाषित indexऔर natsके मामले में index'औरnats'

index' t nसीमा पर परिभाषित किया गया है [1..]। (स्मरण करो जो index tसीमा के ऊपर परिभाषित किया गया है [0..]।) यह पेड़ nको बिट्स की एक स्ट्रिंग के रूप में मानकर और रिवर्स में बिट्स के माध्यम से पढ़कर काम करता है । यदि बिट है 1, तो यह दाहिने हाथ की शाखा लेता है। यदि बिट है 0, तो यह बाएं हाथ की शाखा लेता है। जब यह अंतिम बिट (जो होना चाहिए 1) तक पहुंच जाता है तो यह रुक जाता है ।

index' (Tree l m r) 1 = m
index' (Tree l m r) n = case n `divMod` 2 of
                          (n', 0) -> index' l n'
                          (n', 1) -> index' r n'

जिस तरह natsसे परिभाषित किया गया indexहै कि index nats n == nहमेशा सच है, के nats'लिए परिभाषित किया गया है index'

nats' = Tree l 1 r
  where
    l = fmap (\n -> n*2)     nats'
    r = fmap (\n -> n*2 + 1) nats'
    nats' = Tree l 1 r

अब, natsऔर indexबस हैं nats'और index'लेकिन 1 द्वारा स्थानांतरित मूल्यों के साथ:

index t n = index' t (n+1)
nats = fmap (\n -> n-1) nats'

धन्यवाद। मैं एक बहुभिन्नरूपी समारोह को याद कर रहा हूं, और इससे मुझे वास्तव में काम करने में मदद मिली कि सूचकांक और नट्स वास्तव में क्या कर रहे थे।
किट्सटिल

8

जैसा कि एडवर्ड केमट के उत्तर में कहा गया है, चीजों को गति देने के लिए, आपको महंगी गणनाओं को कैश करने की आवश्यकता है और उन्हें जल्दी से एक्सेस करने में सक्षम होना चाहिए।

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

जबकि मुख्य दोष यह है कि आपको एक गैर-मोनोडिक फ़ंक्शन मिलता है, आपको संरचना को अब और अनुक्रमित करने की आवश्यकता नहीं है, और बस सहयोगी कंटेनरों के मानक कार्यान्वयन का उपयोग कर सकते हैं।

ऐसा करने के लिए, आपको पहले किसी भी प्रकार के मोनेद को स्वीकार करने के लिए आपको फिर से लिखना होगा।

fm :: (Integral a, Monad m) => (a -> m a) -> a -> m a
fm _    0 = return 0
fm recf n = do
   recs <- mapM recf $ div n <$> [2, 3, 4]
   return $ max n (sum recs)

अपने परीक्षणों के लिए, आप अभी भी एक फ़ंक्शन को परिभाषित कर सकते हैं जो Data.Function.fix का उपयोग करके कोई संस्मरण नहीं करता है, हालांकि यह थोड़ा अधिक स्पष्ट है:

noMemoF :: (Integral n) => n -> n
noMemoF = runIdentity . fix fm

फिर आप चीजों को गति देने के लिए Data.Map के साथ संयोजन में स्टेट मोनड का उपयोग कर सकते हैं:

import qualified Data.Map.Strict as MS

withMemoStMap :: (Integral n) => n -> n
withMemoStMap n = evalState (fm recF n) MS.empty
   where
      recF i = do
         v <- MS.lookup i <$> get
         case v of
            Just v' -> return v' 
            Nothing -> do
               v' <- fm recF i
               modify $ MS.insert i v'
               return v'

मामूली बदलाव के साथ, आप डेटा के साथ काम करने के लिए कोड को अनुकूलित कर सकते हैं। इसके बजाय:

import qualified Data.HashMap.Strict as HMS

withMemoStHMap :: (Integral n, Hashable n) => n -> n
withMemoStHMap n = evalState (fm recF n) HMS.empty
   where
      recF i = do
         v <- HMS.lookup i <$> get
         case v of
            Just v' -> return v' 
            Nothing -> do
               v' <- fm recF i
               modify $ HMS.insert i v'
               return v'

लगातार डेटा संरचनाओं के बजाय, आप ST मोनर्स के संयोजन में परस्पर डेटा संरचनाओं (जैसे Data.HashTable) को भी आज़मा सकते हैं:

import qualified Data.HashTable.ST.Linear as MHM

withMemoMutMap :: (Integral n, Hashable n) => n -> n
withMemoMutMap n = runST $
   do ht <- MHM.new
      recF ht n
   where
      recF ht i = do
         k <- MHM.lookup ht i
         case k of
            Just k' -> return k'
            Nothing -> do 
               k' <- fm (recF ht) i
               MHM.insert ht i k'
               return k'

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

मानदंड के रूप में मानदंड का उपयोग करते हुए, मैं देख सकता हूं कि Data.HashMap के साथ कार्यान्वयन वास्तव में DataMap और Data.HashTable की तुलना में थोड़ा बेहतर (लगभग 20%) का प्रदर्शन किया, जिसके लिए समय बहुत समान थे।

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


2
जीएचसी अपरिवर्तनीय संरचनाओं के आसपास अनुकूलन का एक बहुत अच्छा काम करता है। C से अंतर्ज्ञान हमेशा बाहर पैन नहीं होता है।
जॉन टायर्री

3

कुछ साल बाद, मैंने इसे देखा और महसूस किया कि रैखिक समय का उपयोग करके zipWithऔर सहायक समारोह में इसे याद करने का एक सरल तरीका है :

dilate :: Int -> [x] -> [x]
dilate n xs = replicate n =<< xs

dilateकाम का गुण है कि dilate n xs !! i == xs !! div i n

इसलिए, मान लें कि हमें f (0) दिया गया है, यह गणना को सरल बनाता है

fs = f0 : zipWith max [1..] (tail $ fs#/2 .+. fs#/3 .+. fs#/4)
  where (.+.) = zipWith (+)
        infixl 6 .+.
        (#/) = flip dilate
        infixl 7 #/

हमारे मूल समस्या वर्णन की तरह बहुत कुछ देख रहे हैं, और एक रैखिक समाधान दे रहे हैं ( sum $ take n fsओ (एन) ले जाएगा)।


2
तो यह एक जेनरेटर (corecursive?), या गतिशील प्रोग्रामिंग, समाधान है। प्रत्येक उत्पन्न मान के अनुसार O (1) समय लेना, जैसे सामान्य फिबोनाची कर रहा है। महान! और EKMETT का समाधान लॉगरिदमिक बिग-फाइबोनैचि की तरह है, बड़ी संख्या में बहुत तेजी से हो रहा है, इन-बीटवेंस पर ज्यादा लंघन कर रहा है। क्या यह सही है?
विल नेस

या हो सकता है कि यह हैमिंग नंबरों के लिए एक के करीब हो, तीन बैक-पॉइंटर्स के साथ अनुक्रम में जो उत्पादन किया जा रहा है, और उनमें से प्रत्येक के लिए अलग-अलग गति इसके साथ आगे बढ़ रही है। वास्तव में सुंदर।
को Ness

2

एडवर्ड केमट के उत्तर का एक और परिशिष्ट: एक आत्म-निहित उदाहरण:

data NatTrie v = NatTrie (NatTrie v) v (NatTrie v)

memo1 arg_to_index index_to_arg f = (\n -> index nats (arg_to_index n))
  where nats = go 0 1
        go i s = NatTrie (go (i+s) s') (f (index_to_arg i)) (go (i+s') s')
          where s' = 2*s
        index (NatTrie l v r) i
          | i <  0    = f (index_to_arg i)
          | i == 0    = v
          | otherwise = case (i-1) `divMod` 2 of
             (i',0) -> index l i'
             (i',1) -> index r i'

memoNat = memo1 id id 

इसका प्रयोग इस प्रकार करें कि एक पूर्णांक arg (जैसे रिट्रेसमेंट) के साथ किसी फ़ंक्शन को याद रखने के लिए:

fib = memoNat f
  where f 0 = 0
        f 1 = 1
        f n = fib (n-1) + fib (n-2)

केवल गैर-नकारात्मक तर्कों के लिए मूल्यों को कैश किया जाएगा।

नकारात्मक तर्कों के लिए मूल्यों को कैश करने के लिए memoInt, निम्नानुसार परिभाषित , उपयोग करें :

memoInt = memo1 arg_to_index index_to_arg
  where arg_to_index n
         | n < 0     = -2*n
         | otherwise =  2*n + 1
        index_to_arg i = case i `divMod` 2 of
           (n,0) -> -n
           (n,1) ->  n

दो पूर्णांक तर्कों के साथ कार्यों के लिए मूल्यों को कैश करने के लिए memoIntInt, निम्नानुसार परिभाषित किया गया है:

memoIntInt f = memoInt (\n -> memoInt (f n))

2

अनुक्रमण के बिना एक समाधान, और एडवर्ड KMETT के आधार पर नहीं।

मैं एक आम माता-पिता के लिए सामान्य उपप्रकारों का कारक हूं ( और के f(n/4)बीच साझा किया जाता है , और ) और के बीच साझा किया जाता है । माता-पिता में एकल चर के रूप में उन्हें बचाकर, उप-गणना की गणना एक बार की जाती है।f(n/2)f(n/4)f(n/6)f(2)f(3)

data Tree a =
  Node {datum :: a, child2 :: Tree a, child3 :: Tree a}

f :: Int -> Int
f n = datum root
  where root = f' n Nothing Nothing


-- Pass in the arg
  -- and this node's lifted children (if any).
f' :: Integral a => a -> Maybe (Tree a) -> Maybe (Tree a)-> a
f' 0 _ _ = leaf
    where leaf = Node 0 leaf leaf
f' n m2 m3 = Node d c2 c3
  where
    d = if n < 12 then n
            else max n (d2 + d3 + d4)
    [n2,n3,n4,n6] = map (n `div`) [2,3,4,6]
    [d2,d3,d4,d6] = map datum [c2,c3,c4,c6]
    c2 = case m2 of    -- Check for a passed-in subtree before recursing.
      Just c2' -> c2'
      Nothing -> f' n2 Nothing (Just c6)
    c3 = case m3 of
      Just c3' -> c3'
      Nothing -> f' n3 (Just c6) Nothing
    c4 = child2 c2
    c6 = f' n6 Nothing Nothing

    main =
      print (f 123801)
      -- Should print 248604.

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

प्रत्येक गणना के बाद मेमो को खारिज कर दिया जाता है। (फिर से, मैं दो स्ट्रिंग मापदंडों के बारे में सोच रहा था।)

मुझे नहीं पता कि यह अन्य उत्तरों की तुलना में अधिक कुशल है या नहीं। प्रत्येक लुकअप तकनीकी रूप से केवल एक या दो चरण है ("अपने बच्चे या अपने बच्चे के बच्चे को देखें"), लेकिन बहुत अधिक मेमोरी उपयोग हो सकता है।

संपादित करें: यह समाधान अभी तक सही नहीं है। बांटना अधूरा है।

संपादित करें: इसे अब ठीक से उप-बच्चों को बांटना चाहिए, लेकिन मुझे एहसास हुआ कि इस समस्या में बहुत सारे साझाकरण हैं: n/2/2/2और n/3/3यह समान हो सकता है। समस्या मेरी रणनीति के लिए एक अच्छी फिट नहीं है।

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