クロージャを使ったカウンター

新人プログラマに知ってもらいたいメソッドを読みやすく維持するいくつかの原則 - Qiita
を見ていたら、クロージャを使っておもしろい仕方でカウンターが作れることを知った。

元のコードは JavaScript のようであるが、Ruby だとこんな感じ。

$ pry
[1] pry(main)> counter = ->{
[1] pry(main)*   count = 0
[1] pry(main)*   ->{count += 1}
[1] pry(main)* }.call  
=> #<Proc:0x005594b3626450@(pry):3 (lambda)>
[2] pry(main)> counter.call
=> 1
[3] pry(main)> counter.call
=> 2
[4] pry(main)> counter.call
=> 3

おもしろいですよね。呼び出すごとに確かに 1 ずつ増えていくな。
ちなみに、変数 counter に入っているのは Proc オブジェクト ->{count += 1} です。


こんな記事もあります。ここでもクロージャを使ってカウントしていますね。
obelisk.hatenablog.com

「モンティ・ホール問題」をシミュレートする(Ruby)

rubyでモンティホール問題に挑戦 - Qiita
ここでいわゆる「モンティ・ホール問題」を Ruby でシミュレートしているのを目にして、自分なりにやってみたくなりました。

「モンティ・ホール問題」とはこういうものです。

三つの扉があり、その向こうのひとつにだけ当たりがあります。
 
挑戦者はまず三つの扉の中からひとつを選びます。
司会者(モンティ・ホール)は当たりの扉を知っています。そして二つあるハズレの扉のひとつを開けます。
挑戦者はそこで前に選んだ扉をもういちどそのまま選択してもよいし、閉まっている残りの二つのどちらかを選びなおしてもよいです。
 
さて、挑戦者はここで扉を替えた方がよいのか、そのままにした方がよいのか、それともいずれでも当たる確率は一緒なのか。
どうなのでしょうか?

 
先に答えを言っておくと、これは「扉を替えた方」が当たる確率は高くなるのです。扉を替えない場合、当たる確率は 1/3 で、扉を替えると 2/3 になります。
不思議ですか? ただ扉を開けただけなのに?


コードと結果。
monty_hall_problem.rb

N = 100_0000

#一回の試行
def trial(is_reselection)
  doors = [true, false, false]
  select_of_challenger = rand(3)
  select_of_monty = (select_of_challenger == 3) ? 2 : 3
  
  if is_reselection
    doors.delete_at(select_of_monty)
    reselect_of_challenger = select_of_challenger.zero? ? 1 : 0
    doors[reselect_of_challenger]
  else
    doors[select_of_challenger]
  end
end

#N回試行して確率の計算
def calc(is_reselection)
  co = 0
  N.times { co += 1 if trial(is_reselection) }
  co.to_f / N
end

#扉を替えない場合
puts calc(false)    #=>0.333056
#扉を替える場合
puts calc(true)     #=>0.666614

確かにそうなります。

説明が必要でしょう。まず、扉 0, 1, 2 があって、当たりは必ず扉 0 にあるとします。挑戦者は何も知らないので、この仮定で問題ありません。これが doors = [true, false, false] で表されます。
あと、モンティは最初に挑戦者の選んだ扉は開けられません。


あとはさほど問題はないと思います。

格子点の列挙(Ruby)

格子点の列挙 - みずぴー日記
ここの問題で遊んでみました。

問題を再掲しておきます。

二次元平面上の格子点(X,Y座標がともに整数の点)を、原点から近い順に列挙してください。

同じ距離の点はどういう順番でも構いませんが、可能であればX軸に一番近い第一象限の点から原点を中心として反時計回りの順に列挙してください。 列挙の方法は、1行に一つの点の、X,Y座標を出力することとします。

http://d.hatena.ne.jp/mzp/20071006/lattice

 
結果の例(括弧をつけた出力にしました)。

( 3, -2)
(-6,  1)
(-5,  4)
( 4, -5)
( 5, -7)
(-9, -2)
(-2, -9)
( 7,  7)
(-8,  8)
( 8, -9)

 
コード。

