簡體   English   中英

如何使用遞歸就地反轉 Rust 字符串?

[英]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,這意味着

  1. 代碼點沒有固定長度
  2. 沒有一個關於應該交換什么單位的定義。

如果您只想交換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 您可以嘗試轉換數據類型,但您可能必須以不同的方式編寫程序。

我不確定您為什么要嘗試使用遞歸來做到這一點,但我敢肯定您有這樣做的理由;)

我做了一個工作演示,展示了如何在不使用切片但僅使用Stringchar的情況下天真地做到這一點:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM