What is the best way to compare the chars of string?

I need to compare a char to another char in same string and get total number of chars meeting that condition. I have currently implemented this in C++ style by converting string into array of u8 bytes and comparing using indices of elements and primitive for loop. Is there more idiomatic way of doing it ? Also can i do it in functional way using iterator adapters such as map,filter etc?

My current implementation

let s = "ABAAB".as_bytes();
let n= s.len();
let mut count = 0;
for i in 0..n / 2 {
            if s[i] != s[n - i - 1] {
                count += 1
            };
        }
println!("total matched chars: {}",count);

The main concern here is to compare ith char with n-i-1 th char and keep count of chars matching that condition. I would like to make it more concise and nicer like python implementation below:

count=sum(int(s[i] != s[n-1-i]) for i in xrange(n//2))
1 Like
pub fn matching_chars(s: &str) -> usize {
    let mid = s.len() / 2;
    
    let left = &s[..mid];
    let right = &s[s.len() - mid..];
    
    let left_chars = left.chars();
    let right_chars = right.chars().rev();

    left_chars.zip(right_chars).filter(|(a,b)| a == b).count()
}

fn main() {
    println!("{}", matching_chars("ABAAB"));
    println!("{}", matching_chars("ABAABA"));
}
0
3

Edit: the computation of the midpoint is wrong for strings with multi-byte characters, but it would work for bytes

3 Likes

The bytes implementation is incorrect. Multi-byte chars will will break it.

let string = "abфПфba";
let mut count = 0;
let forward = string.char_indices();
let reverse = string.char_indices().rev();
for ((i, a), (j, b)) in forward.zip(reverse) {
    if i >= j { break; }
    if a == b { count += 1; }
}
dbg!(count);
9 Likes

In general, if you have a string and want to do X for each char, and you instead do X for each byte, your code is broken.

8 Likes

Now, that's just not true. E.g. if X is 'find out if it's a newline', you can very well just do this on bytes.

1 Like

Thank u so much for replying. No problem for multi-byte chars as I am only using ASCII chars. Its very neat and idiomatic.I was really struggling to express the logic in idiomatic way. Turns out I was only thinking in imperative way.

No problem for multi-byte chars as I am only using ASCII chars. Your solution is really neat especially using char indices to test breaking condition. I will definitely check it out in my code. Thank u so much for helping.

Suggestion then for your (internal) API: Make the function take &[u8], so it's clear from the signature that it won't handle multibyte chars properly (or at least, it's sort of implied), and it won't panic from slicing a multibyte char in half. The caller can then decide to use it with s.as_bytes(), and does have a choice what to do if the string had multibyte chars to begin with.

6 Likes

Ok I will do it if i will implement it in API. But for now I am just reading string from stdin. I think the given strings in my case will always be array of u8 bytes. I am using above code to solve this google kickstart problem using c++ solution given here . I was really struggling in processing strings in idiomatic way in rust. I only had experience doing it in imperative way in c++. Now I will try using above solutions.

If you find yourself wishing there were as many facilities for &[u8] as there are for str, I recommend the bstr crate. (Not always a possibility in coding competitions, being an external crate, though.)

2 Likes

Thanks for recommendation. yeah external crate might not be allowed but I will definitely check it and hope that I'll get some ideas on strings manipulation so that i could come up with my own logic.

Why not use str::split_at? Like let (left, right) = s.split_at(mid)? The only case I can think it won't work if it splits at utf-8 boundary but if it's ASCII this would work.

@s3bk's impl is just fine, but another way I'd imagine doing it is using the .chars() iterator and taking both .next() and .next_back() from the iterator in each iteration. Stop iteration if either end is empty. Indexes are not needed for this double-ended sequence.

pub fn matching_chars(s: &str) -> usize {
    let mut chars = s.chars();
    let mut matching = 0;
    while let (Some(c1), Some(c2)) = (chars.next(), chars.next_back()) {
        // unsure from given code and description if != or == is wanted here
        matching += if c1 != c2 { 1 } else { 0 };
    }
    matching
}

fn main() {
    println!("{}", matching_chars("ABAAB"));
    println!("{}", matching_chars("ABAABA"));
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.