Practical Programming David Bouchet
Practical Work #7

Rust: Miscellaneous

Submission

Due Date

By Friday 19 April 2019 23:42

Directory Hierarchy

Create your git repository (replace john.smith by your own login).

$ git clone git@git.cri.epita.net:p/2022-s4-tp/tp07-john.smith

It must contain the following files and directories:

Do not submit any executable or binary files. You have to delete all the target directories.

The AUTHORS file must contain the following information.

AUTHORS
First Name
Family Name
Login
Email Address

The last character of your AUTHORS file must be a newline character.

For instance:

AUTHORS
$ cat AUTHORS
John
Smith
john.smith
john.smith@epita.fr
$ # Command prompt ready for the next command...

Each time you connect to one of the school's computers, you have to set the default configuration of Rust's tools.

$ rustup default stable

Be careful, if you do not follow all the given instructions, no point will be given to your answers.

Chaining Iterators

Introduction

The purpose of this section is to familiarize yourself with iterators. You will learn how to chain iterator generators, iterator adapters and iterator consumers in order to reduce the number of instructions and to avoid for loops.

For instance, we can add up the numbers of an array in this way:

RUN
fn main()
{
    let a = [ 1, 2, 4, 8 ];
    let s = sum(&a);
    dbg!(s);
}

fn sum(a: &[i8]) -> i8
{
    let mut sum = 0;

    for i in 0..a.len()
    {
        sum += a[i];
    }

    sum
}

Or we can also do it in this way.

RUN
fn main()
{
    let a = [ 1, 2, 4, 8 ];
    let s = sum(&a);
    dbg!(s);
}

fn sum(a: &[i8]) -> i8
{
    a.iter().sum()
}

These two different programs have the same output:

[src/main.rs:5] s = 15

The second sum() function is made up of one instruction only.

Chaining iterators does not have any runtime cost, which actually means the second program is as fast as the first one.

But chaining iterators is not always as simple as that. You have to find the right iterators and know how to combine them.

Provided Code

main.rs
fn main()
{
    println!("get_min(&[5, 4, 3, 2, 1]) = {}",
        get_min(&[5, 4, 3, 2, 1]));

    println!("remove_space(\"  No s pa  ce any more  \") = \"{}\"",
        remove_space("  No s pa  ce any more  "));

    println!("remove_odd(&[1, 2, 3, 4, 5]) = {:?}",
        remove_odd(&[1, 2, 3, 4, 5]));

    println!("remove_vowel(\"I don't understand!\") = \"{}\"",
        remove_vowel("I don't understand!"));

    println!("initialism(\"test driven development\") = \"{}\"",
        initialism("test driven development"));

    println!("count_vowel(\"This string contains 7 vowels.\") = {}",
        count_vowel("This string contains 7 vowels."));

    println!("space_to_underscore(\"avoid underscores in filenames\") = {}",
        space_to_underscore("avoid underscores in filenames"));

    let mut a = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    println!("a = {:?}", a);
    println!("swap_chunks_mut(&mut a)");
    swap_chunks_mut(&mut a);
    println!("a = {:?}", a);
}

fn get_min(a: &[i32]) -> i32
{
    let mut min = a[0];

    for i in 0..a.len()
    {
        if a[i] < min
        {
            min = a[i];
        }
    }

    min
}

fn remove_space(s: &str) -> String
{
    let mut r = String::new();

    for c in s.chars()
    {
        if c != ' '
        {
            r.push(c);
        }
    }

    r
}

fn remove_odd(a: &[i32]) -> Vec<i32>
{
    let mut v = Vec::new();

    for i in 0..a.len()
    {
        if a[i] % 2 == 0
        {
            v.push(a[i])
        }
    }

    v
}

fn is_vowel(c: char) -> bool
{
    let c = c.to_ascii_lowercase();
    c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'
}

fn count_vowel(s: &str) -> usize
{
    let mut count = 0;

    for c in s.chars()
    {
        if is_vowel(c)
        {
            count += 1
        }
    }

    count
}

fn remove_vowel(s: &str) -> String
{
    let mut r = String::new();

    for c in s.chars()
    {
        if !is_vowel(c)
        {
            r.push(c);
        }
    }

    r
}

fn initialism(s: &str) -> String
{
    let mut r = String::new();
    let mut iter = s.chars();
    let mut copy = false;

    r.push(iter.next().unwrap().to_ascii_uppercase());

    for c in iter
    {
        if copy
        {
            r.push(c.to_ascii_uppercase());
        }

        copy = c == ' ';
    }

    r
}

