Perl初歩の初歩

プログラミング言語Perlの初心者のためのわかりやすい解説ブログ(を目指しています)

if / elsif / else もしも空を飛べたなら

2009-06-04 22:09:49 | 目次・はじめに

if は、「もし~だったら、…をする」というように、ある条件にあてはまったときにだけ実行するためのものです。

if ($hour < 12) { print '午前です'; }

基本形は、if ( 条件 ) { 条件があてはまったときの命令 } となります。
上の例では、$hour が 12未満だったときに、「午前です」と表示されます。

このとき、空白を詰めて書いたり、改行を入れて書いても構いません。

if($hour<12){print'午前です'}

if ($hour < 12) {
  print '午前中です';
}

できるだけ見やすく書くといいでしょう。

午前と表示するなら、ついでに午後も表示するようにしましょう。

if ($hour < 12) {
  print '午前です';
} else {
  print '午後です';
}

else は、必ず if とセットで使います。
if の条件にあてはまらなかったときに行うことを else の後の { } の間に書きます。
この場合は、$hour が 12未満のときには「午前です」、それ以外のときは「午後です」と表示されます。

ただ、上のプログラムには欠陥があるのがわかるでしょうか?

もし、$hour が -4 という数値だったらどうでしょうか?
あるいは、$hour が 125 とかいう大きな数値だったらどうでしょうか?

$hour が -4 のときは、12未満ですので「午前です」と、
$hour が 125 のときは、12未満ではないので「午後です」と表示されてしまいます。

そこで改良版はこうなります。

if ($hour >= 0 && $hour < 12) {
  print '午前です';
} elsif ($hour >= 12 && $hour < 24) {
  print '午後です';
} else {
  print '正しい時刻ではありません';
}

条件が複雑になり、途中に elsif が入りました。

elsif も if とセットで使い、if の条件にあてはまらなかった場合に、別の条件を用意して、それがあてはまれば直後の { } の中を実行します。

上の場合は、$hour が 0 以上 12 未満のときに、「午前です」と表示して、
$hour が 12 以上 24 未満のときに、「午後です」と表示して、
さらに、if の条件も elsif の条件にもあてはまらなかった場合に「正しい時刻ではありません」と表示します。

ここまでは大丈夫でしょうか? さらに複雑になります。

もし、$hour が 数値ではなくて文字だったらどうなるでしょうか?
仮に $hour = 'コロッケ' だったとすると、$hour >= 0 があてはまってしまいます。
「コロッケ」を無理やり数字とみなした場合、0 となるからです。

そこで、$hour が数字じゃなかった場合に備えて、さらに改良します。

$hour = 3;

if ($hour eq '' || $hour == 0 && $hour ne '0') {
  print '数字を入力してください';
} else {
  if ($hour >= 0 && $hour < 12) {
    print '午前です';
  } elsif ($hour >= 12 && $hour < 24) {
    print '午後です';
  } else {
    print '正しい時刻ではありません';
  }
}

大きく if と else で囲んで、その else の中に、さきほどまでの if が入っています。
このように、if や elsif else の中にさらに if elsif else を入れることもできます。
上の例では、まず $hour に何も入っていないか、$hour が 0 だけど $hour が '0' ではない時に「数字を入力してください」と表示され、それ以外のときに午前か午後かを調べています。

$hour eq '' は、$hour という変数に何も入っていなければ…、という条件です。
$hour == 0 は、$hour が文字のときか 0 のときにあてはまりますので、さらに、$hour ne '0' で 0 ではないとき、という条件を追加して、この2つで$hour が 文字であるとき、という条件になっています。

条件の中の || と && では && を優先しますので、$hour が空欄か、あるいは、文字のとき、という条件になります。

$hour = 3; の部分を $hour = 15; や $hour = -5; や $hour = 'コロッケ'; などいろいろ変えて、試してみましょう。


リサイクルの心がけ…サブルーチンの話

2009-06-01 15:07:37 | ルールを覚えよう

プログラムを作っていくと、何度も同じことをしなくてはならないことがあります。
プログラムのはじめのほうで出てきた一連のやりとりが、プログラムの中盤にも終盤にも出てきたりして、そのたびに同じコードをコピーした結果、あちこちに同じ一連のコードがあるという状況はけっして良くありません。もし、その部分を変更しなくてはいけなくなったときに、何か所も直さなくてはいけなくなりますから、のちのち不便になります。
そこで、サブルーチンの出番です。
これは、一連のコードをまとめておくプログラムの固まりです。

