sironekotoroの日記

Perl で楽をしたい

Perl入学式 2019 in東京 秋開講 第5回 は中止となります

Perl入学式 2019 in東京 秋開講 第5回は中止となります

第1回〜第4回まで講師をやったジャージの人です。

Perl入学式 2019 in東京 秋開講 第5回は延期ではなく中止となります。

これは2020年初頭から蔓延し、猛威を振るっている新型コロナウイルス(COVID-19)の影響によるものです。

Perl入学式は「解説と練習問題を繰り返し、不明点があればサポーターが近くで教える」という、ワークショップ形式です。

呼気・接触で広がる新型コロナウイルスの性質を考えると、ワークショップ形式の勉強会を開催することはできません。

テキストは公開しております!

従来通り、第5回のテキストを公開しています。

github.com

また、講義で使っているスライド形式のものはこちらになります。

appslideshare.tugougaii.site

自作のスライドはこちらです。使いたかったなぁ・・・

docs.google.com

問題の意味がわからない、とか、このような解答例はどうだろう?という方はSlackのPerl入学式チャンネル(招待フォーム)や、twitterハッシュタグ #Perl入学式 をつけて聞いてみてください。

応答速度、監視頻度などの面からSlackの方をお勧めします。

第5回について

Perl入学式の第5回は第1回〜第4回までとは少し毛色の異なる回です。

第1回〜第4回がPerlの基礎をやってきたのに対し、第5回はその基礎を使う一場面としてWebアプリを作ります。

Perl入学式ではMojolicious(モジョリシャス)というWebフレームワークを利用してWebアプリの作り方を学びます。最初に生成される雛形を拡張し、フォームからの入力、加工、Webアプリ上での表示を可能にしていきます。

講義スライドでは、その時点でのコードの追加・修正履歴を追えるようにしています。

ぜひ挑戦してみてください!

2020年度のPerl入学式

今年度前半のワークショップ形式の開催は難しそうです。オンラインでの開催などを模索して、実施していきます。

printfと私

コロナウイルス禍の昨今

Perl入学式はもとより、Perl のお祭り YAPC::Kyoto 2020 や他の技術系カンファレンスも延期になってしまい、なんとも寂しい限りです。

特にYAPC::Kyoto 2020 は前々日から京都入りして、鞍馬寺比叡山、福知山など、ちょっと遠いところに行こうと思っていたので、本当に残念です。

で、そんな状況とはあんまり関係なく、printfの話をしたいと思います。

printf , sprintf

Perl入学式では取り上げていないprintfなんですが、とても便利な関数です。Perlの組み込み関数なので、モジュールのインポートなどは不要です。

perldoc.jp

通常のprintは引数にとったものをそのまま表示するだけです。

print "hogehoge\n"; # hogehoge

print "hogehoge", "fugafuga", "piyopiyo", "\n"; # hogehogefugafugapiyopiyo

このprintの末尾にfを加えたprintfでは引数を2個以上取ることになります。

printf("%s\n", "hogehoge"); # hogehoge

printも、printfも同じく、 hogehoge と表示して改行するだけのコードです。

printf の最初の引数 "%s\n" は文字列を代入して改行、という意味になります。第二引数である文字列が%sのところに入る訳ですね。

文字列ではなくて、数字も代入できます。第一引数の違いに注目です。

printf("%d\n", 5.30);   # 5

printf("%f\n", 5.30);   # 5.300000

第二引数は双方共5.30 で同じものですが、表示結果は異なります。

"%d\n"の場合には整数、"%f\n"の場合には小数点第6位まで表示されています。

このように、引数に書式を適用して(フォーマットして)出力できるのがこのprintfなのです。

小数点の桁数は、以下のように調節が可能です。

printf("%.2f\n", 5.30);   # 5.30

整数の方も、0埋めが可能です。

printf("%02d\n", 5);   # 05

例えば、日付なんかで2020-03-28 と表示したいときなんかに0埋めができることを知っていると楽です。

printf("%04d-%02d-%02d\n", 2020, 3, 20);   # 2020-03-20

sprintf はフォーマットした文字列や数字を変数に格納するときに使います。引数などはprintfと同じです。

my $date = sprintf("%04d-%02d-%02d\n", 2020, 3, 20);
print $date;    # 2020-03-20

この記事を書こうと思ったきっかけ

かつてのうちは「printf、なんか引数多いし、%sとか%dとかよくわからんし、printでいいよね」と思い綺麗に忘れ去ったのでした。

その結果、このようなサブルーチンが自分のコードのそこかしこから発掘されたのです。

