【问题标题】:How to randomly select a format string如何随机选择格式字符串
【发布时间】:2016-09-11 14:37:23
【问题描述】:

有时,程序可以通过多种方式向用户发送包含动态值的消息。例如:

  • “还剩{} 分钟。”
  • “您需要在 {} 分钟内完成。”

并非所有消息都包含仅作为前缀或后缀的值。在动态语言中,这似乎是字符串格式化的逻辑任务。

对于不希望出现重复性的媒体(例如 Slack 频道),有很多不同的措辞导致产生每个最终的 String 以使用以下方式输出:

pub fn h(x: usize) -> String {
    rand::sample(rand::thread_rng(), vec![
        format!("{} minutes remain.", x),
        format!("Hurry up; only {} minutes left to finish.", x),
        format!("Haste advisable; time ends in {}.", x),
        /* (insert many more elements here) */
    ], 1).first().unwrap_or(format!("{}", x))
}

应该是:

  • 对于作者来说很乏味,因为每次都要输入format!(/*...*/, x)。
  • 浪费内存+时钟周期,因为在选择一个之前,每一个可能性都是完全生成的,而丢弃其他的。

有没有办法避免这些缺点?

如果不是为了格式字符串的编译时评估,返回随机选择的&'static str(来自静态切片)以传递到format! 的函数将是首选解决方案。

【问题讨论】:

标签: random rust string-formatting


【解决方案1】:

Rust 支持在函数内部定义函数。我们可以构建一片函数指针,让rand::sample从中挑选一个,然后调用选定的函数。

extern crate rand;

use rand::Rng;

pub fn h(x: usize) -> String {
    fn f0(x: usize) -> String {
        format!("{} minutes remain.", x)
    }

    fn f1(x: usize) -> String {
        format!("Hurry up; only {} minutes left to finish.", x)
    }

    fn f2(x: usize) -> String {
        format!("Haste advisable; time ends in {}.", x)
    }

    let formats: &[fn(usize) -> String] = &[f0, f1, f2];
    (*rand::thread_rng().choose(formats).unwrap())(x)
}

这解决了原始解决方案的“浪费”方面,而不是“乏味”方面。我们可以通过使用宏来减少重复次数。请注意,在函数中定义的宏也是该函数的本地函数!该宏利用 Rust 的卫生宏定义了多个名为 f 的函数,因此我们在使用宏时无需为每个函数提供名称。

extern crate rand;

use rand::Rng;

pub fn h(x: usize) -> String {
    macro_rules! messages {
        ($($fmtstr:tt,)*) => {
            &[$({
                fn f(x: usize) -> String {
                    format!($fmtstr, x)
                }
                f
            }),*]
        }
    }

    let formats: &[fn(usize) -> String] = messages!(
        "{} minutes remain.",
        "Hurry up; only {} minutes left to finish.",
        "Haste advisable; time ends in {}.",
    );
    (*rand::thread_rng().choose(formats).unwrap())(x)
}

【讨论】:

    【解决方案2】:

    我的建议是使用匹配来避免不必要的计算并保持代码尽可能紧凑:

    use rand::{thread_rng, Rng};
    
    let mut rng = thread_rng();
    let code: u8 = rng.gen_range(0, 5);
    let time = 5;
    let response = match code {
        0 => format!("Running out of time! {} seconds left", time),
        1 => format!("Quick! {} seconds left", time),
        2 => format!("Hurry, there are {} seconds left", time),
        3 => format!("Faster! {} seconds left", time),
        4 => format!("Only {} seconds left", time),
        _ => unreachable!()
    };
    

    (Playground link)

    诚然,从字面上匹配数字有点难看,但它可能是你能得到的最短的数字。

    【讨论】:

    • 我的可维护性问题是match 中的选择数量和gen_range 中的数量的拆分。
    • 是的,这很烦人,但很难避免。我试图通过将上限设置为max_choices 并用n if n >= max_choices 替换无可辩驳的模式来解决这个问题,但编译器无法判断它是详尽无遗的(example of what I mean)。
    【解决方案3】:

    使用闭包(或函数指针)可以直接避免创建多个字符串:

    extern crate rand;
    
    use rand::Rng;
    
    pub fn h(x: usize) -> String {
        let messages: &[&Fn() -> String] = &[
            &|| format!("{} minutes remain.", x),
            &|| format!("Hurry up; only {} minutes left to finish.", x),
            &|| format!("Haste advisable; time ends in {}.", x),
        ];
        let default_message = || format!("{}", x);
    
        rand::thread_rng().choose(messages).unwrap_or(&&(&default_message as &Fn()->String))()
    }
    
    fn main() {
        println!("{}", h(1));
    }
    

    注意事项:

    1. choose 而不是 sample 一个值。
    2. 不需要Vec;数组应该没问题。

    这不太可能改善“美丽”方面。宏可以消除苦差事:

    extern crate rand;
    
    macro_rules! messages {
        {$default: expr, $($msg: expr,)*} => {
            use rand::Rng;
    
            let messages: &[&Fn() -> String] = &[
                $(&|| $msg),*
            ];
            let default_message = || $default;
    
            rand::thread_rng().choose(messages).unwrap_or(&&(&default_message as &Fn() -> String))()
        }
    }
    
    pub fn h(x: usize) -> String {
        messages! {
            format!("{}", x),
            format!("{} minutes remain.", x),
            format!("Hurry up; only {} minutes left to finish.", x),
            format!("Haste advisable; time ends in {}.", x),
        }
    }
    
    fn main() {
        println!("{}", h(1));
    }
    

    注意事项:

    1. 宏需要至少一个参数;这将用作默认消息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-04
      • 1970-01-01
      • 2013-02-21
      • 2020-06-07
      • 1970-01-01
      • 2014-04-03
      • 1970-01-01
      相关资源
      最近更新 更多