【问题标题】:Is there a method like JavaScript's substr in Rust?Rust 中是否有类似 JavaScript 的 substr 的方法?
【发布时间】:2016-09-06 13:50:30
【问题描述】:

我查看了 Rust docs for String,但找不到提取子字符串的方法。

Rust 中是否有类似 JavaScript 的 substr 的方法?如果没有,您将如何实施?

str.substr(start[, length])

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

【问题讨论】:

    标签: string substring rust


    【解决方案1】:

    对于字符,可以使用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);
    }
    

    【讨论】:

    【解决方案2】:

    您可以使用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。

    【讨论】:

    • “不向你隐藏任何可能占用 CPU 周期的东西” - 你能解释一下为什么 substr 可能比它拥有的任何修剪功能更昂贵吗?
    • 嗯...修剪功能有望摆脱他们遇到的所有空白。根据定义,这是一个 O(n) 操作。但是使用substr 方法,用户可能会认为它是O(1),因为他们正在输入索引。
    • but Rust is not hiding anything from you that might take up CPU cycles. 这不是真的,因为Vec::insert 存在
    【解决方案3】:

    对于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"
    }
    

    【讨论】:

      【解决方案4】:

      此代码执行子字符串化和字符串切片,没有恐慌也没有分配:

      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..));
      }
      

      【讨论】:

        【解决方案5】:

        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)));
        

        【讨论】:

          【解决方案6】:

          您也可以使用.to_string()[ &lt;range&gt; ]

          此示例获取原始字符串的不可变切片,然后对该字符串进行变异以证明原始切片被保留。

          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!
          

          【讨论】:

          • 这不像 JavaScript:"ウィキペディアへようこそ".substr(1, 3) vs &amp;"ウィキペディアへようこそ"[1..3]。一个“有效”,另一个无效。
          • @Shepmaster 正确。这不适用于所有角色。 skip.take.collect 方法是理想的。
          【解决方案7】:

          我建议你使用箱子substring。 (如果您想了解如何正确执行此操作,请查看 its source code。)

          【讨论】:

          • 认真的吗?我们必须导入一个包才能获取一个子字符串?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-12-25
          • 1970-01-01
          • 2013-06-07
          • 2011-10-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多