sub add_zero {
    my $num = shift;
    if (length $num == 1){
        $num = 0 . $num;
    }
    return $num;
}

print add_zero(1) . "\n";   # 01
print add_zero(12) . "\n";  # 12

これは、引数にとった数字が1桁だったら0を左側に1つつける(0埋めする)、というサブルーチンです。

しかし、数字と文字のチェックをしていないため、

print add_zero('a') . "\n";  # 0a

という結果が返ってきます。

printfであれば、第一引数が数字%dであるのに文字が入っていると警告が出ます。

printfを知っていれば、このサブルーチン書かなくてもよかったのになー、という思いも込めて(あとしばらくブログ書いてなかったなーという反省もあり)このエントリを書いたのでした。

Perl入学式の小ネタ:正規表現の文字境界

前振り

あなたはあるブログプラットフォームの開発者です。

昨今流行している新型コロナウイルス「COVID-19」について投稿される記事も多くなってきました。

掲載される情報は正しいものもありますが、アクセス数狙いの過激な文言を含む記事もあります。

そのため、COVID-19に関する記事については画面の上部に「信頼しうるソースへのリンク」を掲示することになりました。

開発者は「covid」「COVID-19」「コロナウイルス」「新型コロナ」「発熱」など、いくつかの単語を用意し、記事を正規表現で検索します。

検索結果にマッチした記事だった場合、画面に注意書きを掲載する、という仕組みを作りました。

今回はCOVIDという文字を含む記事、という想定で書いてみました。判定には以下の正規表現を用います。

$entry =~ /COVID/i 
#!/usr/bin/env perl
use strict;
use warnings;

my @entries = (
    'No.1: covid-19の症状について',
    'No.2: COVID-19の症状について',
    'No.3: COVID19の症状について',
    'No.4: COVIDの症状について',
);

for my $entry (@entries) {
    if ( $entry =~ /COVID/i ) {
        print "WARNING!: $entry\n";
    }
}

# 実行結果
# WARNING!: No.1: covid-19の症状について
# WARNING!: No.2: COVID-19の症状について
# WARNING!: No.3: COVID19の症状について
# WARNING!: No.4: COVIDの症状について

Perl入学式の第3回で学習した範囲です。 /i とオプションをつけることで、大文字小文字にかかわらずマッチさせることが可能です。

しかし・・・?

実装後、いくつかのCOVID-19とは関係ない記事に警告が表示されているとの報告がユーザーから寄せられました。

調査の結果、ニコニコ動画のURLを含む記事も正規表現にマッチする結果となっていることが判明しました。

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

my @entries
    = (
    '<a href="https://www.nicovideo.jp/watch/sm9">再ブレイク!レッツゴー陰陽師</a>'
    );

for my $entry (@entries) {
    if ( $entry =~ /COVID/i ) {
        print "WARNING!: $entry\n";
    }
}

# 実行結果
# WARNING!: <a href="https://www.nicovideo.jp/watch/sm9">再ブレイク!レッツゴー陰陽師</a>

なるほど、URLの一部に covid という語が含まれています。

本来のCOVID という語のみを検出し、niconico動画のURLを除外するためにはどうすれば良いでしょうか?

文字境界の /b

正規表現の対象語の前後(あるいは必要なところ)に '/b' を置くことで、より精緻にマッチさせることが可能になります。

\b Match a word boundary

文字の境界とはなんぞや?というと下記のリンク先にも書いてありますが、こういうことです。

単語境界(\b)は\W にマッチングする文字列の始まりと終わりを 連想するような、片側を \w、もう片側を \W で挟まれている点です。

perldoc.jp

\w\W についてはPerl入学式でも解説してあります。

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

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

github.com

以上を踏まえて、正規表現を書き直してみます。

$entry =~ /\bCOVID/i 
#!/usr/bin/env perl
use strict;
use warnings;

my @entries
    = (
    'No.1: covid-19の症状について',
    'No.2: COVID-19の症状について',
    'No.3: COVID19の症状について',
    'No.4: COVIDの症状について',
    'No.5: <a href="https://www.nicovideo.jp/watch/sm9">再ブレイク!レッツゴー陰陽師</a>',
    );

for my $entry (@entries) {
    if ( $entry =~ /\bCOVID/i ) {
        print "WARNING!: $entry\n";
    }
}

# 実行結果
# WARNING!: No.1: covid-19の症状について
# WARNING!: No.2: COVID-19の症状について
# WARNING!: No.3: COVID19の症状について
# WARNING!: No.4: COVIDの症状について

