sironekotoroの日記

Perl で楽をしたい

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