Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mutate iterator elements in Rust to reverse substrings

Tags:

rust

I am trying to make a function that will do the following:
input: a String of arbitrary length of the form "abc/def/ghi"
output: a String where all the substrings separated by "/" are reversed; in this example, the output would be "cba/fed/ihg".

More than the function itself, I care about the general principle behind mutating an iterator generated by the split() function.

Below is my best effort:

fn reverse_string(input: &mut str) -> String {
    input
        .to_string()
        .split('/')
        .map(move |x| x.to_string().rev())
        .collect::<String>()
}

The compiler complains that

error[E0599]: no method named `rev` found for type `std::string::String` in the current scope
 --> src/main.rs:5:37
  |
5 |         .map(move |x| x.to_string().rev())
  |                                     ^^^
  |
  = note: the method `rev` exists but the following trait bounds were not satisfied:
          `&mut std::string::String : std::iter::Iterator`
          `&mut str : std::iter::Iterator`

What does that mean and how can I resolve this problem?

like image 554
SquattingSlavInTracksuit Avatar asked Dec 13 '17 11:12

SquattingSlavInTracksuit


1 Answers

If you are learning iterators, I suggest first deciding what you want to do, before actually doing it.

For example, here is an example with a single memory allocation:

fn reverse_string(input: &str) -> String {
    let mut result = String::with_capacity(input.len());

    for portion in input.split('/') {
        if !result.is_empty() {
            result.push('/');
        }
        for c in portion.chars().rev() {
            result.push(c);
        }
    }

    result
}

Iterators generally are focused on pure methods which do not modify their environment. Unfortunately, this can lead to inefficiencies here as this would imply creating and dropping String left and right.

Now, technically you can mutate the environment in map (it takes a FnMut), it's just frowned upon because by convention readers expect it to be pure.

As a result, when you want to bring additional state in, Iterator::fold is the go-to method:

fn reverse_string(input: &str) -> String {
    input
        .split('/')
        .fold(
            String::with_capacity(input.len()),
            |mut acc, portion| {
                if !acc.is_empty() {
                    acc.push('/');
                }
                for c in portion.chars().rev() {
                    acc.push(c);
                }
                acc
            }
        )
}

The first argument is an accumulator, which is passed to each invocation of the closure, which then return it. Finally, at the end of the fold call, the accumulator is then returned.

This is equivalent to the first function, both in terms of logic and efficiency, but honestly here I kinda prefer the for version to be honest.

like image 165
Matthieu M. Avatar answered Sep 30 '22 04:09

Matthieu M.