for/while文を使った繰り返し処理



  1. Perl



  2. here

Perlの繰り返しの構文について解説します。繰り返しの構文を覚えるとプログラムの幅がすごく広がります。

for文の基礎

まず最初に繰り返しの構文であるfor文について解説します。最初に次のサンプルを見てください。これは、0から9までの数を画面に出力するプログラムです。

for (my $i = 0; $i < 10; $i++) {
  print "$i\n";
}

これの出力結果は以下のようになります。0から9までの数が出力されていますね。

0
1
2
3
4
5
6
7
8
9

では構文について順番に解説します。for文は以下のような構文になっています。

for (ループ変数の初期化; ループする条件; ループ変数の更新) {
  ...
}
1. ループ変数の初期化

最初の部分はループ変数の初期化です。この部分でループ変数が最初にどの値で始まるかを決めます。ループ変数というのは、ループ内で利用される変数のことだと思ってください。

最初の例をもう一度見てみましょう。「my $i = 0」という部分に注目してください。

for (my $i = 0; $i < 10; $i++) {
  print "$i\n";
}

ループ変数の初期化部分では、変数宣言を同時に行うことができます。「$i」という変数を宣言し「0」で初期化しています。

この部分を「my $i = 5」とするとどうなるでしょうか。

for (my $i = 5; $i < 10; $i++) { ... }

「$i」が「5」で初期化されるので、出力結果は以下のようになります。

5
6
7
8
9
2. ループする条件

さて次に、ループする条件について見ていきましょう。

for (ループ変数の初期化; ループする条件; ループ変数の更新) {
  ...
}

for文では、ループする条件が満たされる間、ブロックが繰り返し実行されます。最初の例を見ると「$i」が「10」より小さいという条件になっています。

for (my $i = 0; $i < 10; $i++) {
  print "$i\n";
}

ですので「$i」が「10」より小さい間ブロックが実行されます。

この部分を「$i < 7」とするとどうなるでしょうか。

for (my $i = 0; $i < 7; $i++) { ... }

出力結果は以下のようになります。「$i」が「7」より小さい間ループするので、「6」まで出力されているのがわかります。

0
1
2
3
4
5
6
3. ループ変数の更新

最後に、ループ変数の更新の部分を解説します。

for (ループ変数の初期化; ループする条件; ループ変数の更新) {
  ...
}

ループ変数の更新の部分では、次のブロックを実行する前に、ループ変数の値を変えることができます。

for (my $i = 0; $i < 10; $i++) {
  print "$i\n";
}

最初の例では「$i++」と書いています。これはインクリメントと呼ばれ、$iの値を1増やすことができます。

次のループに進む前に「$i」の値が1増えます。つまりループを進むたびに「1」「2」「3」という風に増えていくことになります。

この部分を「$i += 2」とすればどうなるでしょうか。これは特殊な代入演算子で「$i」の値を「2」増やすことができます。

for (my $i = 0; $i < 10; $i += 2) {
  print "$i\n";
}

2づつ増えていきますので、出力結果は以下のようになります。

0
2
4
6
8

「0」から始まり「2」ずつ増え「10」より小さい間ループしていることを確認してください。

for文のサンプルプログラム

繰り返しの構文はいくつかのサンプルを実際に動かしてみると理解が早まると思います。

「9」から初めて「0」まで出力する

次は「9」から初めて「0」まで出力するプログラムを見てみましょう。

for (my $i = 9; $i >= 0; $i--) {
  print "$i\n";
}

「my $i = 9」と初期化されているので「$i」は「9」から始まりますね。条件は「$i >= 0」なので「$i」が「0以上」のとき繰り返されます。最後の「$i--」で、次のループに移る前に「$i」の値をひとつ減らしています。「--」というのは変数の値を「1」減らすデクリメント演算子です。

出力結果は以下のようになります。

9
8
7
6
5
4
3
2
1
0
ふたつ飛ばしで「9」から初めて「1」まで出力する

次はふたつ飛ばしで「9」から「1」まで出力してみます。

for (my $i = 9; $i >= 0; $i -= 2) {
  print "$i\n";
}

ループ変数の更新の部分が「$i -= 2」となっているので、次のループに入る前に「$i」の値が「2」小さくなります。

出力結果は以下のようになります。

9
7
5
3
1
for文とif文を組み合わせる

for文とif文を組み合わせてみましょう。for文とif文を組み合わせられるようになるとプログラミングが上達していきます。

