2016-09-11 11 views
3

時には、プログラムが動的値を含むメッセージをユーザーにフレーズする方法はたくさんあります。たとえば:ランダムにフォーマット文字列を選択する方法

  • "{}分残っています。"
  • "{分}以内に完了する必要があります。

すべてのメッセージに単なる接頭辞または接尾辞として値が含まれているわけではありません。動的言語では、これは文字列フォーマットの論理的な仕事と思われます。

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

は次のようになります:

反復が望ましくないメディアの場合

(例えばスラックチャネル)のようなものを使用して出力し、各最終Stringを生産する非常に多くの異なるフレージングがあります

  • 退屈に毎回format!(/*...*/, x)と入力してください。
  • 1つの可能性がすべて選択される前に完全に生成され、他のものは破棄されるため、メモリ+クロックサイクルが無駄になります。

これらの欠点を回避する方法はありますか?

フォーマット文字列のコンパイル時の評価ではなく、ランダムに選択された&'static str(静的スライスから)を返してformat!に渡す関数が好ましい解決策でした。

+2

関連:[私はどのようにフォーマットでダイナミックフォーマット文字列を使用できますか?マクロ?](http://stackoverflow.com/q/32572486/155423)。 – Shepmaster

+2

数値を無作為に生成し、 'match'を使ってフォーマットする文字列を選ぶことを考えましたか?それは、すべての文字列を生成してからそれらを投げ捨てることを避けるでしょう。 – Aurora0001

答えて

5

錆は、関数内で関数を定義することをサポートします。関数ポインタのスライスを構築し、rand::sampleのうちの1つを選択してから、選択した関数を呼び出すことができます。

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) 
} 
+0

これは、私がRustのマクロ機能をすぐにもっと見るべき理由を示しています。ありがとう! – mmirate

4

私の提案は、不要な計算を避け、できるだけコンパクトにコードを維持するための一致を使用することです:

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

確かに、それは文字通りの数字を一致させるために少し醜いですおそらくそれを得ることができるのはおそらく最短です。複数の文字列を作成する

+1

メンテナンス性に関する懸念は、 'match'の選択肢数と' gen_range'の数字の分割になります。 – Shepmaster

+1

ええ、それは迷惑ですが、避けるのは難しいです。私は上限を 'max_choices'に設定し、反駁可能なパターンを' n> = max_choices'なら 'n 'に置き換えることで回避しようとしましたが、コンパイラは網羅的であるとは言えません([ (http://play.integer32.com/?gist=de8cf938ebc0bb5571bc3b406324627a))。 – Aurora0001

2

クロージャ(または関数ポインタ)を使用して回避するために、ストレートフォワードです:

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つの値の

  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. マクロは、少なくとも一つの引数を必要とします。これはデフォルトのメッセージとして使用されます。
関連する問題