Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Ruby TIPS

Ruby TIPS

ブロック ― ちょっと便利な繰り返し処理の構文とは?(3)

2016年8月10日

Rubyに用意されている繰り返し処理の構文のうち「ブロック」を使えば、繰り返し処理をより簡潔に書ける。その基本的な使い方と、自作メソッドでの利用例を解説する。

ローグ・インターナショナル 羽山 博
  • このエントリーをはてなブックマークに追加

 Rubyの繰り返し処理には、ブロックと呼ばれる機能が利用できる。ブロックを利用すれば、while文やuntil文、for文などを使った繰り返し処理よりも簡潔な書き方で、オブジェクトの各要素に対する処理ができる。今回は、ブロックを使った繰り返し処理の書き方と、自作のメソッドでブロックを利用できるようにする方法を見ていく。

ブロックを利用する

 ブロックとは、いわゆるイテレーター(反復子)を実装したものである。イテレーターという用語になじみのない人には、実感がわきにくいかもしれないが、要するに、オブジェクトの要素や値を1つずつ取り出して処理することと考えればよい。前回の繰り返し処理についての解説(「TIPS: for文 ― ちょっと便利な繰り返し処理の構文とは?(2)」)でも簡単な例を示したが、まずは使い方から丁寧に見ていこう。

ブロックの使い方(1) - Integerクラスのtimesメソッド

 例えば、整数(Integer)クラスにはtimesというメソッドがあり、整数の値の回数だけ繰り返し処理が実行できる。最初は、ブロック(正確には後述するブロックパラメーター)を使わずに単純に繰り返し処理を行う例を見てみる。

sample001.rb
3.times do
  puts "Hello Ruby!"
end
リスト1.1 Integerクラスのtimesメソッドを使って繰り返し処理を行う

3というリテラルは整数クラスのオブジェクト(正確にはFixnumクラスのオブジェクト)として扱われるので、timesメソッドの後のdoendの間に書かれている文が3回実行される。

 なお、doendの代わりに{}を使ってもよい。実行結果は容易に想像できると思うが、念のため掲載しておこう。

コンソール
$ ruby sample001.rb 
Hello Ruby!
Hello Ruby!
Hello Ruby!
$ 
実行例1.1 繰り返し処理のプログラムを実行する

プログラムを実行すると、timesメソッドの働きによりHello Ruby!が3回表示される。

 この段階では、timesメソッドによる繰り返し処理を行っただけで、ブロックの機能を全て使っているわけではないことに注意してほしい。なお、Integerオブジェクトの値が0以下の場合、timesメソッドによる繰り返しは実行されない。

ブロックパラメーターの使い方

 続けて見ていこう。繰り返し処理の中では、何回目の繰り返しであるかを知りたいことが多い。そこで、ブロックパラメーターを利用する。||で囲まれた範囲にブロックパラメーターと呼ばれる変数を書いておけば、その変数に毎回、値が渡される。timesメソッドの場合は、0整数の値-1が渡される。ブロックパラメーターの変数名は自由に付けてよい。以下の例では、iという名前にした。

sample002.rb
3.times do |i|
  puts "Hello Ruby!" + i.to_s
end
リスト1.2 timesメソッドにブロックパラメーターを指定する

ブロックパラメーターの名前はiとした。timesメソッドの場合、繰り返しを実行するたびに012……という値が順にブロックパラメーターに渡される。+演算子を使って文字列を連結するために、変数iの値をto_sメソッドで文字列に変換していることにも注意。

 timesメソッドのように、ブロックパラメーターが指定できるメソッドのことをブロック付きメソッドと呼ぶ。

 実行結果は以下の通り。ブロックパラメーターを利用することによって、繰り返しが何回目であるかということが分かるようになった。

コンソール
$ ruby sample002.rb
Hello Ruby!0
Hello Ruby!1
Hello Ruby!2
$ 
実行例1.2 ブロックパラメーターを利用して何回目の繰り返しかを知る

プログラムを実行すると、timesメソッドの働きにより処理が3回繰り返される。繰り返しを実行するごとに、変数iには012が順に渡される。

[まとめ]ブロックの使い方

 書き方を十分理解した上で次に進みたいので、ここで、ブロックの使い方を図解しておく。

図1.1 ブロックパラメーターの指定方法と動作

メソッドの後に書かれたdoendの間の文が繰り返し実行される。doの後の||の間に書かれたブロックパラメーターに、毎回、値が渡される。

 ここでは、Integerクラスのtimeメソッドを例に、ブロック付きのメソッドの使い方とブロックパラメーターの指定方法を見てきたが、あくまでもtimeメソッドの使い方なので、他のメソッドの場合は動作が異なることに注意してほしい。

ブロックの使い方(2) - Integerクラスのupto/downtoメソッド

 例えば、Integerクラスのuptoメソッドでは、整数オブジェクトの値から引数の値までを順に返す。

