sironekotoroの日記

Perl で楽をしたい

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を書いてみたいと思っています。

Perl入学式 in 東京 秋開講 第2回 ピザ会でのお題「コラッツの問題」

Perl入学式 in 東京では各回の講義終了後にピザ会(ピザ&ジュース代は参加者負担)を開催しており、そこでサポーター・受講者さんと雑談などをしております。

サポーター含め参加者が抱えているプログラムの問題や、詰まってしまったところを相談したり、エディタやGitの使い方、PerlExcelなど他のツール・ソフトの連携方法、業界の動向について話したりしています。全体的にゆるーい雰囲気です。

その中で、id:xtetsuji さんが課題が出し、志願者がコードを書いてその場で発表、という試みを行っています。

今回のお題:コラッツの問題

xtetsujiさんからのお題ですが、今回は「コラッツの問題」でした。コラッツ・・・聞いたことないですね・・・コラッタの仲間かな?来年ねずみ年だし・・・

コラッツの問題 - Wikipedia

コラッツの問題は、「任意の正の整数 n をとり、

n が偶数の場合、n を 2 で割る
n が奇数の場合、n に 3 をかけて 1 を足す

という操作を繰り返すと、どうなるか」というものである。

ねずみ関係なかった。

このコラッツの問題を 1, 2, 3, 4, 5 ・・・ と与える数字を変えていき、どのような経緯で数値が変わっていくかを出して欲しい、というものでした。

コラッツの問題を解くには・・・?

この問題は今回学習した、「繰り返し」と「条件分岐」に「四則演算」で解くことができますが、それだけではちょっと足りません。

例えば、n = 5 とすると以下のような経緯を経て1になります。

5: 16 -> 8 -> 4 -> 2 -> 1 -> end

n = 7 の場合にはこう。

7: 22 -> 11 -> 34 -> 17 -> 52 -> 26 -> 13 -> 40 -> 20 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1 -> end

つまり、与える数によって繰り返しの数が異なります。繰り返しの数が読めない場合、ちょっとした工夫が必要です。

それは、特定の条件の時に繰り返しを終わらせる last です。

繰り返し を抜ける last

これは Hello world! を10回表示するスクリプトです。

for ( 1 .. 10 ) {
    print "Hello world!\n";
}

これに1行加えます。

for ( 1 .. 10 ) {
    print "Hello world!\n";
    last;
}

このようにすると、Hello world!は1回のみ表示されます。処理が last に到達すると、その繰り返しから抜けると動きとなります。

コラッツの問題、何回ループするかは分からないので、とりあえず10000回くらいにしておきます。

lastを使う条件は n = 1 になった時です。

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

print "input number > ";

my $input_num = <STDIN>;
chomp $input_num;

print "$input_num: ";

for ( 0 .. 10000 ) {

    if ( $input_num == 1 ) {
        print "end\n";
        last;
    }
    elsif ( $input_num % 2 == 0 ) {

        # n が偶数の場合、n を 2 で割る
        $input_num = $input_num / 2;
    }
    else {
        # n が奇数の場合、n に 3 をかけて 1 を足す
        $input_num = $input_num * 3 + 1;
    }

    print $input_num . " -> ";

}

last のようにループ内で使える関数は nextredo があります。

perldoc.jp

回数を指定しないループ、無限ループ

先ほどの解き方だと、10000回の繰り返しで終わらなかった時には途中で終わってしまいます。

このように、繰り返しの回数が分からない場合には while を使います。Perl入学式のテキストでは「落ち穂拾い」として掲載しています。

落ち穂拾い: while ループ

while を使う時には無限ループにならないよう、ループを終わらせる処理を入れることが必要です。last ですね。

もし、処理を書き間違えて無限ループになってしまったら、Ctrl(Control)キーを押しながらCを押すことで強制的に止めることが可能です。

コラッツの問題のwhile版です。

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

print "input number > ";

my $input_num = <STDIN>;
chomp $input_num;

print "$input_num: ";

while (1) {

    if ( $input_num == 1 ) {
        print "end\n";
        last;
    }
    elsif ( $input_num % 2 == 0 ) {
        # n が偶数の場合、n を 2 で割る
        $input_num = $input_num / 2;
    }
    else {
        # n が奇数の場合、n に 3 をかけて 1 を足す
        $input_num = $input_num * 3 + 1;
    }

    print $input_num . " -> ";

}

繰り返しを繰り返す

さて、当初のレギュレーションでは与える数が変わっていく・・・というものでした。

今回はループの中にループを作る「二重ループ」で実現してみました。このような二重ループは「入れ子いれこ構造」「ネスト構造」などとも呼ばれます。

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

