【Rust】CLIプログラムの書き方

これはかなり面白い。というか、参考にさせていただきます!

use std::{collections::HashMap, fs::OpenOptions};
use chrono::NaiveDate;
use clap::{Args, Parser, Subcommand};
use csv::{Writer, Reader, WriterBuilder};
use serde::{Deserialize, Serialize};

#[derive(Parser)]
#[clap(version = "1.0")]
struct App {
    #[clap(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    /// 新しい口座を作る
    New(NewArgs),
    /// 口座に入金する
    Deposit(DepositArgs),
    /// 口座から出金する
    Withdraw(WithdrawArgs),
    /// CSVからインポートする
    Import(ImportArgs),
    /// レポートを出力する
    Report(ReportArgs),
}

#[derive(Args)]
struct NewArgs {
    account_name: String,
}
impl NewArgs {
    fn run(&self) {
        let file_name = format!("{}.csv", self.account_name);
        let mut writer = Writer::from_path(file_name).unwrap();
        writer
            .write_record(["日付", "用途", "金額"])
            .unwrap();
        writer.flush().unwrap();
    }
}
#[derive(Args)]
struct DepositArgs {
    account_name: String,
    date: NaiveDate,
    usage: String,
    amount: u32,
}
impl DepositArgs {
    fn run(&self) {
        let open_option = OpenOptions::new()
            .write(true)
            .append(true)
            .open(format!("{}.csv", self.account_name))
            .unwrap();
        let mut writer = Writer::from_writer(open_option);
        writer
            .write_record([
                self.date.format("%Y-%m-%d").to_string(),
                self.usage.to_string(), 
                self.amount.to_string()])
            .unwrap();
        writer.flush().unwrap();
    }
}

#[derive(Args)]
struct WithdrawArgs {
    account_name: String,
    date: NaiveDate,
    usage: String,
    amount: u32,
}
impl WithdrawArgs {
    fn run(&self) {
        let open_option = OpenOptions::new()
            .write(true)
            .append(true)
            .open(format!("{}.csv", self.account_name))
            .unwrap();
        let mut writer = Writer::from_writer(open_option);
        writer
            .write_record([
                self.date.format("%Y-%m-%d").to_string(),
                self.usage.to_string(), 
                format!("-{}", self.amount),
            ])
            .unwrap();
        writer.flush().unwrap();
    }
}
#[derive(Args)]
struct ImportArgs {
    src_file_name: String,
    dst_account_name: String,
}
impl ImportArgs {
    fn run(&self) {
        let open_option = OpenOptions::new()
            .write(true)
            .append(true)
            .open(format!("{}.csv", self.dst_account_name))
            .unwrap();
        let mut writer = WriterBuilder::new()
            .has_headers(false)
            .from_writer(open_option);
        let mut reader = Reader::from_path(&self.src_file_name).unwrap();
        for result in reader.deserialize() {
            let record: Record = result.unwrap();
            writer.serialize(record).unwrap();
        }
    }
}
#[derive(Serialize, Deserialize)]
struct Record {
    日付: NaiveDate,
    用途: String,
    金額: i32,
}
#[derive(Args)]
struct ReportArgs {
    files: Vec<String>,
}
impl ReportArgs {
    fn run(&self) {
        let mut map = HashMap::new();
        for file in &self.files {
            let mut reader = Reader::from_path(file).unwrap();
            for result in reader.records() {
                let record = result.unwrap();
                let amount : i32 = record[2].parse().unwrap();
                let date: NaiveDate = record[0].parse().unwrap();
                let sum = map.entry(date.format("%Y-%m").to_string())
                    .or_insert(0);
                *sum += amount;
            }
        }
        println!("{:?}", map);
    }
}
fn main() {
    let args = App::parse();
    match args.command {
        Command::New(args) => args.run(),
        Command::Deposit(args) => args.run(),
        Command::Withdraw(args) => args.run(),
        Command::Import(args) => args.run(),
        Command::Report(args) => args.run(),
    }
}