class Complex
  include Comparable
  
  def <=>(a)
    f = ->(θ) {(θ < 0) ? 2 * Math::PI + θ : θ}
    
    l1, l2 = abs2, a.abs2
    θ1, θ2 = f.(angle), f.(a.angle)
    
    if    l1 > l2 then  1
    elsif l1 < l2 then -1
    elsif θ1 > θ2 then  1
    elsif θ1 < θ2 then -1
    else 0
    end
  end
  
  def to_s
    sprintf "(% 2d, % 2d)", real, imaginary
  end
end

ar = 10.times.with_object([]) {|_, x| x << Complex(rand(-9..9), rand(-9..9))}
puts ar.sort

いわゆる「宇宙船演算子」(<=>というやつ)を Complex クラスに定義して解いてみました。並べ替えはふつうに Array#sort でやっています。

ちょっとトリッキーですかね。

モジュール内の特定のメソッドだけ include する(Ruby)

Ruby のモジュールは include でメソッドを Mix-in できますが、定義したすべてのメソッドが include されてしまって、特定のものだけ include するわけにはいきません。そのくらいできそうなので何か簡単なやり方があるのかもしれません。とにかく、考えてみました。

まず、こんなメソッドを Object クラスと Module クラスに付け加えます。
add_methods.rb

class Object
  def add_methods(module_to_include, *method_names)
    method_names.each do |m|
      pr = module_to_include.method(m).to_proc
      define_method(m, &pr)
    end
  end
end

class Module
  def register_all_methods
    instance_methods.each do |m|
      module_function m
    end
  end
end

 
で、メソッドを定義するモジュールを書きます。そのとき、(必要な)すべてのメソッドを module_function で指定するか、上の register_all_methods をモジュールの最後に書きます。(すべて module_function で指定する場合は、register_all_methods メソッドを書く必要はありません。いちいち指定するのが面倒なので作りました。)

module A
  def run_f
    f
  end
  
  def run_g
    g
  end
  
  def f
    puts "f called"
  end
  
  def g
    puts "g called"
  end
  register_all_methods
end

 
Mix-in するモジュールとメソッドを add_methods() で指定します。すると指定されたメソッドだけ使えるようになります。

add_methods A, :run_f, :run_g

run_f    #=>f called
run_g    #=>g called
f        #=>`<main>': undefined local variable or method `f' for main:Object (NameError)

確かに指定されたメソッドだけ使えるようになっています。

クラスの中だとこんな風になります。

class B
  add_methods A, :run_f, :run_g
  def go
    run_f
    run_g
    f
  end
end

B.new.go

 
こんなことも可能。

c1 = Object.new
c2 = Object.new

class << c1
  add_methods A, :run_f
end

c1.run_f    #=>f called
c2.run_f    #=>`<main>': undefined method `run_f' for #<Object:***> (NoMethodError)

 
なお、これは上の例で例えばメソッド f を絶対に外から呼び出せないようにしたわけではなく、A.f などとすれば呼び出せてしまいます(まあしかし、モジュールが名前空間として使いやすくなったりとか、利点もあるのではないかと思います)。けれども、少なくとも間違えて f を呼び出す可能性はだいぶ低くなると思います。

そうそう、注意点ですが、これらと include を併用すると予期しづらい誤動作の原因になる可能性があります。まあ、そんなことをする意味はありませんが。
 

Gem化

Gem 'kaki-utils' に同梱しておきました(ver. 0.0.10)。使い方は

require 'kaki/utils/add_methods'

で Object#add_methods と Module#register_all_methods が使えます。

RubyPico でライフゲーム

RubyPico には clearprint という独自のメソッドがあるので、文字でアニメーションのようなことができます。なので、いわゆる「コンウェイのライフゲーム」を実装してみました。またライフゲームかって言わない。

こんな感じです。iPad mini の画面です。

20180412234202

 
コードはこちら。
RubyPico 用ライフゲーム · GitHub
 
これらをすべて RubyPico でコーディングするのはムリなので、CRuby で clearprint を実装して開発しました。なので、CRuby のコードがそのまま RubyPico で走りました。

ちょっとコードについて説明しておくと、これは iPad mini 用の大きさになっているので、他のデバイスの方は LifeGame::W(フィールドの幅)と LifeGame::H(フィールドの高さ)の定数の値を適当に変更して下さい。また、末尾の LifeGame.new(n).run(wait) の n は最初にばら撒くセルの個数、wait はウェイトの時間(小さい値にすると世代の更新が速くなる)です。


いやー RubyPico、楽しいですね!

