Rubyの配列でごにょごにょするときzipとinjectとevalが便利すぎる件
先日「ブログ書きが不調」と書いたが、言語のアウトプットが不調なときは、それ以外のことをするに限る...たとえば黙々とプログラムを書く、音楽に浸る、身体を酷使する、などだ。というわけで月曜日、天気は晴れなかったものの、研究のデータ処理に使うRubyスクリプトを書きまくって鬱憤を晴らしていた。
ところがそのプログラム脳のまま夜にはVimM#4に行こうとした時、そういえば内定者3人によるSkype会議が予定されていた(つまりダブルブッキング)ことを思い出し、自分の阿呆さに呆れ、本当にすみません、でもって結局気分は晴れず。
前置き(ただの日記)終わり。
本題。
Rubyの配列でいろいろいじる時に、for i in ...と array.each でだいたいできるんだけど、もちっとスマートにやりたいなぁと思って調べていたらこれ使えるんじゃね?と気がついたことがいくつかある。zipとinjectとevalの一般的な(たぶん)使い方を自分なりに書く。素人なので間違ってたり無意味だったりもあるので注意。
Enumerable#zip
Array#zip
zipは任意の数の配列を元に、同じ順番でまとめた配列を生成する。たとえばこうやって使うと、配列を返す。
hiragana = ["あ", "い", "う", "え", "お"] alphabet = ["a", "b", "c", "d", "e"] number = [1, 2, 3, 4, 5] #Fixnum class p number.zip(alphabet) #=> [[1, "a"], [2, "b"], [3, "c"], [4, "d"], [5, "e"]] p hiragana.zip(alphabet, number) #=> [["あ", "a", 1], ["い", "b", 2], ["う", "c", 3], ["え", "d", 4], ["お", "e", 5]] p number.zip(alphabet, hiragana) #=> [[1, "a", "あ"], [2, "b", "い"], [3, "c", "う"], [4, "d", "え"], [5, "e", "お"]]
そして、下にブロックを置くと、index順に一個ずつ取り出して処理することができる。
number.zip(hiragana, alphabet){|n, h, a| print "number is #{n}, alphabet is #{a}, and hiragana is #{h}\n" } # => # number is 1, alphabet is a, and hiragana is あ # number is 2, alphabet is b, and hiragana is い # number is 3, alphabet is c, and hiragana is う # number is 4, alphabet is d, and hiragana is え # number is 5, alphabet is e, and hiragana is お
巨大な配列をindex順に処理したかったので助かった。ループ3重にまわすのとどっちが速いのか知らんけど。
Enumerable#inject
数学的帰納法、つまりn個目の要素に何か処理をしてn+1を作り出す、その操作を要素の数だけ繰り替えしたい時に使うメソッド。injectの一番簡単な使い方は、
ary = [1,2,3,4,5,6,7,8,9,10] ary.inject(0) {|sum, i| sum + i }
解説を加えようとしたが ruby の inject をわかりやすく説明してみる - Moderation is a fatal thing. Nothing succeeds like excess. に非常にわかりやすく書かれていたのでまあいいやw 以下、フィボナッチ数列の例を引用。
(0..10).inject([1, 1]) {|fib, i| fib << fib[i] + fib[i+1] }
なるほど。適当に考え方を箇条書きしてみると
- iの初期値は引数として与える*1
- ブロックの返り値がsumに格納される
- 配列の次の要素がiとなって、次の周が回り始める。
こんなところか。
なんとなくRでやるとどうなるかなとか考えたけど考えただけだった。相変わらずHow toの段階で悪戦苦闘しており、Rでいじるほどの研究データが出ない(定性的なのしか出ない)。楽しいんだけどな...外部的な価値はない。
eval
evalは、与えられた文字列をRubyのコードとして解釈する。これすごい強力だな。 複数の配列に対して同じ処理をしたかったのだけど、evalで一発解決ですよ奥さん。先ほどのinjectと会わせてすっきりと書いた。気持ちいい。
%w(@aryX @aryY @aryZ).each do |a| sum = 0, n = 0 eval(a).inject(eval(a)[0]) {|sum, n| sum + n} averageCorrdinates << sum/eval(a).length end
eval多用すると危険らしいのだけど、どう危険かよくわからん。ローカルで手動で使う分には大丈夫じゃないかと踏んでいる。これeval使わずにできるのかな。
なにやってんの
作ってたのはぴーでぃーびーをいじるスクリプトで、完成品は githubの"lab"ディレクトリ に置いてある。
平行移動、回転、指定領域の分子を複製などの機能をメソッドとして持たせた。rubyで実行する際に引数でオプションいれてやれば使える。bioruby使ううえに糞コードだけどよかったらどうぞ。
Rubyでぴーでぃーびーふぁいるをいじるというのは、検索すると僕のブログかリファレンスしか出ないというひどい状況なのでしっかり解説したりあれこれ検討したいんだけど、研究室にブログを隠しているのでおおっぴらに研究関連の話書けない*2のが悩みどころ。
顔も名前も出してるくせになんで秘密にしているかというのはハックルさんと会ってきた際の記事で少し触れた。
まぁ、どんどん外堀が埋められており、もはやバレるのも時間の問題かもしれないが、卒業まで隠し通せれば御の字。で、最終的にバレてないと思っているのは僕だけになってたりしてな。