perlfaq4 - データ操作
FAQのこのセクションでは、数値、日付、文字列、配列、ハッシュその他の データの取り扱いに関する質問に回答しています。
内部的には、あなたの使っているコンピュータは浮動小数点数を 2 進数を 使って表現しています。 (2 のべき乗のような) デジタルなコンピュータは全ての数値を正確に 保管することはできません。 実数は処理中に精度が落ちることがあります。 これはコンピュータがどのように数値を保管するかの問題で、Perl だけではなく 全てのコンピュータ言語に影響を与えます。
perlnumber には、数値表現と変換に関する不愉快な詳細が記されています。
10 進数の桁数を制限するには、printf
や sprintf
の関数が使えます。 更なる詳細については "Floating Point Arithmetic" を参照してください。
printf "%.2f", 10/3;
my $number = sprintf "%.2f", 10/3;
int()
はほぼ確実に正しく動作しています。 これは、数値というものがあなたの考えているものと違うからです。
まず、"Why am I getting long decimals (eg, 19.9499999999999) instead of the numbers I should be getting (eg, 19.95)?" に対する答えを参照してください。
例えば、これは:
print int(0.6/0.2-2), "\n";
ほとんどのコンピュータでは 1 ではなく 0 を表示します; 0.6 や 0.2 と言った単純な数値であっても、浮動小数点数で正確に表現できません。 さきほどあなたが "3" と考えたものは、実際には 2.9999999999999995559 と いったものです。
(brian d foy によって寄贈されました)
おそらく文字列を数値に変換しようとしているのでしょうが、Perl は 10 進数しか変換しません。 Perl が文字列を数値に変換するとき、先頭の空白とゼロは無視して、残りの 数字は 10 進数であると仮定します:
my $string = '0644';
print $string + 0; # prints 644
print $string + 44; # prints 688, certainly not octal!
この問題は普通コマンドライン引数として 8 進数を使う同じ名前の Unix コマンドがある Perl 組み込み関数に関わります。 この例では、コマンドラインの chmod
は、最初の引数として 8 進数を 求めているので、最初の引数が 8 進数だと分かっています:
%prompt> chmod 644 file
もし同じリテラルの数字 (644) を Perl で使いたいなら、 先頭に 0
を付けるか oct
を使うことで Perl にこれを 8 進数で 扱うように教える必要があります:
chmod( 0644, $file); # right, has leading zero
chmod( oct(644), $file ); # also correct
この問題は、Perl が文字列として考えているところ、例えば @ARGV
のコマンドライン引数から数字を持ってくる場合に起こります:
chmod( $ARGV[0], $file); # wrong, even if "0644"
chmod( oct($ARGV[0]), $file ); # correct, treat string as octal
使っている値があなたの考えている形と一致しているかを確認するために、 いつでも値を 8 進表記で表示することでチェックできます。 8 進数と 10 進数で表示します:
printf "0%o %d", $number, $number;
int()
は 0 へ向かって丸めを行うことを思い出してください。 特定の桁数で丸めを行うには、sprintf()
や printf()
を使うことが 通常はもっとも簡単なやり方です。
printf("%.3f", 3.1415926535); # prints 3.142
(標準 Perl 配布キットの一部である)POSIX
モジュールは ceil()
、 floor()
、そしてその他の数学的な関数や三角関数の多くを実装しています。
use POSIX;
$ceil = ceil(3.5); # 4
$floor = floor(3.5); # 3
perl の 5.000 から 5.003 では、三角関数は Math::Complex
モジュールの中で 実行されていました。 5.004 では、Math::Trig
モジュール(標準 Perl 配布キットの一部です)が 三角関数を実装しています。 内部的にはこれは Math::Complex
を使っていて、一部の関数は実数値を複素数領域へ 変化させることができます。 2 の inverse sine がその一例です。
金融に関係するアプリケーションにおいては、丸めはきちんとした実装を 必要とするかもしれません。 そして、丸めの方法は適切に使われるべきものです。 この場合、Perl が使っているシステムによる丸めを信用すべきではなく、 自分自身で丸め関数を実装するようにすべきでしょう。
なぜかを見るために、中間点反復に関する問題があるということに注意しましょう:
for ($i = 0; $i < 1.01; $i += 0.05) { printf "%.1f ",$i}
0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7
0.8 0.8 0.9 0.9 1.0 1.0
Perl を責めないでください。 これはCでも同じことなのです。 IEEE ではこのようにすることを述べています。 Perl での数値は絶対値で 2**31(32 ビットマシンの場合)以下の場合の整数値であれば 数学的な整数と同じように振る舞います。 それ以外の数値は恩恵を受けません。
Perl ではいつものことですが、これを行うには複数の方法があります。 以下は、一般的な数値表現の変換を行うための手法のいくつかの例です。 これは完全性よりも説明性を意図しています。
perlfaq4 の後の方での例では CPAN にある Bit::Vector
を使っています。 perl 組み込みの関数よりも Bit::Vector
を選択する理由は、 どんな大きさの数でも動作し、いくつかの操作では速度のために最適化されていて、 少なくともいくらかのプログラマにとっては表記がわかりやすいからです。
0x
表記による perl の組み込み変換を使って:
$dec = 0xDEADBEEF;
hex
関数を使って:
$dec = hex("DEADBEEF");
pack
を使って:
$dec = unpack("N", pack("H8", substr("0" x 8 . "DEADBEEF", -8)));
CPAN の Bit::Vector
モジュールを使って:
use Bit::Vector;
$vec = Bit::Vector->new_Hex(32, "DEADBEEF");
$dec = $vec->to_Dec();
sprintf
を使って:
$hex = sprintf("%X", 3735928559); # upper case A-F
$hex = sprintf("%x", 3735928559); # lower case a-f
unpack
を使って:
$hex = unpack("H*", pack("N", 3735928559));
Bit::Vector
を使って:
use Bit::Vector;
$vec = Bit::Vector->new_Dec(32, -559038737);
$hex = $vec->to_Hex();
そして Bit::Vector
は半端なビット数にも対応しています:
use Bit::Vector;
$vec = Bit::Vector->new_Dec(33, 3735928559);
$vec->Resize(32); # suppress leading 0 if unwanted
$hex = $vec->to_Hex();
先頭に 0 を付けることによる Perl 組み込みの変換を使って:
$dec = 033653337357; # note the leading 0!
oct
function 関数を使って:
$dec = oct("33653337357");
Bit::Vector
を使って:
use Bit::Vector;
$vec = Bit::Vector->new(32);
$vec->Chunk_List_Store(3, split(//, reverse "33653337357"));
$dec = $vec->to_Dec();
sprintf
を使って:
$oct = sprintf("%o", 3735928559);
Bit::Vector
を使って:
use Bit::Vector;
$vec = Bit::Vector->new_Dec(32, -559038737);
$oct = reverse join('', $vec->Chunk_List_Read(3));
Perl 5.6 から、0b
表記を使って直接 2 進数を書くことができます:
$number = 0b10110110;
oct
を使って:
my $input = "10110110";
$decimal = oct( "0b$input" );
pack
と ord
を使って:
$decimal = ord(pack('B8', '10110110'));
より大きな文字列に対しては、pack
と unpack
を使って:
$int = unpack("N", pack("B32",
substr("0" x 32 . "11110101011011011111011101111", -32)));
$dec = sprintf("%d", $int);
# substr() is used to left pad a 32 character string with zeros.
Bit::Vector
を使って:
$vec = Bit::Vector->new_Bin(32, "11011110101011011011111011101111");
$dec = $vec->to_Dec();
sprintf
を使って(perl 5.6 以降):
$bin = sprintf("%b", 3735928559);
unpack
を使って:
$bin = unpack("B*", pack("N", 3735928559));
Bit::Vector
を使って:
use Bit::Vector;
$vec = Bit::Vector->new_Dec(32, -559038737);
$bin = $vec->to_Bin();
残りの変換 (16 進 -> 8 進、2 進 -> 16 進、など) は読者への宿題として 残しておきます。
バイナリ算術演算子の振る舞いはそれが数値に対して使われているのか 文字列に対して使われているかということに依存しています。 その演算子は文字列をビットの並びとして扱います("3"
という文字列は 00110011
というビットパターンとなります)。 この演算子はバイナリ形式に対して働きます (3
という数値は 00000011
というビットパターンとして扱われます)。
ですから、11 & 3
は数値に対する "and" として働きます(結果は 3
です)。 "11" & "3"
は文字列に対する "and" として働きます(結果は "1"
です)。
ありがちな問題は &
と |
を使ったときに、プログラマは オペランドが数値と考えているのに実際は文字列であるようなときに 起こります。 例を挙げましょう:
if ("\020\020" & "\101\101") {
# ...
}
この場合の結果は二つのナルバイトを含む文字列となります ("\020\020"
の結果です)が、これは Perl における偽の値では ありません。 以下のようにする必要があります:
if ( ("\020\020" & "\101\101") !~ /[^\000]/) {
# ...
}
Math::Matrix モジュールか、Math::MatrixReal モジュール(CPAN で入手できます)か PDL エクステンション(これも CPAN で入手できます)を使います。
配列の各要素に対して関数を呼び出して、結果を集めるにはこうします:
@results = map { my_func($_) } @array;
例えば:
@triple = map { 3 * $_ } @single;
配列の各要素に対して関数を呼び出すけれども、結果を無視するという 場合にはこうします:
foreach $iterator (@array) {
some_func($iterator);
}
ある(小さな)範囲にある整数に対して関数を呼び出すには、こうも できます:
@results = map { some_func($_) } (5 .. 25);
ただし、..
演算子がその範囲にあるすべての整数の配列を生成するということに 注意すべきでしょう。 これによって大きな範囲を使った場合に大量のメモリを消費することになります。 代わりにこうします:
@results = ();
for ($i=5; $i < 500_005; $i++) {
push(@results, some_func($i));
}
この状況は Perl5.005 で修正されました。 for
ループで ..
を使うことで、 範囲全体を生成することなく特定の範囲の繰り返しを行えます。
for my $i (5 .. 500_005) {
push(@results, some_func($i));
}
このようにしても 500,000 個の整数のリストが生成されたりはしません。
http://www.cpan.org/modules/by-module/Roman モジュールを入手しましょう。
5.004 より前のバージョンの Perl を使っているなら、srand
を プログラムの開始時点で一度呼び出してやって、乱数生成器の種を セットしてやらなければなりません。
BEGIN { srand() if $] < 5.004 }
5.004 以降のものでは開始時点で自動的に srand
を呼び出します。 二度以上 srand
を呼び出してはいけません。 乱数の質を落としてしまいます。
コンピュータは予測できる物事に関しては役に立ちますが、ランダムな ことに対してはそうではありません(それはあなたのプログラム自身のバグによって 引き起こされることですが:-) Tom Phoenix がこの問題について語っている、 http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz の "Far More Than You Ever Wanted To Know" の中の random という記事を 参照してください。 ジョン・フォン・ノイマン曰く、「決定論的手段によって 乱数を作ろうと試みる全ての人はもちろん罪深きものである。」
rand
と srand
が提供するものよりもよりランダムな数値が必要なら、 CPAN にある Math::TrulyRandom
モジュールも チェックしてみると良いでしょう。 これはあなたの使っているシステムのタイマーを乱数を生成するのに 使っていて不完全な面もありますが、十分なものです。 あなたの使うオペレーティングシステムで使えるものよりも もっと良質な擬似乱数を必要としているのなら、 http://www.nr.com にある ``Numerical Recipes in C'' を見るとよいでしょう。
二つの値の間の乱数を得るためには、まず 0 と 1 との間の乱数を得るために rand()
組み込み関数を使います。 それから、これを必要な範囲にシフトします。
rand($x)
は 0 <= rand($x) < $x
という値を返します。 従って、perl に作ってほしいものは、0 から、必要な X と Y との差 までの範囲の乱数です。
つまり、10 から 15 の範囲の値(両端を含む) を得るためには、 0 から 5 の範囲の乱数を求めて、それに 10 を加えます。
my $number = 10 + int rand( 15-10+1 ); # ( 10,11,12,13,14, or 15 )
従って、これを抽象化するために以下のサンプル関数を導き出します。 これは与えられた二つの整数を含む範囲のランダムな整数を選択します。 例えば: random_int_between(50,120)
sub random_int_between {
my($min, $max) = @_;
# Assumes that the two arguments are integers themselves!
return $min if $min == $max;
($min, $max) = ($max, $min) if $min > $max;
return $min + int rand(1 + $max - $min);
}
localtime 関数はその年の何日目であるかを返します。 引数なしの localtime は現在時刻を使います。
$day_of_year = (localtime)[7];
POSIX
モジュールも日付をその年の何日目か、または何週目かに整形します。
use POSIX qw/strftime/;
my $day_of_year = strftime "%j", localtime;
my $week_of_year = strftime "%W", localtime;
任意の日付に対してその年の何日目かを得るには、localtime の引数から 紀元からの秒数を求めるために、POSIX
の mktime
を使います。
use POSIX qw/mktime strftime/;
my $week_of_year = strftime "%W",
localtime( mktime( 0, 0, 0, 18, 11, 87 ) );
Date::Calc
モジュールはこれらを計算する二つの関数を提供します。
use Date::Calc;
my $day_of_year = Day_of_Year( 1987, 12, 18 );
my $week_of_year = Week_of_Year( 1987, 12, 18 );
以下の単純な関数を使ってください:
sub get_century {
return int((((localtime(shift || time))[5] + 1999))/100);
}
sub get_millennium {
return 1+int((((localtime(shift || time))[5] + 1899))/1000);
}
システムによっては、POSIX
モジュールの strftime()
関数が 非標準の方法で %C
フォーマット("century"だと主張されることがあります)を 使うように拡張されているかもしれません。 これは世紀ではありません。 なぜならこのようなシステムのほとんどでは、 これは 4 桁の年の上位 2 桁を示しているだけなので、 現在の世紀や千年紀を決定する信頼できる方法ではありません。
(brian d foy によって寄贈されました)
日付を単に数値として保管して、それから引き算することもできます。 しかし、人生はいつもこんな風に単純とは限りません。 フォーマットされた日付に対して作業したい場合は、 Date::Manip
, Date::Calc
, DateTime
といったモジュールが 助けになるかもしれません。
もしそれが常に同じ書式である十分に標準的な文字列であれば、それを分割して、 その部分部分を標準の Time::Local モジュールの timelocal
に渡せます。 さもなければ、CPAN にある Date::Calc
モジュールと Date::Manip
モジュールを見るべきでしょう。
(brian d foy と Dave Cross によって寄贈されました)
CPAN にある Time::JulianDay
モジュールが使えます。 しかし、本当にユリウス日がほしいのか確認してください; 多くの人々がユリウス日に関して異なる考え方を持っています。 例としては、http://www.hermetic.ch/cal_stud/jdn.htm を参照してください。
日付・時刻をユリウス日に変換できる、DateTime
モジュールを試すことも できます。
$ perl -MDateTime -le'print DateTime->today->jd'
2453401.5
あるいは準ユリウス日にも変換できます:
$ perl -MDateTime -le'print DateTime->today->mjd'
53401
あるいは年の何日目か(これがユリウス日だと考える人もいます)にも変換できます:
$ perl -MDateTime -le'print DateTime->today->doy'
31
(brian d foy によって寄贈されました)
Date モジュールの一つを使いましょう。 DateTime
モジュールは単純で、前日の同じ時刻を返します。
use DateTime;
my $yesterday = DateTime->now->subtract( days => 1 );
print "Yesterday was $yesterday\n";
Date::Calc
モジュールの Today_and_Now
関数を使うこともできます。
use Date::Calc qw( Today_and_Now Add_Delta_DHMS );
my @date_time = Add_Delta_DHMS( Today_and_Now(), -1, 0, 0, 0 );
print "@date_time\n";
ほとんどの人は日付を計算するのにカレンダーではなく時刻を使おうとしますが、 これは 1 日が 24 時間であることを仮定しています。 ほとんどの人々にとって、そうではない日が 2 日あります: 夏時間が始まる日と終わる日はこれを狂わせます。 この作業はモジュールにさせましょう。
もし完全に自分でしなければならない (あるいはこれらのモジュールが 使えない) 場合は、以下が Perl と共に配布されている Time::Local
を 使った解法です:
# contributed by Gunnar Hjalmarsson
use Time::Local;
my $today = timelocal 0, 0, 12, ( localtime )[3..5];
my ($d, $m, $y) = ( localtime $today-86400 )[3..5];
printf "Yesterday: %d-%02d-%02d\n", $y+1900, $m+1, $d;
この場合、正午から始まる日数を計算して、24 時間を引いています。 たとえばカレンダー日の長さが 23 時間や 25 時間でも、結局 カレンダーの前日を得られますが、この場合正午ではありません。 時刻については気にしないので、1 時間のずれは関係なく、最終的に 前日を得られます。
(brian d foy によって寄贈されました)
Perl 自身には決して Y2K 問題はありませんが、人々が Y2K 問題を自身で 作り出すのを止めることはしません。 正しい使い方については localtime
の文書を参照してください。
Perl 5.11 から、localtime
と gmtime
は 32 ビットベースの時刻が オーバーフローする 2038 年 1 月 19 日 03:14:08 以降の日付も 扱えるようになりました。 32 ビットの perl
では警告が出るかもしれません:
% perl5.11.2 -E 'say scalar localtime( 0x9FFF_FFFFFFFF )'
Integer overflow in hexadecimal number at -e line 1.
Wed Nov 1 19:42:39 5576711
64 ビットの perl
では、本当に長いプロジェクトよりも大きい日付も 得られます:
% perl5.11.2 -E 'say scalar gmtime( 0x9FFF_FFFFFFFF )'
Thu Nov 2 00:42:39 5576711
しかし、これでも陽子の崩壊を記録する必要があるならうまくいきません。
(brian d foy によって寄贈されました)
値があなたの予測している、または受け入れたいものであることを保証するには 多くの方法があります。 perlfaq でカバーする特定の例の他に、名前に "Assert" や "Validate" がある モジュールや、Regexp::Common
のようなその他のモジュールを 見ることもできます。
Business::ISBN
, Business::CreditCard
, Email::Valid
, Data::Validate::IP
のように、特定の種類の入力を検査するための モジュールもあります。
それはあなたのいう「エスケープ」がなんであるかによります。 URL のエスケープは perlfaq9 で扱っています。 バックスラッシュによるシェルエスケープは以下のようにして取り除きます:
s/\\(.)/$1/g;
これは \n
だとか \t
、あるいはその他の特殊なエスケープを展開しません。
(brian d foy によって寄贈されました)
文字の組(または文字の並び)を探して、それを一つの実体に置き換えるには 置換演算子が使えます。 この置換で、(.)
で一文字が見付かります。 記憶用のかっこはマッチングした文字を後方参照 \1
に保管し、 同じ文字を直後に要求するために使います。 文字列の一部を $1
にある文字で置き換えます。
s/(.)\1/$1/g;
文字変換演算子 tr///
も使えます。 この例では、tr///
の検索リスト側は何も入っていませんが、c
オプションが ついているので全てが含まれます。 置き換えリスト側にも何も入っていないので、文字変換はほとんど何もしません (より厳密には、文字はその文字自身に置き換えられます)。 しかし、s
オプションは文字列中の重複していて連続した文字を 1 文字に 短縮するので、次に同じ文字がある文字は表示されません:
my $str = 'Haarlem'; # in the Netherlands
$str =~ tr///cs; # Now Harlem, like in New York
(brian d foy によって寄贈されました)
これは perlref に文書化されていて、もっとも読みやすいものでは ありませんが、動きます。 これらの例のそれぞれにおいて、大かっこの内側の関数はリファレンスを デリファレンスするために呼び出します。 もし複数の返り値がある場合、無名配列を構築して、デリファレンスします。 この場合、関数をリストコンテキストで呼び出します。
print "The time values are @{ [localtime] }.\n";
スカラコンテキストで関数を呼び出したい場合、もう少し作業が必要です。 実際に置きたいどんなコードでも中かっこの中に置けるので、 (それをどのようにするかはあなた次第で、中かっこのなかのコードを使えますが) 単にスカラリファレンスで終了する必要があります。 かっこはリストコンテキストを作成するので、関数内でスカラコンテキストを 強制するために scalar
が必要であることに注意してください:
print "The time is ${\(scalar localtime)}.\n"
print "The time is ${ my $x = localtime; \$x }.\n";
関数がすでにリファレンスを返す場合、自分でリファレンスを作る必要は ありません。
sub timestamp { my $t = localtime; \$t }
print "The time is ${ timestamp() }.\n";
Interpolation
モジュールもまたあなたのために多くの魔法を使います。 展開を行う tie されたハッシュを設定するための変数名(この場合は E
)を 指定できます。 同じようにこれを行うその他のいくつかのメソッドを持っています。
use Interpolation E => 'eval';
print "The time values are $E{localtime()}.\n";
ほとんどの場合、文字列連結を使ってスカラコンテキストに強制するほうが おそらくより簡単です。
print "The time is " . localtime() . ".\n";
これは一つの正規表現で解決できないほどの複雑な問題なのです。 単一のキャラクター二つに囲まれた何かを見つけだすには、 /x([^x]*)x/
といったパターンを使えば $1 に検査の結果が得られるでしょう。 複数キャラクターに囲まれたものの場合は、 /alpha(.*?)omega/
のようなパターンが必要となるでしょう。 しかし、ネストしたパターンを扱うようなものはありませんし、できません。 (
, {
, [
, <
のいずれかのバランス表現をデリミタとして 使っている場合、CPAN にある Regexp::Common モジュールを使うか、 "(??{ code })" in perlre を参照してください。 その他の場合では、パーサーを書く必要があります。
もしまじめにパーザを作ろうと考えているのなら、 それを手助けしてくれるようなモジュールやその他のプログラムがあります。 CPAN には Parse::RecDescent
, Parse::Yapp
, Text::Balanced
がありますし、byacc
プログラムもあります。 perl 5.8 から、Text::Balanced
は標準配布の一部になりました。
単純で破壊的な inside-out アプローチもあります。 これは以下のようにして一度に最小のネスト部分を取り出そうというものです。
while (s/BEGIN((?:(?!BEGIN)(?!END).)*)END//gs) {
# do something with $1
}
より複雑で巧妙なやり方に Perl の正規表現エンジンを使うというものがあります。 これは Dean Inada によるもので Obfuscated Perl コンテストに エントリされるような代物ですが、正しく働きます:
# $_ には解析対象の文字列があります
# BEGINとENDはネストしたテキストの開始と終了とを行います。
@( = ('(','');
@) = (')','');
($re=$_)=~s/((BEGIN)|(END)|.)/$)[!$3]\Q$1\E$([!$2]/gs;
@$ = (eval{/$re/},$@!~/unmatched/i);
print join("\n",@$[0..$#$]) if( $$[-1] );
"reverse" in perlfunc で説明されているように、スカラコンテキストで reverse()
を使います。
$reversed = reverse $string;
以下のようにしてできます:
1 while $string =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
あるいは、ただ単に Text::Tabs
モジュール(標準 Perl 配布キットの一部です)を 使ってもできます。
use Text::Tabs;
@expanded_lines = expand(@lines_with_tabs);
Text::Wrap
(標準 Perl 配布キットの一部です)を使います。
use Text::Wrap;
print wrap("\t", ' ', @paragraphs);
Text::Wrap
に与える段落には埋め込みの改行があってはいけません。 Text::Wrap
は行を均等割り付けしません(左寄せします)。
または CPAN の Text::Autoformat
モジュールを使ってください。 ファイルの整形は以下のようにシェルエイリアスを作ることによって 簡単に実現できます:
alias fmt="perl -i -MText::Autoformat -n0777 \
-e 'print autoformat $_, {all=>1}' $*"
その多くの能力を評価するために、Text::Autoformat
の文書を 参照してください。
文字列の先頭の文字へは substr() でアクセスできます。 例えば、最初の文字を得るには、位置 0 から始めて、長さ 1 の文字列を 取得します。
$string = "Just another Perl Hacker";
$first_char = substr( $string, 0, 1 ); # 'J'
文字列の一部を変換するために、省略可能な 4 番目の引数として置き換える 文字列を指定できます。
substr( $string, 13, 4, "Perl 5.8.0" );
substr() を左辺値として使うこともできます。
substr( $string, 13, 4 ) = "Perl 5.8.0";
自分で N 番目の記録を取る必要があります。 例えば、(大小文字の違いを無視して) 5 番目に現れた "whoever"
か "whomever"
を "whosoever"
か "whomsoever"
に変更したいと考えているとしましょう。 以下は全て $_ に変更したい文字列が入っているものとします。
$count = 0;
s{((whom?)ever)}{
++$count == 5 # is it the 5th?
? "${2}soever" # yes, swap
: $1 # renege and leave it there
}ige;
もっと一般的なケースでは、while
ループの中で /g
修飾子を使ってマッチの数を数えることもできます。
$WANT = 3;
$count = 0;
$_ = "One fish two fish red fish blue fish";
while (/(\w+)\s+fish\b/gi) {
if (++$count == $WANT) {
print "The third fish is a $1 one.\n";
}
}
これは "The third fish is a red one."
のように出力します。 以下のようにパターンの繰り返し回数を指定するやり方もあります:
/(?:\w+\s+fish\s+){2}(\w+)\s+fish/i;
様々な効率を持った、いろいろなやり方があります。 文字列中に存在しているある単一キャラクター (X) の数を数えたいのであれば、 tr///
関数を使って次のようにできます:
$string = "ThisXlineXhasXsomeXx'sXinXit";
$count = ($string =~ tr/X//);
print "There are $count X characters in the string";
これは単一キャラクターを対象にするのであればちょうどいいものですが、 大きな文字列中の、複数キャラクターから構成される部分文字列の数を 数えようとしても、tr///
はうまく動作しません。 ここで可能なのは、グローバルなパターンマッチを while() で囲んでしまうという ものです。たとえば、負の数を数えるのならこうします:
$string = "-9 55 48 -2 23 -76 4 14 -44";
while ($string =~ /-\d+/g) { $count++ }
print "There are $count negative numbers in the string";
もう一つのバージョンでは、リストコンテキストでグローバルマッチングを 使って、その結果をスカラに代入することで、マッチングした数を数えます。
$count = () = $string =~ /-\d+/g;
(brian d foy によって寄贈されました)
Damian Conway による Text::Autoformat は、考えるべき全てのことを 処理してくれます:
use Text::Autoformat;
my $x = "Dr. Strangelove or: How I Learned to Stop ".
"Worrying and Love the Bomb";
print $x, "\n";
for my $style (qw( sentence title highlight )) {
print autoformat($x, { case => $style }), "\n";
}
これらの単語のキャピタライズはどうしたいですか?
FRED AND BARNEY'S LODGE # all uppercase
Fred And Barney's Lodge # title case
Fred and Barney's Lodge # highlight case
これは見た目ほど簡単な問題ではありません。 これらにはいくつの単語があると思いますか? 考え中…考え中…。 もし 5 と答えたなら、あなたは正しいです。 Perl での単語は \w+
の集合ですが、これはあなたが キャピタライズしたいものではありません。 アポストロフィの後の s
はキャピタライズしないように Perl に 知らせるには? 正規表現に挑戦してみましょう:
$string =~ s/ (
(^\w) #at the beginning of the line
| # or
(\s\w) #preceded by whitespace
)
/\U$1/xg;
$string =~ s/([\w']+)/\u\L$1/g;
ここで、"and" をキャピタライズしないようにするには? Text::Autoformat を使って、次の問題に取り組んでください :)
いくつかのモジュールがこのようなパースを扱います-- Text::Balanced
, Text::CSV
, Text::CSV_XS
, Text::ParseWords
などです。
カンマで分割された文字列を別々のフィールドに置くような例を 考えてみましょう。 ここで split(/,/)
を使うことはできません。 なぜなら、クォートの内側にあるカンマで分割すべきではないからです。 例えば以下のようなデータを考えてみましょう。
SAR001,"","Cimetrix, Inc","Bob Smith","CAM",N,8,1,0,7,"Error, Core Dumped"
クォートの制約のためにこれは実に複雑な問題です。 ありがたいことに、私たちには Mastering Regular Expressions の著者でもあり、 この問題を私たちのために扱ってくれる Jeffrey Friedl がいます。 彼の提案はこうです(文字列が $text
にあると仮定しています):
@new = ();
push(@new, $+) while $text =~ m{
"([^\"\\]*(?:\\.[^\"\\]*)*)",? # groups the phrase inside the quotes
| ([^,]+),?
| ,
}gx;
push(@new, undef) if substr($text,-1,1) eq ',';
クォーテーションマークで終端されたフィールドの中で クォーテーションマークを表現したいのならば、 それをバックスラッシュで("like \"this\""
のように)エスケープしてください。
あるいは、Text::PaserWords
モジュール(標準 Perl 配布の一部です)を 使ってこうします:
use Text::ParseWords;
@new = quotewords(",", 0, $text);
(brian d foy によって寄贈されました)
置換を使うことで行えます。 1 行では、先頭か末尾のどちらかの空白全てを削除します。 2 つの置換でこれが行えます。
s/^\s+//;
s/\s+$//;
これを 1 回の置換で書くこともできますが、組み合わせた文は分かれている 文よりも遅くなります。 しかし、それはあなたには問題がないかもしれません。
s/^\s+|\s+$//g;
この正規表現において、アンカーは the alternation より低い優先順位を 持つので、the alternation は文字列の先頭か末尾にマッチングします。 /g
フラグが付いていることにより、可能な全てのマッチングについて置換が 行われるので、先頭と末尾の両方で行われます。 引き続く改行は \s+
にマッチングし、$
アンカーは文字列の物理的な 末尾にマッチングするので、改行も消えることを忘れないでください。 単に出力に改行を追加することで、^\s+
がすべて削除してしまう「空の」 (空白だけからなる)行を保存する利点が追加されます。
while( <> )
{
s/^\s+|\s+$//g;
print "$_\n";
}
複数行の文字列に対しては、/m
("multi-line") フラグを追加することにより、 文字列中の論理行毎に正規表現を適用できます。 /m
フラグをつけると、$
組み込まれた改行の 前に マッチングするので、 これを取り除けません。 文字列の末尾の改行は取り除けます。
$string =~ s/^\s+|\s+$//gm;
空白だけからなる文字列は、置換の最初の部分が文字列全体にマッチングして、 それを空文字列に置き換えてしまうので、消えてしまうことに注意してください。 もし組み込まれている空行を保存したいなら、さらにもう少し作業をする必要が あります。 全ての空白(改行を含みます)にマッチングさせる代わりに、単にその他の 空白にマッチングさせます。
$string =~ s/^[\t\f ]+|[\t\f ]+$//mg;
以下に挙げる例で、$pad_len
はパッディングしたい文字列の長さです。 $text
や $num
は文字列にパッディングの対象となる内容を保持していて、 $pad_char
がパッディングに使いたいキャラクターを保持しています。 やっていることがわかっているのなら、$pad_char
という変数の代わりに一 文字のキャラクター文字列を使うこともできます。 そして同様に、パッディングしたい長さが予め分かっているなら、 $pad_len
に整数値を指定することも出来ます。
最も単純なやり方は sprintf
関数を使うというものです。 この関数は文字列の左や右にパッディングを行ったり、0 を左に置いたりする ことができます。 pack
関数は文字列の右側に空白でパッディングすることと、 結果の最大長を $pad_len
に切り詰めることだけができます。
# Left padding a string with blanks (no truncation):
$padded = sprintf("%${pad_len}s", $text);
$padded = sprintf("%*s", $pad_len, $text); # same thing
# Right padding a string with blanks (no truncation):
$padded = sprintf("%-${pad_len}s", $text);
$padded = sprintf("%-*s", $pad_len, $text); # same thing
# Left padding a number with 0 (no truncation):
$padded = sprintf("%0${pad_len}d", $num);
$padded = sprintf("%0*d", $pad_len, $num); # same thing
# Right padding a string with blanks using pack (will truncate):
$padded = pack("A$pad_len",$text);
空白やゼロ以外のキャラクターでパッディングを行いたいのであれば、 以下に挙げるやり方を使うことができます。これらは全て パッディング文字列を x
修飾子で生成して $text
と結合します。 これらのメソッドは $text
を切り詰めません。
任意のキャラクターによる左詰めと右詰めを行い、新しい文字列を作ります:
$padded = $pad_char x ( $pad_len - length( $text ) ) . $text;
$padded = $text . $pad_char x ( $pad_len - length( $text ) );
任意のキャラクターによる左詰めと右詰めを行い、$text
を直接変更します:
substr( $text, 0, 0 ) = $pad_char x ( $pad_len - length( $text ) );
$text .= $pad_char x ( $pad_len - length( $text ) );
(brian d foy によって寄贈されました)
データが含まれている桁が分かっているなら、単一の桁を展開するために substr
が使えます。
my $column = substr( $line, $start_column, $length );
桁が空白やその他のデリミタで分けられていて、データの一部としては 空白やデリミタが現れないなら、split
が使えます。
my $line = ' fred barney betty ';
my @columns = split /\s+/, $line;
# ( '', 'fred', 'barney', 'betty' );
my $line = 'fred||barney||betty';
my @columns = split /\|/, $line;
# ( 'fred', '', 'barney', '', 'betty' );
カンマ区切りの値(CSV)を扱いたい場合は、フォーマットが少し複雑なので これはしないで下さい。 Text::CSV
, Text::CSV_XS
, Text::CSV_PP
のような、この フォーマットを扱うためのモジュールの一つを使ってください。
固定桁の行全体を分解したいなら、unpack
の A (ASCII) フォーマットが 使えます。 フォーマット指定子の後に数値をつけることで、桁数を指定できます。 更なる詳細については perlfunc の pack
と unpack
の項目を 参照してください。
my @fields = unpack( $line, "A8 A8 A8 A16 A4" );
unpack
のフォーマット引数での空白はリテラルな空白を意味しないことに 注意してください。 もし空白で区切られたデータがあるなら、代わりに split
を使ったほうが いいかもしれません。
(brian d foy によって寄贈されました)
Text::Soundex モジュールが使えます。 あいまいマッチングや近傍マッチングを行いたいなら、 String::Approx
, Text::Metaphone
, Text::DoubleMetaphone
といった モジュールを試すのも良いでしょう。
(brian d foy によって寄贈されました)
もし避けることが可能なら、してはいけません; あるいは、Text::Template
や Template
ツールキットのような テンプレートシステムが使えるなら、これらを代わりに使ってください。 sprintf
や printf
を使って作業をこなすことすらできます:
my $string = sprintf 'Say hello to %s and %s', $foo, $bar;
しかし、完全なテンプレートシステムを引っ張り出したくないような一度限りの 簡単な場合には、内部に二つの Perl スカラ変数を持つ文字列を使います。 この例では、$foo
と $bar
をその変数の値に展開したいとします:
my $foo = 'Fred';
my $bar = 'Barney';
$string = 'Say hello to $foo and $bar';
これを行う一つの方法は、置換演算子と二つの /e
フラグを使うものです。 一つ目の /e
は置き換え側の $1
を評価して $foo
に変えます。 二つ目の /e
は $foo
をその値に変えます。 従って、$foo
は 'Fred' に変わって、それが結局最終的に文字列に 残されるものになります:
$string =~ s/(\$\w+)/$1/eeg; # 'Say hello to Fred and Barney'
/e
は暗黙のうちに struct 違反を無視するので、未定義の変数名を 空文字列に置き換えます。 /e
フラグを (2 回も!) 使っているので、eval
を文字列の形で使うのと 同じセキュリティ問題を全て抱えています。 もし $foo
に(おそらく @{[ system "rm -rf /" ]}
のような)変なものが 入っていたら、トラブルに出会うことになります。
セキュリティ問題を避けるために、変数名を評価するのではなくハッシュから 値を取ってくることもできます。 /e
を一つ使って、ハッシュに値があることを確認し、もしなければ、 値をマーカーに置き換えます; この場合は、???
が何かがおかしいことの 印です;
my $string = 'This has $foo and $bar';
my %Replacements = (
foo => 'Fred',
);
# $string =~ s/\$(\w+)/$Replacements{$1}/g;
$string =~ s/\$(\w+)/
exists $Replacements{$1} ? $Replacements{$1} : '???'
/eg;
print $string;
そういったダブルクォートが、強制的に文字列化(stringification)するのが問題で、 たとえそれを望んでいなくても数値やリファレンスが強制的に 文字列に変換されてしまうのです。 このように考えましょう: ダブルクォートは新しい文字列を生成するのに使われる。 もしあなたがすでに文字列を持っているのであれば、使う必要が あるでしょうか?
以下の例のような変な書き方をすると:
print "$var"; # BAD
$new = "$old"; # BAD
somefunc("$var"); # BAD
あなたはトラブルに巻き込まれることになるでしょう。 これらは(99.8% は)、より単純、かつより直接的に書くべきなのです。
print $var;
$new = $old;
somefunc($var);
さもなければ、プログラムを遅くなることのほかにも、スカラが実際には文字列でも 数値でもなくリファレンスであるようなときにあなたのプログラムが おかしくなることになります。
func(\@array);
sub func {
my $aref = shift;
my $oref = "$aref"; # WRONG
}
マジカル ++
オートインクリメント演算子や syscall() 関数のような、 文字列と数値の間の違いを実際に気にするような Perl の幾つかの操作において、 微妙な問題に直面するかもしれません。
文字列化(stringfication)も配列を壊します。
@lines = `command`;
print "@lines"; # WRONG - extra blanks
print @lines; # right
以下の三つの点を確認してください。
ヒアドキュメントのテキストでインデントを使いたいのであれば、 以下のようにしてできます:
# all in one
($VAR = <<HERE_TARGET) =~ s/^\s+//gm;
your text
goes here
HERE_TARGET
しかしこの場合も HERE_TARGET は先頭に置かなければなりません。 もしこれもインデントしたいのなら、インデントをクォートする必要があるでしょう。
($quote = <<' FINIS') =~ s/^\s+//gm;
...we will have peace, when you and all your works have
perished--and the works of your dark master to whom you
would deliver us. You are a liar, Saruman, and a corrupter
of men's hearts. --Theoden in /usr/src/perl/taint.c
FINIS
$quote =~ s/\s+--/\n--/;
以下はインデントされたヒアドキュメントのための汎用 fixer-upper 関数です。 この関数は引数にヒアドキュメントを渡されることを期待しています。 これは共通の部分文字列で始まる各行について、 その部分文字列を剥ぎ取るということを行います。 あるいは、最初の行の先頭にある空白を取り、 続く行に対しても同じ様に削除を行います。
sub fix {
local $_ = shift;
my ($white, $leader); # common whitespace and common leading string
if (/^\s*(?:([^\w\s]+)(\s*).*\n)(?:\s*\1\2?.*\n)+$/) {
($white, $leader) = ($2, quotemeta($1));
} else {
($white, $leader) = (/^(\s+)/, '');
}
s/^\s*?$leader(?:$white)?//gm;
return $_;
}
この関数は先頭にある特別な、動的に決められる文字列に対しても使えます:
$remember_the_main = fix<<' MAIN_INTERPRETER_LOOP';
@@@ int
@@@ runops() {
@@@ SAVEI32(runlevel);
@@@ runlevel++;
@@@ while ( op = (*op->op_ppaddr)() );
@@@ TAINT_NOT;
@@@ return 0;
@@@ }
MAIN_INTERPRETER_LOOP
また、先頭にある特定の個数の空白を取り除いて、インデントを 正しく残すようなこともできます:
$poem = fix<<EVER_ON_AND_ON;
Now far ahead the Road has gone,
And I must follow, if I can,
Pursuing it with eager feet,
Until it joins some larger way
Where many paths and errands meet.
And whither then? I cannot say.
--Bilbo in /usr/src/perl/pp_ctl.c
EVER_ON_AND_ON
(brian d foy によって寄贈されました)
リストとはスカラの固定されたコレクションです。 配列はスカラの変動するコレクションを保持する変数です。 配列は保持しているコレクションをリスト演算に提供できるので、 リスト演算は配列に対しても動作します:
# slices
( 'dog', 'cat', 'bird' )[2,3];
@animals[2,3];
# iteration
foreach ( qw( dog cat bird ) ) { ... }
foreach ( @animals ) { ... }
my @three = grep { length == 3 } qw( dog cat bird );
my @three = grep { length == 3 } @animals;
# supply an argument list
wash_animals( qw( dog cat bird ) );
wash_animals( @animals );
スカラを変更したり再配置したり加えたり取り去ったりするような配列操作は 配列に対してのみ動作します。 これらは(固定されているので)リストに対しては動作しません。 配列操作は shift
, unshift
, push
, pop
, splice
などです。
配列は長さを変えることもできます:
$#animals = 1; # truncate to two elements
$#animals = 10000; # pre-extend to 10,001 elements
配列要素を変更することはできますが、リスト要素を変更することはできません:
$animals[0] = 'Rottweiler';
qw( dog cat bird )[0] = 'Rottweiler'; # syntax error!
foreach ( @animals ) {
s/^d/fr/; # works fine
}
foreach ( qw( dog cat bird ) ) {
s/^d/fr/; # Error! Modification of read only value!
}
しかし、リスト要素それ自身が変数のとき、リスト要素を変更できるように 見えます。 しかし、リスト要素は変数であって、データではありません。 リスト要素を変更することは出来ませんが、リスト要素が参照しているものは 変更できます。 リスト要素自身は変更できません: 同じ値のままです。
また、コンテキストに対しても注意する必要があります。 配列の要素数を得るために配列をスカラに代入できます。 これは配列に対してのみ動作します:
my $count = @animals; # only works with arrays
同じことを、リストと考えているものに対して実行すると、全く異なった 結果になります。 右側にリストがあるように見えますが、Perl は実際にはカンマで区切られた スカラの塊を見ています:
my $scalar = ( 'dog', 'cat', 'bird' ); # $scalar gets bird
スカラに代入しているので、右側はスカラコンテキストです。 スカラコンテキストでのカンマ演算子 (ええ、これは演算子です!) は左側を 評価して、その結果を捨てて、右側を評価して、結果を返します。 事実上、$scalar
へのリストに見えるものの代入は、一番右の値です。 多くの人々は、最後の要素が想定している要素数と同じになっている リストのように見えるものを選択するので、これに混乱します:
my $scalar = ( 1, 2, 3 ); # $scalar gets 3, accidentally
(brian d foy によって寄贈されました)
違いは印 (sigil) で、配列名の前にある特殊文字です。 $
印は「正確に一つのアイテム」を意味し、@
印は「0 個以上のアイテム」を 意味します。 $
では一つのスカラを得られ、@
ではリストを得られます。
人々は間違って印が変数の型を示していると仮定するために混乱が発生します。
$array[1]
は配列の一つの要素です。 これは添え字 1 のアイテム(またはそこにアイテムがなければ undef) を 返します。 もし配列から一つだけの要素を取り出そうとするなら、これが使うべきものです。
@array[1]
は配列スライスですが、一つの添え字しかありません。 @array[1,4,3,0]
のように追加の添え字をリストとして指定することで、複数の 要素を同時に引き出せます。
代入の左側でスライスを使うことで、右側にリストコンテキストを提供します。 これは予想外の結果を引き起こすことがあります。 例えば、もしファイルハンドルから 1 行を読み込みたいとき、スカラ値への 代入はうまくいきます:
$array[1] = <STDIN>;
しかし、リストコンテキストでは、行入力演算子は全ての行をリストとして 返します。 最初の行は @array[1]
に入り、残りの行はどこかに消えてしまします:
@array[1] = <STDIN>; # most likely not what you want
use warnings
プラグマか -w オプションを使うと、添え字が一つだけの スライスを使ったときに警告されます。
(brian d foy によって寄贈されました)
ハッシュを使ってください。 「ユニーク」や「重複」といった単語を考えたときには、 「ハッシュキー」を考えてください。
要素の順番を気にしないなら、単にハッシュを作ってキーを取り出してください。 どのようにハッシュを作るかは重要ではありません: 単にユニークな要素を取り出すために keys
を使うためです。
my %hash = map { $_, 1 } @array;
# or a hash slice: @hash{ @array } = ();
# or a foreach: $hash{$_} = 1 foreach ( @array );
my @unique = keys %hash;
モジュールを使いたいなら、List::MoreUtils
の uniq
関数を 試してみてください。 リストコンテキストでは、リストの順序を保存した形でユニークな要素を返します。 スカラコンテキストでは、ユニークな要素の数を返します。
use List::MoreUtils qw(uniq);
my @unique = uniq( 1, 2, 3, 4, 4, 5, 6, 5, 7 ); # 1,2,3,4,5,6,7
my $unique = uniq( 1, 2, 3, 4, 4, 5, 6, 5, 7 ); # 7
各要素を調べて、一度見つけたものをスキップすることもできます。 記録をつけるためにハッシュを使います。 ループが最初に要素を見つけると、この要素は %Seen
にキーがありません。 next
文はキーを作り、直ちにその値(undef
)を使うので、 ループは push
を続行し、このキーの値をインクリメントします。 次回にループが同じ要素を見つけると、ハッシュにはそのキーが存在し、 かつ そのキーの値が(0 でも undef
でもないので)真なので、 next は反復をスキップし、ループは次の要素に進みます。
my @unique = ();
my %seen = ();
foreach my $elem ( @array )
{
next if $seen{ $elem }++;
push @unique, $elem;
}
grep を使うことでより簡単に書くこともでき、これは同じことになります。
my %seen = ();
my @unique = grep { ! $seen{ $_ }++ } @array;
(この回答の一部は Anno Siegel と brian d foy によって寄贈されました)
ハッシュはこの質問に対する速くて効率の良い解答のために デザインされています。 配列はそうではありません。
幾つかのやり方があります。 Perl 5.10 以降では、アイテムが配列やハッシュに含まれているかを チェックするのにスマートマッチング演算子が使えます:
use 5.010;
if( $item ~~ @array )
{
say "The array contains $item"
}
if( $item ~~ %hash )
{
say "The hash contains $item"
}
以前のバージョンの Perl では、もう少し作業が必要です。 この問い合わせを多くのアイテムに対して 行いたいとか、値が任意の文字列である場合には最も速いやり方は元の 配列の逆のものを作って元の配列の値をキーとするようなハッシュを 管理するというものです:
@blues = qw/azure cerulean teal turquoise lapis-lazuli/;
%is_blue = ();
for (@blues) { $is_blue{$_} = 1 }
こうすれば、$is_blue{$some_color}
がどうであるかでチェックできます。 最初の場所で bules にハッシュのすべてを保持させるのはよい考えでしょう。
値のすべてが小さな整数であれば、単純な添え字付き配列を使うことができます。 この種の配列はより少ない場所しか使いません。
@primes = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31);
@is_tiny_prime = ();
for (@primes) { $is_tiny_prime[$_] = 1 }
# or simply @istiny_prime[@primes] = (1) x @primes;
これで $is_tiny_prime[$some_number] の内容がどうであるかで チェックできます。
問い合わせる値が文字列ではなく整数であるのならば、ビットストリングを 使うことによって大幅に空間を節約することができます。
@articles = ( 1..10, 150..2000, 2017 );
undef $read;
for (@articles) { vec($read,$_,1) = 1 }
これで vec($read,$n,1)
が真かどうかで $n
の検査ができます。
これらのメソッドは個々のテストの速さが保証されていますが、 元のリストや配列の再構成が必要です。 同じ配列に対して複数の値をテストする必要がある場合にのみ元が取れます。
一度だけテストする場合、標準モジュール List::Util
がこの目的のために first
関数をエクスポートしています。 これは要素を見つけると停止することで動作します。 速度のために C で書かれていて、これの Perl の等価な処理は、次のサブルーチンの ようになります:
sub first (&@) {
my $code = shift;
foreach (@_) {
return $_ if &{$code}();
}
undef;
}
速度が問題ではないなら、一般的な方法は、リスト全体をトラバースするために grep をスカラコンテキストで使う(これで条件をパスしたアイテムの数を返します) ことです。 しかし、これには何回マッチングしたのかを知らせるという利点があります。
my $is_there = grep $_ eq $whatever, @array;
実際にマッチングした要素を展開したい場合は、単にリストコンテキストで grep を使ってください。
my @matches = grep $_ eq $whatever, @array;
ハッシュを使います。 以下のプログラム片は質問の両方を行います。 与えられた配列の要素には重複がないと仮定しています。
@union = @intersection = @difference = ();
%count = ();
foreach $element (@array1, @array2) { $count{$element}++ }
foreach $element (keys %count) {
push @union, $element;
push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element;
}
これは 対称的差分、つまり、A か B のどちらかにあって、 両方にあることはない全ての要素である、ということに注意してください。 これは xor 操作のようなものと考えてください。
Perl 5.10 以降では、スマートマッチング演算子が最小の作業で答えを 与えてくれます:
use 5.010;
if( @array1 ~~ @array2 )
{
say "The arrays are the same";
}
if( %hash1 ~~ %hash2 ) # doesn't check values!
{
say "The hash keys are the same";
}
以下に挙げる例は一レベルの配列に対して有効です。 これは文字列としての比較を使い、定義済みと未定義の空文字列を区別しません。 必要に応じて修正してください。
$are_equal = compare_arrays(\@frogs, \@toads);
sub compare_arrays {
my ($first, $second) = @_;
no warnings; # silence spurious -w undef complaints
return 0 unless @$first == @$second;
for (my $i = 0; $i < @$first; $i++) {
return 0 if $first->[$i] ne $second->[$i];
}
return 1;
}
多重レベル構造に対応するために、あなたは以下のような手段を使いたいと 考えるかもしれません。 ここでは CPAN モジュールの FreezeThaw
を使っています:
use FreezeThaw qw(cmpStr);
@a = @b = ( "this", "that", [ "more", "stuff" ] );
printf "a and b contain %s arrays\n",
cmpStr(\@a, \@b) == 0
? "the same"
: "different";
このアプローチはハッシュの比較にも使えます。 以下に二種類の回答をお見せしましょう:
use FreezeThaw qw(cmpStr cmpStrHard);
%a = %b = ( "this" => "that", "extra" => [ "more", "stuff" ] );
$a{EXTRA} = \%b;
$b{EXTRA} = \%a;
printf "a and b contain %s hashes\n",
cmpStr(\%a, \%b) == 0 ? "the same" : "different";
printf "a and b contain %s hashes\n",
cmpStrHard(\%a, \%b) == 0 ? "the same" : "different";
最初のものは二つのハッシュが同じ内容であると報告しますが、二番目の ものは違うと報告します。
条件を満たす最初の配列要素を探すためには、Perl 5.8 から同梱されている List::Util
モジュールの first()
関数が使えます。 この例は "Perl" を含む最初の要素を探します。
use List::Util qw(first);
my $element = first { /Perl/ } @array;
List::Util
が使えない場合は、同じことをするために自分でループを書きます。 要素が見つかったら、last を使ってループを停止します。
my $found;
foreach ( @array ) {
if( /Perl/ ) { $found = $_; last }
}
配列の添え字がほしい場合は、添え字に順番に変えていって、 それぞれの添え字の配列要素をチェックして、条件を満たすものが 見つかるまで繰り返します。
my( $found, $index ) = ( undef, -1 );
for( $i = 0; $i < @array; $i++ ) {
if( $array[$i] =~ /Perl/ ) {
$found = $array[$i];
$index = $i;
last;
}
}
一般的には、Perl ではリンク付きリストを扱う必要はありません。 なぜなら、通常の配列を使って push や pop、shift や unsift を使って両端で 操作できたり、splice を使って任意の場所にある任意個の要素を加えたり 削除したりすることができるからです。 pop と shift は両方ともが、Perl の動的配列に対する O(1) の操作です。 shift や pop がなかった場合、push は一般的には log(N) 回毎のオーダーで 再割り当てが必要になります。 そして unshift は呼ばれる毎にポインターのコピーが必要になるでしょう。
もし、本当に、本当にリンク付きリストを使いたいのなら、perldsc や perltoot で説明されているようなデータ構造を使うことができ、 アルゴリズムの教科書にあるようなことができます。 例えば以下のようなリストノードをを考えてみましょう:
$node = {
VALUE => 42,
LINK => undef,
};
リストを渡り歩くには以下のようにします:
print "List: ";
for ($node = $head; $node; $node = $node->{LINK}) {
print $node->{VALUE}, " ";
}
print "\n";
以下のやり方でリストに追加できます:
my ($head, $tail);
$tail = append($head, 1); # grow a new head
for $value ( 2 .. 10 ) {
$tail = append($tail, $value);
}
sub append {
my($list, $value) = @_;
my $node = { VALUE => $value };
if ($list) {
$node->{LINK} = $list->{LINK};
$list->{LINK} = $node;
}
else {
$_[0] = $node; # replace caller's version
}
return $node;
}
しかし繰り返しますが、Perl の組み込み型は事実上常に充分なものなのです。
(brian d foy によって寄贈されました)
配列を無限に循環したいなら、配列の添え字をインクリメントして 要素数の剰余を取ったものを使います:
my @array = qw( a b c );
my $i = 0;
while( 1 ) {
print $array[ $i++ % @array ], "\n";
last if $i > 20;
}
常に循環配列の次の要素をもつスカラを使うための、 Tie::Cycle
を使うこともできます:
use Tie::Cycle;
tie my $cycle, 'Tie::Cycle', [ qw( FFFFFF 000000 FFFF00 ) ];
print $cycle; # FFFFFF
print $cycle; # 000000
print $cycle; # FFFF00
Array::Iterator::Circular
は循環配列のための反復子オブジェクトを 作ります:
use Array::Iterator::Circular;
my $color_iterator = Array::Iterator::Circular->new(
qw(red green blue orange)
);
foreach ( 1 .. 20 ) {
print $color_iterator->next, "\n";
}
Perl 5.8.0 以降がインストールされているか、Scalar-List-Utils 1.03 以降が インストールされているなら、以下のように出来ます:
use List::Util 'shuffle';
@shuffled = shuffle(@list);
そうでないなら、Fisher-Yates 法が使えます。
sub fisher_yates_shuffle {
my $deck = shift; # $deck is a reference to an array
return unless @$deck; # must not be empty!
my $i = @$deck;
while (--$i) {
my $j = int rand ($i+1);
@$deck[$i,$j] = @$deck[$j,$i];
}
}
# shuffle my mpeg collection
#
my @mpeg = <audio/*/*.mp3>;
fisher_yates_shuffle( \@mpeg ); # randomize @mpeg in place
print @mpeg;
List::Util::shuffle()
はリストを受け取って、混ぜられた新しいリストを 返しますが、上記の実装は配列そのものを混ぜることに注意してください。
splice を使ったシャッフルアルゴリズムを見たことがあるかもしれません。 カレントの要素をランダムに取り出した別の要素と交換します:
srand;
@new = ();
@old = 1 .. 10; # just a demo
while (@old) {
push(@new, splice(@old, rand @old, 1));
}
これは splice が O(N) であり、さらにそれを N 回呼んでいるのですから 良くありません。 つまりこれは O(N**2) のアルゴリズムです。 これは大きな配列に使わなければあなたはその効率の悪さに気がつかないでしょう。
for
/foreach
を使います:
for (@lines) {
s/foo/bar/; # change that word
tr/XZ/ZX/; # swap those letters
}
別の方法です; 球の体積を求めます:
for (@volumes = @radii) { # @volumes has changed parts
$_ **= 3;
$_ *= (4/3) * 3.14159; # this will be constant folded
}
リストを他のリストに変換する map()
を使っても行えます:
@volumes = map {$_ ** 3 * (4/3) * 3.14159} @radii;
同じことをハッシュの値に対して行いたいのであれば、values
は使えません。 Perl 5.6 以降では値はコピーされないので、(この場合では) $orbit を変更すると、 値を変更することになります。
for $orbit ( values %orbits ) {
($orbit **= 3) *= (4/3) * 3.14159;
}
perl 5.6 以前では、values
は値のコピーを返すので、古い perl の コードでは、ハッシュを修正しているところで values %orbits
ではなく @orbits{keys %orbits}
と書いていることがよくあります。
rand()
関数を使います("rand" in perlfunc を参照):
$index = rand @array;
$element = $array[$index];
あるいは、単純に:
my $element = $array[ rand @array ];
CPAN にある List::Permutor
モジュールを使ってください。 リストが実際には配列なら、Algorithm::Permute
モジュール(これも CPAN に あります)を試してください。 これは XS コードで書かれていて、とても効率的です:
use Algorithm::Permute;
my @array = 'a'..'d';
my $p_iterator = Algorithm::Permute->new ( \@array );
while (my @perm = $p_iterator->next) {
print "next permutation: (@perm)\n";
}
より速い実行のために、以下のようにも出来ます:
use Algorithm::Permute;
my @array = 'a'..'d';
Algorithm::Permute::permute {
print "next permutation: (@array)\n";
} @array;
以下の小さなプログラムは入力された行にある各単語の順列をすべて生成します。 関数 permute() で使われているアルゴリズムは Knuth の The Art of Computer Programming の Volume 4 (未発行) で 議論されていて、任意のリストで動作するはずです:
#!/usr/bin/perl -n
# Fischer-Krause ordered permutation generator
sub permute (&@) {
my $code = shift;
my @idx = 0..$#_;
while ( $code->(@_[@idx]) ) {
my $p = $#idx;
--$p while $idx[$p-1] > $idx[$p];
my $q = $p or return;
push @idx, reverse splice @idx, $p;
++$q while $idx[$p-1] > $idx[$q];
@idx[$p-1,$q]=@idx[$q,$p-1];
}
}
permute { print "@_\n" } split;
Algorithm::Loops
モジュールも the NextPermute
と NextPermuteNum
の 関数を提供していて、重複した値が含まれていても、その場で変更して、配列の 全てのユニークな順列を探します: もしその要素が逆順にソートされているなら、配列を反転させて、ソートを 行い、偽を返します; さもなければ次の順列を返します。
NextPermute
は文字列順を使い、NextPermuteNum
は数値順を使うので、 0..9
の全ての順番を数え上げるには以下のようにします:
use Algorithm::Loops qw(NextPermuteNum);
my @list= 0..9;
do { print "@list\n" } while NextPermuteNum @list;
sort() ("sort" in perlfunc に説明があります)のための比較関数を作ります:
@list = sort { $a <=> $b } @list;
デフォルトのソート関数は文字列比較である cmp で、(1, 2, 10)
を (1, 10, 2)
に並び変えます。 上の例では、数値比較演算子である <=>
を使っています。
ソートするものの一部を取り出す必要があるような複雑な関数を使うのなら、 ソート関数の内側でそれを使ってはいけません。 最初にその関数で使う部分を取り出します。 なぜなら、sort BLOCK は同じ要素に対して何度も何度も呼び出される 可能性があるからです。 以下の例は、各アイテムの最初の番号の後にある最初の単語を取り出し、 その後でそれらの単語を大小文字を無視してソートします。
@idx = ();
for (@data) {
($item) = /\d+\s*(\S+)/;
push @idx, uc($item);
}
@sorted = @data[ sort { $idx[$a] cmp $idx[$b] } 0 .. $#idx ];
これはシュワルツ変換と呼ばれるトリックを使って以下のように 書くこともできます:
@sorted = map { $_->[0] }
sort { $a->[1] cmp $b->[1] }
map { [ $_, uc( (/\d+\s*(\S+)/)[0]) ] } @data;
幾つかのフィールドを使ってソートする必要があるのなら、 以下のやり方が便利でしょう。
@sorted = sort {
field1($a) <=> field1($b) ||
field2($a) cmp field2($b) ||
field3($a) cmp field3($b)
} @data;
これは先の例にあったキーの precalculation と組み合わせることも できます。
この手法に関する更なる情報については http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz にある "Far More Than You Ever Wanted To Know" コレクションの sort という記事を 参照してください。
perlfaq4 で後述する、ハッシュのソートに関する質問も参照してください。
pack()
と unpack()
か、vec()
とビット演算を使います。
例えば、それぞれのビットを配列に格納する(これをすると多くのメモリが 無駄になります)必要はありません。 ビットの配列を文字列に変換するには、正しいビットをセットするのに vec()
使います。 これは、$ints[N]
がセットされている場合のみ $vec
の bit N を セットします。
@ints = (...); # array of bits, e.g. ( 1, 0, 0, 1, 1, 0 ... )
$vec = '';
foreach( 0 .. $#ints ) {
vec($vec,$_,1) = 1 if $ints[$_];
}
文字列 $vec
は必要なビット数だけを取ります。 例えば、@ints
に 16 エントリがあるとすると、$vec
はこれを 格納するのに (スカラ変数のオーバーヘッドを除いて) 2 バイトだけを 使います。
次に挙げる例は、$vec
で与えられるベクターのビットを配列 @ints
に 取り出すものです:
sub bitvec_to_list {
my $vec = shift;
my @ints;
# Find null-byte density then select best algorithm
if ($vec =~ tr/\0// / length $vec > 0.95) {
use integer;
my $i;
# This method is faster with mostly null-bytes
while($vec =~ /[^\0]/g ) {
$i = -9 + 8 * pos $vec;
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
}
}
else {
# This method is a fast general algorithm
use integer;
my $bits = unpack "b*", $vec;
push @ints, 0 if $bits =~ s/^(\d)// && $1;
push @ints, pos $bits while($bits =~ /1/g);
}
return \@ints;
}
この方法はビットベクターが疎であるときにさらに高速になります (Tim Bunce と Winfried Koenig によるものです)。
Benjamin Goldberg の提案を使って、while ループをはるかに 短くすることもできます:
while($vec =~ /[^\0]+/g ) {
push @ints, grep vec($vec, $_, 1), $-[0] * 8 .. $+[0] * 8;
}
または CPAN の Bit::Vector
モジュールを使います:
$vector = Bit::Vector->new($num_of_bits);
$vector->Index_List_Store(@ints);
@ints = $vector->Index_List_Read();
Bit::Vector
は、ビットベクタ、小さい整数の就業、「大きな整数」の 計算に関する効果的なメソッドを提供します。
以下は、vec() を使った広範囲な説明です:
# vec demo
$vector = "\xff\x0f\xef\xfe";
print "Ilya's string \\xff\\x0f\\xef\\xfe represents the number ",
unpack("N", $vector), "\n";
$is_set = vec($vector, 23, 1);
print "Its 23rd bit is ", $is_set ? "set" : "clear", ".\n";
pvec($vector);
set_vec(1,1,1);
set_vec(3,1,1);
set_vec(23,1,1);
set_vec(3,1,3);
set_vec(3,2,3);
set_vec(3,4,3);
set_vec(3,4,7);
set_vec(3,8,3);
set_vec(3,8,7);
set_vec(0,32,17);
set_vec(1,32,17);
sub set_vec {
my ($offset, $width, $value) = @_;
my $vector = '';
vec($vector, $offset, $width) = $value;
print "offset=$offset width=$width value=$value\n";
pvec($vector);
}
sub pvec {
my $vector = shift;
my $bits = unpack("b*", $vector);
my $i = 0;
my $BASE = 8;
print "vector length in bytes: ", length($vector), "\n";
@bytes = unpack("A8" x length($vector), $bits);
print "bits are: @bytes\n\n";
}
簡単にいえば、スカラや関数に対してのみ defined を使うべきで、 集成体(aggregates, 配列やハッシュ)に対して使うべきではないのです。 詳しくは 5.004 以降の "defined" in perlfunc を参照してください。
(brian d foy によって寄贈されました)
ハッシュ全体を処理するには二つの方法があります。 キーのリストを取得してからキー毎に処理するか、一度に一つのキー-値の ペアを取得するかです。
全てのキーを得るには、keys
関数を使います。 ハッシュの全てのキーを展開してリストの形で返します。 それから処理したい特定のキーの値を取得します。
foreach my $key ( keys %hash ) {
my $value = $hash{$key}
...
}
キーのリストを取得したら、ハッシュ要素を処理する前にリストを処理できます。 例えば、キーをソートしてレキシカルな順序で処理できます:
foreach my $key ( sort keys %hash ) {
my $value = $hash{$key}
...
}
あるいは、アイテムの一部に対してのみ処理したいかもしれません。 キーが text:
で始まるキーのみ扱いたいなら、単に grep
を使って 選択できます:
foreach my $key ( grep /^text:/, keys %hash ) {
my $value = $hash{$key}
...
}
ハッシュがとても大きい場合、キーの長いリストを作りたくないかもしれません。 メモリを節約するために、まだ取得していないキー-値の組を一つ返す each()
を 使って組を取得できます:
while( my( $key, $value ) = each( %hash ) ) {
...
}
each
演算子は組を一見ランダムな順序で返すので、順序が問題になる場合は、 keys
メソッドを使う必要があります。
しかし、each()
演算子は少しトリッキーです。 Perl が内部的に全ての要素を再ハッシュした後にいくつかの組をスキップしたり 再処理したりすることなく、ハッシュの使用中にハッシュのキーを追加したり 削除したりすることは出来ません。 さらに、一つのハッシュは一つのイテレータしか持っていないので、もし同じ ハッシュに対して keys
, values
, each
を使うと、イテレータを リセットしてしまって処理が無茶苦茶になってしまいます。 さらなる詳細については perlfunc の each
エントリを参照してください。
(brian d foy によって寄贈されました)
二つのハッシュをマージしようと決める前に、もし両方のハッシュに 同じキーがあったときにどうするかと、元のハッシュをそのままに しておくかどうかを決める必要があります。
もし元のハッシュを保存しておきたいなら、一つのハッシュ (%hash1
) を 新しいハッシュ (%new_hash
) にコピーして、それからもう一つのハッシュ (%hash2
) からキーを新しいハッシュに追加します。 キーが既に %new_hash
にある場合、重複をどうするかを決定する 機会が与えられます:
my %new_hash = %hash1; # make a copy; leave %hash1 alone
foreach my $key2 ( keys %hash2 )
{
if( exists $new_hash{$key2} )
{
warn "Key [$key2] is in both hashes!";
# handle the duplicate (perhaps only warning)
...
next;
}
else
{
$new_hash{$key2} = $hash2{$key2};
}
}
新しいハッシュを作りたくない場合は、やはりこのループ技術が使えます; 単に %new_hash
を %hash1
に変更します。
foreach my $key2 ( keys %hash2 )
{
if( exists $hash1{$key2} )
{
warn "Key [$key2] is in both hashes!";
# handle the duplicate (perhaps only warning)
...
next;
}
else
{
$hash1{$key2} = $hash2{$key2};
}
}
片方のキーと値がもう片方で上書きされても気にしないなら、片方のハッシュから もう片方に追加するために単にハッシュスライスを使えます。 この場合、共通のキーがあった場合は、 %hash1
の値は %hash2
の値で置き換えられます:
@hash1{ keys %hash2 } = values %hash2;
(brian d foy によって寄贈されました)
簡単な答えは:「そんなことをするな!」
ハッシュを each() で反復すると、それについて気にすることなく、 最後に返されたキーを削除できます。 他のキーを削除または追加すると、perl はハッシュテーブルを再構成するので、 反復子はキーを読み飛ばしたり重複して読んだりするかもしれません。 perlfunc の each()
のエントリを参照してください。
リバースハッシュを作成します:
%by_value = reverse %by_key;
$key = $by_value{$value};
これは特に効率がよいものではありません。 空間を効率よく使うにはこうします:
while (($key, $value) = each %by_key) {
$by_value{$value} = $key;
}
ハッシュに同じ値がある場合には、このメソッドは最初に見つかったキーだけを 見つけだします。 あなたはこれを気にするかも知れませんし、気にしないかもしれません。 もし気にするのなら、いつでもハッシュの代わりに配列のハッシュを 使うことができます:
while (($key, $value) = each %by_key) {
push @{$key_list_by_value{$value}}, $key;
}
(brian d foy によって寄贈されました)
これは perlfaq4 の "How do I process an entire hash?" と とてもよく似ていますが、一般的な場合では少し単純になります。
ハッシュにいくつのエントリがあるかを知るために、スカラコンテキストで keys()
組み込み関数を使えます:
my $key_count = keys %hash; # must be scalar context!
いくつのエントリが定義されている数を保持しているかを知りたい場合は、少し 違います。 それぞれの値をチェックする必要があります。 grep
が便利です:
my $defined_value_count = grep { defined } values %hash;
ある特定の条件を持つエントリを数える場合には全て同じ構造が使えます。 もしキーに母音が含まれているエントリを数えたい場合、そのように テストを置き換えるだけです:
my $vowel_count = grep { /[aeiou]/ } keys %hash;
スカラコンテキストでの grep
は数を返します。 マッチングしたアイテムのリストが欲しい場合、代わりに単にリストコンテキストで 使います:
my @defined_values = grep { defined } values %hash;
keys()
関数も反復動作を初期化するので、もしこれを each()
のような 他のハッシュ演算子を使っている間に使うと、おかしな結果になります。
(brian d foy によって寄贈されました)
ハッシュをソートするために、キーから始めます。 この例では、キーのリストをソート関数に渡して、それから ASCII 順で比較します (ロケール設定の影響を受けるかもしれません)。 出力リストは ASCII 順のキーのリストです。 キーを得たら、ASCII 順にキーを並べたレポートを作成します。
my @keys = sort { $a cmp $b } keys %hash;
foreach my $key ( @keys )
{
printf "%-20s %6d\n", $key, $hash{$key};
}
しかし、sort()
ブロックでもっと面白いことができます。 キーを比較する代わりに、これらの値を計算してその値を比較に使います。
例えば、大文字小文字を無視した順序のレポートを作るには、全てを小文字に するためにダブルクォートされた文字列の中で \L
シーケンスが使えます。 それから sort()
ブロックがキーを出力する順番を決定するために小文字化された 値を比較します。
my @keys = sort { "\L$a" cmp "\L$b" } keys %hash;
注意: 計算が高くつくものであったり、ハッシュがたくさんの要素を持っている 場合、計算結果をキャッシュするためにシュワルツ変換を使いたいかもしれません。
もし代わりにハッシュの値でソートしたいなら、それを探すためにハッシュキーを 使います。 やはりキーのリストを使いますが、今度はその値でソートします。
my @keys = sort { $hash{$a} <=> $hash{$b} } keys %hash;
ここから、より複雑なものにできます。 ハッシュ値が同じ場合は、ハッシュキーによる第二段階のソートを提供できます。
my @keys = sort {
$hash{$a} <=> $hash{$b}
or
"\L$a" cmp "\L$b"
} keys %hash;
"In Memory Databases" in DB_File にあるように、DB_File
モジュールと tie()
を使った、$DB_BTREE ハッシュ束縛を使うことができます。 CPAN の Tie::IxHash
モジュールも有益かもしれません。 これによりハッシュはソートされた状態のままになりますが、tie インターフェースによって被る速度低下を気に入らないかもしれません。 あなたは本当にこれが必要ですか? :)
ハッシュはスカラのペアからなります: 最初のスカラがキーで、 二番目のスカラが値です。 キーは文字列、数値、リファレンスのいずれの種類のスカラであっても 強制的に文字列にされます。 %hash の中に $key
というキーが既にあれば、exists($hash{$key})
は 真を返します。 与えられたキーに対する値は undef
とすることができます。 これは $hash{$key}
を undef
にして、exists $hash{$key}
が真を 返すという状態です。 これは ($key
, undef
)がハッシュに存在しているということを示しています。
図が助けになるでしょう。 以下は %hash
のテーブルです:
keys values
+------+------+
| a | 3 |
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
そしてこれらが保持している状態はこうです:
$hash{'a'} is true
$hash{'d'} is false
defined $hash{'d'} is true
defined $hash{'a'} is true
exists $hash{'a'} is true (Perl 5 only)
grep ($_ eq 'a', keys %hash) is true
ここで
undef $hash{'a'}
とすると、テーブルはこうなります:
keys values
+------+------+
| a | undef|
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
そしてその状態は以下のようになります。 大文字になっているのが変わった場所です:
$hash{'a'} is FALSE
$hash{'d'} is false
defined $hash{'d'} is true
defined $hash{'a'} is FALSE
exists $hash{'a'} is true (Perl 5 only)
grep ($_ eq 'a', keys %hash) is true
最後の二つに注目してください: あなたは undef 値を保持していますが、 キーは define されているのです!
さて、こんどは以下の例を考えてみましょう:
delete $hash{'a'}
とすると、テーブルはこうなります:
keys values
+------+------+
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
そしてその状態は以下のようになります。 大文字になっているのが変わった場所です:
$hash{'a'} is false
$hash{'d'} is false
defined $hash{'d'} is true
defined $hash{'a'} is false
exists $hash{'a'} is FALSE (Perl 5 only)
grep ($_ eq 'a', keys %hash) is FALSE
ほら、エントリが丸ごとなくなっていまいました!
これは tie されたハッシュの EXISTS() の実装に依存します。 たとえば、DBM* ファイルに tie されたハッシュには undef という考え方はありません。 これはまた DBM* ファイルにとっては exists() と defined() とは同じことであり、 そういったものに対して行っていることは 通常のハッシュに対して行っていることとは違うのだということなのです。
(brian d foy によって寄贈されました)
each
をリセットするために keys
関数か values
関数が使えます。 他に何もせずに単に each
で使われているイテレータをリセットするには、 これらの一つを無効コンテキストで使います:
keys %hash; # resets iterator, nothing else.
values %hash; # resets iterator, nothing else.
perlfunc にある each
の説明を参照してください。
まず最初にハッシュからキーを取りだして、それをリストに格納します。 そして、先に説明した「重複の削除」問題の解決を行います。例:
%seen = ();
for $element (keys(%foo), keys(%bar)) {
$seen{$element}++;
}
@uniq = keys %seen;
あるいはもっと簡潔に:
@uniq = keys %{{%foo,%bar}};
もし本当にメモリ空間を節約したいのなら:
%seen = ();
while (defined ($key = each %foo)) {
$seen{$key}++;
}
while (defined ($key = each %bar)) {
$seen{$key}++;
}
@uniq = keys %seen;
自分自身で構造を文字列化する(うれしくないですね)か、MLDBM モジュール (Data::Dumper を使います)を CPAN から取ってきて、DB_File か GDBM_File のいずれかのトップレイヤーにします。
CPAN にある Tie::IxHash
を使います。
use Tie::IxHash;
tie my %myhash, 'Tie::IxHash';
for (my $i=0; $i<20; $i++) {
$myhash{$i} = 2*$i;
}
my @keys = keys %myhash;
# @keys = (0,1,2,3,...)
(brian d foy によって寄贈されました)
本当に古いバージョンの Perl を使っているのですか?
普通は、存在しないキーの値にアクセスしてもキーは作成 されません。
my %hash = ();
my $value = $hash{ 'foo' };
print "This won't print\n" if exists $hash{ 'foo' };
しかし、$hash{ 'foo' }
をサブルーチンに渡す場合は特別扱いでした。 $_[0]
に直接代入できるので、Perl はそのような代入に備える必要があり、 そのためハッシュキーを予め作成していました:
my_sub( $hash{ 'foo' } );
print "This will print before 5.004\n" if exists $hash{ 'foo' };
sub my_sub {
# $_[0] = 'bar'; # create hash key in case you do this
1;
}
しかし、Perl 5.004 からは、この状況は特別扱いで、Perl は代入が 行われた場合にのみハッシュキーを作成します:
my_sub( $hash{ 'foo' } );
print "This will print, even after 5.004\n" if exists $hash{ 'foo' };
sub my_sub {
$_[0] = 'bar';
}
しかし、昔の振る舞いにしたい(そしてこれにより奇妙な副作用が起こることについて 慎重に考えた)なら、代わりにハッシュスライスを渡せます。 Perl 5.004 はこれを特別扱いしませんでした:
my_sub( @hash{ qw/foo/ } );
通常はハッシュのリファレンスを使います。 多分以下のようになるでしょう:
$record = {
NAME => "Jason",
EMPNO => 132,
TITLE => "deputy peon",
AGE => 23,
SALARY => 37_000,
PALS => [ "Norbert", "Rhys", "Phineas"],
};
リファレンスは perlref と perlreftut に説明があります。 複雑なデータ構造の例が perldesc と perllol にあります。 構造体とオブジェクト指向クラスの例が perltoot にあります。
(brian d foy と Ben Morrow によって寄贈されました)
ハッシュキーは文字列なので、実際にリファレンスをキーとして使うことは できません。 もしそうしようとすると、perl はリファレンスを(例えば HASH(0xDEADBEEF)
の 形に)文字列化します。 少なくとも自分自身で追加の作業をしない限り、文字列化された形から リファレンスを得ることはできません。
ハッシュのエントリはリファレンスされた値がスコープ外に出ても 存在していて、その後 Perl が同じアドレスに別の変数を割り当てる可能性が あることを忘れないでください。 これは、新しい変数が偶然古い変数の値と関連づけられるかもしれないことを 意味します。
Perl 5.10 以降を使っていて、単に後で参照するためにリファレンスで 値を保管したいだけなら、 コアの Hash::Util::Fieldhash モジュールが使えます。 これはまた、もしマルチスレッドを使っている(つまり全ての変数は 新しいアドレスに再割り当てされ、文字列化した結果が変わる)場合は キーの名前の変更を扱い、リファレンスされた変数がスコープ外に 出た場合はガベージコレクションを行います。
本当にそれぞれのハッシュエントリから実際のリファレンスを得る必要があるのなら、 それに必要な作業を行う Tie::RefHash モジュールが使えます。
Perl はバイナリクリーンですから、バイナリデータをうまく扱えます。 しかし、 Windows や DOS では、バイナリファイルに対して行末の変換を避けるために、 binmode
を使う必要があります。 一般的には、バイナリデータを扱いたいときはいつでも binmode
を 使うべきです。
"binmode" in perlfunc と perlopentut も参照してください。
もし 8 ビットテキストデータについて考えているのであれば、perllocale を 参照してください。 ただしマルチバイトキャラクターを扱いたいと考えているなら、幾つかの 罠(gotchas)があります。 正規表現のセクションを参照してください。
"NaN" や "Infinity" のような IEEE 表記については気にしないと仮定すると、 正規表現を使って行うことができます。
if (/\D/) { print "has nondigits\n" }
if (/^\d+$/) { print "is a whole number\n" }
if (/^-?\d+$/) { print "is an integer\n" }
if (/^[+-]?\d+$/) { print "is a +/- integer\n" }
if (/^-?\d+\.?\d*$/) { print "is a real number\n" }
if (/^-?(?:\d+(?:\.\d*)?|\.\d+)$/) { print "is a decimal number\n" }
if (/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/)
{ print "a C float\n" }
この作業のためによく使われるいくつかのモジュールがあります。 Scalar::Util (5.8 と共に配布されています) は、変数が数値に見えるか どうかを決定するために perl の内部関数 looks_like_number
へのアクセスを 提供します。 Data::Types は上記のものとその他の正規表現の両方を使ってデータ型の バリデートを行う関数をエクスポートします。 3 つめとして、Regexp::Common
には様々な種類の数値にマッチングする 正規表現があります。 これら 3 つのモジュールは CPAN にあります。
POSIXシステムを使っているのなら、Perlは POSIX::strtod
関数をサポートしています。 そのセマンティックは扱いにくいもので、もっと便利にアクセスするための getnum
関数を以下に例示します。 この関数は文字列を引数に取り、その文字列中で見つかった数字列に対応する 数値を返し、入力がCの小数点表記にあわないものであれば undef
を返します。 is_numeric
関数は“これは数値か?”ということを知りたい場合に getnum
のフロントエンドとなります。
sub getnum {
use POSIX qw(strtod);
my $str = shift;
$str =~ s/^\s+//;
$str =~ s/\s+$//;
$! = 0;
my($num, $unparsed) = strtod($str);
if (($str eq '') || ($unparsed != 0) || $!) {
return undef;
}
else {
return $num;
}
}
sub is_numeric { defined getnum($_[0]) }
あるいは、CPAN にある String::Scanf モジュールをチェックしてみてください。 POSIX
モジュール(標準 Perl 配布キットの一部です)は文字列から 倍精度浮動小数点数や長整数への変換を適切に行う strtod
と strtol
を 提供しています。
一部の特定のアプリケーションでは、DBM モジュールの一つを使うことができます。 AnyDBM_File を参照してください。 より一般的には、CPAN にある FreezeThaw
, Storable
といった モジュールをあたってみるべきでしょう。 Perl 5.8 から、Storable
は標準配布の一部となりました。 以下に Storable
の store
と retrieve
を使った例を挙げます:
use Storable;
store(\%hash, "filename");
# later on...
$href = retrieve("filename"); # by ref
%hash = %{ retrieve("filename") }; # direct to hash
CPAN にある Data::Dumper
モジュール(5.005 以降では Perl のリリースに 含まれています)はデータ構造を出力するのに向いています。 CPAN にある Storable
モジュール(5.8 以降では Perl のリリースに 含まれています)はその引数を再帰的にコピーする dclone
という関数を提供しています。
use Storable qw(dclone);
$r2 = dclone($r1);
ここで $r1
にはあなたの望むデータ構造のリファレンスを置くことができます。 これは深くコピー(deeply copied)されます。 dclone
はリファレンスを取り リファレンスを返すので、コピーしたいものが配列のハッシュであったりした 場合には余計なpunctuationが必要となるでしょう。
%newhash = %{ dclone(\%oldhash) };
(Ben Morrow によって寄贈されました)
UNIVERSAL
クラス (UNIVERSAL) を参照してください)が使えます。 しかし、これを行うことによる結果の考慮についてはとても注意深く行ってください: 全てのオブジェクトにメソッドを追加すると、想定外の結果になる可能性が とても高いです。 可能なら、あなたの作る全てのオブジェクトを共通の基底クラスから 継承するようにするか、ロールに対応している Moose のような オブジェクトシステムを使う方が良いです。
CPAN から Business::CreditCard
モジュールを入手してください。
CPAN にある PGPLOT
モジュールにある arrays.h/arrays.c というものが それをします。 倍精度実数や単精度実数を大量に扱うのであれば、CPAN にある PDL
モジュールを使うことを考えてみるとよいでしょう。 これは number-crunching を簡単にしてくれます。
コードについては http://search.cpan.org/dist/PGPLOT を参照してください。
Copyright (c) 1997-2010 Tom Christiansen, Nathan Torkington, and other authors as noted. All rights reserved.
This documentation is free; you can redistribute it and/or modify it under the same terms as Perl itself.
Irrespective of its distribution, all code examples in this file are hereby placed into the public domain. You are permitted and encouraged to use this code in your own programs for fun or for profit as you see fit. A simple comment in the code giving credit would be courteous but is not required.