ArrayやHashなどのコンテナーやRangeといった範囲オブジェクトを使っていると、含まれている要素1つ1つに対して、何らかの処理を行いたい場合があります。このようなコンテナーの各要素に対する繰り返しの処理を、概念的に「イテレーター」と呼びます。
少し込み入った話をすると、ArrayやHashは「Enumerable」というモジュールをミックスインすることによってイテレーターを実現する種々のメソッドを実装しています。ミックスインについては、以降の連載のクラスやモジュールを解説する回で説明します。
ArrayオブジェクトにおいてもHashオブジェクトにおいても、要素を1つずつ取り出しながら処理するためのメソッドである「each」を使えます。array01.rbに配列から要素を順に取り出して表示するサンプルコードを示します。
["alice", "in", "wonderland"].each do |term| puts term end
$ ruby array_01.rb alice in wonderland
「do |term| 〜 end」の一塊を「ブロック」と呼び、ブロックには一連の処理を含めることができます。ブロックはメソッドに与える引数の1つで、通常の引数のようにデータそのものを渡すのではなく、「一連の処理そのもの」を渡すと考えると理解しやすいと思います。
この例では、配列「["alice", "in", "wonderland"]」から1つずつ要素が取り出され、変数termに格納されます。この変数名は自由に付けることができるので、必ずtermである必要はありません。termに格納されたオブジェクトは自由に操作できます。
ここでは、単純にtermの内容をputsメソッドで画面に表示しています。
Hashオブジェクトにおいても、同様にeachメソッドが使えます。ただし、こちらはキーとキーに関連付けられたオブジェクトの2つの変数が必要になるので、ブロックの変数を指定する部分が「|key, value|」となっています。
{jack: 11, queen: 12, king: 13}.each do |key, value| puts "key: #{key}, value: #{value}" end
key: jack, value: 11 key: queen, value: 12 key: king, value: 13
each_with_indexメソッドを使うと、繰り返しごとに1回ずつカウントアップされるインデックスを使えるようになります。
["alice", "in", "wonderland"].each_with_index do |term, index| puts "#{index}: #{term}" end
$ ruby array02.rb 0: alice 1: in 2: wonderland
reverse_eachメソッドを使うと、逆順に要素を1つずつ取り出しながら処理を行えます。array03.rbは、そのサンプルコードです。
["alice", "in", "wonderland"].reverse_each do |term| puts term end
$ ruby array03.rb wonderland in alice
ここまで最も基本的なイテレーターを紹介しましたが、実はまだまだ関数型言語由来の便利なメソッドが豊富にあります。eachは汎用性が高く使いやすいのですが、記述が冗長になりがちです。「map」「inject」「select」「reject」「all?」といったメソッドを使いこなすことで、eachをなるべく使わずに簡潔なコードを書くことができます。
これらのメソッドについては、以降の連載のブロックやクロージャの回で紹介する予定です。
Rangeクラスの解説での補足で、多くのRubyプログラマーはイテレーターの概念を使うことを好むと書きました。ここでは、その理由について説明します。
例えば、イテレーターという概念のないC言語で、配列の内容1つずつを画面に出力するようなコードは以下のようになります。
#include <stdio.h> #define LENGTH 10 int main(void) { int array[LENGTH] = {0}; /* 配列のインデックスと等しい値を要素として代入 */ for (i = 0; i < LENGTH; i++) { array[i] = i; } /* 標準出力に出力 */ for (i = 0; i < LENGTH; i++) { printf("%d\n", array[i]); } return 0; }
このような場合、C言語ではfor文を用いてループを回すやり方が定石ですが、このfor文が「全ての要素に対して処理を行っている」という事実は、ループの中身を見ないことには分かりません。このように、画面に出力する、代入するといった単純な処理ならば問題となりませんが、ループの中身が大きくなったり、構造体を使う場合には大変読みづらくなってしまいます。
イテレーターとは、プログラミングでほぼ必ず遭遇する「全ての要素に対する処理」という動作そのものを抽象化し、人間が読みやすいプログラムにするための、一歩進んだループの概念なのです。このような優れた仕組みが存在するのだから、それを使わない手はありません。
今回は、Rubyの組み込みライブラリの紹介の続きということで、範囲を表現するためのRangeクラスと、配列やハッシュ、範囲オブジェクトに深く関わるイテレーターという概念について説明しました。
次回は日付と時刻を表すTimeクラスと、数値を扱うための種々のクラスについて深く掘り下げていく予定です。お楽しみに!
麻田 優真(Rails技術者認定シルバー試験問題作成者)
イタリア、ローマ生まれ。中学生のころHSPに初めて触れ、プログラミングの楽しさを知る。オープンソースやハッカーカルチャーを好む。C#からRubyに転向したときに、動的型付け言語の柔軟性やメタプログラミングの魅力に感動し、Rubyとともにプログラマーとしての人生を歩む決意を固める。
現在は奈良先端科学技術大学院大学で学生として所属するかたわら、株式会社アジャイルウェアでプログラマーとして従事。Ruby on Railsによるコンシューマー向けのWebサービスの開発などに尽力している。
好きなメソッドは、define_method。
Twitter:@Mozamimy、ブログ:http://blog.quellencode.org/
山根 剛司(Ruby業務開発歴7年)
兵庫県生まれ。1997年からベンチャー系のパッケージベンダーで10年間勤務。当時、使用していた言語はJavaとサーバーサイドJavaScript。
2007年よりITコンサル会社に転職し、Rubyと出会って衝撃を受ける。基幹システムをRuby on Railsで置き換えるプロジェクトに従事。それ以来Ruby一筋で、Ruby on Railsのプラグインやgemも開発。
2013年より、株式会社アジャイルウェアに所属。アジャイルな手法で、Ruby on Railsを使って企業向けシステムを構築する業務に従事。
Ruby関西所属。
Twitter:@spring_kuma、Facebook:山根 剛司
Copyright © ITmedia, Inc. All Rights Reserved.