🦬

Emacs で AtCoder 環境を整える

2022/04/06に公開

Ruby 用

atcoder-cli の導入・テスト・提出の手順

pip3 install online-judge-tools
yarn global add atcoder-cli
acc check-oj
acc login
acc session
acc config default-test-dirname-format test
acc config default-task-choice all
acc config default-template ruby

cd ~/Library/Preferences/atcoder-cli-nodejs
mkdir ruby
cd ruby
emacsclient template.json
emacsclient main.rb

mkdir -p ~/src/procon/ruby
cd ~/src/procon/ruby
oj login https://atcoder.jp/
acc new math-and-algorithm
cd math-and-algorithm/001
echo "p gets.to_i + 5" > main.rb
oj t -c "ruby main.rb"
acc s main.rb -- -w 0 -y
template.json
{
  "task":{
    "program": ["main.rb"],
    "submit": "main.rb"
  }
}
main.rb
if $0 == "-"
  require "stringio"
  $stdin = StringIO.new(<<~EOS)
3
10 20 30
----------------------------------------
EOS
end

N = gets.to_i                   # => 3
A = gets.split.collect(&:to_i)  # => [10, 20, 30]

N, M = gets.split.collect(&:to_i) # =>
A = M.times.collect { gets.split.collect(&:to_i) } # =>
  • atcoder-cli の実際のコマンド名が acc
    • acc は AtCoder Command line interface の略と思われる
  • atcoder-cli は Ruby に依存しているわけではない
    • Python C C++ もいける
    • Rust もいけるけど、外部クレートに依存せず rustc だけでコンパイルできるシンプルなコードに限る
      • cargo を使わずに外部クレートと合わせてビルドできないのかな?
  • acc config default-test-dirname-format test が超重要
    • これをやっておかないと oj t -c "ruby main.rb" が動かなくてはまる
    • oj t -c "ruby main.rb" -d tests とすれば動くことがあとでわかったが、とにかくすげーはまった
    • なので最初から test で作るようにしておけば -d tests が不要になり、ひっかかることはなくなる
    • これは両者のテストディレクトリ名のこだわりが影響している
      • acc → tests を想定している
      • oj → test を想定している
  • template.json のなかで提出するファイルに main.rb を指定しているのに acc s -- -w 0 -y と書けないのは謎
    • なので template.json の submit の指定は意味ない
  • oj login は最初に一度だけでよい

Rust 用

cargo-compete の導入・テスト・提出の手順

mkdir -p ~/src/procon/rust
cd ~/src/procon/rust

cargo install cargo-compete
cargo compete init atcoder
cargo compete login atcoder
rustup install 1.42.0

cargo compete new math-and-algorithm
cd math-and-algorithm/bin
cargo compete open --bin 001
cat <<'EOF' > 001.rs
use proconio::input;
fn main() {
    input! { n: isize, }
    println!("{}", n + 5);
}
EOF
cargo compete test 001
cargo compete submit --no-test --no-watch 001
  • cargo-atcoder が以前は主流だったようだが、基本的な部分が引き継がれ、導入のしやすさや使い勝手が cargo-compete の方がさらによくなっている
  • コンテスト math-and-algorithmcargo compete open するとブラウザで 104 個のタブが開かれるので注意
    • たぶん5問ぐらいのコンテストしか想定してない
    • --bin 001 とすれば問題 001 だけ開ける
  • eshell で cargo compete submit すると表がめちゃくちゃになるので --no-watch している
  • atcoder-cli とはディレクトリ構成が大きく異なる
    • どうしても依存 crate を cargo で管理しないといけないので構成も cargo 依存になっている
    • acc ではディレクトリ名が問題インデックス名になっていたが、こちらはファイル名が問題インデックス名になっている

Emacs の設定

CLI で何でもできるようになったとはいえ、そんなのはすぐ忘れるので、次のように1文字打てば実行できるようにする。

これには transient.el が適している。

簡単な使い方
(transient-define-prefix foo ()
  [
    ("a" "説明1" (lambda () (interactive) (message "A")))
    ("b" "説明2" (lambda () (interactive) (message "B")))
  ]
 )

a をタイプすると "A" が表示され b をタイプすると "B" が表示される。これをどんどん拡張して半自動化したいことを追加していく。