「起きる」
「シャワーを浴びる」
「朝ごはんを食べる」
「仕事する」
「昼ごはんを食べる」
「仕事する」
「夕ごはんを食べる」
「シャワーを浴びる」
「寝る」
(一日の終わり)

こんなプログラムがあったとき、「シャワーを浴びる」と「仕事をする」が2か所ずつあります。おなじものは一緒にしてしまおうというのがサブルーチンです。

「起きる」
→サブルーチンAへ
「朝ごはんを食べる」
→サブルーチンBへ
「昼ごはんを食べる」
→サブルーチンBへ
「夕ごはんを食べる」
→サブルーチンAへ
「寝る」
(一日の終わり)

サブルーチンA「シャワーを浴びる」
サブルーチンB「仕事する」

メインの流れとは別に、離れたところにまとめて置いておいて、メインのほうにはサブルーチンを見るようにと指示するわけです。

実際には次のようになります。

$x = 1;
$y = 2;
&tashizan;
$x = 3;
$y = 4;
&tashizan;
exit;

sub tashizan {
  print $x + $y;
}

まず、$x に 1、$y に 2 を入れます。
次にサブルーチン tashizan を実行します。
サブルーチン tashizan は $x と $y を足したものを表示します。
サブルーチンが終わったら前のところに戻ります。
今度は、$x に 3、$y に 4 を入れます。
そして、もう一度、サブルーチン tashizan を実行して、
exit でプログラムを終了させます。

サブルーチンは sub tashizan { } の部分です。
sub につづけて、お好みのサブルーチンの名前をつけます。
サブルーチン使うときには、&とサブルーチンの名前という形になります。


サブルーチンに何らかの値を渡したい場合があります。
たとえば、先程の例でいえば…

「起きる」
→サブルーチンAへ
→サブルーチンC(朝)へ
→サブルーチンBへ
→サブルーチンC(昼)へ
→サブルーチンBへ
→サブルーチンC(夕)へ
→サブルーチンAへ
「寝る」
(一日の終わり)

サブルーチンA「シャワーを浴びる」
サブルーチンB「仕事する」
サブルーチンC「ごはんを食べる」(時間帯)

このように「ごはんを食べる」でひとつにまとめて、メインのほうでは朝昼夕の時間帯だけを指定すると楽なわけです。

このような感じをプログラムにしてみると次のようになります。

$x = 2;
$y = 3;
$z = &kakezan($x, $y);
$z = &kakezan($z, $y);
print $z;
exit;

sub kakezan {
  my ($a, $b) = @_;
  my $c = $a * $b;
  return $c;
}

サブルーチン kakezan では、受け取った2つの数字をかけた答えを返します。

$x に 2、$y に 3 を入れ、kakezan に渡します。
戻ってきた答えが $z に入ります。
次に、$z と $y をもう一度 kakezan に渡し、その答えを $z に入れます。
結果は、2 * 3 * 3 ですから、18 になります。

&kakezan( ) と書き、( ) の中にサブルーチンに渡したいデータを、(複数ある場合は)カンマ区切りで入れます。

サブルーチンのほうでは、my ($a, $b) = @_; という書き方でデータを受け取って、return で返しています。
@_ という特殊な配列に、受け渡されたデータが入っています。
配列ですから、配列の1番目は $_[0] ですし、2番目は $_[1] と表現できます。
つまり、次のような書き方もできます。

my ($a, $b) = @_;

my $a = $_[0];
my $b = $_[1];

my $a = shift @_;
my $b = shift @_;

my $a = shift;
my $b = shift;

上の4種類はどれも同じです。
shift は、配列の先頭からデータを1つ取り出す命令です。
この時、特殊な配列 @_ は省略できるというルールがPerlにはあるので、単に shift で受け取れるわけです。
筆者もはじめて こういう使い方をする shift を見たときには「ナンデ?」って思いました。

$a、$b の前についている my は、この変数はこのサブルーチンの中でしか使いません、ということを主張しています。もし、このサブルーチン以外で $a や $b を使っていた場合、それは別の変数として扱うことになります。
したがって、わかりやすいように $a や $b としましたが、$a が $y で $b が $x であってもなにも問題は起こりません。my さえついていれば、サブルーチンの外と中とで同じ変数名が使え、別のものとして扱ってくれるわけです。

ちなみに、サブルーチンへ受け渡すデータ、サブルーチンで受け取るデータのことを、プログラム業界では、「引数(ひきすう)」と言います。
サブルーチンから返ってくるデータを「戻り値(もどりち)」と言います。

また、サブルーチンのことを、「ユーザー定義関数」と言ったり、単に「関数」と言う人もいます。