「0」から「10」までループしますが、ループ変数が3の倍数だったときだけ、出力するというサンプルプログラムを書いてみます。

for (my $i = 0; $i < 10; $i++) {
  if ($i % 3 == 0) {
    print "$i\n";
  }
}

if文を使った条件分岐の部分について注目してみてください。「$i % 3 == 0」という記述で、「$i」が「3」の倍数であることを確認できます。「3」で割った余りが「0」だった場合に、その数は3の倍数といえるからです。

剰余演算子については以下の記事を参考にしてください。

3の倍数の時だけif文のブロックが実行されるので出力結果は以下のようになります。

0
3
6
9

if文とfor文を組み合わせると、プログラムをうまく制御できるようになります。if文についての詳しい解説は以下の記事を参考にしてください。

while文

次はwhileについて学びましょう。while文は、for文と同じようにループを行うための構文です。

for文と異なるのは、while文は「ループする条件」の部分しか持たないことです。次のサンプルを見てください。

my $i = 0;

while ($i < 10) {
  
  print "$i\n";
  
  $i++;
}

これを実行すると「0」から「9」までの数が画面に出力されます。

0
1
2
3
4
5
6
7
8
9

「$i < 10」という部分がwhile文でループする条件の部分です。for文と違って、初期化の部分やループ変数の更新の部分は、while文にはありません。

while (ループする条件) { ... }

「ループ変数の初期化」と「ループ変数の更新」の部分は、Perlの文として書く必要があります。

# ループ変数の初期化
my $i = 0;

while ($i < 10) {
  
  print "$i\n";
  
  # ループ変数の更新
  $i++;
}

上記のwhile文のサンプルは、次のfor文と対応しています。

for (my $i = 0; $i < 10; $i++) {
  print "$i\n";
}

for文の方がシンプルに書けていますね。ループ変数が必要な場合はfor文の方が書きやすいです。

foreach文

foreach文配列の要素を順番に処理するときに利用できます。次のサンプルプログラムを見てください。

my @animals = ('Cat', 'Dog', 'Mouse');

foreach my $animal (@animals) {
  print "$animal\n";
}

これは('Cat', 'Dog', 'Mouse')という配列の要素を順番出力するプログラムです。出力結果は以下のようになります。

Cat
Dog
Mouse
for文とforeach文と同じ

Perlでは、foreach文はfor文の別名になっていて、foreachと書くべきところをforと書くこともできます。

my @animals = ('Cat', 'Dog', 'Mouse');

for my $animal (@animals) {
  print "$animal\n";
}

最近のPerlの書き方を見ているとforeachよりは常にforを使う傾向があるように思います。短いほうに落ち着くという感じでしょうかね。

範囲演算子と一緒に使う

範囲演算子と一緒に使うこともできます。

for my $num (3 .. 6) {
  print "$num\n";
}

next文

next文を使うと次の繰り返しの先頭にジャンプすることができます。次のサンプルプログラムを見てください。

my @nums = (3, 5, 6, 7);
for my $num (@nums) {
  if ($num == 5) {
    next;
  }
  print "$num\n";
}

(3, 5, 6, 7)という配列の要素を出力していますが「$num」が「5」の時だけ次の繰り返しの先頭にジャンプします。出力結果は以下のようになります。

3
6
7

「5」の時だけ、ループの先頭にジャンプしているので「print関数」が実行されません。

last文

last文を使うと繰り返しを終了して、for文を脱出することができます。次のサンプルプログラムを見てください。

my @nums = (3, 5, 6, 7);
for my $num (@nums) {
  if ($num == 6) {
    last;
  }
  print "$num\n";
}

(3, 5, 6, 7)という配列の要素を出力していますが「$num」が「6」の時にループを脱出します。そのため、出力結果は以下のようになります。

3
5

ここではforと組み合わせる例を紹介しましたが、nextとforはwhile文の中でも使うことができます。

forとwhileとforeachの使い分け

三つの繰り返しの構文について解説しましたが「実際はどうやって使い分けたらいいの」と感じることと思います。そこで、使い分けの指針について書いておきます。

まずはforeachで書けないか考える

まず最初にforeachを使って書けないかを考えます。foreachはループを最も簡単に書けるので、foreachで十分なのであれば、foreachを使います。配列の要素を順番に処理するというのは、頻繁にでてきますが、これはforeachで書けます。

my @animals = ('Cat', 'Dog', 'Mouse');

