লেজ পুনরাবৃত্তি কি?


1692

লিস্প শিখতে শুরু করার পরে, আমি লেজ-পুনরাবৃত্তির শব্দটি জুড়ে এসেছি । এটা ঠিক এর অর্থ কি?


153
কৌতূহলের জন্য: ভাষা এবং ভাষা উভয়ই দীর্ঘকাল ধরে রয়েছে। প্রাচীন ইংরেজী ব্যবহারের সময়; যদিও মধ্যম ইংরেজী বিকাশ। সংযোগ হিসাবে তারা অর্থের বিনিময়যোগ্য, তবে মানক আমেরিকান ইংরাজীতে এখনও টিকেনি।
ফিলিপ বার্টুজি

14
হতে পারে দেরি হয়ে গেছে, তবে এটি লেজ পুনরাবৃত্তির সম্পর্কে একটি খুব ভাল নিবন্ধ: প্রোগ্রামারিনটারভিউ
ইন্ডেক্স.এফপি

5
একটি পুচ্ছ-পুনরাবৃত্তি ফাংশন সনাক্তকরণের একটি দুর্দান্ত সুবিধা হ'ল এটিকে পুনরাবৃত্ত আকারে রূপান্তর করা যায় এবং এভাবে পদ্ধতি-স্ট্যাক-ওভারহেড থেকে অ্যালগরিদমকে পুনরুদ্ধার করা যায়। @ কাইল ক্রোনিন এবং নীচে কয়েকজনের কাছ থেকে প্রতিক্রিয়া দেখতে পছন্দ করতে পারেন
কেঘাটকের ২

@ আইসুদীপের এই লিঙ্কটি আমি খুঁজে পেয়েছি সেরা, সবচেয়ে বিশদ বিবরণ - lua.org/pil/6.3.html
জেফ ফিশার

1
আমাকে কেউ বলতে পারেন, কি মার্জ সাজ্ট এবং দ্রুত সাজানোর মাধ্যমে লেজ পুনরাবৃত্তি (টিআরও) ব্যবহার করা হয়?
মাজুরজিরথন

উত্তর:


1717

একটি সাধারণ ফাংশন বিবেচনা করুন যা প্রথম এন প্রাকৃতিক সংখ্যা যুক্ত করে। (যেমন sum(5) = 1 + 2 + 3 + 4 + 5 = 15)

এখানে একটি সাধারণ জাভাস্ক্রিপ্ট বাস্তবায়ন যা পুনরাবৃত্তি ব্যবহার করে:

function recsum(x) {
    if (x === 1) {
        return x;
    } else {
        return x + recsum(x - 1);
    }
}

যদি আপনি ফোন করেন তবে recsum(5)এটি জাভাস্ক্রিপ্ট দোভাষীর মূল্যায়ন করবে:

recsum(5)
5 + recsum(4)
5 + (4 + recsum(3))
5 + (4 + (3 + recsum(2)))
5 + (4 + (3 + (2 + recsum(1))))
5 + (4 + (3 + (2 + 1)))
15

জাভা স্ক্রিপ্ট ইন্টারপ্রেটারটি যোগফল নির্ধারণের কাজটি শুরু করার আগে প্রতিটি পুনরাবৃত্ত কলটি কীভাবে শেষ করতে হবে তা নোট করুন।

এখানে একই ফাংশনের একটি লেজ-পুনরাবৃত্ত সংস্করণ:

function tailrecsum(x, running_total = 0) {
    if (x === 0) {
        return running_total;
    } else {
        return tailrecsum(x - 1, running_total + x);
    }
}

এখানে কল করা ইভেন্টগুলির ক্রম এখানে রয়েছে tailrecsum(5), (যেটি কার্যকরভাবে কার্যকর হবে tailrecsum(5, 0), কারণ ডিফল্ট দ্বিতীয় যুক্তির কারণে)।

tailrecsum(5, 0)
tailrecsum(4, 5)
tailrecsum(3, 9)
tailrecsum(2, 12)
tailrecsum(1, 14)
tailrecsum(0, 15)
15

পুচ্ছ-পুনরাবৃত্তির ক্ষেত্রে পুনরাবৃত্তির কলটির প্রতিটি মূল্যায়নের সাথে running_totalআপডেট করা হয়।

দ্রষ্টব্য: মূল উত্তরটি পাইথনের উদাহরণ ব্যবহার করেছে। এগুলি জাভাস্ক্রিপ্টে পরিবর্তিত করা হয়েছে, যেহেতু পাইথন অনুবাদকরা লেজ কল অপ্টিমাইজেশন সমর্থন করে না । যাইহোক, টেল কল অপ্টিমাইজেশন ECMAScript 2015 টিপির অংশ হলেও বেশিরভাগ জাভাস্ক্রিপ্ট দোভাষী এটি সমর্থন করে না


32
আমি কি বলতে পারি যে লেজ পুনরাবৃত্তির মাধ্যমে চূড়ান্ত উত্তরটি একাই পদ্ধতিটির সর্বশেষ দোহাই দিয়ে গণনা করা হয়? যদি এটি পুচ্ছ পুনরাবৃত্তি না হয় তবে আপনার উত্তরটি গণনা করার জন্য সমস্ত পদ্ধতির জন্য সমস্ত ফলাফলের প্রয়োজন।
ক্রিসাপোটেক

2
এখানে একটি সংযোজন রয়েছে যা লুয়ার কয়েকটি উদাহরণ উপস্থাপন করে: lua.org/pil/6.3.html পাশাপাশি এটি ব্যবহার করার জন্যও দরকারী হতে পারে! :)
হুদীপ

2
কেউ দয়া করে ক্রিসাপোটেকের প্রশ্নের সমাধান করতে পারেন? আমি বিভ্রান্ত হয়ে পড়েছি কীভাবে tail recursionএমন ভাষায় কীভাবে অর্জন করা যায় যা টেল কলগুলি অপ্টিমাইজ করে না।
কেভিন মেরেডিথ

3
@ কেভিনমারেডিথ "লেজ পুনরাবৃত্তি" অর্থ হ'ল কোনও ফাংশনের সর্বশেষ বিবৃতিটি একই ফাংশনে পুনরাবৃত্তি কল। আপনি ঠিক বলেছেন যে এমন ভাষায় এটি করার কোনও অর্থ নেই যা সেই পুনরাবৃত্তিটি অপ্টিমাইজ করে না। তবুও, এই উত্তরটি ধারণাটি (প্রায়) সঠিকভাবে প্রদর্শন করে। যদি "অন্য:" বাদ দেওয়া হয় তবে এটি আরও পরিষ্কারভাবে একটি লেজ কল হত। আচরণটি পরিবর্তন করবে না, তবে লেজ কলটিকে একটি স্বাধীন বিবৃতি হিসাবে রাখবে। আমি এটি সম্পাদনা হিসাবে জমা দেব।
টুলমেকারস্টেভ

2
সুতরাং পাইথনে কোনও সুবিধা নেই কারণ টেলরেসসাম ফাংশনে প্রতিটি কল সহ একটি নতুন স্ট্যাক ফ্রেম তৈরি হয় - তাই না?
কাজী ইরফান

707

