モジュール内の特定のメソッドだけ 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 でもきちんと動作します。

パスカルの三角形(Haskell)

パスカルの三角形」というのはこういうやつですね。

              1               
             1 1              
            1 2 1             
           1 3 3 1            
          1 4 6 4 1           
        1 5 10 10 5 1         
       1 6 15 20 15 6 1       
     1 7 21 35 35 21 7 1      

この出力を目標に、Haskell で書いてみます。

以前に Ruby で書いてみました。そのコードを再掲しましょう。

pascal = ->(n) {
  each_cons = ->(ar) {
    (ar.size < 2) ? [] : [ar[0] + ar[1]] + each_cons[ar.drop(1)]
  }
  n.zero? ? [1] : [1] + each_cons[pascal[n - 1]] + [1]
}

n = 8
n.times {|i| puts pascal[i].join(" ").center(30)}

簡潔ですね。これを手本にしてみます。

こんな感じでしょうか。

main :: IO ()
main = putStr $ unlines $ map (centering 30 . toString) (map pascal [0..7])

centering :: Int -> String -> String
centering n str = (replicate i ' ') ++ str ++ (replicate j ' ')
    where l = length str
          i = div (n - l) 2
          j = n - l - i

toString :: [Int] -> String
toString xs = tail $ concat $ map f xs
    where f x = ' ': show x

pascal :: Int -> [Int]
pascal 0 = [1]
pascal n = [1] ++ eachCons (pascal (n - 1)) ++ [1]
       
eachCons :: [Int] -> [Int]
eachCons xs = if length xs < 2
              then []
              else (head xs + (head . tail) xs): (eachCons . tail) xs

いやー、これだけでも悩みましたよ。
関数 pascal と eachCons は Ruby の動作とまったく同じです。あとは関数 toString と centering で結果を成形された文字列にしています。

ちなみに、

  • unlines 関数:[String]を改行を入れて結合する。[String] -> String。
  • concat 関数:リストの平坦化。
  • div 関数:整数/整数をして整数で返す。

です。

初心者が気づいたこと。

  • Haskell で繰り返しをするのはまず map を使うとラク でなければ再帰
  • show 関数を使えばとりあえず String になる
  • Ruby でのメソッドチェーンみたいなことができる

 

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

和暦 (明治 大正 昭和 平成) と西暦の変換をおこなう(Ruby)

簡単な遊びのプログラムが作りたかったのでやってみました。Date クラスは使っていません。

こんな感じです。コマンドライン引数を指定します。

#西暦から和暦
$ ruby gengou.rb 2018
平成30年
$ ruby gengou.rb 1945
昭和20年
$ ruby gengou.rb 1912
明治45年
大正元年

#和暦から西暦
$ ruby gengou.rb m1
西暦1868年
$ ruby gengou.rb h30
西暦2018

明治はm、大正はt、昭和はs、平成はh で指定します。

コード。
gengou.rb

years_table = [[1868, 1912], [1912, 1926], [1926, 1989], [1989, 2019]]
period_names = %W(明治 大正 昭和 平成)
period_initials = %W(m t s h)

#西暦から和暦へ
year_to_period = ->(year) {
  year1 = 0
  years_table.each_with_index do |y, i|
    if y[0] <= year and year <= y[1]
      year1 = year - y[0] + 1
      year1 = "" if year1 == 1
      puts period_names[i] + "#{year1}"
    end
  end
  puts "その西暦はサポートしていません" if year1 == 0
}

#和暦から西暦へ
period_to_year = ->(period, year) {
  period.downcase!
  period_initials.each_with_index do |p_ini, i|
    if period == p_ini
      year1 = years_table[i][0] + year - 1
      st = if year1 > years_table[i][1]
        "#{period_names[i]}#{year}年はありません"
      else
        "西暦#{year1}"
      end
      puts st
    end
  end
}

if (m = /^(\d+)$/.match(ARGV[0]))
  year_to_period.(m[1].to_i)
elsif (m = /^([a-zA-Z])(\d+)$/.match(ARGV[0]))
  period_to_year.(m[1], m[2].to_i)
else
  puts "許されない入力です"
end

データを増やせばすべての元号に対応させることも可能です。ただ、頭文字の重複があるでしょうから、漢字で指定するようにしないといけませんね。

遊びですけれど、こういうのを書くには Ruby 最強じゃないですか? 書いていてチョー気持ちいいです。
かんたんな遊びのプログラミングいいですね。もっと題材を探したいです。


こんなのもどうぞ。
obelisk.hatenablog.com

Ruby でイケてないコードを→「いいね」→「スゲーいいね」にリファクタリング?

qiita.com
自分が「Ruby + リファクタリング」でぐぐると上のサイトがいちばん最初にヒットする。内容は題名どおり。添削者はすごい上級者プログラマらしい。添削された人には気の毒なのだけれど、勝手に引用してみる。まず「イケてない」コード。(ごめんなさい)

