Skip to content

Latest commit

 

History

History
773 lines (611 loc) · 26.1 KB

slide.md

File metadata and controls

773 lines (611 loc) · 26.1 KB

Perl入学式

第3回 ハッシュ/正規表現編


諸注意

  • 会場について

    • 飲食・喫煙・トイレ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)である namebirth は、シングルクォート ' ' やダブルクオート '" "' で囲む必要はありません。

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 を作りましょう。

  1. 以下の人物のプロフィールを %larry_profile に格納してください。

    • 名前(name) : Larry Wall
    • 誕生(birth) : 1954
    • 言語(lang) : Perl
  2. key である namebirthlang を使って、それぞれの value を出力してください。

  3. 時間に余裕のある人は、Larry のプロフィールを充実させましょう。


ハッシュの操作


ハッシュの操作

keys, delete, exists

ハッシュを便利に扱うための関数について説明します。

  • keys

    • ハッシュの名前(key)の集合を返す。
  • delete

    • ハッシュの要素を削除する。
  • exists

    • ハッシュの要素が存在するかしないかを返す。

ハッシュの操作

keys

keys 関数はハッシュの名前(key)を配列にして返します。

my %hash = (
    name  => 'Larry',
    birth => 1954,
    lang  => 'Perl',
);
my @keys = keys %hash;
print "@keys\n";    # birth name lang (順不同)

ただし、この keys は名前(key)を 順不同、順番が不定 で返します。

ハッシュに書かれた順番で返ってくるとは限りません。


ハッシュの操作

名前(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

delete 関数は、指定したハッシュの名前(key)と、それに対応する値(value)を削除します。

my %hash = (
    name  => 'Larry',
    birth => 1954,
    lang  => 'Perl',
);
delete $hash{lang};     # lang という名前(key)を指定して削除
print "$hash{lang}\n";

この例では、最後の行で削除した名前(key)に対応する値(value)を表示しようとしています。

このとき、しっかり「おまじない」を書いていれば、存在しないキーをprintしようとしている、と警告してくれます。


ハッシュの操作

exists

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";
}

どのような結果になるでしょうか?


練習問題(1/2)

次の処理をする hash_func.pl を作りましょう。以下のハッシュをコピペして利用してください。

my %hash = (
    name  => 'Larry',
    birth => 1954,
    lang  => 'Perl',
);
  1. 以下の要素を追加してください。

    • 名前(key): software
    • 値(value): patch
  2. keys 関数を使って, %hash の名前(key)をすべて出力してください。

  3. delete 関数を使って, 1で使ったハッシュから birth の要素を削除してください。


練習問題(2/2)

練習問題(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";
}
  • このように, 正規表現として変数を利用することもできます.

任意の1文字

my $ans = 'y';
if($ans =~ /[yY]/) {
    print "文字列にはyないしYが含まれています.\n";
}
  • []で文字をくくると, []の中の任意の1文字にマッチします.
  • よって/[yY]/は, yないしYにマッチします.

任意の1文字(否定)

