プログラミング/ruby/遠い世界の数学 の変更点

更新


[[公開メモ]]

#contents

* 概要 [#ma132a96]

http://qiita.com/cielavenir/items/cadbc5e24525b6a86cf8

こちらで紹介されていた、

http://nabetani.sakura.ne.jp/kanagawa.rb/evalex/

このお題について、単純に面白そうだったので趣旨もよく理解しないまま一人でやってみました。


どうやらこういうものがあったらしいです。> 神奈川Ruby会議

http://regional.rubykaigi.org/kana01/contents/02_pair_programing.html

** 仕様について [#h9437c63]

本来の仕様は数式を表わす文字列を受け取って、答えを表わす「文字列」を返さなければならないのですが、以下の関数の戻り値は整数になっていて、仕様を満たしていません。

ですので、すべて最後に .to_s を追加しないといけません。どうもすみません。

* 1. 始めに思いついた普通の(?)やり方 [#t102675d]

部分統治でしこしこ計算する。~
~& の初期値が -1 なので、32ビット(31ビット?)以上の数値が出てくると破綻しそう。

 LANG:ruby
 def calc(expr)
   expr.split('*').inject(1) do |result, expr|
     result * expr.split('+').inject(0) do |result, expr|
       result + expr.split('&').inject(-1) do |result, expr|
         result & expr.split('|').inject(0) do |result, expr|
           result | expr.to_i
         end
       end
     end
   end
 end

** 1-1. 短くした [#o7cc1ec8]

 LANG:ruby
 def calc(s,o='*+&|')
   s.split(o[0]).map{|s|o=='|'?s.to_i: calc(s,o[1,3])}.reduce(o[0])
 end

素直さや実行効率を考えると、たぶんこれが一番おすすめ。

** 1-2. もう1文字 [#o7cc1ec8]

 LANG:ruby
 def calc(s,o='*+&|')
   o==''?s.to_i: s.split(o[0]).map{|s|calc(s,o[1,3])}.reduce(o[0])
 end

恐らくゴルフじゃないので怒られそう・・・

* 2. 正規表現で手抜きをする [#qb461a1a]

括弧を挿入してあとは eval

 LANG:ruby
 def calc(expr)
   eval expr.gsub(/[^()*]+/,  '(\\0)')
            .gsub(/[^()*+]+/, '(\\0)')
            .gsub(/[^()*+&]+/,'(\\0)')
 end

** 2-1. 短くした [#ecb8c47b]

 LANG:ruby
 def calc(s)
   eval s.gsub(/[^*]+/){"(#{$&.gsub(/[^+]+/){"(#{$&.gsub(/[^&]+/,'(\\0)')})"}})"}
 end

むしろ上のやつのが短い。~
もっと短くできるかしら?

** 2-2. ループにした [#m9b903ed]

 LANG:ruby
 def calc(s)
   eval %w(& + *).inject(->(s){s}){|f,o| ->(s){s.gsub(/[^#{o}]+/){"(#{f.call $&})"}}}.call(s)
 end

長くなった orz

* 3. 逆に考える [#k1384c6d]

 LANG:ruby
 def calc(expr)
   eval expr.gsub(/[|\d]+/,'(\\0)')
            .gsub(/[&|\d()]+/,'(\\0)')
            .gsub(/[+&|\d()]+/,'(\\0)')
 end

** 3-1. 短くする [#o91eb30d]

 LANG:ruby
 def calc(s)
   1.upto(3){|i|s.gsub!(/[#{'|&+'[0,i]}\d()]+/,'(\\0)')};eval s
 end

これはかなりすっきりかも。

** 3-2. 邪悪なゴルフ [#j9e27a6b]

 LANG:ruby
 def calc(s)
   '&+'.gsub(//){s.gsub!(/[|#{$`}\d()]+/,'(\\0)')};eval s
 end

短くは、なる。

** 2-3. この方針でいくなら [#u9985b32]

逆じゃないほうが1文字短い

 LANG:ruby
 def calc(s)
   '+&'.gsub(//){s.gsub!(/[^*#{$`}()]+/,'(\\0)')};eval s
 end

* 4. 括弧を演算子の両側に入れる案 [#g3d3398c]

 LANG:ruby
 def calc(s)
   eval "(((#{s.gsub(/[*]/,   ')\\0(')
               .gsub(/[*+]/,  ')\\0(')
               .gsub(/[*+&]/, ')\\0(')})))"
 end

あるいは、

 LANG:ruby
 def calc(s)
   eval "(((#{s.gsub(/\*/, ')))*(((')
               .gsub(/\+/,  '))+((' )
               .gsub(/\&/,   ')&('  )})))"
 end

** 4-1. ゴルフ [#j9e27a6b]

 LANG:ruby
 def calc(s)
   '+&'.gsub(//){s.gsub!(/[*#{$`}]/,')\\0(')};eval "(((#{s})))"
 end

最後に括弧で括るところで長くなる。

* 括弧を使えるようにする [#r26fe5fb]

お題にはないですが、式の中で括弧を使えるようにするには、

 LANG:ruby
 def calc_with_paren(s)
   while s.sub!(/\(((?:[^(]+|\g<0>)*)\)/){calc_with_paren($1)} 
   end
   calc(s)
 end

とすれば良いはずが、ruby2.1.5 では \g<0> で再帰的なマッチをした後に
とすれば良いはず。ところが、ruby2.1.5 では \g<0> で再帰的なマッチをした後に
$1 を参照すると、negative string size (or size too big) のエラーが出たり、
あるいはおかしな部分文字列が返ってきたりでまともに使えませんでした。
$& は正しく返ってくるので、正規表現ライブラリのバグっぽい雰囲気です。

→ やはりそうでしたので、報告しておきました。~
https://github.com/k-takata/Onigmo/issues/48

このバグを回避するには、$1 を使わず $& を参照して、
以下のようにする必要がありました。

 LANG:ruby
 def calc_with_paren(s)
   while s.sub!(/\((?:[^(]|\g<0>)*\)/){calc_with_paren($&[1,$&.length-2])} 
   end
   calc(s)
 end

* 質問・コメント [#m332a2d2]

#article_kcaptcha

Counter: 3320 (from 2010/06/03), today: 1, yesterday: 0