class OrdersReport
  def initialize(orders, start_date, end_date)
    @orders = orders
    @start_date = start_date
    @end_date = end_date
  end

  def total_sales_within_date_range
    orders_within_range = []
    @orders.each do |order|
      if order.placed_at >= @start_date && order.placed_at <= @end_date
        orders_within_range << order
      end
    end

    sum = 0
    orders_within_range.each do |order|
      sum += order.amount
    end
    sum
  end
end

class Order < OpenStruct
end

うーん、確かに読みにくいかなあ。でもわかるけれど。
RSpec テストコード。

require 'spec_helper'

describe OrdersReport do
  describe '#total_sales_within_date_range' do
    it 'returns total sales in range' do
      order_within_range1 = Order.new(amount:  5, placed_at: Date.new(2016, 10, 10))
      order_within_range2 = Order.new(amount: 10, placed_at: Date.new(2016, 10, 15))
      order_out_of_range  = Order.new(amount:  6, placed_at: Date.new(2016,  1,  1))
      orders = [order_within_range1, order_within_range2, order_out_of_range]

      start_date = Date.new(2016, 10,  1)
      end_date   = Date.new(2016, 10, 31)

      expect(OrdersReport.new(orders, start_date, end_date)
         .total_sales_within_date_range).to eq(15)
    end
  end
end

 
リファクタリング後。「スゲーいいね」のコード。

class OrdersReport
  def initialize(orders, date_range)
    @orders = orders
    @date_range = date_range
  end

  def total_sales_within_date_range
    total_sales(orders_within_range)
  end

  private

  def total_sales(orders)
    orders.map(&:amount).inject(0, :+)
  end

  def orders_within_range
    @orders.select { |order| order.placed_between?(@date_range) }
  end
end

class DateRange < Struct.new(:start_date, :end_date)
  def include?(date)
    (start_date..end_date).cover? date
  end
end

class Order < OpenStruct
  def placed_between?(date_range)
    date_range.include?(placed_at)
  end
end

添削者の言葉。

もう最初のコードと比べると可読性、単一責任、再利用性、疎結合、と全ての要素が満たされて格段に良くなっているのが分かる。ほとんどのメソッド内の行数が1行。(2行なのはinitializeのみ)気持ちいいぐらいに読みやすい。

https://qiita.com/jabba/items/e169adb2f33532c119cf

 
へー、プロの上級者はこんな風にリファクタリングするのだな。でも、自分みたいな初心者素人には謎すぎる。凝りすぎてキモい感じ。「気持ちいいぐらいに読みやすい」ともあまり思えない。まったく自分はセンスがないのだな。「可読性、単一責任、再利用性、疎結合」。確かに、ですな。

初心者素人ならこんな風に書くかも。マネしないように。

class OrdersReport
  def initialize(orders, start_date, end_date)
    @orders = orders
    @start_date = start_date
    @end_date   = end_date
  end

  def total_sales_within_date_range
    @orders.select {|od| @start_date <= od.placed_at and od.placed_at <= @end_date}
       .map(&:amount).inject(:+)
  end
end

class Order < OpenStruct
end

実質一行。あと、レシーバーが @orders で中身がわかるのに、ブロック変数まで order にするのは長くて自分には見にくい感じ。od で充分に思える。あとは select したら足すだけじゃね?

もちろん DateRange クラスを作ってもいいのだけれど、それは DRY 的に必要になってからでいいと思う。元をシンプルにしておけば改変もラク(select を置き換えればよいのが明白)。「不必要に将来の拡張性を考えない。それが必要になることはたぶんない。」「コードが短くなったら great ! といおう。」

自分に「スゲーいいね」のコードが見にくく感じるのは、置き換えるべき select が一般的な Enumerable なので、(ここでモンキーパッチはダメなので)そのためにいろいろいじらなくてはならないからである。そのために新たにクラスをひとつ追加し、メソッドも大幅に増やしている。これほどのことをするくらいなら、select のままではいけないのだろうか? プロってのはむずかしいな。


プログラミングにおいてデータ構造は決定的なファクタのひとつだが、この「スゲーいいね」では DateRange クラスというデータ構造を思いついたためにこうなった。結局、この決定がよいかどうかということなのではないか。


※追記(4/2)


植山類さんのこれで充分(そしてそちらの方がたぶんずっと速い)。じつは本文記事は植山さんの「教え」(?)みたいなのが頭にあったのですが、既に本人がお書きになっていました(^^; 自分の場合は Ruby の便利メソッドを使ってメソッドチェーンにしただけで、考え方は植山さんのコードとまったく同じ。植山さんのなら Ruby 以外でもだいたい似たように書けると思う。あと、もしこれだけというのなら OrdersReport クラスがいらないというのもそのとおりですね。