Rust中是否有类似JavaScript的substr的方法?

e0bqpujr  于 2022-11-24  发布在  Java
关注(0)|答案(8)|浏览(183)

我查看了Rust docs for String,但找不到提取子字符串的方法。
Rust中是否有类似JavaScript的substr的方法?如果没有,您将如何实现它?

str.substr(start[, length])

最接近的可能是slice_unchecked,但它使用字节偏移量而不是字符索引,并标记为unsafe

ubof19bj

ubof19bj1#

对于字符,可以使用s.chars().skip(pos).take(len)

fn main() {
    let s = "Hello, world!";
    let ss: String = s.chars().skip(7).take(5).collect();
    println!("{}", ss);
}

不过要注意Unicode字符的定义。
对于字节,可以使用切片语法:

fn main() {
    let s = b"Hello, world!";
    let ss = &s[7..12];
    println!("{:?}", ss);
}
flvtvl50

flvtvl502#

可以在Chars迭代器上使用as_str方法,以便在进入迭代器之后返回&str切片。

let s = "Some text to slice into";
let mut iter = s.chars();
iter.by_ref().nth(start); // eat up start values
let slice = iter.as_str(); // get back a slice of the rest of the iterator

现在,如果您还想限制长度,首先需要确定length字符的字节位置:

let end_pos = slice.char_indices().nth(length).map(|(n, _)| n).unwrap_or(0);
let substr = &slice[..end_pos];

这可能有点绕路,但Rust并没有向您隐瞒任何可能占用CPU周期的事情。

kuhbmx9i

kuhbmx9i3#

以下代码执行子字符串和字符串切片,不会出现混乱或分配:

use std::ops::{Bound, RangeBounds};

trait StringUtils {
    fn substring(&self, start: usize, len: usize) -> &str;
    fn slice(&self, range: impl RangeBounds<usize>) -> &str;
}

impl StringUtils for str {
    fn substring(&self, start: usize, len: usize) -> &str {
        let mut char_pos = 0;
        let mut byte_start = 0;
        let mut it = self.chars();
        loop {
            if char_pos == start { break; }
            if let Some(c) = it.next() {
                char_pos += 1;
                byte_start += c.len_utf8();
            }
            else { break; }
        }
        char_pos = 0;
        let mut byte_end = byte_start;
        loop {
            if char_pos == len { break; }
            if let Some(c) = it.next() {
                char_pos += 1;
                byte_end += c.len_utf8();
            }
            else { break; }
        }
        &self[byte_start..byte_end]
    }
    fn slice(&self, range: impl RangeBounds<usize>) -> &str {
        let start = match range.start_bound() {
            Bound::Included(bound) | Bound::Excluded(bound) => *bound,
            Bound::Unbounded => 0,
        };
        let len = match range.end_bound() {
            Bound::Included(bound) => *bound + 1,
            Bound::Excluded(bound) => *bound,
            Bound::Unbounded => self.len(),
        } - start;
        self.substring(start, len)
    }
}

fn main() {
    let s = "abcdèfghij";
    // All three statements should print:
    // "abcdè, abcdèfghij, dèfgh, dèfghij."
    println!("{}, {}, {}, {}.",
        s.substring(0, 5),
        s.substring(0, 50),
        s.substring(3, 5),
        s.substring(3, 50));
    println!("{}, {}, {}, {}.",
        s.slice(..5),
        s.slice(..50),
        s.slice(3..8),
        s.slice(3..));
    println!("{}, {}, {}, {}.",
        s.slice(..=4),
        s.slice(..=49),
        s.slice(3..=7),
        s.slice(3..));
}
yduiuuwa

yduiuuwa4#

对于类似my_string.substring(start, len)的语法,可以编写一个自定义特征:

trait StringUtils {
    fn substring(&self, start: usize, len: usize) -> Self;
}

impl StringUtils for String {
    fn substring(&self, start: usize, len: usize) -> Self {
        self.chars().skip(start).take(len).collect()
    }
}

// Usage:
fn main() {
    let phrase: String = "this is a string".to_string();
    println!("{}", phrase.substring(5, 8)); // prints "is a str"
}
0kjbasz6

0kjbasz65#

oli_obk给出的解不处理字符串切片的最后一个索引。可以使用.chain(once(s.len()))修复。
这里的函数substr实现了一个带有错误处理的子字符串切片。如果传递给函数的索引无效,则返回字符串切片的有效部分,其中包含Err-variant。所有极端情况都应该正确处理。

fn substr(s: &str, begin: usize, length: Option<usize>) -> Result<&str, &str> {
    use std::iter::once;
    let mut itr = s.char_indices().map(|(n, _)| n).chain(once(s.len()));
    let beg = itr.nth(begin);
    if beg.is_none() {
        return Err("");
    } else if length == Some(0) {
        return Ok("");
    }
    let end = length.map_or(Some(s.len()), |l| itr.nth(l-1));
    if let Some(end) = end {
        return Ok(&s[beg.unwrap()..end]);
    } else {
        return Err(&s[beg.unwrap()..s.len()]);
    }
}
let s = "abc🙂";
assert_eq!(Ok("bc"), substr(s, 1, Some(2)));
assert_eq!(Ok("c🙂"), substr(s, 2, Some(2)));
assert_eq!(Ok("c🙂"), substr(s, 2, None));
assert_eq!(Err("c🙂"), substr(s, 2, Some(99)));
assert_eq!(Ok(""), substr(s, 2, Some(0)));
assert_eq!(Err(""), substr(s, 5, Some(4)));

请注意,这并不处理unicode字素簇。例如,"y̆es"包含4个unicode char,但包含3个字素簇。板条箱unicode-segmentation可解决此问题。如果部分

let mut itr = s.char_indices()...

被替换为

use unicode_segmentation::UnicodeSegmentation;
let mut itr = s.grapheme_indices(true)...

然后还有以下作品

assert_eq!(Ok("y̆"), substr("y̆es", 0, Some(1)));
c2e8gylq

c2e8gylq6#

我建议您使用板条箱substring。(如果您想学习如何正确地执行此操作,请查看its source code。)

sq1bmfud

sq1bmfud7#

我对 rust 不是很有经验,但我试过了。如果有人能纠正我的答案,请不要犹豫。

fn substring(string:String, start:u32, end:u32) -> String {
    let mut substr = String::new();
    let mut i = start;
    while i < end + 1 {
        substr.push_str(&*(string.chars().nth(i as usize).unwrap().to_string()));
        i += 1;
    }
    return substr;
}

这是一个playground

dwbf0jvd

dwbf0jvd8#

您也可以使用.to_string()[ <range> ]
这个例子取原始字符串的一个不可变的切片,然后改变该字符串以证明原始切片被保留。

let mut s: String = "Hello, world!".to_string();

let substring: &str = &s.to_string()[..6];

s.replace_range(..6, "Goodbye,");

println!("{}   {} universe!", s, substring);

//    Goodbye, world!   Hello, universe!

相关问题