【Rust】TcpListenerとSocket

TcpListener
https://docs.rs/tokio/latest/tokio/net/struct.TcpListener.html

TcpListnerは元々Asyncにラップされているから、シングルスレッドでは使えない。

use tokio::{
    io::{AsyncReadExt, AsyncWriteExt},
    net::TcpListener,
};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let addr = "0.0.0.0:8080";
    let listener = TcpListener::bind(addr).await?;

    loop {
        match listener.accept().await {
            Ok((mut socket, _)) => {
                let mut buf = Vec::with_capacity(4096);
                socket.read_buf(&mut buf).await?;

                let msg = String::from_utf8(buf).expect("failed to convert str");
                println!("{msg}");

                socket.write(msg.as_bytes()).await?;
            }
            Err(err) => {
                println!("{err:?}");
            }   
        };
    }
}

Compiling parallel v0.1.0 (/home/vagrant/dev/rust/parallel)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.74s
Running `target/debug/parallel`
GET / HTTP/1.1
Host: 192.168.33.10:8080
User-Agent: curl/7.81.0
Accept: */*

ncコマンドで接続できる
$ nc 192.168.33.10 8080
hello
hello

【Rust】2進数への変換と2進数から10進数への変換

11を2進数にすると、
11/2 = 5 余り1
5/2 = 2 余り1
2/2 = 1 余り0
1/2 = 0 余り1

余りを先頭に足していく[1101]が答えになる。
それをそのままコードに落とし込む。こういう処理はfor文よりもwhileの方が向いている。なお、基数の値は、2進数から3、4と変えてもきちんと計算できる。

fn main() {
    let mut result = String::new();

    let mut target = 11;
    static cardinal: u32 = 2;
    
    while target >= cardinal {
        let rest = target % cardinal;
        result = format!("{}{}", rest.to_string(), result); 
        target = target / cardinal;
    }
    result = format!("{}{}", target.to_string(), result); 
    println!("{}", result);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.23s
Running `target/debug/rust`
1011

2進数から10進数への変換

fn main() {
    to_cardinal(2, 55);

}

fn from_cardinal(cardinal: u32, t: u32){
    let target:String = t.to_string();
    let digit = target.chars().count();

    let mut result = 0;

    for i in 0..digit {
        let base = cardinal.pow((digit - i - 1).try_into().unwrap());
        let s = target.chars().nth(i).unwrap();
        let num = s.to_digit(10).unwrap() * base;
        result = result + num;
    }
    println!("{}", result);
}

うーん、to_cardinalがなんか違うような気がする
なお、rustには基数変換のライブラリが多数ある模様

【Rust】Reader Writer Lock

use std::sync::RwLock の Read, Write
https://doc.rust-lang.org/stable/std/sync/struct.RwLock.html

概念は結構複雑なんだけど、なんかすげ〜簡単に書いてるな…

use std::sync::{Arc, RwLock};
use std::thread;

struct User {
    name: String,
    age: u32,
}

fn main() {

    let user = Arc::new(RwLock::new(User {
        name: String::from("Alice"),
        age: 30,
    }));

    let mut handles = vec![];

    for i in 0..10 {
        let data_clone = Arc::clone(&user);
        let handle = thread::spawn(move || {
            let shared = data_clone.read().unwrap();
            println!("読み取りスレッド {}: {}は{}です", i, shared.name, shared.age);
        });
        handles.push(handle);
    }

    for i in 0..5 {
        let data_clone = Arc::clone(&user);
        let handle = thread::spawn(move|| {
            let mut shared = data_clone.write().unwrap();
            shared.age += 1;
            shared.name = format!("Alice({})", i);
            println!("書き込みスレッド {}: カウンターを更新しました", i);            
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    let final_state = user.read().unwrap();
    println!("最終状態: {} は{}歳です", final_state.name, final_state.age);
}

Compiling parallel v0.1.0 (/home/vagrant/dev/rust/parallel)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.37s
Running `target/debug/parallel`
読み取りスレッド 0: Aliceは30です
読み取りスレッド 4: Aliceは30です
読み取りスレッド 2: Aliceは30です
読み取りスレッド 5: Aliceは30です
書き込みスレッド 3: カウンターを更新しました
書き込みスレッド 4: カウンターを更新しました
書き込みスレッド 2: カウンターを更新しました
書き込みスレッド 1: カウンターを更新しました
書き込みスレッド 0: カウンターを更新しました
読み取りスレッド 1: Alice(0)は35です
読み取りスレッド 8: Alice(0)は35です
読み取りスレッド 9: Alice(0)は35です
読み取りスレッド 3: Alice(0)は35です
読み取りスレッド 7: Alice(0)は35です
読み取りスレッド 6: Alice(0)は35です
最終状態: Alice(0) は35歳です

【Rust】Producer, Consumer問題をRustで書いてみる…

スレッド自体はProducerとConsumerで同時に走ってるんだけど、なんか全然違うなぁ…

pub static SIZE: u32 = 5;
static slot: Mutex<Vec<i32>> = Mutex::new(Vec::new());

fn work() {
    for i in 0..10 {
        let producer_handle = thread::spawn(move || {
            if slot.lock().unwrap().len() < SIZE.try_into().unwrap() {
                slot.lock().unwrap().push(1);
                println!("Producer {} work: {:?}", i, slot);
                thread::sleep(Duration::from_millis(500));
            } else {
                for j in 0..5 {
                    if slot.lock().unwrap()[j] == 0 {
                        slot.lock().unwrap()[j] = 1;
                        println!("Producer {} work: {:?}", i, slot);
                        thread::sleep(Duration::from_millis(500));
                    } 
                }
            }
            
        });

        let consumer_handle = thread::spawn(move || {
            if slot.lock().unwrap().len() > 0 {
                if slot.lock().unwrap()[0] == 1 {
                    slot.lock().unwrap()[0] = 0;
                    thread::sleep(Duration::from_millis(700));
                    println!("Consumer {} work: {:?}", i, slot);
                } else if slot.lock().unwrap()[0] == 1 {
                    slot.lock().unwrap()[1] = 0;
                    thread::sleep(Duration::from_millis(700));
                    println!("Consumer {} work: {:?}", i, slot);
                }
            }
        });
        
        producer_handle.join().unwrap();
        consumer_handle.join().unwrap();
    }
}

fn main() {
    work();
    println!("{:?}", slot);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/parallel`
Producer 0 work: Mutex { data: [1], poisoned: false, .. }
Producer 1 work: Mutex { data: [0, 1], poisoned: false, .. }
Consumer 1 work: Mutex { data: [0, 1], poisoned: false, .. }
Producer 2 work: Mutex { data: [0, 1, 1], poisoned: false, .. }
Producer 3 work: Mutex { data: [0, 1, 1, 1], poisoned: false, .. }
Producer 4 work: Mutex { data: [0, 1, 1, 1, 1], poisoned: false, .. }
Producer 5 work: Mutex { data: [0, 1, 1, 1, 1], poisoned: false, .. }
Consumer 5 work: Mutex { data: [0, 1, 1, 1, 1], poisoned: false, .. }
Producer 6 work: Mutex { data: [1, 1, 1, 1, 1], poisoned: false, .. }
Producer 7 work: Mutex { data: [1, 1, 1, 1, 1], poisoned: false, .. }
Consumer 7 work: Mutex { data: [1, 1, 1, 1, 1], poisoned: false, .. }
Producer 8 work: Mutex { data: [1, 1, 1, 1, 1], poisoned: false, .. }
Consumer 8 work: Mutex { data: [1, 1, 1, 1, 1], poisoned: false, .. }
Producer 9 work: Mutex { data: [1, 1, 1, 1, 1], poisoned: false, .. }
Consumer 9 work: Mutex { data: [1, 1, 1, 1, 1], poisoned: false, .. }

【Rust】vending machine(自販機)のプログラム

### 前準備
コンソールからの入力の受け取り

fn main() {
    println!("文字を入力してください:");

    let mut word = String::new();
    std::io::stdin().read_line(&mut word).ok();
    let answer = word.trim().to_string();

    println!("{}", answer);
}

文字を入力してください:
aaa
aaa

### お札、硬貨の枚数も合わせて返却する。
Hashmapにinsertするところは関数化したい

use std::collections::HashMap;
use itertools::Itertools;

fn cash_change(mut change: u32) -> HashMap<u32, u32> {
    let mut change_map: HashMap<u32, u32> = HashMap::new();
    if change / 5000 > 0 {
        change_map.insert(5000, change/5000);
        change = change % 5000;
    }
    if change / 1000 > 0 {
        change_map.insert(1000, change/1000);
        change = change % 1000;
    }
    if change / 500 > 0 {
        change_map.insert(500, change/500);
        change = change % 500;
    }
    if change / 100 > 0 {
        change_map.insert(100, change/100);
        change = change % 100;
    }
    if change / 50 > 0 {
        change_map.insert(50, change/50);
        change = change % 50;
    }
    if change / 10 > 0 {
        change_map.insert(10, change/10);
        change = change % 10;
    }
    if change > 0 {
        change_map.insert(1, change);
    }
    change_map
}

fn main() {
    println!("投入する金額を入力してください:");

    let mut word = String::new();
    std::io::stdin().read_line(&mut word).ok();
    let cash: u32 = word.trim().parse().expect("entered string was not a number");


    println!("商品の金額を入力してください:");
    let mut word = String::new();
    std::io::stdin().read_line(&mut word).ok();
    let price: u32 = word.trim().parse().expect("entered string was not a number");

    println!("お釣りは[{}]です。", cash - price);
    let mut change_map = cash_change(cash - price);
    let mut v: Vec<_> = change_map.into_iter().collect();
    v.sort_by(|x,y| y.0.cmp(&x.0));
    println!("{:?}", v);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
Running `target/debug/rust`
投入する金額を入力してください:
1000
商品の金額を入力してください:
333
お釣りは[667]です。
[(500, 1), (100, 1), (50, 1), (10, 1), (1, 7)]

### もっと簡潔に

use std::collections::HashMap;
use itertools::Itertools;

fn cash_change(mut change: u32) -> HashMap<u32, u32> {
    let mut change_map: HashMap<u32, u32> = HashMap::new();
    let coin = vec![5000, 1000, 500, 100, 50, 10, 1];
    for i in coin {
        if change / i > 0 {
            change_map.insert(i, change/i);
            change = change % i;
        }
    }
    change_map
}

fn main() {
    println!("投入する金額を入力してください:");

    let mut word = String::new();
    std::io::stdin().read_line(&mut word).ok();
    let cash: u32 = word.trim().parse().expect("entered string was not a number");


    println!("商品の金額を入力してください:");
    let mut word = String::new();
    std::io::stdin().read_line(&mut word).ok();
    let price: u32 = word.trim().parse().expect("entered string was not a number");

    println!("お釣りは[{}]です。", cash - price);
    let mut change_map = cash_change(cash - price);
    let mut v: Vec<_> = change_map.into_iter().collect();
    v.sort_by(|x,y| y.0.cmp(&x.0));
    println!("{:?}", v);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/rust`
投入する金額を入力してください:
3000
商品の金額を入力してください:
200
お釣りは[2800]です。
[(1000, 2), (500, 1), (100, 3)]

おおおおおおお、なるほど〜。同じ処理のところはイテレータにしてforループで回せば、より簡潔に書けますね。

【Rust】Mutexの使い方を理解したい

シングルスレッド
mutexのデータにアクセスするには、lockメソッドを使用してロックを獲得する

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);

    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }

    println!("m= {:?}", m);
}

スレッド間でsemaforeを利用しようとするとエラーになる。

use std::sync::{Mutex, Arc};
use std::thread;
use rand::Rng;
use std_semaphore::Semaphore;
use std::time::Duration;

static TOTAL_SPOTS: u32 = 3;

fn main() {
    let parked_cars = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    let sem = Semaphore::new(TOTAL_SPOTS.try_into().unwrap());

    for _ in 0..10 {

        let counter = Arc::clone(&parked_cars);

        let handle = thread::spawn(move || {

            let mut rng = rand::thread_rng(); 
            // enter
            sem.acquire();
            let mut num = counter.lock().unwrap();
            *num += 1;
            thread::sleep(Duration::from_millis(rng.gen_range(1..10)));

            // exit
            sem.release();
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *parked_cars.lock().unwrap());
}

15 | for _ in 0..10 {
| ————– inside of this loop

19 | let handle = thread::spawn(move || {
| ^^^^^^^ value moved into closure here, in previous iteration of loop

23 | sem.acquire();
| — use occurs due to use in closure

中々奥が深い..

【Rust】rustでセマフォ(semaphore)を使いたい

mainじゃなくて関数として使いたいな…

use std_semaphore::Semaphore;
use std::thread;
use std::time::Duration;

static TOTAL_SPOTS: u32 = 3;

fn main() {
    let sem = Semaphore::new(TOTAL_SPOTS.try_into().unwrap());

    let mut parked_cars: Vec<u32> = Vec::new();

    let car: u32 = 1;

    // enter
    sem.acquire();
    parked_cars.push(car);
    thread::sleep(Duration::from_millis(500));
    println!("{:?}", parked_cars);

    // exit
    parked_cars.clear();
    sem.release();

    println!("{:?}", parked_cars);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/parallel`
[1]
[]

ランダムな連想配列の計測

選挙投票のカウントを想定したもの

import typing as T
import random

Summary = T.Mapping[int, int]

def process_votes(pile: T.List[int]) -> Summary:
    summary = {}
    for vote in pile:
        if vote in summary:
            summary[vote] += 1
        else:
            summary[vote] = 1
    return summary

if __name__ == "__main__":
    num_candidates = 3
    num_voters = 100
    pile = [random.randint(1, num_candidates) for _ in range(num_voters)]
    print(pile)
    counts = process_votes(pile)
    print(f"Total number of votes: {counts}")

$ python3 count_votes_sequential.py
[1, 1, 2, 1, 2, 3, 2, 2, 1, 2, 2, 3, 3, 2, 3, 1, 2, 2, 2, 3, 1, 1, 2, 3, 1, 2, 3, 3, 2, 2, 3, 2, 1, 3, 2, 2, 3, 3, 2, 3, 3, 3, 2, 3, 2, 1, 1, 3, 2, 3, 3, 2, 2, 1, 1, 1, 1, 2, 3, 3, 1, 2, 2, 3, 2, 1, 2, 2, 1, 3, 1, 1, 2, 1, 1, 2, 3, 3, 1, 2, 1, 2, 3, 3, 3, 1, 2, 2, 2, 1, 1, 3, 2, 1, 2, 2, 3, 2, 3, 1]
Total number of votes: {1: 29, 2: 40, 3: 31}

Rustで書き直す
範囲指定のランダムは rnd.gen_range となる。

use std::time::Instant;
use threadpool::ThreadPool;
use rand::Rng;
use std::collections::HashMap;

fn process_votes(pile: Vec<u32>) -> HashMap<u32, u32> {
    let mut summary = HashMap::new();
    for vote in pile {
        if summary.get(&vote) != None {
            let count = summary.get(&vote).unwrap();
            summary.insert(vote, count + 1);
        } else {
            summary.insert(vote, 1);
        }
    }
    summary
}


fn main(){    
    
    let num_voters = 100;
    let num_candidate = 3;
    
    let mut pile: Vec<u32> = Vec::new();
    let mut rnd = rand::thread_rng();
    for _ in 0..num_voters {
        pile.push(rnd.gen_range(1..(num_candidate + 1)))
    }
    let summary = process_votes(pile);
    println!("{:?}", summary);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.29s
Running `target/debug/parallel`
{3: 42, 2: 33, 1: 25}

ここまではOK

【Rust】ThreadPoolによる文字検索

全然早くないが使い方間違ってる?

use std::env;
use std::fs;
use std::path::Path;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::time::Instant;
use threadpool::ThreadPool;

fn search_file(filename: String, search_string: String) -> Result<bool, Box<dyn std::error::Error>> {
    for line in BufReader::new(File::open(filename.clone())?).lines() {
        if line.unwrap().contains(&search_string) {
            return Ok(true)
        }
    }
    Ok(false)
}

fn get_files(dirname: &str) -> io::Result<Vec<String>> {
    let mut entries: Vec<String> = Vec::new();
    let dir = Path::new(dirname);
    if dir.is_dir(){
        for entry in fs::read_dir(dirname)? {
            let e = entry?;
            let p = e.path().file_name().unwrap().to_string_lossy().into_owned();
            entries.push(p);
        }
    }
    Ok(entries)
}

fn search_files_sequentially(file_locations: String, search_string: String) {
    let entries: Vec<String> = get_files(&file_locations).unwrap();
    for entry in entries {
        let pass = format!("{}{}", file_locations, entry);
        if search_file(pass, search_string.clone()).unwrap() {
            println!("Found word in file: {}", entry);
        }
    }
}

fn search_files_concurrent(file_locations: String, search_string: String) {
    let entries: Vec<String> = get_files(&file_locations).unwrap();
    let pool = rayon::ThreadPoolBuilder::new().num_threads(4).build().unwrap();
    for entry in entries {
        let pass = format!("{}{}", file_locations, entry);
        let mut value = search_string.clone();
        pool.install(move || {
            if search_file(pass, value).unwrap() {
                println!("Found word in file: {}", entry);
            }
        });
    }
}

fn main(){    
    let now = Instant::now();
    search_files_sequentially("./src/".to_string(), "queue".to_string());
    println!("{:?}", now.elapsed());

    let now = Instant::now();
    search_files_concurrent("./src/".to_string(), "queue".to_string());
    println!("{:?}", now.elapsed());
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/parallel queue`
Found word in file: pipeline.rs
Found word in file: main.rs
422.486µs
Found word in file: pipeline.rs
Found word in file: main.rs
701.462µs

【Rust】特定のディレクトリから検索文字列を探す

use std::fs;
use std::path::Path;
use std::fs::File;
use std::io::{self, BufRead, BufReader};

fn search_file(filename: String, search_string: String) -> Result<bool, Box<dyn std::error::Error>> {
    for line in BufReader::new(File::open(filename.clone())?).lines() {
        if line.unwrap().contains(&search_string) {
            return Ok(true)
        }
    }
    Ok(false)
}

fn get_files(dirname: &str) -> io::Result<Vec<String>> {
    let mut entries: Vec<String> = Vec::new();
    let dir = Path::new(dirname);
    if dir.is_dir(){
        for entry in fs::read_dir(dirname)? {
            let e = entry?;
            let p = e.path().file_name().unwrap().to_string_lossy().into_owned();
            entries.push(p);
        }
    }
    Ok(entries)
}

fn search_files_sequentially(file_locations: String, search_string: String) {
    let entries: Vec<String> = get_files(&file_locations).unwrap();
    for entry in entries {
        let pass = format!("{}{}", file_locations, entry);
        if search_file(pass, search_string.clone()).unwrap() {
            println!("Found word in file: {}", entry);
        }
    }
}

fn main(){    
    search_files_sequentially("./src/".to_string(), "password".to_string());
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s
Running `target/debug/parallel queue`
Found word in file: password_cracking.rs
Found word in file: main.rs
Found word in file: password_cracking_parallel.rs