Ruby メモ

ここでは、良く使う Ruby の決まり文句を集める予定です。大半の式は irb で直接動作を確かめることができます。# => の右辺ば左辺の式を評価したときの結果を示します。irb に入力するのは # => の左辺のみです。

代入の落し穴

Ruby を使っていて最初に引っかかるのは代入の仕様です。Ruby の代入はオブジェクトへのリファレンスを作るだけなので、もとの変数の内容を変えると代入した先の変数の値も変化してしまいます。

irb(main):001:0> a = [1,2]
[1, 2]
irb(main):002:0> b = a
[1, 2]
irb(main):003:0> a[0] = "changed"
"changed"
irb(main):004:0> b
["changed", 2]

値だけを代入するにはメソッド dup でオブジェクトのコピーをとってから代入します。

irb(main):005:0> a = [1,2]
[1, 2]
irb(main):006:0> b = a.dup
[1, 2]
irb(main):007:0> a[0] = "changed"
"changed"
irb(main):008:0> b
[1, 2]

行末の改行を取り除く

name = gets.chop; puts "hello, #{name}. nice to meet you."
name = gets.chomp; puts "hello, #{name}. nice to meet you."

配列と文字列の相互変換

フラットファイルのデータベースを使うときに必要です。

['apple', 'orange', 'banana'].join(':') # => "apple:orange:banana"
"apple:orange:banana".split(/:/) # => ["apple", "orange", "banana"]
"hello       |       world".split(/\s+\|\s+/) # => ["hello", "world"]
"The time is 3:45".scan(/\d+/) # => ["3", "45"]

文字列の検索と置換

検索

puts "mathch" if "hello, world" =~ /l*/
['dog', 'cat', 'rat'].find_all( /at/ ) # => ["cat", "rat"]

def findre( re, str )
  if str =~ re
    "#$`[#$&]#$'"
  else
    "not found"
  end
end
findre( /h.*o/, "hello, world" )

置換

