YAPC::Japan::Online 2022 から 2週間が経ちました
興奮冷めやらぬ・・・と言いたいのですが、興奮もそこそこに業務の嵐に飲み込まれた2週間でした。
で、やろうと思っていた Perl入学式プレゼンツのお題やるのすっかり忘れておりました。
和暦西暦変換
こんな感じです。
早速やっていきます。もちろん、Perl 入学式の範囲で!
各元号のスタート年は 元号一覧(日本)) を参考とし、以下とします。
- 慶応:1865
- 明治:1868
- 大正:1912
- 昭和:1926
- 平成:1989
- 令和:2019
まずはやってみる
まずは簡単に、愚直にやってみます。
テストケースは慶応1年(1865年)と慶応2年(1866年)です。
#!/usr/bin/env perl use strict; use warnings; use utf8; use Encode qw/decode/; my %era = ( keiou => 1865, ); my $input = decode( 'utf8', $ARGV[0] ); if ( $input =~ /慶応(\w+)年/ ) { my $start_year = $era{keiou}; print $era{keiou} + $1 - 1; }
$ perl wareki-seireki.pl 慶応1年 1865 $ perl wareki-seireki.pl 慶応2年 1866
順調ですね。
簡単に、とは言っていますが、漢字の年号を正しく正規表現で引っ掛けるために
use utf8; use Encode qw/decode/;
しています。
ここは2021年から追加された Perl と日本語 でやったところです。追加しておいてよかったー
同じように明治も追加しておきます。
コードと実行結果
#!/usr/bin/env perl use strict; use warnings; use utf8; use Encode qw/decode/; my %era = ( keiou => 1865, meiji => 1868 ); my $input = decode( 'utf8', $ARGV[0] ); if ( $input =~ /慶応(\w+)年/ ) { my $start_year = $era{keiou}; print $era{keiou} + $1 - 1; } elsif ( $input =~ /明治(\w+)年/ ) { my $start_year = $era{meiji}; print $era{meiji} + $1 - 1; }
$ perl wareki-seireki.pl 明治1年 1868 $ perl wareki-seireki.pl 明治2年 1869
よかよかー
元年対応
このまま大正、昭和と追加していくか・・・というところで思い出します。
なんか、元年対応してたな、と。
元年対応を盛り込みます。
コードと実行結果
#!/usr/bin/env perl use strict; use warnings; use utf8; use Encode qw/decode/; my %era = ( keiou => 1865, meiji => 1868 ); my $input = decode( 'utf8', $ARGV[0] ); if ( $input =~ /慶応(元|\w+)年/ ) { my $start_year = $era{keiou}; if ( $1 =~ /元|1/ ) { print $era{keiou}; } else { print $era{keiou} + $1 - 1; } } elsif ( $input =~ /明治(元|\w+)年/ ) { my $start_year = $era{meiji}; if ( $1 =~ /元|1/ ) { print $era{meiji}; } else { print $era{meiji} + $1 - 1; } }
$ perl wareki-seireki.pl 慶応元年 1865 $ perl wareki-seireki.pl 慶応1年 1865 $ perl wareki-seireki.pl 慶応2年 1866 $ perl wareki-seireki.pl 明治元年 1868 $ perl wareki-seireki.pl 明治1年 1868 $ perl wareki-seireki.pl 明治2年 1869
大丈夫そうですね。
サブルーチンにまとめてみる
さて、このまま大正、昭和と追加して行ってもいいんですが、なんかコードに似たようなところがあります。
if 文の中身のところですね。
if ( $input =~ /慶応(元|\w+)年/ ) { my $start_year = $era{keiou}; if ( $1 =~ /元|1/ ) { print $era{keiou}; } else { print $era{keiou} + $1 - 1; } } elsif ( $input =~ /明治(元|\w+)年/ ) { my $start_year = $era{meiji}; if ( $1 =~ /元|1/ ) { print $era{meiji}; } else { print $era{meiji} + $1 - 1; } }
似たようなところはサブルーチンにまとめるとコードの見通しもよくなりそう?
ってことでやってみます。
そして、ここに来て my $start_year = $era{keiou};
を全く使っていなかったことに気づきます・・・削除。
コードと実行結果
#!/usr/bin/env perl use strict; use warnings; use utf8; use Encode qw/decode/; my %era = ( keiou => 1865, meiji => 1868 ); my $input = decode( 'utf8', $ARGV[0] ); if ( $input =~ /慶応(元|\w+)年/ ) { calc( 'keiou', $1 ); } elsif ( $input =~ /明治(元|\w+)年/ ) { calc( 'meiji', $1 ); } sub calc { my $era_name = shift; my $year = shift; if ( $1 =~ /元|1/ ) { print $era{$era_name}; } else { print $era{$era_name} + $1 - 1; } }
まとまりましたね。
ここらで ok としましょうか・・・と言ったところで気づきます。
サブルーチンの中に $1
がある、ということに。
修正します。
コードと実行結果
#!/usr/bin/env perl use strict; use warnings; use utf8; use Encode qw/decode/; my %era = ( keiou => 1865, meiji => 1868 ); my $input = decode( 'utf8', $ARGV[0] ); if ( $input =~ /慶応(元|\w+)年/ ) { calc( 'keiou', $1 ); } elsif ( $input =~ /明治(元|\w+)年/ ) { calc( 'meiji', $1 ); } sub calc { my $era_name = shift; my $year = shift; if ( $year =~ /元|1/ ) { print $era{$era_name}; } else { print $era{$era_name} + $year - 1; } }
まだまだ!
そして、まだ同じようなコードが見えます。
if ( $input =~ /慶応(元|\w+)年/ ) { calc( 'keiou', $1 ); # 中略 elsif ( $input =~ /明治(元|\w+)年/ ) { calc( 'meiji', $1 );
変化しているのは 慶応
と keiou
、 明治
と meiji
だけで、あとは同じコードだよね・・・?
というわけで、もう元号を直接ハッシュのkeyに設定してしまいます。こんな感じで。
my %era = ( 慶応 => 1865, 明治 => 1868 );
コードと実行結果
#!/usr/bin/env perl use strict; use warnings; use utf8; use Encode qw/decode/; my %era = ( 慶応 => 1865, 明治 => 1868 ); my $input = decode( 'utf8', $ARGV[0] ); if ( $input =~ /(\w{2,2})(元|\w+)年/ ) { calc( $1, $2 ); } sub calc { my $era_name = shift; my $year = shift; if ( $year =~ /元|1/ ) { print $era{$era_name}; } else { print $era{$era_name} + $year - 1; } }
最後に
これだけ短くなると、もうサブルーチン必要ないのでは?となるので、まとめてしまいます。
それと、元年対応のところの正規表現 /(元|\w+)年/ )
も重複してるので、こちらも整理。
#!/usr/bin/env perl use strict; use warnings; use utf8; use Encode qw/decode/; my %era = ( 慶応 => 1865, 明治 => 1868, 大正 => 1912, 昭和 => 1926, 平成 => 1989, 令和 => 2019, ); my $input = decode( 'utf8', $ARGV[0] ); if ( $input =~ /(\w{2,2})(\w+)年/ ) { if ( $2 eq '元' || $2 == 1 ) { print $era{$1}; } else { print $era{$1} + $2 - 1; } }
Perl入学式範囲外 だったら?
正規表現には名前付きキャプチャというものがあります。これを使って $1
, $2
より可読性の高いコードにします。
コードと実行結果
#!/usr/bin/env perl use strict; use warnings; use utf8; use Encode qw/decode/; my %era = ( 慶応 => 1865, 明治 => 1868, 大正 => 1912, 昭和 => 1926, 平成 => 1989, 令和 => 2019, ); my $input = decode( 'utf8', $ARGV[0] ); if ( $input =~ /(?<era>\w{2,2})(?<year>\w+)年/ ) { if ( $+{year} eq '元' || $+{year} == 1 ) { print $era{$1}; } else { print $era{ $+{era} } + $+{year} - 1; } }
とはいえ、3ヶ月後にこのコードを見た自分はちゃんと理解できるだろうか・・・?
プログラム書いてると、自分の馬鹿さ加減が身に染みてるわけです。
というわけで、ここまで書いてきてなんですが、理解を優先して一番最初の愚直なコードにすることもあるかもです。
さらに
やっぱ、漢数字対応はほしいよね?と思って手を出したら、こっちの方が時間かかりましたね・・・よくあることです。
コードと実行結果
#!/usr/bin/env perl use strict; use warnings; use utf8; use Encode qw/decode/; my %era = ( 慶応 => 1865, 明治 => 1868, 大正 => 1912, 昭和 => 1926, 平成 => 1989, 令和 => 2019, ); my %kanji_num = ( 一 => 1, 二 => 2, 三 => 3, 四 => 4, 五 => 5, 六 => 6, 七 => 7, 八 => 8, 九 => 9, 十 => 10, ); my $input = decode( 'utf8', $ARGV[0] ); if ( $input =~ /^(?<era>\w{2,2})(?<year>\w+)年$/ ) { my $era_name = $+{era}; my $year = $+{year}; if ( $year =~ /一|二|三|四|五|六|七|八|九|十/ ) { print $era{$era_name} + kanji_num($year) - 1; } elsif ( $year eq '元' || $year == 1 ) { print $era{$1}; } else { print $era{$era_name} + $year - 1; } } sub kanji_num { my $kan_suuji = shift; # 10の桁、1の桁がある場合(ex.二十二) if ( $kan_suuji =~ /^(\w)十(\w)$/ ) { return $kanji_num{$1} * 10 + $kanji_num{$1}; } # 10の桁が1の場合(ex.十二) elsif ( $kan_suuji =~ /^十(\w)$/ ) { return 10 + $kanji_num{$1}; } # 10の桁のみ場合(ex.二十) elsif ( $kan_suuji =~ /^(\w)十$/ ) { return $kanji_num{$1} * 10; } # 1桁のみと十のみの場合(ex.二) elsif ( $kan_suuji =~ /^(\w)$/ ) { return $kanji_num{$1}; } }