my $ans = 'n';
if($ans =~ /[^yY]/) {
    print "文字列にはyないしY以外の文字が含まれています.\n";
}
  • []で文字をくくり, その先頭に^を置くと, []の中にない任意の1文字にマッチします.
  • よって/[^yY]/は, yないしY以外の文字にマッチします.
  • ^は, 必ず[の後に置いて, [^の形で用います.

任意の1文字(連続)

my $ans = 'b';
if($ans =~ /[a-c]/) {
    print "文字列にはa, b, cのいずれかが含まれています.\n";
}
  • []の中で, 文字の間に-を挟むことによって, 文字列の範囲を表現できます.
  • この場合, [a-c][abc]と同じ意味になります. [1-5]のように, 数値に対しても利用できます.
  • [a-z0-9] という複数の文字列の範囲を表現することもできます. この場合「アルファベット小文字か数字の1文字」を表します.

任意の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/は, getgotなど, 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は, abcabbbbcなどにマッチしますが, acにはマッチしません.
  • よって, /g.+t/は, g+任意の1文字以上+tにマッチします.

量指定子'*'

my $ans = 'great';
if($ans =~ /g.*t/) {
    print "マッチ!\n";
}
  • *は, その直前の要素が0個以上の場合マッチします.
    • 例えばab*cは, acabc, 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/で, PATTERNREPLACEに置換します.
    • PATTERNを記述する為に, 正規表現を利用することができます.
  • $strに含まれる全てのPATTERNを置換したい場合, s/PATTERN/REPLACE/gと表記します.
    • 最後にオプションとしてgを付けることで, 繰り返し評価・置換します.

練習問題

my $str = 'I love ruby';
  • この$strに格納された文字列を, 置換を利用して, 「I love perl」に書き換えるようなコードを書いてみましょう.
    • コードは, regexp_replace.plという名前で保存しましょう.

正規表現とメタ文字


メタ文字

  • メタ文字を使うと, 「数字とマッチ」や「アルファベットとマッチ」などといった正規表現を, より簡単に表現することができます.
  • ここでは, よく使うメタ文字を紹介します.

メタ文字(1)

  • \w ... アルファベット, 数字, アンダーバーの1文字

    • [a-zA-Z0-9_]と同じ意味です.
  • \W ... アルファベット, 数字, アンダーバー以外の1文字

    • [^a-zA-Z0-9_]と同じ意味です.
  • \d ... 数字の1文字

    • [0-9]と同じ意味です.
  • \D ... 数字以外の1文字

    • [^0-9]と同じ意味です.

メタ文字(1)

  • \s ... 空白文字にマッチ

    • [ \n\r\f\t]と同じ意味です.
  • \S ... 空白文字以外にマッチ

    • [^ \n\r\f\t]と同じ意味です.

メタ文字(1) 使い方

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を使えば, 余分な空白や改行を抜き取ることができます.

メタ文字(2)

  • | ... 選択一致(OR検索)
    • 例えば, abc|def|ghiは, abc, def, ghiのいずれかにマッチします.
  • (PATTERN) ... グループ化
    • 正規表現をグループ化します.
    • 先に説明したように, ()の中のパターンにマッチした文字列は記憶され, $1$2のように後で参照することができます(後方参照).
  • (?:PATTERN) ... 後方参照しないグループ化
    • 正規表現をグループ化しますが, ()の中のパターンにマッチした文字列は記憶されません.

メタ文字(2) 使い方

my $str = 'perl is good!';
if($str =~ /(?:perl|ruby|python) is (good|bad)!/) {
    print "評価は $1 です!\n";
    # "評価は good です!"と表示される.
}
  • perl, ruby, python|でつなぎ, ()で囲うことで, 選択一致をグループ化しています.
  • 更に, (?:とすることで, 後方参照しないようにしています.
    • その為, $1は(good|bad)のパターンにマッチした文字列となります.

正規表現のメタ文字(3)

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";
}

区切り記号の変更(1)

my $str = '/usr/local/bin/perl';
if ($str =~ m|bin/perl|) {
    print "match!\n";
}
  • 正規表現は/で区切りますが, /だと不都合な場合も多いです(例えば, URLを表記する場合など. 全ての/をエスケープする必要がある).
  • そこで, m//のように, 先頭にmを付けると, 任意の記号のペアを区切り記号として利用することができます.
  • 今まで見てきた通り, 区切り記号が / の場合のみ m を省略できます.
  • この場合, |を区切り記号にしています. よって, /をエスケープする必要はありません.

区切り記号の変更(2)

my $str = '/usr/local/bin/perl';
$str =~ s|/usr/local/bin/|/usr/bin/|;
  • 置換の場合, このようにできます.

区切り文字の変更(3)

my $str = '/usr/local/bin/perl';
$str =~ s{/usr/local/bin/}{/usr/bin/};
  • m// や s/// で区切り文字を変える場合, 括弧を使う場合は上記のように対応する閉じ括弧で区切ることになります.

正規表現のオプション


繰り返してマッチ(g)

my $str = 'Hello, hoge! Hello, fuga!';
my @name = ($str =~ /Hello, (\w+?)!/g);
# @name = ('hoge', 'fuga'); となる.
  • gは, 正規表現のマッチングを繰り返し行います.
  • また, 正規表現に()が含まれる場合, マッチした文字列のうち()の中に含まれる文字列をリストとして返します.

繰り返してマッチ(g)

my $str = 'Hello, hoge! Hello, fuga!';
my $str =~ s/Hello/Good morning/g;
  • 置換の部分で説明したように, s///gとすると, 置換の処理を繰り返し行なってくれます.

大文字/小文字を区別しない(i)

my $str = 'John and Beth';
if ($str =~ /john/i) {
    print "match!\n";
}
  • iは, 正規表現中のアルファベットの大文字・小文字を区別せずにマッチングを行います.
  • よって, /john/iは, johnはもちろん, JohnJOHN, jOhNなどにもマッチします.

練習問題 (1/3)

while (chomp(my $input = <STDIN>)) {
    ...
}
  • 上記のコードは, 標準入力から入力された文字列を, ひたすら$inputに代入するコードである.
  • このコードの...の部分を, 次の条件を満たすように書き換えてみよう.
  • この問題のコードは, while_input.plという名前で保存するようにしよう.

練習問題 (2/3)

  • 文字列が0の場合, ループを抜ける(lastを使って...).
  • 文字列がperlないしPerlを含む場合, 「Find Perl!」と表示する.
  • 文字列に大文字小文字問わず, pythonの文字列が含まれる場合, 「Find Python!」と表示する.
  • 文字列にperlないしrubyないしpythonが含まれる場合, 「Love Programming!」と表示する.

練習問題 (3/3)

  • 文字列の先頭にLarryがある場合, 「Find Larry!」と表示する.
  • 文字列にHelloが含まれる場合, その後に続く単語xxxxを使って「Hello! xxxx!」と表示する.
    • 例えば, 文字列に「Hello Larry」が含まれる場合, 「Hello! Larry!」と表示すればOKです.

質問タイム


復習問題


お疲れさまでした