ラベル perl の投稿を表示しています。 すべての投稿を表示
ラベル perl の投稿を表示しています。 すべての投稿を表示

2011年2月5日土曜日

[Perl] ハッシュのスライスとハッシュリファレンスのスライス

ハッシュからいくつかの値を配列として取りたい時に使えるスライスの紹介です。

my %hash = (
    key1 => 1,
    key2 => 2,
    key3 => 3,
);

# 普通に key2 と key3 の値を取る。
my @values1 = ($hash{key2}, $hash{key3});
print join(', ', @values1); # 2, 3

# スライスで key2 と key3 の値を取る。
my @values2 = @hash{qw/key2 key3/};
print join(', ', @values2); # 2, 3

$hash を都度タイプしなくていいので楽ですね。
続いて、ハッシュリファレンスだった場合の書き方。

my $hash_ref = {
    key1 => 1,
    key2 => 2,
    key3 => 3,
};

# 普通に key2 と key3 の値を取る。
my @values1 = ($hash_ref->{key2}, $hash_ref->{key3});
print join(', ', @values1); # 2, 3

# スライスで key2 と key3 の値を取る。
my @values2 = @{$hash_ref}{qw/key2 key3/};
print join(', ', @values2); # 2, 3

$hash_ref をデリファレンスしつつスライスした配列を返すため、
@{$hash_ref}{qw/key2 key3/}
という書き方になります。
続いてちょっと発展。

my @saiyans = (
    {name => 'ラディッツ', power => 1500,  is_prince => 0},
    {name => 'ナッパ',     power => 4000,  is_prince => 0},
    {name => 'ベジータ',   power => 18000, is_prince => 1},
);

# @saiyans から name をキーにした power のハッシュがほしい。
my %power = map {@{$_}{qw/name power/}} @saiyans;

# %power = (
#    'ラディッツ' => 1500,
#    'ナッパ'     => 4000,
#    'ベジータ'   => 18000,
# );

参考: perldata - Perl のデータ型(Slices)

2011年1月25日火曜日

[Perl] FizzBuzz Code Golf

FizzBuzz の Code Golf サイトを会社の方経由で見つけたので、不意に Perl でやってみました。

まず普通にやってみる。(64 bytes)
print$_%15?$_%3?$_%5?$_:"Buzz":"Fizz":"FizzBuzz","\n"for(1..100)

ダブルクォートとFizzの重複を消してみる。(57 bytes)
print$_%3?$_%5?$_:Buzz:Fizz,$_%15?"":Buzz,"\n"for(1..100)

Buzzの重複を消してみる。(56 bytes)
print(($_%3?"":Fizz).($_%5?"":Buzz)||$_,"\n")for(1..100)

3項演算子に無駄があるので削ってみる。(52 bytes)
print((Fizz)[$_%3].(Buzz)[$_%5]||$_,"\n")for(1..100)

for のカッコを削ってみる。(51 bytes)
print((Fizz)[$_%3].(Buzz)[$_%5]||$_,"\n")for 1..100

頑張ってみたけどここで限界…。
"\n" と for の間のカッコは外せるけど、print が正しく動かなくなってしまう。
48 bytes が 1 位ってどんなコードなんだろうか。。

----- 2011/01/25 追記 -----

どうやらこれが 48 bytes のコードのようです!
print+(Fizz)[$_%3].(Buzz)[$_%5]||$_,$/for 1..100
なるほど、"\n" をセパレータ変数の $/ にしつつ、print() のカッコを無くして + で逃げる。
ここから更には厳しそうですねぇ…

2010年7月11日日曜日

[Perl] for と foreach は同じ。

配列の繰り返し処理でよく使われる foreach。
実は foreach を for と書き変えても全く同じ動作をしてくれます。

例) 下記のコードは全く同じ処理
foreach my $var (@vars) {
    print $var;
}
for my $var (@vars) {
    print $var;
}

foreach は for のエイリアスなので、どちらを使ってもOKです。
perldoc にもある通り、読みやすさのために foreach、簡潔さのために for な感じでしょうか。
http://perldoc.jp/docs/perl/5.8.8/perlsyn.pod (Foreach Loops for foreach)

一応ベンチマークを取ってみました。(ついでに個人的によく使う map も)
ベンチマークコードです。
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark qw/cmpthese timethese/;

