配列、ハッシュ、ブロック、イテレータ

配列

まずは配列

a = [1, 3, 5, 7, 9]
a[-1] # => 9
a[-2] # => 7
a[-99] # => nil

後ろから指定することもできる

インデックスを[start, count]で指定する。startからはじまるcount個のオブジェクトへの参照が返る

a = [1, 3, 5, 7, 9]
a[1, 3] # => [3, 5, 7]
a[3, 1] # => [7]
a[-3, 2] # => [5, 7]

配列は範囲で参照することもできる。範囲は開始、終了の位置を二つまたは三つのピリオドで区切って指定する。
二つの場合は終了位置を含める。三つの場合は終了位置は含まれない。これいつも間違える。

a = [1, 3, 5, 7, 9]
a[1..3] #=> [3, 5, 7]
a[1...3] #=> [3, 5]
a[-3..-1] #=> [5, 7, 9]

[]=で要素を代入できる

a = [1, 3, 5, 7, 9]
a[1] = 'bat'            ->[1, "bat", 5, 7, 9]
a[3] = [9, 8]          ->[1, "bat", 5, [9, 8], 9]
a[6] = 99              ->[1, "bat", 5, [9, 8],  9, nil, 99]

[]=も同じく開始位置と長さを指定して代入できる。長さ0の場合、右辺が開始位置の前に代入される。

a = [1, 3, 5, 7, 9]
a[2, 2] = 'cat'        -> [1, 3, "cat", 9]
a[2, 0] = 'dog'       -> [1, 3, "dog", 9]

配列の便利なメソッドを使うことで、スタック、キュー等として扱うことができる。
以下はpush、popを使用することで配列の末尾に要素を追加、末尾の要素を削除できるのでスタックとして扱う。

stack = []
stack.push "red"
stack.push "green"
stack.push "blud"
p stack
puts stack.pop
puts stack.pop
puts stack.pop
p stack

出力結果

["red", "green", "blue"]
blue
green
red
[]

以下は先頭要素を削除するshiftとpushの組み合わせで配列をキューとして扱う

queue = []
queue.push = "red"
queue.push = "green"
puts queue.shift
puts queue.shift

出力結果

red
green

first、lastメソッドはそれぞれ先頭、末尾からn番目までのエントリを返す

array = [1, 2, 3, 4, 5, 6, 7]
p array.first(4)
p array.last(4)

出力結果

[1, 2, 3, 4]
[4, 5, 6, 7]

ハッシュ

続いてハッシュ。以下のようにして作る。

h = { 'dog' => 'carine', 'cat' => 'feline', 'donkey' => 'azinine' }
h.length #=> 3
h['dog']   #=> "carine"
h[12]      #=> 99
h            # => {"dog" => "carine", "cat" =>'feline', "donkey" => "asinine", 12 => "dodecine"}

シンボルを使用する場合は以下のようになる

h = { dog: 'carine', cat: 'feline', donkey: 'asinine' }

ブロック

以下のように書く

some_array.each { |value|  puts value * 3}

sum = 0
other_array.each do |value|
  sum += value
  puts value / sum
end

一行に収まる場合はブレース、複数行の場合はdo/end形式で書く。

イテレータ

Rubyイテレータはコードブロックを呼び出せるメソッドのことを言う。メソッド内でyield文を使うことで、ブロックを呼び出すことができる。ブロックを抜けるとyield分の直後に制御が戻る。

def three_times
  yield
  yield
  yield
end
three_times { puts "Hello" }

出力結果

Hello
Hello
Hello

次はフィボナッチ数列を出力するプログラム。

def fib_up_to(max)
  i1, i2 = 1, 1
  while i1 <= max
    yield i1
    i1, i2 = i2, i1 + i2
  end
end

fib_up_to(1000) { |f| print f, " "}

yield文に引数を与えることで、値をブロックに渡している。

次はArrayクラスのfindメソッド概要。ブロックからメソッドに値を返している。

class Array
  def find
    for i in 0 ...size
      value = self[i]
      return value if yield(value)
    end
  end
end

[1, 3, 5, 7, 9].find {|v| v*v > 30 } #=> 7

配列の各要素がブロックに渡され、ブロックがtrueを返すとfindメソッドはその値を返す。
これはなかなかJavaとかではできない。Arrayクラスが要素を調べる処理をしてくれるが、Javaだと自分のコードでその処理をかかないといけない。

eachは最も単純なイテレータで各要素に対してyield分を実行するだけ。

[1, 3, 5, 7, 9].each {|i| puts i}

出力結果

1
3
5
7
9

collect(map)はコレクションの各要素を受け取り、ブロックに渡す。ブロックから返された値を使って新しい配列を作る。

["H", "A", "L"].collect {|x| x.succ } #=> ["I", "B", "M"]

ブロックを通過した回数を記録したい場合は、each_with_indexメソッドを使用する。

f = file.open("testfile")
f.each_with_index do |line, index|
  puts "Line #{index} is : #{line}"
end
f.close

Enumerableモジュールに定義されているinjectメソッドを使うとコレクションの各メンバの値を累算できる。

[1, 3, 5, 7].inject(0) {|sum, element| sum + element }           #=> 16
[1, 3, 5, 7].inject(1) {|product, element| product * element}  #=> 105

ブロックが最初に呼ばれるとsumにinjectの引数(0)が設定され、elementにコレクションの先頭要素が設定される。ブロックの2回目以降の呼び出しではsumに前回のブロック呼び出しで返された値が設定される。injectの最終値はブロックの最後の呼び出しで返された値になる。injectを引数なしで呼び出すとコレクションの先頭要素が初期値として使われ、繰り返しは2番目の要素から開始される。

[1, 3, 5, 7].inject {|sum, element| sum + element}           # =>16
[1, 3, 5, 7].inject {|product, element| product * element}  #=> 105

injectには各要素に適用するメソッドの名前を引数として与えることができる。

[1, 3, 5, 7].inject(:+)   #=> 16
[1, 3, 5, 7].inject(:*)    #=> 105