"hello, world".gsub(/wo.*/, 'there') # => "hello, there"
['dog', 'cat', 'rat'].collect {|s| s.gsub(/.at/, 'bat') # => ["dog", "bat", "bat"] 

配列の要素全てに操作を加える

[1, 2, 3, 4, 5].collect {|x| x * x } # => [1, 4, 9, 16, 25]
(1..5).collect {|x| x + 10} # => [11, 12, 13, 14, 15]
["H", "A", "L"].collect {|c| c.succ}

フィルター

自分用のプログラムはフィルターで作るのが便利です。フィルターは gets と puts を使うだけで簡単に作れます。フィルターを引数無しで起動すると、コマンドラインからデータを入力できます。Ruby の場合フィルターの引数には < を省略してファイル名をとることができます。この場合引数のファイルのデータが利用されます。フィルターの出力をファイルにするには > のリダイレクトを使います。

ファイル名:mean.rb

#!/usr/local/bin/ruby
n, totoal, ss = 0, 0, 0
while ( data = gets )
  x = data.to_f
  n += 1
  total += x
  ss += x * x
end
mean = total / n
sd = Math.sqrt( ss / n - mean * mean )
puts "n = #{n}, total = #{total}, mean = #{mean}, sd = #{sd}"

実行例

$ ./mean.rb
1
2
3
(Ctrl-d)
n = 3, total = 6.0, mean = 2.0, sd = 0.8164965809
$ ./mean.rb data.txt
n = 5, total = 15.0, mean = 3.0, sd = 1.414213562

スタックとキュー

LIFO(スタック)

a = [] # a => []
a.push('a') # a => ["a"]
a.push('b') # a => ["a", "b"]
b = a.pop # a => ["a"], b => "b"
c = a.pop # a => [], c => "a"
d = a.pop # a => [], d => nil

FIFO(キュー)

a = []
a.push('a', 'b') # a => ["a", "b"]
a.push('c') # a => ["a", "b", "c"]
b = a.shift # a => ["b", "c"], b => "a"

読みもどし

a = ['b', 'c']
a.unshift('a') # a => ["a", "b", "c"]

HTML の特殊文字のエスケープ

require "cgi"
CGI.escapeHTML("a < b && a > d") # => "a &lt; b &amp;&amp; a &gt; d"
CGI.unescapeHTML("a &lt; b &amp;&amp; a &gt; d") # => "a < b && a > d"

HTML の特殊文字をエスケープするフィルター

#!/usr/local/bin/ruby
require "cgi"
while ( a = gets )
  puts CGI.escapeHTML( a )
end

cgi.rb の CGI::escapeHTML(string) のコード

def CGI::escapeHTML(string)
    string.gsub(/&/n, '&amp;').gsub(/\"/n, '&quot;').gsub(/>/n, '&gt;').gsub(/</n, '&lt;')
end

2次元配列の初期化

def dim( row, column )
  (0...row).collect { Array.new(column).fill(0) }
end

複数の戻り値をメソッドから返す

メソッドから複数の戻り値を返したいときがあります。Ruby では多重代入ができるので、メソッドからの戻り値を配列で返すと、複数の戻り値をメソッドから返したように見せることができます。

def test
  a = [1,2]
  b = "apple"
  [a, b]
end
c, d = test
puts c.inspect
puts d

メソッドに渡した二次元配列のコピーをとる

メソッドの引数に二次元配列を渡すときに、元の配列の内容を変えたくない場合があります。そこでメソッド内のローカル変数に引数のコピーを渡すつもりで temp = arry.dup としても、メソッドの中から抜けたときにもとの配列の内容も書き変わってしまいます。

$ irb
irb(main):001:0> def test( arry )
irb(main):002:1>   temp = arry.dup
irb(main):003:1>   temp[0][0] = "changed"
irb(main):004:1>   temp
irb(main):005:1> end
nil
irb(main):006:0> a = [[1,2],[3,4]]
[[1, 2], [3, 4]]
irb(main):007:0> test( a )
[["changed", 2], [3, 4]]
irb(main):008:0> a
[["changed", 2], [3, 4]]

これは dup メソッドによるコピーの仕様が"浅い"コピーであるためではないかと思います。次のようにすれば、メソッド内で"深い"コビーをとることができます。

irb(main):009:0> def test( arry )
irb(main):010:1>   temp = arry.collect{|c| c.dup }
irb(main):011:1>   temp[0][0] = "changed"
irb(main):012:1>   temp
irb(main):013:1> end
nil
irb(main):014:0> a = [[1,2],[3,4]]
[[1, 2], [3, 4]]
irb(main):015:0> test( a )
[["changed", 2], [3, 4]]
irb(main):016:0> a
[[1, 2], [3, 4]]

Marshal モジュールを利用してオブジェクトのコピーを取る

Marshal モジュールを利用するとどんなオブジェクトの「深い」コピーもとれます。

irb(main):001:0> a = [[1, 2],[3, 4]]
[[1, 2], [3, 4]]
irb(main):002:0> b = Marshal.load( Marshal.dump( a ))
[[1, 2], [3, 4]]
irb(main):003:0> a[0][0] = 'changed'
"changed"
irb(main):004:0> a
[["changed", 2], [3, 4]]
irb(main):005:0> b
[[1, 2], [3, 4]]
irb(main):006:0> 

さいころ

irb(main):001:0> begin
irb(main):002:1* srand
irb(main):003:1> for i in 1..6
irb(main):004:2>   puts rand(6)+1
irb(main):005:2> end
irb(main):006:1> end

irb で vi を使う

irb は Ruby を対話的に使えて便利ですが、ちょっと長いプログラムをためすのは編集ができないので不便です。次のようにすれば vi を使ってプログラムを編集できます。

irb(main):001:1> system 'vi temp.rb'

編集が終了したら次のようにして実行します。

irb(main):002:1> system 'ruby temp.rb'

実行が失敗してもう一度編集したいときは Ctrl-p で system 'vi temp.rb' をコマンドライン上に呼び出して Ret キーを押します。

ストリームのスキャン

改行に関係なくストリームの整数データを一気に取得する方法です

data = $stdin.read.scan(/[+-]*\d+/)

C言語プログラムのコメントを消すプログラム

print ARGF.read.gsub(/\/\*.*?\*\//m, "")

数値データを GNUPLOT でグラフにする

gp = IO.popen("gnuplot -persist", "w")
gp.puts 'plot "-" w l'
(-2).step(3, 0.1) do |x|
    y =  x * (x + 1) * (x - 2)
    gp.puts "#{x} #{y}"
end
gp.puts "end"
gp.close