第4章「配列とリスト」つづき

「配列とリストに関する関数」節ということで、しばらくは配列とリストで使える関数の紹介。

配列の要素を連結して文字列化

joinでできますよ、と。C#ではSystem.String.Joinメソッドがあるね。

my @score = (64, 90, 75);
print join(',', @score), "\n";

別に配列じゃなくても良いそうだ。

print join(',', (64, 90, 75)), "\n";

さらに、リストの部分をカッコとコッカで囲まなくてもいいそうだ。

print join(',', 64, 90, 75), "\n";

最後はもはや2引数の関数に見えない。
実行結果は http://ideone.com/8Onyj

文字列を分解してリストにする

joinの反対のことをやってくれるのがsplit関数。つまり、与えられた区切り文字で、指定した文字をちょん切る。

@score = split(/,/, '64,90,75');
print "@score\n\n";

上では、'64'と'90'と'75'という要素からなる配列@scoreを作ってる。
実は区切り文字の部分は単なる文字列ではなくて、正規表現で指定できるのだそうだ。正規表現に関してはまたいつか。
実行結果は http://ideone.com/xy0oE

配列の末尾に要素を追加

配列の末尾(添字が (配列の要素数 - 1) となる位置)に要素を追加するためのpush関数なるものがあるそうな。

my @ar = (0, 1, 2);
# 末尾に-1を追加
push(@ar, -1);
# 末尾に'foo'と'bar'を追加(複数の要素をpushできる)
push(@ar, ('foo', 'bar'));
# 末尾に'HOGE'と'FUGA'を追加(カッコとコッカで囲まなくてもいい)
push(@ar, 'HOGE', 'FUGA');
# どうなってるか確認
print join(', ', @ar), "\n\n";

実行結果は http://ideone.com/rxFoL

配列の末尾から要素を取り出す

配列の末尾から要素を取り出すためのpop関数というものがあるそうな。

my @ar = (0, 1, 2);
# 末尾の要素を取り出す(2を取得)
my $item = pop(@ar);
print "\$item = $item, \@ar = (", join(', ', @ar), ")\n";
# 末尾の要素を取り出す(1を取得)
$item = pop(@ar);
print "\$item = $item, \@ar = (", join(', ', @ar), ")\n";
# 末尾の要素を取り出す(0を取得)
$item = pop(@ar);
print "\$item = $item, \@ar = (", join(', ', @ar), ")\n";
# 末尾の要素を取り出す
# 空の配列に対してpopすると、undefという値が取得される
$item = pop(@ar);
print "\$item = $item, \@ar = (", join(', ', @ar), ")\n";
# 値がundefでないかどうか確かめるには、defined関数を使える
print ("\$item is undef.\n") if(!defined($item));

配列の変な場所に要素を追加したらどうなるのかな? - チキン煮込みチーズミックス4辛 でみたエラーメッセージと同じや!そうか、あれはundefが入ってるのね。たぶん。
実行結果は http://ideone.com/O2ftq

配列の先頭に要素を挿入する

pushは配列の末尾に対する操作だったけど、先頭(添字が0となる位置)に挿入するunshift関数というものがあるそうな。

my @ar = (0, 1, 2);
# 先頭に-1を挿入
unshift(@ar, -1);
# 先頭に'foo'と'bar'を挿入(複数の要素をunshiftできる)
unshift(@ar, ('foo', 'bar'));
# 先頭に'HOGE'と'FUGA'を挿入(カッコとコッカで囲まなくてもいい)
unshift(@ar, 'HOGE', 'FUGA');
# どうなってるか確認
print join(', ', @ar), "\n\n";

先頭に要素を追加するので、それ以降の要素が一個ずつずれる必要があり、push関数よりも時間がかかるのではないだろうか。
実行結果は http://ideone.com/3PLa3

配列の先頭の要素を取り出す

pop関数は配列の末尾から要素を取り出す操作だったけど、先頭から要素を取り出すshift関数というのもあるらしい。

my @ar = (0, 1, 2);
# 先頭の要素を取り出す(0を取得)
my $item = shift(@ar);
print "\$item = $item, \@ar = (", join(', ', @ar), ")\n";
# 先頭の要素を取り出す(1を取得)
$item = shift(@ar);
print "\$item = $item, \@ar = (", join(', ', @ar), ")\n";
# 先頭の要素を取り出す(2を取得)
$item = shift(@ar);
print "\$item = $item, \@ar = (", join(', ', @ar), ")\n";
# 末尾の要素を取り出す
# 空の配列に対してshiftすると、undefという値が取得される
$item = shift(@ar);
print "\$item = $item, \@ar = (", join(', ', @ar), ")\n";
# 値がundefでないかどうか確かめるには、defined関数を使える
print ("\$item is undef.\n") if(!defined($item));

これもunshift関数と同様に、先頭から要素を削除するということはそれ以降の要素が1個ずつずれる必要がある。pop関数よりも処理が重くなるような気がする。
実行結果は http://ideone.com/U8aPG

配列でスタックを実現

push関数とpop関数を組み合わせるとスタックを実現できる。http://ideone.com/tGAcT

# 空っぽのスタックを用意
my @stk = ();
# 'one'をプッシュ
push(@stk, 'one');
# 'two'と'three'をプッシュ
push(@stk, 'two', 'three');
# 1個取り出す('three'が取得できる)
my $item = pop(@stk);
print $item, ' ';
# 'FOUR'と'FIVE'をプッシュ
push(@stk, 'FOUR', 'FIVE');
# また取り出す('FIVE'が取得できる)
$item = pop(@stk);
print $item, ' ';
# 残り全部取り出す
# whileループはまだだけど6章をカンニング
while (defined($item = pop(@stk)))
{
  print $item, ' ';
}