無事、niconico動画のURLについてはマッチしなくなりました。

今回、COVIDの前にだけ \b を置いたのは、COVIDの後に「-(ハイフン)数字」や「数字」が続くパターンがあり、文字の境界である \w, \W の判別を行なっていないためです。

  • ハイフンが0個以上ある
  • 数字が0個以上ある

という条件を追加することで、より正確なにマッチさせることが可能になります。(マッチしたものは使わないので?:を使っています)

$entry =~ /\bCOVID(?:(-)?\d+)?\b/i

というわけで

Perl入学式 in東京 第5回は延期となってしまいましたが、こういった小ネタを拾っていきたいと思っています。

後、「これをするにはどうしたら良いか?」とか「これをやりたい」「書いてみたがうまく動かない」みたいなのがある方、Perl入学式Slackに書き込んでいただくと、寄ってたかって回答が来ます。ぜひご利用ください。

うまく動かないコードなどはblogやgistなどに貼り付けていただくと、より質の高い回答が来ると思います。

docs.google.com

元ネタ

noteの深津さんのtweetでした。

Perl入学式 2019 in東京 秋開講 第4回 お疲れ様でした

Perl入学式 2019 in東京 秋開講 第4回

受講された方、サポーターの方、お疲れ様でした。 講師をやったジャージの人です。

講義に利用したスライドはMarkdown形式で公開しています。復習に使ってください。

www.perl-entrance.org

また、復習問題を用意しています。
第1回から第4回の内容で解ける問題となっています。ぜひ挑戦してみてください。
復習問題の解答例もありますので、参考にしてください。

なお、この第4回の復習問題はかなり難しく、骨のある問題となっています!(意訳:解けなくても落ち込むな)

問題の意味がわからない、とか、このような解答例はどうだろう?という方はSlackのPerl入学式チャンネル(招待フォーム)や、twitterハッシュタグ #Perl入学式 をつけて聞いてみてください。
応答速度、監視頻度などの面からSlackの方をお勧めします。

会場を提供いただいたadishさん、ありがとうございました。

www.adish.co.jp

講義の途中でちょっとだけ使ったスライド

Perlの初学者向け書籍が「リファレンス」についてどう書いているかを調べてみたものです。

docs.google.com

リファレンス!!

講義中何度も言いましたが、他言語で間接参照(C言語などのポインタ)の概念を習得している方にとっては余裕だったと思います。

しかし、間接参照の概念に初めて触れた方は、理解まで相当時間がかかって大変だと思うんですよね・・・うちがそうでした。

私自身もリファレンスについては習得まで苦労した覚えがあります。

リファレンスについては過去のエントリで大体語り尽くしてる感があるので、そちらもどうぞ。

sironekotoro.hateblo.jp

sironekotoro.hateblo.jp

Perlのリファレンスに関しては、完全な自信をもって深沢千尋さんの「すぐわかる オブジェクト指向 Perl」をお勧めします。

Perl入学式を第4回まで受講してきた方なら、今すぐにでも読み始めて大丈夫なレベル感です。

リファレンスの活用例

以下は2020年2月7日〜2020年2月14日までの日経平均のデータです。平日のみなので5日分です。

このような「日時」に紐づいたデータは色々な場で見ることができると思います。売り上げの日報や、夏休みの宿題にある毎日の気温の記録とか。

info.finance.yahoo.co.jp

日付   始値  高値  安値  終値
2020年2月14日    23,714.52   23,738.42   23,603.48   23,687.59
2020年2月13日    23,849.76   23,908.85   23,784.31   23,827.73
2020年2月12日    23,741.21   23,869.73   23,693.72   23,861.21
2020年2月10日    23,631.79   23,788.25   23,621.72   23,685.98
2020年2月7日     23,899.01   23,943.45   23,759.42   23,827.98

このデータをリファレンスなしで表現するのはとても大変です。変数がいくつも必要になることでしょう。

しかし、リファレンスを使うことで、一つの変数の中にこれらの情報をまとめることが可能になります。

まずはこのように書いてみました。求めたいのは期間中の終値(close)の最大、最小、平均です。講義中に紹介したList::Utilモジュールを使って求めています。

配列の要素にハッシュリファレンスを入れており、そのハッシュリファレンスのkeyは日付、value始値(open), 高値(high), 底値(low), 終値(close)のハッシュリファレンスです。

・・・言葉にすると???なのですが、コードを見た方がわかりやすく思えるはずです。

#!/usr/bin/env perl
use strict;
use warnings;
use List::Util qw/max min sum/;
use Data::Dumper;