ইন ঐতিহ্যগত পুনরাবৃত্তির , সাধারণত মডেল যে আপনি আপনার recursive কল প্রথম কার্য সম্পাদন, এবং তারপর আপনি recursive কল ফেরত মান গ্রহণ করা এবং ফলাফল গণনা করা হয়। এই পদ্ধতিতে, আপনি প্রতিটি পুনরাবৃত্তি কল থেকে ফিরে না আসা পর্যন্ত আপনি আপনার গণনার ফলাফল পাবেন না।

ইন লেজ পুনরাবৃত্তির , আপনি আপনার গণনার প্রথম কার্য সম্পাদন, এবং তারপর আপনি recursive কল চালানো, পরবর্তী রিকার্সিভ ধাপে আপনার বর্তমান পদক্ষেপ ফলাফল ক্ষণস্থায়ী। সর্বশেষ বিবৃতি আকারে এই ফলাফল (return (recursive-function params))মূলত, কোনও প্রদত্ত পুনরাবৃত্ত পদক্ষেপের রিটার্ন মান পরবর্তী রিকার্সিভ কলের রিটার্ন মান হিসাবে সমান

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


17
"আমি বেশ নিশ্চিত যে লিস্প এটি করে" - স্কিমটি করে তবে সাধারণ লিপ সবসময় হয় না।
হারুন

2
@ ড্যানিয়েল "মূলত, কোনও প্রদত্ত পুনরাবৃত্ত পদক্ষেপের রিটার্ন মান পরবর্তী রিকার্সিভ কলের রিটার্ন মানের সমান" "- লরিন হচস্টিন পোস্ট করা কোড স্নিপেটের পক্ষে এই যুক্তি সত্য বলে আমি দেখতে ব্যর্থ হই। আপনি দয়া করে বিস্তারিত বলতে পারেন?
গীক

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

3
স্কালা করে তবে আপনার এটি প্রয়োগের জন্য @ টেইল্রিক নির্দিষ্ট করা দরকার।
সাইলেন্টডিরজ

2
"এই পদ্ধতিতে, আপনি প্রতিটি পুনরাবৃত্তি কল থেকে ফিরে না আসা পর্যন্ত আপনি আপনার গণনার ফলাফল পাবেন না।" - হয়তো আমি এই ভুল বুঝে ভাবেন, কিন্তু এই অলস ভাষার জন্য বিশেষ করে সত্য যেখানে নয় ঐতিহ্যগত পুনরাবৃত্তির আসলে সব recursions কলিং ছাড়া একটি ফলাফল পেতে একমাত্র উপায় (যেমন ভাঁজ && সঙ্গে Bools অসীম তালিকা বেশি)।
hasufell

205

একটি গুরুত্বপূর্ণ বিষয় হ'ল পুচ্ছ পুনরাবৃত্তি মূলত লুপিংয়ের সমতুল্য। এটি কেবল সংকলক অপ্টিমাইজেশনের বিষয় নয়, ভাব প্রকাশের একটি মৌলিক বিষয়। এটি উভয় উপায়ে যায়: আপনি ফর্মের যে কোনও লুপ নিতে পারেন

while(E) { S }; return Q

যেখানে Eএবং Qএক্সপ্রেশন এবং Sবিবৃতিগুলির ক্রম, এবং এটি একটি লেজ পুনরাবৃত্ত ফাংশনে পরিণত করে

f() = if E then { S; return f() } else { return Q }

অবশ্যই, E, S, এবং Qকিছু ভেরিয়েবল উপর কিছু মজার মান গনা সংজ্ঞায়িত করতে হবে। উদাহরণস্বরূপ, লুপিং ফাংশন

sum(n) {
  int i = 1, k = 0;
  while( i <= n ) {
    k += i;
    ++i;
  }
  return k;
}

পুচ্ছ-পুনরাবৃত্তি ফাংশন (গুলি) এর সমতুল্য

sum_aux(n,i,k) {
  if( i <= n ) {
    return sum_aux(n,i+1,k+i);
  } else {
    return k;
  }
}

sum(n) {
  return sum_aux(n,1,0);
}

(কম পরামিতি সহ কোনও ফাংশন সহ লেজ-পুনরাবৃত্ত ফাংশনের এই "মোড়ানো" একটি সাধারণ ক্রিয়ামূলক প্রতিমা))


@ লরিনহচস্টিনের উত্তরে আমি বুঝতে পেরেছিলাম, তাঁর ব্যাখ্যাটির ভিত্তিতে, পুনরাবৃত্ত অংশটি "রিটার্ন" অনুসরণ করে যখন লেজ পুনরাবৃত্তি হবে, তবে আপনার মধ্যে লেজ পুনরাবৃত্তি হয় না। আপনি কি নিশ্চিত যে আপনার উদাহরণটি যথাযথভাবে পুচ্ছ পুনরাবৃত্তি হিসাবে বিবেচিত?
কোডি বাগস্টাইন

1
@ ইমরে লেজ পুনরুক্তিযোগ্য অংশটি Sum_aux এর মধ্যে "রিটার্ন Sum_aux" বিবৃতি।
ক্রিস কনওয়ে

1
@ লমরে: ক্রিসের কোডটি মূলত সমতুল্য। সীমাবদ্ধ পরীক্ষার if / এর ক্রম এবং শৈলীর ক্রম ... যদি x == 0 বনাম যদি (i <= n) ... স্থগিত করার মতো কিছু নয়। মুল বক্তব্যটি হ'ল প্রতিটি পুনরাবৃত্তি তার ফলাফলটিকে পরের দিকে পাস করে।
টেলর

else { return k; }পরিবর্তন করা যাবেreturn k;
c0der

144

লুয়ায় প্রোগ্রামিং বইয়ের এই সংক্ষিপ্তসারটি দেখায় যে কীভাবে একটি উপযুক্ত লেজ পুনরাবৃত্তি করা যায় (লুয়ায়, তবে লিস্পের জন্যও প্রয়োগ করা উচিত) এবং কেন এটি আরও ভাল।

একটি লেজ কল [লেজ পুনরাবৃত্তি] কল হিসাবে পোশাক পরা এক ধরণের গোটো। একটি ফেইল কলটি ঘটে যখন কোনও ফাংশন অন্যটিকে তার শেষ ক্রিয়া হিসাবে কল করে, তাই এর আর কিছু করার নেই। উদাহরণস্বরূপ, নিম্নলিখিত কোডে, কলটি gএকটি লেজ কল:

function f (x)
  return g(x)
end

fকল করার পরে g, এর আর কিছু করার নেই। এই জাতীয় পরিস্থিতিতে, কলটি ফাংশন শেষ হয়ে গেলে প্রোগ্রামটি কলিং ফাংশনে ফিরে আসার প্রয়োজন হয় না। সুতরাং, লেজ কল করার পরে, প্রোগ্রামটির স্ট্যাকের মধ্যে কলিং ফাংশন সম্পর্কে কোনও তথ্য রাখার দরকার নেই। ...

যেহেতু একটি সঠিক টেল কল কোনও স্ট্যাক স্পেস ব্যবহার করে না, "নেস্টেড" টেল কলগুলির কোনও সীমা নেই যা কোনও প্রোগ্রাম করতে পারে। উদাহরণস্বরূপ, আমরা নিম্নলিখিত ক্রিয়াকে যে কোনও সংখ্যার সাথে যুক্তি হিসাবে কল করতে পারি; এটি স্ট্যাকটি কখনই উপচে পড়বে না:

function foo (n)
  if n > 0 then return foo(n - 1) end
end

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

এই গেমটি একটি সাধারণ স্টেট মেশিন, যেখানে বর্তমান ঘরটি রাজ্য। আমরা প্রতিটি ঘরের জন্য একটি ফাংশন সহ এ জাতীয় গোলকধাঁটি বাস্তবায়ন করতে পারি। আমরা একটি ঘর থেকে অন্য ঘরে যাওয়ার জন্য টেল কল ব্যবহার করি। চারটি কক্ষ সহ একটি ছোট গোলকধাঁধা এর মতো দেখতে পাওয়া যেতে পারে:

function room1 ()
  local move = io.read()
  if move == "south" then return room3()
  elseif move == "east" then return room2()
  else print("invalid move")
       return room1()   -- stay in the same room
  end
end

function room2 ()
  local move = io.read()
  if move == "south" then return room4()
  elseif move == "west" then return room1()
  else print("invalid move")
       return room2()
  end
end

function room3 ()
  local move = io.read()
  if move == "north" then return room1()
  elseif move == "east" then return room4()
  else print("invalid move")
       return room3()
  end
end

function room4 ()
  print("congratulations!")
end

সুতরাং আপনি যখন দেখেন যে আপনি যখন পুনরাবৃত্তি কল করেন তখন:

function x(n)
  if n==0 then return 0
  n= n-2
  return x(n) + 1
end

এটি টেল রিকার্সিভ নয় কারণ পুনরাবৃত্তি কল করার পরে আপনার সেই কাজটিতে এখনও কিছু করতে হবে (1 যোগ করুন)। আপনি যদি খুব বেশি সংখ্যক ইনপুট করেন তবে এটি সম্ভবত স্ট্যাকের ওভারফ্লো হতে পারে।


9
এটি একটি দুর্দান্ত উত্তর কারণ এটি স্ট্যাক আকারের উপর লেজ কলগুলির প্রভাবগুলি ব্যাখ্যা করে।
অ্যান্ড্রু সোয়ান

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

1
আমার প্রিয় উত্তরটি স্ট্যাকের আকারের সাথে জড়িত থাকার কারণেও।
njk2015

80

নিয়মিত পুনরাবৃত্তি ব্যবহার করে, প্রতিটি পুনরাবৃত্তি কল কল স্ট্যাকের উপরে আরেকটি প্রবেশ ঠেলে দেয়। যখন পুনরাবৃত্তিটি সম্পন্ন হয়, তখন অ্যাপটিকে সমস্ত প্রবেশদ্বার পুরোপুরি নীচে নেমে পপ করতে হয়।

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

মূলত লেজ পুনরাবৃত্তি পুনরাবৃত্তিতে অনুকূলিত করতে সক্ষম।


1
"একটি বৃহত রিকার্সিভ ক্যোয়ারী আসলে একটি স্ট্যাকের ওভারফ্লো করতে পারে।" 1 ম অনুচ্ছেদে থাকা উচিত, ২ য় (লেজ পুনরাবৃত্তি) একটিতে নয়? লেজ পুনরাবৃত্তির বড় সুবিধা হ'ল এটি (প্রাক্তন: স্কিম) এমনভাবে অনুকূলিত করা যেতে পারে যে স্ট্যাকের "কল" জমে না যায়, তাই বেশিরভাগই স্ট্যাকের ওভারফ্লোগুলি এড়ানো হবে!
অলিভিয়ার ডুলাক

69

জারগন ফাইলটির লেজ পুনরাবৃত্তির সংজ্ঞা সম্পর্কে বলা আছে:

লেজ পুনরাবৃত্তি / এন। /

আপনি যদি ইতিমধ্যে অসুস্থ না হন তবে লেজ পুনরাবৃত্তি দেখুন।


68

এটি শব্দ দিয়ে ব্যাখ্যা করার পরিবর্তে, এখানে একটি উদাহরণ। এটি ফ্যাক্টরিয়াল ফাংশনের একটি স্কিম সংস্করণ:

(define (factorial x)
  (if (= x 0) 1
      (* x (factorial (- x 1)))))

এখানে ফ্যাক্টরিয়াল এর একটি সংস্করণ যা পুচ্ছ-পুনরাবৃত্তিযোগ্য:

(define factorial
  (letrec ((fact (lambda (x accum)
                   (if (= x 0) accum
                       (fact (- x 1) (* accum x))))))
    (lambda (x)
      (fact x 1))))

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


4
লেজ-পুনরাবৃত্তির সর্বাধিক গুরুত্বপূর্ণ দিকটি উল্লেখ করার জন্য +1 যে এগুলি পুনরাবৃত্ত আকারে রূপান্তরিত হতে পারে এবং এর ফলে এটিকে ও (1) স্মৃতি জটিলতায় রূপে রূপান্তরিত করতে পারে।
কেঘাটক 25'17

1
@ কেঘাটক ঠিক নয়; উত্তরটি সঠিকভাবে "ধ্রুব স্ট্যাক স্পেস" সম্পর্কে বলে, সাধারণভাবে স্মৃতি নয়। নিটপিকিং করা হবে না, কেবল কোনও ভুল বুঝাবুঝি তা নিশ্চিত করার জন্য। যেমন টেল-রিকার্সিভ লেস্ট-লেজ-মিউটেশন list-reverseপ্রক্রিয়া ধ্রুব স্ট্যাক স্পেসে চলবে তবে স্তূপে একটি ডেটা স্ট্রাকচার তৈরি এবং বৃদ্ধি করবে। একটি ট্রি ট্রভারসাল একটি অতিরিক্ত যুক্তিতে সিমুলেটেড স্ট্যাক ব্যবহার করতে পারে। ইত্যাদি
নেস

45

লেজ পুনরাবৃত্তি পুনরাবৃত্তিমূলক আলগোরিদিম মধ্যে সর্বশেষ যুক্তি নির্দেশে পুনরাবৃত্ত কল কল হতে বোঝায়।

সাধারণত পুনরাবৃত্তির ক্ষেত্রে আপনার একটি বেস-কেস থাকে যা হ'ল পুনরাবৃত্তি কলগুলি থামায় এবং কল স্ট্যাকটি পপিং শুরু করে। ক্লাসিক উদাহরণটি ব্যবহার করতে, যদিও লিস্পের চেয়ে সি-ইশ বেশি, তথাকথিত ফাংশন লেজ পুনরাবৃত্তি চিত্রিত করে। পুনরাবৃত্ত কলটি বেস-কেস শর্তটি যাচাই করার পরে ঘটে ।

factorial(x, fac=1) {
  if (x == 1)
     return fac;
   else
     return factorial(x-1, x*fac);
}

ফ্যাকটোরিয়ালের প্রাথমিক কলটি factorial(n)যেখানে fac=1(ডিফল্ট মান) এবং এন হবে সেই সংখ্যাটি যার জন্য ফ্যাক্টরিয়াল গণনা করতে হবে।


