古い Ruby の define_method

AtCoder の過去問をやっていて、手元では通るコードがことごとく RE になる理由が全然わからなかった。いろいろ考えてみたが、コードはどう考えても正しい気がする。


ふと、自分は横着して何も考えずに Ruby 2.7.0 を使っていたが、AtCoderRuby は 2.3.3 なことにハッと気づいた。たまたま rbenv で Ruby 2.3.3 を既にインストールしてあったので、$ rbenv local で切り替えて Ruby 2.3.3 で実行してみたところ、何とエラーが出る。これだったのか。

詳しくは書かないけれど、2.7.0 のコードは Module#define_method をこんな風に使っていた。

class Hoge
end

a = 1

Hoge.define_method(:output) do
  puts a
end

Hoge.new.output    #=>1

ローカル変数 a をふつうはスコープの外であるメソッド内と共有しようという意図である。まあ、この手のコード自体がよいものかは別だが、とにかくこれは 2.7.0 で動く。


しかしこれ、2.3.3 ではエラーが出る。何故かというと、2.3.3 では Module#define_method がプライベート・メソッドだからである。つまり、レシーバーを付けて呼び出せないようになっている。
解決方法はある。Object#send を使えばよい。これを使えば、御存知のとおりプライベート・メソッドまで呼び出せてしまう。

Hoge.send(:define_method, :output) do
  puts a
end

これで AtCoder でも動いた。

なお、Module#defile_method がパブリック・メソッドになったのは、たぶん 2.5.0 からである。るりまで調べてみた。

ブレゼンハムのアルゴリズム(Ruby)

ja.wikipedia.org「ブレゼンハムのアルゴリズム」とは画面に線分を描くアルゴリズムです。コードはここJava 版を移植させていただきました。

Ruby コードです。描画に Cairo を使っています。

require "cairo"

class Draw
  W = 400
  Side = 10
  
  def initialize  
    @surface = Cairo::ImageSurface.new(W, W)
    @context = Cairo::Context.new(@surface)
    
    #背景色
    @context.set_source_color(Cairo::Color::BLACK)
    @context.rectangle(0, 0, W, W)
    @context.fill
  end
  
  def point(x, y)
    @context.set_source_color(Cairo::Color.parse("#73F829"))
    x1 = x * Side
    y1 = y * Side
    @context.rectangle(x1, y1, Side, Side)
    @context.fill
  end
  
  def finish
    @surface.write_to_png("pic.png")
  end
  
  def self.exe(&bk)
    d = Draw.new
    d.instance_exec(&bk)
    d.finish
  end
end


def bresenham(x0, y0, x1, y1)
  dx = x1 - x0
  step_x = (dx >= 0) ? 1 : -1
  dy = y1 - y0
  step_y = (dy >= 0) ? 1 : -1
  
  dx = dx.abs * 2
  dy = dy.abs * 2
  
  @f.point(x0, y0)
  x = x0
  y = y0
  
  if dx > dy
    fraction = dy - dx / 2
    until x == x1
      if fraction >= 0
        y += step_y
        fraction -= dx
      end
      x += step_x
      fraction += dy
      @f.point(x, y)
    end
  else
    fraction = dx - dy / 2
    until y == y1
      if fraction >= 0
        x += step_x
        fraction -= dy
      end
      y += step_y
      fraction += dx
      @f.point(x, y)
    end
  end
end


if $0 == __FILE__
  @f = Draw.new
  bresenham( 3,  3, 35, 15)
  bresenham(35, 15, 18, 37)
  bresenham(18, 37,  3,  3)
  @f.finish
end

メソッドbresenham が求めるものです。Drawクラスは Cairo での描画をしています。

結果はこんな感じです。
20200123012619

Ruby で簡単な Gray-Scott

qiita.comここの Python 版を Ruby に移植しただけです。計算に Gem 'numo-narray' を使っています。画像化には Gem 'gdk_pixbuf2', 'rmagick' を使いました。できたものはこんな感じ。
20191115013736
 
Ruby コード。Ruby 2.6.0, Linux Mint 19.2 で動作確認。
gray_scott.rb

require 'numo/narray'
require 'gdk_pixbuf2'
require 'rmagick'

include Numo

SPACE_GRID_SIZE = 256
VISUALIZATION_STEP = 8
Dx = 0.01
Du = 2e-5
Dv = 1e-5

