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:
-
pw_07_rust_miscellaneous/
- AUTHORS
-
iterators/
- Cargo.lock
- Cargo.toml
-
src/
- main.rs
-
jumbled_text/
- Cargo.lock
- Cargo.toml
-
src/
- main.rs
-
to_letters/
- Cargo.lock
- Cargo.toml
-
src/
- main.rs
Do not submit any executable or binary files.
You have to delete all the target
directories.
The AUTHORS
file
must contain the following information.
First Name
Family Name
Login
Email Address
The last character of your
AUTHORS
file must be a newline character.
For instance:
$ 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:
RUNfn 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.
RUNfn 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
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:
get_min()
remove_space()
remove_odd()
is_vowel()
count_vowel()
remove_vowel()
initialism()
space_to_underscore()
swap_chunks_mut()
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 thematch
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
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:
- The problem with string slices is that we cannot access their characters by using indices. But we can easily convert a string slice into a vector of characters and vice versa.
- You can manipulate the characters of a vector by their indices.
- The swap() method can be used to swap two elements of a vector.
-
Also, you may need these methods:
- chars()
- split_mut()
- contains(): the pattern can be a character.
- filter()
- chunks_exact_mut()
- for_each()
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
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:
- You can use constant arrays to store string literals of some relevant numbers (e.g. "one", "two", "hundred", "quintillion").
- If one of your function is recursive, only two or three functions are enough.
- Pattern matching can be used in order to reduce the size of your code (fewer than ten instructions per function).
Do not forget to clean your directory.