ElixirでAtCoder参加してみよう（atcoder-toolsの導入）

# はじめに

AtCoderに先ずは慣れようとおもって、Pythonで何度か参加してみました。
調べてみると、コンテストの参加を手助けしてくれるソフトがいくつかあるんですね。
そのなかで、[atcoder-tools](https://github.com/kyuridenamida/atcoder-tools)を使って便利だったので、これをElixirでも使えるようにしてみました。

# atcoder-toolsの紹介

どんな機能があるかというと
- AtCoderへのログイン，入出力例データなどの抽出
- 枝刈り探索による高精度・高速な入力フォーマット解析 (ARC、ABC、AGCについては約9割ほど)
- 問題文中に含まれるMOD値、YES/NO文字列、誤差ジャッジのための誤差値等の定数値抽出
- サンプルのローカルテスト機能
- 誤差ジャッジに対応 by @chaemon
- コード提出機能
- 入力フォーマット解析結果や抽出した定数値を用いたテンプレートからのコード自動生成(以下の表に記載されている言語をサポートしています)
（サイトからの転記）

と、とても便利です。

問題画面からコピペしなくてもテストできるのはとても便利です。


# インストール方法

Elixir対応版は、本家にはまだ含まれていません。

Forkしたものを公開したので、
pip installでGitのURL指定でインストールすれば使えると思います。

Python 3.6以上が動作する必要があります。
本家の資料を基にインストールしてください。

https://github.com/kyuridenamida/atcoder-tools

pip install atcoder-toolsの部分は、以下のようにGitのURL指定にします。

```bash
pip install git+https://github.com/masahiro-999/atcoder-tools.git@elixir
```

atcoder-toolsコマンドを実行したときに、`markupsafe`に関するエラーが発生した場合は、`pip install MarkupSafe==2.0.1`でバージョン指定すると解決します。

<details><summary>こんなエラー</summary>
```bash
(atcoder) masa@WINPC1:~$ atcoder-tools
Traceback (most recent call last):
  File "/home/masa/.venv/atcoder/bin/atcoder-tools", line 5, in <module>
    from atcodertools.atcoder_tools import main
  File "/home/masa/.venv/atcoder/lib/python3.10/site-packages/atcodertools/atcoder_tools.py", line 8, in <module>
    from atcodertools.tools.envgen import main as envgen_main
  File "/home/masa/.venv/atcoder/lib/python3.10/site-packages/atcodertools/tools/envgen.py", line 14, in <module>
    from atcodertools.client.atcoder import AtCoderClient, Contest, LoginError, PageNotFoundError
  File "/home/masa/.venv/atcoder/lib/python3.10/site-packages/atcodertools/client/atcoder.py", line 15, in <module>
    from atcodertools.common.language import Language
  File "/home/masa/.venv/atcoder/lib/python3.10/site-packages/atcodertools/common/language.py", line 4, in <module>
    from atcodertools.codegen.code_generators import cpp, elixir, java, rust, python, nim, d, cs, swift, go, julia
  File "/home/masa/.venv/atcoder/lib/python3.10/site-packages/atcodertools/codegen/code_generators/cpp.py", line 2, in <module>
    from atcodertools.codegen.template_engine import render
  File "/home/masa/.venv/atcoder/lib/python3.10/site-packages/atcodertools/codegen/template_engine.py", line 5, in <module>
    from jinja2 import Environment
  File "/home/masa/.venv/atcoder/lib/python3.10/site-packages/jinja2/__init__.py", line 12, in <module>
    from .environment import Environment
  File "/home/masa/.venv/atcoder/lib/python3.10/site-packages/jinja2/environment.py", line 25, in <module>
    from .defaults import BLOCK_END_STRING
  File "/home/masa/.venv/atcoder/lib/python3.10/site-packages/jinja2/defaults.py", line 3, in <module>
    from .filters import FILTERS as DEFAULT_FILTERS  # noqa: F401
  File "/home/masa/.venv/atcoder/lib/python3.10/site-packages/jinja2/filters.py", line 13, in <module>
    from markupsafe import soft_unicode
ImportError: cannot import name 'soft_unicode' from 'markupsafe' (/home/masa/.venv/atcoder/lib/python3.10/site-packages/markupsafe/__init__.py)
```
</details>

私の使用している、テンプレートファイルと、.atcodertools.tomlを参考に載せておきます。

```elixir:my_template.ex
defmodule Main do
    import Bitwise
    def next_token(acc \\ "") do
        case IO.getn(:stdio, "", 1) do
          " " -> acc
          "\n" -> acc
          x -> next_token(acc <> x)
        end
    end
    def input(), do: IO.read(:line) |> String.trim()
    def ii(), do: next_token() |> String.to_integer()
    def li(), do: input() |> String.split(" ") |> Enum.map(&String.to_integer/1)

{% if mod %}
    MOD = {{ mod }}
{% endif %}
{% if yes_str %}
    YES = "{{ yes_str }}"
{% endif %}
{% if no_str %}
    NO = "{{ no_str }}"
{% endif %}

{% if prediction_success %}
    def solve({{ actual_arguments }}) do

    end
{% else %}
    def solve() do

    end
{% endif %}

    def main() do
{% if prediction_success %}
        {{input_part}}
        solve({{ actual_arguments }})
{% else %}
        # Failed to predict input format
{% endif %}
    end

end
```

```toml:.atcodertools.toml
[codestyle]
indent_type='space' # 'tab' or 'space'
indent_width=4
template_file='~/atcoder-workspace-ex/my_template.ex'
workspace_dir='~/atcoder-workspace-ex/'
lang='elixir' # Check README.md for the supported languages.
#code_generator_file="~/custom_code_generator.py"
#code_generator_toml="~/atcoder-workspace/code_generator-ex.toml"
[postprocess]
#exec_on_each_problem_dir='clang-format -i ./*.cpp'
#exec_on_contest_dir='touch CMakeLists.txt'
[compiler]
#compile_command='g++ main.cpp -o main -std=c++17'
#compile_only_when_diff_detected=true
[tester]
compile_before_testing=true
compile_only_when_diff_detected=true
timeout_adjustment=1.2
[etc]
download_without_login=false
parallel_download=false
save_no_session_cache=false
skip_existing_problems=false
in_example_format="in_{}.txt"
out_example_format="out_{}.txt"
```

# 使い方（コード生成）

[abc139コンテスト](https://atcoder.jp/contests/abc139/tasks/abc139_b)の場合

```
atcoder-tools gen abc139
```

main.exのひな型が生成されます。

```Elixir
defmodule Main do
    def input(), do: IO.read(:line) |> String.trim()
    def ii(), do: input() |> String.to_integer()
    def li(), do: input() |> String.split(" ") |> Enum.map(&String.to_integer/1)


    def solve(a, b) do

    end

    def main() do
        a = ii()
        b = ii()
        solve(a, b)
    end

end
```

この問題は、二つの整数a,bが与えられるので、solveに与える引数もa,bとなっています。
オリジナルのatcoder-toolsでは、問題文の表記と同じA,Bと大文字の変数名が使われますが、Elixirでは大文字の変数名が使えないのですべて小文字に変換されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/d353e0f6-003c-605e-b052-e39be1f22be6.png)

入力の値のプログラムの自動生成は、Elixirの対応はまだうまく行かない場合もあるので、あてにせず確認して下さい。
この課題のA Bが同じ行になっている場合、私の設定ではうまくいかず、

```Elixir
[a,b] = li()
```
とする必要があります。

# テスト

solve関数を実装してテストしてみます。

```Elixir
defmodule Main do
    def input(), do: IO.read(:line) |> String.trim()
    def ii(), do: input() |> String.to_integer()
    def li(), do: input() |> String.split(" ") |> Enum.map(&String.to_integer/1)

    def get_ans(num_tap, socket, a, b) do
        cond do
         socket >= b -> num_tap
         num_tap == 0 -> get_ans(1, a, a, b)
         :true -> get_ans(num_tap+1, socket+a-1, a, b)
        end
    end

    def solve(a,b) do
        ans = get_ans(0, 1, a, b)
        IO.puts(ans)
    end

    def main() do
        [a, b] = li()
        solve(a, b)
    end

end

```
main.exのあるディレクトリで次のコマンドを実行すると、テストが実行されます。
```
$ atcoder-tools test
```

次のような感じで結果が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/4a6ecb1c-d47e-858a-855c-2bb83f65fbe7.png)

問題のページにある例以外の値をテストした場合は、`in_*.txt`と`out_*.txt`のファイルを追加すれば任意の値でテストできます。

https://kenkoooo.com/atcoder/#/table/

# 提出

提出もでます。

```bash
$ atcoder-tools submit 
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/a3eb9648-4cdb-bdde-a355-0c7ebaa5bb9c.png)

提出前に、テストを実行してOKの場合のみ提出する設定がデフォルトになっています。
この例では、すでに提出済みなので、エラーになっています。二度目の提出の場合は`-u`を付けます

# まとめ

これで問題の回答に専念できる環境ができました。

ElixirでAtCoderに参加してみましょう！


## 2023/9/23 
pip installのパスを変更しました。