print 人間へのメッセージ

2009-05-31 14:53:38 | 命令を覚えよう

Perlでは、print には主に2つの意味があります。
ひとつは画面に表示すること、もうひとつはファイルに書き込むことです。

print "画面に表示します";

open(DAT, ">>test.dat");
print DAT "ファイルに書き込みます";
close(DAT);

いずれの場合も考え方は一緒で、コンピュータの内部で行った結果を、人間が確認できるように、画面上もしくはファイル上へ出力するわけです。

画面に表示するほうの print は、表示したい内容を print の後ろに書きます。

$x = '桜';
$menu{'Aランチ'} = '野菜炒め定食';
print 1 + 2;
print "$xは春に花が咲きます";
print "今日の昼ご飯は$menu{'Aランチ'}です";
print '$xは変数です';
print $x . 'の花は淡いピンクです';

上の例では、「3」「桜は春に花が咲きます」「今日の昼ご飯は野菜炒め定食です」「$xは変数です」「桜の花は淡いピンクです」と表示されます。

print の後ろに計算式があれば、計算した結果を表示します。
" " で囲んだ場合は、変数があればその中身に置き換えて表示します。
' ' で囲んだ場合は、そのまま表示します。
 . で変数とほかの文字とをつなぐことができます。


print <<__HTML__;
<a href="http://www.goo.ne.jp/">goo</a><br>
gooのトップページはこちら<br>
__HTML__

上の例は、print <<__HTML__; の次の行から、 __HTML__ の手前までをそのまま表示します。(ヒアドキュメントという)
__HTML__の部分はアルファベットや数字で自由に変えられます。

print <<goo;
<a href="http://www.goo.ne.jp/">goo</a><br>
gooのトップページはこちら<br>
goo

極端な話、上のようにしても大丈夫です。
print <<goo; のあとに、単独で goo が出てくるところまでを表示してくれるわけです。
ただ、この極端な例ではさすがに紛らわしいので、普通は大文字のアルファベットを用いるのが慣例となっています。
EOF や EOS や _HTML_ などを使う人が多いようです。


ファイルに書き込むほうの print は、また別の機会で。


わかれ道を作ってこそ…条件の話

2009-05-30 12:05:02 | ルールを覚えよう

プログラムが最初から最後まで1本道だったら、いつも同じ答えが出てしまい、応用がききません。
やっぱり、プログラムにはいろいろな場合場合によって、状況を変えるわかれ道が必要で、そうでなくてはなりません。

Perlでわかれ道を作るには次のようにします。

if ($hour == 6) { print "おはようございます"; }

if は もし~なら という英語で、if の後に条件があって、もし条件に合えば、{ } の中を実行し、もし条件に合わなければ、{ } は無視して次に進むようになっています。

上の例では、もし $hour という変数が 6 だった場合のみ、「おはようございます」と表示され、6 以外だった場合は、何も表示されないという意味になります。

== は 数字を比較するときに使う特殊な記号(比較演算子)で、同じかどうかを調べます。

if ($hour != 6) { print "6時ではありません"; }

!= も 数字を比較するときに使う特殊記号で、違うかどうかを調べます。
上の例では、$hour が 6 ではないときに、{ } の中を実行します。

if ($hour < 6) { print "まだ6時になってません"; }
if ($hour > 6) { print "もう6時は過ぎました"; }
if ($hour <= 6) { print "6時か6時前ですね"; }
if ($hour >= 6) { print "6時以降ですね"; }

不等号を使って比較することもできます。


文字を比較するときには別の記号を使います。

if ($shumi eq 'マラソン') { print "何km走れますか?"; }
if ($shumi ne 'マラソン') { print "走るのは嫌いですか?"; }

eq は 文字が同じかどうかを調べます。英語のイコール(equal)を略したんでしょうね。
ne は 文字が違うかどうかを調べます。not equal の略でしょう。


さらに、2つ以上の条件を使うには次のようにします。

if ($hour >= 6 && $hour <= 9) { print "おはようございます"; }
if ($hour <= 4 || $hour >= 18) { print "こんばんは"; }

$hour が 6時から9時の間のときに、「おはようございます」を表示します。
$hour が 4時より前、あるいは 18時以降のときに、「こんばんは」を表示します。

条件と条件の間に && を入れることで、両方の条件を満たすかどうかを、
同様に間に || を入れることで、どちらかの条件を満たすかどうかを調べます。

これらを使って複雑な条件を作ることができます。

if ($hour >= 6 && $hour <= 8 && ($shumi eq 'マラソン' || $shumi eq 'ジョギング')) { print "さあ、朝のランニングに行きましょう"; }