cmpthese timethese(10000, {
    'foreach' => sub { foreach (1 .. 10000) {} },
    'for'     => sub {     for (1 .. 10000) {} },
    'map'     => sub { map {;} (1 .. 10000)    },
});

そして実行結果。
Benchmark: timing 10000 iterations of for, foreach, map...
for:  6 wallclock secs ( 6.07 usr +  0.00 sys =  6.07 CPU) @ 1647.45/s (n=10000)
foreach:  6 wallclock secs ( 6.14 usr +  0.00 sys =  6.14 CPU) @ 1628.66/s (n=10000)
map:  8 wallclock secs ( 7.52 usr +  0.01 sys =  7.53 CPU) @ 1328.02/s (n=10000)
          Rate     map foreach     for
map     1328/s      --    -18%    -19%
foreach 1629/s     23%      --     -1%
for     1647/s     24%      1%      --

foreach と for は速度的にもほとんど同じですね。(map は遅いなぁ)

2010年4月10日土曜日

[Perl] HTML::Template::Pro で found strange parameter

HTML::Template::Pro で TMPL_LOOP を扱っている時にちょっと注意すること。

通常、TMPL_LOOP にアサインするのは HASHREF の ARRAYREF ですが、そうではないときに以下のような warning が出ます。

HTML::Template::Pro:_lowercase_keys:in param_tree: found strange parameter $param while hash was expected

どこかからか受け取った HASHREF の ARRAYREF をそのまま TMPL_LOOP に渡しているようなコードで、
受け取り先の出力仕様が突然変わったときなどに原因追求が困難になります。

例えば、Module を他の開発者が担当しており、@LOOP_VARS が HTML::Template::Pro に渡されていることを知らないとします。

use strict;
use warnings;
use HTML::Template::Pro;

package Module;
our @LOOP_VARS = (
    {key1 => 'value1'}, # 通常の渡し方。問題なし。
    {key2 => [1,2,3]},  # HASHREF の value が ARRAYREF になった。問題あり。
    'key3',             # HASHREF が SCALAR になった。問題あり。
);

package main;
my $tmpl = HTML::Template::Pro->new(filename => 'test.tmpl');
$tmpl->param(loop => \@Module::LOOP_VARS);

実行結果はこんな感じです。

HTML::Template::Pro:_lowercase_keys:in param_tree: found strange parameter 1 while hash was expected at hoge.pl line 14
HTML::Template::Pro:_lowercase_keys:in param_tree: found strange parameter 2 while hash was expected at hoge.pl line 14
HTML::Template::Pro:_lowercase_keys:in param_tree: found strange parameter 3 while hash was expected at hoge.pl line 14
HTML::Template::Pro:_lowercase_keys:in param_tree: found strange parameter key3 while hash was expected at hoge.pl line 14

ご注意ください。

2010年3月7日日曜日

[Perl] 複数行をコメントアウトする

Perl コードを書いていて、複数行をコメントアウトしたいときがでてきます。
全ての行頭に # をタイプしてもいいのですが、いかんせん手間がかかります。

そこで、pod を使うことで簡単にコメントアウトすることができます。
=for comment
ここからコメント
ほげほげ
ここまでコメント
=cut

=for comment ではなく =pod から始めてもいいのですが、=for comment は伝統的に POD のトランスレータも無視してくれるようになっています。

詳しくはこのあたりのドキュメントを。
http://perldoc.jp/docs/perl/5.10.0/perlpod.pod

[プログラミング] 面接官になったら尋ねるべき質問実例集

RSSリーダーで見つけた記事です。
回答するのが流行っている?ようなので自分も Perl で解いてみました。

プログラマーの力量を見極める--面接官になったら尋ねるべき質問実例集
http://japan.zdnet.com/sp/feature/07tenthings/story/0,3800082984,20409456-2,00.htm

# 数値Xのフィボナッチ数を計算する。
sub fibonacci {
    my $x = shift;
    return $x if $v == 0 or $x == 1;
    return fibonacci($x-2) + fibonacci($x-1);
}
負の値についてもアルゴリズムがあるようです。

# 指定された数値Xが素数であるかどうかを判定する。
sub is_prime {
    my $x = shift;
    for (2 .. $x-1) {
       return 0 if $x % $_ == 0;
    }
    return 1;
}
引数の平方根以上の数値では判定しないようにしたり、偶数は2の倍数なので無視したり、素数で無いものは無視したりすることでもっと効率は良く出来ますが、面倒なのでこの辺でw

