7
\$\begingroup\$

The title says it all. I'm reading "The Rust Programming Language" 2nd ed. right now, and just finished the "Collections" chapter. I know there's a ton of Pig Latin code here, but I think Rust deserves a separate question.

use std::io;
use std::iter::FromIterator;

fn main() {
    println!("Enter the string to be Pig-latinized");

    let mut input = String::new();

    io::stdin().read_line(&mut input)
        .expect("Failed to read from stdin");

    let s: Vec<char> = input.chars().collect();
    let mut new_s: Vec<char> = Vec::new();
    let consonants = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q',
    't', 'r', 's', 'v', 'w', 'x', 'z', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q',
    'T', 'R', 'S', 'V', 'W', 'X', 'Z'];

    let mut i = 0;

    while i < s.len() {

        if !s[i].is_alphabetic() {
            new_s.push(s[i]);
            i += 1;
            continue;
        }

        let first_letter = s[i];
        let mut appendage: Vec<char> = "-hay".chars().collect();
        if consonants.contains(&first_letter) {
            appendage[1] = first_letter;
            i += 1;
        }

        while s[i].is_alphabetic() && i < s.len() {
            new_s.push(s[i]);
            i += 1;
            continue;
        }

        new_s.append(&mut appendage);
    }

    println!("{}", String::from_iter(new_s)); 
}

The code feels clunky, but I don't really know how to make it better. I'd love to see an idiomatic Rust solution!

\$\endgroup\$

1 Answer 1

11
\$\begingroup\$

For overall issues, these jump out:

  1. s[i].is_alphabetic() && i < s.len() can panic, because you access the value before checking the length, it would have to be i < s.len() && s[i].is_alphabetic().
  2. is_alphabetic considers lots of things alphabetic that you probably don't want. If you look at the docs the examples include assert!('京'.is_alphabetic()); which probably isn't what you want.

For Rust itself:

  1. Rather than converting the string to a vector of chars, it's easier to use an iterator. In your case it needs to be an iterator that can .peek so you can stop before consuming the character.
  2. Pattern-matching can make the readability of checking the vowels vs others easier, and it allows you to collapse the logic in your first if. The matches also allow you to cleanly create and define the suffix value earlier, and avoid the usage of the problematic is_alphabetic function.

Here's an updated implementation:

let mut chars = input.chars().peekable();
let mut new_s = String::new();
while let Some(c) = chars.next() {
    let suffix = match c {
        'a' | 'e' | 'i' | 'o' | 'u' => {
            new_s.push(c);
            String::from("-hay")
        }
        'a'...'z' | 'A'...'Z' => {
            format!("-{}ay", c)
        }
        _ => {
            new_s.push(c);
            continue;
        }
    };

    while let Some(&c) = chars.peek() {
        match c {
            'a'...'z' | 'A'...'Z' => {
                chars.next();
                new_s.push(c);
            }
            _ => break,
        }
    }

    new_s += &suffix;
}
println!("{}", new_s);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.