Perl入学式2013年度第5回補講・参加レポート vol.6(calc_string答え合わせ編)

おつかれさまです。今日も多忙ですが、昨日の内容がやや宙吊りな感じで終わりましたので、キリのよいところまで更新してしまいます。

昨日の記事はこちら。
Perl入学式2013年度第5回補講・参加レポート vol.5(whileなど) - the code to rock

練習問題「calc_string」に対するマイ回答を載せたところで終わっていました。
簡単にさらいますと、問題はこれ。

  • 引数として与えられた文字列が, 数値A 演算子 数値Bという文字列であれば, その値を計算して, 結果を返すような関数calc_stringを書いてみましょう
    • 「数値A」は任意の桁の正・負の整数とします. また, 演算子は+-*/%が使えるものとします.
    • 但し, 引数が与えられなかった場合(空の文字列の場合)は, undefを返します
    • また, 数値A 演算子 数値Bというフォーマットと一致しない場合もundefを返します
  • 関数calc_stringとwhile文を使って, Ctrlキーとdキーを押すまでの間標準入力から文字列を受け取り, 文字列に書かれた式を計算するようなコードを書いてみましょう

で、私の最終的な回答は、こちら。

#!/usr/bin/env perl
use strict;
use warnings;

while (my $input =<STDIN>) {
    print calc_string($input)."\n";
}

sub calc_string {
    my $str = shift;
    unless ($str =~ /^(-?\d+) (.) (-?\d+)$/) {
        return "Error!";
    } else {
        if ($2 eq '+') {
            return $1 + $3;
        } elsif ($2 eq '-') {
            return $1 - $3;
        } elsif ($2 eq '*') {
            return $1 * $3;
        } elsif ($2 eq '/') {
            return $1 / $3;
        } elsif ($2 eq '%') {
            return $1 % $3;
        }
    }
}

はい。実際の昨日の記事では、書き込みを促すところと、エラーメッセージがもうちょっとコチャコチャ書いてありましたが、この後の校長の回答とつき合わせづらくなるので、そこだけシンプルに変えてます。

で、その校長の回答ですが、まずは問題の前者、while文挿入の前までが、こんなふうでした。

#!/usr/bin/env perl
use strict;
use warnings;

sub calc_string {
    my $str = shift;
    if ($str =~ /^(-?\d+) ([\+\-\*\/%]) (-?\d+)$/) {
        my ($l, $op, $r) = ($1, $2, $3);
        return $l + $r if $op eq '+';
        return $l - $r if $op eq '-';
        return $l * $r if $op eq '*';
        return $l / $r if $op eq '/';
        return $l % $r if $op eq '%';
    }
    return undef;
}

my $answer = calc_string('10 / 2');
if (defined $answer) {
    print $answer."\n";
} else {
    print "!!! ERROR !!!\n";
}

はい。ええと、いろいろ異なるポイントはありますが・・まず一番大きな違いは、サブルーチンの中で「後置if」を使っていることですね。
考えてみたら、この練習問題の前に思いきり後置ifを扱ってるので、そこをしっかり念頭に置いておかなければいけなかったな・・と思いましたが。たしかに、これがあるとかなりスッキリしますね・・

それから、最初のif文(正規表現のやつ)にマッチしないものについては、elseでつなげるのでなく、もう単にif文の外に出して、そのままundefをreturnされています。
これについては、校長も最初はelseで繋げて、その中に「return undef;」と書かれていたのですが、その省略形のような感じで、こちらの方がいいかな、という感じでこのようになっていました。ちょっとすぐには、これは思いつかないですね・・面白いです。

んで、終盤で「my $answer〜」から始まる一連の流れがありますが、ここは最初は、シンプルに

print calc_string('10 / 2');

というだけだったのですが、サポーターさんから、「この練習問題から考えると、undefの返し方も少しケアしたほうがいいのでは」というツッコミが入り、それに対応する形でこのようになっています。
なるほど・・
でもやっぱり、「undefを返す」というのは、ここでは「それ用のエラーメッセージを作って出力させる」というふうにしてますね。僕としてはこれでまたひとつ、得るものがあった気がします。

さて、そのまま続けて、じゃあwhile文&標準入力を入れるとどうなるか、というと、これはその「my $answer」以降を、このように囲むだけでした。

while (chomp(my $input = <STDIN>)) {
	...(処理)...
}

そうか、考えたら僕の回答では標準入力したやつをchompしてなかったですね・・しかもこんな風に1行にまとめられるんだ。

と、ここでまた新たにサポーターさんからのツッコミ。「0で割ったらどうなるんですか?」と。
で、それに対しては「割れません(笑)」と。実際、ここでたとえばサブルーチンの引数を「3 / 0」などとすると、

Illegal division by zero at なんとかかんとか

というふうに出てきます。

で、「もしどうしても、それに対してもちゃんとundefを返したいって言うなら・・」ということで、そこまで含めて諸々組み込んだ全部入りの校長の解答が以下になります。

#!/usr/bin/env perl
use strict;
use warnings;

sub calc_string {
    my $str = shift;
    if ($str =~ /^(-?\d+) ([\+\-\*\/%]) (-?\d+)$/) {
        my ($l, $op, $r) = ($1, $2, $3);
        return $l + $r if $op eq '+';
        return $l - $r if $op eq '-';
        return $l * $r if $op eq '*';
        if ($op eq '/') {
            return undef if $r == 0;
            return $l / $r if $op eq '/';
        }
        return $l % $r if $op eq '%';
    }
    return undef;
}

while (chomp(my $input = <STDIN>)) {
    my $answer = calc_string($input);
    if (defined $answer) {
        print $answer."\n";
    } else {
        print "!!! ERROR !!!\n";
    }
}

はい。割り算のところ、ゼロじゃなかった場合はもう一回演算子の内容を聞いてるんですね・・ぼくの直感的には、こんな感じかな、とも思ったのですが。

if ($op eq '/') {
    return undef if $r == 0;
    return $l / $r if $r != 0;
}

でも、どちらでも動くようです。

その他、正規表現での演算子の扱いも違ったり、あと校長は特殊変数$1などを別の変数に代入していますが、それも僕の方ではとくにやっていなかったり、いろいろ違うのがやっぱり面白いです。

特殊変数については、念のためそのまま($l, $op, $rをそれぞれ$1, $2, $3のままで)この校長の解答の上で実行してみたのですが、それはそれで動くみたいですが・・スタイルとしてはやはり別の変数に代入しておいた方が綺麗なのでしょうかね。

ということで、当日の会場はここで一旦休憩に入りつつ、その間にeval講座が開かれたりしたのですが(大変興味深い)、ここではそれは割愛し(笑)、次回は資料の以下、後半戦の「package/名前空間」の方に入っていきたいと思います。
https://github.com/perl-entrance-org/workshop-2013-05/blob/master/slide.md#package--%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93

以上です!