আমি আপনার ব্যাখ্যাটি বোঝার পক্ষে সবচেয়ে সহজ খুঁজে পেয়েছি তবে এটি যদি কিছু করে যায় তবে লেজ পুনরাবৃত্তি কেবলমাত্র একটি বিবৃতি বেসের ক্ষেত্রে ফাংশনগুলির জন্য দরকারী। এই postimg.cc/5Yg3Cdjn এর মতো একটি পদ্ধতি বিবেচনা করুন । দ্রষ্টব্য: বাইরেরটি elseএমন পদক্ষেপ যা আপনি "বেস কেস" কল করতে পারেন তবে বেশ কয়েকটি লাইন জুড়ে রয়েছে। আমি কি আপনাকে ভুল বোঝাবুঝি করছি নাকি আমার ধারণাটি সঠিক? লেজ পুনরাবৃত্তি শুধুমাত্র একটি liners জন্য ভাল?
আমি উত্তরগুলি চাই

2
@ আইওয়ানটআনসওয়ার্স - না, ফাংশনটির দেহটি নির্বিচারে বড় হতে পারে। লেজ কলের জন্য যা যা করা দরকার তা হ'ল এটি যে শাখায় রয়েছে সেগুলি ফাংশনটিকে এটি সবচেয়ে শেষ হিসাবে ডাকে এবং ফাংশনটি কল করার ফলাফলটি দেয়। factorialউদাহরণটি কেবল ক্লাসিক সরল উদাহরণ, এগুলিই।
টিজে ক্রাউডার

28

এর অর্থ হ'ল স্ট্যাকের উপর নির্দেশিকা নির্দেশককে চাপ দেওয়ার পরিবর্তে আপনি কেবল একটি পুনরাবৃত্ত ফাংশনের শীর্ষে ঝাঁপিয়ে পড়তে পারেন এবং সম্পাদন চালিয়ে যেতে পারেন। এটি স্ট্যাককে উপচে না ফেলে অনির্দিষ্টকালের জন্য পুনরাবৃত্তি করতে ফাংশনগুলিকে মঞ্জুরি দেয়।

আমি এই বিষয়টিতে একটি ব্লগ পোস্ট লিখেছি , যাতে স্ট্যাক ফ্রেমগুলি দেখতে কেমন তার গ্রাফিকাল উদাহরণ রয়েছে।


21

দুটি ফাংশনের তুলনা করে এখানে একটি দ্রুত কোড স্নিপেট। প্রদত্ত সংখ্যার ফ্যাকটোরিয়াল সন্ধানের জন্য প্রথমটি হ'ল traditionalতিহ্যবাহী পুনরাবৃত্তি। দ্বিতীয়টি লেজ পুনরাবৃত্তি ব্যবহার করে।

বুঝতে খুব সহজ এবং স্বজ্ঞাত।

যদি একটি পুনরাবৃত্ত ফাংশনটি একটি লেজ পুনরাবৃত্ত হয় তা বলার একটি সহজ উপায় হ'ল যদি এটি বেসের ক্ষেত্রে একটি কংক্রিট মান দেয়। এর অর্থ এটি 1 বা সত্য বা এর মতো কিছুই ফেরায় না। এটি সম্ভবত পদ্ধতির পরামিতিগুলির কোনওটির কিছু বৈকল্পিক ফিরে আসবে।

অন্য উপায়টি হ'ল তা জানাচ্ছি যে যদি পুনরাবৃত্তি কলটি কোনও সংযোজন, পাটিগণিত, পরিবর্তন, ইত্যাদি থেকে মুক্ত থাকে ... এর অর্থ এটি খাঁটি পুনরাবৃত্তি কল ছাড়া কিছুই নয়।

public static int factorial(int mynumber) {
    if (mynumber == 1) {
        return 1;
    } else {            
        return mynumber * factorial(--mynumber);
    }
}

public static int tail_factorial(int mynumber, int sofar) {
    if (mynumber == 1) {
        return sofar;
    } else {
        return tail_factorial(--mynumber, sofar * mynumber);
    }
}

3
0! 1। সুতরাং "মাইনিম্বার == 1" "মাইনিম্বার == 0" হওয়া উচিত।
পোলারটো

19

আমার বোঝার সর্বোত্তম tail call recursionউপায়টি পুনরাবৃত্তির একটি বিশেষ ক্ষেত্রে যেখানে শেষ কল (বা লেজ কল) নিজেই ফাংশন।

পাইথনে প্রদত্ত উদাহরণগুলির তুলনা করা:

def recsum(x):
 if x == 1:
  return x
 else:
  return x + recsum(x - 1)

^ recursion

def tailrecsum(x, running_total=0):
  if x == 0:
    return running_total
  else:
    return tailrecsum(x - 1, running_total + x)

R টেল রিসার্শন

আপনি যেমন সাধারণ পুনরাবৃত্ত সংস্করণে দেখতে পাচ্ছেন, কোড ব্লকের মধ্যে চূড়ান্ত কল x + recsum(x - 1)। সুতরাং recsumপদ্ধতি কল করার পরে , অন্য অপারেশন যা হয় x + ..

যাইহোক, লেজ পুনরাবৃত্ত সংস্করণে কোড ব্লকের চূড়ান্ত কল (বা লেজ কল) tailrecsum(x - 1, running_total + x)যার অর্থ শেষ কলটি পদ্ধতিতে নিজেই করা হয়েছিল এবং তার পরে কোনও অপারেশন নেই।

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

সম্পাদনা

বিশেষ দ্রষ্টব্য। মনে রাখবেন যে উপরের উদাহরণটি পাইথনে লেখা আছে যার রানটাইম টিসিও সমর্থন করে না। পয়েন্টটি ব্যাখ্যা করার জন্য এটি কেবল একটি উদাহরণ। টিসিও স্কিম, হাস্কেল প্রভৃতি ভাষায় সমর্থিত


12

জাভাতে, এখানে ফিবোনাচি ফাংশনটির একটি সম্ভাব্য লেজ পুনরাবৃত্তিমূলক বাস্তবায়ন রয়েছে:

public int tailRecursive(final int n) {
    if (n <= 2)
        return 1;
    return tailRecursiveAux(n, 1, 1);
}

private int tailRecursiveAux(int n, int iter, int acc) {
    if (iter == n)
        return acc;
    return tailRecursiveAux(n, ++iter, acc + iter);
}

স্ট্যান্ডার্ড পুনরাবৃত্তিমূলক বাস্তবায়নের সাথে এর বিপরীতে:

public int recursive(final int n) {
    if (n <= 2)
        return 1;
    return recursive(n - 1) + recursive(n - 2);
}

1
এটি আমার জন্য ভুল ফলাফলগুলি ফিরিয়ে দিচ্ছে, ইনপুট 8 এর জন্য আমি 36 পেয়েছি, এটি 21 হতে হবে I আমি কি কিছু মিস করছি? আমি জাভা ব্যবহার করছি এবং অনুলিপি এটি আটকানো।
আলবার্তো জ্যাকাগানি 21

1
এটি [1, n] এ আমার জন্য এসএমএম (i) প্রদান করে। ফিব্বোনাকির সাথে কিছুই করার নেই। একটি ফিব্বোর জন্য, আপনার একটি পরীক্ষা প্রয়োজন যা কখন বাদ iterদেয় । acciter < (n-1)
এসকোলইন