my @nikkei225 = (
    {   '2020-02-14' => {
            open  => 23714.52,
            high  => 23738.42,
            low   => 23603.48,
            close => 23687.59
        }
    },
    {   '2020-02-13' => {
            open  => 23849.76,
            high  => 23908.85,
            low   => 23784.31,
            close => 23827.73
        }
    },
    {   '2020-02-12' => {
            open  => 23741.21,
            high  => 23869.73,
            low   => 23693.72,
            close => 23861.21
        }
    },
    {   '2020-02-10' => {
            open  => 23631.79,
            high  => 23788.25,
            low   => 23621.72,
            close => 23685.98
        }
    },
    {   '2020-02-07' => {
            open  => 23899.01,
            high  => 23943.45,
            low   => 23759.42,
            close => 23827.98
        }
    },

);

my @close_values;
for my $row (@nikkei225) {
    my ($date) = keys %{$row};

    # keys は配列を返すが、この例ではkeyは1つの値(日付)だけが必要なので、このようにして受け取る

    push @close_values, $row->{$date}->{close};
}

print "MAX: " . max(@close_values) . "\n";
print "MIN: " . min(@close_values) . "\n";
print "AVG: " . sum(@close_values) / scalar (@close_values) . "\n";

# scalar (@配列) で、配列の要素数を求めることができる

# MAX: 23861.21
# MIN: 23685.98
# AVG: 23778.098

欲しかった結果は得ることができました。

しかし、ここで「欲望」が囁きます。最大値、最小値を記録したのは何月何日なのだ?と。

うーん、これは難しい。そう、このリファレンスのままでは難しい

というわけで、別なデータ構造に書き直してみます。ついでに、最大と最小はList::Utilモジュールを使わずにやってみます。

配列の中にハッシュリファレンスを入れたものです。先ほどのよりもシンプルな構造です。

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

my @nikkei225 = (
    {   date  => '2020-02-14',
        open  => 23714.52,
        high  => 23738.42,
        low   => 23603.48,
        close => 23687.59
    },

    {   date  => '2020-02-13',
        open  => 23849.76,
        high  => 23908.85,
        low   => 23784.31,
        close => 23827.73
    },
    {   date  => '2020-02-12',
        open  => 23741.21,
        high  => 23869.73,
        low   => 23693.72,
        close => 23861.21
    },
    {   date  => '2020-02-10',
        open  => 23631.79,
        high  => 23788.25,
        low   => 23621.72,
        close => 23685.98
    },
    {   date  => '2020-02-07',
        open  => 23899.01,
        high  => 23943.45,
        low   => 23759.42,
        close => 23827.98
    },
);

my $max = 0;     # 最大値を記録するための変数
my $max_date;    # 最大値の日付を記録するための変数
my $min = 38915
    ; # 最少値を記録するための変数(初期値は1989年12月29日の日経平均 史上最高値)
my $min_date;    # 最少値の日付を記録するための変数
my $sum = 0;
for my $row (@nikkei225) {
    my $close = $row->{close};

# $maxよりも大きい終値($row->{close})だったら変数の値と日付を更新
    if ( $max < $close ) {
        $max      = $close;
        $max_date = $row->{date};
    }

# $minよりも小さい終値($row->{close})だったら変数の値と日付を更新
    if ( $close < $min ) {
        $min      = $close;
        $min_date = $row->{date};
    }

    # 平均を求めるために、終値の合計額を集計しておく
    $sum += $close;
}

print "MAX: $max_date : " . $max . "\n";
print "MIN: $min_date : " . $min . "\n";
print "AVG: " . $sum / scalar(@nikkei225) . "\n";

# scalar (@配列) で、配列の要素数を求めることができる

# MAX: 2020-02-12 : 23861.21
# MIN: 2020-02-10 : 23685.98
# AVG: 23778.098

これで「欲望」の求めることを達成することができました。

このように、リファレンスを習得することで、「欲しいもの」を得やすいデータ構造を作り出すことができるようになります。

・・・ところで、第2回で学んだsortを覚えていますか?配列の要素を並べ替えするための関数です。

終値を順に並べれば、もっと楽に最大値、最小値を求めることができるのでは・・・?

リファレンスのソートはなかなかに難易度が高いです。興味のある人は是非Googleで検索しつつ実装してみてください。私も書いてみました。

最大値、最小値を求めるときにsortを使ってみる(クリックで展開)

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

