ぱるも日記

ぱるも日記は id:palmo がプログラミング言語 Perl を1から勉強していく日記です。

2006-07-24

範囲演算子

スライスを勉強した時に「範囲構文」として勉強した「a..b」という書き方ですが、ラクダ本を読んでいたところ、これは構文というよりは「..」という「範囲演算子」によるもの、という事がわかりました。

今まで「整数整数の間の範囲の整数リストを作り出す」という程度の認識で使っていたのですが、やはりちゃんと勉強しないとダメですね。知らなかった使い方が見つかりました。(^_^;)

というわけで、範囲演算子をちゃんと勉強したいと思います。


ラクダ本プログラミングPerl〈VOLUME1〉)によると、範囲演算子(range operator)は「コンテキスト」によってその意味が変わるそうです。

「コンテキスト」は、ぱるも日記でも何度か登場して勉強していますが、「文脈」の事ですね。スカラー変数への代入文の右辺や if 文の条件文など、「スカラー値」が必要と判断される場所は「スカラーコンテキスト」と呼ばれ、配列変数への代入文の右辺や foreach 文の対象など、「リスト値」(複数のスカラー値)が必要と判断される場所は「リストコンテキスト」になるのでした。


範囲演算子を「リストコンテキスト」で使った場合は、今までの使い方である「整数整数の間の整数リストを生成」という意味になります。

my @onetoten = (1..10);   # 配列変数への代入なのでリストコンテキスト
print "@onetoten\n";
1 2 3 4 5 6 7 8 9 10

また、Perl には「マジックインクリメント」という機能があり、/^[a-zA-Z]*[0-9]*$/ にマッチする文字列が入ったスカラー変数をインクリメント(++)する事ができます。数字の10文字にアルファベット26文字を加えて、「36進数」であるかのように振舞うのです。ケタ上がりにも対応しています。

my @nums = qw(A ab AZ Z9 01 perl);
print "$_ -> ", ++$_, "\n" for (@nums);

@nums に入れた文字列をそれぞれインクリメントして表示します。実行結果は以下の通りです。

A -> B
ab -> ac
AZ -> BA
Z9 -> AA0
01 -> 02
perl -> perm

範囲演算子は内部でマジックインクリメントを利用しているので、範囲演算子の項には文字列を渡す事もできます。

my @atoz = ('a'..'z');
print "@atoz\n";
a b c d e f g h i j k l m n o p q r s t u v w x y z

便利ですね。(^_^)


範囲演算子スカラーコンテキストで評価した場合の動作は全く異なります。

スカラーコンテキストでの範囲演算子は、左右に真偽値をとり、真偽値を返す、論理演算子のように振舞います。しかし、ただの演算子とは違い、それぞれの範囲演算子が個別の「内部状態」(ON/OFF)を持っている、というのがポイントです。どの範囲演算子の内部状態も最初は「OFF」となっています。

例えば「A..B」がスカラーコンテキストに書かれていて、繰り返し構文などで何度も評価されるとします。

この「A..B」を評価すると、内部状態が「OFF」の時は A が評価されます。A が偽の場合、範囲演算子は何もせずに偽を返します。ですが、A が真になると範囲演算子の内部状態は「ON」となり、範囲演算子は真を返します。「A..B」が評価された時に、内部状態が「ON」の時は B が評価されます。B が真の場合、範囲演算子は何もせずに真を返しますが、B が偽になると内部状態が「OFF」になり、範囲演算子は偽を返します。

つまり、スカラーコンテキストでの範囲演算子は、「ON 条件と OFF 条件をとるスイッチ」のように振舞うのです。最初は ON 条件だけを見張っていて、一度 ON 条件が真になれば次からは OFF 条件を見張りだす、というように動くわけですね。見張られていない方の条件は「評価されない」事に注意してください。

また、ON 条件が真になった時 OFF 条件も評価されますが、ドットを3つにする(...)と ON 条件が真になっても次回の評価まで OFF 条件の評価は始まりません。


このスカラーコンテキストでの範囲演算子は、使い道が思い浮かばないかもしれませんが、色々な場面で利用できます。スイッチの代わりになる、というのが便利なのです。

while (<DATA>) {
  chomp;
  print "$_\n" if ( /^_START_$/ .. /^_END_$/ );
}

__END__
AAA
_START_
BBB
CCC
_END_
DDD

特殊ファイルハンドル DATA には __END__ 以降のテキストが入っているのでしたね。DATA に入っているテキストを行入力演算子で毎行読み込んで、範囲演算子正規表現パターンを組み合わせて判定しています。

この場合 "_START_" という行が見つかったら、範囲演算子は "_END_" という行を見つけるまで真を返すようになります。("_START_" から "_END_" まで)

実行結果は以下の通りです。

_START_
BBB
CCC
_END_

_START_ と _END_ の間のテキストだけを表示する事ができました。


また、ON/OFF 条件として「リテラルな正の整数」(0, 1, 2...)を指定した場合、暗黙のルールとして特殊変数「$.」(最後に行入力演算子で読み込んだ行の行番号)と比較されます。

while (<DATA>) {
  chomp;
  print "$_\n" if (2..4);
}

__END__
AAA
BBB
CCC
DDD
EEE

この場合の範囲演算子は「2行目から4行目」と読み替えればいいわけですね。(^_^)

実行結果は以下の通りです。

BBB
CCC
DDD

「○○から××まで」を直感的に書く事ができて、コードがとても簡潔になりますね。使いどころがちょっと難しいですが、色々と応用できそうです。

栗田栗田 2006/09/07 02:19 範囲指定演算子を調べていてこのページにたどり着きました。いろんなページを見てもスカラコンテキストの振る舞いを理解できませんでしたが、このページでようやく理解できました。ありがとうございます。

jwoz2sJXjdjwoz2sJXjd 2006/09/15 15:47 eqHunY6Myr wr9KkkEuL5 w4EyTnYChcWU7x

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/palmo/20060724/rangeop
Connection: close