for my $number ( 1 .. 10 ) {

    print "$number: ";

    while (1) {

        if ( $number == 1 ) {
            print "end\n";
            last;
        }
        elsif ( $number % 2 == 0 ) {

            # n が偶数の場合、n を 2 で割る
            $number = $number / 2;
        }
        else {
            # n が奇数の場合、n に 3 をかけて 1 を足す
            $number = $number * 3 + 1;
        }

        print $number . " -> ";

    }

}
1: end
2: 1 -> end
3: 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1 -> end
4: 2 -> 1 -> end
5: 16 -> 8 -> 4 -> 2 -> 1 -> end
6: 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1 -> end
7: 22 -> 11 -> 34 -> 17 -> 52 -> 26 -> 13 -> 40 -> 20 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1 -> end
8: 4 -> 2 -> 1 -> end
9: 28 -> 14 -> 7 -> 22 -> 11 -> 34 -> 17 -> 52 -> 26 -> 13 -> 40 -> 20 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1 -> end
10: 5 -> 16 -> 8 -> 4 -> 2 -> 1 -> end

二重ループについては、掛け算の「九九の表」をスクリプトで再現してみる、ってのも良いかもしれません。

Perl入学式の第2回までの範囲で書いてみるとこんな感じですかね。

スペースの扱いをもっとスマートにしたい!という人は printf 関数に挑戦してみると良いと思います。

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

print '    1  2  3  4  5  6  7  8  9' . "\n";

for my $tate (1 .. 9){
    print "$tate:";
    for my $yoko ( 1 .. 9){

        if ($tate * $yoko < 10){
            print '  ' . $tate * $yoko;
        }else{
            print  ' ' . $tate * $yoko;
        }
    }
    print "\n";
}
    1  2  3  4  5  6  7  8  9
1:  1  2  3  4  5  6  7  8  9
2:  2  4  6  8 10 12 14 16 18
3:  3  6  9 12 15 18 21 24 27
4:  4  8 12 16 20 24 28 32 36
5:  5 10 15 20 25 30 35 40 45
6:  6 12 18 24 30 36 42 48 54
7:  7 14 21 28 35 42 49 56 63
8:  8 16 24 32 40 48 56 64 72
9:  9 18 27 36 45 54 63 72 81

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

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

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

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

www.perl-entrance.org

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

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

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

わかりやすい変数名をつけて、3ヶ月後の自分を救いましょう!

docs.google.com

FizzBuzzの素晴らしさ

講義中も何度か言いましたが、FizzBuzz問題は「繰り返し」「条件分岐」というプログラムの基礎を内包した素晴らしい問題です。

是非、次回の講習までにFizzBuzzをマスターしてしまいましょう!

FizzBuzzや配列については、このブログの昨年の記事も参考にどうぞ。

sironekotoro.hateblo.jp

ピザ会のお題

ちょっと長くなったので別エントリに起こします。起こしました

sironekotoro.hateblo.jp

次回のPerl入学式 in東京は1月25日!

既にconnpassに掲載しています。皆様の参加をお待ちしています!

perl-entrance-tokyo.connpass.com

おまけ

Sublime Text 3 の Build System で (システム|ユーザー)Perl を動かす

今日は、Sublime Text 3 というテキストエディタで、書いたそばから Perl スクリプトを実行できる Build System についての解説です。

同様の記事は検索するとそれなりにあるんですが、自分用にも残しておこうと思いました。

なぜなら、ブログとかホームページサービスってよく消えるんですよね・・・

他にも、いろいろ。

だから、誰かが書いてくれたものが残っていて、検索できるからいいかな?ではなく、自分で書き残しておくことにも意味があるなぁ、と思ったんですね。

Sublime Text 3 の Build System で システム Perl を動かす

Sublime Text 3 の初期状態では、Build System に Perl はありません。悲しい。

そこで、追加していきます。

この記事を書いた環境は以下となります。

  • macOS Mojave (10.14.6)
  • Sublime Text 3 (Version 3.2.2, Build 3211)

メニューから以下のように進みます。

Tools -> Build System -> New Build System...

untitled.sublime-build というタブが開かれます。初期状態はこれだけ。

{
    "shell_cmd": "make"
}

これを以下のように編集し、保存します。ファイル名は Perl.sublime-build とします。

{
    "cmd": ["perl", "-w", "$file"],
    "file_regex": ".* at (.*) line ([0-9]*)",
    "selector":"source.perl"
}

これだけです。

早速、Sublime Text 3 の新しいタブで以下のPerlスクリプトを作成し、 Build System で動かしてみましょう。ファイル名は perl_ver.pl とします。

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

print "Perl version: " . $^V ;

特殊変数 $^V を使っています。これは実行しているPerlのバージョンを表示する、定義済みの変数です。定義済みなので my は不要です。

自分の環境(macOS Mojave)では Perl version: 5.18.4 と表示されます。

Sublime Text 3 の Build System で ユーザーPerl (plenv) を動かす

Perl入学式でもお伝えしているように、 Perl にはインストールの仕方によって「システムPerl」と「ユーザーPerl」とに分けることができます。

