続・はじめてのPerl 11章
11章 オブジェクト入門
11.1 動物と話すことができたら
use strict; use warnings; sub Cow::speak { print "a Cow goes moooo!\n"; } sub Horse::speak { print "a Horse goes neigh!\n"; } sub Sheep::speak { print "a Sheep goes baaah!\n"; } my @pasture = qw/ Cow Cow Horse Sheep Sheep /; { no strict 'refs'; for my $beast ( @pasture ) { &{$beast . '::speak'}; # シンボリックコードレフ } }
シンボリックコードレフは読みにくい 他の方法はないのか?
11.2 メソッド呼び出しの矢印
Classとは 同じような動作と特性を持つものの集まり Classパッケージ内のmethodサブルーチンを呼び出す方法 Class->method メソッドとは サブルーチンのオブジェクト指向バージョン
# メソッド呼び出しの矢印を使う
Cow->speak;
Horse->speak;
Sheep->speak;
# さらに以下のように書ける my @pasture = qw/ Cow Cow Horse Sheep Sheep /; for my $beast ( @pasture ) { # シンボリックコードレフを使う必要がなくなった $beast->speak; }
OOPの原則 共通コードを最小限に減らすこと コーディング量の削減による時間の節約 テストとデバッグの効率化による時間の節約
11.3 メソッド呼び出しの特別な引数
矢印を使った、メソッド呼び出し Class->method( @args ); これは、以下のようにClass::method を呼び出すこととなる Class:method( 'Class', @args ); 第一引数として、クラス名を受け取る @argsが指定されていない場合、クラス名だけを引数として受け取る
# 第一引数を使うことで、以下のようにメソッドを定義できる use strict; use warnings; sub Cow::speak { my $class = shift; print "a $class goes moooo!\n"; } sub Horse::speak { my $class = shift; print "a $class goes neigh!\n"; } sub Sheep::speak { my $class = shift; print "a $class goes baaah!\n"; } my @pasture = qw/ Cow Cow Horse Sheep Sheep /; for my $beast ( @pasture ) { $beast->speak; }
11.4 第二のメソッドを呼び出して話を単純化する
前節のコードは、同じ構造のコードを多数含む どのように単純化するべきか
# use strict; { # Animalクラスを作成 # speakメソッドを定義する package Animal; sub speak { my $class = shift; print "a $class goes ", $class->sound, "!\n"; } } { package Cow; @ISA = qw/ Animal /; sub sound { 'moooo' } } Cow->speak;
Cow->speak; の処理の流れ 1. 引数リストを作る 上記の場合、Cowのみ 2. Cow::speakを探す しかし、Cowにはspeakメソッドはない 3. 継承配列の@Cow::ISAをチェックする @Cow::ISAは実在し、Animalという名前を持つ 4. Animalの中に、speakメソッド(Animal::speak)があるかチェックする speakを見つけることができる 5. 引数リストを使い、メソッドを呼び出す 以下のコードを実行することと同じことになる Animal::speak( 'Cow' ); 6. Animal::speakメソッド内で、$classにCowが格納される 7. $class->sound の呼び出しで、Cow->soundが呼び出される 8. "a Cow goes moooo!" という出力が得られる
11.5 @ISAについての注意
@ISA 「イズア」と発音 Cow is a Animal ということを宣言する 実際には、言語学用語 複数の親クラスを検索することがありえるので、配列 Animalも@ISAを持っている場合、それも検索対象 検索は、再帰的で深さ優先 @ISAの左の要素から右の要素へ検索が行われる 一般に@ISA配列の要素は1つだけ 多重継承がされている場合、複数になる use strictを有効にすると、@ISAについて警告される @ISAが、明示的なパッケージ名を含む変数でなく、レキシカル変数でもないため @ISAは、レキシカル変数にすることは不可能 継承メカニズムにより見つけられるパッケージに属していなければならない @ISAの宣言と設定方法 最も簡単な方法:パッケージ名を書く @Cow::ISA = qw/ Animal /; Perl5.6以降では、our宣言を使うことができる package Cow; our @ISA = qw/ Animal /; 暗黙に名前を与えたパッケージ変数として扱う package Cow; use vars qw/ @ISA /; @ISA = qw/ Animal /; これは、use base を使うことで以下のように書ける package Cow; use base qw/ Animal /; use baseはコンパイル時に実行される
11.6 メソッドのオーバーライド
use strict; { # Animalクラスを作成 # speakメソッドを定義する package Animal; sub speak { my $class = shift; print "a $class goes ", $class->sound, "!\n"; } } { package Mouse; use base qw/ Animal /; sub sound { 'squeak' } sub speak { my $class = shift; print "a $class goes ", $class->sound, "!\n"; print "[but you can barely hear it!]\n"; } } Mouse->speak;
Mouseは独自のspeakメソッドを持っている Animalクラスのspeakメソッドを呼び出すわけではない これをオーバーライドという オーバーライド 基底クラス(Animal)のメソッドの代わりとなる専用のメソッドを持っている場合に、 派生クラス(Mouse)のメソッドを呼び出すために使われる しかし、上記のMouse::speakは、Animal::speakのコードと同じ箇所がある Animal::speakに変更が発生した場合、 Mouse::speakにも手を加えなければならない可能性がある OOPの原則 コードの再利用はコピペではなく、継承で行う
# よりOOPらしいコードに use strict; { package Animal; sub speak { my $class = shift; print "a $class goes ", $class->sound, "!\n"; } } { package Mouse; use base qw/ Animal /; sub sound { 'squeak' } sub speak { my $class = shift; Animal::speak( $class ); # 矢印によるメソッド呼び出しではない!! print "[but you can barely hear it!]\n"; } } Mouse->speak;
矢印によるメソッド呼び出しを使わない理由 矢印を使うと、Animal::speakの第一引数は、"Animal"になってしまう soundメソッドを呼び出すときに、オブジェクトに合ったメソッドが呼び出せない しかし、Animal::speakを直接呼び出すと、また別の問題が起こる 矢印によるメソッド呼び出しを使わなかった場合に起こる問題 継承関係の解決が行われない 呼び出そうとしたメソッドが継承されたメソッドだった場合、プログラムは異常終了する よって、上記のコードの方法は正しくないやり方
11.7 異なる場所からの検索の開始
use strict; { package Animal; sub speak { my $class = shift; print "a $class goes ", $class->sound, "!\n"; } } { package Mouse; use base qw/ Animal /; sub sound { 'squeak' } sub speak { my $class = shift; $class->Animal::speak( @_ ); print "[but you can barely hear it!]\n"; } } Mouse->speak;
上記のコードは正しく動作する Animalでspeakを探す 見つからない場合は、Animalの継承チェーンをたどりspeakを探す 第一引数は、$classとなる この方法の問題点 @ISAと最初に検索するパッケージを同じものにする手作業が入る @ISAに複数のエントリがある場合、どちらがspeakを定義しているのか不明な場合がある
11.8 SUPERなやり方
use strict; { package Animal; sub speak { my $class = shift; print "a $class goes ", $class->sound, "!\n"; } } { package Mouse; use base qw/ Animal /; sub sound { 'squeak' } sub speak { my $class = shift; $class->SUPER::speak( @_ ); # SUPERクラスを使う print "[but you can barely hear it!]\n"; } } Mouse->speak;
Animalクラスを、SUPERクラスに書き換えることで、 @INCにエントリーされているクラスから、speakが検索されるようになる @INCの中で複数のspeakが定義されている場合 最初に見つかったものが呼び出される
11.9 @_の処理
親クラスの動作に何か処理を追加している場合 @_を引数として渡すべき $class->SUPER::speak( @_ ); 親クラスの動作を正確に制御するような場合 明示的に引数リストを見定めて、渡すべき
- 作者: Randal L. Schwartz,brian d foy,Tom Phoenix,吉川英興,伊藤直也,田中慎司,株式会社ロングテール/長尾高弘
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2006/10/21
- メディア: 大型本
- 購入: 9人 クリック: 389回
- この商品を含むブログ (99件) を見る
無名ハッシュコンストラクタのブレースの前に+を付けないといけないケース
気になったので調べてみた。
perlrefを見れば一発だった。
そもそも+を付けるって何
中かっこは、BLOCK を始めとして他のことにも使われますから、 開きの中かっこが BLOCK の開始でないことを Perl に教える ために文の最初の中かっこの前に + や return をつけて、 曖昧さをなくすようにする必要がある場合があります。
perlref - Perlのリファレンスとネストしたデータ構造 - perldoc.jp
どんなときに+を付けないといけないのか
perlrefのサンプルそのままなんだけど一応。
# sample.pl use strict; use warnings; use Data::Dumper; sub hashem { { @_ }; # ハッシュリファレンスを返したい } print Dumper hashem( qw/ a 1 b 2 / );
$ perl sample.pl $VAR1 = 'a'; $VAR2 = '1'; $VAR3 = 'b'; $VAR4 = '2';
ハッシュリファレンスを返すつもりが、配列が返される。
では、+を付けて実行すると
use strict; use warnings; use Data::Dumper; sub hashem { +{ @_ }; # ハッシュリファレンスを返したい } print Dumper hashem( qw/ a 1 b 2 / );
$ perl sample.pl $VAR1 = { 'a' => '1', 'b' => '2' };
想定通りに、ハッシュリファレンスが返される。
なるほど、納得!
続・はじめてのPerl 10章
10章 大規模なプログラムの構築
10.2 evalによるコードの挿入
# Example.pm sub println { my $message = shift; print "$message\n"; }
# sample.pl sub load_common_subroutin { my $pm_file = shift; open my $code_fh, $pm_file or die $!; undef $/; my $code = <$code_fh>; close $code_fh; eval $code; # $code内に構文エラーが含まれている場合、 # $@に適切なエラーメッセージが格納される die $@ if $@; } load_common_subroutin( 'Example.pm' ); println( "Hello world!" );
10.3 doを使う方法
10.2 のsample.plはdoを使うことで、以下のようにかける
do 'Example.pm'; die $@ if $@; println( "Hello world!" );
do演算子 現在のプログラムに、Example.pmのコードをインクルードするように動作する インクルードされたファイル内のレキシカル変数とuse strictなどのディレクティブは、 メインプログラムに影響を与えない
10.4 requireを使う方法
以下のように、Example.pmをインクルードするExample2.pmが定義する
# Example2.pm do 'Example.pm'; die $@ if $@; sub printlist { my @list = @_; for ( @list ) { println( $_ ); } }
そして、sample.plを以下のように修正する
# sample.pl do 'Example.pm'; die $@ if $@; do 'Example2.pm'; die $@ if $@; println( "■ コーヒー豆一覧" ); my @list = qw/ アメリカンブレンド ブルマンブレンド マイルドブレンド モカブレンド /; printlist( @list );
sample.plを実行すると、Perlは、println()を2回定義してしまう。
警告を有効にしている場合、サブルーチンの再定義を行ったと警告を受けてしまう。
$ perl sample.pl Subroutine println redefined at Example.pm line 3. ■ コーヒー豆一覧 アメリカンブレンド ブルマンブレンド マイルドブレンド モカブレンド
ファイルをインクルードしたのか、してないのか管理する仕組みが必要 Perlでは、require演算子でその仕組みを提供する require演算子 Perlga読み出したファイルを%INCを使い管理する 一度ファイルの処理に成功すると、同じファイルに対するrequire処理を無視する 読み込むファイルに構文エラーが含まれている場合、プログラムは異常終了する do演算子を津型場合の、"die $@ if $@;"が不要になる ファイル内で最後に評価される式は、真の値を返さなければならない これが、requireで評価されるファイルのコードの最終行に1;が含まれる理由 真の値を返すルール インクルードしたファイルから呼び出し元に対し、 コードの処理に成功して、エラー条件が発生してないことを知らせる手段だった しかし、実際は、die if ... という方法が使われるようになったため、 このエラー通知は歴史的な理由で残っている煩わしい習慣になってしまった
requireを使い、Example.pm Example2.pm sample.pl を書き直すと以下のようになる
# Example.pm sub println { my $message = shift; print "$message\n"; } 1;
require 'Example.pm'; sub printlist { my @list = @_; for ( @list ) { println( $_ ); } } 1;
# sample.pl require 'Example.pm'; require 'Example2.pm'; println( "■ コーヒー豆一覧" ); my @list = qw/ アメリカンブレンド ブルマンブレンド マイルドブレンド モカブレンド /; printlist( @list );
10.5 requireと@INC
これまでの方法は、プログラムとライブラリが同じディレクトリにあり、 そのディレクトリからプログラムを実行するという条件のときのみうまく動作する ライブラリがカレントディレクトリにない場合 Perlはサーチパスに沿ってライブラリを検索する サーチパス 特殊変数@INCに格納された要素のリスト デフォルトでは、以下のディレクトリが含まれる ・カレントディレクトリ ・Perlコンパイル時に指定したディレクトリ @INCの確認方法 ・perl -V で出力される内容の最後の方 ・perl -le "print for @INC"
10.5.1 @INCの拡張
マシンのメンテナンス責任者でもない限り、 @INCに設定されたディレクトリにモジュールのインストールはできない require文が実行される前に、@INCを書き換えることで、 モジュールを読み込むディレクトリを追加できる unshift @INC, '/home/tyamaguc07/perl-lib'; unshiftすることで、Perlはまず、/home/tyamaguc07/perl-libからモジュールを探す これにより、ファイル間の名前の衝突が起こる場合、 /home/tyamaguc07/perl-libにあるファイルが優先される @INCの拡張は、他の何より早く行いたい BEGINブロックを使う BEGIN { unshift @INC, '/home/tyamaguc07/perl-lib' }; BEGINブロックを使わない場合 requireをする前にunshiftを配置しなければならない この処理はよく行われる Perlはそのためのプラグマ "use lib" を用意している use lib コンパイル時に実行される @INCに対して、指定したディレクトリをunshiftする use lib qw( /home/tyamaguc07/perl-lib ); 標準モジュール FindBinを使うことで、移植性を高める use FindBin qw( $Bin ); とすることで、 $Binにはスクリプトが格納しているディレクトリのパスが格納される スクリプトディレクトリのサブディレクトリにライブラリがある場合、 以下のように、パスを組み立てることができる use FindBin qw( $Bin ); use lib "$Bin/lib";
10.5.2 PERL5LIBを使った@INCの拡張
環境変数 PERL5LIB にプライベートライブラリのディレクトリ名を設定する csh系の場合 setenv PERL5LIB /home/tyamaguc07/perl-lib bsh系の場合 PERL5LIB=/home/tyamaguc07/perl-lib; export PERL5LIB PERL5LIBは"個人用"には便利
10.6 名前空間の衝突の問題
複数のライブラリで、同じサブルーチン名でサブルーチンが定義されている場合、 先にrequireされたライブラリのサブルーチンは、 後からrequireされたモジュールのサブルーチンで再定義されてしまう サブルーチンだけではなく、各種変数にも起こりえる 再定義を防ぐ方法 各種変数、サブルーチン名に一意になるようなプレフィックスを付ける この方法は、変数名が長くなるなどあまり美しくない
10.7 名前空間のセパレータとしてのパッケージ
プレフィックスを付ける代わりに、packageを使う
# Example.pm package Example; # パッケージ宣言 sub printlist { my @list = @_; for ( @list ) { println( $_ ); } } sub println { my $message = shift; print "$message\n"; } 1;
package宣言 ファイル内のほとんどの名前の前に、Example::を挿入することと実質的に同じ意味 上記のコードは以下のような意味となる
# Example.pm sub Example::println { my @list = @_; for ( @list ) { println( $_ ); } } sub Example::printlist { my $message = shift; print "$message\n"; } 1;
このライブラリを使うとき... ライブラリ内で定義されたサブルーチンにはExample::を付けアクセスする 自身で定義したサブルーチンには、何も付けずにアクセスする
# sample.pl require 'Example.pm'; sub printhash { my %hash = @_; Example::printlist( map { "$_ : $hash{$_}" } sort keys %hash ); } printhash( 'アメリカンブレンド' => 420, 'ブルマンブレンド' => 700, 'マイルドブレンド' => 420, 'モカブレンド' => 420, );
パッケージ名 変数名と似ている ・英数字と下線を使うことはできる ・先頭を数字にすることはできない perlmodlibで説明されている理由から ・パッケージ名の先頭文字は大文字 ・既存のCPANモジュールやコアモジュール名と重複しないようにする 二重のコロンで区切って複数の名前を入れることも可能 例えば、Example::Print ほとんどの変数名やサブルーチン名には、 カレントパッケージの名前がプレフィックスとして付けられる ただし、名前に二重コロンが含まれない場合 パッケージ内で定義された変数にも、 メインプログラムでパッケージ名を付けることでアクセスできる すべてのプログラムは、mainという名前のパッケージに含まれる ファイルの冒頭にpackage main; という文を記述した場合と同じ
10.8 packageディレクティブのスコープ
すべてのファイルの冒頭には、package main; と書かれていると考えることができる packageディレクティブは、次のpackageディレクティブまで有効 ただし、次のpackageディレクティブが、ブレースの中にある場合は異なる スコープの終了とともに、元のpackageディレクティブが有効になる カレントパッケージは、レキシカルスコープ
package Example; { package main; # ここからパッケージはmain sub printhash { ... } } # パッケージはExampleに戻る sub println { ... }
10.9 パッケージとレキシカル変数
パッケージ変数は常にグローバル変数 完全名を知っていれば、いつでも参照可能 レキシカル変数には、カレントパッケージのプレフィックスは付かない レキシカル変数にアクセスしたい場合、パッケージプレフィックスを付けない パッケージ変数にアクセスしたい場合、パッケージプレフィックスを付ける
# Example.pm package Example; @list = qw/ 1 2 3 4 /; sub printlist { my @list = qw/ a b c d /; print map { "$_\n" } @list; # レキシカル変数を参照 print map { "$_\n" } @Example::list; # パッケージ変数を参照 } sub list { return @list; # パッケージ変数を参照 } 1;
# sample.pl require 'Example.pm'; Example::printlist(); print Example::list(); =comment 実行すると以下のような出力が得られる perl sample.pl a b c d 1 2 3 4 1234
感想
細かい仕様については把握出来ていなかったな。
知ることができてよかった。
そして、11章が楽しみ。
- 作者: Randal L. Schwartz,brian d foy,Tom Phoenix,吉川英興,伊藤直也,田中慎司,株式会社ロングテール/長尾高弘
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2006/10/21
- メディア: 大型本
- 購入: 9人 クリック: 389回
- この商品を含むブログ (99件) を見る
最新脳科学が教える 高校生の勉強法 読了
- 作者: 池谷裕二
- 出版社/メーカー: ナガセ
- 発売日: 2002/04/01
- メディア: 単行本(ソフトカバー)
- 購入: 17人 クリック: 281回
- この商品を含むブログ (64件) を見る
人の記憶の仕組みから始まり、
高校生となるあたりから訪れる得意とする記憶のメカニズムの変化。
そして、それに対応するために、どのように勉強を行うのが効率的なのか
分かりやすく説明されていおり、高校生の頃の自分に読ませたい一冊でした。
なお、高校生の勉強法となってはいますが、
本書によると、高校生の脳は大人の脳と同じとのことなので、
大学生でも社会人でもきっと役に立つと思います。
最後に、これはと思った箇所をいくつか。
第2章 脳のうまいダマし方 2-4 がむしゃらだけでは報われない 54p
もっとも効率的な復習プラン
「学習した翌日に、一回目」
「その一週間後に、二回目」
「二回目の復習から二週間後に、三回目」
「三回目の復習から一ヶ月後に、四回目」
第3章 海馬とLTP 3-5 危機的学習法 86p
夕食前の空腹の時間こそ学習の格好の時間帯
第4章 ファジーな脳 4-8 まずは得意科目を伸ばそう 123p
ある分野の理解の仕方を覚えると、
他の分野に対する理解の仕方までが上達する
...
ひとつの科目を集中して勉強して、まずはそれを究めてしまう
個人的な反省点
読書のやり方を改善しないといけないと思った。
本書内のコラムでも指摘されているのだが、
目だけを使うのではなく、手も使うようにして読書していきたいと思う。
GitHub事始め
アカウントを作る
"Plans, Prining ans Signup"をクリック
"Create a free account"をクリック
ユーザ名、メールアドレス、パスワードを入力し、"Create an account"をクリック
Gitセットアップ for Windows
以下のページを見ながら
Set up Git - GitHub Help
Gitのインストール
Google Code Archive - Long-term storage for Google Code Project Hosting.
上記のページから、Git-1.7.4-preview20110204.exe をダウンロード
Git-1.7.4-preview20110204.exe インストール
設定はデフォルトのままでインストール
SSHキーの設定
GitHub上で、"Account Settings"クリック
"SSH Public Keys"クリック
"Add another public key"クリック
公開鍵の入力画面が出るので、コピペして"Add key"クリック
接続テスト
Git Bashから以下のコマンドを実行
$ ssh git@github.com
問題がなければ、以下のような出力が得られる
Enter passphrase for key '/c/Documents and Settings/***/.ssh/id_rsa': Hi tyamaguc07! You've successfully authenticated, but GitHub does not provide shell access. Connection to github.com closed.
usernameとemailの設定
以下のコマンドを実行
$ git config --global user.name "Firstname Lastname" $ git config --global user.email "your_email@youremail.com"
user.nameは、実際の名前でないといけないっぽい
GitHub tokenの設定
Account Settingページの、"Account Admin"をクリック
API Tokenの文字列をコピー
以下のコマンドを実行
$ git config --global github.user tyamaguc07 $ git config --global github.token 0123456789yourf0123456789token
リポジトリ作成
"Dashboard"をクリック
"New Repository"をクリック
"Project Name" "Description" "Homepage URL"を入力
"Create Repository"をクリック
感想
思っていたより簡単だった。
gist.githubを使う以外では、embed用のscriptタグとかは出力できないのかな?
インクリメントのベンチマーク
Perlは、プリインクリメントとポストインクリメントに性能差が殆ど無いと、
初めてのPerlか、Perlベストプラクティスかに書いてあった気がした。
実際どうなのか、前から気になっていたのでベンチマークを取ってみた。
ベンチマークスクリプト
use Benchmark qw( cmpthese ); my ( $num, $assign ) = (); sub pre_increment { $num = shift; ++$num; } sub post_increment { $num = shift; $num++; } sub pre_inc_assign { $num = shift; $assign = ++$num; } sub post_inc_assign { $num = shift; $assign = $num++; } cmpthese( 10_000_000, { pre => sub { pre_increment( 1 ) }, post => sub { post_increment( 1 ) }, pre_assign => sub { pre_inc_assign( 1 ) }, post_assign => sub { post_inc_assign( 1 ) }, });
結果
Rate post_assign pre_assign pre post post_assign 2247191/s -- -16% -28% -32% pre_assign 2688172/s 20% -- -14% -19% pre 3115265/s 39% 16% -- -6% post 3300330/s 47% 23% 6% -- Rate post_assign pre_assign pre post post_assign 1865672/s -- -23% -23% -30% pre_assign 2427184/s 30% -- -0% -9% pre 2433090/s 30% 0% -- -9% post 2673797/s 43% 10% 10% -- Rate post_assign post pre_assign pre post_assign 2100840/s -- -12% -16% -23% post 2386635/s 14% -- -5% -12% pre_assign 2500000/s 19% 5% -- -8% pre 2717391/s 29% 14% 9% -- Rate post post_assign pre_assign pre post 2232143/s -- -10% -15% -19% post_assign 2481390/s 11% -- -5% -10% pre_assign 2610966/s 17% 5% -- -5% pre 2754821/s 23% 11% 6% -- Rate post_assign pre_assign pre post post_assign 2127660/s -- -13% -24% -25% pre_assign 2457002/s 15% -- -13% -13% pre 2808989/s 32% 14% -- -1% post 2832861/s 33% 15% 1% --
まとめ
ぶっちゃけ大差ない。
それでもやはり遅いのは変数代入を行うポストインクリメントかなという印象。
変数代入を行わない場合は、プリインクリメントでもポストインクリメントでも本当に変わらない。
きっとコンパイル時に、最適化されてるんだろう。
続・はじめてのPerl 9章
9章 リファレンスを使った実践的なテクニック
9.1 ソートとは
sort演算子 デフォルトの動作は、テキスト本来の順序付け 現在のロケールと、文字セットで決まるソート順 詳細は、perllocale 他のソート順でソートしたい場合 ソートアルゴリズムを書く必要はない 2個のアイテムを処理するコードを書くだけでいい 2個のアイテムを処理するコード sortキーワードと対象配列の間にソートブロックを指定 ソートブロック内で、新しい比較方法を指定する ソートブロックの代わりに、名前付きサブルーチンを指定することも可能 ソートブロック内での比較方法 比較する2つのアイテムは、$aと$bに格納されて渡される ソートブロックは、ソート順を示す値を返さなければならない ・返す値が -1 $aが$bより前に配置されるようにしたい場合 ・返す値が +1 $bが$aより前に配置されるようにしたい場合 ・返す値が 0 どちらの順番でもかまわない場合 0が返された場合 最近のPerlでは、$aと$bの順番は、元のリストの順番が維持される これを安定ソートという 安定ソートかどうかは、バージョンに依存する 将来のPerlでは、安定ソートではないかもしれない
my @array = qw/ 4 3 2 43 53 13 1 /; my @new_array = (); # $aと$bを使ったソートブロック @new_array = sort { if ( $a < $b ) { -1; } elsif ( $a > $b ) { 1; } else { 0; } } @array; print "@new_array\n"; # 1 2 3 4 13 43 53 # スペースシップ演算子を使う @new_array = sort { $a <=> $b } @array; print "@new_array\n"; # 1 2 3 4 13 43 53 # 降順ソート @new_array = reverse sort { $a <=> $b } @array; print "@new_array\n"; # 53 43 13 4 3 2 1 # 降順ソート、もう一つのやり方 @new_array = sort { $b <=> $a } @array; print "@new_array\n"; # 53 43 13 4 3 2 1 # ソートブロックの代わりにサブルーチンを使う sub asc_order { $a <=> $b; } @new_array = sort asc_order @array; print "@new_array\n"; # 1 2 3 4 13 43 53
9.2 添え字を使ったソート
my @input = qw( Gilligan Skipper Professor Ginger Mary_Ann ); # @sorted_positions には、元の要素を指す添え字が入る # 並び順は、元の要素を文字列で昇順ソートしたときのもの my @sorted_positions = sort { $input[$a] cmp $input[$b] } 0 .. $#input; print "@sorted_positions\n"; # 0 3 4 2 1 print "@input[ @sorted_positions ]\n"; # Gilligan Ginger Mary_Ann Professor Skipper # @input の要素が、ソート後何番目に来るのかを求める my @rank = (); @rank[ @sorted_positions ] = ( 0 .. $#sorted_positions ); print "@rank\n"; # 0 4 3 1 2
9.3 効率のよいソート
my %age = ( myself => 26, brother => 20, mother => 50, father => 50, ); sub get_age { print "return $_ age\n"; return $age{$_}; } # 若い順に名前を並べる my @name = sort { get_age( $a ) <=> get_age( $b ) } keys %age; print "@name\n"; __END__ 実行すると以下のような出力が得られる return: father age return: brother age return: mother age return: myself age return: brother age return: myself age return: myself age return: father age return: father age return: mother age brother myself father mother
サブルーチンが何度も呼ばれているのが分かる サブルーチンの処理コストが高い場合、これは問題となりえる これを効率よく処理するためには、以下のように処理する
# 名前と年齢を値にもつ配列リファレンスの配列を作る my @name_and_age = map { [ $_, get_age( $_ ) ] } keys %age; # @name_and_age を使い若い順に並び替える my @asc_name_and_age = sort { $a->[1] <=> $b->[1] } @name_and_age; # @asc_name_and_age から名前を取り出す my @name = map { $_->[0] } @asc_name_and_age; print "@name\n"; __END__ 実行すると以下のような出力が得られる return: father age return: brother age return: mother age return: myself age brother myself father mother
サブルーチンの呼び出し回数が減ったことが確認できる
9.4 シュワルツ変換
「9.3 効率のよいソート」のコードにおける、 @name_and_age @asc_name_and_age は、@name を求めるための中間変数 以下のようにステップをまとめる事で、変数名を考える必要がなくなる このようなものを一般に、「シュワルツ変換」と呼ぶ
my @name = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [ $_, get_age( $_ ) ] } keys %age;
9.5 シュワルツ変換を使ったマルチレベルソート
シュワルツ変換が役に立つ場合 複数の基準に基づいてソートしなければならない場合
my %age = ( myself => 26, brother => 20, mother => 50, father => 50, ); my %sex = ( myself => 'man', brother => 'man', mother => 'woman', father => 'man', ); my %sex_dv = ( woman => 1, man => 2, ); sub get_age { my $name = shift; return $age{$name}; } sub get_sex_dv { my $name = shift; return $sex_dv{ $sex{$name} }; } # sex_dvの値と、年齢で昇順ソート my @name = map { $_->[0] } sort { $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] } map { [ $_, get_sex_dv( $_ ), get_age( $_ ) ] } keys %age; print "@name\n"; # mother brother myself father
9.6 再帰的に定義されたデータ
再帰的なアルゴリズム 基本ケースからスタート その上に構造を積み重ねていく 基本ケースとは もっとも単純な条件で何をするか 例えば、 ・葉ノードが枝を持たないとき ・配列が空のとき ・カウンタが0のとき 基本ケースを持たない再帰アルゴリズムは無限ループとなる 再帰サブルーチン 自分自身を呼び出す部分と、基本ケースを呼び出す部分を持つ
# 階乗計算。最も単純な再帰関数の1つ sub factorial { my $num = shift; if ( $num <= 1 ) { return 1; } else { return $num * factorial( $num - 1 ); } } print factorial( 3 ), "\n"; # 6 print factorial( 5 ), "\n"; # 120
9.7 再帰的に定義されたデータの構築
# 指定したパスのディレクトリ構造をハッシュに落とし込むサンプル use Data::Dumper; sub data_for_path { my $path = shift; # ファイルまたは、シンボリックリンクの場合 if ( -f $path or -l $path ) { return undef; } if ( -d $path ) { my %directory = (); opendir my $dir, $path or die "Cannot opendir $path: $!"; my @names = readdir $dir; closedir $dir; for my $name ( @names ) { next if $name eq '.' or $name eq '..'; $directory{$name} = data_for_path( "$path/$name" ); } return \%directory; } warn "$path is neither a file not a directory\n"; return undef; } my $directory = data_for_path( '../' ); print Dumper $directory;
感想
頭の中で処理の流れを追えなくなるので再帰は苦手。
場数こなせば、得意になるのかな。