sironekotoroの日記

Perl で楽をしたい

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力を高めていきたいですね。