# ループを使わずに配列の順序を逆にする。
sub array_reverse {
    return @_ ? ($_[$#_], array_reverse(@_[0..$#_-1])) : ();
}
pop() を使えばもっとシンプルになりますが、組み込み関数を使わないでやってみました。

# FizzBuzz
sub fizzbuzz {
    my $x = shift;
    return 'FizzBuzz' if $x % (3 * 5) == 0;
    return 'Fizz'     if $x % 3 == 0;
    return 'Buzz'     if $x % 5 == 0;
    return $x;
}
そのままですね。。

2009年12月26日土曜日

[Perl] %d でフォーマットする時の注意点 (printf/sprintf)

Perl で文字列をフォーマットする時に使う printf/sprintf。
大きな数値に対して %d でフォーマットすると、思わぬ結果になることがあるのでご注意を。
printf("2147483646:  %d\n", 2147483646);  # 2147483646
printf("2147483647:  %d\n", 2147483647);  # 2147483647
printf("2147483648:  %d\n", 2147483648);  # -2147483648
printf("2147483649:  %d\n", 2147483649);  # -2147483647
上記の様に、2147483648 を越えてから符号がマイナスになっています。
これは、%d が SIGNED INTEGER (-2147483648~2147483647) を意味しているからです。
2147483648 を更に大きくしていくと徐々に -1 に近づいていき、
これ以上大きくなると以下の様に -1 以外返さなくなります。
printf("10000000000: %d\n", 10000000000); # -1
乗算等で知らず知らずのうちに大きな値になったものについては要注意です。
解決策は、%d ではなく %s を使うこと。
大きな数値でもそのまま文字列としてフォーマットされます。
printf("10000000000: %s\n", 10000000000); # 10000000000
地味にハマるところなのでご注意を。

2009年12月12日土曜日

[Perl] 文字のバイトコードを調べる

文字のバイトコードが知りたい時はこんな感じで出力。
my $string = q/あいうえお/;
print unpack("H*", $string);
# ワンライナーならこんな感じ
# perl -le 'print unpack("H*", $ARGV[0]);' 'あいうえお'
あとは正規表現で使うなりいろいろどうぞ。

2009年11月13日金曜日

[文字] 漢数字のソート

漢数字は思い通りにソートできない。
my @chars = qw/一 二 三 四/;
print join(', ', sort @chars); # 一, 三, 二, 四
文字コード順が次のようになっているので、
正しく並べたい場合は自前でソートしないとダメ。
字 区 点 JIS  SJIS EUC  UTF-8  UTF-16
一 16 76 306C 88EA B0EC E4B880 4E00
二 38 83 4673 93F1 C6F3 E4BA8C 4E8C
三 27 16 3B30 8E4F BBB0 E4B889 4E09
四 27 45 3B4D 8E6C BBCD E59B9B 56DB

2009年11月6日金曜日

[Perl] 既存の関数を上書きする

主にPerlのテストコードを書くとき、
既存の関数にテスト用の振る舞いをさせたいことがたまにある。
その時はこんな感じで上書くべし。
sub hoge { print 'original'; }

no warnings 'redefine'; # warningが気になる場合
*hoge = sub { print 'override'; };
日付を扱っているコードのテストを書くときはこんな感じで。
use Date::Calc;

*Date::Calc::Today = sub { return (2000,1,1) };
my @today_dummy = Date::Calc::Today();

2009年10月18日日曜日

[Perl] mod_perl で Constants を操作する際の注意点

mod_perl で Constants を操作したときにはまったのでメモ。
#!/usr/bin/perl
use strict;
use warnings;

use CGI;
use Data::Dumper;

use constant {
    HASH_REF_LIST => [
        { id => 1 },
        { id => 2 },
        { id => 3 },
    ],
};

my $cgi = CGI->new();
my $id  = $cgi->param('id') || '';

# 定数をコピーしてから操作したので安全のつもり
my @array = @{HASH_REF_LIST()};
for (@array) {
    $_->{selected} = 1 if ($_->{id} == $id);
}

print $cgi->header;
print Dumper(\@array);
id クエリを受け取って、定数内のキーと一致すれば、
そのキーに selected フラグを付ける。
まあプルダウンメニューとかで使われそうなコードです。

これを mod_perl で適当に id を変えて実行していると気づきます。
id と一致していないキーに seleted が付く時があると。。

原因は @array に定数を完全コピーをしたつもりが、
定数内がリファレンスになっているので外側だけしかコピーできていない点。

リファレンスを操作しちゃっているので、
当然それもキャッシュされて残ってしまいます。

解決策は、Storable::dclone とかでクローンを作るか、
自前でデリファレンスしてコピーする。

よく見ればわかりますが、なかなか気づきにくいのでご注意を。

2009年10月14日水曜日

[Perl] 配列・ハッシュが空かを調べる

配列・ハッシュが空かを調べる。
普通に if で。defined ではダメです。
※defined はスカラーにのみ有効。
my @array = ();
my %hash  = ();
if (@array) {
    # not empty
}
if (%hash) {
    # not empty
}
[参考] http://perldoc.jp/docs/perl/5.8.8/perldiag.pod

2009年7月29日水曜日

[Perl] 480bytes BBS

昔作ったBBSのコード
#!/usr/bin/perl
$_=(split'=',$ENV{QUERY_STRING})[1];tr/+/ /;s/%([\da-f]{2})/pack'H2',$1/ieg;
s/\0//g;s/&/&amp;/g;s/</&lt;/g;s/>/&gt;/g;s/"/&quot;/g;s/'/&#39;/g;$l='bbs';
open F,'+'.((-e$l)?'<':'>').$l;flock F,2;@d=<F>;if($_){$_.='/'.localtime."\n";
unshift@d,$_;seek F,0,0;print F join'',@d[0..19];truncate F,tell F;}close F;
print"Content-Type:text/html;charset=euc-jp\n\n<html><body><form><input type=".
"text name=n><input type=submit></form>".join('<hr>',@d)."</body></html>";
拡張子をcgiにして実行権限をつければ一応動きます。

2009年7月10日金曜日

[Perl] 文字列の最初と最後に正しくマッチ

文字列のマッチングで「^」から「$」までのような正規表現を書くことがありますが、
実は文字列の最初と最後という意味ではないことに注意。

メタ文字 意味
^ 行頭
$ 行末、または改行の直前
\A 文字列の先頭(1文字目の手前)
\Z 文字列の最後(または改行の直前)
\z 文字列の最後

「^」「$」だと、改行を含む文字列の場合に意図した動作をしなくなります。
直感的に扱うのであれば「\A」「\z」を使うようにするのがおすすめです。
うーん、地味にややこしい。

[Perl] スペースを正しく取り除く

PerlであるEUC-JPの文字列から全角スペースを取り除いてみる。

my $original = ' ぁ?';

my $str1 = $original;
$str1 =~ s/\xA1\xA1//g;
print qq(str1: $str1\n);

my $str2 = $original;
$str2 = Encode::decode('euc-jp', $str2);
$str2 =~ s/ //g;
$str2 = Encode::encode('euc-jp', $str2);
print qq(str2: $str2\n);

# 本スクリプトの文字コードはEUC-JPとする
2通りのやり方結果どちらも「ぉ」が出力される。

解説

「 ぁ?」をバイトコードで表すと「\xA1\xA1\xA4\xA1\xA1\xA9」。
全角スペースは「\xA1\xA1」なので、「\xA1\xA1\xA4\xA1\xA1\xA9」の
赤字部分がトリミングされて「\xA4\xA9」が残る。
「\xA4\xA9」は「ぉ」なのだ!!

回避方法

バイト単位で処理されるとこうなってしまうので、、
EUC-JP文字を1字ずつマッチングさせていく必要がある。
my $char_eucjp_regexp = '(?:[\x00-\x7F]|[\x8E\xA1-\xFE][\xA1-\xFE]|\x8F[\xA1-\xFE][\xA1-\xFE])'; # EUC-JP 1文字のバイトコード正規表現
my $original = ' ぁ?';

my $str = $original;
$str =~ s/\G($char_eucjp_regexp*?)\xA1\xA1/$1/g;
print qq(str: $str\n);

# 本スクリプトの文字コードはEUC-JPとする
これで晴れて「ぁ?」が表示される。
意外と気付かないまま書いているケースがあるので注意しよう。