procon.el
(require 'transient)
(require 'f)

(defun procon-eshell-send (command)
  "eshellを現在のディレクトリに移動して指定のコマンドを実行"
  (interactive)
  (let ((dir default-directory))
    (eshell)
    (goto-char (point-max))
    (eshell-bol)
    (unless (eobp)
      (kill-line))
    (insert (format "pushd '%s'" dir))
    (eshell-send-input)
    (insert command)
    (eshell-send-input)))

(defun procon-current-file-name ()
  (f-filename (buffer-file-name)))

(defun procon-current-task-name ()
  (f-filename (f-dirname (buffer-file-name))))

(transient-define-prefix procon-launcher ()
  "プロコン用ランチャー"
  [
   ["Ruby"
    ;; :if-derived (ruby-mode dired-mode eshell-mode)
    ("!" "セットアップ"
     (lambda ()
       (interactive)
       (procon-eshell-send (format "acc new %s" (read-string "コンテスト名(例: abc001): ")))))
    ("i" "問題情報入力"
     (lambda ()
       (interactive)
       (beginning-of-buffer)
       (insert (shell-command-to-string (format "ruby -e \"
require 'pathname'
require 'json'
file = Pathname('../contest.acc.json')
info = JSON.parse(file.read, symbolize_names: true)[:tasks].find { |e| e[:label].casecmp?(ARGV[0]) }
puts '# [' + info[:label] + '] ' + info[:title]
puts '# ' + info[:url]
\" %s" (procon-current-task-name))))))

    ("o" "問題URLをブラウザで開く"
     (lambda ()
       (interactive)
       (beginning-of-buffer)
       (shell-command (format "ruby -e \"
require 'pathname'
require 'json'
file = Pathname('../contest.acc.json')
info = JSON.parse(file.read, symbolize_names: true)[:tasks].find { |e| e[:label].casecmp?(ARGV[0]) }
system 'open ' + info[:url]
\" %s" (procon-current-task-name)))))
    ("1" "単体実行"
     (lambda ()
       (interactive)
       (set (make-local-variable 'compile-command) (format "ruby %s" (procon-current-file-name)))
       (call-interactively 'compile)))
    ("t" "テスト"
     (lambda ()
       (interactive)
       (set (make-local-variable 'compile-command) (format "oj t -c 'ruby %s'" (procon-current-file-name)))
       (call-interactively 'compile)))
    ("s" "提出"
     (lambda ()
       (interactive)
       (procon-eshell-send (format "acc s %s -- -w 0 -y" (procon-current-file-name)))))
    ("l" "問題一覧の確認"
     (lambda ()
       (interactive)
       (procon-eshell-send "acc tasks")))
    ("c" "コンテスト名の確認"
     (lambda ()
       (interactive)
       (procon-eshell-send "acc contest")))
    ("n" "次の問題を取得"
     (lambda ()
       (interactive)
       (procon-eshell-send "acc add -c next")))
    ]
   [""
    ("e" "テンプレート編集"
     (lambda ()
       (interactive)
       (find-file "~/Library/Preferences/atcoder-cli-nodejs/ruby/main.rb")))
    ("z" "設定"
     (lambda ()
       (interactive)
       (dired "~/Library/Preferences/atcoder-cli-nodejs")))
    ]
   ]
  ["Rust"
   ;; :if-derived (rust-mode dired-mode eshell-mode)
   ;; :if-derived (rust-mode)
   ("rt" "テスト (cargo compete test)"
    (lambda ()
      (interactive)
      (set (make-local-variable 'compile-command) (format "cargo compete t %s" (f-base (buffer-file-name))))
      (call-interactively 'compile))
    ;; :if rust-mode
    )
   ("rs" "提出 (cargo compete submit)"
    (lambda ()
      (interactive)
      (procon-eshell-send (format "cargo compete s --no-test --no-watch %s" (f-base (buffer-file-name)))))
    ;; :if rust-mode
    )
   ]
  )

;; (global-set-key (kbd "C-j p") 'procon-launcher)

実行手順

次の手順で進めていく

  1. ! → セットアップ (上の上のディレクトリで実行)
  2. i → 問題情報入力 (main.rb を開いてから)
  3. o → 問題URLをブラウザで開く
  4. 1 → 単体実行
  5. t → テスト
  6. s → 提出
  7. n → 次の問題を取得 (acc new で一括セットアップしていない場合)
  8. 2 に戻る

関連

https://zenn.dev/megeton/articles/38566f0cc1f800

参照

Discussion