[英]How to reverse a Rust String in-place using recursion?
当使用递归反转String
时,我发现很难将String
的一部分继续到下一个,因为 slice 是&str
类型而不是String
。
这不运行:
fn reverse_string(s: &mut String) {
if s.is_empty() {
return;
}
// how to pass a correct parameter?
reverse_string(&mut s[1..]);
s.push(s.chars().nth(0).unwrap());
s.remove(0);
}
error[E0308]: mismatched types
--> src/lib.rs:6:20
|
6 | reverse_string(&mut s[1..]);
| ^^^^^^^^^^^ expected struct `String`, found `str`
|
= note: expected mutable reference `&mut String`
found mutable reference `&mut str`
Rust 字符串为 UTF-8,这意味着
如果您只想交换ASCII 字符串的字符,则可以这样做:
use std::mem;
fn reverse_string_ascii(s: &mut str) {
if !s.is_ascii() {
return;
}
// Safety: We have checked that the string is ASCII,
// so it's fine to treat it as a slice of bytes.
unsafe {
fn rev(b: &mut [u8]) {
match b {
[] => {}
[_] => {}
[h, rest @ .., t] => {
mem::swap(h, t);
rev(rest)
}
}
}
rev(s.as_bytes_mut());
}
}
fn main() {
let mut s = String::from("hello");
reverse_string_ascii(&mut s);
println!("{}", s);
}
虽然没有真正的理由在这里使用递归,但迭代更好:
let mut todo = s.as_bytes_mut();
loop {
match todo {
[] => break,
[_] => break,
[h, rest @ .., t] => {
mem::swap(h, t);
todo = rest;
}
}
}
也可以看看:
String
数据类型的切片属于数据类型&str
,因此您的程序无法编译,编译器还声明他期望String
但得到了str
。 您可以尝试转换数据类型,但您可能必须以不同的方式编写程序。
我不确定您为什么要尝试使用递归来做到这一点,但我敢肯定您有这样做的理由;)
我做了一个工作演示,展示了如何在不使用切片但仅使用String
和char
的情况下天真地做到这一点:
fn rec_rev_str(mut s: String) -> String {
if s.is_empty() {
s
} else {
let removed_char = s.remove(0);
let mut s = rec_rev_str(s);
s.push(removed_char);
s
}
}
fn main() {
let s = String::from("A test String :)");
println!("{}", rec_rev_str(s));
}
我会写出比上面建议的更有效的版本。 我的版本具有 O(n) 复杂度。 如果字符串是 ASCII,它不会分配,但如果字符串是 unicode,它仍然需要它。
虽然还有其他改进的可能性,例如,如果字符串中的所有字符在 utf8 格式中具有相同的长度,您可以取消分配(但不要忘记 alignment 这样做)。
另外,我让 function 接受&mut str
因为它更好,因为它允许更广泛的输入(例如,仅反转子字符串)。
您可以在评论中看到在不安全的 Rust 中使用 unicode 时需要考虑多少事情。
fn reverse_str(s: &mut str){
fn reverse_slice<T: Copy>(slice: &mut [T]){
let slice_len = slice.len();
if slice_len < 2{
return;
}
slice.swap(0, slice_len-1);
reverse_slice(&mut slice[1..slice_len-1]);
}
if s.is_ascii(){
// Simple case: can reverse inplace
unsafe{
// Safety: string is ASCII
reverse_slice(s.as_bytes_mut());
}
}
else{
// complex case: we need to work with unicode
// Need to allocate, unfortunately
let mut chars: Vec<char> = s.chars().collect();
reverse_slice(&mut chars);
unsafe {
// Safety: We write same chars -> we have same length
// Safety: We write correct UTF8 symbol by symbol
// Safety: There are not possible panics in this unsafe block
let mut bytes = s.as_bytes_mut();
for c in chars{
let bytes_written = c.encode_utf8(bytes).len();
bytes = &mut bytes[bytes_written..]
}
}
}
}
fn main(){
// ASCII
let mut s = "Hello".to_string();
reverse_str(&mut s);
println!("{}", s);
// Unicode
let mut s = "Авокадо 126".to_string();
reverse_str(&mut s);
println!("{}", s);
// Substring
let mut s = "Hello world".to_string();
reverse_str(&mut s[6..]);
println!("{}", s);
}
Output:
olleH
621 одаковА
Hello dlrow
此外,LLVM 成功地对该递归进行了尾部优化: https://rust.godbolt.org/z/95sqfM
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.