2.10.1. 変数#
Perl の変数にはいくつかの種類があります。1 つの値を代入できるスカラー、複数の値を格納できる配列やハッシュ、さらに変数のメモリ上のアドレスを保持するリファレンスなどが存在します。
2.10.1.1. スカラー#
スカラーは、数値や文字列といった単一の値を 1 つだけ保持できる変数です。変数を利用するには、プログラムの中で「今後この変数を使います」と、あらかじめプログラムに伝える必要があります。この操作を宣言とよびます。
スカラー変数を利用する場合は、変数名の前に $ を付けて宣言します。また、変数を宣言するときは my を付けることが推奨されます。my は変数の首輪です。付けないと、どこへ行くか分かりません。
my $a;
$a = 1;
変数を宣言すると同時に値を代入することもできます。通常は、次のように変数の宣言と値の代入を 1 行で記述します。
my $p = 3.14;
my $dna = "ACGAGCTTATATAGTCTAC";
my $rna = "";
また、変数に値を代入する際、すでに値が定義されている場合には何もせず、値が未定義の場合のみ代入する、という処理も行えます。
my $a;
if (!defined $a) {
$a = "atg";
}
print($a);
## atg
このような「未定義のときだけ代入する」処理は、if を使わずに //= 演算子を用いると簡潔に書けます。
my $a;
$a //= "atg";
print($a);
## atg
同様に、値が真か偽かを判定し、偽の場合のみ代入したいときは、if を使うか ||= 演算子を利用します。なお、Perl では、変数の値が 0 または空文字の場合は偽と判定され、それ以外の場合は真と判定されます。
my $b = 0;
if ($b) {
$b = "1";
}
print($b);
## 1
my $b = 0;
$b ||= "1";
print($b)
## 1
このように、変数の代入だけでも暗号のように複数の書き方が存在します。「いつか使うかも」と学生の頃にメモした代入演算子ですが、その「いつか」は来ませんでした。
2.10.1.2. 配列#
複数の値をまとめて扱うには、配列と呼ばれる変数型を利用します。配列に格納される各値は要素と呼ばれ、インデックスと呼ばれる順序番号によって管理されます。インデックスは 0 から始まり、要素の順序を表します。
2.10.1.2.1. 配列の宣言#
配列を宣言する際には、変数名の前に @ を付けます。複数の値を配列に代入する場合は、各要素をカンマで区切り、全体を ( と ) で囲んで指定します。
my @a = ("A", "B", "C", "D", "E");
my @b = (1, 2, 3, 4, 5);
配列の個々の要素を操作する場合は、$ を用いてインデックスを指定します。例えば、2 番目の要素を書き換え、3 番目の要素を出力する場合は次のように記述します。
my @a = ("A", "B", "C", "D", "E");
$a[1] = "X";
print($a[2]);
## C
配列を宣言する際、要素数や各要素の値をあらかじめ決めておく必要はありません。まず空の配列を用意し、後から任意のインデックスに値を代入することもできます。
my @a = ();
$a[0] = 0;
$a[3] = 3.14;
$a[4] = 8;
1 番や 2 番が未定義でも、いきなり 3 番があっても気にしません。欠番だらけでも動く。それが Perl 流。
2.10.1.2.2. 配列の要素#
配列の要素を順に処理するには、for または foreach を用います。for はインデックスを使って要素にアクセスする方法です。
my @arr = ("A", "C", "D", "B");
for (my $i = 0; $i < @arr; $i++) {
print $arr[$i] . "\n";
}
## A
## C
## D
## B
foreach は、配列の各要素を順に一時変数へ代入して処理する構文です。インデックスを意識せずに記述できるため簡潔になります。一時変数の名前は任意に付けることができ、以下の例では $ele としています。
foreach my $ele (@arr) {
print $ele . "\n";
}
## A
## C
## D
## B
2.10.1.2.3. 要素の並べ替え#
配列の要素を昇順に並べ替えるには sort 関数を使用します。reverse と組み合わせることで降順にすることも可能です。
my @arr = ("A", "C", "D", "B");
foreach my $ele (sort @arr) {
print $ele . "\n";
}
## A
## B
## C
## D
foreach my $ele (reverse sort @arr) {
print $ele . "\n";
}
## D
## C
## B
## A
2.10.1.2.4. 要素の削除#
配列から要素を削除するには、shift、pop、splice を使用します。shift は先頭要素、pop は末尾要素を削除し、splice は指定範囲の要素を削除します。削除された要素は戻り値として返されます。
my @arr = ("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K");
my $a = shift(@arr);
# $a => A
# @arr => ("B", "C", "D", "E", "F", "G", "H", "I", "J", "K")
my $b = pop(@arr);
## $b => K
## @arr => ("B", "C", "D", "E", "F", "G", "H", "I", "J")
splice では、開始位置と削除する要素数を指定します。
my @c = splice(@arr, 2, 3);
## @c => ("D", "E", "F")
## @arr => ("B", "C", "G", "H", "I", "J")
2.10.1.2.5. 要素の追加#
配列の先頭に要素を追加するには unshift、末尾に追加するには push を使用します。
my @arr = ("A", "B", "C", "D", "E");
unshift(@arr, "0");
## @arr => ("0", "A", "B", "C", "D", "E")
push(@arr, "Z");
## @arr => ("0", "A", "B", "C", "D", "E", "Z")
配列の途中に要素を追加する場合は splice を使用します。splice は要素を削除する関数だが、4 番目の引数を与えることで、配列から要素を削除した上で、その位置に新しい要素を追加することもできます。なお、元の配列から要素を削除しない場合は、3 番目の引数に 0 を指定します。
my @arr = ("A", "B", "C", "D", "E");
splice(@arr, 2, 0, "X");
## @arr => ("A", "B", "X", "C", "D", "E")
my @arr = ("A", "B", "C", "D", "E");
splice(@arr, 2, 2, "X");
## @arr => ("A", "B", "X", "E")
my @arr = ("A", "B", "C", "D", "E");
my @b = (1, 2, 3);
splice(@arr, 2, 2, @b);
## @arr => ("A", "B", 1, 2, 3, "E")
2.10.1.2.6. 要素の検索#
grep 関数を使うと、条件に一致する要素だけを抽出できます。例えば、文字列 ATG を含む要素を取り出して、新しい配列に保存する場合は次のように記述します。
my @atg = grep(/ATG/, @seq);
また、検索結果を変数に保存せずに、そのまま foreach などに渡して処理することも可能です。
foreach my $ele (grep(/ATG/, @seq)) {
print "$ele\n";
}
2.10.1.3. ハッシュ#
ハッシュは、配列と同様に複数の要素を格納できる変数型です。ただし、配列が各要素をインデックスで管理するのに対し、ハッシュではキーと値を 1 組のペアとして要素を管理します。例えば、辞書のようにキーを単語、値を解説文としてデータを管理したり、コドンをキー、対応するアミノ酸を値とした対応表を作成したりすることができます。このように、キーを用いて値を直接参照できる点がハッシュの大きな特徴です。
2.10.1.3.1. ハッシュの宣言#
ハッシュを宣言する際は、変数名の前に % を付けます。複数のキーと値のペアを代入する場合は、各ペアをカンマで区切り、全体を { と } で囲んで指定します。また、個々の値を操作する際には $ を用い、操作したいキーを { と } の中に指定します。なお、キーは文字列として扱われるため、クォーテーションで囲みます。
my %dna2aa = ("ATC" => "I", "CCA" => "P", "GTA" => "V");
print($dna2aa{"CCA"});
## P
次のように、まず空のハッシュを宣言し、あとから個々の要素を追加することもできます。
my %codon = ();
$codon{"AUG"} = "M";
$codon{"CGA"} = "R";
$codon{"CCA"} = "P";
2.10.1.3.2. ハッシュの要素#
ハッシュのすべての要素を取り出すには、keys 関数を使ってキーの一覧を取得し、それぞれのキーに対応する値を参照します。なお、ハッシュでは要素の順序は保証されないため、取り出される順番は一定ではありません。
my %dna2aa = ("ATC" => "I", "CCA" => "P", "GTA" => "V");
for my $k (keys %dna2aa) {
print "key: $k, value: $dna2aa{$k}\n";
}
## key: GTA, value: V
## key: CCA, value: P
## key: ATC, value: I
また、keys の代わりに each 関数を用いることで、キーと値のペアを同時に順に取得できます。この場合は for ではなく while を使用します。
while (my ($k, $v) = each %dna2aa) {
print "key: $k, value: $v\n";
}
## key: ATC, value: I
## key: GTA, value: V
## key: CCA, value: P
2.10.1.3.3. 要素の削除#
ハッシュから要素を削除するには delete 関数を使用します。削除したい要素のキーを指定すると、そのキーと対応する値がハッシュから取り除かれます。
my %dna2aa = ("ATC" => "I", "CCA" => "P", "GTA" => "V");
delete $dna2aa{"CCA"};
for my $k (keys %dna2aa) {
print "key: $k, value: $dna2aa{$k}\n";
}
## key: GTA, value: V
## key: ATC, value: I
2.10.1.3.4. 要素の追加#
要素を追加するには、新しいキーを指定して値を代入します。すでに存在するキーを指定した場合は、そのキーに対応する値が上書きされます。
my %dna2aa = ("ATC" => "I", "CCA" => "P", "GTA" => "V");
$dna2aa{"AUG"} = "M";
$dna2aa{"CCA"} = "Q";
for my $k (keys %dna2aa) {
print "key: $k, value: $dna2aa{$k}\n";
}
## key: GTA, value: V
## key: CCA, value: Q
## key: ATC, value: I
## key: AUG, value: M
2.10.1.3.5. 要素の並べ替え#
for 文や while 文でハッシュの要素を取り出す場合、取得される順番はランダムになります。これは、ハッシュが配列のようなインデックスを持たないためです。そのため、保存した順序で要素を取り出すことはできません。ただし、キーや値を基準に並べ替えてから処理することは可能です。
キーを昇順に並べ替えるには、keys と sort を組み合わせて使用します。
my %hash = (
"A" => 455,
"B" => 976,
"C" => 321
);
for my $key (sort keys %hash) {
my $value = $hash{$key};
print "key: $key value: $value\n";
}
## key: A value: 455
## key: B value: 976
## key: C value: 321
値を基準に並べ替える場合は、次のように比較関数を指定します。なぜこう書くのかは学生の頃に覚えたはずですが、今はもう思い出せません。ここは分かった気になって進みましょう。
foreach my $key (sort {$hash{$a} <=> $hash{$b}} keys %hash){
print "key: $key value: $hash{$key}\n";
}
## key: C value: 321
## key: A value: 455
## key: B value: 976
foreach my $key (sort {$hash{$b} <=> $hash{$a}} keys %hash){
print "key: $key value: $hash{$key}\n";
}
## key: B value: 976
## key: A value: 455
## key: C value: 321
2.10.1.4. リファレンス#
リファレンスは、変数そのものではなく、変数が格納されているメモリアドレスを保存するための変数です。通常の変数は値そのものを保持しますが、リファレンスを用いることで、値の実体を共有しながら操作することが可能になります。この仕組みにより、二次元配列や二次元ハッシュ、配列のハッシュなど、複雑なデータ構造を柔軟に扱えるようになります。
2.10.1.4.1. リファレンスの宣言#
メモリアドレスは 1 つの値として扱えるため、リファレンスはスカラー変数として $ を用いて宣言します。ある変数のアドレスを取得するには、変数名の前に \ を付けます。
例えば、スカラー変数 $a のアドレスを取得し、それをリファレンス変数 $r に代入する場合は次のように記述します。
my $a = 3.14;
my $r = \$a;
print($r);
## SCALAR(0x7ffc0e02d8f0)
配列やハッシュの場合も同様に、@ や % の前に \ を付けてアドレスを取得します。
my @arr = (1, 2, 3, 4, 5);
my $r = \@arr;
print($r);
## ARRAY(0x562a312d5838)
my %hash = ("A" => 1, "C" => 2, "G" => 3, "T" => 4);
my $r = \%hash;
print($r);
## HASH(0x562a312d5958)
また、配列やハッシュを直接リファレンスとして作成することもできます。この方法では、変数を別途用意せずにリファレンスを生成できます。
my $r_arr = [1, 2, 3, 4, 5];
print($r_arr);
## ARRAY(0x555aec1c0538)
my $r_hash = {"A" => 1, "C" => 2, "G" => 3, "T" => 4};
print($r_hash);
## HASH(0x555aec1ec408)
2.10.1.4.2. リファレンスの値#
リファレンス変数が保持しているのはアドレスであるため、そのままでは元の値を直接利用できません。そこで、デリファレンスと呼ばれる操作によって、元の変数型に戻して値を参照します。
元の変数がスカラーの場合は、リファレンス変数の前にさらに $ を付けて参照します。つまり、$r がスカラーへのリファレンスであれば、$$r として値を取得します。
my $a = 3.14;
my $r = \$a;
print(${r});
## 3.14
同様に、配列やハッシュを参照する場合は、それぞれ @ や % を用いてデリファレンスします。
my @arr = (1, 1, 2, 3, 5);
my $ref = \@arr;
for (my $i = 0; $i < @{$ref}; $i++) {
print @{$ref}[$i] . "\n";
}
## 1
## 1
## 2
## 3
## 5
my %hash = ("AUG" => "M", "GUA" => "V");
my $ref = \%hash;
while (my ($k, $v) = each %{$ref}) {
print "key: $k, value: $v\n";
}
## key: AUG, value: M
## key: GUA, value: V
2.10.1.4.3. アロー演算子#
配列やハッシュへのリファレンスに対しては、アロー演算子 -> を用いることで、より直感的に要素へアクセスできます。配列の場合は ->[]、ハッシュの場合は ->{} を使用します。
my @arr = (1, 1, 2, 3, 5, 8, 13);
my $ref = \@arr;
print $ref->[0];
## 1
print $ref->[3];
## 3
for (my $i = 0; $i < @{$ref}; $i++) {
print $ref->[$i] . "\n";
}
## 1
## 1
## 2
## 3
## 5
## 8
## 13
my %hash = ("AUG" => "M", "GUA" => "V");
my $ref = \%hash;
print $ref->{"AUG"};
## M
print $ref->{"GUA"};
## V
for my $key (keys %{$ref}) {
print "key: $key, value: $ref->{$key}\n";
}
## key: AUG, value: M
## key: GUA, value: V
2.10.1.4.4. ハッシュのハッシュ#
リファレンスを用いることで、ハッシュの中に別のハッシュを格納するといった多階層構造を作成できます。まず 1 階層目のハッシュを用意し、その値としてハッシュのリファレンスを保存します。
my $r = {};
$r->{"key1"} = {};
$r->{"key1"}->{"subKey1"} = "value11";
$r->{"key1"}->{"subKey2"} = "value12";
$r->{"key2"} = {};
$r->{"key2"}->{"subKey1"} = "value21";
$r->{"key2"}->{"subKey2"} = "value22";
このような構造からすべての要素を取り出すには、for 文や while 文を入れ子にして処理します。
for $k1 (keys %{$r} ){
for $k2 (keys %{$r->{$k1}} ){
print "$k1, $k2, $r->{$k1}->{$k2}\n";
}
}
## key2, subKey2, value22
## key2, subKey1, value21
## key1, subKey1, value11
## key1, subKey2, value12
2.10.1.4.5. ハッシュの配列#
配列の各要素をハッシュにすることも可能です。例えば、次のように遺伝子名とその長さを格納したハッシュの配列を作成できます。
my $arr = [
{"gene" => "GeneX" , "length" => 455 }
{"gene" => "GeneY" , "length" => 976 }
{"gene" => "GeneZ" , "length" => 321 }
];
このようなデータ構造に対しては、ハッシュの値を基準にした並べ替えなども行えます。以下は、length を基準に昇順でソートする例です。
for my $elem (sort {$a->{length} <=> $b->{length}} @{$arr}) {
print "gene: " . $elem->{"gene"} . ", length: " . $elem->{"length"} . "\n";
}
リファレンスをうまく利用することで、複雑なデータ構造も正確に記述でき、解析効率も向上すると言いたいところですが、プログラムはますます読めなくなります。
でも、心配はいりません。これは欠点ではなく、Perl の設計思想です。
青春なんて、だいたい読めません。