Vim script で AtCoder に参戦する方法

先週、進捗キャンプという知り合いで集まって進捗を出す会に行ってきて、そこで @ さんと表題の件について色々話した。
その後個人的に手法をカイゼンしたりしたので、結果をまとめておく。

AtCoder とは

http://atcoder.jp/
私自身も「とは」と言って語れるほど詳しくはないので簡単に説明すると、主に日本人向けの競技プログラミングサイト。
問題は全て日本語で、定期的に様々なコンテストが開催されている。問題の難易度が低い初心者向けのコンテスト(AtCoder Beginner Contest 通称 ABC)なんかもあるので、競技プログラミングの入口としては最適だ。

AtCoder の対応言語

競技プログラミングでは、ソースコードを提出し、提出されたコードが正しく動くかどうかサーバ側で実際に動かして検証を行う。つまり、サーバ側に各言語の処理系が必要であり、競技プログラミングで使用できる言語はその点で制約がかかる。
どの言語が使えるかは競技プログラミングによって様々であるが、AtCoder はかなり多くの言語に対応している。
しかし、Vim script は対応言語には含まれていない。
それもそのはず。Vim script は言語としてマイナーという点を除いても、とある問題がある。

競技プログラミングの問題形式と Vim script

競技プログラミングにおける問題は、多くの場合、標準入力と標準出力を使う。
標準入力から問題のデータを受け取り、問題を解いて、結果を標準出力に出力させる。
AtCoder も例に漏れずこの形式である。
標準入出力は多くの言語が標準で扱えるので、この形式にすることで多くの言語に対応することができるためだと思われる。
しかし、Vim script はテキストエディタ Vim を拡張するための言語。標準入出力を直接扱うことは、できない。

ではどうするか

AtCoder の対応言語の1つに、Bash がある。
Bash は、それ自身にもそれなりの演算能力はあるが、どちらかと言うと外部プログラムを呼び出す能力に長けている。
実際、awk や bc なんかがよく使われたりするらしい(よく知らないけど)。
つまり、この Bash の環境に Vim が入っていれば、あとは標準入出力を Bash 側で面倒を見ることによって、Vim script が使えることになる。
試したところ Vim はちゃんと入っているようだった*1。これで Vim script で問題が解ける。

Vim script で問題を解く

AtCoder には練習用のコンテストがあるので、そこにある練習問題を解いてみる。
http://practice.contest.atcoder.jp/tasks/practice_1

vim -u NONE -i NONE -N -n -e -s -S <(cat <<EOF
function! s:main(input) abort
  let a = a:input[0]
  let [b, c] = split(a:input[1], ' ')
  let s = a:input[2]
  return (a + b + c) . ' ' . s
endfunction

let s:input = getline(1, '$')
enew
put =s:main(s:input)
1 delete _
%print
EOF
) <(cat)

結果はこちら
ちゃんと解けてる。

何をしているのか

まず、このコード自体は Bash であるが、事実上その大部分は Vim script である。
ちょっとずつ分解していくと、まず vim を起動するコードは Vim script の部分を省略すると以下のようになる(... の部分が省略部分)。

vim -u NONE -i NONE -N -n -e -s -S <(...) <(cat)
  • -u NONE
    • vimrc ファイルを読み込まない。
  • -i NONE
    • vininfo ファイルを作成しない。
  • -N
    • vi 互換モードをOFFにして(つまりVimとして)起動する。
  • -n
  • -e -s
    • バッチモードで起動する。詳細は割愛。(気になる人は :help -s-ex を参照)
  • -S {ファイル}
    • 起動後にファイルを Vim script として実行する。
  • <(cat)
    • Vim で開くファイルの指定。<(...) はプロセス置換という bash の機能で、コマンドの結果をファイルとして渡すことができる。

さて、Vim を起動していることはわかったので、次は Vim script 部分。

function! s:main(input) abort
  let a = a:input[0]
  let [b, c] = split(a:input[1], ' ')
  let s = a:input[2]
  return (a + b + c) . ' ' . s
endfunction

let s:input = getline(1, '$')
enew
put =s:main(s:input)
1 delete _
%print

ちょっとずつ見ていく。