my @nikkei225 = (
    {   date  => '2020-02-14',
        open  => 23714.52,
        high  => 23738.42,
        low   => 23603.48,
        close => 23687.59
    },

    {   date  => '2020-02-13',
        open  => 23849.76,
        high  => 23908.85,
        low   => 23784.31,
        close => 23827.73
    },
    {   date  => '2020-02-12',
        open  => 23741.21,
        high  => 23869.73,
        low   => 23693.72,
        close => 23861.21
    },
    {   date  => '2020-02-10',
        open  => 23631.79,
        high  => 23788.25,
        low   => 23621.72,
        close => 23685.98
    },
    {   date  => '2020-02-07',
        open  => 23899.01,
        high  => 23943.45,
        low   => 23759.42,
        close => 23827.98
    },
);

my $sum = 0;
for my $row (@nikkei225) {
    my $close = $row->{close};

    # 平均を求めるために、終値の合計額を集計しておく
    $sum += $close;
}

my @sorted = sort { $a->{close} <=> $b->{close} } @nikkei225;

# 配列の最初の要素を求めるときの添え字は[0]
# 配列の最後の要素を求めるときの添え字は[-1]

print "MAX: $sorted[-1]->{date} : " . $sorted[-1]->{close} . "\n";
print "MIN: $sorted[0]->{date} : " . $sorted[0]->{close} . "\n";
print "AVG: " . $sum / scalar(@nikkei225) . "\n";

# scalar (@配列) で、配列の要素数を求めることができる

# MAX: 2020-02-12 : 23861.21
# MIN: 2020-02-10 : 23685.98
# AVG: 23778.098

Perl入学式 第5回 Webアプリ編

次回のPerl入学式 in東京は秋季講習の最終回、第5回 Webアプリ編です。

サブルーチンとWebアプリの基礎をやります。connpassでページを公開しています。

ただし、新型コロナウイルスの蔓延状況次第では延期の可能性があります。ご了承ください。【2月18日追記】残念ながら、第5回は延期となりました。

perl-entrance-tokyo.connpass.com

また、Webアプリを作成していくにあたり、HTMLの最低限の知識が必要となります。もし不安な方は一度下記のテキスト「HTML入学式」を見ておくことをお勧めします。

github.com

Perl入学式 第3回までの範囲(+α)でROT13

というわけで、前回からの続きです。

sironekotoro.hateblo.jp

Perl入学式 第3回ではハッシュと正規表現を扱いました。

ハッシュを使ってROT13を解く

ROT13はハッシュと正規表現の文字クラスを利用することで簡単に解くことが可能です。

まずはハッシュで解いてみます。ちょっと長くなりますが、密度は薄いです。

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

my $secret_str = 'uryyb jbeyq';    # 暗号化済みの文字列
my @secret_str = split "",
    $secret_str;    # 暗号化文字列を1文字ずつ配列に格納

# 一文字ずつ対応したハッシュ
my %hash = (
    a => 'n',
    b => 'o',
    c => 'p',
    d => 'q',
    e => 'r',
    f => 's',
    g => 't',
    h => 'u',
    i => 'v',
    j => 'w',
    k => 'x',
    l => 'y',
    m => 'z',
    n => 'a',
    o => 'b',
    p => 'c',
    q => 'd',
    r => 'e',
    s => 'f',
    t => 'g',
    u => 'h',
    v => 'i',
    w => 'j',
    x => 'k',
    y => 'l',
    z => 'm',
);

for my $char (@secret_str) {
    if ( $char eq ' ' )
    {    # 文字ではなく、スペースだったらそのまま表示
        print $char;
    }
    else {
        print $hash{$char};
    }
}

正規表現の文字クラスを使ってROT13を解く

現在のカリキュラムでは、正規表現を使った置換を解説しています。 s///gですね。

ただ、今回のように置換前と置換後の文字列が1文字ずつ対応する場合には、tr/// がベストマッチです。

例えばこんな感じです。tr の前半にある置換対象の数字が、置換後の文字に置き換わっています。

my $str = '123';
$str =~ tr/123/abc/;

# 1 と a が対応する
# 2 と b が対応する
# 3と c が対応する

print $str; # abc

あくまで1文字ずつ対応していれば良いので、このような変換も可能です。

my $str = '123';
$str =~ tr/123/cba/;

print $str; # cba

置換前の文字列と、置換後の文字列には、文字クラスを利用することが可能です。最初の例を文字クラスを使って書いてみるとこんな感じです。

2020年2月1日追記

tr/SEARCHLIST/REPLACEMENTLIST/ のSEARCHLISTに該当するところには文字クラスに[ ] は不要です。ということで掲載したサンプルコードを編集しています。本エントリのxtetsujiさんのコメントに感謝。