【Rust】コマンド入力とargs

fn main() {

    let command_name = std::env::args().nth(0).unwrap_or("CLI".to_string());
    let name = std::env::args().nth(1).unwrap_or("WORLD".to_string());
    println!("Hello {} via {}!", name, command_name);
}

Running `target/debug/kakeibo arg1 arg2`
Hello arg1 via target/debug/kakeibo!

use clap::Parser;

#[derive(Parser)]
#[clap(version = "1.0")]
struct Args {
    arg1: String,
    arg2: String
}

fn main() {
    let _args = Args::parse();
}
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[clap(version = "1.0")]
struct App {
    #[clap(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    /// 新しい口座を作る
    New,
    /// 口座に入金する
    Deposit,
    /// 口座から出金する
    Withdraw,
    /// CSVからインポートする
    Import,
    /// レポートを出力する
    Report,
}

fn main() {
    let _args = App::parse();
}

$ ./target/debug/kakeibo -h
Usage: kakeibo

Commands:
new 新しい口座を作る
deposit 口座に入金する
withdraw 口座から出金する
import CSVからインポートする
report レポートを出力する
help Print this message or the help of the given subcommand(s)

Options:
-h, –help Print help
-V, –version Print version

【Rust】字句解析(Lexical analysis)の初級

use std::io::stdin;

fn main() {

    let mut memory = Memory {
        slots: vec![0.0; 10],
    }
    let mut prev_result: f64 = 0.0;
    for line in stdin().lines() {
        let line = line.unwrap();
        if line.is_empty() {
            break;
        }

        let tokens: Vec<&str> = line.split(char::is_whitespace).collect();

        let is_memory = tokens[0].starts_with("mem");
        if is_memory && tokens[0].ends_with('+') {
            add_and_print_memory(&mut memory, tokens[0], prev_result);
            continue;
        } else if is_memory && tokens[0].ends_with('-') {
            add_and_print_memory(&mut memory, tokens[0], -prev_result);
            continue;
        }

        let left = eval_token(tokens[0], &memory);
        let right = eval_token(tokens[2], &memory);
        let result = eval_expression(left, tokens[1], right);
        print_output(result);

        prev_result = result;
    }
}

struct Memory {
    slots: Vec<f64>,
}

