🦬
Emacs で AtCoder 環境を整える
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-algorithm
でcargo 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)
実行手順
次の手順で進めていく
-
!
→ セットアップ (上の上のディレクトリで実行) -
i
→ 問題情報入力 (main.rb を開いてから) -
o
→ 問題URLをブラウザで開く -
1
→ 単体実行 -
t
→ テスト -
s
→ 提出 -
n
→ 次の問題を取得 (acc new で一括セットアップしていない場合) - 2 に戻る
関連
Discussion