ලිප්ස් ඉගෙන ගැනීමට පටන් ගන්නා අතරම, මට වලිගය-පුනරාවර්තන යන යෙදුම හමු විය . එය හරියටම අදහස් කරන්නේ කුමක්ද?
ලිප්ස් ඉගෙන ගැනීමට පටන් ගන්නා අතරම, මට වලිගය-පුනරාවර්තන යන යෙදුම හමු විය . එය හරියටම අදහස් කරන්නේ කුමක්ද?
Answers:
පළමු N ස්වාභාවික සංඛ්යා එකතු කරන සරල ශ්රිතයක් සලකා බලන්න. (උදා 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)
පෙරනිමි දෙවන තර්කය නිසා එය effectively ලදායී වනු ඇත ).
tailrecsum(5, 0)
tailrecsum(4, 5)
tailrecsum(3, 9)
tailrecsum(2, 12)
tailrecsum(1, 14)
tailrecsum(0, 15)
15
පුනරාවර්තන ඇමතුමෙහි සෑම ඇගයීමක් සමඟම, වලිගය-පුනරාවර්තන අවස්ථාවෙහිදී, running_total
යාවත්කාලීන වේ.
සටහන: මුල් පිළිතුර පයිතන් වෙතින් උදාහරණ භාවිතා කරයි. පයිතන් පරිවර්තකයන් වලිග ඇමතුම් ප්රශස්තිකරණයට සහය නොදක්වන බැවින් මේවා ජාවාස්ක්රිප්ට් ලෙස වෙනස් කර ඇත . කෙසේ වෙතත්, වලිග ඇමතුම් ප්රශස්තිකරණය ECMAScript 2015 පිරිවිතරයේ කොටසක් වන අතර , බොහෝ ජාවාස්ක්රිප්ට් පරිවර්තකයන් එයට සහාය නොදක්වයි .
tail recursion
වලිග ඇමතුම් ප්රශස්තිකරණය නොකරන භාෂාවකින් එය සාක්ෂාත් කරගන්නේ කෙසේදැයි මම ව්යාකූල වී සිටිමි .
සාම්ප්රදායික පුනරාවර්තනයේ දී , සාමාන්ය ආකෘතිය වන්නේ ඔබ පළමුව ඔබේ පුනරාවර්තන ඇමතුම් සිදු කරන අතර පසුව ඔබ පුනරාවර්තන ඇමතුමේ ප්රතිලාභ අගය ගෙන ප්රති .ලය ගණනය කිරීමයි. මේ ආකාරයට, ඔබ සෑම පුනරාවර්තන ඇමතුමකින්ම නැවත පැමිණෙන තෙක් ඔබේ ගණනය කිරීමේ ප්රති result ලය නොලැබේ.
දී වලිගය සහානුයාත වලට , ප්රථමයෙන් ඔබේ ගණනය කිරීම් සිදු වන අතර එහිදී ඔබ හට ඉදිරි ආවර්තනික පියවර ඔබේ වත්මන් පියවර ප්රතිඵල පසුකර යන, ආවර්තනික ඇමතුමක් ක්රියාත්මක කිරීම. මෙහි ප්රති results ලය වනුයේ අවසාන ප්රකාශය ස්වරූපයෙන් වීමයි (return (recursive-function params))
. මූලික වශයෙන්, ඕනෑම පුනරාවර්තන පියවරක ප්රතිලාභ අගය ඊළඟ පුනරාවර්තන ඇමතුමේ ප්රතිලාභ වටිනාකමට සමාන වේ.
මෙහි ප්රතිවිපාකය නම්, ඔබ ඔබේ ඊළඟ පුනරාවර්තන පියවර සිදු කිරීමට සූදානම් වූ පසු, ඔබට වත්මන් සිරස් රාමුව තවදුරටත් අවශ්ය නොවේ. මෙය යම් ප්රශස්තිකරණයකට ඉඩ දෙයි. ඇත්ත වශයෙන්ම, නිසි ලෙස ලියා ඇති සම්පාදකයෙකු සමඟ, ඔබට කිසි විටෙකත් වලිග පුනරාවර්තන ඇමතුමක් සහිත තොග පිටාර ගැලීමේ ස්නයිකර් නොතිබිය යුතුය . ඊළඟ පුනරාවර්තන පියවර සඳහා වත්මන් සිරස් රාමුව නැවත භාවිතා කරන්න. මට හොඳටම විශ්වාසයි ලිස්ප් මෙය කරන බව.
වැදගත් කරුණක් නම් වලිග පුනරාවර්තනය අත්යවශ්යයෙන්ම ලූපයට සමාන වීමයි. එය හුදෙක් සම්පාදක ප්රශස්තිකරණය පිළිබඳ කාරණයක් නොව, ප්රකාශන හැකියාව පිළිබඳ මූලික කරුණකි. මෙය දෙයාකාරයෙන්ම යයි: ඔබට පෝරමයේ ඕනෑම පුඩුවක් ගත හැකිය
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);
}
(අඩු පරාමිතීන් සහිත ශ්රිතයක් සහිත වලිග පුනරාවර්තන ශ්රිතයේ මෙම “එතීම” පොදු ක්රියාකාරී මෝඩය.)
else { return k; }
වෙනස් කළ හැකියreturn k;
ක්රමලේඛන ලුආ නම් පොතේ මෙම උපුටා ගැනීම මඟින් නිසි වලිග පුනරාවර්තනයක් සිදු කරන්නේ කෙසේද යන්න පෙන්වයි (ලුආ හි, නමුත් ලිස්ප් සඳහාද එය අදාළ විය යුතුය) සහ එය වඩා හොඳ ඇයි.
ඒ වලිගය ඇමතුමක් [වලිගය සහානුයාත] ඇමතුමක් ලෙස සැරසී Goto කාරුණික වේ. ශ්රිතයක් තවත් ක්රියාවක් එහි අවසාන ක්රියාව ලෙස හැඳින්වූ විට වලිග ඇමතුමක් සිදු වේ, එබැවින් එයට වෙනත් කිසිවක් කළ නොහැක. උදාහරණයක් ලෙස, පහත දැක්වෙන කේතයේ, ඇමතුම
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 එකතු කරන්න) ඇත. ඔබ ඉතා ඉහළ සංඛ්යාවක් ආදානය කළහොත් එය බොහෝ විට තොග පිටාර ගැලීමක් ඇති කරයි.
නිත්ය පුනරාවර්තනය භාවිතා කරමින්, එක් එක් පුනරාවර්තන ඇමතුම තවත් ප්රවේශයක් ඇමතුම් තොගයට තල්ලු කරයි. පුනරාවර්තනය සම්පුර්ණ වූ විට, යෙදුමට පසුව සෑම ප්රවේශයක්ම පසුපසට එබිය යුතුය.
වලිගය පුනරාවර්තනය සමඟ, භාෂාව මත පදනම්ව සම්පාදකයාට එක් ප්රවේශයක් දක්වා තොගය බිඳ දැමිය හැකිය, එබැවින් ඔබ තොග ඉඩ ඉතිරි කරයි ... විශාල පුනරාවර්තන විමසුමක් ඇත්ත වශයෙන්ම තොග පිටාර ගැලීමට හේතු වේ.
මූලික වශයෙන් වලිග පුනරාවර්තනයන් නැවත ක්රියාකාරීත්වයට ප්රශස්තිකරණය කළ හැකිය.
එය වචන වලින් පැහැදිලි කරනවා වෙනුවට මෙන්න උදාහරණයක්. මෙය සාධකීය ශ්රිතයේ යෝජනා ක්රමයකි:
(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))))
පළමු අනුවාදයේ දී සත්යයට පුනරාවර්තන ඇමතුම ගුණ කිරීමේ ප්රකාශනයට පෝෂණය වන බව ඔබට පෙනෙනු ඇත, එබැවින් පුනරාවර්තන ඇමතුම ලබා ගැනීමේදී රාජ්යය තොගයේ ඉතිරි කළ යුතුය. වලිගය-පුනරාවර්තන අනුවාදයේ පුනරාවර්තන ඇමතුමේ වටිනාකම බලාපොරොත්තුවෙන් වෙනත් S- ප්රකාශනයක් නොමැති අතර, තවත් වැඩක් නොමැති බැවින්, රාජ්යය තොගයේ ඉතිරි කළ යුතු නොවේ. රීතියක් ලෙස, යෝජනා ක්රමයේ වලිගය-පුනරාවර්තන කාර්යයන් නියත සිරස් ඉඩ භාවිතා කරයි.
list-reverse
කිරීමේ ක්රියාවලිය නියත තොග අවකාශයේ ක්රියාත්මක වන නමුත් ගොඩවල් මත දත්ත ව්යුහයක් නිර්මාණය කර වර්ධනය වේ. ගසක ගමන් කරන්නෙකුට අතිරේක තර්කයක් තුළ අනුකලනය කළ හැකි තොගයක් භාවිතා කළ හැකිය. ආදිය
වලිග පුනරාවර්තනය යනු පුනරාවර්තන ඇල්ගොරිතමයේ අවසාන තාර්කික උපදෙස් වල පුනරාවර්තන ඇමතුමයි.
සාමාන්යයෙන් පුනරාවර්තනයේදී , ඔබට පාදක නඩුවක් ඇති අතර එය පුනරාවර්තන ඇමතුම් නවත්වන අතර ඇමතුම් තොගය ආරම්භ කිරීමට පටන් ගනී. සම්භාව්ය නිදසුනක් භාවිතා කිරීම සඳහා, ලිස්ප් වලට වඩා සී-ඊෂ් වුවද, සාධකීය ශ්රිතය වලිග පුනරාවර්තනය පෙන්නුම් කරයි. පුනරාවර්තන ඇමතුම සිදුවන්නේ මූලික නඩුවේ තත්වය පරීක්ෂා කිරීමෙන් පසුවය .
factorial(x, fac=1) {
if (x == 1)
return fac;
else
return factorial(x-1, x*fac);
}
ක්රමාරෝපිත කිරීමට මූලික ඇමතුමක් වනු ඇත factorial(n)
එහිදී fac=1
(පෙරනිමි අගය) හා n එය සඳහා ක්රමාරෝපිත ගණනය කිරීමට නියමිත සංඛ්යාව වේ.
else
යනු ඔබ "පාදක නඩුවක්" ලෙස හැඳින්විය හැකි නමුත් පේළි කිහිපයක් හරහා විහිදේ. මම ඔබව වරදවා වටහාගෙන තිබේද? නැතහොත් මගේ උපකල්පනය නිවැරදිද? වලිග පුනරාවර්තනය එක් ලයිනර් සඳහා පමණක් හොඳද?
factorial
උදාහරණය එච්චරයි බව පමණක් සම්භාව්ය සරල උදාහරණයක් වේ.
එහි අර්ථය වන්නේ උපදෙස් දර්ශකය තොගයේ තල්ලු කිරීමට අවශ්ය නොව, ඔබට පුනරාවර්තන ශ්රිතයක මුදුනට පැන ඉදිරියට ගෙන යා හැකි බවයි. මෙමඟින් තොගය පිරී ඉතිරී නොයා කාර්යයන් දින නියමයක් නොමැතිව නැවත ලබා ගත හැක.
මම මෙම මාතෘකාව පිළිබඳ බ්ලොග් සටහනක් ලිවූ අතර , එහි රාමු වල පෙනුම කෙබඳුද යන්න පිළිබඳ චිත්රක උදාහරණ ඇත.
කාර්යයන් දෙකක් සංසන්දනය කරන ඉක්මන් කේත ස්නිපටයක් මෙන්න. පළමුවැන්න දී ඇති සංඛ්යාවක සාධකය සොයා ගැනීම සඳහා සාම්ප්රදායික පුනරාවර්තනයයි. දෙවැන්න වලිග පුනරාවර්තනය භාවිතා කරයි.
තේරුම් ගැනීමට ඉතා සරල හා බුද්ධිමත්.
පුනරාවර්තන ශ්රිතයක් වලිග පුනරාවර්තනයක් දැයි පැවසීමට පහසු ක්රමයක් නම්, එය මූලික නඩුවේ කොන්ක්රීට් අගයක් ලබා දෙන්නේ නම්. එයින් අදහස් වන්නේ එය 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);
}
}
මට තේරුම් ගැනීමට ඇති හොඳම ක්රමය tail call recursion
වන්නේ පුනරාවර්තනයේ විශේෂ අවස්ථාවකි, එහිදී අවසාන ඇමතුම (හෝ වලිග ඇමතුම) ශ්රිතය වේ.
පයිතන් හි දක්වා ඇති උදාහරණ සංසන්දනය කිරීම:
def recsum(x):
if x == 1:
return x
else:
return x + recsum(x - 1)
RE පුනරාවර්තනය
def tailrecsum(x, running_total=0):
if x == 0:
return running_total
else:
return tailrecsum(x - 1, running_total + x)
A ටේල් පුනරාවර්තනය
සාමාන්ය පුනරාවර්තන අනුවාදයේ ඔබට දැකිය හැකි පරිදි, කේත කොටසෙහි අවසාන ඇමතුම වේ x + recsum(x - 1)
. එබැවින් recsum
ක්රමවේදය ඇමතීමෙන් පසුව , තවත් මෙහෙයුමක් ඇත x + ..
.
කෙසේ වෙතත්, වලිග පුනරාවර්තන අනුවාදයේ, කේත වාරණයේ ඇති අවසාන ඇමතුම (හෝ වලිග ඇමතුම) tailrecsum(x - 1, running_total + x)
යන්නෙන් අදහස් වන්නේ අවසාන ඇමතුම ක්රමයටම කර ඇති අතර ඉන් පසුව ක්රියාත්මක නොවේ.
මෙම කරුණ වැදගත් වන්නේ මෙහි දක්නට ලැබෙන පරිදි වලිගය පුනරාවර්තනය වීම මතකය වර්ධනය නොවන බැවිනි. මන්දයත් යටින් පවතින වීඑම් විසින් වලිග ස්ථානයකට කැඳවන ශ්රිතයක් දුටු විට (ශ්රිතයකදී ඇගයීමට ලක් කළ අවසාන ප්රකාශනය), එය වත්මන් සිරස් රාමුව ඉවත් කරයි. Tail Call Optimization (TCO) ලෙස හැඳින්වේ.
සැ.යු. ඉහත උදාහරණය ලියා ඇත්තේ පයිතන්හි බව මතක තබා ගන්න, එහි ධාවන කාලය TCO සඳහා සහය නොදක්වයි. කාරණය පැහැදිලි කිරීමට මෙය උදාහරණයක් පමණි. යෝජනා ක්රමය, හැස්කල් වැනි භාෂාවලින් TCO සඳහා සහය දක්වයි
ජාවා හි, ෆිබොනාච්චි ශ්රිතය නැවත නැවත ක්රියාත්මක කළ හැකි ය:
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);
}
iter
කිරීමට acc
විට iter < (n-1)
.
මම ලිස්ප් ක්රමලේඛකයෙක් නොවෙමි, නමුත් මෙය උපකාරී වනු ඇතැයි මම සිතමි .
මූලික වශයෙන් එය ක්රමලේඛන ශෛලියකි, එනම් පුනරාවර්තන ඇමතුම ඔබ කරන අවසාන දෙයයි.
වලිගය පුනරාවර්තනය භාවිතා කරමින් සාධක සාධක කරන පොදු ලිස්ප් උදාහරණයක් මෙන්න. තොග අඩු ස්වභාවය නිසා කෙනෙකුට ඉතා විශාල සාධක සාධක ගණනය කිරීම් කළ හැකිය ...
(defun ! (n &optional (product 1))
(if (zerop n) product
(! (1- n) (* product n))))
විනෝදය සඳහා ඔබට උත්සාහ කළ හැකිය (format nil "~R" (! 25))
කෙටියෙන් කිවහොත්, වලිග පුනරාවර්තනයට අන්තිමයා ලෙස පුනරාවර්තන ඇමතුම ඇත ශ්රිතයේ ප්රකාශය සඳහා රැඳී සිටීමට අවශ්ය නොවේ.
එබැවින් මෙය වලිග පුනරාවර්තනයකි. දෙවන පරාමිතිය p අතරමැදි නිෂ්පාදන අගය දරයි.
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);
}
" වලිග පුනරාවර්තනය අවබෝධ කර ගැනීම - දෘශ්ය ස්ටුඩියෝ සී ++ - එකලස් දර්ශනය " යන මාතෘකාව යටතේ මම දිගු ලිපියක් ලිවීය.
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
}
මෙය වලිග පුනරාවර්තනය පිළිබඳ පරිගණක වැඩසටහන් වල ව්යුහය සහ අර්ථ නිරූපණයෙන් උපුටා ගැනීමකි.
පුනරාවර්තනය හා පුනරාවර්තනයට වෙනස්ව, පුනරාවර්තන ක්රියාවලියක් යන සංකල්පය පුනරාවර්තන ක්රියා පටිපාටියක් සමඟ පටලවා නොගැනීමට අපි පරෙස්සම් විය යුතුය. අපි ක්රියාපටිපාටියක් පුනරාවර්තන ලෙස විස්තර කරන විට, අප යොමු කරන්නේ ක්රියාපටිපාටියේ අර්ථ දැක්වීම (සෘජුව හෝ වක්රව) ක්රියාපටිපාටියටම යොමු වන සින්ටැක්ටික් කාරණයට ය. නමුත් ක්රියාවලියක් රේඛීයව පුනරාවර්තනය වන රටාවක් අනුගමනය කරන බව අප විස්තර කරන විට, අප කතා කරන්නේ ක්රියාවලිය පරිණාමය වන ආකාරය ගැන මිස ක්රියා පටිපාටියක් ලියා ඇති ආකාරය පිළිබඳ වාක්ය ඛණ්ඩය ගැන නොවේ. පුනරාවර්තන ක්රියාවලියක් උත්පාදනය කිරීම වැනි කරුණු-ඉටර් වැනි පුනරාවර්තන ක්රියා පටිපාටියකට අප යොමු වීම කරදරකාරී බවක් පෙනෙන්නට තිබේ. කෙසේ වෙතත්, ක්රියාවලිය සැබවින්ම ක්රියාකාරී වේ: එහි තත්වය එහි රාජ්ය විචල්යයන් තුනෙන් මුළුමනින්ම ග්රහණය කර ගන්නා අතර, ක්රියාවලිය ක්රියාත්මක කිරීම සඳහා පරිවර්තකයෙකුට විචල්යයන් තුනක් පමණක් නිරීක්ෂණය කළ යුතුය.
ක්රියාවලිය හා ක්රියා පටිපාටිය අතර වෙනස අවුල් සහගත වීමට එක් හේතුවක් නම්, පොදු භාෂාවන් (අඩා, පැස්කල් සහ සී ඇතුළුව) බොහෝ ක්රියාත්මක කිරීම් සැලසුම් කර ඇත්තේ ඕනෑම පුනරාවර්තන ක්රියා පටිපාටියක අර්ථ නිරූපණය සමඟ වර්ධනය වන මතක ප්රමාණයක් පරිභෝජනය කරන ආකාරයට ය. විස්තර කර ඇති ක්රියාවලිය ප්රතිපත්තිමය වශයෙන් පුනරාවර්තනය වන විට පවා ක්රියා පටිපාටි ඇමතුම් ගණන. මෙහි ප්රති consequ ලයක් ලෙස, මෙම භාෂාවන්ට පුනරාවර්තන ක්රියාවලීන් විස්තර කළ හැක්කේ කරන්න, පුනරාවර්තනය කරන්න, එන තුරු, සහ සිටියදී වැනි විශේෂ අරමුණු සහිත “ලූප ඉදිකිරීම්” වෙත යොමු වීමෙන් පමණි. යෝජනා ක්රමය ක්රියාත්මක කිරීම මෙම අඩුපාඩුව බෙදා නොගනී. පුනරාවර්තන ක්රියාවලියක් මඟින් පුනරාවර්තන ක්රියාවලිය විස්තර කළද, එය නියත අවකාශයේ පුනරාවර්තන ක්රියාවලියක් ක්රියාත්මක කරනු ඇත. මෙම දේපල සමඟ ක්රියාත්මක කිරීම වලිග පුනරාවර්තන ලෙස හැඳින්වේ. වලිගය-පුනරාවර්තන ක්රියාවට නැංවීමත් සමඟ, සාමාන්ය ක්රියා පටිපාටි ඇමතුම් යාන්ත්රණය භාවිතයෙන් නැවත ප්රකාශනය කළ හැකි අතර එමඟින් විශේෂ පුනරාවර්තන ඉදිකිරීම් ප්රයෝජනවත් වන්නේ සින්ටැක්ටික් සීනි ලෙස පමණි.
වලිග පුනරාවර්තනය යනු ඔබ දැන් ජීවත්වන ජීවිතයයි. “පෙර” රාමුවකට නැවත යාමට හේතුවක් හෝ ක්රමයක් නොමැති නිසා ඔබ නැවත නැවතත් එකම සිරස් රාමුව ප්රතිචක්රීකරණය කරයි. අතීතය අවසන් වී ඇති අතර එය ඉවත දැමිය හැකිය. ඔබේ ක්රියාවලිය නොවැළැක්විය හැකි ලෙස මිය යන තුරුම ඔබට එක් රාමුවක් ලැබෙනු ඇත.
සමහර ක්රියාදාමයන් අතිරේක රාමු උපයෝගී කර ගත හැකි යැයි ඔබ සලකන විට ප්රතිසම බිඳ වැටේ.
පුනරාවර්තන ශ්රිතය යනු තනිවම කැඳවන ශ්රිතයකි
අවම කේත ප්රමාණයක් භාවිතා කරමින් කාර්යක්ෂම වැඩසටහන් ලිවීමට ක්රමලේඛකයන්ට එය ඉඩ දෙයි .
අවාසිය නම් නිසි ලෙස ලියා නොමැති නම් ඒවා අනන්ත ලූප සහ වෙනත් අනපේක්ෂිත ප්රති results ල ඇති කළ හැකි වීමයි.
සරල පුනරාවර්තන ශ්රිතය සහ වලිග පුනරාවර්තන ශ්රිතය යන දෙකම මම පැහැදිලි කරමි
සරල පුනරාවර්තන ශ්රිතයක් ලිවීම සඳහා
දී ඇති උදාහරණයෙන්:
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)
public static int fact(4){
if(4 <=1)
return 1;
else
return 4 * fact(4-1);
}
If
ලූප් අසමත් වන බැවින් else
එය නැවත ලූපයට යයි4 * fact(3)
සිරස් මතකයේ, අපට තිබේ 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)
මතක තබා ගන්න අපි `` `4 * සත්යය (3)` `
සඳහා ප්රතිදානය fact(3) = 3 * fact(2)
මෙතෙක් තොගයේ තිබේ 4 * fact(3) = 4 * 3 * fact(2)
සිරස් මතකයේ, අපට තිබේ 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)
සිරස් මතකයේ, අපට තිබේ 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
අවසාන වශයෙන්, සත්යයේ ප්රති result ලය (4) = 4 * 3 * 2 * 1 = 24
මෙම Tail සහානුයාත වලට වනු ඇත
public static int fact(x, running_total=1) {
if (x==1) {
return running_total;
} else {
return fact(x-1, running_total*x);
}
}
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)
සිරස් මතකයේ, අපට තිබේ 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)
සිරස් මතකයේ, අපට තිබේ 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)
සිරස් මතකයේ, අපට තිබේ 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
අවසාන වශයෙන්, සත්යයේ ප්රති result ලය (4,1) = 24
වලිග පුනරාවර්තනය යනු පුනරාවර්තන ශ්රිතයක් වන අතර එමඟින් ශ්රිතය අවසානයේදී ("වලිගය") කැඳවනු ලැබේ. බොහෝ සම්පාදකයින් පුනරාවර්තන ඇමතුමක් වලිග පුනරාවර්තන හෝ පුනරාවර්තන ඇමතුමකට වෙනස් කිරීමට ප්රශස්තිකරණය කරයි.
සංඛ්යාවක සාධක සාධක ගණනය කිරීමේ ගැටළුව සලකා බලන්න.
Approach ජු ප්රවේශයක් වනුයේ:
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
ඉහත නඩුවේ උපරිම පුනරාවර්තන ගැඹුර O (n) වේ.
කෙසේ වෙතත්, පහත උදාහරණය සලකා බලන්න:
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
මෙන්න, උපරිම පුනරාවර්තන ගැඹුර O (n) වන නමුත් කිසිදු ඇමතුමක් තොගයට අමතර විචල්යයක් එක් නොකරයි. එබැවින් සම්පාදකයාට තොගයක් ඉවත් කළ හැකිය.
ඒ වලිගය ආවර්තනික කාර්යය නැවත කියන්නේ ආවර්තනික කාර්යය ඇමතුමක් පෙර අන්තිම මෙහෙයුම එය එහිදී ආවර්තනික කාර්යයකි. එනම්, පුනරාවර්තන ශ්රිත ඇමතුමේ ප්රතිලාභ අගය වහාම ආපසු ලබා දෙනු ලැබේ. උදාහරණයක් ලෙස, ඔබේ කේතය මේ වගේ වනු ඇත:
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
පරාමිතිය effectively ලදායී ලෙස ගෝලීය විචල්යයක් ලෙස භාවිතා කරයි.)
නමුත් ටේල් කෝල් ප්රශස්තිකරණයේ වාසිය නම් එය පිටාර ගැලීමේ දෝෂ වළක්වයි. (පුනරාවර්තන එකක් වෙනුවට පුනරාවර්තන ඇල්ගොරිතමයක් භාවිතා කිරීමෙන් ඔබට මෙම ප්රතිලාභය ලබා ගත හැකි බව මම සටහන් කරමි.)
ඇමතුම් තොගයේ රාමු වස්තූන් ඕනෑවට වඩා තල්ලු කර ඇති විට සිරස් පිටාර ගැලීම් සිදු වේ. ශ්රිතයක් කැඳවූ විට රාමු වස්තුවක් ඇමතුම් තොගයට තල්ලු වන අතර ශ්රිතය නැවත පැමිණෙන විට ඇමතුම් තොගයෙන් ඉවත් වේ. රාමු වස්තූන් තුළ දේශීය විචල්යයන් සහ ශ්රිතය නැවත පැමිණෙන විට කුමන කේත රේඛාවක් වෙත ආපසු යා යුතුද යන්න වැනි තොරතුරු අඩංගු වේ.
ඔබගේ පුනරාවර්තන ශ්රිතය නැවත පැමිණීමෙන් තොරව බොහෝ පුනරාවර්තන ඇමතුම් ලබා ගන්නේ නම්, ඇමතුම් තොගය එහි රාමු වස්තු සීමාව ඉක්මවා යා හැක. (අංකය වේදිකාව අනුව වෙනස් වේ; පයිතන්හි එය පෙරනිමියෙන් රාමු වස්තු 1000 කි.) මෙය තොග පිටාර ගැලීමේ දෝෂයක් ඇති කරයි. (හේයි, මෙම වෙබ් අඩවියේ නම පැමිණියේ එතැනිනි!)
කෙසේ වෙතත්, ඔබේ පුනරාවර්තන කාර්යය අවසන් වරට කරන්නේ පුනරාවර්තන ඇමතුම ලබා දී එහි ප්රතිලාභ අගය ආපසු ලබා දීම නම්, වර්තමාන රාමු වස්තුව ඇමතුම් තොගයේ රැඳී සිටීමට අවශ්ය කිසිදු හේතුවක් නැත. සියල්ලට පසු, පුනරාවර්තන ශ්රිත ඇමතුමෙන් පසුව කේතයක් නොමැති නම්, වත්මන් රාමු වස්තුවෙහි දේශීය විචල්යයන් මත රැඳී සිටීමට හේතුවක් නැත. එබැවින් අපට වර්තමාන රාමු වස්තුව ඇමතුම් තොගයේ තබා ගැනීමට වඩා වහාම ඉවත් කළ හැකිය. මෙහි අවසාන ප්රති result ලය වනුයේ ඔබේ ඇමතුම් තොගය ප්රමාණයෙන් වර්ධනය නොවන අතර එමඟින් පිටාර ගැලීම කළ නොහැක.
සම්පාදකයෙකුට හෝ පරිවර්තකයෙකුට වලිග ඇමතුම් ප්රශස්තිකරණය යෙදිය හැකි විට හඳුනා ගැනීමට හැකි වන පරිදි අංගයක් ලෙස ටේල් කෝල් ප්රශස්තිකරණය තිබිය යුතුය. එසේ වුවද, වලිග ඇමතුම් ප්රශස්තිකරණය භාවිතා කිරීම සඳහා ඔබේ පුනරාවර්තන ශ්රිතයේ කේතය නැවත සකස් කර ඇති අතර, කියවීමේ හැකියාවෙහි මෙම විභව අඩුවීම ප්රශස්තිකරණයට සුදුසු නම් එය ඔබට භාරයි.
සාමාන්ය පුනරාවර්තනයට සාපේක්ෂව වලිග පුනරාවර්තනය ඉතා වේගවත්ය. එය වේගවත් වන්නේ මුතුන් මිත්තන්ගේ ඇමතුමේ ප්රතිදානය ලුහුබැඳීම සඳහා තොගයේ ලියා නොතිබෙන බැවිනි. නමුත් සාමාන්ය පුනරාවර්තනයේදී සියලුම මුතුන් මිත්තන් ලුහුබැඳීම සඳහා ලියා ඇති ප්රතිදානය තොග වශයෙන් ලියා ඇත.
වලිග-ඇමතුම් පුනරාවර්තනය සහ වලිග නොවන ඇමතුම් පුනරාවර්තනය අතර ඇති මූලික වෙනස්කම් කිහිපයක් අවබෝධ කර ගැනීම සඳහා අපට මෙම ශිල්පීය ක්රමවල .NET ක්රියාත්මක කිරීම් ගවේෂණය කළ හැකිය.
C #, F #, සහ C ++ \ CLI: C #, F #, සහ C ++ \ CLI හි වලිග පුනරාවර්තනයේ වික්රමාන්විත උදාහරණ කිහිපයක් සහිත ලිපියක් මෙන්න .
සී # වලිග ඇමතුම් පුනරාවර්තනය සඳහා ප්රශස්තිකරණය නොකරන අතර එෆ් # එසේ කරයි.
මූලධර්මයේ වෙනස්කම් වලට ලම්ප්ඩා එදිරිව ලැම්බඩා ගණනය කිරීම් ඇතුළත් වේ. සී # නිර්මාණය කර ඇත්තේ ලූප මනසේ තබාගෙන වන අතර එෆ් # සෑදී ඇත්තේ ලැම්බඩා කැල්කියුලස් මූලධර්ම මගිනි. ලැම්බඩා කැල්කියුලස් මූලධර්ම පිළිබඳ ඉතා හොඳ (සහ නිදහස්) පොතක් සඳහා, බලන්න , ඇබෙල්සන්, සුස්මාන් සහ සුස්මාන් විසින් පරිගණක වැඩසටහන් වල ව්යුහය සහ අර්ථ නිරූපණය .
F # හි වලිග ඇමතුම් සම්බන්ධයෙන්, ඉතා හොඳ හඳුන්වාදීමේ ලිපියක් සඳහා, F # හි වලිග ඇමතුම් පිළිබඳ සවිස්තරාත්මක හැඳින්වීමක් බලන්න . අවසාන වශයෙන්, වලිග නොවන පුනරාවර්තනය සහ වලිග ඇමතුම් පුනරාවර්තනය අතර වෙනස ආවරණය කරන ලිපියක් මෙන්න (F # හි): වලිගය-පුනරාවර්තනය එදිරිව එෆ් තියුණු වල වලිග නොවන පුනරාවර්තනය .
C # සහ F # අතර වලිග ඇමතුම් පුනරාවර්තනයේ සමහර සැලසුම් වෙනස්කම් ගැන කියවීමට ඔබට අවශ්ය නම්, C # සහ F # හි ටේල්-කෝල් ඔප්කෝඩ් උත්පාදනය කිරීම බලන්න .
C # සම්පාදකයා වලිග-ඇමතුම් ප්රශස්තිකරණයෙන් වළක්වන්නේ කුමන කොන්දේසි දැයි දැන ගැනීමට ඔබ ප්රමාණවත් නම්, මෙම ලිපිය බලන්න: JIT CLR වලිග-ඇමතුම් කොන්දේසි .
මූලික පුනරාවර්තන වර්ග දෙකක් තිබේ: හිස පුනරාවර්තනය සහ වලිග පුනරාවර්තනය.
දී හිස සහානුයාත වලට , විශාල උත්සවයක් එහි ආවර්තනික ඇමතුමක් කරයි ඉන්පසු තව ගණනය කිරීම් සිදු කරන අතර, සමහර විට උදාහරණයක් ලෙස, ආවර්තනික ඇමතුමක් ප්රතිඵලයක් භාවිතා කිරීම.
දී වලිගය ආවර්තනික කාර්යය, සියලු ගණනය කිරීම් පළමු සිදුවන්නේ ආවර්තනික ඇමතුමක් සිදු වන අවසන් දේ වේ.
මෙම සුපිරි නියමයි පෝස්ට් එකෙන් . කරුණාකර එය කියවීම සලකා බලන්න.
පුනරාවර්තනය යනු තමන් විසින්ම කැඳවන ශ්රිතයකි. උදාහරණයක් වශයෙන්:
(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)))
උපකාරක ක්රියාපටිපාටියේදී, වම නිශ්ශබ්ද නොවන්නේ නම් එය කරන අන්තිම දෙය නම් තමාටම ඇමතීමයි (යමක් පරිහරණය කිරීමෙන් පසුව සහ සීඩීආර් යමක්). මෙය මූලික වශයෙන් ඔබ ලැයිස්තුවක් සිතියම් ගත කරන ආකාරයයි.
වලිගය පුනරාවර්තනයට විශාල වාසියක් ඇත, එය පරිවර්තකයාට (හෝ සම්පාදකයාට භාෂාව සහ වෙළෙන්දා මත රඳා පවතී) එය ප්රශස්තිකරණය කළ හැකි අතර එය කාල ලූපයකට සමාන දෙයක් බවට පරිවර්තනය කරයි. ඇත්ත වශයෙන්ම, යෝජනා ක්රමයේ සම්ප්රදායට අනුව, බොහෝ “සඳහා” සහ “අතර” ලූපය වලිග පුනරාවර්තන ආකාරයෙන් සිදු කරනු ලැබේ (මා දන්නා තරමින් සහ අතර කාලයක් නොමැත).
මෙම ප්රශ්නයට හොඳ පිළිතුරු රාශියක් ඇත ... නමුත් "වලිග පුනරාවර්තනය" හෝ අවම වශයෙන් "නිසි වලිග පුනරාවර්තනය" යන්න නිර්වචනය කරන්නේ කෙසේද යන්න පිළිබඳ විකල්ප පියවරක් ගැනීමට මට උදව් කළ නොහැක. එනම්: යමෙකු එය වැඩසටහනක විශේෂිත ප්රකාශනයක දේපලක් ලෙස සැලකිය යුතුද? නැතහොත් එය ක්රමලේඛන භාෂාවක් ක්රියාත්මක කිරීමේ දේපලක් ලෙස සැලකිය යුතුද?
පසුකාලීන දෘෂ්ටිය පිළිබඳ වැඩි විස්තර සඳහා, සම්භාව්ය කඩදාසි ඇතවිල් ක්ලින්ගර් විසින් රචිත “නිසි වලිග පුනරාවර්තනය සහ අභ්යවකාශ කාර්යක්ෂමතාව” (පීඑල්ඩීඅයි 1998) විසින් ඇත, එය “නිසි වලිග පුනරාවර්තනය” යනු ක්රමලේඛන භාෂා ක්රියාත්මක කිරීමේ දේපලක් ලෙස අර්ථ දැක්වීය. අර්ථ දැක්වීම ගොඩනඟා ඇත්තේ ක්රියාත්මක කිරීමේ තොරතුරු නොසලකා හැරීමට යමෙකුට ඉඩ දීම සඳහා ය (ඇමතුම් තොගය සැබවින්ම නිරූපණය වන්නේ ධාවන කාල තොගය හරහා ද නැතහොත් ගොඩවල් වෙන් කළ සම්බන්ධිත රාමු ලැයිස්තුවක් හරහා ද යන්න).
මෙය සිදු කිරීම සඳහා, එය අසමමිතික විශ්ලේෂණයක් භාවිතා කරයි: සාමාන්යයෙන් දකින පරිදි වැඩසටහන් ක්රියාත්මක කිරීමේ වේලාව නොව, වැඩසටහන් අවකාශය භාවිතා කිරීම . මේ ආකාරයෙන්, ධාවන කාල ඇමතුම් තොගයක් එදිරිව ගොඩවල් වෙන් කරන ලද සම්බන්ධිත ලැයිස්තුවක අවකාශය භාවිතය අසමමිතික ලෙස සමාන වේ; එබැවින් ක්රමලේඛන භාෂා ක්රියාත්මක කිරීමේ විස්තරය නොසලකා හැරිය යුතුය (එය ප්රායෝගිකව තරමක් වැදගත් වන නමුත්, යම් ක්රියාවලියක් “දේපල වලිගය පුනරාවර්තන” වීමේ අවශ්යතාවය සපුරාලන්නේද යන්න තීරණය කිරීමට යමෙක් උත්සාහ කරන විට ජලය මඩ කළ හැකිය. )
හේතු ගණනාවක් නිසා කඩදාසි හොඳින් අධ්යයනය කිරීම වටී:
එය වැඩසටහනක වලිග ප්රකාශන සහ වලිග ඇමතුම් සඳහා ප්රේරක අර්ථ දැක්වීමක් ලබා දෙයි . (එවැනි අර්ථ දැක්වීමක් සහ එවැනි ඇමතුම් වැදගත් වන්නේ ඇයිද යන්න මෙහි දී ඇති අනෙක් බොහෝ පිළිතුරු වල මාතෘකාව බව පෙනේ.)
පෙළෙහි රසය ලබා දීම සඳහා එම අර්ථ දැක්වීම් මෙන්න:
අර්ථ දැක්වීම 1 මෙම වලිගය ප්රකාශන පහත සඳහන් පරිදි ප්රේරක Core යෝජනා ක්රමය ලියා වැඩසටහනක් අර්ථ නිරූපනය කර ඇත.
- ලැම්බඩා ප්රකාශනයක ශරීරය වලිග ප්රකාශනයකි
- නම්
(if E0 E1 E2)
වලිගයකට ප්රකාශනය, එවිට ද වේE1
හාE2
වේ වලිගය ප්රකාශන.- වෙන කිසිවක් වලිග ප්රකාශනය නොවේ.
අර්ථ දැක්වීම 2 ඒ වලිගය ඇමතුමක් පටිපාටියක් ඇමතුමක් බව වලිගයකට ප්රකාශනයකි.
(වලිග පුනරාවර්තන ඇමතුමක්, හෝ පුවත්පත පවසන පරිදි, “ස්වයං-වලිග ඇමතුම” යනු ක්රියා පටිපාටියම ක්රියාත්මක වන වලිග ඇමතුමක විශේෂ අවස්ථාවකි.)
එය එක් එක් යන්ත්රය එම නිරීක්ෂණය චර්යාවක් එහිදී ප්රධාන යෝජනා ක්රමය, ඇගයීම සඳහා විවිධ "යන්ත්ර" හයක් විධිමත් අර්ථ දැක්වීම් ලබා හැර සඳහා asymptotic එක් එක් වන බව අවකාශය සංකීර්ණත්වය පන්තියේ.
නිදසුනක් ලෙස, පිළිවෙලින් යන්ත්ර සඳහා නිර්වචන ලබා දීමෙන් පසු, 1. සිරස් මත පදනම් වූ මතක කළමනාකරණය, 2. කසළ එකතු කිරීම, නමුත් වලිග ඇමතුම් නැත, 3. කසළ එකතු කිරීම සහ වලිග ඇමතුම්, කඩදාසි ඊටත් වඩා දියුණු ගබඩා කළමනාකරණ උපාය මාර්ග සමඟ ඉදිරියට යයි. 4. වලිග ඇමතුමක අවසාන උප ප්රකාශන තර්කය ඇගයීම හරහා පරිසරය ආරක්ෂා කිරීමට අවශ්ය නොවන “එව්ලිස් ටේල් පුනරාවර්තනය”, 5. වසා දැමීමේ පරිසරය එම වසා දැමීමේ නිදහස් විචල්යයන්ට පමණක් අඩු කිරීම සහ 6. ඇපල් සහ ෂාවෝ විසින් අර්ථ දක්වා ඇති ඊනියා "අභ්යවකාශයට ආරක්ෂිත" අර්ථ නිරූපණය .
යන්ත්ර ඇත්ත වශයෙන්ම එකිනෙකට වෙනස් අභ්යවකාශ සංකීර්ණ පන්ති හයකට අයත් බව සනාථ කිරීම සඳහා, කඩදාසි, සංසන්දනය කරන සෑම යන්ත්ර යුගලයක් සඳහාම, එක් යන්ත්රයක් මත අසමමිතික අභ්යවකාශ පිපිරීමක් නිරාවරණය කරන වැඩසටහන් පිළිබඳ සංයුක්ත උදාහරණ සපයයි.
(දැන් මගේ පිළිතුර කියවන විට, ක්ලින්ජර් පුවත්පතේ තීරණාත්මක කරුණු ග්රහණය කර ගැනීමට මට හැකි දැයි මට විශ්වාස නැත . එහෙත්, අහෝ, මෙම පිළිතුර සංවර්ධනය කිරීම සඳහා මට දැන් වැඩි කාලයක් කැප කළ නොහැක.)
බොහෝ අය දැනටමත් මෙහි පුනරාවර්තනය පැහැදිලි කර ඇත. රිකාඩෝ ටෙරල් විසින් රචිත “.NET හි සමගාමී, සමගාමී හා සමාන්තර වැඩසටහන්කරණයේ නවීන රටාවන්” නම් පොතෙන් පුනරාවර්තනයෙන් ලැබෙන වාසි කිහිපයක් පිළිබඳ අදහස් කිහිපයක් උපුටා දැක්වීමට මම කැමැත්තෙමි.
“ක්රියාකාරී පුනරාවර්තනය යනු එෆ්පී හි පුනරාවර්තනය වීමේ ස්වාභාවික ක්රමය වන අතර එය රාජ්යයේ විකෘතිය වළක්වයි. එක් එක් පුනරාවර්තනය අතරතුර, යාවත්කාලීන කිරීම සඳහා නව අගයක් ලූප් කන්ස්ට්රක්ටර් වෙත යවනු ලැබේ (විකෘති). ඊට අමතරව, පුනරාවර්තන ශ්රිතයක් රචනා කළ හැකි අතර, එමඟින් ඔබේ වැඩසටහන වඩාත් මොඩියුලර් කරවන අතරම සමාන්තරකරණය සූරාකෑමට අවස්ථා හඳුන්වා දෙනු ලැබේ.
වලිග පුනරාවර්තනය පිළිබඳ එකම පොතේ රසවත් සටහන් කිහිපයක් ද මෙන්න:
ටේල්-කෝල් පුනරාවර්තනය යනු අවදානම් සහ අතුරු ආබාධ නොමැතිව විශාල යෙදවුම් හැසිරවිය හැකි නිත්ය පුනරාවර්තන ශ්රිතයක් ප්රශස්තිකරණය කළ අනුවාදයක් බවට පරිවර්තනය කරන තාක්ෂණයකි.
සටහන වලිග ඇමතුමක් ප්රශස්තිකරණය ලෙස දැක්වීමට මූලික හේතුව වන්නේ දත්ත ප්රදේශය, මතක භාවිතය සහ හැඹිලි භාවිතය වැඩි දියුණු කිරීමයි. වලිග ඇමතුමක් කිරීමෙන්, ඇමතුම්කරු ඇමතුම්කරුට සමාන ඉඩ ප්රමාණයක් භාවිතා කරයි. මෙය මතක පීඩනය අඩු කරයි. නව හැඹිලි රේඛාවක් සඳහා ඉඩක් ලබා දීම සඳහා පැරණි හැඹිලි රේඛාවක් ඉවත් කරනවාට වඩා, එකම මතකය පසුව ඇමතුම් සඳහා නැවත භාවිතා කරන අතර හැඹිලියේ රැඳී සිටිය හැකි නිසා එය හැඹිලිය සුළු වශයෙන් වැඩි කරයි.
එය විශේෂ පුනරාවර්තනයක් වන අතර එහිදී ශ්රිතයක අවසාන මෙහෙයුම පුනරාවර්තන ඇමතුමකි. වර්තමාන සිරස් රාමුව තුළ ඇමතුම ක්රියාත්මක කිරීමෙන් සහ නව සිරස් රාමුවක් නිර්මාණය කරනවාට වඩා එහි ප්රති result ලය ලබා දීමෙන් පුනරාවර්තනය ප්රශස්ත කළ හැකිය.
පුනරාවර්තන ශ්රිතයක් වලිගය පුනරාවර්තන වේ. උදාහරණයක් ලෙස පහත දැක්වෙන C ++ ශ්රිත මුද්රණය () වලිග පුනරාවර්තන වේ.
වලිග පුනරාවර්තන ක්රියාකාරිත්වයට උදාහරණයක්
void print(int n)
{
if (n < 0) return;
cout << " " << n;
print(n-1);}
// The last executed statement is recursive call
වලිගය පුනරාවර්තන ලෙස වලිග පුනරාවර්තන කාර්යයන් වඩා හොඳ යැයි සැලකෙන වලිග පුනරාවර්තන කාර්යයන් සම්පාදකයාට ප්රශස්තිකරණය කළ හැකිය. පුනරාවර්තන ඇමතුම අවසාන ප්රකාශය බැවින් වත්මන් ශ්රිතයේ කිසිවක් කිරීමට ඉතිරිව නැති බැවින් වත්මන් ශ්රිතයේ සිරස් රාමුව සුරැකීමෙන් කිසිදු ප්රයෝජනයක් නොමැත.