my $str = '123';
$str =~ tr/1-3/a-c/;

print $str; # abc

置換対象に含まれていない文字についてはそのままです。以下の例だと、4から9までの数字です。

my $str = '123456789';
$str =~ tr/1-3/a-c/;

print $str; # abc456789

さて、これを踏まえてROT13を解いてみるとこんな感じです。

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

my $secret_str = 'uryyb jbeyq';    # 暗号化済みの文字列
$secret_str =~ tr/a-z/n-za-m/;
print $secret_str;  # hello world

実質3行で終わってしまいました。

文字クラスは頻出の a-z だけではなく、 n-za-m のような利用も可能です。

a-z はアルファベット順の abcdefghijklmnopqrstuvwxyz です。

n-za-mnopqrstuvwxyzabcdefghijklm と同じです。

PerlでROT13を解いてみて

ということで、Perl入学式の範囲でROT13を解いてみました。

文字列を置換する、という本来の目的に沿った関数を使うことでスクリプトが簡潔になります。

for文で1文字ずつバラして書くのも「頭の対応」「縛りプレイ」感があって楽しいのですが、できれば楽したいっすね。

なんか遠回りしてるな・・・?と思ったら、目的に沿った関数やモジュールが公開されているかもしれません。

実現したい方法を探そうにも、それがぼんやりしていて・・・という人は是非Perl入学式のSlackや懇親会で相談してみてください。うちもよく相談してます。

(余談)なんでROT13?

なぜROT13でエントリを2つ書いたのか?というと、現在勉強中のGo言語で同じ問題が出たからです。

あ、ちなみにGo言語の公式チュートリアルである Tour of Go は途中で理解できなくなった勢なので、わかるところまでレベル下げた本がこれって感じです。まだ半分くらいだけど、うちにとっては、とても分かりやすくて良い本です。

Go言語の勉強・・・に限らずですが、練習問題に出会うと「この問題はPerlではどう解くのだろう?」となってしまい、2倍くらい時間がかかってる感じですね・・・

まずGoで自力で解いてみる、解答を写経してみる、次にGo言語っぽくPerlで書いて、次にめっちゃPerlっぽく書く。時間がかかるんだけど楽しいのでやめられないですよね。

(目的と手段がすり替わるいつものパターン)

package main

import "fmt"

func main() {
    message := "uryyb jbeyq"

    for i := 0; i < len(message); i++ {

        c := message[i]
        if 'a' <= c && c <= 'z' {
            c += 13

            switch {
            case c > 'z':
                c = c - 26
            }
        }
        fmt.Printf("%c", c)
    }

}

何と、Go言語では文字に数字足したり引いたりできるんですよすごい。こんな感じ。

package main

import "fmt"

func main() {
    char := 'c'

    fmt.Printf("%c\n", char) // c
    char = char + 1
    fmt.Printf("%c\n", char) // d
}

ついでに、Go言語で書いたFizzBuzzです。Perlしか知らなくても、何となく読めませんかね? for とか if とか剰余の % とか。

package main

import "fmt"

func main() {

    for i := 1; i <= 100; i++ {
        if i%3 == 0 && i%5 == 0 {
            fmt.Println("fuzzbuzz")
        } else if i%3 == 0 {
            fmt.Println("fizz")
        } else if i%5 == 0 {
            fmt.Println("buzz")
        } else {
            fmt.Println(i)
        }
    }
}

うちは産湯を使った言語がPerlなので、型がある言語をちゃんと勉強するのは初めてです。

でも、Perlで学んだ処理の流れ+ Go ならではの知識・やり方を知るのが楽しいですし、それによって自分のPerlコードも変わっていく、というのがまた楽しいです。

「わかることは変わること」とよく言われますが、本当にそういう感じです。

いろんな言語の良いところを知って、それを取り込み、Perl力を高めていきたいですね。

Perl入学式 2019 in東京 秋開講 第3回 お疲れ様でした

Perl入学式 2019 in東京 秋開講 第3回

受講された方、サポーターの方、お疲れ様でした。 講師をやったジャージの人です。

講義に利用したスライドはMarkdown形式で公開しています。復習に使ってください。

www.perl-entrance.org

また、復習問題を用意しています。
第1回から第3回の内容で解ける問題となっています。ぜひ挑戦してみてください。
復習問題の解答例もありますので、参考にしてください。

問題の意味がわからない、とか、このような解答例はどうだろう?という方はSlackのPerl入学式チャンネル(招待フォーム)や、twitterハッシュタグ #Perl入学式 をつけて聞いてみてください。
応答速度、監視頻度などの面からSlackの方をお勧めします。