function! s:main(input) abort
  let a = a:input[0]
  let [b, c] = split(a:input[1], ' ')
  let s = a:input[2]
  return (a + b + c) . ' ' . s
endfunction

わかりやすくするため、問題を解く部分を関数に分けた。
引数の input は、入力のテキストが行単位で配列で渡ってくる。
結果を文字列か、行単位の配列で返す。

let s:input = getline(1, '$')

入力はファイルとして渡ってきており、バッファに開かれている。
バッファの全行を配列として s:input に保存している。

enew

output 用の新しいバッファを開く。

put =s:main(s:input)
1 delete _

結果をバッファに展開している。
空のバッファに put でテキストを置くと1行目に空行が残ってしまうので、delete でこの余計な空行を消している。

%print

バッチモードの Vim では、print などのいくつかのコマンドの結果は標準出力へ出される。
%print でバッファ全体を標準出力へ結果として出力している。
ちなみにこの方法だと、最後に改行のない出力が行えない*2が、大抵の競技プログラミングの問題は最後に改行を出力させるので、問題になることはないだろう。

解く環境を整える

テンプレート

こうして見るとわかる通り、実際に触るのは s:main() 関数の部分だけであり、他の部分はテンプレートだ。
テンプレート系のプラグインを使って、テンプレート化しておくと便利だろう。
ちなみに私は template.vim を使っている(宣伝)。

Vim script 部分だけ編集する

ファイル全体としては Bash だけど、編集したいのは Vim script だ。main の部分だけを、filetype=vim で編集したい。
そんな時に便利なのが partedit.vim だ。
バッファの一部を別のバッファで開き、別のファイルタイプで編集できる。保存すると元のバッファに適用される。

https://i.gyazo.com/14a70ff2a7148b9e1507170bda083710.gif

上の動作例では :Partedit コマンドを引数なしで動かしているが、そうするためにはオプションの設定が必要だ。

" 部分を編集するバッファを開くコマンドの指定。これは縦分割したい場合の例 (デフォルトだと現在のウィンドウにそのまま開かれる)
let g:partedit#opener = 'vsplit'
" 部分を編集するバッファの filetype。デフォルトだと元のバッファと同じ filetype が適用される。
let g:partedit#filetype = 'vim'

ファイルタイプは常に同じだと別のシーンで使いたい場合に困ると思うので、localrc.vim などを利用して、b:partedit_filetype を設定するといいだろう。

テストケースを実行したい その1

答えが正しいか、提出する前に手元で実行して確認したいだろう。
開いているファイルを実行したい、と来れば、quickrun.vim が便利だ。
問題の入力を標準入力で与える必要があるが、幸い quickrun.vim は標準入力にも対応している。input オプションを設定すればよい。

" input.txt ファイルの中身を標準入力として使う
QuickRun -input input.txt

" = で始めることでファイルではなく入力文字列を直接与えられる
QuickRun -input =input-text
" i レジスタの中身を標準入力として使う
QuickRun -input =@i
" b:input 変数の中身を標準入力として使う
QuickRun -input =%{b:input}

" 事前に設定しておけば毎回指定する必要はない
let g:quickrun_config = {'_': {}}
let g:quickrun_config._.input = '=@i'
let g:quickrun_config._.input = '=%{b:input}'

" 実行する際に変数に値を入れれば OK
let b:input = "1\n2 3\ntest\n"

print デバッグするには、put コマンドを使えばよい。
最終的なバッファの内容が出力結果になるので、put コマンドでバッファに行を足してやる。

let hoge = 'debug'
put =hoge
" put の中では " はコメント扱いになり使えないので注意
put ="foo"  これはコメントになるので動かない
put =\"foo\"  " これなら動く
テストケースを実行したい その2

実は、もっと楽な方法がある。ここで s:main() を切り分けておいたのが役に立つ。
単純に s:main() を呼んでしまえば良い。

function! s:main(input) abort
  let a = a:input[0]
  let [b, c] = split(a:input[1], ' ')
  let s = a:input[2]
  return (a + b + c) . ' ' . s
endfunction

echo s:main(['1', '2 3', 'test'])

これで OK だ。ただし、この状態で Bash としてこれを実行しても結果を見ることはできない。
しかし先ほどの partedit.vim で、このバッファは Vim script として独立している。そう。quickrun.vim を使えばよい。このバッファはファイルとして存在していないが、quickrun.vim であれば実行可能だ。

こちらの方法で print デバッグしたい場合は、echo コマンドを使えば OK だ。わかりやすい。

ちなみにこれらの echo の行は、Bash としての実行結果には一切影響を及ぼさないので、そのまま投稿することも可能だが、当然実行時間は長くなってしまうので注意が必要だ。

最後に

Vim script で AtCoder に参加する方法について紹介した。
今回の例では出てこなかったが、当然 main 以外の関数を定義することもできる。
また、よくある操作については vital.vim のコードが参考になる。Data.ListData.StringMath 辺りには便利関数が揃っている。NYSL なので、コピペして使ってもまったく問題ない。是非活用して欲しい。
Enjoy Programming!

*1:軽く調べてみたところ、練習用コンテストの Vim のバージョンは 7.3.429 だった。Ubuntu 12.04 の Vim がこのバージョンなので、この環境を使っている可能性が高い。コンテストによっては環境が異なる可能性があるので注意。

*2:一応別の方法で回避はできる。

Yokohama.vim.reboot #6 に行ってきた

Yokohama.vim.reboot #6 に行ってきた!
最近は勉強会自体あまり行けてなくて、それなのに行っても感想記事サボっているので反省…。久々に書く。

アイスブレイク

Vim に関するキーワードが書かれたカードを、自分には見えない(というより見ない)ように、他の人には見えるように首から下げる。
そしてペアを組んで、軽く自己紹介したあと、互いに自分のカードに書かれた内容について、はいかいいえで答えられる質問をしてって、自分の持ってるカードに書かれたキーワードを当てる、というゲームをした。ペアは適当な時間毎に組替える。
これが意外にむずくて、なかなか当てられない…。キーワードは初心者向けと上級者向けに分かれていたので、私は空気を読んで上級者向けのを取ったのだけど、上級者向けのワードはそもそも初心者の人に質問しても相手がその単語についてよくわかっていなくて曖昧な答えが返ってきたりして、そうなることも計算に入れつつ質問を考えて答えを探すのがめっちゃむずかしかったけど楽しかった。
かなり盛り上がって、予定の時間より長くなったけどめっちゃ良かった。ちなみに私のキーワードは view でした。view コマンド使わないなぁ…。

KazuakiM さんの発表

Vim を使うようになった軌跡みたいなお話。Vim にデレていく話はいい話ですね。
ちなみにサクラエディタを使ってたという話だったけど、私も以前はサクラエディタ使ってました。よさ。

vimrcソースリーディング

各テーブルに分かれて vimrc 読んだりなんだりした。
他のテーブルがどういう感じだったのかは知らないのだけど、私のところのテーブルは3人で、私以外の二人の vimrc を順番に読んでいって改善ポイントとか探したり、改善のための編集操作で、こうした方がいいよ、とかこういう方法があるよ、みたいなアドバイスをしていくみたいな偉そうなアレをしてました。マンボウさん偉そう。
ガッツリ読んで改善して、みたいなことをしていたので、予定のタイムテーブルにあった、スクラッチからの vimrc 構築は結局やらなかった…。希望者が少なかったのもあるけど、スクラッチからはやはりなかなか難しい感じがある。

懇親会

ビアバッシュで、寿司とかナン+カレーとかチキンとか食べつつおしゃべりも少ししつつ割ともくもくと食べてた。
食べ終わって、勢いでやるって言ってあったライブコーディングをすることになったんだけど、ネタは考えてなくもなかったんだけど正直みんなに楽しんで貰えるネタか自信がなくてその場でみんなにネタ聞いたりしてやった。
その結果、とあるsyntax定義プラグインが微妙なので直すのをやって欲しい、みたいな話があって始めたのだけど、なぜか Vim がエラー出しまくって、調査をした結果 Vim のバグっぽいということがわかり、そのIssueを登録するということをして、当初の目的は忘れられてライブVimデバッギングになってしまった…すまぬ…。
これはこれで滅多に見られないもの見せられたしよかったかな感はあるけど、所詮結果論だし、グダってしまったのは本当に申し分けない。その場でお題を募集なんてすると下調べもないもんだからどうやってもグダるし、やはりやるときはちゃんと自分でお題を用意しとこうって思った。次に活かしたい。

まとめ

次回も行きます!

漢字パズルを解く word-finder というのを作った

例えば、以下のような問題があったとします。

    全  全
    ↓  ↓
中→□→□→結
    ↓  ↓
    曲  金

□に入る漢字を答えろ

この手の問題を解く場合、自前の知識の中から、時には勘も交えて総検索することになります。
ヒラメキと言えば聞こえはいいですが、別に考え方を変えたりして解ける問題ではないので、やることはただひたすら検索です。
なら機械にやらせてもいいのでは、ってことで、こういう問題を解くやつを作りました。
とりあえず動くレベルで、仕様とか見た目がかなり雑ですが、動くので公開します。

http://thinca.github.io/word-finder/

使い方

上記の URL を開いても、(少なくとも記事公開時点では)謎のフォームと[検索]ボタンがあるのみで、意味不明です。
というわけで使い方。
まず、わからない部分をアルファベットに置き換えます。

    全  全
    ↓  ↓
中→A →B →結
    ↓  ↓
    曲  金

今のところ置き換えられる文字は半角英数字と一部の記号のみです。
そして、ここから拾える単語を列挙します。

全A
中A
A曲
全B
B結
B金
AB

この単語のリストをフォームに貼り付けます。
そして検索ボタンを押すと…答えが出ます!
ちなみに単語の文字数としては 2-5 文字まで対応していますが、合成単語とかは出ないかもしれないです。詳細は下記の「辞書について」を参照。

なぜブラウザなのか

スマホから使いたかった。

辞書について

できればフリーの国語辞典でもないかなーと思って探したんですが、単語の一覧を簡単に扱えるようなものは見付けられなかったので、最終的に SKK の辞書ファイルに落ち着きました。
SKK の辞書から必要な部分を切り出して使っています。なので、SKK の辞書に載っていない単語は検索できません。
ところで SKK の辞書データって GPL っぽいのだけど、データが GPL の場合にその扱いについて調べたけれど、GPL はソフトウェアに適用されるものらしく、データの場合はよくわからなかった…。
word-finder を GPL にしないといけないならどうせコードは公開しているので問題はないのだけど、なんか気持ち悪いので詳しい方いたら教えていただけると助かります。

そう言えば完全に余談だけど、SKK の辞書って未だに EUC-JP なんですね。そろそろ UTF-8 とかにしてもいいのでは…。辞書に登録できない文字とかも出てきちゃうだろうし。

今後の課題

Web ページを作るということを普段しないので、習作も兼ねているので、作りがだいぶ雑です。CSS すらないし…。

  • 使い方が意味不明なのでせめて説明をページ内に入れたい。
    • スペース取るのは邪魔なのでポップアップするリンクがあると良さそう。
  • 見た目もう少しマシにしたい(案はない)。
  • 辞書を加工するツールをなんとなく Ruby で書いたけど、ブラウザ側のコードが JavaScript(正確には CoffeeScript)なので、統一する意味で nodejs 辺りで書き直したい。
  • jQuery を使っているけど、最近だとあまりよろしくないらしいとの噂なのでなんか置き換えるなどしたいかも。
    • この程度の規模なら別によいって話もあるかもしれないけどよくわかっていない。
  • 読み(カタカナ)の辞書を足せばクロスワードパズルにも使えそうな予感。

OmniSharp.vim のメンテナになりました

http://i.gyazo.com/648e0bbd21949a25313e8ab983e4df04.png
https://github.com/OmniSharp/omnisharp-vim
顔アイコンの中にマンボウアイコンを潜り込ませることに成功しました。壮観ですね。
問題の報告や機能要望などは私に直接言ってもらっても大丈夫です。日本語でOK。サーバ絡みだと対応は難しいかもしれないですが、善処します。
GitHub の Issue の方は世界中の人が見ているので、あちらに書く場合は英語でお願いします。

master への push を禁止するローカル git hook の正しい書き方

GitHub などで Pull Request ベースで開発をしていると、master には間違っても push したくないわけです。
GitHub 側には残念ながら master への push を禁止するような設定はできないので、仕方ないのでクライアント側の Hook で対応しようってことになり、この方法についてググるこことかこことか、いくつか方法を紹介しているページが出てくるんですが、どれもやり方が間違っている*1ので、正しい方法を紹介。

