How can I iterate over a backwards range?

2020-05-07 06:27发布

I'm attempting to create pub fn sing(start: i32, end: i32) -> String that returns a concatenated string of the results of calling pub fn verse(num: i32) -> String repeatedly on each number between start and end.

I've googled for the answer and it seems that Rust String concatenation answers my question, and if I even write my code in playground it works, but:

my code:

pub fn verse(num: i32) -> String {
    match num {
        0 => "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n".to_string(),
        1 => "1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n".to_string(),
        2 => "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n".to_string(),
        num => format!("{0} bottles of beer on the wall, {0} bottles of beer.\nTake one down and pass it around, {1} bottles of beer on the wall.\n",num,(num-1)),
    }
}

pub fn sing(start: i32, end: i32) -> String {
    (start..end).fold(String::new(), |ans, x| ans+&verse(x))
}

The problem is that

#[test]
fn test_song_8_6() {
    assert_eq!(beer::sing(8, 6), "8 bottles of beer on the wall, 8 bottles of beer.\nTake one down and pass it around, 7 bottles of beer on the wall.\n\n7 bottles of beer on the wall, 7 bottles of beer.\nTake one down and pass it around, 6 bottles of beer on the wall.\n\n6 bottles of beer on the wall, 6 bottles of beer.\nTake one down and pass it around, 5 bottles of beer on the wall.\n");
}

fails with beer::sing(8,6) returning "".

1条回答
Viruses.
2楼-- · 2020-05-07 06:50

Your problem has nothing to do with string concatenation. It has to do with the fact that 8..6 is an empty iterator, because a range only iterates forwards. Because 8 >= 6, the iterator yields None on the first call to next.

fn main() {
    for i in 8..6 {
        println!("{}", i); // never reached
    }
}

This can be fixed by swapping start and end and calling rev() to iterate backwards.

fn main() {
    for i in (6..8).rev() {
        println!("{}", i);
    }
}

However, there's still another problem. In a range start..end, start is inclusive but end is exclusive. For example, the code above prints 7 and 6; 8 is not printed. See How do I include the end value in a range?

Putting it all together, sing should look like:

pub fn sing(start: i32, end: i32) -> String {
    (end..=start)
        .rev()
        .fold(String::new(), |ans, x| ans + &verse(x))
}

Note: Your test still fails because it expects two newlines between each verse, but your code only generates one. I'll leave this up to you to fix.

查看更多
登录 后发表回答