AHC027で最低限のローカルテスト環境を作った話

:::note info
この記事は[SLP-KBIT AdventCalendar2023](https://adventar.org/calendars/9222)12日目の記事です。
:::
# 目次
 **・はじめに** 
 **・ローカルテスト手順** 
 **・seeds.txtに乱数を書く** 
 **・提出用コードをファイル入出力用にする(C++)** 
 **・vis.rsをn回実行する(Rust)**
 **・おわりに** 
# はじめに
こんにちは、kaede9030です。先週行われたAHC027のローカルテスト環境構築について書きます。 **最低限** のため並列処理とかはしません。
AHCやローカルテスト環境の必要性については[この記事](https://www.terry-u16.net/entry/ahc001-how-to)を読んでください。
# ローカルテスト手順
まず[Rust実行環境](https://www.rust-lang.org/ja/tools/install)を用意し、問題文のページからローカル版ツールを入手しましょう。
```
tools
|---in
|---src
|---target
|---seeds.txt
|---README.html
|---その他.なにか
```
その中の`README.html`を読み、ローカルテストの手順を考えますが、とりあえず動くことを確かめます。
### 入力生成
`tools`ディレクトリに移動し、入力生成コマンド
```
cargo run -r --bin gen seeds.txt
```
を打つと、`in`ディレクトリに`0000.txt~0099.txt`ができると思います。
### ビジュアライザ実行
`tools`ディレクトリ内に`in.txt`, `out.txt`を作り、それぞれ問題文の入力例と出力例を書き、ビジュアライザ実行コマンド
```
cargo run -r --bin vis in.txt out.txt
```
を打つと、`tools`ディレクトリに`vis.html`が生成され、標準出力には
```
Score = 3302174
```
と表示されるはずです。
***
さて、ここまでまとめると
1. `入力.txt`を用意する
2. `出力.txt`を用意する(コンテスト提出用コードをファイル操作に対応させる)
3. 上２つを使ってビジュアライザを動かす
↑これらを行えばよさそうです。
# seeds.txtに乱数を書く
 **README.html** によると
 >in ディレクトリに予め生成された seed=0~99 に対する入力ファイルが置かれています。 より多くの入力が欲しい場合は、seeds.txt に欲しい入力ファイルの数だけ乱数seed値(符号なし64bit整数値)を記入し、以下のコマンドを実行します。
```
cargo run -r --bin gen seeds.txt
```
らしいので、`tools`ディレクトリに乱数を`seeds.txt`に書き込むプログラム
```c:seeds_gen.c
//言語は何でもいい
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main(void){
    int n=1000; //生成したい数
    FILE *fp;
    fp=fopen("seeds.txt","w");
    if(fp==NULL){
        printf("cannot open file\n");
        exit(1);
    }
    srand((unsigned)time(NULL)); //今更だけどこれ符号なし32bitでは…
    for(int i=0;i<n;i++){
        unsigned tmp=rand();
        fprintf(fp,"%u\n",tmp);
    }
    fclose(fp);
    return 0;
}
```
を置き、実行します。先のコマンド(`cargo run -r --bin gen seeds.txt`)も実行。
# 提出用コードをファイル入出力用にする
自分が普段使う言語で提出用コードを書き、先ほどの入力ファイル(`0000.txt`など)を受け取り、答えを出力ファイル(`0.txt`など)に書き込みます。このファイルは`in`ディレクトリに置いてください。
こんな感じ↓
```C++:submit.cpp
#include<bits/stdc++.h>   //問題文のDFSするだけACコード
using namespace std;

int N;
int di[4]={0,1,0,-1},dj[4]={1,0,-1,0};
string dir="RDLU";
vector<string> h(39);
vector<string> v(40);
vector<vector<int>> d(40,vector<int>(40));
string ans="";

void dfs(int i,int j,vector<vector<bool>>* vis){
    (*vis)[i][j]=true;
    for(int k=0;k<4;k++){
        int ii=i+di[k],jj=j+dj[k];
        if(0<=ii&&ii<N&&0<=jj&&jj<N&&!(*vis)[ii][jj]){
            if((di[k]==0&&v[i][min(j,jj)]=='0')||(dj[k]==0&&h[min(i,ii)][j]=='0')){
                ans+=dir[k];
                dfs(ii,jj,vis);
                ans+=dir[(k+2)%4];
            }
        }
    }
}

int main(void){
    for(int i=0;i<1000;i++){   //1000個分ファイル処理
        string filename="";
        string num=to_string(i);
        int x=4-num.size();
        for(int j=0;j<x;j++){
            filename+="0";
        }
        filename+=num+".txt";
        ifstream infile(filename);
        if(infile.is_open()){
            infile>>N;
            for(int j=0;j<N-1;j++){
                infile>>h[j];
            }
            for(int j=0;j<N;j++){
                infile>>v[j];
            }
            for(int j=0;j<N;j++){
                for(int k=0;k<N;k++){
                    infile>>d[j][k];
                }
            }
            infile.close();
            vector<vector<bool>> visited(N,vector<bool>(N,false));
            dfs(0,0,&visited);
        }else{
            cout<<"cannot open "<<filename<<endl;
            exit(1);
        }
        string fname=to_string(i)+".txt";
        ofstream outfile(fname);
        if(outfile.is_open()){
            outfile<<ans<<endl;
            outfile.close();
            ans="";
        }
    }
    //cin>>N;   //提出用
    //for(int i=0;i<N-1;i++){
    //    cin>>h[i];
    //}
    //for(int i=0;i<N;i++){
    //    cin>>v[i];
    //}
    //for(int i=0;i<N;i++){
    //    for(int j=0;j<N;j++){
    //        cin>>d[i][j];
    //    }
    //}
    //vector<vector<bool>> visited(N,vector<bool>(N,false));
    //dfs(0,0,&visited);
    //cout<<ans<<endl;
    return 0;
}
```
実行すると`in`ディレクトリに`.txt`ファイルがいっぱいできると思います。
入力ファイル`0000.txt`と出力ファイル`0.txt`が対応しています。`tools`ディレクトリの`in.txt`, `out.txt`は使わないので消しても構いません。
# vis.rsをn回実行する
ここで躓きました。まず思いついた方法は、提出コードをテストケースの数だけ繰り返したように、`vis.rs`の中にループの処理を追記するやり方です。
```Rust:こんな感じ？.rs
fn main(){
    for i in 0..1000{   //テストケースの数だけループ
    //vis.rsにもともとある処理+α
    }
}
```
この方法は不採用です。理由は、私がRustの知識が皆無だからです。公式のツールをいじるとか怖くてできません。
### 他の方法は？
vis.rsをいじりたくないなら`cargo~`コマンドをn回実行したらどうか？と思い「Rust コマンド実行」などと検索していると[std::process::Commandなるもの](https://doc.rust-jp.rs/rust-by-example-ja/std_misc/process.html)を見つけました。
#### 実装
自力ですべて実装するのは厳しいため、ChatGPT先生を頼ります。文字列難しい

:::note warn
このファイルは`tools`ディレクトリに置いてください。
:::

```Rust:vis_rep.rs
use std::process::Command;

fn main() {
    let mut ave: u64=0;    //u128のほうが安心
    for i in 0..1000{      
        let infile=format!("{:04}",i);    //"0000"~"0999"
        let outfile=i.to_string();        //"0"~"999"
        let inpath=format!("in/{}{1}",infile,".txt");  
        let outpath=format!("in/{}{1}",outfile,".txt");
        let output=Command::new("cargo") //ターミナルに打ち込んでいたコマンド
            .arg("run")
            .arg("-r")
            .arg("--bin")
            .arg("vis")
            .arg(inpath)
            .arg(outpath)
            .output()
            .expect("Failed");
        //ここからほとんどChatGPTが一晩でやってくれました
        if let Ok(stdout) = String::from_utf8(output.stdout) {
            let s=stdout.clone();    //所有権が怖いためコピーしたけどいらない？
            println!("vis.rs output: {}", s);
            if let Some(score_str) = s.split_once('=') {
                if let Ok(score) = score_str.1.trim().parse::<u64>() {
                    println!("Score: {}", score);
                    ave+=score;
                } else {
                    println!("Error: Unable to parse score as integer");
                }
            } else {
                println!("Error: Invalid format for score");
            }
        }
    }    //私が書くとこんなに丁寧にエラー処理しない()
    println!("average score is {}",ave/1000);
}
```
何をしているかとても簡単に説明すると
```
cargo run -r --bin vis in/0000.txt in/0.txt
```
このコマンドを繰り返し実行し、標準出力に
```
vis.rs output: Score = 123456789

Score: 123456789
```
と、スコアを表示するか、エラーを表示します。最後に平均スコアも表示されます。
# おわりに
汚コード、過不足しかない説明の中、ここまで読んでくださりありがとうございます。
これだけローカルテスト環境について書いておきながら、私は問題文のサンプルACコードしか提出していません。~~私がRust公式文書を漁っていた時間は何だったのでしょうか..~~
茶下位こーだーにしては頑張ったということでこの記事はおしまいです。
##### 他の人のAHC環境構築記事などいろいろ
https://kato-hiro.github.io/AtCoderClans/articles/heuristic/
##### 参考資料
https://doc.rust-jp.rs/book-ja/ 
https://doc.rust-jp.rs/rust-by-example-ja/std_misc/process.html 
***
昨日の記事:https://qiita.com/nao_bc/private/20f802ea9ac41a634cb6 
明日の記事:https://qiita.com/saimagu/private/0c939e67c896cd74ac7d