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

Perl入学式2013年度第5回補講(2014/01/11)・参加レポート vol.2

おつかれさまです。昨日の続きです。
昨日の内容はこちらです。
Perl入学式2013年度第5回補講(2014/01/11)・参加レポート vol.1 - the code to rock

まだ本題には入っておらず、今回も入らない予定ですが、前回ぼくが作った回答に対して、果たして模範解答はどうだったかを見ていきたいと思います。

calc.pl-1

まず1問目。問題はこれで、

  • 1. calc.pl

2つの数値を引数として, add (+), sub (-), mul (*), div (/), mod (%) といった key に演算結果を代入し, そのハッシュリファレンスを返り値とするような関数 calc を作成してください.

僕の回答はこれでした。

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

sub calc {
    my ($l, $r) = @_;
    my $result = {
        add => $l + $r,
        sub => $l - $r,
        mul => $l * $r,
        div => $l / $r,
        mod => $l % $r,
    }
}
p calc(3, 5);

実行結果等は、前回の内容をご参照ください。→ Click!!

で、模範回答は最初、このように書かれました。

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

sub calc {
    my ($left, $right) = @_;
	
    my $add = $left + $right;
    my $sub = $left - $right;
    my $mul = $left * $right;
    my $div = $left / $right;
    my $mod = $left % $right;
	
    return {
        add => $add,
        sub => $sub,
        mul => $mul,
        div => $div,
        mod => $mod,
    };
}

はい。まずこの時点でぼくと違うのは、計算内容を$addなどの変数に代入していること。あと、僕のようにわざわざハッシュリファレンスのための変数は作らずに、ハッシュリファレンスをそのままreturnしちゃってるところですね。

とくに後者については「そうなんだ〜」という新鮮な感覚を持ちました。(たぶん今までに見たことがない例)

とはいえ、この前者の変数への代入は、説明のためにあえて冗長に書いたものということで、それにちょっと手を入れたのがこれ。

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

sub calc {
    my ($left, $right) = @_;
	
    return {
        add => $left + $right,
        sub => $left - $right,
        mul => $left * $right,
        div => $left / $right,
        mod => $left % $right,
    };
}

こうなるとほぼ僕のと同じで、上述のreturnのところだけ違う感じです。
んで、最後に

use DDP { deparse => 1 };
p calc(10, 3);

というのを加えて実行すると・・(DDPの後に付いているのはよく分かりませんが、メモしたのでそのまま使う)

\ {
    add   13,
    div   3.33333333333333,
    mod   1,
    mul   30,
    sub   7
}

こんな風になりました。ここまではなんとか付いていってます。

calc.pl-2

次の問題。

  • 2. calc.pl

上記で作成した関数における引数が数字であるかどうか正規表現を使って判定するように改良してください.
数値以外が引数であった場合は undef を返すようにしてください.

僕の回答はこれでした。

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

sub calc {
    my ($l, $r) = @_;
    unless ($l =~ /-?\d+/ && $r =~ /-?\d+/) {
	print "ERROR!!\n";
    } else {
	my $result = {
            add => $l + $r,
            sub => $l - $r,
            mul => $l * $r,
            div => $l / $r,
            mod => $l % $r,
	}
    }
}
p calc(3, 5);
p calc('z', 5);
p calc(3, 's');
p calc('t', 'i');

対して、校長の回答はこのようでした。

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

sub calc {
    my ($left, $right) = @_;

    if ($left =~ /-?\d+/ && $left =~ /-?\d+/) {
        return {
	    add => $left + $right,
	    sub => $left - $right,
	    mul => $left * $right,
	    div => $left / $right,
	    mod => $left % $right,
	};
    } else {
        return undef;
    }
}

use DDP { deparse => 1 };
p calc('a', 3);

最後の行、サブルーチンの引数の「10」だったものを「'a'」にして、実行すると・・

undef

おお・・綺麗に「undef」って返ってますね。これ以上文句なしに回答することはできないんじゃないか、というぐらい綺麗な「undef」です。

さて、ここであらためて、ぼくの昨日の回答内容に戻りますと、同じ引数('a', 3)で実行した場合、こんなのが返ってきます。

ERROR!!
1

はい。上記の「undef」に比べると目も当てられない感じがしますが、そして何よりこの「1」ってなんだよ・・という話だったのですが、校長のと見比べると、校長が「return」を使っているところで、ぼくは「print」を使っているのが結構大きな違いであるようです。
加えて言うと、"ERROR!!"の後に改行も入っています。

