-
会場について
- 飲食・喫煙・トイレetc
-
写真撮影について
-
写真撮影NGな方はお手数ですが申し出てください
-
写真はPerl普及団体の JPA ( Japan Perl Association )への活動報告に利用します
-
- 講師・サポーター紹介
-
せっかく今日集まったので, テーブルで自己紹介をしましょう.
-
話題は自由ですが, 以下がオススメです
-
名前(ハンドルネーム)
-
なぜPerlを勉強してみようと思ったか
-
なぜPerl入学式に参加してみようと思ったか
-
- 前回の復習
- ハッシュ
- ハッシュの操作( keys, delete, exists )
- 正規表現
- 正規表現と置換
- 正規表現とメタ文字
- 正規表現のオプション
- 2019年度 第2回の練習問題からセレクト
ハッシュはPerlのデータ構造の1つで、配列と同じく要素の格納・取り出しができます。
ただし、配列と異なり 名前(key) と 値(value) のペアで格納されます。
このペアのことを 要素 と呼びます。
ハッシュはシジル %
を使って定義します。
my %hash = (
name => 'Larry', # 名前(key)はname, 値(value)は'Larry'
birth => 1954, # 名前(key)はbirth, 値(value)は 1954
);
名前(key)と 値(value)の間にある =>
はファットコンマ演算子と呼ばれ、コンマと同等の役割を果たします。
my %hash = ( 'name' , 'Larry', 'birth' , 1954, );
このようにコンマに置き換えても動きますが、配列と見分け難くなります。ハッシュではファットコンマ演算子を使いましょう。
名前(key)は文字列として解釈されます。
my %hash = (
name => 'Larry',
birth => 1954,
);
上記の例のように、名前(key)である name
と birth
は、シングルクォート ' '
やダブルクオート '" "' で囲む必要はありません。
1つのハッシュ内の値(value)に文字列や数値が混在しても構いません。
最後の要素の末尾に ,
があってもなくても構いません。しかし、追加や変更の可能性をふまえて付けることをお勧めします。
ハッシュの要素にアクセスし、value(値)を取り出してみましょう。
my %hash = (
name => 'Larry',
birth => 1954,
);
print "$hash{name}\n"; # Larry
print "$hash{birth}\n"; # 1954
ハッシュの value(値)を取り出すときは、配列と同様に添字を使います。
配列では角括弧 [ ]
を使いましたが、ハッシュでは波括弧 { }
を使います。
波括弧 { }
に名前(key)を入れることで、対応する値(value)を取り出すことができます。
ハッシュに要素を追加してみましょう。
my %hash = (
name => 'Larry',
birth => 1954,
);
$hash{lang} = 'Perl'; # 名前(key)が 'lang', 値(value)が 'Perl' の要素を追加
print "$hash{lang}\n"; # Perl
追加は取り出すときと同様に、{key}
を使います。ハッシュの中身は以下のようになります。
name => 'Larry',
birth => 1954,
lang => 'Perl',
既に存在する名前(key)の値を代入すると、上書きされます。
配列は名前(key)で値(value)にアクセスできるため、格納順番に左右されることがありません。これは配列との大きな違いであり利点です。
配列を例に説明します。
my @user = ( 'Name', 'Job', 'lang' );
print "$user[1]\n" # Job
もし突然、要素の順番が入れ替わってしまったらどうなるでしょう?
my @user = ( 'lang', 'Name', 'Job' );
print "$user[1]\n" # Name
同じ添字 [1]
でも、配列の中身が変わっているため、同じ結果にはなりません。
しかしハッシュであれば、値は添字の数値(順番)ではなく、名前(key)で対応づけられています。 このため、ハッシュ内部の要素の格納順に影響を受けません。
my %hash = (
lang => 'Perl',
birth => 1954,
name => 'Larry',
);
print "$hash{name}\n"; # name が表示される => "Larry"
print "$hash{birth}\n"; # birth が表示される => 1954
print "$hash{lang}\n"; # lang が表示される => "Perl"
- 質問: ハッシュの中身を全部一度に見たい場合はどうするの?ダブルクォーテーションで囲っても、変数展開できない!
my %hash = (
name => 'Larry',
birth => 1954,
lang => 'Perl',
);
print "%hash\n"; # %hash 変数名がそのまま表示される
- 回答:
Data::Dumper
モジュールを使います。これについては第4回で解説します。
use Data::Dumper; # チラ見せ
print Dumper \%hash; # ハッシュリファレンス(第4回で説明)
次の処理をする hash_profile.pl
を作りましょう。
-
以下の人物のプロフィールを
%larry_profile
に格納してください。- 名前(name) : Larry Wall
- 誕生(birth) : 1954
- 言語(lang) : Perl
-
key である
name
、birth
、lang
を使って、それぞれの value を出力してください。 -
時間に余裕のある人は、Larry のプロフィールを充実させましょう。
ハッシュを便利に扱うための関数について説明します。
-
keys
- ハッシュの名前(key)の集合を返す。
-
delete
- ハッシュの要素を削除する。
-
exists
- ハッシュの要素が存在するかしないかを返す。
keys
関数はハッシュの名前(key)を配列にして返します。
my %hash = (
name => 'Larry',
birth => 1954,
lang => 'Perl',
);
my @keys = keys %hash;
print "@keys\n"; # birth name lang (順不同)
ただし、この keys
は名前(key)を 順不同、順番が不定 で返します。
ハッシュに書かれた順番で返ってくるとは限りません。
名前(key)を同じ順番で受け取りたい場合は、 sort
関数を使って並び替えます。
my %hash = (
name => 'Larry',
birth => 1954,
lang => 'Perl',
);
my @keys = keys %hash; # この時点では順不同
my @sorted = sort @keys; # sort で並び替える
print "@sorted\n"; # birth lang name (常にこの順番)
値のみを順不同で受け取る values
関数もありますが、Perl入学式のカリキュラムでは使いません。
Perl入門ゼミ values関数 - ハッシュのすべての値の取得
delete
関数は、指定したハッシュの名前(key)と、それに対応する値(value)を削除します。
my %hash = (
name => 'Larry',
birth => 1954,
lang => 'Perl',
);
delete $hash{lang}; # lang という名前(key)を指定して削除
print "$hash{lang}\n";
この例では、最後の行で削除した名前(key)に対応する値(value)を表示しようとしています。
このとき、しっかり「おまじない」を書いていれば、存在しないキーをprintしようとしている、と警告してくれます。
exists
関数は、指定したハッシュの名前(key)が存在するか確認します。
my %hash = (
name => 'Larry',
birth => 1954,
lang => 'Perl',
);
if ( exists $hash{name} ) { print "exists\n" } # exists
if ( exists $hash{foo} ) { print "exists\n" } # 何も出てこない
-
名前(key)が存在すれば
1
(真)を返します。 -
名前(key)が存在しなければ
' '
(空文字、偽)を返します。
ハッシュの名前(key)は文字列が入ったスカラー変数でも指定可能です。
my %hash = (
name => 'Larry',
birth => 1954,
lang => 'Perl',
);
my $key = 'lang';
print $hash{$key}; # Perl
-
{foo}
であれば foo という文字列が名前(key)となります。 -
{$foo}
であればスカラー変数$foo
に代入された文字列が名前(key)となります。
keys
関数は配列を返します。これをfor文と組み合わせて、ハッシュのすべての要素を処理することができます。
my %hash = (
name => 'Larry',
birth => 1954,
lang => 'Perl',
);
for my $key ( keys %hash ) {
my $value = $hash{$key};
print "$key is $value\n";
}
どのような結果になるでしょうか?
次の処理をする hash_func.pl
を作りましょう。以下のハッシュをコピペして利用してください。
my %hash = (
name => 'Larry',
birth => 1954,
lang => 'Perl',
);
-
以下の要素を追加してください。
- 名前(key): software
- 値(value): patch
-
keys
関数を使って,%hash
の名前(key)をすべて出力してください。 -
delete
関数を使って, 1で使ったハッシュから birth の要素を削除してください。
練習問題(1/2)で作成した hash_func.pl
を利用します。
exists
関数を使って、name
, birth
, lang
, software
の各要素が存在するか確認してください。名前(key)
は各要素のkey名が入るものとします。
-
存在している場合は
名前(key) is exist.
と表示する。 -
存在しない場合は
名前(key) is not exist.
と表示しする。
- ここでは, データ処理の強い味方「正規表現」を取り上げます.
- 正規表現を使うことで, 文字列を自由自在に検出したり, 置き換えたりすることができます.
- 正規表現は非常に複雑なので(正規表現だけで分厚い1冊の技術書が書けるほどです), Perl入学式で全てを紹介することはできませんが, コードを書く上でよく使う「基本的な部分」を中心に, 紹介していきます.
my $str = 'Larry loves perl!';
if ($str =~ /perl/) {
print "'$str'は'perl'を含みます.";
}
$str =~ /perl/
は,$str
の中に「perl」という文字列が含まれるなら真, そうでないなら(含まれないなら)偽, になります.- この,
/
に囲まれた, 文字列のパターンを表現するものが「正規表現」です.
my $str = 'Larry loves perl!';
if ($str eq 'perl') {
print "'$str'は'perl'です.";
}
if ($str =~ /perl/) {
print "'$str'は'perl'を含みます.";
}
eq
は完全一致か否かしか判定できません. しかし正規表現とパターンマッチを活用することで, 「xxxという文字列を含む」や, その逆の「xxxという文字列を含まない」といった複雑な判定を行うことができます.
my $str = 'Larry loves perl!';
if ($str !~ /ruby/) {
print "'$str'は'ruby'を含みません.";
}
$str !~ /ruby/
と書くことで,$str
の中に「ruby」という文字列を含まないなら真, そうでないなら(含むなら)偽, になります.
my $str = 'perl ruby python';
my $pattern = 'perl';
if($str =~ /$pattern/) {
print "'$str'には'$pattern'が含まれます.\n";
}
- このように, 正規表現として変数を利用することもできます.
my $ans = 'y';
if($ans =~ /[yY]/) {
print "文字列にはyないしYが含まれています.\n";
}
[
と]
で文字をくくると, []の中の任意の1文字にマッチします.- よって
/[yY]/
は,y
ないしY
にマッチします.
my $ans = 'n';
if($ans =~ /[^yY]/) {
print "文字列にはyないしY以外の文字が含まれています.\n";
}
[
と]
で文字をくくり, その先頭に^
を置くと, []の中にない任意の1文字にマッチします.- よって
/[^yY]/
は,y
ないしY
以外の文字にマッチします. ^
は, 必ず[
の後に置いて,[^
の形で用います.
my $ans = 'b';
if($ans =~ /[a-c]/) {
print "文字列にはa, b, cのいずれかが含まれています.\n";
}
[
と]
の中で, 文字の間に-
を挟むことによって, 文字列の範囲を表現できます.- この場合,
[a-c]
は[abc]
と同じ意味になります.[1-5]
のように, 数値に対しても利用できます. [a-z0-9]
という複数の文字列の範囲を表現することもできます. この場合「アルファベット小文字か数字の1文字」を表します.
my $ans = 'yes';
if($ans =~ /[Yy]es/) {
print "Yes が選択されました\n";
}
- 任意の1文字を応用すると, 一部だけ大文字小文字を許容する正規表現が書けます.
my $ans = 'get';
if($ans =~ /g.t/) {
print "マッチ!\n";
}
.
は, 改行文字(\n
)を除く, 任意の1文字にマッチします.- よって
/g.t/
は,get
やgot
など,g+任意の1文字+t
にマッチします..
がマッチするのは1文字だけなので,goat
などはマッチしません.- また,
gt
にもマッチしません.
my $ans = 'gt';
if($ans =~ /g.?t/) {
print "マッチ!\n";
}
?
は, その直前の要素が0個または1個の場合にマッチします.- 例えば
ab?
は,a
またはab
にマッチします.
- 例えば
- よって
/g.?t/
は,g+任意の1文字+t
に加え,gt
にもマッチします.
my $ans = 'get';
if($ans =~ /g.+t/) {
print "マッチ!\n";
}
+
は, その直前の要素が1個以上の場合マッチします.- 例えば
ab+c
は,abc
やabbbbc
などにマッチしますが,ac
にはマッチしません.
- 例えば
- よって,
/g.+t/
は,g+任意の1文字以上+t
にマッチします.
my $ans = 'great';
if($ans =~ /g.*t/) {
print "マッチ!\n";
}
*
は, その直前の要素が0個以上の場合マッチします.- 例えば
ab*c
は,ac
やabc
,abbbbbc
などにマッチします.
- 例えば
- よって
/g.*t/
は,g
で始まりt
で終わる全てのフレーズとマッチします(great
など).
my $str = 'Gyaaaaaaaaa!';
print "マッチ!\n" if $str =~ /a{5,}/;
# マッチする
my $str2 = 'Gyaa!';
print "マッチ!\n" if $str2 =~ /a{5,}/;
# マッチしない
{m,n}
... その直前の要素がm回以上, n回以下繰り返す場合マッチ{m,}
... その直前の要素がm回以上繰り返す場合マッチ{m}
... その直前の要素がm回繰り返す場合マッチ
- 標準入力から文字列を受け取り, その文字列に
perl
ないしPerl
が含まれるなら「Perl Monger!」と表示するスクリプトを書いてみましょう. - コードは,
perl_checker.pl
という名前で保存するようにしましょう.
my $str = '私は perl が好きです.';
if($str =~ /私は (.+) が好き/) {
print "彼は, $1 が好きです.\n";
# => 彼は, perl が好きです
}
- 正規表現のパターンを
()
を囲むと, そのパターンに一致する文字列を取得することができます. - 例えばこの場合, $1には
perl
が入り,彼は, perl が好きです.
と表示されるはずです.
my $str = '私は perl と 旅行 が好きです.';
if($str =~ /私は (.+) と (.+) が好き/) {
print "彼は, $1 と $2 が好きです.\n";
# => '彼は, perl と 旅行 が好きです.'と表示.
}
- 複数の
()
が存在する場合, 先頭から$1
,$2
... で取得することができます.
my $str = 'Hello hoge! Hello fuga!';
if($str =~ /Hello (.+)!/) {
print "Nice to meet you, $1!\n";
}
hoge
を抜き出してNice to meet you, hoge!
としたいので, このようなコードを書きました.- しかしながら, 実際には
Nice to meet you, hoge! Hello fuga!
と表示されます.
my $str = 'Hello hoge! Hello fuga!';
if($str =~ /Hello (.+?)!/) {
print "Nice to meet you, $1!\n";
}
- これは, 正規表現が「なるべく長くマッチする(最長マッチ)」ようになっている為です.
- このように, 量指定子のあとに
?
を付けて, 最短マッチにすれば,Nice to meet you, hoge!
と出力されるはずです.
my @sentences = (
'alice loves meat!',
'bob loves sushi!',
);
-
このような配列を受け取り, 格納された文字列について, 「loves」の後に記述されている好きな食べ物の単語を正規表現で取得し, 「alice -> meat」, 「bob -> sushi」のように表示するスクリプト,
love_food
を書いてみよう. -
このコードは,
love_food.pl
という名前で保存するようにしましょう.
my $str = 'abc def ghi abc';
$str =~ s/abc/ABC/;
# $str = 'ABC def ghi abc';
s/PATTERN/REPLACE/
で,PATTERN
をREPLACE
に置換します.PATTERN
を記述する為に, 正規表現を利用することができます.
$str
に含まれる全てのPATTERN
を置換したい場合,s/PATTERN/REPLACE/g
と表記します.- 最後にオプションとして
g
を付けることで, 繰り返し評価・置換します.
- 最後にオプションとして
my $str = 'I love ruby';
- この
$str
に格納された文字列を, 置換を利用して, 「I love perl」に書き換えるようなコードを書いてみましょう.- コードは,
regexp_replace.pl
という名前で保存しましょう.
- コードは,
- メタ文字を使うと, 「数字とマッチ」や「アルファベットとマッチ」などといった正規表現を, より簡単に表現することができます.
- ここでは, よく使うメタ文字を紹介します.
-
\w
... アルファベット, 数字, アンダーバーの1文字[a-zA-Z0-9_]
と同じ意味です.
-
\W
... アルファベット, 数字, アンダーバー以外の1文字[^a-zA-Z0-9_]
と同じ意味です.
-
\d
... 数字の1文字[0-9]
と同じ意味です.
-
\D
... 数字以外の1文字[^0-9]
と同じ意味です.
-
\s
... 空白文字にマッチ[ \n\r\f\t]
と同じ意味です.
-
\S
... 空白文字以外にマッチ[^ \n\r\f\t]
と同じ意味です.
my $str1 = '2019年7月22日';
if($str1 =~ /(\d+)年(\d+)月(\d+)日/) {
print "$1/$2/$3";
# "2019/7/22"と表示される.
}
my $str2 = "この 文章 は\n 読みにく\nい で \t す\n";
$str2 =~ s/\s+//g;
# $str2 = "この文章は読みにくいです";
\s
を使えば, 余分な空白や改行を抜き取ることができます.
|
... 選択一致(OR検索)- 例えば,
abc|def|ghi
は,abc
,def
,ghi
のいずれかにマッチします.
- 例えば,
(PATTERN)
... グループ化- 正規表現をグループ化します.
- 先に説明したように,
()
の中のパターンにマッチした文字列は記憶され,$1
や$2
のように後で参照することができます(後方参照).
(?:PATTERN)
... 後方参照しないグループ化- 正規表現をグループ化しますが,
()
の中のパターンにマッチした文字列は記憶されません.
- 正規表現をグループ化しますが,
my $str = 'perl is good!';
if($str =~ /(?:perl|ruby|python) is (good|bad)!/) {
print "評価は $1 です!\n";
# "評価は good です!"と表示される.
}
perl
,ruby
,python
を|
でつなぎ,(
と)
で囲うことで, 選択一致をグループ化しています.- 更に,
(?:
とすることで, 後方参照しないようにしています.- その為, $1は
(good|bad)
のパターンにマッチした文字列となります.
- その為, $1は
my $str = 'john is dead.';
if ($str =~ /dead\./) {
print "match!\n";
}
\
... メタ文字を無効化する- 正規表現の中で特殊な意味を持つ文字(例えば
/
や.
など)を無効化します.
- 正規表現の中で特殊な意味を持つ文字(例えば
- この場合.
$str =~ /dead./
は,john is dead!
などでもマッチしてしまう(.
は任意の1文字とマッチ, なので). \.
のようにすれば.
そのものとのマッチができます.
- アンカーは, 行頭や行末など, 文字列の特定の位置とマッチします.
^
... 行頭$
... 行末
my $str = 'john is great';
# 行頭に'john'がある場合のみマッチ
if ($str =~ /^john/) {
print "match!\n";
}
my $str = '/usr/local/bin/perl';
if ($str =~ m|bin/perl|) {
print "match!\n";
}
- 正規表現は
/
で区切りますが,/
だと不都合な場合も多いです(例えば, URLを表記する場合など. 全ての/
をエスケープする必要がある). - そこで,
m//
のように, 先頭にm
を付けると, 任意の記号のペアを区切り記号として利用することができます. - 今まで見てきた通り, 区切り記号が
/
の場合のみm
を省略できます. - この場合,
|
を区切り記号にしています. よって,/
をエスケープする必要はありません.
my $str = '/usr/local/bin/perl';
$str =~ s|/usr/local/bin/|/usr/bin/|;
- 置換の場合, このようにできます.
my $str = '/usr/local/bin/perl';
$str =~ s{/usr/local/bin/}{/usr/bin/};
- m// や s/// で区切り文字を変える場合, 括弧を使う場合は上記のように対応する閉じ括弧で区切ることになります.
my $str = 'Hello, hoge! Hello, fuga!';
my @name = ($str =~ /Hello, (\w+?)!/g);
# @name = ('hoge', 'fuga'); となる.
g
は, 正規表現のマッチングを繰り返し行います.- また, 正規表現に
()
が含まれる場合, マッチした文字列のうち()
の中に含まれる文字列をリストとして返します.
my $str = 'Hello, hoge! Hello, fuga!';
my $str =~ s/Hello/Good morning/g;
- 置換の部分で説明したように,
s///g
とすると, 置換の処理を繰り返し行なってくれます.
my $str = 'John and Beth';
if ($str =~ /john/i) {
print "match!\n";
}
i
は, 正規表現中のアルファベットの大文字・小文字を区別せずにマッチングを行います.- よって,
/john/i
は,john
はもちろん,John
やJOHN
,jOhN
などにもマッチします.
while (chomp(my $input = <STDIN>)) {
...
}
- 上記のコードは, 標準入力から入力された文字列を, ひたすら
$input
に代入するコードである. - このコードの
...
の部分を, 次の条件を満たすように書き換えてみよう. - この問題のコードは,
while_input.pl
という名前で保存するようにしよう.
- 文字列が
0
の場合, ループを抜ける(last
を使って...). - 文字列が
perl
ないしPerl
を含む場合, 「Find Perl!」と表示する. - 文字列に大文字小文字問わず,
python
の文字列が含まれる場合, 「Find Python!」と表示する. - 文字列に
perl
ないしruby
ないしpython
が含まれる場合, 「Love Programming!」と表示する.
- 文字列の先頭に
Larry
がある場合, 「Find Larry!」と表示する. - 文字列に
Hello
が含まれる場合, その後に続く単語xxxx
を使って「Hello! xxxx!」と表示する.- 例えば, 文字列に「Hello Larry」が含まれる場合, 「Hello! Larry!」と表示すればOKです.
- https://github.com/perl-entrance-org/workshop-2019/blob/master/4th/practice.md
- 今回の内容を復習できる問題集です。
- 不明点があれば, 気軽にサポーターに質問してください。