講義の途中でちょっとだけ使ったスライド

正規表現の量演算子., ?, +, * )について解説したスライドです。参考にどうぞ。最後の2枚には3月27日、28日に開催される YAPC::Kyoto 2020 について紹介しています。ホテル確保はお早めに!!

docs.google.com

ハッシュ

今回の第3回の前半ではハッシュを学びました。

Perlにおける基本的な変数は以下の3つです。今回で全てを学習したことになります。

  • $ から始まるスカラー変数
  • @ から始まる配列
  • % から始まるハッシュ

次回学習するリファレンスはこの3つの変数を利用する応用編ともいえます。

正規表現

正規表現により、単なる検索、置換以上のことが可能になります。

VimVSCodeなどのエディタでは検索条件として「検索文字そのもの」だけではなく、正規表現で検索が可能です。

Everything のようなソフトウェアを利用することで、PC内のファイル検索に正規表現が利用できます。

富士通_2020_0190.xlsx 富士ゼロックス_2019_0820.xlsx

などのファイルは富士.+_\d{4}_\d{4}\.xlsxという形で検索できますし、もっとざっくり 富士.*\.xlsxでも引っかけることができますね。

WebサーバとしてメジャーなApachenginxにおいても、URLの書き換え等に正規表現を用る事ができます。

もちろん、Perl以外のプログラム言語でも正規表現は利用できます。今回学習した「マッチング」「後方参照」「最長マッチ・最短マッチ」等の概念は他でも応用可能なものです。

このように活用の機会がそこかしこにあるのが正規表現です。

全てを理解するのは大変ですが、わからない時に講義資料を見にきてくれれば良いなぁと思ってます。

また、現Perl入学式校長である id:xtetsuji さんが昨年、Web+DB Press正規表現についての記事を書いています。

今回の講義で物足りなかった方、もっと先に進みたい方、正規表現の深淵を覗いてみたい方は是非ご覧ください。

gihyo.jp

次回のPerl入学式 in東京 は2月15日

次回はリファレンスを学びます。

これまでに学んだ3種の変数をベースにより複雑なデータ構造を作り、利用することが可能になります。

リファレンス、それは初学者に立ちはだかる壁・・・実際うちもPerl入学式1周目でわからず、本を読んで薄ぼんやり、Perl入学式2周目でなんとか、あとは欲望のままに欲望を叶えるスクリプトを書いているうちに身に付けました。

つまり数です。

脳筋理論で申し訳ないのですが、数をこなせば乗り越えられる壁なので、2度3度分からない程度でくじけずやっていきましょう!

perl-entrance-tokyo.connpass.com

Perl入学式 第2回までの範囲(+α)でROT13

ROT13 というのは、簡単な暗号の一つです。

暗号化した文字列 uryyb jbeyq を以下の表をもとに置換すると、hello world という文字列になります。

変換前 a b c d e f g h i j k l m
変換後 n o p q r s t u v w x y z
変換前 n o p q r s t u v w x y z
変換後 a b c d e f g h i j k l m

名前の通り、 a は 13文字後の n に置換されており、他の文字も同様です。13文字後が z 以降になる場合には、a に戻ります。

ja.wikipedia.org

さて、これを Perl入学式の第2回までの範囲でやってみましょう!

Perl入学式の第2回は四則演算、配列、配列操作の関数、forを使った繰り返しまで、でした。

github.com

書いてみたものがこちらです。

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;

my $secret_str = 'uryyb jbeyq';          # 暗号化済みの文字列
my @secret_str = split "", $secret_str;  # 暗号化文字列を1文字ずつ配列に格納

my @alphabet = ( 'a' .. 'z' );    # アルファベットが1文字ずつ格納された配列

# 暗号化した文字列を1文字ずつ処理する。
for my $char (@secret_str) {

    if ( $char eq ' ' )
    {    # 文字ではなく、スペースだったらそのまま表示
        print $char;
    }
    else {

        my $i     = 0;  # ループが何回目かを保存する変数
        my $index = 0;  # アルファベットの何文字目かを保存する変数
                        # @alphabetの添え字と同じ

        for my $c (@alphabet) {     #アルファベットを1文字ずつ取り出して比較する

            if ( $char eq $c ) {    # 文字列なので比較は eq
                $index = $i;        # 合致したら $indexに何番目のアルファベットだったか保存
            }
            else {
                $i++;               # 合致しなかったら、$indexに1加える
            }
        }

        my $recover_index = $index + 13;   # 今回の暗号は13文字ずらしたことがわかっているので、
                                                                  # 13文字進めたものを本来の文字列の添字とする

        if ( $recover_index > 26 ) {    # 13文字分添字を足したら、アルファベットの文字数を超えた場合

            print $alphabet[ $recover_index % 26 ]; # 剰余で26を超えた分だけ添字にする

        }
        else {
            print $alphabet[$recover_index];        # 添字をそのまま使う
        }
    }
}