fn space_to_underscore(s: &str) -> String
{
    let mut r = String::new();

    for c in s.chars()
    {
        if c == ' '
        {
            r.push('_');
        }

        else
        {
            r.push(c);
        }
    }

    r
}

fn swap_chunks_mut(a: &mut[u8])
{
    for i in (0..(a.len() - 1)).step_by(2)
    {
        a.swap(i, i + 1);
    }
}

#[cfg(test)]
mod tests
{
    use super::*;

    #[test]
    fn test_get_min()
    {
        assert_eq!(get_min(&[1, 2, 10, -5, -18, 23]), -18);
        assert_eq!(get_min(&[1, 2]), 1);
        assert_eq!(get_min(&[-5, 5]), -5);
    }

    #[test]
    fn test_remove_odd()
    {
        assert_eq!(remove_odd(&[1, 2, 10, -5, -18, 23]), [2, 10, -18]);
        assert_eq!(remove_odd(&[1, 2]), [2]);
        assert_eq!(remove_odd(&[-5, 5]), []);
    }

    #[test]
    fn test_remove_space()
    {
        assert_eq!(remove_space("Hello World"), "HelloWorld");
        assert_eq!(remove_space("I'am not a number"), "I'amnotanumber");
        assert_eq!(remove_space("    Good    bye!    "), "Goodbye!");
    }

    #[test]
    fn test_is_vowel()
    {
        assert!(is_vowel('a'));
        assert!(is_vowel('e'));
        assert!(is_vowel('i'));
        assert!(is_vowel('o'));
        assert!(is_vowel('u'));

        assert!(is_vowel('A'));
        assert!(is_vowel('E'));
        assert!(is_vowel('I'));
        assert!(is_vowel('O'));
        assert!(is_vowel('U'));

        assert!(!is_vowel('b'));
        assert!(!is_vowel('C'));
        assert!(!is_vowel('m'));
        assert!(!is_vowel('M'));
        assert!(!is_vowel('.'));
        assert!(!is_vowel('0'));
        assert!(!is_vowel('9'));
        assert!(!is_vowel(' '));
    }

    #[test]
    fn test_count_vowel()
    {
        assert_eq!(count_vowel("Hello World"), 3);
        assert_eq!(count_vowel("I'am not a number"), 6);
        assert_eq!(count_vowel("Good bye!"), 3);
    }

    #[test]
    fn test_remove_vowel()
    {
        assert_eq!(remove_vowel("Hello World"), "Hll Wrld");
        assert_eq!(remove_vowel("I'am not a number"), "'m nt  nmbr");
        assert_eq!(remove_vowel("Good bye!"), "Gd by!");
    }

    #[test]
    fn test_initialism()
    {
        assert_eq!(initialism("acquired immune deficiency syndrome"), "AIDS");
        assert_eq!(initialism("british broadcasting company"), "BBC");
        assert_eq!(initialism("cascading Style Sheets"), "CSS");
        assert_eq!(initialism("gnu compiler collection"), "GCC");
        assert_eq!(initialism("european space agency"), "ESA");
    }

    #[test]
    fn test_space_to_underscore()
    {
        assert_eq!(space_to_underscore("  abcd  "), "__abcd__");
        assert_eq!(space_to_underscore("my filename"), "my_filename");
        assert_eq!(space_to_underscore(" a    b c  "), "_a____b_c__");
        assert_eq!(space_to_underscore("        "), "________");
        assert_eq!(space_to_underscore("a      b"), "a______b");
    }

    #[test]
    fn test_swap_chunks_mut()
    {
        let mut a = [1, 2, 3, 4, 5, 6];

        swap_chunks_mut(&mut a);
        assert_eq!(a, [2, 1, 4, 3, 6, 5]);

        swap_chunks_mut(&mut a);
        assert_eq!(a, [1, 2, 3, 4, 5, 6]);

        let mut a = [1, 2, 3, 4, 5, 6, 7];

        swap_chunks_mut(&mut a);
        assert_eq!(a, [2, 1, 4, 3, 6, 5, 7]);

        swap_chunks_mut(&mut a);
        assert_eq!(a, [1, 2, 3, 4, 5, 6, 7]);
    }
}

Implementation

Create a new project.

$ cargo new iterators --vcs none
    Created binary (application) `iterators` package
$ cd iterators

