7

I'm trying to join strings in a vector into a single string, in reverse from their order in the vector. The following works:

let v = vec!["a".to_string(), "b".to_string(), "c".to_string()];
v.iter().rev().map(|s| s.clone()).collect::<Vec<String>>().connect(".")

However, this ends up creating a temporary vector that I don't actually need. Is it possible to do this without a collect? I see that connect is a StrVector method. Is there nothing for raw iterators?

3 Answers 3

11

I believe this is the shortest you can get:

fn main() {
    let v = vec!["a".to_string(), "b".to_string(), "c".to_string()];
    let mut r = v.iter()
        .rev()
        .fold(String::new(), |r, c| r + c.as_str() + ".");
    r.pop();
    println!("{}", r);
}

The addition operation on String takes its left operand by value and pushes the second operand in-place, which is very nice - it does not cause any reallocations. You don't even need to clone() the contained strings.

I think, however, that the lack of concat()/connect() methods on iterators is a serious drawback. It bit me a lot too.

Sign up to request clarification or add additional context in comments.

2 Comments

Do you know if there's been discussion about implementing those methods for iterators before? Is it not done because it's tricky or because no one's got around to it?
@Ray, it is most certainly that no one's got around to it. I'm not aware of any discussions, and for some reason (laziness, most probably :() I didn't start any myself.
7

I don't know if they've heard our Stack Overflow prayers or what, but the itertools crate happens to have just the method you need - join.

With it, your example might be laid out as follows:

use itertools::Itertools;
let v = ["a", "b", "c"];
let connected = v.iter().rev().join(".");

Comments

6

Here's an iterator extension trait that I whipped up, just for you!

pub trait InterleaveExt: Iterator + Sized {
    fn interleave(self, value: Self::Item) -> Interleave<Self> {
        Interleave {
            iter: self.peekable(),
            value: value,
            me_next: false,
        }
    }
}

impl<I: Iterator> InterleaveExt for I {}

pub struct Interleave<I>
where
    I: Iterator,
{
    iter: std::iter::Peekable<I>,
    value: I::Item,
    me_next: bool,
}

impl<I> Iterator for Interleave<I>
where
    I: Iterator,
    I::Item: Clone,
{
    type Item = I::Item;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        // Don't return a value if there's no next item
        if let None = self.iter.peek() {
            return None;
        }

        let next = if self.me_next {
            Some(self.value.clone())
        } else {
            self.iter.next()
        };

        self.me_next = !self.me_next;
        next
    }
}

It can be called like so:

fn main() {
    let a = &["a", "b", "c"];
    let s: String = a.iter().cloned().rev().interleave(".").collect();
    println!("{}", s);

    let v = vec!["a".to_string(), "b".to_string(), "c".to_string()];
    let s: String = v.iter().map(|s| s.as_str()).rev().interleave(".").collect();
    println!("{}", s);
}

I've since learned that this iterator adapter already exists in itertools under the name intersperse — go use that instead!.

Cheating answer

You never said you needed the original vector after this, so we can reverse it in place and just use join...

let mut v = vec!["a".to_string(), "b".to_string(), "c".to_string()];
v.reverse();
println!("{}", v.join("."))

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.