sample003.rb
5.upto(8) do |i|
  puts "Hello Ruby!" + i.to_s
end
リスト1.3 uptoメソッドを利用して、引数に指定した値までの繰り返しを実行する

5という整数オブジェクトのuptoメソッドを呼び出す。引数に8を指定すると、58までの(つまり4回の)繰り返し処理が行われる。繰り返し処理が実行されるたびに、5678が順にブロックパラメーターに渡される。

 実行例は以下の通り。ちなみに、uptoメソッドとは逆に、値を減らしていくdowntoメソッドもある。引数やブロックパラメーターの指定方法は同じである。

コンソール
$ ruby sample003.rb 
Hello Ruby!5
Hello Ruby!6
Hello Ruby!7
Hello Ruby!8
$ 
実行例1.3 5~8までの値を取り出すプログラムの実行例

uptoメソッドでは、引数(8)までの値が順にブロックパラメーターに渡される。開始値は整数オブジェクトの値(5)そのものなので、58の値が順にブロックパラメーターに渡されることになる。

ブロックの使い方(3) - Arrayクラスのeachメソッド

 もう1つ例を見ておこう。配列を表すArrayクラスではeachメソッドが使える。eachメソッドは、配列の要素数だけ繰り返し処理を実行する。ブロックパラメーターには配列の各要素が順に渡される。

sample004.rb
a = [2, 3, 5, 7, 11]
a.each do |i|
  puts "Hello Ruby!" + i.to_s
end
リスト1.4 ブロックを利用して配列の要素を全て処理する

配列aの要素数は5なので、繰り返し処理は5回実行される。ブロックパラメーターのiには配列aの要素が順に渡される。

 実行例は以下の通り。

コンソール
$ ruby sample004.rb
Hello Ruby!2
Hello Ruby!3
Hello Ruby!5
Hello Ruby!7
Hello Ruby!11
$
実行例1.4 プログラムを実行して配列の要素を全て表示する

ブロックパラメーターには配列の要素(5つの素数)が順に渡されるので、繰り返し処理によりそれらが順に表示される。

ブロック付きメソッドを作成する

 これまで、ブロックパラメーターを指定して、ブロック付きメソッドを呼び出す方法を見てきた。ここからは、ブロック付きメソッドを自分で作成する方法を見ていく。やはり簡単な例から見ていこう。yieldメソッドを使えば、ブロックパラメーターに渡す値を指定できる。

引数なしのブロック付きメソッドの作り方 ― yieldメソッドの使い方

 以下の例は、"red""green""blue"という文字列を順に返すだけのcolorEnumメソッドの作成例と利用例である。colorEnumメソッドの中では、yieldメソッドの引数にブロックパラメーターに渡す値を指定する。繰り返しの回数はyieldメソッドが呼び出される回数と考えればよい。

sample005.rb
def colorEnum    # colorEnumメソッドを定義する
  yield "red"
  yield "green"
  yield "blue"
end

colorEnum do |x|    # colorEnumメソッドを呼び出す
  puts x  
end
リスト1.5 色の名前を順に返すブロック付きメソッドの作成例と利用例

ここで作成したcolorEnumメソッドではyieldメソッドが3回実行されるので、繰り返し数は3となり、putsメソッドは3回実行される。ブロックパラメーターにはyieldメソッドの引数に指定した"red""green""blue"が順に渡される。

 このプログラムの実行結果も容易に想像できるが、以下に示しておこう。

コンソール
$ ruby sample005.rb 
red
green
blue
$
実行例1.5 ブロック付きメソッドを利用して色の名前を順に表示する

yieldメソッドによって色の名前がブロックパラメーターに渡され、順に表示された。

 実用的なプログラムを作るときには、上のプログラムのようにyieldメソッドをいくつも書くのは現実的ではない。そのためには、メソッド定義の中で繰り返し処理(や、他のブロック)を使えばいい。

引数ありのブロック付きメソッドの作り方

 以下の例は、指定した個数だけ奇数を順に表示するプログラムである。ブロック付きメソッドはmakeoddという名前とし、繰り返し処理を使ってブロックパラメーターに値を渡すようにしてある。ただし、繰り返しの回数はmakeoddメソッドの引数として渡すものとする。リスト1.5の例は引数のないメソッドだったが、ここでは引数のあるメソッドであることにも注意しよう。

sample006.rb
def makeodd(limit)  # makeoddメソッドを定義する
  for i in 0..limit-1
    yield 2 * i + 1
  end
end

makeodd(5){|x|  # makeoddメソッドを呼び出す
  puts x
}
リスト1.6 指定した個数だけ奇数を順に返すブロック付きメソッドの作成例と利用例

makeoddメソッドの引数limitには、繰り返しの回数を渡す。for文によって、0からlimit-1まで繰り返すので、yieldメソッドはlimit回実行される。ブロックパラメーターには1から順に奇数が渡される。makeoddメソッドを呼び出すときに、引数に5を指定しているので、引数limitの値は5となる。yieldメソッドは5回実行されるので、5回の繰り返しとなる。

 実行例は以下の通り。