Replace the default main.rs file by the provided file. The latter is functional. You can compile it and run it.

$ cargo -q run
get_min(&[5, 4, 3, 2, 1]) = 1
remove_space("  No s pa  ce any more  ") = "Nospaceanymore"
remove_odd(&[1, 2, 3, 4, 5]) = [2, 4]
remove_vowel("I don't understand!") = " dn't ndrstnd!"
initialism("test driven development") = "TDD"
count_vowel("This string contains 7 vowels.") = 7
space_to_underscore("avoid underscores in filenames") = avoid_underscores_in_filenames
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
swap_chunks_mut(&mut a)
a = [2, 1, 4, 3, 6, 5, 8, 7, 9]

Some tests are also provided.

$ cargo -q test

running 9 tests
test tests::test_get_min ... ok
test tests::test_count_vowel ... ok
test tests::test_initialism ... ok
test tests::test_is_vowel ... ok
test tests::test_remove_vowel ... ok
test tests::test_remove_odd ... ok
test tests::test_remove_space ... ok
test tests::test_space_to_underscore ... ok
test tests::test_swap_chunks_mut ... ok

test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

So, this code works perfectly. However, you will rewrite the nine following functions:

The code of these functions can be replaced by iterator chains. Usually, chaining iterators reduces the number of instructions and improve code quality. But the purpose of this exercise is not about optimization or code quality. It is about getting used to using iterators.

For all of these functions (but is_vowel()) only one iterator chain is enough. In other words, you have to rewrite these functions with only one main instruction per function. This main instruction will be made up of iterators and can potentially end with an iterator consumer.

Some iterators require closures. Your closures must be simple (i.e. do not use curly braces with closures).

In order to understand what these functions do, you can look at their bodies and their associated test functions as well as the output of the program (i.e. the lines printed on the terminal by the main function).

Here are some tips:

is_vowel()
For this function, you must not use iterators. This function is right but instead of the if keyword, use the match keyword. This case is really simple, so it is a good introduction to pattern matching. You can see some examples on the following page: match.
swap_chunks_mut()
This function must directly modify the value of the parameter. That is why the parameter is a mutable reference. To be allowed to modify the parameter, you need a mutable iterator. So, call the chunks_exact_mut() to get a mutable iterator over chunks of two elements. Then, use for_each() and swap() to swap the two elements of each chunk.

Do not forget to clean your directory.

Jumbled Text

Introduction

You will write a program that generates jumbled text. You have already written a version of this program in the C language (see the previous version). The output of the Rust version must be exactly the same as that of the C version.

Provided Code

main.rs
const SEPARATORS: &str = " ,;:!?./%*$=+)@_-('\"&1234567890\r\n";

fn main()
{
    unimplemented!();
}

fn mix(s: &str) -> String
{
    unimplemented!();
}

// You are free to write other functions.

#[cfg(test)]
mod tests
{
    use super::*;

    #[test]
    fn test_mix()
    {
        unimplemented!();
    }
}

Implementation

Create a new project.

$ cargo new jumbled_text --vcs none
     Created binary (application) `jumbled_text` package
$ cd jumbled_text

Replace the default main.rs file by the provided file.

The mix() function converts a string of characters into jumbled text. The string to convert is passed as a parameter and the jumbled text is returned.

Write the test_mix() function by using the text given on the previous tutorial.

Write the mix() function so that it passes the test. You are free to use the method of your choice. If need be, you can write other functions than mix(), but if you use iterators efficiently, the mix() function is enough and can even remain short (around six lines of code).

Here are some tips:

Finally, write the main() function.

The expected result is as follows:

$ cargo -q run -- "Hello World!"
Hello World!
Hlelo Wrold!
$ cargo -q run -- "I am not a number! I'm a free man!"
I am not a number! I'm a free man!
I am not a nmuebr! I'm a fere man!
$ cargo -q run -- "I was born ready!"
I was born ready!
I was bron raedy!

If the number of parameters that are passed in is different from one, your program must use a default text. You are free to use the text you want. For instance:

$ cargo -q run --
This is the default text.
Tihs is the dfeualt txet.
$ cargo -q run -- Hello World
This is the default text.
Tihs is the dfeualt txet.

Do not forget to clean your directory.

Converting Integers into Letters

Introduction

The purpose of this exercise is to convert an integer into a number written in letters.

For instance:

$ cargo -q run -- 128
one hundred and twenty-eight
$ cargo -q run -- 1028
one thousand twenty-eight

