【发布时间】:2016-09-06 13:50:30
【问题描述】:
我查看了 Rust docs for String,但找不到提取子字符串的方法。
Rust 中是否有类似 JavaScript 的 substr 的方法?如果没有,您将如何实施?
str.substr(start[, length])
最接近的可能是slice_unchecked,但它使用字节偏移量而不是字符索引并标记为unsafe。
【问题讨论】:
我查看了 Rust docs for String,但找不到提取子字符串的方法。
Rust 中是否有类似 JavaScript 的 substr 的方法?如果没有,您将如何实施?
str.substr(start[, length])
最接近的可能是slice_unchecked,但它使用字节偏移量而不是字符索引并标记为unsafe。
【问题讨论】:
对于字符,可以使用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);
}
【讨论】:
chars(),谢谢!是否也可以将 char 索引映射到字节偏移量并从中创建一个切片?
str::char_indices。 doc.rust-lang.org/std/primitive.str.html#method.char_indices
b"Hello, world!" ?
您可以使用Chars 迭代器上的as_str 方法在您踩到迭代器后取回&str 切片。所以要跳过第一个start 字符,你可以调用
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 周期的东西。也就是说,我想知道为什么还没有提供 substr 方法的 crate。
【讨论】:
substr 方法,用户可能会认为它是O(1),因为他们正在输入索引。
but Rust is not hiding anything from you that might take up CPU cycles. 这不是真的,因为Vec::insert 存在
对于my_string.substring(start, len)-like 语法,您可以编写自定义特征:
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"
}
【讨论】:
此代码执行子字符串化和字符串切片,没有恐慌也没有分配:
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..));
}
【讨论】:
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 chars,但包含 3 个字素簇。板条箱unicode-segmentation 解决了这个问题。如果部分正确处理 Unicode 字素簇
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)));
【讨论】:
您也可以使用.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!
【讨论】:
"ウィキペディアへようこそ".substr(1, 3) vs &"ウィキペディアへようこそ"[1..3]。一个“有效”,另一个无效。
skip.take.collect 方法是理想的。
我建议你使用箱子substring。 (如果您想了解如何正确执行此操作,请查看 its source code。)
【讨论】: