Hatena::ブログ(Diary)

mfumiの日記

2010-02-24

ProjectEuler.vim : VimからProjectEuler

ProjectEulerとは?

ProjectEulerとは、プログラムで解く数学の問題集です。

公式サイト:no title

つまり、数学的知識が必要で、かつプログラムを組まないととてもじゃないけど解けない問題が多数公開されています(2009-2-24時点で279問あります)。ユーザー登録することで問題に解答できるようになります。問題の日本語訳は以下のサイトにあります。

日本語訳:Project Euler - PukiWiki

ProjectEuler.vimって何?

このProjectEulerの問題をVimから閲覧・取得・解答できるようにすのがこのProjectEurler.vimです。なんとなく作ってみました。

インストール方法

ソースは no title にあります(gitとかはじめて使った)。

以上のファイル/ディレクトリを適当な場所に置いて、パスを通して下さい。

helptag path-to/doc/ でヘルプが追加されます。

コマンド一覧

:ProjectEuler [id]

Problem <id> の解答をProjectEulerに投稿します。

idが指定されなかった場合はファイル名から推測、それでも分らなかった場合尋ねます。


:ProjectEuler -l [pagenr]

問題のリストの1ページ目を表示します。

pagenrを指定すればそのページのリストを表示します。


:ProjectEuler -p [id]

Probrem <id> の問題を表示します。

idが指定されなかった場合はファイル名から推測、

それでも分らなかった場合尋ねます。


:ProjectEuler -s

自分のプロファイルを表示します。

問題の箇所で<Enter>を押せばその問題を表示します。


:ProjectEuler -c

現在開いている問題の言語を入れ替えます。


:ProjectEuler -o [id]

Probrem <id> の問題のページをブラウザで開きます。

idが指定されなかった場合はファイル名から推測、

それでも分らなかった場合尋ねます。


:ProjectEuler -u [username]

<username>にユーザーを切り替えます。

<username>が指定されなければ現在のユーザー名を表示します。


:ProjectEuler -r

現在のユーザーで強制的にログイン処理を行います。

Vimを使用中にCookieの有効期限が切れてしまった時に行って下さい。

変数一覧

g:projecteuler_user

ProjectEulerにログインするデフォルトのユーザー名


g:projecteuler_base_dir

ProjectEulerの問題およびCookieを保存するためのディレクトリ

let g:projectuler_base_dir = "path-to/projecteuler/"

みたいに設定して下さい。最後の/を忘れると正しく動作しません。

デフォルトではプラグインのおかれたディレクトリから../projecteuler/となります。

このディレクトリはあらかじめ作成する必要があります


g:projecteuler_browser_command

ProjectEulerの問題等を閲覧するブラウザ

let g:projecteuler_browser_command = "コマンド名 %URL% &"

みたいに設定して下さい

%URL%をを抜かすと正しく動作しません

デフォルトでは多分デフォルトブラウザが起動します(ややこしい…)


g:projecteuler_audio_play_command

CAPTCHAの音声を再生するためのコマンド

let g:projecteuler_audio_play_command = "コマンド名 %AUDIO% &"

みたいに設定して下さい

%AUDIO%をを抜かすと正しく動作しません

デフォルトでは、WindowsがMediaPlayer,MaciTunes,その他ではpaplayがあればpaplayです。


g:projecteuler_hold_cookie

ProjectEulerのCookieを保存するかどうか。

0:保存しない(Vim終了時に破棄) 1:保存する

デフォルトでは保存します


g:projecteuler_see_next

問題を正解したあと次の問題を表示するかどうか。

0:表示しない 1:表示する

デフォルトでは尋ねます


g:projecteuler_open_forum

問題を正解したあとフォーラムをブラウザで開くかどうか。

0:開かない 1:開く

デフォルトでは開きません


g:projecteuler_problem_lang

問題を取得・閲覧する際の言語を設定します。

let g:projecteuler_problem_lang = "ja"

とすれば日本語の問題文を取得・閲覧します

(日本語の問題は最初に紹介した日本語訳のページから取得しています)

デフォルトでは英語です

その他の機能

・問題のリスト表示時に、問題の説明箇所で<Enter>を押せばその問題を表示します。もし一番下のページ番号で<Enter>を押せばそのページ番号に移動します。

・プロファイル表示時に、問題の箇所で<Enter>を押せばその問題を表示します。

・問題表示時に、Problem 67のようにテキストがダウンロードできる場所ではその場所で<Enter>を押せばテキストがg:projecteuler_dirにダウンロードされます。

動作の仕組みなど

音声の再生方法

ProjectEulerでは、解答の投稿にはCAPTCHA認証が必要です。ProjectEuler.vimではこれを、CAPTCHAの音声ファイルを取得して再生するという強引なやりかたで切り抜けます(あーでもこれ読むかぎりなんかいけそう)。デフォルトでは、WindowsではWindows MediaPlayer , MacではiTunesが起動しますが、これでは全然うれしくありません。Macでは、

