【问题标题】:Running a number of consecutive replacements on the same string在同一字符串上运行多个连续替换
【发布时间】:2014-12-15 03:21:21
【问题描述】:

我找到了这个子字符串替换的例子:

use std::str;
let string = "orange";
let new_string = str::replace(string, "or", "str");

如果我想在同一个字符串上运行多个连续替换,出于清理目的,如何在不为每个替换分配新变量的情况下做到这一点?

如果您要编写惯用的 Rust,您将如何编写多个链式子字符串替换?

【问题讨论】:

  • 您需要的不仅仅是逐字节替换吗?

标签: rust


【解决方案1】:

regex engine 可用于对字符串进行多次替换的单次传递,但如果这实际上更高效,我会感到惊讶:

extern crate regex;

use regex::{Captures, Regex};

fn main() {
    let re = Regex::new("(or|e)").unwrap();
    let string = "orange";
    let result = re.replace_all(string, |cap: &Captures| {
        match &cap[0] {
            "or" => "str",
            "e" => "er",
            _ => panic!("We should never get here"),
        }.to_string()
    });
    println!("{}", result);
}

【讨论】:

  • 从 1.0.5 版的 regex crate 开始,match &cap[0] { ... }.to_string() 中的 .to_string() 不再需要,因为 Regex::replace_all 闭包可以返回任何 T: AsRef<str>,而不仅仅是 String。所以它可以直接返回字符串文字(&'static str)。
【解决方案2】:

如何编写多个链式子串替换?

我会按照要求去做:

fn main() {
    let a = "hello";
    let b = a.replace("e", "a").replace("ll", "r").replace("o", "d");
    println!("{}", b);
}

如果您问的是如何进行多个并发替换,只通过一次字符串,那么它确实会变得更加更难。

这确实需要为每个replace 调用分配新内存,即使不需要替换。 replace 的替代实现可能会返回一个 Cow<str>,它仅在发生替换时包含拥有的变体。一个 hacky 的实现可能如下所示:

use std::borrow::Cow;

trait MaybeReplaceExt<'a> {
    fn maybe_replace(self, needle: &str, replacement: &str) -> Cow<'a, str>;
}

impl<'a> MaybeReplaceExt<'a> for &'a str {
    fn maybe_replace(self, needle: &str, replacement: &str) -> Cow<'a, str> {
        // Assumes that searching twice is better than unconditionally allocating
        if self.contains(needle) {
            self.replace(needle, replacement).into()
        } else {
            self.into()
        }
    }
}

impl<'a> MaybeReplaceExt<'a> for Cow<'a, str> {
    fn maybe_replace(self, needle: &str, replacement: &str) -> Cow<'a, str> {
        // Assumes that searching twice is better than unconditionally allocating
        if self.contains(needle) {
            self.replace(needle, replacement).into()
        } else {
            self
        }
    }
}

fn main() {
    let a = "hello";
    let b = a.maybe_replace("e", "a")
        .maybe_replace("ll", "r")
        .maybe_replace("o", "d");
    println!("{}", b);

    let a = "hello";
    let b = a.maybe_replace("nope", "not here")
        .maybe_replace("still no", "i swear")
        .maybe_replace("but no", "allocation");
    println!("{}", b);
    assert_eq!(b.as_ptr(), a.as_ptr());
}

【讨论】:

  • 你如何从str::replace(string, "or", "str"); 到能够在字符串对象上链接方法调用?也就是说,为什么会这样?
  • 我不确定我是否理解...您可以直接称呼它:"hello".replace("h", "y")。
  • 这是否类似于 D 如何将method(str, arg) 变成str.method(arg)?如果您不熟悉 D,编译器会在编译时透明地将后者转换为前者。
  • 我认为 D 将其称为通用函数调用语法 (UFCS),Rust 有 称为 UFCS,但据我所知,它们是不同的。在这种情况下,replace 有两个定义:free function 和 trait method。
【解决方案3】:

标准库中没有办法做到这一点;根据许多因素,在如何做的大量变化中正确处理是一件棘手的事情。您需要自己编写这样的函数。

【讨论】:

  • 我知道在 Ruby 中,我可以将 gsub 链接在一起,并且会做正确的事情。如果替换函数是字符串对象的方法,我认为 Rust 也可以这样做。但事实并非如此。如果你要编写惯用的 Rust,不管这意味着什么,你会为每一步创建一个新变量吗?
  • 这取决于我在做什么以及替换的性质。是按字符翻译吗?所有从/到对的大小是否相等?那里有多少?在我知道性能将成为问题之前,我总是倾向于将它写成多个链式替换。
【解决方案4】:

我不会为此使用正则表达式或.replace().replace().replace() 或.maybe_replace().maybe_replace().maybe_replace()。他们都有很大的缺陷。

  • 正则表达式可能是最合理的选择,但如果您完全可以避免正则表达式,则它们只是一个可怕的可怕想法。如果您的模式来自用户输入,那么您将不得不处理转义它们,这是一场安全噩梦。
  • .replace().replace().replace() 很糟糕,原因显而易见。
  • .maybe_replace().maybe_replace().maybe_replace() 只比这稍微好一点,因为它只在模式不匹配时提高效率。如果它们都匹配,它并不能避免重复分配,在这种情况下实际上更糟,因为它搜索字符串两次。

有一个更好的解决方案:使用the AhoCarasick crate。自述文件中甚至还有一个示例:

use aho_corasick::AhoCorasick;

let patterns = &["fox", "brown", "quick"];
let haystack = "The quick brown fox.";
let replace_with = &["sloth", "grey", "slow"];

let ac = AhoCorasick::new(patterns);
let result = ac.replace_all(haystack, replace_with);
assert_eq!(result, "The slow grey sloth.");

出于消毒目的

我还应该说,将“坏”字符串列入黑名单是完全错误的清理方法。

【讨论】:

    猜你喜欢
    • 2021-11-07
    • 2010-10-23
    • 2022-01-01
    • 2020-08-31
    • 1970-01-01
    • 2015-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多