1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
const CONSONANTS: &str = "bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ";
const VOWELS: &str = "aeiouyAEIOUY";

fn stem(word: &str) -> (String, String) {
    let start = word
        .chars()
        .take_while(|c| CONSONANTS.contains(*c))
        .collect::<String>();
    let end = word.chars().skip(start.len()).collect::<String>();
    (start, end)
}

fn stem2(word: &str) -> (&str, &str) {
    let pattern = |c: char| VOWELS.contains(c);
    let index = word.find(pattern).unwrap_or(0);
    (&word[0..index], &word[index..])
}

/// Splits a phase of two words and shuffles their beginnings.
///
/// Naive implementation
///
/// # Example
///
/// ```
/// assert_eq!(zummi::zummi_naive("hello world"), Some(String::from("wello horld")));
/// ```
///
pub fn zummi_naive(phrase: &str) -> Option<String> {
    let mut words = phrase.split_whitespace();
    let (first, second) = (words.next(), words.next());
    if let (Some(first), Some(second)) = (first, second) {
        let (f, irst) = stem(first);
        let (s, econd) = stem(second);
        Some(s + &irst + " " + &f + &econd)
    } else {
        None
    }
}

/// Broken version where I accidentally cause an unnecessary allocation.
///
/// # Example
///
/// ```
/// assert_eq!(zummi::zummi_broke("hello world"), Some(String::from("wello horld")));
/// ```
///
pub fn zummi_broke(phrase: &str) -> Option<String> {
    let mut words = phrase.split_whitespace();
    let (first, second) = (words.next(), words.next());
    if let (Some(first), Some(second)) = (first, second) {
        let (f, irst) = stem2(first);
        let (s, econd) = stem2(second);
        let capacity = f.len() + irst.len() + s.len() + econd.len();
        let spoonerism = String::with_capacity(capacity) + s + irst + " " + f + econd;
        Some(spoonerism)
    } else {
        None
    }
}

/// Splits a phase of two words and shuffles their beginnings.
///
/// # Example
///
/// ```
/// assert_eq!(zummi::zummi("hello world"), Some(String::from("wello horld")));
/// ```
///
pub fn zummi(phrase: &str) -> Option<String> {
    let mut words = phrase.split_whitespace();
    let (first, second) = (words.next(), words.next());
    if let (Some(first), Some(second)) = (first, second) {
        let (f, irst) = stem2(first);
        let (s, econd) = stem2(second);
        let capacity = f.len() + irst.len() + s.len() + econd.len() + " ".len();
        let spoonerism = String::with_capacity(capacity) + s + irst + " " + f + econd;
        Some(spoonerism)
    } else {
        None
    }
}