এই ফিবোনাচি-ফাংশনটি কীভাবে স্মরণে রাখা হয়?


114

এই ফিবোনাচি-ফাংশনটি কোন মেকানিজমে মেমোমাইজ করা হয়?

fib = (map fib' [0..] !!)                 
     where fib' 1 = 1                                                        
           fib' 2 = 1                                                        
           fib' n = fib (n-2) + fib (n-1)                    

এবং সম্পর্কিত নোটে, কেন এই সংস্করণটি নয়?

fib n = (map fib' [0..] !! n)                                               
     where fib' 1 = 1                                                        
           fib' 2 = 1                                                        
           fib' n = fib (n-2) + fib (n-1)                    

13
সামান্য unrelatedly, fib 0বিনষ্ট করে না: আপনি সম্ভবত জন্য বেস মামলা চান fib'হতে fib' 0 = 0এবং fib' 1 = 1
হুন

1
মনে রাখবেন যে প্রথম সংস্করণটি আরও সংক্ষিপ্ত করা যেতে পারে: fibs = 1:1:zipWith (+) fibs (tail fibs)এবং fib = (fibs !!)
বাসটিয়ান

উত্তর:


95

হাস্কেলের মূল্যায়ন প্রক্রিয়াটি প্রয়োজন অনুসারে : যখন কোনও মান প্রয়োজন হয় তখন তা গণনা করা হয়, আবার জিজ্ঞাসা করার ক্ষেত্রে প্রস্তুত রাখা হয় kept যদি আমরা কিছু তালিকা সংজ্ঞায়িত করি, xs=[0..]এবং পরে এর 100 তম উপাদানটির জন্য জিজ্ঞাসা করি xs!!99, তালিকার 100 তম স্লটটি "মশালানো" হয়ে যায়, 99এখনই সংখ্যাটি রেখে , পরবর্তী অ্যাক্সেসের জন্য প্রস্তুত।

সেই কৌশলটিই "গ্লো-থ্রু-এ-লিস্ট" শোষণ করছে। ফিবোনাচি সংজ্ঞায় দ্বিগুণভাবে পুনরাবৃত্তি হওয়াতে fib n = fib (n-1) + fib (n-2), ফাংশনটি নিজে থেকেই ডাকা হয়, উপরে থেকে দু'বার ঘাতক বিস্ফোরণ ঘটায়। তবে সেই কৌশলটি দিয়ে আমরা অন্তর্বর্তীকালীন ফলাফলের জন্য একটি তালিকা প্রস্তুত করেছি এবং "তালিকার মধ্য দিয়ে" চলেছি:

fib n = (xs!!(n-1)) + (xs!!(n-2)) where xs = 0:1:map fib [2..]

কৌশলটি হ'ল সেই তালিকাটি তৈরি হওয়ার কারণ এবং সেই তালিকাকে কল করার মধ্যে (আবর্জনা সংগ্রহের পথে) সরিয়ে না দেওয়া fib। এটি অর্জনের সবচেয়ে সহজ উপায়, সেই তালিকাটির নামকরণ"আপনি যদি নামটি দেন তবে তা থাকবে।"


আপনার প্রথম সংস্করণটি একটি মনোরফিক ধ্রুবককে সংজ্ঞায়িত করে এবং দ্বিতীয়টি একটি পলিমারফিক ফাংশন সংজ্ঞায়িত করে। পলিমারফিক ফাংশন বিভিন্ন ধরণের সেবার জন্য একই অভ্যন্তরীণ তালিকাটি ব্যবহার করতে পারে না যার জন্য এটির প্রয়োজন হতে পারে, তাই ভাগ করে নেওয়া হয় না , কোনও স্মৃতিচারণ নয়।

প্রথম সংস্করণ সহ, সংকলক আমাদের সাথে উদার হচ্ছে , ধ্রুবক subexpression ( map fib' [0..]) গ্রহণ করে এটিকে একটি পৃথক অংশীদারিত্বের সত্তা হিসাবে তৈরি করে, তবে এটি করার কোনও বাধ্যবাধকতা নেই। এবং এমন কিছু ঘটনা রয়েছে যেখানে আমরা এটি আমাদের জন্য স্বয়ংক্রিয়ভাবে এটি করতে চাই না

( সম্পাদনা করুন) এই পুনরায় লেখাগুলি বিবেচনা করুন:

fib1 = f                     fib2 n = f n                 fib3 n = f n          
 where                        where                        where                
  f i = xs !! i                f i = xs !! i                f i = xs !! i       
  xs = map fib' [0..]          xs = map fib' [0..]          xs = map fib' [0..] 
  fib' 1 = 1                   fib' 1 = 1                   fib' 1 = 1          
  fib' 2 = 1                   fib' 2 = 1                   fib' 2 = 1          
  fib' i=fib1(i-2)+fib1(i-1)   fib' i=fib2(i-2)+fib2(i-1)   fib' i=f(i-2)+f(i-1)

সুতরাং আসল গল্পটি নেস্টেড স্কোপ সংজ্ঞা সম্পর্কে বলে মনে হচ্ছে। 1 ম সংজ্ঞা সহ কোনও বাহ্যিক সুযোগ নেই, এবং 3 য় বাহ্যিক-স্কোপটিকে কল না করার জন্য সতর্ক fib3, তবে একই স্তরের f

প্রত্যেকটি নতুন আবাহন fib2নতুনভাবে তার নেস্টেড সংজ্ঞা তৈরি করতে কারণ তাদের কোন মনে হয় পারে (তত্ত্ব) ভিন্নভাবে সংজ্ঞায়িত করা নির্ভর করে মূল্যের ওপর n(যে ইশারা জন্য Vitus এবং Tikhon ধন্যবাদ)। প্রথম defintion সঙ্গে কোন ব্যাপার nউপর নির্ভর করে এবং তৃতীয় সহ একটি নির্ভরতা, কিন্তু প্রতিটি পৃথক কল fib3কল মধ্যে fযা একই পর্যায়ের সুযোগ থেকে মাত্র কল সংজ্ঞা, এই নির্দিষ্ট আবাহন অভ্যন্তরীণ সতর্কতা অবলম্বন করা হয় fib3, তাই একই xsপায় এর অনুরোধের জন্য পুনরায় ব্যবহার করা (অর্থাত্ ভাগ করা) fib3

তবে কোনও কিছুই সংস্করণকারীকে এই স্বীকৃতি থেকে বিরত রাখে না যে উপরের কোনও সংস্করণে অভ্যন্তরীণ সংজ্ঞাগুলি বাইরের বাঁধাইয়ের তুলনায় প্রকৃতপক্ষে স্বাধীন , ল্যাম্বডা উত্তোলনn সম্পাদন করার জন্য , সম্পূর্ণ স্মৃতিচারণের ফলে (পলিমারফিক সংজ্ঞা ব্যতীত)। প্রকৃতপক্ষে মোনমোরফিক প্রকারের সাথে ঘোষিত এবং -O2 পতাকার সাথে সংকলিত হয়ে তিনটি সংস্করণে ঠিক একই ঘটনা ঘটে। পলিমারফিক ধরণের ঘোষণার সাথে, স্থানীয় ভাগ করে নেওয়া এবং কোনও অংশীদারি প্রদর্শন করা হয় না।fib3fib2

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

সংক্ষিপ্ত উত্তরটি হল, এটি একটি সংকলক জিনিস। :)


4
কেবলমাত্র কিছু বিশদ ঠিক করার জন্য: দ্বিতীয় সংস্করণটি কোনও ভাগ করে নেবে না কারণ স্থানীয় ফাংশনটি fib'প্রতিটিটির জন্য পুনরায় সংজ্ঞায়িত হয় nএবং এইভাবে fib'ইন- ইন থাকে যা fib 1সূচিত করে যে তালিকাগুলি পৃথক। আপনি যদি মনোমরফিক হতে টাইপটি ঠিক করেন তবে এটি এখনও এই আচরণটি প্রদর্শন করে। fib'fib 2
ভিটাস

1
whereধারাগুলি অনেকগুলি মত letপ্রকাশের মতো ভাগ করে নেওয়া পরিচয় করিয়ে দেয় তবে তারা এ জাতীয় সমস্যাগুলি লুকিয়ে রাখে। এটি আরও কিছুটা স্পষ্ট করে আবার লিখতে
ভিটাস

1
আপনার পুনর্লিখন সম্পর্কে আরও একটি আকর্ষণীয় বিষয়: আপনি যদি এগুলিকে মনোমরফিক টাইপ দেন (অর্থাত্ Int -> Integer), তবে তা ক্ষণস্থায়ী fib2সময়ে সঞ্চালিত হয় fib1এবং fib3উভয়ই রৈখিক সময়ে চালিত হয় তবে fib1স্মৃতিচারণও হয় - আবার কারণ fib3স্থানীয় সংজ্ঞাগুলির জন্য প্রত্যেকটির জন্য নতুন সংজ্ঞা দেওয়া হয়েছে n
ভিটাস

1
@ মিস্টারবি তবে সংকলকটির কাছ থেকে একরকম আশ্বাস পেয়ে ভালো লাগবে; নির্দিষ্ট সত্তার মেমরি রেসিডেন্সির উপর একরকম নিয়ন্ত্রণ। কখনও কখনও আমরা ভাগ করে নিতে চাই, কখনও কখনও আমরা এটি প্রতিরোধ করতে চাই। আমি কল্পনা / আশা করি এটি সম্ভব হওয়া উচিত ...
উইল নেস

1
@ এলিজা ব্র্যান্ড বলতে আমার অর্থ হ'ল কখনও কখনও আমরা ভারী কিছু গণনা করতে চাই যাতে এটি আমাদের কাছে স্মৃতিতে ধরে রাখা হয় না - অর্থাত স্মরণে রাখা বিশাল ব্যয়ের তুলনায় পুনঃব্যবহারের ব্যয় কম। একটি উদাহরণ পাওয়ারশক্তি তৈরি: pwr (x:xs) = pwr xs ++ map (x:) pwr xs ; pwr [] = [[]]আমরা pwr xsস্বাধীনভাবে, দুবার গণনা করতে চাই , সুতরাং এটি উড়ে উড়ে জঞ্জাল হতে পারে যেমন এটি উত্পাদন করা হয় এবং সেবন করা হয়।
নেস

23

আমি পুরোপুরি নিশ্চিত নই, তবে এখানে একটি শিক্ষিত অনুমান:

সংকলকটি ধরে নিয়েছে যে অন্যের fib nচেয়ে আলাদা হতে পারে nএবং তাই প্রতিবারের জন্য তালিকাটি পুনরায় গণনা করা দরকার। whereবিবৃতিতে থাকা বিটগুলি সর্বোপরি নির্ভর করতে পারেn । এই ক্ষেত্রে, সংখ্যার পুরো তালিকাটি মূলত একটি ফাংশন n

সংস্করণ ছাড়াই n একবার তালিকা তৈরি করতে পারে এবং এটিকে কোনও ফাংশনে আবদ্ধ করতে পারে। তালিকাটি উত্তীর্ণের মানের উপর নির্ভর করতে পারে নাn এবং এটি যাচাই করা সহজ। তালিকাটি একটি ধ্রুবক যা এরপরে সূচকযুক্ত হয়। এটি অবশ্যই একটি ধ্রুবক যা অলসভাবে মূল্যায়ন করা হয়, সুতরাং আপনার প্রোগ্রামটি পুরো (অসীম) তালিকা অবিলম্বে পাওয়ার চেষ্টা করবে না not যেহেতু এটি একটি ধ্রুবক, এটি ফাংশন কলগুলিতে ভাগ করা যায়।

এটি মোটেও স্মৃতিচারণ করেছে কারণ পুনরাবৃত্ত কলটি কেবলমাত্র একটি তালিকার একটি মান সন্ধান করতে পারে। যেহেতু fibসংস্করণটি তালিকাটি একবার অলসভাবে তৈরি করে, তাই এটি অনর্থক গণনা না করে উত্তর পাওয়ার জন্য যথেষ্ট পরিমাণে গণনা করে। এখানে, "অলস" অর্থ এই তালিকার প্রতিটি প্রবেশ একটি থাঙ্ক (একটি মূল্যহীন অভিব্যক্তি)। যখন আপনি না thunk মূল্যায়ন, এটি একটি মান হয়ে, তাই এটি পরের বার কোন গণনার বার বার ফিরে আসে অ্যাক্সেস। যেহেতু তালিকাটি কলগুলির মধ্যে ভাগ করা যায়, পূর্ববর্তী সমস্ত এন্ট্রিগুলি আপনার পরবর্তী সময়টির প্রয়োজন অনুসারে ইতিমধ্যে গণনা করা হয়।

এটি GHC এর অলস শব্দার্থবিদ্যার উপর ভিত্তি করে গতিশীল প্রোগ্রামিংয়ের মূলত একটি চতুর এবং কম ভাড়ার ফর্ম। আমি মনে করি মানকটি কেবলমাত্র এটি অ-কঠোর হতে হবে তা সুনির্দিষ্ট করে , সুতরাং কোনও কমপ্লায়েন্ট কম্পাইলার এই কোডটি স্মরণে না রাখার জন্য সম্ভাব্যভাবে সংকলন করতে পারে । তবে, অনুশীলনে, প্রতিটি যুক্তিসঙ্গত সংকলক অলস হতে চলেছে।

দ্বিতীয় কেসটি কেন কাজ করে সে সম্পর্কে আরও তথ্যের জন্য, পুনরাবৃত্তভাবে সংজ্ঞায়িত তালিকা (জিপবিথের শর্তে ফাইবস) বোঝা পড়ুন


আপনার অর্থ সম্ভবত " fib' nআলাদা আলাদা হতে পারে n"?
নেস

আমি মনে করি আমি খুব পরিষ্কার ছিলাম না: আমার অর্থ হ'ল fibঅন্তর্ভুক্ত fib'সমস্ত কিছু আলাদা আলাদা হতে পারে n। আমি মনে করি যে আসল উদাহরণটি কিছুটা বিভ্রান্তিকর কারণ fib'এটি নিজের উপরও নির্ভর করে nযা অন্যটি ছায়া দেয় n
টিখন জেলভিস

20

প্রথমত, -O2gc -7.4.2 সহ, সংকলিত , নন-মেমোসাইজড সংস্করণটি খুব খারাপ নয়, ফাংশনে প্রতিটি শীর্ষ স্তরের কলের জন্য ফিবোনাচি সংখ্যাগুলির অভ্যন্তরীণ তালিকাটি এখনও স্মরণীয় রয়েছে। তবে এটি নয় এবং যুক্তিসঙ্গতভাবে বিভিন্ন শীর্ষ-স্তরের কলগুলিতে স্মরণ করা যায় না। তবে, অন্য সংস্করণের জন্য, তালিকাটি কলগুলিতে ভাগ করা আছে।

এটি মনোমরফিজম সীমাবদ্ধতার কারণে।

প্রথমটি একটি সাধারণ প্যাটার্ন বাঁধাই দ্বারা আবদ্ধ (কেবল নাম, কোনও যুক্তি নয়), সুতরাং মনোমরফিজম সীমাবদ্ধতার দ্বারা এটি অবশ্যই মনোমরফিক টাইপ পান। অনুমিত টাইপ হয়

fib :: (Num n) => Int -> n

এবং এই জাতীয় সীমাবদ্ধতা ডিফল্ট হয় (কোনও পূর্বনির্ধারিত ঘোষণার অনুপস্থিতিতে অন্যথায় বলায়) Integer, প্রকারটি ঠিক করে

fib :: Int -> Integer

এইভাবে [Integer]স্মৃতিচারণের জন্য কেবল একটি তালিকা রয়েছে (টাইপের )।

দ্বিতীয়টি একটি ফাংশন আর্গুমেন্টের সাথে সংজ্ঞায়িত করা হয়, সুতরাং এটি বহুবর্ষীয় থেকে যায় এবং যদি অভ্যন্তরীণ তালিকাগুলি কলগুলিতে মেমোয়েজ করা হয় তবে প্রতিটি তালিকার জন্য প্রতিটি তালিকা মেমোয়েজ করতে হবে Num। এটি ব্যবহারিক নয়।

মনোমরফিজম সীমাবদ্ধতা অক্ষম করা বা অভিন্ন ধরণের স্বাক্ষর সহ উভয় সংস্করণ সংকলন করুন এবং উভয়ই ঠিক একই আচরণ দেখান। (এটি পুরানো সংকলক সংস্করণগুলির জন্য সত্য ছিল না, আমি জানি না যে সংস্করণটি প্রথমে এটি করেছে did)


প্রতিটি ধরণের জন্য একটি তালিকা স্মরণে রাখা কেন অবৈধ? নীতিগতভাবে, জিএইচসি রানটাইম চলাকালীন প্রতিটি নুম টাইপের জন্য আংশিক গণিত তালিকা রাখতে একটি অভিধান তৈরি করতে পারে (যেমন শ্রেণি-সীমাবদ্ধ ফাংশনগুলি কল করার জন্য)?
মিস্টারবি

1
@ মিস্টারবি নীতিগতভাবে, এটি পারে, তবে যদি প্রোগ্রামটি fib 1000000বিভিন্ন ধরণের কল করে, যা একটি টন মেমরি খায়। এটি এড়াতে, একজনের একটি হিউরিস্টিকের প্রয়োজন হবে যা খুব বড় হয়ে গেলে ক্যাশে থেকে ফেলে দেওয়ার জন্য তালিকাবদ্ধ করে। এবং এই জাতীয় স্মৃতিচারণ কৌশলটি সম্ভবত অন্যান্য ফাংশন বা মানগুলির ক্ষেত্রেও প্রযোজ্য, সম্ভবত, তাই সংকলকটি সম্ভাব্য অনেক ধরণের স্মৃতিচারণ করার জন্য সম্ভাব্য সংখ্যক জিনিস নিয়ে কাজ করতে হবে। আমি মনে করি যে যুক্তিসঙ্গতভাবে ভাল উত্তরাধিকার সূত্রে বহুবচনীয় স্মারক (আংশিক) প্রয়োগ করা সম্ভব হবে, তবে আমি সন্দেহ করি এটি সার্থক হবে।
ড্যানিয়েল ফিশার

5

হাস্কেলের জন্য আপনার স্মৃতিচারণের প্রয়োজন নেই need কেবল ইমপ্রায়টিভ প্রোগ্রামিং ভাষার ক্ষেত্রে সেই ফাংশন প্রয়োজন। তবে হাস্কেল হ'ল ফাংশনাল ল্যাং এবং ...

সুতরাং, এটি খুব দ্রুত ফিবোনাচি অ্যালগরিদমের উদাহরণ:

fib = zipWith (+) (0:(1:fib)) (1:fib)

জিপউইথ স্ট্যান্ডার্ড প্রিলিওড থেকে ফাংশন:

zipWith :: (a->b->c) -> [a]->[b]->[c]
zipWith op (n1:val1) (n2:val2) = (n1 + n2) : (zipWith op val1 val2)
zipWith _ _ _ = []

টেস্ট:

print $ take 100 fib

আউটপুট:

[1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986,102334155,165580141,267914296,433494437,701408733,1134903170,1836311903,2971215073,4807526976,7778742049,12586269025,20365011074,32951280099,53316291173,86267571272,139583862445,225851433717,365435296162,591286729879,956722026041,1548008755920,2504730781961,4052739537881,6557470319842,10610209857723,17167680177565,27777890035288,44945570212853,72723460248141,117669030460994,190392490709135,308061521170129,498454011879264,806515533049393,1304969544928657,2111485077978050,3416454622906707,5527939700884757,8944394323791464,14472334024676221,23416728348467685,37889062373143906,61305790721611591,99194853094755497,160500643816367088,259695496911122585,420196140727489673,679891637638612258,1100087778366101931,1779979416004714189,2880067194370816120,4660046610375530309,7540113804746346429,12200160415121876738,19740274219868223167,31940434634990099905,51680708854858323072,83621143489848422977,135301852344706746049,218922995834555169026,354224848179261915075,573147844013817084101]

সময় কেটে গেছে: 0.00018s


এই সমাধান দুর্দান্ত!
ল্যারি
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.