RubyPico でカレンダーを表示させてみた

obelisk.hatenablog.com
ここでの Ruby スクリプトを移植してみました。


入力はこんな感じ。

20180412124247

出力。

20180412124244

 
コードはこちらです。もともと(遊びで)Date クラスを使わずにコーディングしていたので、Date クラスのない mruby でも動いてちょうどよかったな。
obelisk68/calender.rb - Gist
 

気がついたこと 気になったこと

1。String#center が mruby にはないようなので、適当に実装しました。ここは Rubyオープンクラスの柔軟なところ。

2。整数 / 整数が mruby では整数にならないこと。これは CRuby のコードを移植する際はもっとも危険そうですね。mruby への移植を最初から考えるなら、div() を使う方がいいのかも知れません。

3。これはよくわからないのだが、sprintf("%2d", i) で i が1桁の整数であるばあい、後ろに余分な空白ができます。こんな感じ。

20180412125723

これは仕様? バグ? それとも僕のかん違いかな。


どこにいても Ruby でコーディングできる RubyPico、とってもいいですね。iPhone/iPad をお持ちの Rubyist には是非おすすめのアプリです。よくできていますよ。

次は RubyPico でライフゲームを作ってみたいですね!
作ってみました!

いわゆる「コンウェイのライフゲーム」をエディタ付きで実装してみた(Ruby)

20180415235457

これまで

などで「ライフゲーム」を実装してきましたが、フィールド・エディタが欲しくなったので作ってみました。Gtk2 を使っています。


Gem化してあるので、インストールして実行できます。Bundler かまたは $ gem install kaki-lifegame でインストールできます。実行はたんに

$ kaki-lifegame

か、Bundler を使っている人は

$ bundle exec kaki-lifegame

GUI が立ち上がります。Gem 'gtk2' が必要です。Linux Mint 18.3, Ruby 2.3.3 で確認しました。Windows 8.1, Ruby 2.2.2p95 (2015-04-13 revision 50295) [i386-mingw32] でも確認しましたが、格子の表示がおかしくなることがあるようです*1

使い方。

  • マウスのクリックでセルを ON/OFF できます。
  • マウスの左ボタンを押しながらドラッグすると連続的にセルを描くことができます。また、右ボタンを押しながらドラッグすると、連続的にセルを消すことができます。


  • 開始
    • 実行します。
  • 停止
    • 停止します。以下の機能はすべて停止中にのみ有効です。
  • 1ステップ進める
    • ステップ実行です。
  • ばらまく
    • 適当にセルを散布します。
  • 格子
    • 格子の表示を切り替えます。
  • 緑色/橙色
    • セルの色です。
  • 全クリア
    • すべてのセルを消します。
  • 一時保存
    • 一時的にフィールドを内部に保存します。
  • 復帰
    • 内部に保存したデータをフィールドに戻します。
  • ファイルに保存
    • フィールドをファイルに保存します。
  • ファイルの読み込み
    • ファイルに保存したフィールドのデータを読み込んで表示します。
  • 終了
    • 終了します。

 
実行動画を作ってみました。

 
ソースコードは以下です。
エディタ付きライフゲーム(Ruby) · GitHub
これだけでも実行できます。Gem のコードとはほんの一部だけちがいますが、本質的には同じです。

コーディングにはほぼ2日間ほどかかりました。Ruby/Gtk について調べるのに手間を食いました。Ruby ってのは書きやすくてホントすばらしい言語ですね。Rubyオブジェクト指向プログラミングの書きやすさは快感です。


※参考
ライフゲーム - 人工知能に関する断創録
 

追記(4/11)

機能をさらに追加しました。

  • 1ステップ戻る
    • ひとつ前のフィールドに戻ります。戻れるのは過去20ステップまでです。
  • ウェイト
    • ウェイトする時間を変更します。値が小さいほど更新が速くなります。なお、これは停止中でなくとも有効なボタンです。

Gem もアップデートしてバージョン0.0.3 になりました。Gem の更新は $ gem update kaki-lifegame、また Bundler を使っている人は $ bundle update kaki-lifegame です。
 

追記(4/15)

バージョン0.0.4 でデフォルトの画面を小さくし、画面の大小ボタンを付けました。

*1:バージョン0.0.2 で解消されました。Windows でもきちんと動作します。