何がまずいのか

上記に挙げた方法では、細かい部分は違ってたりするけど、git symbolic-ref HEAD を使って現在ブランチを見て、master だったら push を禁止する、という方法を取っている。
しかし、push はカレントブランチから行われるとは限らない。dev ブランチにいるときに git push origin master とすれば、当然 master ブランチが push される。この場合上記の方法だと防げない。他にもありがちなパターンだと、git push origin --all などでも master が push される。

正しい方法

pre-push hook を以下のような感じにする。

#!/bin/bash

while read local_ref local_sha1 remote_ref remote_sha1
do
  if [[ "${remote_ref##refs/heads/}" = "master" ]]; then
    echo "Do not push to master branch!!!"
    exit 1
  fi
done

標準入力から push 先のブランチ名の情報なんかがくるので、そこをチェックする。詳しく知りたい人は man githooks の pre-push の辺りを見るとよい。

なぜ間違った方法が出回っているのか

恐らくだけど、master ブランチへのコミットを禁止する pre-commit のスクリプトが流用されたのだと思う。pre-commit の場合は、現在ブランチを見ればいいので、それで正しい。それを pre-push へそのまま適用しようとしてはいけない。

ところで

そもそも GitHub さんがサーバ側で master への push を禁止できる機能を入れてくれると色々と捗るのでそういう機能是非入れて欲しい。なんでないのー。

*1:少なくとも私には正しい方法を記述したページを見付けられなかった

Ruby の Hash で値だけ map で変換したかった

hash = {a: 1, b: 2, c: 3}
hash2 = hash.map {|k, v| [k, v * 2] }.to_h
p hash2  # => {a: 2, b: 4, c: 6}

めんどくさい。Scala には mapValues というのがあるらしい。

Ruby で書くならこうかな。

class Hash
  def map_values(&block)
    dup.map_values!(&block)
  end

  def map_values!(&block)
    update(self) {|_, v| block.call(v) }
  end
end


hash = {a: 1, b: 2, c: 3}
hash2 = hash.map_values {|v| v * 2 }
p hash2  # => {a: 2, b: 4, c: 6}

ググる似たようなはちらほらあるっぽい。Ruby にも標準で欲しかった。

無料で使える CI サービス 8 個まとめ

CI サービスをいくつか触ってみたのでまとめ。
今回の目的は、テストを実行すること。なので、ビルドやデプロイ辺りはちゃんとは見ていない。
ドキュメントで確認しただけの項目などもあったりするので、間違っていたらごめんなさい。教えてもらえると助かります。
ただ、これは記事を書いた時点での比較で、今後のサービスの変更に対応する予定はないです。

触ってみたサービス一覧

アルファベット順。

codeship ってのもあったけど、無料プランは月100ビルドまでとかで常用には耐えないと感じたので中身見てない。

機能比較

機能比較は全て無料プランでのもの。有料だと対応している場合でもここでは x にしている。
比較項目は私の独断と偏見で適当に選出した。

項目 AppVeyor CircleCI Drone IO Magnum CI semaphore shippable Travis CI wercker 備考
YAML による設定 × × リポジトリ内の YAML ファイルからビルド手順を設定できる
Web UI による設定 × × × Web UI でビルド手順のスクリプトを書くことができる
GitHub 連携 GitHubリポジトリ一覧から新規プロジェクトを作れる
BitBucket 連携 × × BitBucket のリポジトリ一覧から新規プロジェクトを作れる
GitHub PR 対応 × × GitHub の Pull Request を自動でビルドしてステータスを返せる
ビルド環境キャッシュ × × × × × 複数ビルド間を跨ぐキャッシュ領域を利用できる
手動リビルド × *1 過去のビルドを手動でリビルドできる
複数環境ビルド × × × × × 環境変数などで複数の環境に対してビルドできる
private なプロジェクト × × × ビルド状況などを private にできる(かなり未検証)
ビルド時間制限 30分 20分 15分 30分 60分 60分 50分 25分 無出力時間による制限が別途ある場合もある