foreach my $animal (@animals) {
  print "$animal\n";
}

あるいはエイリアスのforを使って以下のように書きます。

my @animals = ('Cat', 'Dog', 'Mouse');

for my $animal (@animals) {
  print "$animal\n";
}

まずはこの方法で書けないかを検討します。

要素番号が必要なときは通常のfor文

次に要素番号が必要な場合があるとします。そのときは通常のfor文を使います。次のサンプルプログラムは、要素番号と配列の要素を出力するものになっています。「@animals」をスカラコンテキストで評価すると、配列の長さが取得できることに注意してください。

my @animals = ('Cat', 'Dog', 'Mouse');

for (my $i = 0; $i < @animals; $i++) {
  my $animal = $animals[$i];
  print "$i : $animal\n";
}

出力結果は以下のようになります。

0 : Cat
1 : Dog
2 : Mouse
要素番号を増やしていかないような繰り返しはwhile文

最後に要素番号を増やしていかないような繰り返しであればwhile文を使いましょう。よくあるサンプルをいくつか書いておきます。

1. ファイルから行を読み込む

ファイルから行を読み込む場合はwhile文を使用します。

while (my $line = <>) {
  print $line;
}

ファイル入出力については以下で詳しく解説しています。

2. 配列をpopする

配列をpop関数を使って取り出してなくなったら終わりというような処理はwhile文で書きます。

my @animals = ('Cat', 'Dog', 'Mouse');

while (my $animal = pop @animals) {
  print "$animal\n";
}

出力結果は以下になります。

Mouse
Dog
Cat

popされて空になったら、ループが終了します。

3. 無限ループを書きたい

無限ループを書きたいときはwhile文を使用します。条件を1にすると、while文をずっと実行され続けます。

while (1) {
  # 処理
}

何らかの条件が満たされた場合に、無限ループから脱出したい場合はlastを使いましょう。

while (1) {
  # 処理
  
  if ($flag) {
    last;
  }
}

多次元データ構造と繰り返し

最後に多次元データ構造と繰り返しのサンプルプログラムを一つ書いておきます。Perlにおいては、ハッシュの配列というデータ構造を頻繁に使うので、そのサンプルを書いておきます。

# ハッシュの配列のサンプルプログラム
my $students = [
  {name => 'kimoto', age => 36},
  {name => 'kana', age => 21},
  {name => 'jiro', age => 56}
];

for my $student (@$students) {
  my $name = $student->{name};
  my $age = $student->{age};
  
  print "Name : $name, Age : $age\n";
}

出力結果は以下のようになります。

Name : kimoto, Age : 36
Name : kana, Age : 21
Name : jiro, Age : 56

ここではまだ一度も解説していない「[]」や「{}」「@$students」「$student->{name}」というものが登場しています。これはリファレンスと呼ばれるPerlの構文です。多次元データ構造の処理については、以下で詳しく解説していますので、参考にしてください。

ループを簡単に書くための関数

grep関数

Perlにはgrep関数というものがあり、配列の要素の中でマッチするものだけを取り出すことができます。

my @matches = grep { $_ > 5 } @values;

これは、for文で次のように書くのと同じですが、より簡潔です。

my @matches;
for my $value (@$values) {
  if ($value > 5) {
    push @matches, $value;
  }
}
map関数

Perlにはmap関数というものがあり、すべての配列の要素の変換を簡単に書くことができます。

my @values2 = map { "$_.txt" } @values1;

これは、for文で次のように書くのと同じですが、より簡潔です。

my @values2;
for my $value1 (@values1) {
  my $value2 = "$value1.txt";
  push @values2, $value2;
}

後置のfor - for修飾子

特殊なforの構文として、forを文の後ろに置くことができるfor修飾子というものがあります。1行でfor文が書ける場合には、この書き方もシンプルです。

以下は、配列のすべての要素を出力するサンプルです。配列の要素は順番にデフォルト引数である「$_」に代入されます。

my @values = (3, 4, 5);
print "$_\n" for @values;

読みやすさを考えると通常のfor文を常に使うようにするのがよいと思います。

でも実は、後置のforには明らかな利点があります。それは、後置のforはスコープを作らないので、通常のforよりもパフォーマンスがよいということです。

日常の業務で書くほとんどのプログラムにおいては、パフォーマンスが問題にならないでしょう。でも、1行で実行できるもので、速さをどうしても稼ぎたい場合は、後置のforを使うとよいでしょう。