6時から8時の間で、かつ、趣味がマラソンかジョギングのときに表示されます。

このとき、優先度に注意してください。
>= や <= や eq が最優先で、次に && 最後に || という順番があります
上の場合、2番目の && よりも マラソンとジョギングをつなぐ || を優先したいので、( ) でくくっています。

もし、カッコがなかった場合は、
 6時から8時の間でかつ趣味がマラソン、あるいは、趣味がジョギング
という条件になってしまいます。

 


小数の四捨五入と丸め誤差の話

2009-05-29 15:43:59 | テクニックを身につけよう

print int((0.1 + 0.7) * 10);

上の答えはいくつになるでしょうか?
int( ) は カッコ内の数値の整数部分だけを取り出す命令です。

0.1 + 0.7 = 0.8 ですから、それを10倍して 8 となり、その整数部分といっても小数はありませんから、答えは 8 となりそうです。

ところが実際計算してみると、7 と表示されてしまいます。おかしな話です。

これはコンピュータの内部ではデータを2進数で扱っている、ということに原因があります。2進数とは 0 と 1 の2つの数字だけを使って表現する方法。われわれ人間は普通は 0 から 9 までの数字を使いますから 10進数 です。

1 は 2進数では 1 のままですが、2 は 2進数では 10 となります。
3 は 2進数では 11 となり、4 は 2進数では 100 となります。

では、小数はどうなるでしょう。

0.5 が 2進数では 0.1 です。0.25 が 2進数では 0.01 となります。
0.125 は 2進数では 0.001 となり、0.0625 が 2進数では 0.0001 となります。

計算過程は省きますが、10進数の 0.1 は 2進数では 0.0001100110011…という循環小数となってしまいます。0.7 は 2進数では 0.101100110011…という循環小数です。

この2つを足すということは、10進数でいうところの 0.3333… + 0.6666… に似ています。極限まで計算すれば正しい答えが得られますが、コンピュータはある程度の桁数で計算をやめてしまいます。
0.3333… + 0.6666… が 0.9999… となって 1 にならないように、0.1 + 0.7 は 2進数で計算すると、わずかに 0.8 に足りない数字となってしまうのです。
これを「丸め誤差」といいます。

では、どうすればいいのでしょうか。

sprintf(  ) を使って 数値を文字にしてしまう方法があります。

$x = sprintf("%.0f", 1.001);
$y = sprintf("%.0f", 0.999);

上の式は、両方とも 1 という文字になります。

$x = sprintf("%.1f", 1.001);
$y = sprintf("%.1f", 0.999);

上の式は、両方とも 1.0 という文字になります。

したがって、最初の式 int((0.1 + 0.7) * 10) を正しく計算するには、

$x = sprintf("%.0f", (0.1 + 0.7) * 10);
print $x;

とすれば 正しく 8 という答えが出ます。


四捨五入の場合も、この丸め誤差を考慮にいれておく必要があります。

Perlには round関数がありませんので、通常は次のようになります。

$y = int($x * 10 + 0.5) / 10;

小数点以下第2位を四捨五入して、小数点以下1桁に丸めているわけですが、ここでさきほどの「丸め誤差」が出る場合があります。

そこで次のようにします。

$x = 0.34999;
$y = int(sprintf("%.1f", $x * 10 + 0.5)) / 10;

$x の値が実際は 0.35 であるはずなのに、わずかに誤差が出ていると仮定します。
$x * 10 + 0.5 は 3.9999 になります。
それを sprintf( ) で 4.0 にしてその整数部分 4 を 10で割ると、答えは 0.4 になります。

ただし、$x が 0.349 という数値で、それが誤差ではなく正しいものだった場合、上の例ですとうまくいきません。
その場合は、$y = int(sprintf("%.2f", $x * 10 + 0.5)) / 10; と sprintf での桁数を変えます("%.2f" の 2)。
$x に正しい数値 0.3499 がある場合は %.3f にするわけです。


※ もしかしたら 最後にさらに $y = sprintf("%.1f", $y); を加える必要があるかも…?(10で割った数値に誤差が出るから?)


以上の場合は、小数点以下1桁に丸めたわけですが、小数点以下2桁にする場合はこうです。

$y = int(sprintf("%.1f", $x * 100 + 0.5)) / 100;

* 10 や / 10 の部分を * 100 と / 100 にすればいいわけです。


おまけ: モジュール Math::Round というものもあるそうなので、使用できる環境のかたは、これを使ったほうが手っ取り早いかも。