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.

1 Like

: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