ちょっと気は進まないけど、unshift関数とshift関数を使ってもスタックを実現できる。http://ideone.com/2pXbr

# 空っぽのスタックを用意
my @stk = ();
# 'one'をプッシュ
unshift(@stk, 'one');
# 'two'と'three'をプッシュ
unshift(@stk, 'two', 'three');
# 1個取り出す('two'が取得できる)
my $item = shift(@stk);
print $item, ' ';
# 'FOUR'と'FIVE'をプッシュ
unshift(@stk, 'FOUR', 'FIVE');
# また取り出す('FOUR'が取得できる)
$item = shift(@stk);
print $item, ' ';
# 残り全部取り出す
while (defined($item = shift(@stk)))
{
  print $item, ' ';
}

要は、入口と出口が同じであれば、スタックができるわけね。PHPではSPLにスタックのクラスがあるけど、同じように配列をスタックとして扱うための関数があったような気がする。

配列でキューを実現

キューは入口の反対側に出口があるので、push/pop/unshift/shiftからそうなるような関数の組合わせ、つまり

  • push関数とshift関数
  • unshift関数とpop関数

の2通りでキューを実現できる。

# 空っぽのキューを用意
my @que = ();
# 'one'をキューに格納
push(@que, 'one');
# 'two'と'three'をキューに格納
push(@que, 'two', 'three');
# 1個取り出す('one'が取得できる)
my $item = shift(@que);
print $item, ' ';
# 'FOUR'と'FIVE'をキューに格納
push(@que, 'FOUR', 'FIVE');
# また取り出す('two'が取得できる)
$item = shift(@que);
print $item, ' ';
# 残り全部取り出す
while (defined($item = shift(@que)))
{
  print $item, ' ';
}
# 空っぽのキューを用意
my @que = ();
# 'one'をキューに格納
unshift(@que, 'one');
# 'two'と'three'をキューに格納
unshift(@que, 'two', 'three');
# 1個取り出す('one'が取得できる)
my $item = pop(@que);
print $item, ' ';
# 'FOUR'と'FIVE'をキューに格納
unshift(@que, 'FOUR', 'FIVE');
# また取り出す('three'が取得できる)
$item = pop(@que);
print $item, ' ';
# 残り全部取り出す
while (defined($item = pop(@que)))
{
  print $item, ' ';
}

本によると、push/shiftの方が速いらしい。push/shiftを使いましょう。
実行結果は http://ideone.com/0a0oj http://ideone.com/zqv1r

splice関数について

splice関数を使うと、push/pop/unshift/shiftすべて実装できるらしい。「〜するための関数」と一言で言い表しにくい!引数の与え方に4通りのバリエーションがあるらしい。以下に。
1個目のパターンは、引数4個。

  • 第1引数で与えられる配列の、
  • 第2引数で与えられる添字の要素から、
  • 第3引数で与えられる要素数だけ切り取り戻り値とし、
  • 第4引数で与えられる配列を切り取ったところに挿入する。
my @ar = ('a', 'b', 'c', 'd', 'e', 'f');
my @xyz = ('X', 'Y', 'Z');
my @removed = splice(@ar, 1, 4, @xyz);
print "\@ar = @ar, \@removed = @removed\n";

2個目のパターンは、引数3個。切り取ったところに挿入しない。

my @ar = ('a', 'b', 'c', 'd', 'e', 'f');
my @removed = splice(@ar, 1, 4);
print "\@ar = @ar, \@removed = @removed\n";

3個目のパターンは、引数2個。切り取る長さを指定しない場合は、末尾まで切り取られる。

my @ar = ('a', 'b', 'c', 'd', 'e', 'f');
my @removed = splice(@ar, 1);
print "\@ar = @ar, \@removed = @removed\n";

4個目のパターンは、引数1個。切り取りを開始する添字を指定しない場合は、先頭から切り取られる。つまり、下のスクリプトを実行すると、@arの内容がそっくりそのまま@removedに移動する。

my @ar = ('a', 'b', 'c', 'd', 'e', 'f');
my @removed = splice(@ar);
print "\@ar = @ar, \@removed = @removed\n\n";

実行結果は http://ideone.com/pJfOb

配列を文字コードの昇順で並べ替える

配列を文字コードの昇順で並べ替えるためのsort関数というのがある。

my @week = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
my @sorted = sort(@week);
print join(', ', @sorted), "\n";

実行結果は http://ideone.com/Yvls2

配列の要素を逆順にする

reverse関数が使える。

my @week = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
my @reversed = reverse(@week);
print join(', ', @reversed), "\n";

実行結果は http://ideone.com/nfecI

数値をsortするには

sort関数は文字コードで比較するので、数値の配列を数値の大小で比較したい場合に期待通りに動かない。http://ideone.com/zg0vB

my @nums = (31, 41, 59, 26, 53, 58, 9, 93);
my @sorted = sort(@nums);
print join(', ', @sorted), "\n";

数値としてで比較したい場合は、次のようにする。http://ideone.com/89DQi

my @nums = (31, 41, 59, 26, 53, 58, 9, 93);
my @sorted = sort { $a <=> $b } @nums;
print join(', ', @sorted), "\n";

sort関数の実行のところに、何やら中カッコで囲まれたコードブロックが見える。こいつで制御しているらしい。必ず$aと$bという名前にしないとダメなんだって。