システムPerlとユーザPerl

OSに最初から入っているPerlを システムPerl と呼ぶことがあります。

それに対して、自分専用・開発専用のPerl環境である ユーザPerl を構築することもあります。本格的な開発においては、ユーザPerlを利用することが多くなっています。

とはいえ、単にPerlの勉強を始める、という状況であれば、システムPerlでも十分です。

Perl入学式では「システムPerl」を利用しながら、Perlを勉強していきます。

システムPerlを使うのであれば、上記の設定だけで動きます。

しかし、 plenv などで複数のユーザーPerlを入れている場合、 plenv で利用している Perl のバージョンが Build System で反映されません。

このため、ターミナルと Build System で実行する Perl バージョンが異なる、という現象が発生します。

ターミナルで実行

2019-12-19 12:10:36 ~/Dropbox
$ plenv -v
plenv 2.2.0

2019-12-19 12:11:31 ~/Desktop
$ plenv global
5.28.1

2019-12-19 12:11:36 ~/Desktop
$ perl perl_ver.pl
Perl version: v5.28.1

Sublime Text 3 の Build System で実行

Perl version: v5.18.4
[Finished in 0.0s]

同じスクリプトを動かしても、ターミナルでは 5.28.1Sublime Text の Build System では5.18.4

いくら Perl後方互換性に優れているとはいえ、なんか嫌・・・とは思いませんか?うちは思います。生まれし日、時は違えども同じPerlを使いたい!

ということで、さらに設定を進めていきます。

Sublime Text -> Preferences -> Settings と進めると、左右に分割された編集画面が開きます。この右側の Preferences.sublime-settings -- User こちらを編集します。

{
    "build_env":
        {
            "PATH": "$HOME/.plenv/shims:$PATH"
        },
}

この設定を追加します。Sublime Text 3 の設定ファイルはJSON形式なので、この設定を末尾に書く場合には最後の , カンマを取り除いてください。

この設定を加えることで、 Build System のPerlがPlenvで設定したPerlを使ってくれるようになります。(追加後は Sublime Text3 の再起動が必要かも?)

これは、Sublime Text の Build Systemが .bashrc や .bash_profile 、はたまた .zshrc などの、いわゆるシェルで設定した環境変数を読み込んでいないため、ということのようです。

とりあえず、これで任意のバージョンのユーザーPerlSublime Text で動かすことができるようになりました。

余談1

plenv 、複数の Perl 入れらるのってどんなメリットがあるのかなー?とかつては思っていました。

お仕事の現場では、サービスの導入時期によって利用しているPerlのバージョンが異なっていることがあります。

  • サービスA: Perl 5.8.8
  • サービスB: Perl 5.16.4
  • サービスC: Perl 5.20.0

こんな感じで。

最新のPerlのバージョンに合わせていくのが理想ではありますが、時間、お金、人員などの兼ね合いでなかなかそうもいかない場合も多い・・・ものです(一般化

未だ現役なPerl5.8 & MySQL4.0とどう戦うか? ライブドアブログが生んだカオスとレガシーからの脱却 - ログミーTech

Perl がいかに互換性に優れた言語とはいえ、セキュリティ対策などでバージョン間で差異が発生することがあります。

そういったとき、任意のバージョンの Perl を切り替えて開発していきたい、となるわけですね。

余談2

Perl入学式では、開講時期の都度、利用しやすいエディタを受講生の方に勧めています。

私が受講生だった2013年当時は Sublime Text 2 でした。

https://github.com/perl-entrance-org/workshop-2013-01/blob/master/02.introduction/slide.md#sublime-text2

特にこだわりのない方は, Sublime Text2を試してみることをおすすめします.

これがうちと Sublime Text との出会いです。2014年に70$でライセンスを購入。今は80$になってますね。

とても使いやすく、十分に元を取れた投資でした。

余談3

Perlスクリプトを成形するPerlTidyというものがあり、それをSublime Text3から呼び出す Sublime PerlTidyというpackage(sublimetextのアドオン)があります。

github.com

これを動かした際にも、パスが通っていないよ!との警告が出たので、Sublime Text -> Preferences -> SettingsPreferences.sublime-settings -- Userを以下のように追加しています。Sublime Text3 の再起動が必要でした。

       "PATH": "/usr/local/bin/:$HOME/.plenv/shims:$PATH"

全体としてはこのようになっています。

{
    "build_env":
    {
        "PATH": "/usr/local/bin/:$HOME/.plenv/shims:$PATH"
    },
    "close_windows_when_empty": false,
    "color_scheme": "Packages/Color Scheme - Default/Sixteen.sublime-color-scheme",
    "font_size": 19,
    "ignored_packages":
    [
        "Vintage"
    ],
    "tab_size": 4,
    "translate_tabs_to_spaces": true,
    "PATH": "/usr/local/bin/:$HOME/.plenv/shims:$PATH"
}