Note that, in this exercise, the and conjuction appears after hundred only.

The number will be between 0 and 18,446,744,073,709,551,615 (the maximum value for a 64-bit unsigned integer).

Provided Code

main.rs
use structopt::StructOpt;

// You are free to define some constants.

#[derive(Debug, StructOpt)]
#[structopt(name = "Numbers to Words Converter",
    about = "Convert an integer into letters.")]
struct Arg
{
    // TODO
}

fn main()
{
    let arg = Arg::from_args();
    println!("{}", to_letters(arg.number));
}

fn to_letters(n: u64) -> String
{
    // TODO
}

// You are free to define other functions.

#[cfg(test)]
mod tests
{
    use super::*;

    #[test]
    fn test_quintillions()
    {
        assert_eq!(to_letters(0), "zero");
        assert_eq!(to_letters(1), "one");
        assert_eq!(to_letters(2), "two");
        assert_eq!(to_letters(3), "three");
        assert_eq!(to_letters(4), "four");
        assert_eq!(to_letters(5), "five");
        assert_eq!(to_letters(6), "six");
        assert_eq!(to_letters(7), "seven");
        assert_eq!(to_letters(8), "eight");
        assert_eq!(to_letters(9), "nine");

        assert_eq!(to_letters(10), "ten");
        assert_eq!(to_letters(11), "eleven");
        assert_eq!(to_letters(12), "twelve");
        assert_eq!(to_letters(13), "thirteen");
        assert_eq!(to_letters(14), "fourteen");
        assert_eq!(to_letters(15), "fifteen");
        assert_eq!(to_letters(16), "sixteen");
        assert_eq!(to_letters(17), "seventeen");
        assert_eq!(to_letters(18), "eighteen");
        assert_eq!(to_letters(19), "nineteen");
        assert_eq!(to_letters(20), "twenty");
        assert_eq!(to_letters(21), "twenty-one");
        assert_eq!(to_letters(30), "thirty");
        assert_eq!(to_letters(32), "thirty-two");
        assert_eq!(to_letters(40), "forty");
        assert_eq!(to_letters(43), "forty-three");
        assert_eq!(to_letters(50), "fifty");
        assert_eq!(to_letters(54), "fifty-four");
        assert_eq!(to_letters(60), "sixty");
        assert_eq!(to_letters(65), "sixty-five");
        assert_eq!(to_letters(70), "seventy");
        assert_eq!(to_letters(76), "seventy-six");
        assert_eq!(to_letters(80), "eighty");
        assert_eq!(to_letters(87), "eighty-seven");
        assert_eq!(to_letters(90), "ninety");
        assert_eq!(to_letters(98), "ninety-eight");

        assert_eq!(to_letters(100), "one hundred");
        assert_eq!(to_letters(101), "one hundred and one");
        assert_eq!(to_letters(115), "one hundred and fifteen");
        assert_eq!(to_letters(165), "one hundred and sixty-five");
        assert_eq!(to_letters(200), "two hundred");
        assert_eq!(to_letters(277), "two hundred and seventy-seven");
        assert_eq!(to_letters(580), "five hundred and eighty");
        assert_eq!(to_letters(999), "nine hundred and ninety-nine");

        assert_eq!(to_letters(1_000), "one thousand");
        assert_eq!(to_letters(5_454), "five thousand four hundred and fifty-four");
        assert_eq!(to_letters(9_999), "nine thousand nine hundred and ninety-nine");

        assert_eq!(to_letters(100_002), "one hundred thousand two");
        assert_eq!(to_letters(200_100_003), "two hundred million one hundred thousand three");

        assert_eq!(to_letters(18_446_744_073_709_551_615),
            "eighteen quintillion four hundred and forty-six \
             quadrillion seven hundred and forty-four trillion \
             seventy-three billion \
             seven hundred and nine million \
             five hundred and fifty-one thousand \
             six hundred and fifteen");
    }
}

Implementation

Create a new project.

$ cargo new to_letters --vcs none
     Created binary (application) `to_letters` package
$ cd to_letters

Replace the default main.rs file by the provided file.

The main function is given (two instructions only). You will use StructOpt to parse the argument. Also, some tests are provided. Look at them to find out the spelling of some numbers.

Complete the Arg structure and the to_letters() functions. You are free to use the method of your choice. You can write as many functions as you want.

Only three tips are given:

Do not forget to clean your directory.