Ray
Ray

Reputation: 1859

String join on strings in Vec in reverse order without a `collect`

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?

Upvotes: 7

Views: 4150

Answers (3)

Vladimir Matveev
Vladimir Matveev

Reputation: 127791

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.

Upvotes: 11

Shepmaster
Shepmaster

Reputation: 430741

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("."))

Upvotes: 6

ArtemGr
ArtemGr

Reputation: 12547

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(".");

Upvotes: 7

Related Questions