【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

【Rust】Axumでpostgresのmigrationを実行したい

tokio-postgres-migrationを使用します。
https://crates.io/crates/tokio-postgres-migration

Cargo.toml

tokio-postgres-migration = "0.1.0"

./asset/0001-create-table-books-up.sql

CREATE TABLE books ( 
    id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    name varchar(10),
    body text NOT NULL
);
use tokio_postgres_migration::Migration;
//
const SCRIPTS_UP: [(&str, &str); 2] = [
    (
        "0001-create-table-books",
        include_str!(".././asset/0001-create-table-books-up.sql"),
    ),
    (
        "0002-create-table-pets",
        include_str!(".././asset/0002-create-table-pets-up.sql"),
    )
];
//

async fn handle_migration() -> impl IntoResponse {
    let _ = create_table().await;
    (StatusCode::OK, format!("Migration completed"))
}

async fn psql_connect() -> Result<Client, Box<dyn std::error::Error>>  {
    let (client, connection) = tokio_postgres::connect("host=localhost user=postgres password=hogehoge", NoTls).await?;

    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });
    Ok(client)
}

async fn create_table() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = psql_connect().await.unwrap();
    let migration = Migration::new("table_to_keep_migrations".to_string());
    migration.up(&mut client, &SCRIPTS_UP).await?;
    Ok(())
}

これだと、booksとpetsは作られるんだけど、Migration::new(“${table_name}”)のtable_nameもselfで作られてしまうな…
https://github.com/jdrouet/tokio-postgres-migration/blob/main/src/lib.rs

migrationがどういうものかは理解した。

【Rust】ワークスペースによる開発

引き続き「RustによるWebアプリケーション開発(豊田優貴著)」を参考に、ワークスペースによる開発を学んでいきます。
https://github.com/rust-web-app-book/rusty-book-manager

$ tree
.
├── Cargo.lock
├── Cargo.toml
├── compose.yaml
├── Dockerfile
├── makefile.toml
├── src
│ └── main.rs
└── target

sr/bin/app.rs にmain関数を記載する。main.rsは削除

### ワークスペースメンバーの作成
$ cargo new –lib api
$ cargo new –lib kernel
$ cargo new –lib adapter
$ cargo new –lib shared
$ cargo new –lib registry

### ワークスペースのメンバーを定義
Cargo.toml

[[bin]]
name = "app"
path = "src/bin/app.rs"

[workspace]
members = ["api", "kernel", "adapter", "shared", "registry"]

[workspace.dependencies]
adapter = { path = "./adapter" }
api = { path = "./api" }
kernel = { path = "./kernel" }
shared = { path = "./shared" }
registry = { path = "./registry" }
anyhow = "1.0.75"
axum = {version = "0.7.5", features = ["macros"]}
tokio = { version = "1.37.0", features = ["full"]}

[dependencies]
adapter.workspace = true
api.workspace = true
registry.workspace = true
shared.workspace = true
anyhow.workspace = true
tokio.workspace = true
axum.workspace = true

[dev-dependencies]
rstest = "0.18.2"

なるほど、なかなか複雑やわ ワークスペースの開発方法はもうちょっと勉強する必要がある。。

【Rust】tokio_postgresの接続を外出しにする

「RustによるWebアプリケーション開発(豊田優貴著)」を読むと、axumのルーティングでwith_state(conn_pool)で接続情報を持たせてますね。まぁそういう方法もありかと思いますが、今回は、関数で呼び出す方法を採用することにした。
参考のソースコード:
https://github.com/kingluo/tokio-postgres-hello-world/blob/master/src/main.rs

async fn psql_connect() -> Result<Client, Box<dyn std::error::Error>>  {
    let (client, connection) = tokio_postgres::connect("host=localhost user=postgres password=hogehoge", NoTls).await?;

    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });
    Ok(client)
}

async fn check_users()-> Result<(), Box<dyn std::error::Error>>  {
    
    let client = psql_connect().await.unwrap();
    for row in client.query("SELECT id, username, password From users", &[]).await? {
        let id: i32 = row.get(0);
        let name: String = row.get(1);
        let password: String = row.get(2);

        println!("{} {} {}", id, name, password);
    }
    Ok(())
}