コンソール
$ ruby sample006.rb 
1
3
5
7
9
$
実行例1.6 ブロック付きメソッドを利用して奇数を順に表示する

makeoddメソッドの引数limitに渡された回数、つまり5回だけ繰り返しが実行される。yieldメソッドによって奇数が1から順にブロックパラメーターに渡され、表示された。

[番外編]既存クラスへのブロック付きメソッドの追加

 最後に、おまけとして、既存のクラスにブロック付きメソッドを追加する例も示しておこう。Rubyでは既存のクラスと同じ名前でクラスを定義すれば、クラスにメソッド(インスタンスメソッド)を追加できる。そこで、Integerクラスにmakeoddメソッドを追加してみる。ただし、このmakeoddメソッドは上の例とは異なり、Integerクラスのオブジェクトの値から、引数で指定された値の間にある奇数を順に取り出すものとする。

sample007.rb
class Integer
  def makeodd(limit)
    start = self.odd? ? self: self + 1  # 偶数なら次の奇数から
    limit = limit.odd? ? limit: limit - 1  # 偶数なら前の奇数まで
    count = (limit - start) / 2 + 1 # その間にある奇数の個数  
    for i in 0..count - 1
      yield start + i * 2 # 順に奇数を返す
    end
  end
end

2.makeodd(9){|x|
  puts x
}
リスト1.7 Integerクラスにブロック付きメソッドを追加する

Integerというクラスはすでに存在するが、同じ名前のクラスを定義すればインスタンスメソッドが追加できる。ここではmakeoddメソッドを追加した。オブジェクトの値が偶数であっても、引数として与えた上限の値が偶数であっても、正しく動作するように前処理をしているが、基本的には、指定された範囲内にある奇数をブロックパラメーターに渡すだけである。

 実行例は以下の通り。Integerオブジェクトの値は2であり、引数として9を与えているので、2以上、9以下の範囲にある奇数が求められる。

コンソール
$ ruby sample007.rb 
3
5
7
9
$
実行例1.7 Integerクラスに追加したブロック付きメソッドを利用する

Integerオブジェクトの値を開始値として、makeoddメソッドの引数limitに渡された値までの奇数が求められる。開始値と引数limitの間に奇数がない場合や、開始値よりも引数limitに渡した値が小さいときには何も返されない。

 本連載では、クラスの取り扱いについてはまだ説明していないが、リスト1.7のコードはそれほど複雑ではないので、ぜひ追いかけてみてほしい。

まとめ

 ブロック付きメソッドを利用すると、繰り返し処理によってオブジェクトの要素などを全て処理したり、毎回の処理の際にブロックパラメーターに値を渡してもらったりすることができる。

 また、既存のものを利用するだけでなく、ブロック付きのメソッドを自作することもできる。yieldメソッドを使えば、自作のブロック付きメソッドからブロックパラメーターに値を渡せる。yieldメソッドを実行する回数が繰り返し回数となる。

処理対象:ブロック付きメソッド|yieldメソッド|ブロックパラメーター カテゴリ:文法 > 制御構造 > ブロック

※以下では、本稿の前後を合わせて5回分(第9回~第13回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

Ruby TIPS
9. while修飾子/until修飾子/while文/until文 ― ちょっと便利な繰り返し処理の構文とは?(1)

Rubyに用意されている繰り返し処理構文のうち、while文/until文の基本的な使い方と落とし穴を解説。また、while/until「修飾子」を使った簡潔な記述方法にも言及する。

Ruby TIPS
10. for文 ― ちょっと便利な繰り返し処理の構文とは?(2)

Rubyに用意されている繰り返し処理の構文のうち、for文の基本的な使い方と配列と組み合わせた利用例を解説。範囲や配列を利用して一定の回数だけ繰り返して文を実行する方法や、配列全体を処理する方法を紹介する。

Ruby TIPS
11. 【現在、表示中】≫ ブロック ― ちょっと便利な繰り返し処理の構文とは?(3)

Rubyに用意されている繰り返し処理の構文のうち「ブロック」を使えば、繰り返し処理をより簡潔に書ける。その基本的な使い方と、自作メソッドでの利用例を解説する。

Ruby TIPS
12. クラスとそのコンストラクター/アクセサー/メソッドを定義し利用するには?

Rubyプログラミングの基本中の基本として、クラスの定義から、そのインスタンスの作成・利用、インスタンスメソッドの定義、変数へのアクセスまでを説明する。

Ruby TIPS
13. クラスを継承するには? メソッドの呼び出しをprivate/protectedで制限するには?

オブジェクト指向言語の特長である「クラスの継承」をRubyで実現する方法を解説。スーパークラスのメソッドの呼び出し制限で、Ruby言語特有の内容についても紹介する。

サイトからのお知らせ

Twitterでつぶやこう!