def laplacian(ary, base)
  #NArray オブジェクトのコピー
  inflow_from_top    = base.reshape(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  inflow_from_bottom = base.reshape(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  inflow_from_left   = base.reshape(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  inflow_from_right  = base.reshape(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  outflow            = base.reshape(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  
  #ラプラシアンの計算
  (SPACE_GRID_SIZE - 1).times do |i|
    inflow_from_top[i + 1, true]  = ary[i, true]
    inflow_from_bottom[i, true]   = ary[i + 1, true]
    inflow_from_left[true, i + 1] = ary[true, i]
    inflow_from_right[true, i]    = ary[true, i + 1]
  end
  outflow = ary * 4
  
  (inflow_from_top + inflow_from_bottom + inflow_from_left +
    inflow_from_right - outflow) / (Dx * Dx)
end

#計算(f, k の値によって反応が変わります)
def calc(u, v, f, k)
  u_base = SFloat.ones(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  v_base = SFloat.zeros(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  VISUALIZATION_STEP.times do
    partial_u = laplacian(u, u_base) * Du - u * v * v + (1.0 - u) * f
    partial_v = laplacian(v, v_base) * Dv + u * v * v - (f + k) * v
    u += partial_u
    v += partial_v
  end
  [u, v]
end


#初期設定
Dir.chdir("picture")

SQUARE_SIZE = 20

u = SFloat.ones(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
v = SFloat.zeros(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
square_start = SPACE_GRID_SIZE / 2 - SQUARE_SIZE / 2
square_end   = SPACE_GRID_SIZE / 2 + SQUARE_SIZE / 2
u[square_start..square_end, square_start..square_end] = 0.5
v[square_start..square_end, square_start..square_end] = 0.25

#画像の生成
400.times do |i|
  u, v = calc(u, v, 0.022, 0.051)
  
  for_visualize = UInt8.cast(u * 255)
  data = UInt8.zeros(SPACE_GRID_SIZE, SPACE_GRID_SIZE, 3)
  data[true, true, 0] = for_visualize
  data[true, true, 1] = for_visualize
  data[true, true, 2] = for_visualize
  
  pixbuf = GdkPixbuf::Pixbuf.new(data: data.to_string,
              width: SPACE_GRID_SIZE, height: SPACE_GRID_SIZE)
  pixbuf.save("%04d.png" % i)
end

#gif化
list = Magick::ImageList.new
Dir.glob("*").sort.each do |fn|
  list.concat(Magick::ImageList.new(fn))
end
list.delay = 3
list.write("gifanime.gif")

作業用にカレントディレクトリに 'picture' ディレクトリを作って下さい。このコードの場合だと、400ステップ分の画像を生成します。

なお、ラプラシアンの差分方程式化はここなどが参考になります。

※参考
Gray-Scott というのを作ってみた記録 – ✈
Numo::NArray概要 · ruby-numo/numo-narray Wiki · GitHub
 

f = 0.012, k = 0.05 の場合。
20191115014553
 
Gtk によるリアルタイム・アニメーション版はこちら。
https://gist.github.com/obelisk68/ecd97e79b87f69365a0fb0747ffcedca

文字列を一文字づつ飛ばし文字にするのこと(Ruby)

まず、「esehara」という文字列がある。この文字列を「eeaa」といったように、一文字ずつ飛ばし文字にするにはどうしたらいいか

http://bugrammer.hateblo.jp/entry/2016/10/24/175704

あるブログでこんなのを見て考えてみた。

コード例。

def one_jump(str)
  str.chars.each_slice(2).map {|a, *b| a}.join
end

puts one_jump("esehara")    #=>eeaa

こんなのでどうですかね。chars してから each_slice しているので、効率はあまりよくないかも知れない。一応ワンライナーではある。


文字列を一回しか走査しないバージョン(メソッド one_jump1)を書いてベンチマークを取ってみる。

require "benchmark"

def one_jump(str)
  str.chars.each_slice(2).map {|a, *b| a}.join
end

def one_jump1(str)
  flag = true
  result = ""
  str.each_char do |c|
    result << c if flag
    flag = !flag
  end
  result
end


str = [*""..""].join

Benchmark.bm do |x|
 x.report {one_jump(str) }
 x.report {one_jump1(str)}
end

結果。

       user     system      total        real
   0.008000   0.004000   0.012000 (  0.008788)
   0.004000   0.000000   0.004000 (  0.005388)

正確に半分の時間になっているな。しかしどちらも計算量は O(n) だろうから、大してちがわない。one_jump の方が短い Ruby コードのせいか、文字数を大きくすると差が縮まってくることが観測された。

GTK+ で落書き 18(Ruby)

どうでもよいのですが、遊んでみました。
20190729010046
種がそれっぽく描けない…。

oekaki_sample24.rb

require 'oekaki'
include Math

rs = 64 * (270 - 45)
rd = 64 * 90

Oekaki.app do
  draw do
    clear
    
    red   = color(0xffff, 0, 0)
    green = color(0, 0xffff, 0)
    black = color(0, 0, 0)
    
    arc2 = ->(fill, x, y, r, d1, d2, color = nil) {
      arc(fill, x - r, y - r, 2 * r, 2 * r, d1, d2, color)
    }
    
    arc2.(true, 150, 50, 180, rs, rd, green)
    arc2.(true, 150, 50, 150, rs, rd, red)
    
    n = 8
    n.times do |i|
      red_r = 80 + rand(3)
      rad = PI * (225 + 10 + (70.0 / n) * i) / 180
      
      circle(true, 150 + red_r * cos(rad), 50 - red_r * sin(rad), 3, black)
      red_r += 15
      circle(true, 150 + red_r * cos(rad), 50 - red_r * sin(rad), 3, black)
    end
  end
end

二重の扇形を描くのに組み込みのメソッド arc() では描きにくいので、arc2.() という関数を作りました。中心の座標と半径を与えれば描けます。

描画には自作の Gem 'oekaki' を使っています。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura

Ruby の dup と clone

Ruby でプロトタイプベースの OOP っぽいのをやるとき、ちょっと疑問に思ったので。

dup でオブジェクトをコピーすると、特異メソッドはコピーされません。なのでこれではプロトタイプベースの OOP には向きません。

$ pry
[1] pry(main)> a = Object.new
=> #<Object:0x000055900f87b7f8>
[2] pry(main)> class << a
[2] pry(main)*   def hello
[2] pry(main)*     puts "Hello!"
[2] pry(main)*   end  
[2] pry(main)* end  
=> :hello
[3] pry(main)> a.hello
Hello!
=> nil
[4] pry(main)> b = a.dup
=> #<Object:0x000055900f987ef8>
[5] pry(main)> b.hello
NoMethodError: undefined method `hello' for #<Object:0x000055900f987ef8>
from (pry):9:in `__pry__'

 
では、clone でコピーすると?

[6] pry(main)> c = a.clone
=> #<Object:0x000055900f9db3f0>
[7] pry(main)> c.hello
Hello!
=> nil

なるほど、こちらは可能なのですね。

※参考
https://docs.ruby-lang.org/ja/latest/class/Object.html#I_CLONE