Exercises – Functions

Part 1

Redo the same exercises from the Rust handbook, but this time use functions wherever it makes sense.

You can use your previous solution and extract functions out of it; or you can start from scratch.

Exersice - Rust Fundamentals
fn main() {
    // 1. Convert temperatures between Fahrenheit and Celsius.
    let celsius1 = 37.0;
    let celsius2 = 0.0;
    let farenheit1 = 32.0;
    let farenheit2 = 104.0;
 
    println!("0.0 C = 32.0 F");
   
    // 2. Generate the nth Fibonacci number.
    let n = 15;
   
    // 3. Print the lyrics to the Christmas carol
    // “The Twelve Days of Christmas,”
    // taking advantage of the repetition in the song.
 
/*
On the first day of Christmas
My true love sent to me
A partridge in a pear tree
 
On the second day of Christmas
My true love sent to me
Two turtle-doves
And a partridge in a pear tree
 
On the third day of Christmas
My true love sent to me
Three French hens
Two turtle-doves
And a partridge in a pear tree
 
On the fourth day of Christmas
My true love sent to me
Four calling birds
Three French hens
Two turtle-doves
And a partridge in a pear tree    
 
On the fifth day of Christmas
My true love sent to me
Five golden rings (five golden rings)
Four calling birds
Three French hens
Two turtle-doves
And a partridge in a pear tree
 
On the sixth day of Christmas
My true love sent to me
Six geese a laying
Five golden rings (five golden rings)
Four calling birds
Three French hens
Two turtle-doves
And a partridge in a pear tree
*/
}

Part 2

Answer the following questions:

  1. Are function calls stored on the Stack or on the Heap? Why is one more beneficial than the other?
  2. What is Memoization and how does it help executing recursion faster?
  3. What is Tail recursion (Tail Call Optimization) and how does it help executing recursion faster?

PART 1

:one: Convert temperatures between Fahrenheit and Celsius (with functions)

fn celsiustofarenheit(c: f64) -> f64 {
    (c * 1.8) + 32.0
}

fn farenheittocelsius(f: f64) -> f64 {
    (f - 32.0) * 5.0 / 9.0
}

fn main() {
    let celsius1 = 37.0;
    let celsius2 = 0.0;
    let farenheit1 = 32.0;
    let farenheit2 = 104.0;

    // Celsius => Farenheit: (°C × 1.8) + 32 = °F
    println!("{} C = {:.1} F", celsius1, celsiustofarenheit(celsius1));     // Prints 37 C = 98.6 F
    println!("{} C = {:.1} F", celsius2, celsiustofarenheit(celsius2));     // Prints 0 C = 32.0 F

    // Farenheit => Celsius: (°F − 32) × 5.0 / 9.0 = °C
    println!("{} F = {:.1} C", farenheit1, farenheittocelsius(farenheit1)); // Prints 32 F = 0.0 C
    println!("{} F = {:.1} C", farenheit2, farenheittocelsius(farenheit2)); // Prints 104 F = 40.0 C
}

Output

37 C = 98.6 F
0 C = 32.0 F
32 F = 0.0 C
104 F = 40.0 C

:two: Generate the nth Fibonacci number (with functions)

I struggled a little bit with mutable values an how they would be passed as arguments, but made the user function as simple as possible, taking just 1 argument.
From the user facing findfibonacci function, it initializes a result variable to zero and sends it to the fibonacciround function, which is recursive - taking 3 sets of conditions.
The arguments taken in this function are abstracted away from the user, though the findfibonacci function where n_minus_1, n_minus_2 were equal to 0.

fn main() {
    let n = 10;     // This is our input
    println!("The {}th Fibonacci number is {}", n, findfibonacci(n));
}

fn findfibonacci(n: i32) -> i32 {
    let result: i32 = 0;
    fibonacciround(n, 0, 0, result)
}

