جافا سكريبت الذيل العودية الأمثل

مرحبا القارئ.

في بعض الأحيان لحل المشكلة ، عليك استخدام Recursion ، الذي له إيجابيات وسلبيات. واجهت مشكلة تجاوز سعة المكدس.
الحد الأقصى لعمق العودية محدد بواسطة محرك JavaScript. يمكنك الاعتماد بدقة على 10000 مكالمة متداخلة ، يسمح بعض المترجمين الفوريين بالمزيد ، لكن بالنسبة لمعظمهم 100000 مكالمة خارج نطاق القدرات. هناك تحسينات تلقائية تساعد على تجنب تجاوز مكدس المكالمة ("تحسين تكرار العودية الذيل") ، لكنها غير مدعومة بعد في كل مكان وتعمل فقط للحالات البسيطة.

مثال على دالة متكررة:
function sum(n) { return n === 0 ? 0 : n + sum(n - 1) } sum(5) // 1 + 2 + 3 + 4 + 5 = 15 sum(100000) // Error: Maximum call stack size exceeded. 


يتيح الرجوع إلى الذيل تحسين المكالمات من قبل المترجم وهو موجود بالفعل في المعيار ES6 ، لكن دعم المتصفح يترك الكثير مما هو مرغوب فيه .

مثال على وظيفة ذيل العودية:
 function sum(number, s = 0){ return number === 0 ? s : sum(number - 1, s + number) } 

ولكن بدون دعم المستعرض ، سنواجه نفس المشكلة - تجاوز سعة مكدس الذاكرة المؤقتة. يمكننا محاولة استخدام جنبا إلى جنب مع الترامبولين .

سنقوم بكتابة ديكور يأخذ وظيفة عودية معدلة (الخطوة التالية) وتنفيذها في حلقة ، دون زيادة عمق المكالمة.
 function trampoline(fn) { return function(...args) { let result = fn.apply(fn.context, args) while (typeof result === 'function') { result = result() } return result } } 


الآن يجب أن تقوم دالة العودية بإرجاع وظيفة سيتم تنفيذها فورًا بواسطة الديكور. بهذه الطريقة ، سنقوم بعمل مجموعة مشروطة من الوظائف وننفذها بدورها. ولكن نظرًا لأننا نرجع وظيفة مجهولة جديدة في كل مرة ، فإن هذا الرمز سيعمل بشكل أبطأ قليلاً.
 function sum(number, s = 0) { return number === 0 ? s : () => sum(number - 1, s + number) } 


نستخدم:
 const trampolineSum = trampoline(sum) trampolineSum(100000) // 5000050000 

هذا هو مقالي الأول. حاولت عدم إعادة سرد المفاهيم وأشرت إلى وجود صلة بالمصادر. هذا الموضوع ليس فريدًا وقد وجد منذ فترة طويلة على مواقع باللغة الإنجليزية ، لكنني لم أجده باللغة الروسية لسوء الحظ.

شكرا لاهتمامكم اتمنى لك يوم لطيف :)

تحديث
الأداء:

# 1 العودية
الكود # 1
 function sum(n) { return n === 0 ? 0 : n + sum(n - 1) } console.time("run") sum(1000) console.timeEnd("run") 


النتيجة رقم 1 من Safari 12.1.2
المدى [تصحيح]: 0.353ms
المدى [Debug]: 0.281 مللي ثانية
[تصحيح] المدى: 0.305ms
[تصحيح] المدى: 0.315ms
المدى [تصحيح]: 0.319ms
المدى [تصحيح]: 0.231ms
المدى [تصحيح]: 0.255ms
المدى [تصحيح]: 0.334ms
المدى [تصحيح]: 0.370ms
المدى [Debug]: 0.274 مللي ثانية
المدى [Debug]: 0.260 مللي ثانية

النتيجة رقم 1: Google Chrome 78.0.3892.0
المدى: 0.118896484375ms
المدى: 0.101806640625ms
المدى: 0.099853515625ms
المدى: 0.10205078125ms
المدى: 0.10302734375ms
المدى: 0.099853515625ms
المدى: 0.106201171875ms
المدى: 0.103759765625ms
المدى: 0.105224609375ms
المدى: 0.262939453125ms
المدى: 0.136962890625ms
المدى: 0.10107421875ms
المدى: 0.10302734375ms


# 2 التكرار
الكود # 2
 function sum(n) { let sum = 0 for (let i = 1; i <= n; i++) { sum += i } return sum } console.time("run") sum(1000) console.timeEnd("run") 


