Ruby のローカル変数登録について

以下の Ruby コードを見てほしい。

if false
  a = 1
end
p a    #=>nil

if true
  nil
else
  b = 1
end
p b    #=>nil

c = 1 if false
p c    #=>nil

while false
  d = 1
end
p d    #=>nil

いずれも nil を出力し、「undefined local variable or method」の NameError は出ない。

覚えておくと役に立つかも知れない。

応用編。

$ pry
[1] pry(main)> i = 1 if i.nil?
=> 1
[2] pry(main)> i
=> 1
[3] pry(main)> j = 1 if j == 1
=> nil
[4] pry(main)> j
=> nil

これはたぶん何の役にも立たない。

二次元配列の行(あるいは列)を入れ替える(Ruby)

Julia では簡単とあったので(よく知らない)、Ruby で考えてみた。

まず二次元配列を作る。

$ pry
[1] pry(main)> ary = (1..100).each_slice(10).to_a
=> [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
 [11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
 [21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
 [31, 32, 33, 34, 35, 36, 37, 38, 39, 40],
 [41, 42, 43, 44, 45, 46, 47, 48, 49, 50],
 [51, 52, 53, 54, 55, 56, 57, 58, 59, 60],
 [61, 62, 63, 64, 65, 66, 67, 68, 69, 70],
 [71, 72, 73, 74, 75, 76, 77, 78, 79, 80],
 [81, 82, 83, 84, 85, 86, 87, 88, 89, 90],
 [91, 92, 93, 94, 95, 96, 97, 98, 99, 100]]

 
行を入れ替えるのは簡単。3行目と5行目を入れ替えてみる。

[2] pry(main)> ary[2], ary[4] = ary[4], ary[2]
[3] pry(main)> ary
=> [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
 [11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
 [41, 42, 43, 44, 45, 46, 47, 48, 49, 50],
 [31, 32, 33, 34, 35, 36, 37, 38, 39, 40],
 [21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
 [51, 52, 53, 54, 55, 56, 57, 58, 59, 60],
 [61, 62, 63, 64, 65, 66, 67, 68, 69, 70],
 [71, 72, 73, 74, 75, 76, 77, 78, 79, 80],
 [81, 82, 83, 84, 85, 86, 87, 88, 89, 90],
 [91, 92, 93, 94, 95, 96, 97, 98, 99, 100]]

 
列を交換するのは少し工夫が要る。さらに3列目と5列目を入れ替えてみる。

[4] pry(main)> ary.each {|a| a[2], a[4] = a[4], a[2]}
[5] pry(main)> ary
=> [[1, 2, 5, 4, 3, 6, 7, 8, 9, 10],
 [11, 12, 15, 14, 13, 16, 17, 18, 19, 20],
 [41, 42, 45, 44, 43, 46, 47, 48, 49, 50],
 [31, 32, 35, 34, 33, 36, 37, 38, 39, 40],
 [21, 22, 25, 24, 23, 26, 27, 28, 29, 30],
 [51, 52, 55, 54, 53, 56, 57, 58, 59, 60],
 [61, 62, 65, 64, 63, 66, 67, 68, 69, 70],
 [71, 72, 75, 74, 73, 76, 77, 78, 79, 80],
 [81, 82, 85, 84, 83, 86, 87, 88, 89, 90],
 [91, 92, 95, 94, 93, 96, 97, 98, 99, 100]]

こんな感じでどうですかね。

ふう、Ruby でも一応できるかな。
 

非破壊的変更で

上の例では元の配列は破壊されてしまう。非破壊的にやるのは、もう少し手間である。それぞれ次のようにやればできる。

行の入れ替え。

exchanged = ary.dup
exchanged[2], exchanged[4] = exchanged[4], exchanged[2]

列の入れ替え。

exchanged = ary.map(&:dup)
exchanged.each {|a| a[2], a[4] = a[4], a[2]}

 
以上で、元の配列 ary は変更されない。 なお、dup を使わず Marshal.load(Marshal.dump(ary)) のように deep copy してもよいが、ここではそこまですることもない。

Ruby と rcairo でベジェ曲線を描いてみる

ベジェ曲線Wikipedia)は滑らかな曲線を描くために使われるものです。いくつかの「制御点」を指定して描きます。計算はそんなにむずかしくなくて、上の Wikipedia の記事で充分わかりますし、ネット上にわかりやすい記事がたくさんあるので検索してみて下さい。

Ruby と 'rcairo' で描いてみた例です。
20190320195050
青い線が制御点を結んだ折れ線で、赤い曲線がベジェ曲線です。

Ruby コード。
bezier_curve.rb

require 'cairo'
require 'matrix'

class BezierCurve
  def initialize(points, step = 0.01)
    @points = points.map {|a| a.class == Array ? Vector[*a] : a}
    @step = step
  end
  
  def calc
    n = @points.size - 1
    Enumerator.new do |y|
      c = [1] + (1..n).map {|k| (n - k + 1..n).inject(&:*) / (1..k).inject(&:*)}
      0.0.step(1.0, @step) do |t|
        j = ->(i) { c[i] * t ** i * (1 - t) ** (n - i) }
        y << @points.map.with_index {|b, i| b * j.(i)}.inject(&:+)
      end
      y << @points.last
    end
  end
end


if __FILE__ == $0
  #画像の大きさ
  W = 300
  
  #cairoの初期設定
  surface = Cairo::ImageSurface.new(W, W)
  context = Cairo::Context.new(surface)
  
  #背景
  context.set_source_color(Cairo::Color.parse("#F1F389"))
  context.rectangle(0, 0, W, W)
  context.fill
  
  #制御点を与える
  points = [[20.0, 280.0], [60.0, 100.0], [200.0, 120.0], [290.0, 230.0]]
  
  #制御点を結ぶ青い線
  context.set_source_color(Cairo::Color::BLUE)
  context.set_line_width(2)
  context.move_to(*points.first)
  points.drop(1).each {|r| context.line_to(*r)}
  context.stroke
  
  #ベジェ曲線(赤色)の描画
  context.set_source_color(Cairo::Color::RED)
  context.move_to(*points.first)
  
  BezierCurve.new(points).calc.each do |r|
    context.line_to(*r.to_a)
  end
  context.stroke
  
  #png画像として出力
  surface.write_to_png("bezier_curve.png")
end

使い方としては、配列 points に制御点を入れて(制御点は配列あるいは Vector クラスで表現します)、BezierCurve.new(points, step).calc で折れ線(step を細かくすればベジェ曲線に見えるわけです)の頂点(Vector クラスで表現されています)を順に与える Enumerator を返します。step は省略されれば 0.01 がデフォルトになります。曲線は t = 0 が開始で t = 1 が終了なので、step は一回に進む t の値を指定します。step = 0.01 ならば曲線が 100分割されるということです。


なお、なめらかな曲線としては「スプライン曲線」というのもあります。下の記事で扱っています。
obelisk.hatenablog.com

Gem 'Ruby2D' でライフゲーム

20190320020235
いつもの得意技(?)のライフゲームです。Ruby 用のグラフィック・ライブラリ 'Ruby2D' を使っています。

コード。
lifegame_for_Ruby2D.rb

require 'ruby2d'
include Ruby2D::DSL

class LifeGame
  CellWidth = 10
  Margin = 20
  Space = 2
  SideWidth = 35
  W = CellWidth * SideWidth + Space * (SideWidth - 1) + Margin * 2
  
  def initialize(num)
    set width: W, height: W, title: "LifeGame", fps_cap: 3
    
    cells = SideWidth.times.map do |y|
      SideWidth.times.map do |x|
        Square.new x: Margin + (CellWidth + Space) * x,
                   y: Margin + (CellWidth + Space) * y,
                   size: CellWidth,
                   color: Color.new([rand, rand, rand, 1.0]),
                   z: 0
      end
    end
    each_cell {|x, y| cells[y][x].remove}
    
    f = [1] * num + [0] * (SideWidth ** 2 - num)
    f.shuffle!
    @field = f.each_slice(SideWidth).to_a
    
    clear
    
    update do
      each_cell do |x, y|
        @field[y][x].nonzero? ? cells[y][x].add : cells[y][x].remove
      end
      next_field
    end
  end
  
  def each_cell
    SideWidth.times {|y| SideWidth.times {|x| yield(x, y)} }
  end
  
  def next_field
    tmp = Array.new(SideWidth) {Array.new(SideWidth, 0)}
    each_cell do |x, y|
      num = neighbor(x, y)
      if @field[y][x].nonzero?
        tmp[y][x] = 1 if num == 2 or num == 3
      else
        tmp[y][x] = 1 if num == 3
      end
    end
    @field = tmp
  end
  
  def neighbor(x, y)
    dirs = [[1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1]]
    dirs.map do |dx, dy|
      x1 = x + dx
      y1 = y + dy
      next if x1 < 0 or x1 >= SideWidth
      next if y1 < 0 or y1 >= SideWidth
      @field[y1][x1]
    end.compact.inject(&:+)
  end
  
  def go
    show
  end
end

LifeGame.new(250).go

ちょっとチュートリアルだけではわからないところがあったので、ソースコードを見る必要がありました。まずクラスの中で Ruby2D のメソッドをどう使ったらよいかで、これは最初に include Ruby2D::DSL を宣言しておくことで解決(他の方法もあります)。それから、メソッド update のブロックの中で状態がその都度更新されるのですが、そのフレーム数の指定はどうすればよいか。これはメソッド set のキーワード引数で :fps_cap で指定してやればできることがわかりました。なお、ソースコードはとても読みやすいものでした。
 
'Ruby2D' については以下で紹介しています。
obelisk.hatenablog.com
 

上とは関係のないどうでもいいこと

Ruby は型の指定がないのでコードが読みにくいという根強い意見がありますが、たぶんこれではないかと。

  • そもそもそのコード自体が読みにくい、クソなものである → Ruby のコードは一般にシンプルで短めなので、さっと読むのは楽な方です
  • 全体のコードが長すぎて読みにくい → ソースファイルを分割する必要がある
  • Ruby にはジェネリクスがない → Ruby の書き方を知らないにもほどがある

なお、これらはじつは他の言語にも応用できることです。別に Ruby は完璧な言語ではありませんが、実用的な(といってもまあいろいろでしょうけれど)言語としては非常によくできたものです。オブジェクト指向言語としてはもっともよくできたもののひとつでしょうし、クロージャ、第一級関数もサポートしており、関数型プログラミングのエッセンスも詰まっています。初心者にもやさしいですし、高度なプログラミングのできるプログラマにはそれなりの優れた書き方ができます。あんまり知らないでバカにするのはやめましょう。

僕は Ruby 以外では Go が結構好きなので(ソースコードの見た目が好きです笑)、Ruby と Go が連携できるような仕組みを作って欲しいのですけれど…。C言語がうまく書けるようになるのはなかなか大変…。

それから、「デザインパターンはオワコン」という意見も見ましたが、オワコンであろうが知っておいた方がよいと思います。純粋にプログラミング技術としておもしろいものです。最近はプロのプログラマの絶対数が増えたので、プロであり自信満々でもじつはよくわかっていない人も多いですから、我々素人のプログラミング好きは気をつけたいものです。


しかし、PythonPerl で型の指定がないので読みにくいという意見は、ほとんど目にしたことがないのだが…。

Gem 'Ruby 2D' で遊ぶ(1)

20190308180919
 
Ruby でグラフィック表示のできる Gem 'Ruby 2D' で遊んでみました。アニメーションをしています。三角が丸たちの上にあります。色は半透明になっています。
コード。
ruby2d_sample2.rb

require 'ruby2d'

Width = 500
C = 15    #円の数
R = 20    #円の半径

L = 150   #三角形の一辺の長さ

set width: Width, height: Width

circles = C.times.map {
  Circle.new x: rand(R..Width - R), y: rand(R..Width - R),
     radius: R, color: Color.new([rand, rand, rand, 0.8]), z: 0
}
cvs = circles.map {[rand(-3.0..3.0), rand(-3.0..3.0)]}    #円の移動ベクトル

triangle = 4.times.map {|i|
  cn = Width / 2.0
  xs = [cn - L / 2.0, cn + L / 2.0]
  height = L * 0.866
  
  case i
  when 0, 1
    j = i * 2 - 1
    x1, y1 = xs[0], cn - height / 2 * j
    x2, y2 = xs[1], cn - height / 2 * j
    x3, y3 = cn, cn + height / 2 * j
  else
    j = i * 2 - 5
    x1, y1 = cn - height / 2 * j, xs[0]
    x2, y2 = cn - height / 2 * j, xs[1]
    x3, y3 = cn + height / 2 * j, cn
  end
  
  Triangle.new x1: x1, y1: y1, x2: x2, y2: y2, x3: x3, y3: y3,
     color: "#FAB536", z: 10, opacity: 0.8
}
triangle.each(&:remove)

h = {0=>0, 1=>2, 2=>1, 3=>3}
t = 0

update do
  #円の移動
  circles.zip(cvs).each do |c, vec|
    c.x += vec[0]
    c.y += vec[1]
    c.x = Width + R if c.x < -R
    c.y = Width + R if c.y < -R
    c.x = -R if c.x > Width + R 
    c.y = -R if c.y > Width + R 
  end
  
  #三角形の回転
  k = t / 100 % 4
  triangle[h[k]].add
  triangle[h[(k - 1) % 4]].remove
  
  t += 1
end

show

 
'Ruby 2D' についてはこちらで紹介しています。
obelisk.hatenablog.com

Gem 'Ruby 2D' でお絵かきしてみる

これまで Ruby でのグラフィック描画には自作の Gem 'oekaki' を使ってきましたが、高機能で簡単に使える Gem 'Ruby 2D' がリリースされたのでちょっと使ってみました。
www.ruby2d.com

インストール

Window, Mac, Linux で使えるようですが、WindowsMinGW 環境が必要なようです。自分は Linux Mint 19.1, Ruby 2.6.0 で確認しました。
とりあえず Linux 環境でのインストール方法です。上のサイトの手順どおりです。

まず SDL を使っているようなので、パッケージをインストールします。

$ sudo apt install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev

 
「simple2d」をインストールします。ここから「Source code」をダウンロード、解凍してフォルダ内に入り、

$ make && sudo make install

でインストールされます。(追記:現在はこれは不要なようです。4/27記)
 
Gem をインストールします。'gem install ruby2d' あるいは Bundler でインストール可能です。確認は例えば

$ pry
[1] pry(main)> require 'ruby2d'
=> true

となれば OK です。

(追記:現在 Windows では Gem のインストールだけで使えます。ただし MinGW-64bit 環境のみです。4/27記)
 

ヒルベルト曲線を描いてみる

Ruby 2D の使い方はシンプルで、上のサイトを見れば簡単にわかります。ヒルベルト曲線を描画してみます。
20190302102935
 
コード。
ruby2d_sample1.rb

require 'ruby2d'
require 'matrix'

Width = 600
N = 5

class Pen
  def initialize
    @po = Vector[35, Width - 35]
    @dir = Vector[1, 0]
  end
  
  def go
    step = (Width - 50) / (2 ** N - 1)
    next_po = @po + @dir * step
    c = Color.new("red")    #色
    Line.new(x1: @po[0], y1: @po[1], x2: next_po[0], y2: next_po[1],    #線を描く
       width: 1, color: c)
    @po = next_po
  end
  
  def left(a)
    @dir = Matrix[[0, -1], [1, 0]] * @dir * a
  end
  
  def right(a)
    @dir = Matrix[[0, 1], [-1, 0]] * @dir * a
  end
end

set title: "Hilbert curve", width: Width, height: Width    #ウィンドウの大きさなどの指定
set background: "#F8F8FF"    #背景色
pen = Pen.new

draw = ->(depth, a) {
  return if depth.zero?
  pen.right(a)
  draw.(depth - 1, -a)
  pen.go
  pen.left(a)
  draw.(depth - 1,  a)
  pen.go
  draw.(depth - 1,  a)
  pen.left(a)
  pen.go
  draw.(depth - 1, -a)
  pen.right(a)
}

draw.(N, 1)
show    #描画

 
Ruby 2D はまだまだ機能がたくさんあります。もっと使ってみたいと思います。

線分への垂線の足を求める(Ruby)

点 P から線分 AB への垂線の足 H を求めます。

Ruby の標準添付ライブラリ 'Matrix' を使います。コード。

require 'matrix'

def perpendicular_foot(a, b, p)
  s = Rational((p - a).dot(b - a), (b - a).dot(b - a))
  [h = a + (b - a) * s, s, (h - p).norm]
end

点 A, B, P は Vector[] で指定します。こんな感じ。

$ pry
[7] pry(main)> a = Vector[-2, 0]
=> Vector[-2, 0]
[8] pry(main)> b = Vector[1, 3]
=> Vector[1, 3]
[9] pry(main)> p = Vector[0, 0]
=> Vector[0, 0]
[10] pry(main)> perpendicular_foot(a, b, p)
=> [Vector[(-1/1), (1/1)], (1/3), 1.4142135623730951]

配列が返ります。順に、点 H を表す Vector、H の AB上での位置 s (H = A のとき 0、H = B のとき 1)、PH の長さを表わします。s が 0 と 1 の間にない場合は、H は線分上にはありません(もちろん直線 AB 上にはあります)。

なお、これは何次元でも使えます。