2010年08月14日 ゴルフ感覚で
_ rubyで式量計算
私は、データ処理の大部分にrubyを使っている。物質を扱っていると、式量を計算する必要が出てくることがあるが、これもrubyで計算している。具体的には、formula.datに元素記号と原子量のデータを入れておいて、化学式の中に含まれる、元素記号と数字を読み取って、原子量と数をかけて足している。かなり前に書いたスクリプトがこれである。def fmlwt(fml) awt={} IO::foreach("formula.dat") do |l| awt[$1]=$2.to_f if l=~/([A-Z][a-z]?)\s+([\d\.]*)/ end fw=0 while fml.size>0 break unless m=/([A-Z][a-z]?)([\d\.]*)/.match(fml) aw=awt[m[1]] an=(m[2]=="")?1:m[2].to_f fw+=an*aw fml=m.post_match end fw=1 if fw==0 return fw end原子量のデータを一行ずつ読んで、hashに記録して、その後で化学式を一元素ごとに正規表現でマッチして処理している。しかし、かなり長い気がする。そこで、久しぶりに書き直してみた。まず、書いたのがこれ。
def fmlwt2(fml) awt=Hash[*IO.readlines("formula.dat").map{|l| l.strip.split(/\s+/)}.flatten] fml.split(/(?=[A-Z])/).map{|e| awt[$1].to_f*(($2=="")?1:$2.to_f) if e=~/([A-Z][a-z]?)([\d\.]*)/ }.inject{|s,x| s+x} end原子量のデータは一気に読んで行列にして、一行の前後の空白を除去してから空白で区切って、それをhashに変換している。ここで、コードの節約のために、原子量はまだ文字列のままである。その後で、各元素ごとに区切ってから、正規表現を使って処理して、それを最後にinjectで足している。だんだんゴルフ感覚になってきた。もう少し短くできた。
def fmlwt3(fml) awt=Hash[*IO.read("formula.dat").strip.split(/\s+/m)] fml.split(/(?=[A-Z])/).grep(/([A-Z][a-z]?)([\d\.]*)/){ awt[$1].to_f*(($2=="")?1:$2.to_f) }.inject{|s,x| s+x} endファイルを行をくっつけて読み込んで、それをそのまま配列にしてからhashにしている。後半はgrepを使って正規表現部分をすっきりとさせた。昔書いたコードを見ると、美しく感じられないことが多い。徐々にスクリプトを書く流儀が変化しているからなのだろうが、まだまだ未熟な気がする。しかし、短く書けば良いというものでも無い。後半の処理は良くなったと思うのだが、原子量のhashを作る部分は、最初のやつの方が何をしているのかはっきりしているようにも感じられる。化学式に関して言えば、本当は括弧などにも対応したいのだが、再帰を使えば出来そうな気がしているが、まだ手をつけていない。しかし、こういったプログラムを使うときには、単純な入力ミスが、誤ったデータ処理につながるので、逐一ミスが無いことをチェックすることが重要である。
_ 2010/8/15追記
分かりやすさと短さを考えて、こんなのはどうだろうか。def fmlwt4(fml) awt=Hash[*IO.read("formula.dat").strip.split(/\s+/m)] r=0 fml.scan(/([A-Z][a-z]?)([\d\.]*)/){|s| s[1]=1 if s[1]=="" r+=awt[s[0]].to_f*s[1].to_f } r end
_ 2010/10/11追記
scanを{|a,n|}で受ければ、s[0]とか書かないでも良くなることに気がついた。