fn fibonacciround(n: i32, mut n_minus_1: i32, mut n_minus_2: i32, mut result: i32) -> i32 {
    if n == 0 {
        result
    } else if n_minus_1 == 0 && n_minus_2 == 0 {
        fibonacciround(n - 1, 1, 0, result)
    } else {
        result = n_minus_1 + n_minus_2;
        n_minus_2 = n_minus_1;
        n_minus_1 = result;
        fibonacciround(n - 1, n_minus_1, n_minus_2, result)
    }
}

Output

The 10th Fibonacci number is 55

:three: Print the lyrics to the Christmas carol “The Twelve Days of Christmas,” taking advantage of the repetition in the song (with functions)

fn main() {
    singsong();
}

fn singsong() {
    let start = "On the ";
    let start_2 = " day of Christmas\nMy true love gave to me";
    let end = "And a partridge in a pear tree.";
    let mut day: &str;
    let song: [&str; 6] = ["A partridge in a pear tree.", "Two turtle doves,", "Three French hens,", "Four calling birds,", "Five golden rings,", "Six geese a-laying,"];

    for gift in 1..=6 {
        match gift {
            1 => { day = "First"; }
            2 => { day = "Second"; }
            3 => { day = "Third"; }
            4 => { day = "Fourth"; }
            5 => { day = "Fifth"; }
            _ => { day = "Sixth"; }
        }

        println!("{}{}{}", start, day, start_2);

        if gift == 1 {
            println!("{}", song[0]);
        } else {
            let mut j = gift;
            loop {
                if j == 1 {
                    break;
                }
                println!("{}", song[j - 1]);
                j = j - 1;
            }
            println!("{}", end);
        }
        println!()
    }

Output

On the First day of Christmas
My true love gave to me
A partridge in a pear tree.

On the Second day of Christmas
My true love gave to me
Two turtle doves,
And a partridge in a pear tree.

On the Third day of Christmas
My true love gave to me
Three French hens,
Two turtle doves,
And a partridge in a pear tree.

On the Fourth day of Christmas
My true love gave to me
Four calling birds,
Three French hens,
Two turtle doves,
And a partridge in a pear tree.

On the Fifth day of Christmas
My true love gave to me
Five golden rings,
Four calling birds,
Three French hens,
Two turtle doves,
And a partridge in a pear tree.

On the Sixth day of Christmas
My true love gave to me
Six geese a-laying,
Five golden rings,
Four calling birds,
Three French hens,
Two turtle doves,
And a partridge in a pear tree.

PART 2

:one: Are function calls stored on the Stack or on the Heap? Why is one more beneficial than the other?

Function calls are stored on the stack, which makes them particularly quick and efficient.
There is significant benefits to having function calls being stored locally on the stack as it allows the machine to process functions as fast as possible.

If functions were stored in the heap, they would be available at all times throughout the programs lifecycle, where it may potentially be used very infrequently. Doing this would chew through unnecessary memory and slow the program down significantly.

As functions are placed on and off the stack, the machine can pop the function and all of its arguments onto the stack only when it’s needed, and then take it off the stack once it has been used, de-allocating the memory it was using.

The result is a much less resource intensive program, which will typically run faster.

:two: What is Memoization and how does it help executing recursion faster?

Memoization is the process of eliminating some repetitive task by storing the results of the task in cache the first time the action is performed, and storing it in cache. Then when you need to perform task again, instead of re-doing the work, you can simply pull from the cache where the result was previously stored.

These repetitive tasks occur regularly in recursive functions - where the same task is performed many times with the same input. If we were to utilize memoization, we could rewrite the function to store the result from the first iteration, and then pull in subsequent rounds - IF it is logical and safe to do so.

When retrieving the 6th Fibonacci number using a recursive function, the 2nd Fibonacci number is called 5 times… This could be memoized, and the work wouldn’t have to …

Whilst memoization is great in performance respects, it can add a decent amount of complexity to what could be a simple function, so memoization should mainly be used when there are many repetitive iterations, or if speed is absolutely vital.

:three: What is Tail recursion (Tail Call Optimization) and how does it help executing recursion faster?

Tail recursion exists in recursive functions where the last thing that is called is the recursive function itself. This generally means that the function will begin with a qualifier where if the iteration does not meet a condition, the recursion will break - and the tail recursion will not be called.

Tail recursion is good practice, as it can be optimized by the compiler.
For non tail-recursive functions, the stack depth will typically increase more, as the information from the previous iteration has not yet been fully popped off the stack, and continues to take resources for following iterations. The resources consumed will increase (at least) linearly until all iterations are completed and taken off the stack.

For tail-recursive functions, the recursive call is the last statement, therefore there are no more resources needing to be maintained by the function, and the entirety of this iteration can be popped off the stack before entering the next iteration.

2 Likes

:warning: Exercise Spoiler Alert

Code
// 1. Convert temperatures between Fahrenheit and Celsius.
fn temperatures(_temp_type: &str, _temp_value: u16) {
    match _temp_type {
        "F" => {
            let _to_celsius: u16 = (_temp_value - 32) * 5 / 9;
            println!("Fahrenheit:{} => Celsius:{}", _temp_value, _to_celsius);
        }
        "C" => {
            // 15 °C = 15 × 9/5 + 32 = 59 °F
            let _to_fahrenheit: u16 = _temp_value * 9 / 5 + 32;
            println!("Celsius:{} =>  Fahrenheit:{}", _temp_value, _to_fahrenheit);
        }
        _ => {
            println!("Incorrect temperature type");
        }
    }
}

// 2. Generate the nth Fibonacci number.
fn fibonacci(n: u16) {
    let mut num1: u16 = 0;
    let mut num2: u16 = 1;
    let mut sum: u16;
    let mut i: u16 = 0;
    loop {
        sum = num1 + num2;
        num1 = num2;
        num2 = sum;
        println!("fibonnaci: {}", sum); // statements inside the loop
        i = i + 1; // increment
        if i > n {
            // terminal condition
            break;
        }
    }
}

// 3. Print the lyrics to the Christmas carol
// “The Twelve Days of Christmas,”
// taking advantage of the repetition in the song.
fn christmas_carol() {
    let ordinal = [
        "first", "second", "third", "fourth", "Fifth", "Sixth", "Seventh", "Eighth", "Ninth",
        "Tenth", "Eleventh", "Twelfth",
    ];
    let verses = [
        "A partridge in a pear tree",
        "Two turtle doves, and",
        "Three french hens",
        "Four calling birds",
        "Five golden rings",
        "Six geese a-laying",
        "Seven swans a-swimming",
        "Eight maids a-milking",
        "Nine ladies dancing",
        "Ten lords a-leaping",
        "Eleven pipers piping",
        "Twelve drummers drumming",
    ];

    for i in 0..ordinal.len() {
        println!(
            "On the {} day of Christmas, my true love sent to me",
            ordinal[i]
        );
        if i == 0 {
            println!("{}", verses[0]);
        } else {
            let mut j = i;
            loop {
                println!("{}", verses[j]);
                if j == 0 {
                    break;
                }
                j = j - 1;
            }
        }
    }
}

fn main() {
    temperatures("F", 50);
    temperatures("C", 10);
    temperatures("D", 10);
    fibonacci(10);
    christmas_carol();
}

Part 1

fn main() {
    // 1. Convert temperatures between Fahrenheit and Celsius.
    fn celsius_to_fahrenheit(celsius: f32) -> f32{
        celsius * 1.8 + 32.0
    }

    fn fahrenheit_to_celsius(fahrenheit: f32) -> f32{
        (fahrenheit - 32.0) /1.8    
}

    let celsius1 = 37.0;
    let celsius2 = 0.0;
    let fahrenheit1 = 32.0;
    let fahrenheit2 = 104.0;

    println!("{} C = {:.1} F", celsius1, celsius_to_fahrenheit(celsius1));
    println!("{} C = {} F", celsius2, celsius_to_fahrenheit(celsius2));
    println!("{} C = {} F", fahrenheit1, fahrenheit_to_celsius(fahrenheit1));
    println!("{} C = {} F", fahrenheit2, fahrenheit_to_celsius(fahrenheit2)); 

    // 2. Generate the nth Fibonacci number.

    fn fibonacci(n: u32) -> u32{
        if n == 0{
            0
        } else if (n == 1) {
            1
        } else {
            fibonacci(n-1) + fibonacci(n-2)
        }
    }

    let n = 15;
    println!("{}th Fibonacci number: {}", n, fibonacci(n));

    // 3. Print the lyrics to the Christmas carol
    // “The Twelve Days of Christmas,”
    // taking advantage of the repetition in the song.    
    let days = ["first", "second", "third", "fourth", "fifth", "sixth"];
    fn print_things(i: usize){
        let two = "Two turtle-doves";
        let three = "Three French hens";
        let four = "Four calling birds";
        let five = "Five golden rings (five golden rings)";
        let six = "Six geese a laying";
  
        let numbers = [two, three, four, five, six];        
        for j in (2..=i).rev(){
            println!("{}", numbers[j-2]);
        }
    }

    let mut i = 1;
    while i <= 6 {
        println!{"On the {} day of Christmas", days[i-1] };
        println!{"My true love sent to me" };
        print_things(i);

        if i == 1 {
            print!("A ")
        } else {
            print!("And a ")
        }

        println!{"partridge in a pear tree" };
        println!{"" };
        i = i + 1;
    }
}

Part 2

  1. Are function calls stored on the Stack or on the Heap? Why is one more beneficial than the other?
    They are stored on the stack, the advantage is that local variables are automatically deleted when the functions exists.

  2. What is Memoization and how does it help executing recursion faster?
    With memoization, results of function calls can be stored in the cache, so when we call a recursive function, we don’t have to call the same functions again and can retrieve results from the cache, making the execution faster.

  3. What is Tail recursion (Tail Call Optimization) and how does it help executing recursion faster?
    Recursive function is optimized to use only one stack frame for the whole execution instead one stack frame per function call.

1 Like

Part 1

Exercise - Functions Code

1. Convert temperatures between Fahrenheit and Celsius.
fn celsius_to_farenheit(celsius: f64) -> f64 {
    (celsius * 1.8) + 32.0
}

fn farenheit_to_celsius(farenheit: f64) -> f64 {
    (farenheit - 32.0) * 5.0 / 9.0
}

fn temperature(temp_value: f64, temp_type: &str) {
    if temp_type == "C" {
        let celsius = temp_value;
        println!("{} C = {:.1} F", celsius, celsius_to_farenheit(celsius));
    } else if temp_type == "F" {
        let farenheit = temp_value;
        println!("{:.1} F = {} C", farenheit, farenheit_to_celsius(farenheit));
    } else {
        println!("Unknown temperature type.");
    }
}

fn main() {
    // Convert temperatures between Fahrenheit and Celsius.
    temperature(37.0, "C");
    temperature(0.0, "C");
    temperature(32.0, "F");
    temperature(104.0, "F");
    temperature(104.0, "E");
}
2. Generate the nth Fibonacci number.
fn fib(n: i32) -> i32 {
    let mut prev = 0;
    let mut curr = 1;
    let _i = 1;
    
    for _i in 2..=n {
        let res = prev + curr;
        prev = curr;
        curr = res;
    }
    curr
}

fn print_fib_number(n: i32) {
    if n == 0 {
        println!("The {} Fibonacci number: {}", n, fib(n));
    } else if n == 1 {
        println!("The {}st Fibonacci number: {}", n, fib(n));
    } else {
        println!("The {}th Fibonacci number: {}", n, fib(n));    
    }
    println!();
}

fn main() {
    // Generate the nth Fibonacci number.
    print_fib_number(1);
    print_fib_number(0);
    print_fib_number(10);
    print_fib_number(15);
}
3. Print the lyrics to the Christmas carol
fn print_xmas_carol() {
    let days = [
        "first", 
        "second", 
        "third", 
        "fourth", 
        "fifth", 
        "sixth"
    ];
    let lyrics = [
        "And a partridge in a pear tree",
        "Two turtledoves",
        "Three French hens",
        "Four calling birds",
        "Five gold rings (five golden rings)",
        "Six geese a-laying",
    ];

    for i in 0..days.len() {
        println!(
            "On the {} day of Christmas, \nmy true love sent to me", days[i]
        );
        if i == 0 {
            println!("A partridge in a pear tree");
        } else {
            let mut j = i;
            loop {
                println!("{}", lyrics[j]);
                if j == 0 {
                    break;
                }
                j -= 1;
            }
        }
        println!();
    }
}

fn main() {
    // Print the lyrics to the Christmas carol 
    // “The Twelve Days of Christmas,” 
    // taking advantage of the repetition in the song.
    print_xmas_carol();
}

Part 2

Exercise - Research Functions

Are function calls stored on the Stack or on the Heap?

Function calls are stored on the Stack.

Why is one more beneficial that the other?

It’s more beneficial because it’s faster to compute since stored variables are automatically destroyed when returned from a function call.

What is Memoization and how does it help executing recursion faster?

Memoization is an optimization technique that stores the function outputs in the cache so they can be used later.

It helps recursion faster by making the function check if the required computation is already held in cache before running the function computation again.

What is Tail recursion (Tail Call Optimization) and how does it help recursion faster?

Tail Call Optimization makes it possible to call a function from another function without growing the call stack.

A Tail recursive function is a function that removes its stack frame from the call stack after calling itself.

It helps recursion faster by only using 1 stack frame for the function execution.

1 Like
pub fn run() {
    // 1. Convert temperatures between Fahrenheit and Celsius.
    let celsius1 = 37.0;
    let celsius2 = 0.0;
    let farenheit1 = 32.0;
    let farenheit2 = 104.0;

    println!("\nGenerate Celsius and Fahreneit\n\n0.0 C = 32.0 F\n--------------");

    fn get_fahr(celc_degree: f32) -> f32 {
        (celc_degree * 1.8) + 32.0
    }
    fn get_cels(fahr_degree: f64) -> f64 {
        (fahr_degree - 32.0) / 1.8
    }

    println!("{}⁰C\t= {}⁰F", celsius1, get_fahr(celsius1));
    println!("{}⁰C\t= {}⁰F", celsius2, get_fahr(celsius2));
    println!("{}⁰F\t= {}⁰C", farenheit1, get_cels(farenheit1));
    println!("{}⁰F\t= {}⁰C", farenheit2, get_cels(farenheit2));

    println!("--------------\n\nGenerate the nth Fibonacci number\n");

    fn fib_rcsv(n: u64) -> u64 {
        if n <= 0 { 0 }
        else if n == 1 { 1 }
        else { fib_rcsv(n - 1) + fib_rcsv(n - 2) }
    }

    let n = 15;
    let result = fib_rcsv(n);
    print!("{}nth value of Fib.seq. is {}\n", n, result);


    println!("--------------\n\nGenerate Christmas Carol Song with function\n");

    // 3. Print the lyrics to the Christmas carol with functions
    // “The Twelve Days of Christmas,”
    // taking advantage of the repetition in the song.



    fn generate_christmas_song(){

        let days = ["first", "second", "third", "fourth", "fifth", "sixth"];
        let gifts = [
            "A partridge in a pear tree",
            "Two turtle-doves",
            "Three French hens",
            "Four calling birds",
            "Five golden rings",
            "Six geese a laying",
        ];

        println!("\nThe {} Days of Christmas\n", days.len());
        println!("On the {} day of Christmas", days[0]);
        println!("My true love sent to me\n{}", gifts[0]);

        for _i in 1..=(days.len() - 1) {
            println!("\nOn the {} day of Christmas\nMy true love sent to me", days[_i]);
            let mut _g = _i;
            while _g >= 1 {
                println!("{}", gifts[_g]);
                _g = _g - 1;
            }

            println!("And a partridge in a pear tree");
        }
    }

    generate_christmas_song();
}
  1. Are function calls stored on the Stack or on the Heap? Why is one more beneficial than the other?

In Rust, function calls and their associated local variables are stored on the stack. Rust follows a “stack discipline” for function calls, which means that when a function is called, its local variables and other related data are pushed onto the stack, and when the function returns, this data is removed from the stack.

Both the stack and the heap have their advantages and are for different use cases in programming. But the choice between using the stack and the heap depends on the specific requirements of the program:

Stack must be chosen when needed fast and automatic memory management for short-lived local variables and function calls.
The heap is better when needed dynamic memory allocation, require data to persist beyond the scope of a function, or when working with large, complex data structures.

In Rust, the ownership and borrowing system enforces strict rules that ensure memory safety and prevent issues, making it easier to manage both stack-allocated and heap-allocated data. By following these rules, Rust provides a balance between the benefits of both stack and heap memory allocation.

  1. What is Memoization and how does it help executing recursion faster?

Memoization is a technique used to optimize the performance of recursive functions by caching the results of expensive function calls and reusing them when the same inputs occur again. It helps reduce redundant calculations and can dramatically improve the execution speed of recursive algorithms.

When a recursive function is called multiple times with the same arguments, the function may end up recalculating the same results over and over again. Memoization stores these results in a data structure (often a hash map or an array) so that the function can look up the cached result instead of recomputing it.

Example:

fn fibonacci(n: u64) -> u64 {
    fn fibonacci_tail(n: u64, a: u64, b: u64) -> u64 {
        if n == 0 {
            a
        } else {
            // a is the previous fibonacci number
            // b is the current
            fibonacci_tail(n - 1, b, a + b)
        }
    }

    fibonacci_tail(n, 0, 1)
}

fn main() {
    let n = 15;
    let result = fibonacci(n);
    println!("The {}th Fibonacci number is {}", n, result);
}

In the regular recursive implementation of the Fibonacci function, the same Fibonacci numbers are recalculated multiple times, leading to redundant calculations and a high time complexity.

In the memoized version, we introduce an additional memo parameter to store the results of previously calculated Fibonacci numbers. Before performing the recursive calls, the function first checks if the result for the given n is already stored in the memo object.

In Rust, you can achieve memoization by using data structures like HashMap or Vec to cache the results of function calls. However, Rust doesn’t have automatic memoization like some other languages, so you need to explicitly implement memoization in your code if you want to use it.

  1. What is Tail recursion (Tail Call Optimization) and how does it help executing recursion faster?

Tail recursion, also known as Tail Call Optimization (TCO), is a technique occurs when a function makes its recursive call as its last operation before returning a value. In other words, the recursive call is in the “tail position” of the function.

In a language or compiler that supports Tail Call Optimization, when a function is called recursively in a tail position, the current function’s stack frame can be reused for the recursive call instead of creating a new stack frame. This process essentially eliminates the need for additional stack space for each recursive call, making the recursion more memory-efficient and avoiding the risk of stack overflow for deep recursive calls.

Example without Tail Call Optimization:

fn fact(n: u64) -> u64 {
  if n <= 1 {
    1
  } else {
    n * factorial(n - 1)
  }
}

In this recursive factorial function, the multiplication operation (n * fact(n - 1)) is performed after the recursive call to fact(n - 1). As a result, each recursive call needs to be stored on the stack, leading to a potentially large stack depth for large values of n.

With Tail Call Optimization:

fn fact_tail(n: u64, accumulator: u64) -> u64 {
  if n <= 1 {
    accumulator
  } else {
    fact_tail(n - 1, n * accumulator)
  }
}

fn fact(n: u64) -> u64 {
  fact_tail(n, 1)
}

In the tail-optimized version of the factorial function, the recursive call to fact_tail(n - 1, n * accumulator) is performed as the last operation before returning a value. The intermediate results are accumulated in the accumulator parameter. This allows the compiler to optimize the recursion by reusing the same stack frame for each recursive call, without the need to keep a record of the previous function call.

Tail Call Optimization significantly improves the efficiency of recursive functions, making them run as fast as their iterative counterparts while maintaining the elegance and clarity of the recursive code. Rust does not have automatic Tail Call Optimization. Therefore, when dealing with recursive functions in Rust, it’s essential to be mindful of potential stack overflows for deep recursion and consider alternative iterative approaches or explicit tail-call optimizations using loops.

1 Like

The main function:

fn main() {
    // Re-write the iterations exercises, but using functions

    // Temperature conversions:
    let celsius1 = 37.0;
    let celsius2 = 0.0;
    let farenheit1 = 32.0;
    let farenheit2 = 104.0;

    for celsius in [celsius1, celsius2] {
        println!("{} C = {:.1} F", celsius, far_conv(celsius));
    };

    for farenheit in [farenheit1, farenheit2] {
        println!("{} F = {:.1} C", farenheit, cel_conv(farenheit));
    };


    // The Nth fibonacci number
    let n = 15;
    println!("The {}nth fib (recursive): {}", n, fib_recursive(n));
    println!("The {}nth fib: {}", n, fibonacci(n));

    // The Chistmas Carol
    print_xmas_carol();

}

The Temperature conversion external functions:

// 1. Convert temperatures between Fahrenheit and Celsius
fn cel_conv(farenheit: f64) -> f64 {
    (farenheit - 32.0) / 1.8
}

fn far_conv(celsius: f64) -> f64 {
    32.0 + 1.8 * celsius
}

Output:

37 C = 98.6 F
0 C = 32.0 F
32 F = 0.0 C
104 F = 40.0 C
The 15nth fib (recursive): 610
The 15nth fib: 610

The fibonacci functions:

// 2. Generate the nth Fibonacci number
fn fib_recursive(n: u32) ->  u32 {
    // Recursive function to get the Nth fibonacci number
    // Less efficient

    if n < 2 {
        n
    } else {
        fib_recursive(n - 2) + fib_recursive(n - 1)
    }
}

fn fibonacci(n: u32) ->  u32 {
    // The straight solution
    
    let mut prev: u32 = 0;
    let mut next: u32 = 1;
    let mut fib: u32 = if n == 0 {0} else {1};
    for _ in 1..n {
        fib = prev + next;
        prev = next;
        next = fib;
    }

    fib
}

Output:

The 15nth fib (recursive): 610
The 15nth fib: 610

The Christmas Carol

//  3. Print the lyrics to the Christmas carol 
//  "The Twelve Days of Christmas,"
//  taking advantage of the repetition in the song
fn print_xmas_carol() {

    let days: [&str; 12] = [
        "first", "second", "third", "fourth", "fifth", "sixth", 
        "seventh", "eigth", "ninth", "tenth", "eleventh", "twelfth"
    ];

    let gifts = [
        "partridge in a pear tree!",
        "Two turtle doves",
        "Three french hens",
        "Four calling birds",
        "Five golden rings!",
        "Six geese a layin'",
        "Seven swans a swimmin'",
        "Eight maids milkin'",
        "Nine ladies dancin'",
        "Ten lords a leapin'",
        "Eleven pipers pipin'",
        "Twelve drummers drummin'"
    ];

    let mut prefix_first_gift;

    for verse in 1..=gifts.len() {
        println!("");
        println!("On the {} day of Christmas", days[verse - 1]);
        println!("My true love sent to me");

        let mut j = verse;
        while j > 1 {
            j -= 1;
            println!("{}", gifts[j]);
        };

        prefix_first_gift = if verse == 1 {"A"} else {"And a"};
        println!("{} {}", prefix_first_gift, gifts[0]);
    };
}

Output:


On the first day of Christmas 
My true love sent to me       
A partridge in a pear tree!   

On the second day of Christmas
My true love sent to me       
Two turtle doves
And a partridge in a pear tree!

On the third day of Christmas
My true love sent to me
Three french hens
Two turtle doves
And a partridge in a pear tree!

On the fourth day of Christmas
My true love sent to me
Four calling birds
Three french hens
Two turtle doves
And a partridge in a pear tree!

On the fifth day of Christmas
My true love sent to me
Five golden rings!
Four calling birds
Three french hens
Two turtle doves
And a partridge in a pear tree!

On the sixth day of Christmas
My true love sent to me
Six geese a layin'
Five golden rings!
Four calling birds
Three french hens
Two turtle doves
And a partridge in a pear tree!

On the seventh day of Christmas
My true love sent to me
Seven swans a swimmin'
Six geese a layin'
Five golden rings!
Four calling birds
Three french hens
Two turtle doves
And a partridge in a pear tree!

On the eigth day of Christmas
My true love sent to me
Eight maids milkin'
Seven swans a swimmin'
Six geese a layin'
Five golden rings!
Four calling birds
Three french hens
Two turtle doves
And a partridge in a pear tree!

On the ninth day of Christmas
My true love sent to me
Nine ladies dancin'
Eight maids milkin'
Seven swans a swimmin'
Six geese a layin'
Five golden rings!
Four calling birds
Three french hens
Two turtle doves
And a partridge in a pear tree!

On the tenth day of Christmas
My true love sent to me
Ten lords a leapin'
Nine ladies dancin'
Eight maids milkin'
Seven swans a swimmin'
Six geese a layin'
Five golden rings!
Four calling birds
Three french hens
Two turtle doves
And a partridge in a pear tree!

On the eleventh day of Christmas
My true love sent to me
Eleven pipers pipin'
Ten lords a leapin'
Nine ladies dancin'
Eight maids milkin'
Seven swans a swimmin'
Six geese a layin'
Five golden rings!
Four calling birds
Three french hens
Two turtle doves
And a partridge in a pear tree!

On the twelfth day of Christmas
My true love sent to me
Twelve drummers drummin'
Eleven pipers pipin'
Ten lords a leapin'
Nine ladies dancin'
Eight maids milkin'
Seven swans a swimmin'
Six geese a layin'
Five golden rings!
Four calling birds
Three french hens
Two turtle doves
And a partridge in a pear tree!

The function calls and the references are stored in the stack memory. The reason why function calls are stored in the stack is to keep track of the order in which functions are called and to ensure that each function call returns to the correct location in the program. When a function is called, its return address is pushed onto the stack, along with any arguments and local variables that it needs. When the function returns, its return value is placed in a register or on the stack, and the stack pointer is adjusted to remove the stack frame.

The use of a stack for function calls allows for recursion , which is when a function calls itself. Each recursive call creates a new stack frame, which allows the function to maintain its state across multiple calls. The use of a stack also allows for interrupt handling , which is when an interrupt service routine (ISR) interrupts the normal flow of a program to handle an event such as an input/output operation or a timer expiration.

Storing function calls in the stack has several benefits over storing them in the heap. First, accessing data on the stack is faster than accessing data on the heap because it involves fewer memory operations. Second, because the stack is organized as a LIFO data structure, it allows for efficient memory management and reduces fragmentation. Finally, because each function call creates a new stack frame, it allows for recursion and interrupt handling.

Memoization is an optimization technique used in computer science to speed up the execution of recursive or computationally expensive functions by caching the results of function calls and returning the cached results when the same inputs occur again 123. A memoized function will immediately output a pre-computed value if it’s given inputs that it’s seen before 2. Memoization is a type of caching, distinct from other forms of caching such as buffering and page replacement 1. It is a run-time optimization rather than a compile-time optimization, which means that it is implemented during program execution rather than during compilation 1. Memoization is also known as tabling in some logic programming languages 1.

This is a type recursion, where the compiler optimizes the recursive function and transforms behind the scenes into a regular loop, so to save space memory and computation, and specially avoid the stackoverflow problem. The main benefit is that programmers can still write code with recursive techniques which are more human readable without sacrificing computer capabilities.