#import <AppKit/AppKit.h>

int main(int argc, char *argv[])
{
	if (argc < 2) return 0;
	NSAutoreleasePool *pool = [ [NSAutoreleasePool alloc] init];
	NSSound *sound =  [ [ [NSSound alloc] initWithContentsOfFile:[NSString stringWithCString: argv[1] encoding:NSUTF8StringEncoding] byReference:YES] autorelease];
	if (sound)
	{
		[sound play];
		while([sound isPlaying]){}
	}
	[pool release];
	return 0;
}

というプログラムを作って、これを

$ gcc -Wall play.m -framework AppKit -o play

みたいにコンパイルして、.vimrcに

let g:projecteuler_audio_play_command = '~/play %AUDIO% &'

とするといいと思います(参考: コマンドラインで WAV/AIFF を再生 - 空想犬猫記)。

Windowsならば、ここからplay.exeをダウンロードして、_vimrcに

let g:projecteuler_audio_play_command = 'C:\play.exe %AUDIO%'

みたいにするといいと思います。linuxなんかではpaplayがいいでしょう。こうすれば他のアプリケーションを立ち上げずにすみます。

IDの特定方法

:ProjectEuler などのコマンドでID(問題番号)を省略した場合には、現在のファイル名から推測を行います。実際には現在のファイル名から数字だけ抜き出します。つまり、Problem1.plというファイル名ならばIDは1になりますが、Problem1(2).plみたいなファイル名だとIDは12になります。予期しないIDになってしまったら素直にIDを指定してやりなおしてください。

問題の取得について

問題文はわざわざ何度も取得しても意味がないので、g:projecteuler_dirに保存します。まれに問題文が変わることがあるみたいですが、そうした場合は一度問題を消してから再び取得して下さい。

ログイン処理について

ログインCookieで管理します。一度ログインしたユーザーは記憶されていて、それ以降Cookieの有効期限を確認せずに通信します。そのため、Vimを起動中にCookieの有効期限が切れてしまうと解答などができなくなってしまいます。もしそうなってしまったら:ProjectEuler -r で強制的にログイン処理をおこなって下さい。ちなみにprojecteuler.netのCookieの有効期限はだいたい1ヶ月です。

Cookieの削除

Cookieの削除についての設定はスクリプト読み込み時に:autocommandで設定しているのでCookieを保存したくない場合はあらかじめvimrcに

let g:projecteuler_hold_cookie = 0

としてください。

解答について

ProjectEulerでは一度問題を間違えて解答すると30秒しなければ次に解答できません。

スクリプト文字コード

スクリプトUTF-8で書かれています。iconvがないとWindowsでは文字化けして使いものにならなくなっちゃうとおもいます。

(2010-2-25 追記):scriptencodingというコマンドを使えば大丈夫ということなのでそれに変更しました。

問題点

・すでに解答してある問題にも解答できます。そのまま成否が判定されます(まぁでもこれはこれでいいかな)。

Windowsではパスにスペースがあると上手く動作しないかもしれません。

ログイン処理で「ログインに成功しました」となるのにプロファイルなどがうまく取得できなかったり問題に投稿できなかったりしたらg:projecteuler_dirが存在しないからだと思います。g:projecteuler_dirを作成してみて下さい。

vimrc

私の.vimrcは次のように設定しています。

let g:projecteuler_user = "mfumi"
let g:projecteuler_audio_play_command = g:projecteuler_dir . "play %AUDIO% &"
let g:projecteuler_problem_lang = "ja"
nnoremap	 <Space>p		<NOP>
nnoremap <Space>pp	:<C-u>ProjectEuler<CR>
nnoremap <Space>pl		:<C-u>projecteuler -l<CR>
nnoremap <Space>ps		:<C-u>ProjectEuler -s<CR>
nnoremap <Space>pc		:<C-u>ProjectEuler -c<CR>
nnoremap <Space>po	:<C-u>ProjectEuler -o<CR>
nnoremap <Space>pr		:<C-u>ProjectEuler -r<CR>
nnoremap <Space>pu	:<C-u>ProjectEuler -u<CR>

その他(感想など)

quickrun.vimと組合せれば完全にVimで完結してProjectEulerができると思います。

はじめは問題の取得しか考えていなくて、その後どんどん思いつきで機能を足していったのでコードがごちゃごちゃになってしまいました(言い訳)。特に問題文の整形処理などはいろいろと試行錯誤しながらやったのでひどいかもしれません。まぁでも自分の予想以上のものができたと思います。プラグインを作ったのは初めてだったので、いろいろなプラグインを参考にさせて頂きました。特にログイン処理はhatena.vimを、ブラウザの起動などはGist.vimを参考にしています。%とか&とかがpatternで展開されるのに気づかなかったり、'と"はいつでも同じだろうと思ったり、Windowsのパスの区切り「\」の扱いなど、結構はまりました。基本的にMacOSX SnowLeopard & MacVim-kaoriyaでしか動作確認していないのでおかしいところがまだまだあると思います。もし何かあれば、メールもしくはここにコメント頂けたら嬉しいです。


スクリーンショット

f:id:mFumi:20100224205827p:image


f:id:mFumi:20100224205820p:image


f:id:mFumi:20100224205814p:image

ただでさえ試験でやってなかったのに最近はこれ作ってたから全然進んでない…

2010-02-23

Cookie.txtの内容

cURLで受け取って保存したcookieの読み方のメモ。ここに書いてありました。

[domain]                [flag]  [path]   [secure]   [expiration]      [name]             [value]
projecteuler.net        FALSE     /       FALSE   1269495117      p_username      hoge

domain … cookieを発行&読みとれるドメイン

flag … cookieを受け取ったドメイン内全てのマシンがそのcookieを読めるかどうか

path … cookieが有効となるドメインのパス

secure … セキュアな通信が必要かどうか

expiration … 有効期限。UNIX時間

name … 変数の名前

value … 変数の値

2010-02-18

VimでFizzBuzz

vimscriptを勉強してみることにしました。とりあえず新しい言語やるときはHello World!とFizzBuzzだよね!ってことでやってみました。

let s:n = 50

function! Fizzbuzz()
  let i = 1
  while i <= s:n
    if !(i % 15)
      echo "FizzBuzz"
    elseif !(i % 5)
      echo "Buzz"
    elseif !(i % 3)
      echo "Fizz"
    else
      echo i
    endif
    let i += 1
  endwhile
endfunction

call Fizzbuzz()

そのまんまです。何もひねってないです。

ちなみにこんなの見つけました。

no title

wow…

2010-02-17

changelog.vim : vimでchangelogメモ

これを読んでみて私もchangelogメモを始めてみることにしました。

Vimには標準でchangelog.vimというプラグインがあります。使い方はこちらが参考になります。

で、このプラグインはヘルプによれば.vimrcに

runtime ftplugin/changelog.vim

とするとグローバルにマッピングされます。この状態で<Leader>oを押すとカレントディレクトリから再帰的にルートまで、ファイル名が「Changelog」のファイルを探し、もし見つかればそのファイルにエントリーを追加するとなっています(見つからなかったら何もしません)。ところが、私のMacVim-kaoriyaに付属してきた、Last Revision 20009-05-25 のchangelog.vimだと、「E118: 関数引数が多過ぎます: <SNR>15_new_changelog_entry」とエラーになってしまいます。ということで、ソースを見てみると、

173  function! s:new_changelog_entry()

となっているのに、

296  call s:new_changelog_entry(prefix)

とおもいっきり引数有りで呼び出しています。少し調べてみたところ、このページが見つかりました。

Proposed change to ftplugin/changelog.vim

これを読む限り、b:changelog_entry_prefixという関数が定義されてあればそれによって、そうでなければChangelogファイルから現在のファイルまでの総体パスがprefixとなり、エントリーが追加されます。つまり、

Changelog
hoge/
  hogehoge.c

みたいなときに、hogehoge.cを開いているバッファから<Leader>oとすれば

 * hoge/hogehoge.c : 

と自動で現在のバッファの情報を付け加えてくれるようです。ところがなぜか、changelog.vimはcall s:new_changelog_entry(prefix)となっているのに肝心の関数本体は変更されていません。個人的には<Leader>oと押したときに、「Changelog」ファイルが見つからなければChangelogメモを開いてそこにエントリーを追加してくれればいいなと思ったので、そこら辺を修正してみました。

最後の部分で変更してるところがChangelogメモを開くために付け加えたところです(Changelogメモに現在のファイルへのパスを書いてもしかたないので)。それ以外のところは上記のサイトそのままです。これで、どんなファイルを開いていても、<Leader>oを押せば、

・もしルートまで再帰的に探索していって「Changelog」ファイルがあれば、それに(prefixつきで)エントリーを追加

・そうでなければ、Changelogメモを開いてエントリーを追加

となります。ここら辺はいずれ変更されるんでしょうか。とりあえず今のところはなかなかいい感じです。


(参考)

:help ft-changelog-plugin

2010-02-15

メモ:ポインタのインクリメントについて

int *pについて。+は値が1増加したことを示す。はじめのp,p[0],p[1]の値は式の評価時時点での値。その次が評価終了時点での値。

*と++は優先順位が等しい(右から左へ評価される)


評価値p p[0] p[1]p p[0] p[1]
*++p p[1] + - - - - -
++*p = ++(*p) p[0]+1- + - - - -
*p++ p[0] - - - + - -
(*p)++ p[0] - - - - + -
++*++p = ++(*++p) p[1]+1+ - + - - -
(*++p)++ p[1] + - - - - +
impossible p[1] + + - - - -
impossible p[1] + - - - + -
++*p++ = ++(*p++) p[0]+1- + - + - -
(*p++)++ p[0] - - - + + -
impossible p[0] - - + + - -
impossible p[0] - - - + - +
Connection: close