ساخت الگوریتم بازگشتی در سوئیفت — از صفر تا صد

۲۰ بازدید
آخرین به‌روزرسانی: ۳۰ آذر ۱۳۹۸
زمان مطالعه: ۳ دقیقه

منظور از الگوریتم‌های بازگشتی به طور خلاصه یک تکنیک کدنویسی است که مجموعه نامعینی از ارجاع‌ها را به خود تابع بازگشت می‌دهد. در این مقاله به بررسی مفهوم «بازگشت» (Recursion) پرداختi و شیوه ساخت الگوریتم بازگشتی در سوئیفت را مورد بررسی قرار می‌دهیم.

طرز کار الگوریتم بازگشتی در سوئیفت

برای درک بهتر بازگشت لازم است آن را با برنامه‌نویسی سنتی شیءگرا مقایسه کنیم. در اغلب راه‌حل‌ها، کد از اشیای متفاوتی تشکیل می‌یابد که در اغلب موارد با هم قابل تعویض هستند. بدین ترتیب برنامه‌نویس به اشیای مختلف (ماند کلاس‌ها) ارجاع می‌دهد تا یک مدل بسازد. با این حال تکنیک بازگشتی مدلی با یک شیء می‌سازد که به خودش ارجاع می‌دهد:

 الگوریتم بازگشتی در سوئیفت

رویکرد سنتی – ارجاع اشیا به اشیای دیگر

 الگوریتم بازگشتی در سوئیفت

رویکرد بازگشتی – ارجاع شیء به خودش

CLASS یا STRUCT

چند روش برای اجرای بازگشت در سوئیفت وجود دارد. اگر به زبان‌هایی مانند C عادت دارید، باید بدانید که شیو مدیریت انواع از سوی سوئیفت متفاوت است. بسیاری از اشیای سوئیفت مانند protocol-ها، struct-ها و enum-ها به عنوان اعضای درجه یک زبان محسوب می‌شوند. بدین ترتیب اغلب انواع عملیات که برای کلاس تدارک دیده شده‌اند را می‌توان با یک struct جایگزین کرد:

//simple struct example - methods & properties
struct Car {
    
    var color: String
    var make: String
        
    init(color: String, make: String) {        
        self.color = color
        self.make = make
    }
    
    func buildCar() {
        print("a \(color) \(make) has been built..")
    }   
}

Struct-های سوئیفت کامپوننت‌های سبکی هستند که به عنوان انواع مقداری (By Value) عمل می‌کنند. به طور عکس، کلاس‌ها در سوئیفت به صورت ارجاعی (By Reference) عمل می‌کنند. از آنجا که وهله‌های Struct کپی می‌شوند و مورد ارجاع قرار نمی‌گیرند، نمی‌توان از آن‌ها برای ساخت مدل‌های بازگشتی استفاده کرد:

//simple usable class
class LLNode<T> {  
    var key: T?
    var previous: LLNode?
    var next: LLNode?
}
//gives compilation error
struct Tree<T> {    
    var key: T?
    var left: Tree?
    var right: Tree?
}

تابع‌ها

بازگشت را می‌توان در دنباله مشهور فیبوناچی نیز مشاهده کرد. ایده آن ساخت یک دنباله عددی است که از جمع کردن دو عضو قبلی دنباله ساخته می‌شود. در ادامه تکنیک‌های سنتی و بازگشتی را با هم مقایسه می‌کنیم:

//fibonacci sequence - traditional approach
extension Int {
func fibNormal() -> Array<Int>? {
                
           //check trivial condition
           guard self > 2 else {
               return nil
           }
//initialize the sequence
           var sequence: Array<Int> = [0, 1]                
           var i: Int = sequence.count
while i != self {            
               let results: Int = sequence[i - 1] + sequence[i - 2]
               sequence.append(results)            
               i += 1
           }
return sequence        
     }
}
//execute sequence
let positions: Int = 4
let results: Array<Int>? = positions.fibNormal()

تکنیک بازگشتی به صورت زیر است:

//fibonacci sequence - recursive approach
extension Int {
   
     mutating func fibRecursive(_ sequence: Array<Int> = [0, 1]) -> Array<Int>? {
        
        var final = Array<Int>()
                
        //mutated copy
        var output = sequence
//check trivial condition
        guard self > 2 else {
            return nil
        }
let i: Int = output.count
        
        
        //set base condition
        if i == self {
            return output
        }
                
        let results: Int = output[i - 1] + output[i - 2]
        output.append(results)        
        
        //set iteration
        final = self.fibRecursive(output)!        
        
        return final       
    }
}
//execute sequence
var positions: Int = 4
let results: Array<Int>? = positions.fibRecursive()

به تفاوت‌های تابع‌ها توجه کنید. در نگاه نخست، می‌بینیم که fibRecursive از یک حالت مبنا استفاده می‌کند و سپس خودش را فرا می‌خواند. به طور عکس fibNormal منطق کنترلی را در یک حلقه while نگه می‌دارد. چنان که می‌بینید منطق بازگشتی در اغلب موارد موجب افزایش پیچیدگی می‌شود، چون کل یک کلاس، تابع یا متد در اغلب موارد به عنوان یک ساختار کنترلی مورد استفاده قرار می‌گیرد.

کلاس‌ها

با این که بازگشت در اغلب موارد موجب افزایش پیچیدگی می‌شود، اما در این بخش الگوریتم «جستجوی عمق-اول» (depth-first search) را بررسی می‌کنیم. چنان که می‌دانید این کد همراه با یک «پشته فراخوانی» (Call Stack) برای پیمایش یک «درخت جستجوی دودویی» (binary search tree) مورد استفاده قرار می‌گیرد. با این که می‌توان این الگوریتم را طوری بازنویسی کرد که از تکنیک تکرار بهره بگیرد، اما الگوریتم بازگشتی یک راه‌حل بهینه ارائه می‌کند:

   //recursive depth-first search 
    
    func traverse() {   
    
        guard self.key != nil else {
            return
        }
        
        //process left side
        if self.left != nil {
            left?.traverse()
        }
        
       print("...node \(self.key!) visited..")
    //process the right side
       if self.right != nil {
            right?.traverse()
        }
        
    }

Enum-ها

Enum-ها نیز مانند بسیاری از اشیای درجه اول سوئیفت عمل می‌کنند. همان طور که Enum-ها در زبان‌های دیگر استفاده می‌شوند، در سوئیفت نیز الگوریتم نوع enum برای مدل‌سازی رفتار استفاده می‌شود. این مدل به مدیریت حالت‌های خاص کمک می‌کند. enum یک نوع بازگشتی نامیده می‌شود، زیرا شامل مقادیری است که از نوع Algorithm نیز هستند:

//recursive enumeration
indirect enum Algorithm<T> {
    
    case Empty
    case Elements(Array<T>)
    case InsertionSort(Algorithm<T>)
    case BubbleSort(Algorithm<T>)
    case SelectionSort(Algorithm<T>)    
}

یکی از اهداف Enum-ها بهبود خوانایی کد است. در این مورد می‌توانیم از Algorithm برای ارائه پشتیبانی با نوع امن برای یک پیاده‌سازی استفاده کنیم و می‌توانیم آن را به سهولت برای پشتیبانی از سناریوهای دیگر نیز بسط دهیم:

//build an algorithm model
let list = Algorithm.Elements([8, 2, 10, 9, 7, 5])
let model = Algorithm.InsertionSort(list)
...

بدین ترتیب به پایان این مقاله می‌رسیم.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
swift-algorithms-data-structures

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *