55

Since a string supports iteration but not indexing, I would like to convert a string into a list of chars. I have "abc" and I want ['a', 'b', 'c'].

It can be any type as long as I can index into it. A Vec<char> or a [char; 3] would be fine, other ideas would also be interesting!

Faster would be better since I am dealing with very long strings. A version that is more efficient when assuming that the string is ASCII would also be cool.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Sarien
  • 6,647
  • 6
  • 35
  • 55
  • 1
    I wonder why you need to index it? Maybe this is a XY problem. You have so much tool in the std lib to deal with strings that I cannot see any reason to look for a random access to strings. – Boiethios Dec 15 '17 at 10:11
  • I am at work, the company blocks chats. I ask the question, because maybe your problem can be solved without indexing the chars. – Boiethios Dec 15 '17 at 11:49

3 Answers3

85

Use the chars method on String or str:

fn main() {
    let s = "Hello world!";
    let char_vec: Vec<char> = s.chars().collect();
    for c in char_vec {
        println!("{}", c);
    }
}

Here you have a live example

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Netwave
  • 40,134
  • 6
  • 50
  • 93
17

I've already come to this solution:

let chars: Vec<char> = input.chars().collect();

In a comment somebody suggested using .split("") but it seems to be implemented in a way that is annoying for this case:

println!("{:?}", "abc".split("").collect::<Vec<&str>>());

gives ["", "a", "b", "c", ""]

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Sarien
  • 6,647
  • 6
  • 35
  • 55
  • 5
    If you want a `Vec<&str>` like `split("")` would give you, but you don't want the empty strings at beginning and end, use `split_terminator("").skip(1)` – trent Dec 15 '17 at 15:41
3

Since you are trying to solve the first day of Advent of Code, I'm assuming that you need to compare two consecutive characters and update an accumulator depending on whether the characters are the same. You can do that without indexing by using the fold function on chars. Something like this (untested):

fn example(s: &str) -> u32 {
    let mut i = s.chars()
        .map(|c| c.to_digit(10).expect("Invalid input: Non-digit found!"));
    if let Some(first) = i.next() {
        let (tot, last) = i.fold((0, first), |acc, x| {
            let (tot, prev) = acc;
            if prev == x {
                (tot + prev, x)
            } else {
                (tot, x)
            }
        });
        if last == first {
            tot + last
        } else {
            tot
        }
    } else {
        0
    }
}

The fold iterator method allows you to walk a value over all the iterator items updating that value at each step. Here the value is a tuple containing the accumulated result and the value of the previous digit. We initialize it with some initial result and the value of the first character. Then at each step we compare the current character with the previous one taken from the current value, update the accumulated result depending on the result of the comparison and return a new value with the updated accumulator and the current character.

The fold method returns the last value, which in our case contains the accumulated result and the last character, at which point you may check if the last character is the same as the first one and update the result if you need to.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Jmb
  • 18,893
  • 2
  • 28
  • 55
  • Interesting idea. I had something similar for the first part of the question but thought that treating the beginning and end differently in this way was a bit messy. In the second part the offset is different though and this becomes infeasible. – Sarien Dec 16 '17 at 09:38