Antonin GAVREL
Antonin GAVREL

Reputation: 11259

Rust: Most efficient way to iterate over chars of an ASCII string

My original approach:

pub fn find_the_difference(s: String, t: String) -> char {
        let mut c:u8 = 0;
        for i in 0..s.chars().count() {
            c ^= t.chars().nth(i).unwrap() as u8 ^ s.chars().nth(i).unwrap() as u8;
        }
        return (c ^ t.chars().nth(s.chars().count()).unwrap() as u8) as char;
        
    }

But it was too slow and quite crazy also all what I had to write to replace t[i] ^ s[i] (see below original C++ function). So I looked for something else and found this method where we convert the string into a char array and got some good results (went from 8ms to 0ms).

pub fn find_the_difference(s1: String, t1: String) -> char {
        let mut c:u8 = 0;
        let s: Vec<char> = s1.chars().collect();
        let t: Vec<char> = t1.chars().collect();

        for i in 0..s1.chars().count() {
            c ^= t[i] as u8 ^ s[i] as u8;
        }
        return (c ^ t[s1.chars().count()] as u8) as char;

    }

But perhaps no need to collect, nor do I care about index, I just want to iterate on one char after another. My current attempt:

pub fn find_the_difference(s1: String, t1: String) -> char {
        let mut c:u8 = 0;
        let mut s = s1.chars();
        let mut t = t1.chars();
        let n = s.count();
        
        for i in 0..n {
            c ^= t.next().unwrap() as u8 ^ s.next().unwrap() as u8; // c ^= *t++ ^ *s++ translated in C++
        }
        return (c ^ t.next().unwrap() as u8) as char;
        
    }

I get the following error message:

Line 9, Char 44: borrow of moved value: `s` (solution.rs)
   |
4  |         let mut s = s1.chars();
   |             ----- move occurs because `s` has type `std::str::Chars<'_>`, which does not implement the `Copy` trait
5  |         let mut t = t1.chars();
6  |         let n = s.count();
   |                 - value moved here
...
9 |             c ^= t.next().unwrap() as u8 ^ s.next().unwrap() as u8;
   |                                            ^ value borrowed here after move
error: aborting due to previous error

Is it possible to achieve this kind of code c = *t++ ?

NB: s1.chars.count() = t1.chars.count() - 1 and the goal is to find the extra letter in t1

NB2: original C++ function:

char findTheDifference(string s, string t) {
        char c = 0;
        for (int i = 0; t[i]; i++)
            c ^= t[i] ^ s[i];
        return c;
    }

Upvotes: 1

Views: 6582

Answers (2)

NovaDenizen
NovaDenizen

Reputation: 5325

I think you're confused about the differences between C and Rust string handling, and the distinctions between Rust's str, String, &[u8], char, and u8 types.

That said, here is how I'd implement your function:

fn find_the_difference(s: &[u8], t: &[u8]) -> u8 {
    assert!(t.len() > s.len());
    let mut c: u8 = 0;
    for i in 0..s.len() {
        c ^= s[i] ^ t[i];
    }
    c ^ t[s.len()]
}

If your data is currently String, you can get a &[u8] view of it using the as_bytes() method. Like this:

let s: String = ...some string...;
let t: String = ...some string...;

let diff = find_the_difference(s.as_bytes(), t.as_bytes());

Upvotes: 6

Schwern
Schwern

Reputation: 165546

zip the two iterators together.

And, as Peter Hall comments, it's safer. You can't assume characters are 1 byte. Just use !=.

fn main() {
    let a = "☃ Thiñgs";
    let b = "☃ Thiñks";

    let both = a.chars().zip(b.chars());
    
    for pair in both {
        if pair.0 != pair.1 {
            println!("Different {} {}", pair.0, pair.1);
        }
    }
}

This will stop when either iterator is exhausted.

If you want the indicies as well, use char_indicies.

Because they are a key feature of Rust, iterators are a "zero-cost abstraction" meaning Rust will do the optimization for you. Iterators are generally as fast or faster than hand-written loops.

Upvotes: 2

Related Questions