fn add_and_print_memory(memory: &mut Memory, token: &str, prev_result: f64) {
    let slot_index: usize = token[3..token.len() - 1].parse().unwrap();
    memory.slots[slot_index] += prev_result;
    print_output(memory.slots[slot_index]);
}
fn eval_token(token: &str, memory: &Memory) -> f64 {
    if token.starts_with("mem") {
        let slot_index: usize = token[3..].parse().unwrap();
        memory.slots[slot_index]
    } else {
        token.parse().unwrap()
    }
}
fn eval_expression(left: f64, operator: &str, right: f64) -> f64 {
    match operator {
        "+" => left + right,
        "-" => left - right,
        "*" => left * right,
        "/" => left / right,
        _ => {
            unreachable!()
        }
    }
}
fn print_output(value: f64) {
    println!(" => {}", value);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.49s
Running `target/debug/app`
1 + 2
=> 3
mem1+
=> 3
3 + 4
=> 7
mem2-
=> -7
mem1 * mem2
=> -21

【Rust】ポーカー

このソース見た時、凄い感慨したんよね。絵柄と1~13の数字でdeckを作るところ。
pocker chaseの裏側ってどうやってんだろうずっと思ってたんだけど、これを見ると、カードの配り方や、最後の役完成時の判定をどうやってるかがわかる。素晴らしい!

use rand::seq::SliceRandom;

#[derive(Debug, Clone, Copy, PartialEq)]
enum Suit {
    Club,
    Diamond,
    Heart,
    Spade,
}

#[derive(Debug, Clone, Copy, PartialEq)]
struct Card {
    suit: Suit,
    rank: i32,
}

fn main() {
    let mut deck: Vec<Card> = Vec::new();
    let suits = [Suit::Club, Suit::Diamond, Suit::Heart, Suit::Spade];

    for suit in suits {
        for rank in 1..=13 {
            deck.push(Card {suit, rank});
        }
    }
    let mut rng = rand::thread_rng();
    deck.shuffle(&mut rng);
    

    let mut hand: Vec<Card> = Vec::new();
    for _ in 0..5 {
        hand.push(deck.pop().unwrap());
    }

    hand.sort_by(|a, b| a.rank.cmp(&b.rank));

    for (i, card) in hand.iter().enumerate() {
        println!("{:}: {:?} {:}", i +1, card.suit, card.rank);
    }

    println!("入れ替えたいカードの番号を入力してください(例: 1 2 3)");
    let mut input = String::new();
    std::io::stdin().read_line(&mut input).unwrap();

    let numbers : Vec<usize> = input.split_whitespace()
        .map(|x| x.parse().unwrap())
        .collect::<Vec<usize>>();
    for number in numbers {
        hand[number - 1] = deck.pop().unwrap();
    }

    hand.sort_by(|a, b| a.rank.cmp(&b.rank));

    for (i, card) in hand.iter().enumerate() {
        println!("{:}: {:?} {:}", i +1, card.suit, card.rank);
    }

    let suit = hand.first().unwrap().suit;
    let flash = hand.iter().all(|c| c.suit == suit);

    let mut count = 0;
    for i in 0..hand.len() - 1 {
        for j in i + 1..hand.len() {
            if hand[i].rank == hand[j].rank {
                count += 1;
            }
        }
    }

    if flash {
        println!("flash!");
    } else if count >= 3 {
        println!("スリーカード!");
    } else if count == 2 {
        println!("2ペア");
    } else if count == 1 {
        println!("1ペア");
    } else {
        println!("役なし...");
    }
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.62s
Running `target/debug/app`
1: Spade 3
2: Spade 4
3: Club 5
4: Diamond 8
5: Spade 13
入れ替えたいカードの番号を入力してください(例: 1 2 3)
1 2 3 4
1: Diamond 10
2: Club 10
3: Diamond 12
4: Heart 12
5: Spade 13
2ペア

【Rust】dbg!によるデバッグ

fn main() {
    println!("1 + 1 = ??");
    println!("??の値を入力してください。");
    let mut ans_input = String::new();
    std::io::stdin().read_line(&mut ans_input).unwrap();
    dbg!(ans_input);
}

Compiling app v0.1.0 (/home/vagrant/dev/rust/app)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s
Running `target/debug/app`
1 + 1 = ??
??の値を入力してください。
2
[src/main.rs:6:5] ans_input = “2\n”

【Rust】Result型によるエラー処理

fn main() {

    let value = check_negative(-32);
    println!("{:?}", value);

    let value = check_negative(10);
    println!("{:?}", value);
}

fn check_negative(num: i32) -> Result<i32, Box<dyn std::error::Error>> {
    if num < 0 {
        return Err("負の値です".into());
    }
    Ok(num)
}

Compiling app v0.1.0 (/home/vagrant/dev/rust/app)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.40s
Running `target/debug/app`
Err(“負の値です”)
Ok(10)

Box>はかなり頻繁に見るが、意味合いとしては
Box: ヒープ上にデータを格納するためのスマートポインタ
dyn: dynamicの略。実行時にトレイトの具体的な型を決めたいときに使う
error::Error: Rustの標準ライブラリのerrorモジュールにあるErrorトレイト

エラートレイトの何らかの値を返す
Resultを理解すると、エラー処理の書き方が変わってきますね。

【Rust】Option型

Option型は値がない可能性を示す。

fn main() {

    let mut values: Vec<Option<i32>> = Vec::new();

    values.push(Some(777));
    values.push(None);

    for value in values {
        match value {
            Some(_) => println!("It's threre, {}", value.unwrap()),
            None => println!("No value, it's None"),
        }
    }
}

Compiling app v0.1.0 (/home/vagrant/dev/rust/app)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
Running `target/debug/app`
It’s threre, 777
No value, it’s None

Option型を使う場合は、Some、Noneもセットで使うのね。
なるほど〜、結構基本的なところだな〜

【Rust】逆ポーランド記法

演算子を前に置くのをポーランド記法という 4 + 5 * 8 – 9 / 3 は、「- + 4 * 5 8 / 9 3」っと書く。
逆に、演算子を後ろに置くのを逆ポーランド記法という。「4 5 8 * + 9 3 / -」と書く。
逆ポーランド記法はスタックで処理しやすく、色々な場面で使われる。

fn calc(expression: String) {
    let mut stack: Vec<i32> = Vec::new();
    for i in expression.split(' ') {
        println!("{:?}", stack);
        if i == '+'.to_string() {
            let b = stack.pop().unwrap();
            let a = stack.pop().unwrap();
            stack.push(a + b);
        } else if i == '-'.to_string() {
            let b = stack.pop().unwrap();
            let a = stack.pop().unwrap();
            stack.push(a - b);
        } else if i == '*'.to_string() {
            let b = stack.pop().unwrap();
            let a = stack.pop().unwrap();
            stack.push(a * b);
        } else if i == '/'.to_string() {
            let b = stack.pop().unwrap();
            let a = stack.pop().unwrap();
            stack.push(a / b);
        } else {
            stack.push(i.parse::<i32>().unwrap());
        }
    }
    println!("{:?}", stack);
}

fn main() {
    calc("4 6 2 + * 3 1 - 5 * -".to_string());
}

[]
[4]
[4, 6]
[4, 6, 2]
[4, 8]
[32]
[32, 3]
[32, 3, 1]
[32, 2]
[32, 2, 5]
[32, 10]
[22]

なるほど、これは面白い。
コンパイラのOP_CODEでも同じようなことをやったような気がする。

【Rust】Boyer-Moore法

use std::collections::HashMap;

fn main() {
    let text = ['J','A','P','A','N','H','O','L','D','I','N','G',];
    let pattern = ['H','O','L','D'];

    let mut skip = HashMap::new();
    for i in 0..(pattern.len() - 1) {
        skip.insert(
            pattern[i],
            pattern.len() - i - 1,
        );
    }
    println!("{:?}", skip);

    let mut i = pattern.len() - 1;
    while i < text.len() {
        let mut flg = true;
        for j in 0..pattern.len() {
            if text[i-j] != pattern[pattern.len()-1-j] {
                flg = false;
                break;
            }
        }
        if flg == true {
            println!("{}", i - pattern.len() + 1);
            break;
        } else if skip.get(&text[i]).is_some(){
            i += skip[&text[i]];
        } else {
            i += pattern.len();
        }
    }
}

Compiling rust v0.1.0 (/home/vagrant/dev/algorithm/rust)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.24s
Running `target/debug/rust`
{‘H’: 3, ‘O’: 2, ‘L’: 1}
5

【Rust】文字列探索の力任せ法

線形ソートみたいな感じ
text[i + j] != pattern[j]とすることで、patterを一文字ずつ合致するか判定する。
regexもこれと同じアルゴリズムなんだろうか???

fn main() {
    let text = ['J','A','P','A','N','H','O','L','D','I','N','G',];
    let pattern = ['H','O','L','D'];

    for i in 0..text.len() {

        let mut flg = true;
        for j in 0..pattern.len() {
            if text[i + j] !=  pattern[j] {
                flg = false;
                break;
            }
        }
        if flg {
            println!("{}", i);
        }    
    }
}

Running `target/debug/rust`
5