サブロウ丸

Sabrou-mal サブロウ丸

主にプログラミングと数学

Master mind in Rust; part 4 argument parser

今回やること

  • Clapを使って引数をパースする

Clap

Clapは引数をパースするための(現状、デファクトの)ライブラリです。 master mindの場合、ピンの個数や色の個数、色の重複を許すかどうか、といったハイパーパラメータを今はソースコードに直接記載していますが、それを引数で指定できるようにします。

Cargo.tomlの変更

[dependencies]clapを追加します。最新のversionはhttps://docs.rs/clap/latest/clap/で調べられます。今は4.5.1が最新のようですね。

[dependencies]
clap = {version = "4.5.1", features = ["derive"]}

parserの作成

#[derive(Parser)]を使って、引数をパースするための構造体を作成します。

  • #[command(about)]は、cargo run -- --helpで表示される説明文です。aboutの他にauthorやversionも指定できます。
  • #[arg(xxx)]は、引数の設定です。
    • help: cargo run -- --helpで表示される説明文です。
    • short: 短縮系でのオプションの指定ができます。
    • long: 長いオプションの指定ができます。
    • default_value_t: デフォルト値を指定します。tは型を指定します。
use clap::Parser;

#[derive(Parser)]
#[command(about)]
struct Args {
    #[arg(default_value_t = 6, help = "number of colors")]
    color_num: u8,
    #[arg(default_value_t = 4, help = "number of pins")]
    pin_num: u8,
    #[arg(short, long, help = "codes do not have duplicate colors")]
    non_duplicate: bool,
}

引数の受け取りについてです。

fn main() {
    // parse command line arguments
    let cli = Args::parse();
    // cli.color_num
    // cli.pin_num
    // cli.non_duplicate のようにして引数を受け取ります。

私はこのようなハイパーパラメータを管理するContext構造体を作成し、その中に引数の情報を格納するようにしました.

#[derive(Debug)]
struct Context {
    color_num: usize,
    pin_num: usize,
    duplicate: bool,
}

fn main() {
    // parse command line arguments
    let cli = Args::parse();
    let context = Context {
        color_num: cli.color_num,
        pin_num: cli.pin_num,
        duplicate: !cli.non_duplicate,
    };
    println!("{:?}", context);

コード列挙の作成

さて上記のargument parserで任意のピンの個数と色の個数を指定できるようになりました。今まではソースコードに直接手入力していましたが、それをプログラムで列挙しましょう。次のように関数型プログラミングのような形で列挙することができます。

use itertools::Itertools;

// enumerate all codes according to context
fn get_all_codes(context: &Context) -> CodeSet {
    match context.duplicate {
        true => (0..context.pin_num)
            .map(|_| 0..context.color_num)
            .multi_cartesian_product()
            .collect(),
        false => (0..context.color_num)
            .permutations(context.pin_num)
            .collect(),
    }
}

重複がある場合とない場合で列挙する方法が異なります。

重複がない場合

(0..context.color_num)
    .permutations(context.pin_num)
    .collect()

これだけ見ても直感的に理解するのは難しいですが、順に見ていくとこうなります。

  • (0..context.color_num)で0からcontext.color_num-1までの数字を列挙します。
  • permutations(context.pin_num)でその数字の中からcontext.pin_num個の数字を選ぶ組み合わせを列挙します。
  • collect()でそれを集めます。

重複がある場合

(0..context.pin_num)
    .map(|_| 0..context.color_num)
    .multi_cartesian_product()
    .collect()
  • (0..context.pin_num)で0からcontext.pin_num-1までの数字を列挙します。
  • map(|_| 0..context.color_num)でそれぞれの数字に対して0からcontext.color_num-1までの数字を列挙します。
    • 例えばpin_num=2, color_num=3の場合、(0..2).map(|_| 0..3)は、[0..3, 0..3]を生成するイテレーターとなります。
    • (0..context.pin_num).map(|_| 0..context.color_num)は、vec![0; context.pin_num].iter().map(|_| 0..context.color_num)と同じです。
  • multi_cartesian_product()でそれらの数字の組み合わせを列挙します。
  • collect()でそれを集めます。

コード

参考

関連