10

আমি লিস্প প্রোগ্রামার নই, তবে আমি মনে করি এটি সাহায্য করবে।

মূলত এটি এমন একটি প্রোগ্রামিংয়ের স্টাইল যা পুনরাবৃত্ত কলটি আপনার শেষ কাজ।


10

এখানে একটি সাধারণ লিস্পের উদাহরণ যা লেজ-পুনরাবৃত্তি ব্যবহার করে ফ্যাক্টরিয়ালগুলি করে। স্ট্যাক-কম প্রকৃতির কারণে, কেউ অত্যন্ত বড় কল্পিত গণনা সম্পাদন করতে পারে ...

(defun ! (n &optional (product 1))
    (if (zerop n) product
        (! (1- n) (* product n))))

এবং তারপরে মজা করার জন্য আপনি চেষ্টা করতে পারেন (format nil "~R" (! 25))


9

সংক্ষেপে, একটি পুচ্ছ পুনরাবৃত্তি ফাংশন শেষ বিবৃতি হিসাবে পুনরাবৃত্তি কল যাতে এটি পুনরাবৃত্তি কল জন্য অপেক্ষা করতে হবে না।

সুতরাং এটি একটি লেজ পুনরাবৃত্তি অর্থাৎ এন (এক্স - 1, পি * এক্স) হল ফাংশনটির শেষ বিবৃতি যেখানে সংকলকটি চতুর তা নির্ধারণ করতে চতুর যে এটি একটি লুপ (ফ্যাকটোরিয়াল) এ অনুকূলিত হতে পারে। দ্বিতীয় প্যারামিটার পি মধ্যবর্তী পণ্য মান বহন করে।

function N(x, p) {
   return x == 1 ? p : N(x - 1, p * x);
}

এটি উপরের ফ্যাক্টরিয়াল ফাংশনটি লেখার নন-লেজ-পুনরাবৃত্তিমূলক উপায় (যদিও কিছু সি ++ সংকলক যাইহোক এটি এটিকে অনুকূল করতে সক্ষম হবেন)।

function N(x) {
   return x == 1 ? 1 : x * N(x - 1);
}

তবে এটি নয়:

function F(x) {
  if (x == 1) return 0;
  if (x == 2) return 1;
  return F(x - 1) + F(x - 2);
}

আমি " টেল রিকার্সন বোঝার - ভিজ্যুয়াল স্টুডিও সি ++ - অ্যাসেম্বলি ভিউ " শীর্ষক একটি দীর্ঘ পোস্ট লিখেছিলাম

এখানে চিত্র বর্ণনা লিখুন


1
আপনার ফাংশন এন টেল-রিকভারসিভ কীভাবে?
ফ্যাবিয়ান পাইজেক

এন (এক্স -১) হ'ল ফাংশনটির সর্বশেষ বিবৃতি যেখানে
সংকলকটি এটির

আমার উদ্বেগটি হ'ল আপনার ফাংশন এন হ'ল এই বিষয়টির স্বীকৃত উত্তর থেকে ফাংশনটি রিক্সাম (এটি বাদে এটি একটি যোগফল এবং একটি পণ্য নয়), এবং সেই রিক্সামটি লেজ-পুনরাবৃত্তি হিসাবে বলা হয়?
ফ্যাবিয়ান পাইজেকে

8

এখানে tailrecsumপূর্বে উল্লিখিত ফাংশনের পার্ল 5 সংস্করণ রয়েছে ।

sub tail_rec_sum($;$){
  my( $x,$running_total ) = (@_,0);

  return $running_total unless $x;

  @_ = ($x-1,$running_total+$x);
  goto &tail_rec_sum; # throw away current stack frame
}

8

এটি পুচ্ছ পুনরাবৃত্তি সম্পর্কে কম্পিউটার প্রোগ্রামগুলির কাঠামো এবং ব্যাখ্যা এর একটি উদ্ধৃতি ।

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

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


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

8

পুনরাবৃত্তি ফাংশন একটি ফাংশন যা নিজেই কল করে

এটি প্রোগ্রামারদের ন্যূনতম পরিমাণ কোড ব্যবহার করে দক্ষ প্রোগ্রাম লেখার অনুমতি দেয় ।

ক্ষয়ক্ষতিটি হ'ল তারা যথাযথভাবে লেখা না থাকলে অসীম লুপ এবং অন্যান্য অপ্রত্যাশিত ফলাফলের কারণ হতে পারে

আমি সাধারণ পুনরাবৃত্তি ফাংশন এবং টেল রিকার্সিভ ফাংশন উভয়ই ব্যাখ্যা করব

একটি সাধারণ পুনরাবৃত্তির কাজ লিখতে যাতে

  1. বিবেচনা করার প্রথম বিষয়টি হ'ল লুপটি কখন লুপ থেকে বেরিয়ে আসার সিদ্ধান্ত নেবে
  2. দ্বিতীয়টি হ'ল আমরা আমাদের নিজস্ব ফাংশন হলে কী প্রক্রিয়া করব

প্রদত্ত উদাহরণ থেকে:

public static int fact(int n){
  if(n <=1)
     return 1;
  else 
     return n * fact(n-1);
}

উপরের উদাহরণ থেকে

if(n <=1)
     return 1;

লুপ থেকে প্রস্থান করার সময় সিদ্ধান্ত নেওয়ার কারণ

else 
     return n * fact(n-1);

আসল প্রক্রিয়াজাতকরণ করা

সহজ বোঝার জন্য আমাকে একের পর এক কাজটি বিরতি দিন।

আমি দৌড়ে গেলে অভ্যন্তরীণভাবে কী ঘটে তা দেখা যাক fact(4)

  1. প্রতিস্থাপন n = 4
public static int fact(4){
  if(4 <=1)
     return 1;
  else 
     return 4 * fact(4-1);
}

Ifলুপ ব্যর্থ হয় তাই এটি elseলুপ যায় তাই এটি ফিরে4 * fact(3)

  1. স্ট্যাক মেমরি, আমরা আছে 4 * fact(3)

    প্রতিস্থাপন n = 3

public static int fact(3){
  if(3 <=1)
     return 1;
  else 
     return 3 * fact(3-1);
}

Ifলুপ ব্যর্থ তাই এটি elseলুপ যায়

সুতরাং এটি ফিরে আসে 3 * fact(2)