よって、「print」を「return」に、そして改行も外して実行してみると・・

"ERROR!!"

ふうむ・・。まあさっきよりイイ感じですが、やっぱり問題で「undef返せ」と言われたら、たんに「undef」返したいですね。
ということで、以下のように書き換えました。

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

sub calc {
    my ($l, $r) = @_;
    unless ($l =~ /-?\d+/ && $r =~ /-?\d+/) {
	return undef;
    } else {
	my $result = {
            add => $l + $r,
            sub => $l - $r,
            mul => $l * $r,
            div => $l / $r,
            mod => $l % $r,
	};
	return $result;
    }
}
p calc(3, 5);
p calc('z', 5);
p calc(3, 's');
p calc('t', 'i');

実行結果は・・

\ {
    add   8,
    div   0.6,
    mod   3,
    mul   15,
    sub   -2
}
undef
undef
undef

ひゅ〜。(アメリカ映画風に)
いいですね。undefの返り方が仲の良い3姉妹のようにこの上なく揃っています。少なくとも僕はいいと思いますね。

しかし昨日やったときは、どうもreturn文が(一応試しはしていたんですが)上手く動かなかったんだよな・・それで仕方なくprint文にしたのですが、やっぱり基本、サブルーチンの結果はprintで戻しちゃいけないよ、ということに尽きるのでしょうか。少なくとも僕はそう思いましたね。

food.pl

では復習問題の最後。設問はこちら。

  • 3. food.pl
my $data = q{
papix : sushi
moznion : soba
boolfool : sushi
macopy : sushi
};

food.pl では, 上記のように, $data に人物名と好きな食べ物を (スペース):(スペース) 区切りで与えています.
この変数から食べ物が何回出現したかカウントして表示させてください.

  • はじめに $data を改行ごとに split するとよいでしょう
  • 分割後, 正規表現により必要な文字列を抽出してください

僕の回答はこちら。

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

my $data = q{
papix : sushi
moznion : soba
boolfool : sushi
macopy : sushi
};

my @data = split /\n/, $data;
my %food;
for my $data2 (@data) {
    if ($data2 =~ /^(\w+) : (\w+)$/) {
        $food{$2}++;
    }
}
p %food;

ちなみに、ここで校長から「q{ }」について補足がありました。上記の$dataは以下のように書き換えられるのだと。

my $data = "papix : sushi\nmoznion : soba\nboolfool : sushi\nmacopy : sushi";

その上で、出てきた模範回答はこちら。

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

my $data = "papix : sushi\nmoznion : soba\nboolfool : sushi\nmacopy : sushi";

my @data = split /\n/, $data;
use DDP { deparse => 1 };

my %favorite_food;
for my $d (@data) {
    if ($d =~ /(\w+)$/) {
        $favorite_food{$1}++;
    }
}

p %favorite_food;

はい。細かいところはちょこちょこ違いますが、おおむね僕のと同じでしょうか。
あえて言うと、たしかにこの用途から言ったら、僕みたいに人名まで抽出する必要はなくて、food名だけ取り出せば充分でしたね。

ということで、昨日の記事へのアンサー(文字通りアンサー)記事は以上です。

余談ですが、今回、校長の模範回答発表時に、iPhoneの動画撮影機能でずーっとスクリーンを撮っていたのですが、これは思いのほか役立ちました。
やっぱり現場では、「模範回答を書き写すこと」と「それを咀嚼・理解すること」を同時にやるのはかなり難しく(僕には)、そもそも書き写すだけすら時間的にけっこう無理があるので、こうしてきっちり記録しておく(それも負担の少ない方法で)というのはいいかもしれません。

同様の意味をもつ方策として、校長なりの模範解答を作ってくださった方のエディタ内容をどこかに(Gistとかに)UPして共有して頂きたい、みたいな話はこれまでにも何度か目にしたり、自分でも言ったりした気はしますが、それはそれでやって頂けたらありがたいのだとしても、でもその方向でお任せしすぎてしまうと運営サイドの負担が増えるスパイラルに入ってしまうような懸念もなくはなく、基本的には生徒サイドでこのように対応できる部分に関しては積極的に(サポーターをサポートする的な)やってみるというのがいい気はします。

生徒有志でヴィデオを回す、というのもいいかもしれませんし、もし公式に撮影するのであればそれはもう閲覧有料、というぐらいでも良いかもな気すらします。(そのぐらい役立つので/べつに有料課金が最適解というつもりもないですが)

では次回から、ようやく第5回の講義内容に入っていきたいと思います。