YAML による設定の利点は、プロジェクト構成の変更とビルド手順の変更を紐付けることができること。
Web UI による設定の利点は、プロジェクトに直接関係ないファイルを置かなくて済むこと。特にファイルを置く権限がない場合などには助かる。

各サービスについて雑感

以下、かなり主観かつざっくりな雑感。

AppVeyor

http://www.appveyor.com/

.NET 向けの CI サービス。
.NET に特化した機能が色々使えるけど、重要なのは、テスト環境が Windows であるということ。他に Windows 環境での CI サービスを少なくとも私は知らないのでかなり貴重。.NET 製じゃなくてもこれを使えば Windows 環境でテストができる。
設定のスクリプトは、Windows のバッチファイル(.bat) と、PowerShellスクリプト(.ps1)の形式で書ける。PowerShell .NET のライブラリにアクセスできるので、外部からリソースのダウンロードなども可能。
ビルドトリガーが引かれると、新しいビルドは QUEUE に積まれて、実際にビルドが始まるまでに結構時間がかかる。

ビルド時間について、このページでは全てのプランは40分と書いてあるが、このページでは、Free、Professional、Premium は 30 分で Enterprise は 50 分と書いてある。どちらが正しいか不明。

CircleCI

https://circleci.com/

必要な機能は一通り揃っている印象。
YAML での設定において、各ビルドフェーズに、pre(フェーズ前)、override(フェーズのデフォルト動作を上書き)、post(フェーズ後)それぞれに対してスクリプトが書けたり、コマンド毎にカレントディレクトリや環境変数タイムアウト時間などを指定できるので、柔軟に設定できる。

Drone IO

https://drone.io/

Go 言語製の CI サービス。
他のサービスがビルド手順をいくつかに分割している中、Drone IO のビルド手順は1つのみと、とてもシンプル。
Pull Request 時のビルドは、がんばればできるらしいけど、それでもPR以外の普段の push で API が叩かれたり、ビルドページから Pull Request のページへ飛べなかったりするので、できるとは言わない方が良さそう。

Magnum CI

https://magnum-ci.com/

現時点でまだ public beta の CI サービス。結構前からずっと beta な気がする。
使い勝手については特に問題はない。普通に使える。
beta なので、private リポジトリが無制限で使える模様。いつ beta が取れるのかは不明。

semaphore

https://semaphoreapp.com/

ビルド中にスレッドが使えるのが特徴。ビルドパイプラインの各コマンド単位で、どのスレッドで走らせるか指定できる。無料で 1 ビルド中に 2 スレッドまで使える。
あとはプロジェクト構成を見て、一般的な構成の場合は自動で検出してビルド設定を自動で終わらせてくれるらしい。私が試したプロジェクトは一般的な構成ではなかったので、どこまでやってくれるのかよくわかってない。

shippable

https://www.shippable.com/

Travis CI と互換があり、.travis.yml ファイルが置いてあるとそれを認識してビルドを行ってくれる。ただ、どこまで互換があるのかは不明。Travis CI には language に generic が指定できるのだけど(ただしドキュメント化されていない)、shippable では指定できなかった。
Web UI が開かれるまでに結構時間がかかる。

Travis CI

https://travis-ci.org/

恐らく今回まとめた中では一番有名な CI サービス。
GitHub 限定とは言え、テストをするのに必要な機能は十分揃っている。鉄板。

wercker

http://wercker.com/

他の CI と比べても変わった、box と step という特徴がある。
box はテストの実行環境。ユーザーが作成したものを wercker ディレクトリに登録しておけば、誰でも使えるようになる。特定のツールがインストール済みの環境などを作っておけば、ビルド時間が短縮できる。標準でいくつか用意されているので、それらを使ってももちろんよい。
step はビルドの手順の1つ。bash で言う1つのコマンドで、wercker ではこの step のリストをビルド手順として指定する。step は、特定の動作を抽象化してまとめたもので、Amazon 3S との同期とか、npm install とか、そういう感じのが1つの step として登録されている。最悪 script step というのがあるので、これで bash でやってもよい。この step もユーザーが自作できる。

まとめ

無料でもこれだけの選択肢があるというのがすごい。
よりどりみどりなので、各自のユースケースにあった CI サービスを選んで使うといいと思う。

*1:ブランチ最新のみ