মনে রাখবেন আমরা called `` 4 * ফ্যাক্ট (3) `` বলেছি `

জন্য আউটপুট fact(3) = 3 * fact(2)

এখনও পর্যন্ত স্ট্যাক আছে 4 * fact(3) = 4 * 3 * fact(2)

  1. স্ট্যাক মেমরি, আমরা আছে 4 * 3 * fact(2)

    প্রতিস্থাপন n = 2

public static int fact(2){
  if(2 <=1)
     return 1;
  else 
     return 2 * fact(2-1);
}

Ifলুপ ব্যর্থ তাই এটি elseলুপ যায়

সুতরাং এটি ফিরে আসে 2 * fact(1)

মনে আছে আমরা ডেকেছি 4 * 3 * fact(2)

জন্য আউটপুট fact(2) = 2 * fact(1)

এখনও পর্যন্ত স্ট্যাক আছে 4 * 3 * fact(2) = 4 * 3 * 2 * fact(1)

  1. স্ট্যাক মেমরি, আমরা আছে 4 * 3 * 2 * fact(1)

    প্রতিস্থাপন n = 1

public static int fact(1){
  if(1 <=1)
     return 1;
  else 
     return 1 * fact(1-1);
}

If লুপ সত্য

সুতরাং এটি ফিরে আসে 1

মনে আছে আমরা ডেকেছি 4 * 3 * 2 * fact(1)

জন্য আউটপুট fact(1) = 1

এখনও পর্যন্ত স্ট্যাক আছে 4 * 3 * 2 * fact(1) = 4 * 3 * 2 * 1

শেষ অবধি, ফলাফলের ফলাফল (4) = 4 * 3 * 2 * 1 = 24

এখানে চিত্র বর্ণনা লিখুন

লেঙ্গুড় Recursion হবে

public static int fact(x, running_total=1) {
    if (x==1) {
        return running_total;
    } else {
        return fact(x-1, running_total*x);
    }
}

  1. প্রতিস্থাপন n = 4
public static int fact(4, running_total=1) {
    if (x==1) {
        return running_total;
    } else {
        return fact(4-1, running_total*4);
    }
}

Ifলুপ ব্যর্থ হয় তাই এটি elseলুপ যায় তাই এটি ফিরেfact(3, 4)

  1. স্ট্যাক মেমরি, আমরা আছে fact(3, 4)

    প্রতিস্থাপন n = 3

public static int fact(3, running_total=4) {
    if (x==1) {
        return running_total;
    } else {
        return fact(3-1, 4*3);
    }
}

Ifলুপ ব্যর্থ তাই এটি elseলুপ যায়

সুতরাং এটি ফিরে আসে fact(2, 12)

  1. স্ট্যাক মেমরি, আমরা আছে fact(2, 12)

    প্রতিস্থাপন n = 2

public static int fact(2, running_total=12) {
    if (x==1) {
        return running_total;
    } else {
        return fact(2-1, 12*2);
    }
}

Ifলুপ ব্যর্থ তাই এটি elseলুপ যায়

সুতরাং এটি ফিরে আসে fact(1, 24)

  1. স্ট্যাক মেমরি, আমরা আছে fact(1, 24)

    প্রতিস্থাপন n = 1

public static int fact(1, running_total=24) {
    if (x==1) {
        return running_total;
    } else {
        return fact(1-1, 24*1);
    }
}

If লুপ সত্য

সুতরাং এটি ফিরে আসে running_total

জন্য আউটপুট running_total = 24

অবশেষে, ফলাফলের ফলাফল (4,1) = 24

এখানে চিত্র বর্ণনা লিখুন


7

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

যখন আপনি কিছু প্রক্রিয়া অতিরিক্ত ফ্রেম ব্যবহার করতে পারেন তবে স্ট্যাকটি যদি অসম্পূর্ণভাবে বৃদ্ধি না ঘটে তবে বিবেচনা করা হচ্ছে সাদৃশ্যটি ভেঙে যায়।


1
এটি বিভক্ত ব্যক্তিত্ব ব্যাধি ব্যাখ্যার অধীনে ভাঙ্গা না । :) মন একটি সমাজ ; সোসাইটি হিসাবে একটি মন। :)
নেস

কি দারুন! এখন এটির বিষয়ে ভাববার আর একটি উপায় রয়েছে
সুতানু ডালুই

7

একটি পুচ্ছ পুনরাবৃত্তি একটি পুনরাবৃত্তি ফাংশন যেখানে ফাংশনটি ফাংশনের শেষে ("লেজ") নিজেকে কল করে যেখানে পুনরাবৃত্তির কল ফিরে আসার পরে কোনও গণনা করা হয় না। অনেক সংকলক একটি লেজ পুনরাবৃত্ত বা একটি পুনরাবৃত্তি কল একটি পুনরাবৃত্তি কল পরিবর্তন অনুকূলিতকরণ।

একটি সংখ্যার ফ্যাকটোরিয়াল গণনা করার সমস্যাটি বিবেচনা করুন।

একটি সহজ পদ্ধতি হবে:

  factorial(n):

    if n==0 then 1

    else n*factorial(n-1)

ধরুন আপনি ফ্যাকটোরিয়াল (4) কল করেছেন। পুনরাবৃত্তি গাছ হবে:

       factorial(4)
       /        \
      4      factorial(3)
     /             \
    3          factorial(2)
   /                  \
  2                factorial(1)
 /                       \
1                       factorial(0)
                            \
                             1    

উপরের ক্ষেত্রে সর্বাধিক পুনরাবৃত্তির গভীরতা হ'ল ও (এন)।

তবে, নিম্নলিখিত উদাহরণটি বিবেচনা করুন:

factAux(m,n):
if n==0  then m;
else     factAux(m*n,n-1);

factTail(n):
   return factAux(1,n);

ফ্যাক্ট টেল (4) এর জন্য পুনরাবৃত্তি গাছ হ'ল:

factTail(4)
   |
factAux(1,4)
   |
factAux(4,3)
   |
factAux(12,2)
   |
factAux(24,1)
   |
factAux(24,0)
   |
  24

এখানেও সর্বাধিক পুনরাবৃত্তির গভীরতা ও (এন) তবে কলগুলির মধ্যে কোনওটিই স্ট্যাকটিতে কোনও অতিরিক্ত পরিবর্তনশীল যুক্ত করে না। সুতরাং সংকলক একটি স্ট্যাক দিয়ে দূরে করতে পারেন।


7

টেল পুনরাবৃত্তি সাধারণ পুনরাবৃত্তির তুলনায় বেশ দ্রুত। এটি দ্রুত কারণ পূর্বপুরুষদের কলের আউটপুট ট্র্যাক রাখতে স্ট্যাকে লেখা হবে না। তবে স্বাভাবিক পুনরাবৃত্তিতে সমস্ত পূর্বপুরুষ কল করে রাখার জন্য স্ট্যাকের লিখিত আউটপুট কল করে।


6

একটি লেজ রিকার্সিভ ফাংশন একটি recursive ফাংশন যেখানে ফিরে আসার আগে এটি শেষ অপারেশনটি recursive ফাংশন কল করে make অর্থাত, পুনরাবৃত্ত ফাংশন কলটির রিটার্ন মানটি সঙ্গে সঙ্গে ফিরে আসে। উদাহরণস্বরূপ, আপনার কোডটি দেখতে এমন হবে:

def recursiveFunction(some_params):
    # some code here
    return recursiveFunction(some_args)
    # no code after the return statement

কম্পাইলার এবং দোভাষী যে বাস্তবায়ন লেজ কল অপ্টিমাইজেশান বা লেজ কল বর্জন স্ট্যাক উপচে প্রতিরোধ রিকার্সিভ কোড অপটিমাইজ করতে পারেন। আপনার সংকলক বা দোভাষী যদি টেল কল অপ্টিমাইজেশন প্রয়োগ করে না (যেমন সিপিথন ইন্টারপ্রেটার) আপনার কোডটি এভাবে লেখার কোনও অতিরিক্ত সুবিধা নেই।

উদাহরণস্বরূপ, এটি পাইথনের একটি স্ট্যান্ডার্ড পুনরাবৃত্ত ফ্যাক্টরিয়াল ফাংশন:

def factorial(number):
    if number == 1:
        # BASE CASE
        return 1
    else:
        # RECURSIVE CASE
        # Note that `number *` happens *after* the recursive call.
        # This means that this is *not* tail call recursion.
        return number * factorial(number - 1)

এবং এটি ফ্যাক্টরিয়াল ফাংশনের একটি লেজ কল পুনরাবৃত্তি সংস্করণ:

def factorial(number, accumulator=1):
    if number == 0:
        # BASE CASE
        return accumulator
    else:
        # RECURSIVE CASE
        # There's no code after the recursive call.
        # This is tail call recursion:
        return factorial(number - 1, number * accumulator)
print(factorial(5))

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

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

তবে টেল কল অপ্টিমাইজেশনের সুবিধা হ'ল এটি স্ট্যাক ওভারফ্লো ত্রুটিগুলি রোধ করে। (আমি লক্ষ করব যে আপনি পুনরাবৃত্তির পরিবর্তে পুনরাবৃত্ত অ্যালগরিদম ব্যবহার করে এই একই সুবিধা পেতে পারেন))

স্ট্যাক ওভারফ্লোগুলি ঘটে যখন কল স্ট্যাকের অনেক বেশি ফ্রেম অবজেক্ট থাকে pushed একটি ফ্রেম অবজেক্টটি যখন কোনও ফাংশন বলা হয় তখন কল স্ট্যাকের দিকে ঠেলে দেওয়া হয় এবং ফাংশন ফিরে আসার সাথে কল স্ট্যাকটি পপআপ করে। ফ্রেম অবজেক্টগুলিতে স্থানীয় ভেরিয়েবল এবং ফাংশনটি ফিরে আসলে কোডের কোন রেখায় ফিরে আসার মতো তথ্য থাকে।

যদি আপনার পুনরাবৃত্তি ফাংশন ফিরে না পেয়ে অনেক বেশি পুনরাবৃত্ত কল করে, কল স্ট্যাকটি এর ফ্রেম অবজেক্টের সীমা ছাড়িয়ে যেতে পারে। (সংখ্যাটি প্ল্যাটফর্মের দ্বারা পরিবর্তিত হয়; পাইথনে এটি ডিফল্টরূপে 1000 ফ্রেম অবজেক্ট হয়)) এটি স্ট্যাকের ওভারফ্লো ত্রুটির কারণ ঘটায় । (আরে, এই ওয়েবসাইটটির নামটি এখান থেকেই এসেছে!)

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

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


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

এর অর্থ কি এই যে কেউ যদি ওয়েবসাইটটি অপ্টিমাইজ করে এবং পুনরাবৃত্ত কল টেল-রিকার্সিভ রেন্ডার করে তবে আমাদের আর স্ট্যাকওভারফ্লো সাইট থাকবে না ?! এটা কি সাংঘাতিক.
নাদজিব মামি

5

টেল-কল পুনরাবৃত্তি এবং নন-টেল-কল পুনরাবৃত্তির মধ্যে কিছু মূল পার্থক্য বুঝতে আমরা এই কৌশলগুলির .NET বাস্তবায়ন অন্বেষণ করতে পারি।

এখানে সি #, এফ #, এবং সি ++ \ সিএলআই-এর কয়েকটি উদাহরণ সহ একটি নিবন্ধ রয়েছে: সি #, এফ #, এবং সি ++ \ সিএলআইতে টেল পুনরাবৃত্তিতে অ্যাডভেঞ্চারস

সি # টিলে-কল পুনরাবৃত্তির জন্য অনুকূল করে না যেখানে এফ # করে।

নীতির পার্থক্যগুলি লুপ বনাম ল্যাম্বদা ক্যালকুলাসের সাথে জড়িত। সি # লুপদা ক্যালকুলাসের নীতি থেকে তৈরি করা হয়েছে, তবে ল # লুপদা ক্যালকুলাসের নীতি থেকে তৈরি করা হয়েছে। ল্যাম্বদা ক্যালকুলাসের নীতিগুলি সম্পর্কে একটি খুব ভাল (এবং বিনামূল্যে) বইয়ের জন্য, অ্যাবেলসন, সুসমান এবং সুসমানের কম্পিউটার প্রোগ্রামগুলির স্ট্রাকচার এবং ব্যাখ্যার বিবরণ দেখুন

এফ # এ লেজ কল সংক্রান্ত, একটি খুব ভাল পরিচায়ক নিবন্ধটি জন্য, দেখুন এফ # টি লেঙ্গুড় কল বিস্তারিত ভূমিকা । অবশেষে, এখানে একটি নিবন্ধ রয়েছে যা অ-লেজ পুনরাবৃত্তি এবং লেজ-কল পুনরাবৃত্তি (এফ # তে) এর মধ্যে পার্থক্যকে অন্তর্ভুক্ত করে: টেল-পুনরাবৃত্তি বনাম এফ শার্পে অ-লেজ পুনরাবৃত্তি

আপনি যদি সি # এবং এফ # এর মধ্যে টেল-কল পুনরাবৃত্তির কিছু ডিজাইন পার্থক্য সম্পর্কে পড়তে চান তবে দেখুন সি # এবং এফ # তে টেল-কল অপকোড তৈরি করা

আপনি যদি শর্তাদি সি # সংকলকটিকে টেল-কল অপ্টিমাইজেশান সম্পাদন করা থেকে বিরত রাখেন তা জানতে আগ্রহী হন, এই নিবন্ধটি দেখুন: জেআইটি সিএলআর টেল-কল শর্তাদি


4

দুটি মূল ধরণের পুনরাবৃত্তি রয়েছে: মাথা পুনরাবৃত্তি এবং লেজ পুনরাবৃত্তি।

ইন মাথা পুনরাবৃত্তির , একটি ফাংশন তার recursive কল করে তোলে এবং তারপর আরো কিছু গণনার সঞ্চালিত, হয়তো recursive কল ফল ব্যবহার করে, উদাহরণস্বরূপ।

একটি লেজ পুনরাবৃত্তির ক্রিয়ায়, সমস্ত গণনাগুলি প্রথমে ঘটে এবং পুনরাবৃত্ত কলটি ঘটে যা ঘটে শেষ thing

এই দুর্দান্ত দুর্দান্ত পোস্টটি থেকে নেওয়া হয়েছে । এটি পড়া বিবেচনা করুন।


4

পুনরাবৃত্তি মানে একটি ফাংশন নিজেকে কল করা। উদাহরণ স্বরূপ:

(define (un-ended name)
  (un-ended 'me)
  (print "How can I get here?"))

টেল-পুনরুক্তি অর্থ পুনরাবৃত্তি যা ফাংশনটি সমাপ্ত করে:

(define (un-ended name)
  (print "hello")
  (un-ended 'me))

দেখুন, শেষ কাজ শেষ না করা ফাংশন (পদ্ধতি, স্কিম জারগনে) নিজেকে কল করা। আরেকটি (আরও দরকারী) উদাহরণ হ'ল:

(define (map lst op)
  (define (helper done left)
    (if (nil? left)
        done
        (helper (cons (op (car left))
                      done)
                (cdr left))))
  (reverse (helper '() lst)))

সাহায্যকারী পদ্ধতিতে, বামটি শূন্য না হলে সর্বশেষ কাজটি করে নিজেকে ডেকে আনা (আফটার কনস এবং সিডিআর কিছু)। এটি মূলত আপনি কীভাবে একটি তালিকা মানচিত্র করেন।

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


3

এই প্রশ্নের অনেক দুর্দান্ত উত্তর রয়েছে ... তবে আমি "লেজ পুনরাবৃত্তি" বা কমপক্ষে "যথাযথ পুচ্ছ পুনরাবৃত্তি" কীভাবে সংজ্ঞায়িত করতে পারি তার বিকল্প গ্রহণের সাথে সাহায্য করতে পারি না। যথা: একটি প্রোগ্রামে একটি বিশেষ অভিব্যক্তি সম্পত্তি হিসাবে এটি তাকানো উচিত? অথবা কোনও এটিকে কোনও প্রোগ্রামিং ভাষার প্রয়োগের সম্পত্তি হিসাবে দেখা উচিত ?

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

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

কাগজটি বেশ কয়েকটি কারণে সতর্কতার সাথে অধ্যয়নযোগ্য:

  • এটি কোনও প্রোগ্রামের লেজ এক্সপ্রেশন এবং লেজ কলগুলির একটি প্ররোচিত সংজ্ঞা দেয় । (এই জাতীয় সংজ্ঞা, এবং কেন এই কলগুলি গুরুত্বপূর্ণ, এখানে দেওয়া অন্যান্য উত্তরগুলির বেশিরভাগ বিষয় মনে হয়।)

    এই সংজ্ঞাটি এখানে দেওয়া হয়েছে, কেবলমাত্র পাঠ্যের স্বাদ সরবরাহ করার জন্য:

    সংজ্ঞা 1 লেজ এক্সপ্রেশন কোর স্কিম লেখা একটি প্রোগ্রাম এর সংজ্ঞায়িত করা হয় inductively যেমন অনুসরণ করে।

    1. ল্যাম্বডা এক্সপ্রেশনটির দেহ একটি লেজ প্রকাশ expression
    2. যদি (if E0 E1 E2)একটি লেজ এক্সপ্রেশন হয়, তবে উভয় E1এবং E2লেজ এক্সপ্রেশন হয়।
    3. আর কিছুই কিছুই লেজের ভাব নয়।

    সংজ্ঞা 2 একটি লেজ কল একটি লেজ এক্সপ্রেশন যা একটি পদ্ধতি কল।

(একটি লেজ পুনরাবৃত্তি কল, বা কাগজ হিসাবে বলা হয়েছে, "স্ব-লেজু কল" একটি লেজ কল একটি বিশেষ ঘটনা যেখানে প্রক্রিয়াটি নিজেই আহবান করা হয়))

  • এটা তোলে কোর স্কিম, যেখানে প্রতিটি মেশিনের একই পর্যবেক্ষণযোগ্য আচরণ করল মূল্যায়নের জন্য ছয় ভিন্ন "মেশিন" এর জন্য আনুষ্ঠানিক সংজ্ঞা প্রদান করে ব্যতীত জন্য asymptotic স্থান জটিলতা বর্গ প্রতিটি হয়।

    উদাহরণস্বরূপ, যথাক্রমে মেশিনগুলির জন্য সংজ্ঞা দেওয়ার পরে, 1. স্ট্যাক-ভিত্তিক মেমরি পরিচালনা, ২. আবর্জনা সংগ্রহ কিন্তু কোনও লেজ কল নয়, ৩. আবর্জনা সংগ্রহ এবং লেজ কলগুলি, কাগজটি আরও উন্নত স্টোরেজ ম্যানেজমেন্ট কৌশলগুলি সহ এগিয়ে চলেছে, যেমন ৪. "লেজ পুনরাবৃত্তি" প্রকাশ করুন, যেখানে একটি পুচ্ছ কলগুলিতে সর্বশেষ উপ-এক্সপ্রেশন যুক্তির মূল্যায়ন জুড়ে পরিবেশ সংরক্ষণের দরকার নেই, ৫. একটি ক্লোজারের পরিবেশটিকে কেবলমাত্র বন্ধের মুক্ত ভেরিয়েবলের মধ্যে হ্রাস করে , এবং App . আপেল এবং শাও দ্বারা সংজ্ঞায়িত তথাকথিত "নিরাপদ-স্থান" শব্দার্থক ।

  • মেশিনগুলি প্রকৃতপক্ষে ছয়টি স্বতন্ত্র স্থান জটিলতার শ্রেণীর অন্তর্গত তা প্রমাণ করার জন্য, কাগজটি তুলনামূলকভাবে প্রতিটি জোড় মেশিনের জন্য, এমন প্রোগ্রামগুলির সুনির্দিষ্ট উদাহরণ সরবরাহ করে যা একটি মেশিনে অ্যাসিম্পটোটিক স্পেস ব্লোআপকে প্রকাশ করবে তবে অন্যটি নয়।


(আমার উত্তরটি এখনই পড়ছি, আমি নিশ্চিত নই যে আমি আসলে ক্লিঞ্জার পেপারের গুরুত্বপূর্ণ বিষয়গুলি ক্যাপচার করতে পেরেছি কিনা । তবে হায়, আমি এখনই এই উত্তরটি বিকাশে আরও বেশি সময় দিতে পারি না।)


1

ইতিমধ্যে অনেক লোক এখানে পুনরাবৃত্তি ব্যাখ্যা করেছেন। আমি রিকার্ডো টেরেলের রচনা "নেট ইন কনকুরান্সি, সমকালীন এবং সমান্তরাল প্রোগ্রামিংয়ের আধুনিক ধরণ" বইটি থেকে পুনরাবৃত্তির কিছু সুবিধাগুলি সম্পর্কে কয়েকটি চিন্তাভাবনা উদ্ধৃত করতে চাই:

"কার্যকরী পুনরাবৃত্তি এফপিতে পুনরাবৃত্তি করার প্রাকৃতিক উপায় কারণ এটি রাষ্ট্রের পরিবর্তনকে এড়িয়ে চলে avo প্রতিটি পুনরাবৃত্তির সময়, আপডেট করার পরিবর্তে একটি নতুন মান লুপ কনস্ট্রাক্টরে পাস করা হয় (রূপান্তরিত)। এছাড়াও, একটি পুনরাবৃত্ত ফাংশন রচনা করা যেতে পারে, যা আপনার প্রোগ্রামকে আরও মডুলার করে তোলে, পাশাপাশি সমান্তরাল ব্যবহারের সুযোগগুলি প্রবর্তন করে ""

পুচ্ছ পুনরাবৃত্তি সম্পর্কে একই বইয়ের কয়েকটি আকর্ষণীয় নোট এখানে দেওয়া হয়েছে:

টেইল-কল পুনরাবৃত্তি এমন একটি কৌশল যা নিয়মিত পুনরাবৃত্তি ফাংশনটিকে একটি অনুকূলিত সংস্করণে রূপান্তর করে যা কোনও ঝুঁকি এবং পার্শ্ব প্রতিক্রিয়া ছাড়াই বড় ইনপুটগুলি পরিচালনা করতে পারে।

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

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