I'm new to Rust and am working through the exercises found on the bottom of this page. The function below converts a string into pig latin. The requirements are as follows.
Convert strings to Pig Latin, where the first consonant of each word is moved to the end of the word with an added "ay", so "first" becomes "irst-fay". Words that start with a vowel get "hay" added to the end instead ("apple" becomes "apple-hay").
I would like to ensure that my code is as idiomatic as possible, specifically in the string manipulation area. Behold.
fn pigify(text: &str) -> String {
let map_func = |word: &str| -> String {
let mut chars = word.chars();
let first_char = chars.next().unwrap();
match first_char {
'a' | 'e' | 'i' | 'o' | 'u' => String::from(word) + "-hay",
_ => {
let mut xform = String::new();
for ch in chars {
xform.push(ch);
}
xform.push('-');
xform.push(first_char);
xform.push_str("ay");
xform
}
}
};
let folder = |current: String, next: String| -> String {
let mut this_str = String::new();
this_str.push_str(¤t);
if !current.is_empty() {
this_str.push(' ');
}
this_str.push_str(&next);
this_str
};
text.split_whitespace()
.map(map_func)
.fold(String::new(), folder)
}
2 Answers 2
You've got a good use of match
and some solid iterator usage, very nice!
Your closures don't need to be closures -- they don't capture any environment. You can just make them functions (even defined inside the other function, if you'd like). Normally, prefer to define closures at the site of use, instead of as variables ahead of the use. This allows type inference to work on them more effectively (probably why you had to specify types for the closure arguments and return values).
Chars::as_str
allows you to get the remainder of the underlying string, so you don't need to iterate though all the remaining characters.The
format!
macro allows you to build a string from components in a nicer way.The code doesn't handle empty strings (
unwrap
makes this easy to find); there's no reason to not handle them.You don't need to create a new
String
infolder
, just mark the incomingString
as mutable
fn pigify_one(word: &str) -> String {
let mut chars = word.chars();
let first_char = match chars.next() {
Some(c) => c,
None => return String::new(),
};
match first_char {
'a' | 'e' | 'i' | 'o' | 'u' => format!("{}-hay", word),
_ => format!("{}-{}ay", chars.as_str(), first_char),
}
}
fn folder(mut current: String, next: String) -> String {
if !current.is_empty() {
current.push(' ');
}
current.push_str(&next);
current
}
fn pigify(text: &str) -> String {
text.split_whitespace()
.map(pigify_one)
.fold(String::new(), folder)
}
I'm a big fan of itertools, which provides methods like join
:
extern crate itertools;
use itertools::Itertools;
fn pigify(text: &str) -> String {
text.split_whitespace()
.map(pigify_one)
.join(" ")
}
See What's an idiomatic way to print an iterator separated by spaces in Rust?
not sure if i did the best way, i did this to solve the problem in RUST Book
use std::io;
fn main() {
println!("Enter Word: ");
let mut input_word = String::new();
io::stdin()
.read_line(&mut input_word)
.expect("Error while Reading Input");
let mut output = String::new();
for (i, c) in input_word.trim().chars().enumerate() {
if is_vowel(c) {
output = match i {
x if x == 0 => format!("{}-hay", &input_word),
_ => format!("{}-{}ay", &input_word[i..], &input_word[0..i]),
};
break;
}
}
println!("Latin Word = {}", output);
}
fn is_vowel(character: char) -> bool {
let vowels = ['a', 'e', 'i', 'o', 'u'];
match vowels.iter().find(|&&v| v == character) {
Some(_) => true,
_ => false,
}
}
```
-
1\$\begingroup\$
not sure if i did the best way
Please argue why it is preferrable over the code presented in the question - Every answer must make at least one insightful observation about the code in the question. \$\endgroup\$greybeard– greybeard2020年05月13日 10:15:28 +00:00Commented May 13, 2020 at 10:15