日本語(EUC-JP)の substr
今の Perl(5.8 以降)は文字列が utf8 だったら標準添付の substr() を使って終了なネタなわけだが、わけあって EUC-JP な文字列で日本語も 1 文字と数えて substr をするサブルーチンを考えてみた。今さら。
かの有名な Perlメモに日本語(EUC-JP)を含む文字列の split というのが(文字単位に分割する)あるんだけど、これを参考に euc_substr() というサブルーチンを書いてみた。一応 offset だけでもイケル。
#!/usr/local/bin/perl # this script is written in EUC-JP use strict; use warnings; use Benchmark; my $ascii = qr{ [\x00-\x7F] }xms; my $two_bytes = qr{ [\x8E\xA1-\xFE][\xA1-\xFE] }xms; my $three_bytes = qr{ \x8F[\xA1-\xFE][\xA1-\xFE] }xms; my @euc_str = ( 'となりの客は良く柿食う客だ', '500円1000円1万円', '「日本語」は英語で Japanese です', ); timethese( 50_000, { euc_substr => sub { for my $str (@euc_str) { my $substr = euc_substr( $str, 0, 5 ); } }, ksubstr => sub { for my $str (@euc_str) { my $substr = ksubstr( $str, 0, 5, 'euc' ); } }, } ); sub euc_substr { my $str = shift; my $offset = shift; my $length = shift; my(@chars) = ( $str =~ /($ascii|$two_bytes|$three_bytes)/og ); # if ($length) { # return ( join( "", @chars[ $offset .. $length ] ) ); # 恥ずかしいので直しておく if ( defined $length and ( $offset + $length ) <= $#chars ) { return ( join( "", @chars[ $offset .. ( $offset + $length - 1 ) ] ) ); } else { return ( join( "", @chars[ $offset .. $#chars ] ) ); } } sub ksubstr { ... }
別に優劣を競おうってわけじゃないんだけど、ksubstr() っていうサブルーチンが perl-lib.pl ていうライブラリに収録されているので、比較ベンチマークしてみた。euc_substr() は正規表現で配列に格納してスライス、一方 ksubstr() は 一文字づつ文字を見ているようだ。
Benchmark: timing 50000 iterations of euc_substr, ksubstr... euc_substr: 2 wallclock secs ( 2.43 usr + 0.00 sys = 2.43 CPU) @ 20578.78/s (n=50000) ksubstr: 7 wallclock secs ( 6.38 usr + 0.00 sys = 6.38 CPU) @ 7833.54/s (n=50000)
ksubstr は unpack( "C*", $str ); にすればもっと早いような気もするが、それはまた別の話で、使ってみた感じはどっちもそんなに重くはなさそうだ。euc_substr() は $offset と $length を全く validation していないけど、必要なら Scalar::Util::looks_like_number でも使えば良いかと思われ。
まあ、utf8 使うのが最も良い解決方法な気はするけど、EUC-JP な文字列をそのまま処理するとなるとこんな感じかなあ。もっと簡単かつ早いコードがあれば教えて欲しいです。