Part 6. Iterating over string (10 points)

Strings in Rust are similar to vectors of characters except that, unfortunately, they’re slightly more difficult to use because text is inherently complicated than vectors.

The most obvious difficulty is you cannot access individual characters in the same way you’d access individual elements of a vector.

#![allow(unused)]
fn main() {
let x = vec![1, 2, 3];
assert_eq!(x[0], 1);

let y = "ABC";
assert_eq!(y[0], 'A'); // FAILS!
}

That last line fails (click the Run button to see the error) because you cannot index into a string like this. The problem is that Rust stores all strings in the UTF-8 encoding. This is a variable-length encoding where different characters may take a different number of characters to encode. So if you want to get the 10 th character from a string, for example, you cannot easily figure out where in the encoded data for the string the 10 th character starts without starting at the beginning of the string.

This means that to work with characters, we’re going to need to use an iterator over the characters. Fortunately, the chars() function returns an iterator that returns each character.

#![allow(unused)]
fn main() {
let example = "Here is my string";
for ch in example.chars() {
    println!("{ch}");
}
}

Just as we’ve made a distinction between Vec<i32> and &[i32] where the latter is a reference to the former, in Rust we have a String data type and a reference to a string &str. When we have a function that takes a reference to a string, we use an argument of type &str. Note that strings we create by enclosing text in quotation marks have type &str and not String. If we have a String and we want to pass it to a function that takes a &str, we get a reference to the string using & just as we did with vectors and reference to vectors.

Your task

Write a function

#![allow(unused)]
fn main() {
fn clown_case(s: &str) -> String {
  todo!("Implement me")
}
}

that takes a &str as input and returns a new String that alternates capitalization and includes a clown emoji, 🤡, at the beginning and end of the string as long as s is not the empty string. If s is the empty string, return just a single clown emoji.

Here are some examples. I recommend you turn them into unit tests before you implement clown_case.

  • "" -> "🤡"
  • "I'm just asking questions" -> "🤡 i'M jUsT aSkInG qUeStIoNs 🤡"
  • "Μην είσαι κλόουν στα ελληνικά!" -> "🤡 μΗν ΕίΣαΙ κΛόΟυΝ σΤα ΕλΛηΝιΚά! 🤡"

Tip

  1. If ch is a char, you can use ch.is_alphabetic() to decide if you want to lowercase or uppercase the letter or just include it in the result unchanged. I.e., only uppercase/lowercase the characters for which ch.is_alphabetic() returns true.
  2. If ch is a char, then ch.to_lowercase() and ch.to_uppercase() return iterators to chars rather than a char itself. Why? Well, in some languages converting the case changes the number of characters. E.g, a capital ß is SS. You can add the elements of an iterator to a string using the extend() function.
    #![allow(unused)]
    fn main() {
    let mut result = String::new();
    result.extend('ß'.to_uppercase());
    assert_eq!(result, "SS");
    }
  3. You can append a char to a String by using push(). You can append a &str to a String by using push_str().

At this point you’re done and can submit your code.

However, this is one more optional step you can take if you’d like: Comment out the existing code in your main() function. (In VS Code, you can comment or uncomment multiple lines at once by selecting all of the lines and hitting Ctrl-/.) Write some code in main() to read a line of input from stdin, pass it to clown_case(), and then print the results. Now you can run the program and have it turn any input you enter into clown case. (I have been reliably informed that this is a lot of fun to use.)