読者です 読者をやめる 読者になる 読者になる

Perl入学式in東京第5回練習問題よりcalc_string.pl

前々回に予告しました通り、今日はこの問題。
workshop-2013-05/slide.md at master · perl-entrance-org/workshop-2013-05 · GitHub

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

大きく2問ありますが、1問目のやつがすごいハマったというか、時間かかったですね・・

ええ、順番に行きますが。とりあえず、

引数として与えられた文字列が, 数値A 演算子 数値Bという文字列であれば, その値を計算して, 結果を返すような関数calc_stringを書いてみましょう

ということで、こんなのを書いてみました。

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

sub calc_string {
    my $str = shift;
    if ($str =~ /^(\d+)\s([\+\-\*\/%])\s(\d+)$/) {
        print "$1$2$3\n";
    } else {
        print "undef\n";
    }
}
calc_string(5 + 6);

実行すると、こんな。

undef

ふむ・・「11」が出るはずなんだけどな・・としばらく考えつつ、こんなのも試してみたりしたんですが、

sub calc_string {
    my ($a, $b, $c) = @_;
    if ($a =~ /\d+/ && $b =~ /[\+\-\*\/\%]/ && $c =~ /\d+/) {
        print "$a $b $c\n";
    } else {
        print "undef\n";
    }
}
calc_string(5 + 6);

まったく見当違いだったようで、実行するとこんな・・

Use of uninitialized value $b in pattern match (m//) at calc_string.pl line 7.
undef

すいません、すいません! て感じですが。

でもまずは何より、上記はいずれもサブルーチンの引数「5 + 6」をクォートで囲っていないので、if文に入れる前に「11」になってしまっていて、問題では「引数として与えられた文字列が」となっているけど、引数の時点で文字列じゃない状況になってる、ということにかなり経ってから気づきました。

なので、最初のトライしたのをあらためてシングルクォートで囲ってみて、

sub calc_string {
    my $str = shift;
    if ($str =~ /^(\d+)\s([\+\-\*\/%])\s(\d+)$/) {
        print "$1$2$3\n";
    } else {
        print "undef\n";
    }
}
calc_string('5 + 6');

実行!

5+6

・・・なんか違う。というかまったく違う。

ん〜、もしかして、そしたら引数がこうか?

calc_string('5', '+', '6');

あるいは、こうか?

calc_string('5' + '6');

とかやるんですが、これらで実行すると「undef」で。

おかしい・・もうこうなると、そもそもの「引数を文字列として与える」という文意自体が分からなくなってきて・・「文字列」とはそもそも何なのか・・とか樹海に入っていく感じで。
でもたぶん、問題文の例では、

「数値A 演算子 数値B」という文字列であれば

と明記されているので、やっぱり

calc_string('5 + 6');

でいいはずだ、というところまでは思って。もうそういう仮定の上で続けることにして。

そうなると、現状の戻りが「5+6」になってるのを、いかにして「11」にするかっていう話なんですが、上記の

sub calc_string {
    my $str = shift;
    if ($str =~ /^(\d+)\s([\+\-\*\/%])\s(\d+)$/) {
        print "$1$2$3\n";

だと、ようは「5」も「+」も「6」も、数値でもなければ数値演算子でもない何か(文字列)になってしまっているのだろうと、それで計算されないんだろうと、そう仮定しまして。

であれば、5と6は数値で、+は数値演算子であると認識させればいいんだろう、ということになるわけですが、ええと、それってどうすればいいんだ? ということでここからクチャクチャいろいろ試行錯誤して、ものすごいダサい感じはするんだけど、とりあえずこんなのを作りまして。

sub calc_string {
    my $str = shift;
    if ($str =~ /^(\d+)\s([\+\-\*\/%])\s(\d+)$/) {
        if ($2 == '+') {
            print "$1 + $3\n";
        } else {
            print "undef\n";
        }
    }
}
calc_string('5 + 6');

まずは何が何でも「11」を出そうと。「5+6」ではなく。
他の演算子については足し算がクリア出来てから対応するとして。みたいな。
でも実行すると、こんな言われて。

Argument "+" isn't numeric in numeric eq (==) at 11.pl line 8.
Argument "+" isn't numeric in numeric eq (==) at 11.pl line 8.
5 + 6

ああ、「+」は数値じゃないから「==」じゃなくて「eq」使いなさいってことかな〜、と思ってそこを書き換えて実行すると、

5 + 6

戻ってる〜・・一つ前よりはいいけど。
ん〜・・なんで演算されないで文字列のまま出てきてしまうんだ・・考えろ考えろ・・というかしかし、そもそも「+」とかの数値演算子って文字列の一種なんだろうか、それとも数値の一種なんだろうか・・とか考え始めると余計混乱してきたりして。

でもその中でたまたま、一旦スカラ変数(「$1」と「$3」及びそれを使った演算部分)をダブルクォーテーションの外に出して、こんな風にしてみたら・・

sub calc_string {
    my $str = shift;
    if ($str =~ /^(\d+)\s([\+\-\*\/%])\s(\d+)$/) {
        if ($2 eq '+') {
            print $1 + $3."\n";
        } else {
            print "undef\n";
        }
    }
}
calc_string('5 + 6');

実行。

11

おーー、できた〜!!
という感じで。どうも変数をクォートで囲っていたら、それがダブルクォーテーションでも数値としては展開されない(文字列として展開される?)、ということなのかなあ、よくわからないが、ともかく外に出したら数値として演算されてくれたので、そのまま他の演算にも適応させて、こんな。

sub calc_string {
    my $str = shift;
    if ($str =~ /^(\d+)\s([\+\-\*\/\%])\s(\d+)$/) {
        if ($2 eq '+') {
            print $1 + $3."\n";
        } elsif ($2 eq '-') {
            print $1 - $3."\n";
        } elsif ($2 eq '*') {
            print $1 * $3."\n";
        } elsif ($2 eq '/') {
            print $1 / $3."\n";
        } elsif ($2 eq '%') {
            print $1 % $3."\n";
        }
    } else {
        print "undef\n";
    }
}
calc_string('5 + 6');

かなり野暮ったい気もしますが・・まあ、第1問目の回答はようやくこんな感じで。相当すでに息切れしていますが。

んで、そのまま第2問の方もやると、これは結構あっさり目に、こんな感じになりました。
#ここまでをまとめてGistに上げた。

はい。どうでしょうか。

ということで、とりいそぎ更新しておきたかった内容はここまでで一区切りかなあ、という感じなので、今後はまた、これまでにあまり触れていないPerl入学式の練習問題や復習問題を一個ずつ扱っていければと思っています。

とりあえず、一つ戻って第4回の内容(たとえば練習問題で模範解答と自分の回答との突き合わせ)に行くか、同じ第5回の内容や練習問題を頭からやっていってみるか、という感じでしょうかね。