النتيجة رقم 2 من Safari 12.1.2
المدى [تصحيح]: 0.562ms
المدى [تصحيح]: 0.552ms
المدى [تصحيح]: 0.502ms
المدى [تصحيح]: 0.527ms
المدى [تصحيح]: 0.434ms
المدى [تصحيح]: 0.462ms
المدى [تصحيح]: 0.511ms
المدى [تصحيح]: 0.528ms
المدى [تصحيح]: 0.549ms
المدى [تصحيح]: 0.475ms
المدى [تصحيح]: 0.530ms
[تصحيح] المدى: 0.494ms

النتيجة رقم 2 من جوجل كروم 78.0.3892.0
المدى: 0.932861328125ms
المدى: 0.751953125ms
المدى: 0.4580078125ms
المدى: 0.678955078125ms
المدى: 0.424072265625ms
المدى: 0.505859375ms
المدى: 0.563720703125ms
المدى: 0.404052734375ms
المدى: 0.411865234375ms
المدى: 0.634033203125ms
المدى: 0.4169921875ms
المدى: 0.390869140625ms
المدى: 0.464111328125ms


# 3 ذيل العودية والترامبولين
الكود # 3
 function trampoline(fn) { return function(...args) { let result = fn.apply(fn.context, args) while (typeof result === 'function') { result = result() } return result } } function sum(number, s = 0) { return number === 0 ? s : () => sum(number - 1, s + number) } const trampolineSum = trampoline(sum) console.time("run") trampolineSum(1000) console.timeEnd("run") 


النتيجة رقم 3 من Safari 12.1.2
المدى [Debug]: 0.936 مللي ثانية
المدى [Debug]: 0.792 مللي ثانية
المدى [تصحيح]: 0.882ms
المدى [تصحيح]: 0.826ms
المدى [Debug]: 0.968 مللي ثانية
المدى [تصحيح]: 0.818ms
المدى [Debug]: 1.681ms
المدى [تصحيح]: 0.777ms
المدى [تصحيح]: 1.109ms
المدى [تصحيح]: 0.832ms
المدى [تصحيح]: 0.826ms

النتيجة رقم 3 من جوجل كروم 78.0.3892.0
المدى: 0.60888671875ms
المدى: 0.989990234375ms
المدى: 0.567138671875ms
المدى: 0.56005859375ms
المدى: 1.0087890625ms
المدى: 0.5400390625ms
المدى: 0.578125ms
المدى: 0.541015625ms
المدى: 0.557861328125ms
المدى: 1.97607421875ms
المدى: 0.570068359375ms
المدى: 0.593017578125ms
المدى: 0.530029296875ms
المدى: 0.89794921875ms
المدى: 0.590087890625ms


# 4 ذيل الإعادة والترامبولين (عدد كبير)
الكود # 4
 function trampoline(fn) { return function(...args) { let result = fn.apply(fn.context, args) while (typeof result === 'function') { result = result() } return result } } function sum(number, s = 0) { return number === 0 ? s : () => sum(number - 1, s + number) } const trampolineSum = trampoline(sum) console.time("run") trampolineSum(100000) console.timeEnd("run") 


النتيجة رقم 4 من Safari 12.1.2
المدى [تصحيح]: 33.693ms
المدى [Debug]: 24.564ms
المدى [تصحيح]: 25.313ms
المدى [Debug]: 23.262 مللي ثانية
المدى [تصحيح]: 24.848ms
المدى [تصحيح]: 23.909ms
المدى [تصحيح]: 24.248ms
المدى [تصحيح]: 32.416ms
المدى [تصحيح]: 24.090ms
المدى [Debug]: 23.986 مللي ثانية

النتيجة رقم 4 من جوجل كروم 78.0.3892.0
المدى: 40.73681640625ms
المدى: 33.955078125ms
المدى: 40.907958984375ms
المدى: 37.693115234375ms
المدى: 28.929931640625ms
المدى: 30.7548828125ms
المدى: 29.720947265625ms
المدى: 40.8310546875ms
المدى: 31.5830078125ms
المدى: 30.712890625ms
المدى: 30.162841796875ms
المدى: 31.56103515625ms


وفقا للنتائج ، نرى أن لدينا ديكور ، على الرغم من أنه يسمح لنا بتجنب خطأ تجاوز سعة المكدس ، لكنه يعمل بشكل أبطأ من الإصدار التكراري والتكراري. لذلك يجب استخدام هذه الطريقة فقط إذا لم تتمكن من استبدال العودية بالتكرار أو إذا كنت خائفًا من تجاوزات مكدس الذاكرة المؤقتة عند تنفيذ وظيفتك العودية.

Source: https://habr.com/ru/post/ar464915/


All Articles