如何随机 select 一个格式字符串
How to randomly select a format string
有时,程序可以通过多种方式向其用户发送包含动态值的消息。例如:
- “还剩 {} 分钟。”
- "You need to finish in less than {} minutes."
并非所有消息都包含仅作为前缀或后缀的值。在动态语言中,这似乎是字符串格式化的合乎逻辑的任务。
对于不希望出现重复性的媒体(例如 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!
的函数将是首选解决方案。
我的建议是使用匹配来避免不必要的计算并使代码尽可能紧凑:
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!()
};
不可否认,从字面上匹配数字有点难看,但这可能是你能得到的最短的。
通过使用闭包(或函数指针)可以直接避免创建多个字符串:
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));
}
备注:
choose
而不是 sample
一个值。
- 不需要
Vec
;数组应该没问题。
这不太可能改善 "beautiful" 方面。宏可以消除苦差事:
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));
}
备注:
- 宏需要至少一个参数;这将用作默认消息。
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)
}
这解决了原始解决方案的 "wasteful" 方面,但没有解决 "tedious" 方面的问题。我们可以通过使用宏来减少重复的数量。请注意,在函数内定义的宏也是该函数的局部宏!这个宏利用 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)
}
有时,程序可以通过多种方式向其用户发送包含动态值的消息。例如:
- “还剩 {} 分钟。”
- "You need to finish in less than {} minutes."
并非所有消息都包含仅作为前缀或后缀的值。在动态语言中,这似乎是字符串格式化的合乎逻辑的任务。
对于不希望出现重复性的媒体(例如 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!
的函数将是首选解决方案。
我的建议是使用匹配来避免不必要的计算并使代码尽可能紧凑:
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!()
};
不可否认,从字面上匹配数字有点难看,但这可能是你能得到的最短的。
通过使用闭包(或函数指针)可以直接避免创建多个字符串:
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));
}
备注:
choose
而不是sample
一个值。- 不需要
Vec
;数组应该没问题。
这不太可能改善 "beautiful" 方面。宏可以消除苦差事:
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));
}
备注:
- 宏需要至少一个参数;这将用作默认消息。
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)
}
这解决了原始解决方案的 "wasteful" 方面,但没有解决 "tedious" 方面的问题。我们可以通过使用宏来减少重复的数量。请注意,在函数内定义的宏也是该函数的局部宏!这个宏利用 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)
}