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

(1213d) 更新

公開メモ

概要

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

仕様について

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

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

1. 始めに思いついた普通の(?)やり方

部分統治でしこしこ計算する。

& の初期値が -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. 短くした

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文字

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. 正規表現で手抜きをする

括弧を挿入してあとは eval

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

2-1. 短くした

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

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

2-2. ループにした

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

長くなった orz

3. 逆に考える

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

3-1. 短くする

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

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

3-2. 邪悪なゴルフ

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

短くは、なる。

2-3. この方針でいくなら

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

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

4. 括弧を演算子の両側に入れる案

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. ゴルフ

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

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

括弧を使えるようにする

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

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> で再帰的なマッチをした後に $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

質問・コメント





Counter: 2295 (from 2010/06/03), today: 2, yesterday: 0