ううむ、書けなくはないのですが、ちょっと長いですね・・・でも書けないことはないです!

lastでforをぬけてみる。

さて、ここから第2回の範囲を超えていくとどうなるのか?というのをやっていきます。

for 文や while 文などの繰り返しの中で、途中で抜けたいときに使う last を使います。ちょっとだけ短くなりました。変数も1つ($i)消えました。

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;

my $secret_str = 'uryyb jbeyq';
my @secret_str = split "", $secret_str;

my @alphabet = ( 'a' .. 'z' );

for my $char (@secret_str) {

    if ( $char eq ' ' )
    {
        print $char;
    }
    else {

        my $index = 0;

        for my $c (@alphabet) {

            if ( $char eq $c ) {
                last;               # 文字が合致した場合には、そこで抜ける
            }
            else {
                $index++;
            }
        }

        my $recover_index = $index + 13;

        if ( $recover_index > 26 ) {

            print $alphabet[ $recover_index % 26 ];

        }
        else {
            print $alphabet[$recover_index];
        }
    }
}

index関数で、何文字目かを文字列から取ってみる

さらに超えていきます。Perlindex 関数は以下のように用います。文字列から、特定の文字が何番目にあるかを返す関数です。

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

my $alphabet = 'abcdefghijklmnopqrstuvwxyz';

my $index = index $alphabet, 'a';
print $index . "\n";    # 0

$index = index $alphabet, 'n';
print $index . "\n";    # 13

$index = index $alphabet, 'z';
print $index . "\n";    # 25

この index関数があれば、アルファベットの添え字が何番目かを調べるのにforを使う必要がなくなりますね!

for文 の中にある for文(入れ子のfor文) が消えてすっきりしました。

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;

my $secret_str = 'uryyb jbeyq';
my @secret_str = split "", $secret_str;

my @alphabet = ( 'a' .. 'z' );

# index関数を使うため、アルファベットを格納したスカラー変数を用意
my $alphabet_str = join "", @alphabet;

for my $char (@secret_str) {

    if ( $char eq ' ' ) {
        print $char;
    }
    else {

        my $index = index $alphabet_str, $char;  # index関数で何文字目かを調べる

        my $recover_index = $index + 13;

        if ( $recover_index > 26 ) {

            print $alphabet[ $recover_index % 26 ];

        }
        else {
            print $alphabet[$recover_index];
        }
    }
}

三項演算子を使う

まだ、短くすることはできるのでしょうか?もちろん可能です。ただ、「第2回までの範囲をそれほど逸脱しない」となると難しいですね・・・次で最後とします。

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;

my $secret_str = 'uryyb jbeyq';
my @secret_str = split "", $secret_str;

my @alphabet = ( 'a' .. 'z' );

# index関数を使うため、アルファベットを格納したスカラー変数を用意
my $alphabet_str = join "", @alphabet;

for my $char (@secret_str) {

    if ( $char eq ' ' ) {
        print $char;
    }
    else {

        my $index = index $alphabet_str,
            $char;    # index関数で何文字目かを調べる

        my $recover_index = $index + 13;

        # 三項演算子
        $recover_index
            = $recover_index > 26 ? $recover_index % 26 : $recover_index;

        print $alphabet[$recover_index];

    }
}

添字が26より大きかった場合を処理していたif文が消えています。

これは三項演算子を用いた条件分岐の書き方です。三項演算子Perlだけではなく、C言語Java, PHPにもあります。

ja.wikipedia.org

Perl入学式の第2回までの範囲から、last, index, 三項演算子を用い他スクリプトを書いてみました。

同じ結果をもたらすスクリプトでも、行数や変数の数が大きく変わってきます。

Perl入学式の範囲から越境して、いろいろな関数を触ってみてください!

perldoc.jp

Perl入学式第3回までの範囲だと・・・?

2020年01月25日はPerl入学式 in東京の第3回です。

perl-entrance-tokyo.connpass.com

ハッシュと正規表現を使うことで、ROT13は更にわかりやすくなり、更に短くすることも可能です。

(多分)講義では取り上げませんが、このブログでは第3回の範囲でROT13を書いてみたいと思っています。