ようこそゲストさん

Magical Diary, beta version

[Perl] リスト操作

2007/07/19 23:42 HIRATA Yasuyuki

今日はPerl初心者向け。

リスト操作関数

Perlのリストを操作する関数には map, grep, sort などがある。

リストを元に別のリストを得る: map BLOCK LIST
LIST の要素それぞれについて BLOCK を評価した結果に変更したリストを返す。ここで、BLOCK 内では現在処理中の要素が $_ に代入される。プログラム例を以下に示す:
@original = qw[0 1 2 3 4];
@altered = map { $_ + 5 } @original;
print "@altered\n";
実行結果:
5 6 7 8 9
リストから特定の条件で抜き出したリストを得る: grep BLOCK LIST
LIST の要素それぞれについて BLOCK を評価した結果が真 ("0", undef 以外) である要素を抜き出したリストを返す。$_ の扱いは map と同様。プログラム例を以下に示す:
@original = qw[0 1 2 3 4];
@altered = grep { $_%2 == 0 } @original;
print "@altered\n";
実行結果:
0 2 4
リストを並べ替える: sort BLOCK LIST
LISTBLOCK を評価した結果に基づいて並べ替えたリストを返す。ここで、BLOCK 内では LIST 中から $a, $b として2個の値が与えられるので、その値を元に整数を返す必要がある。返すべき値は、得たいリストで $a を先にしたい場合には負の値、$b を先にしたい場合には正の値である。プログラム例を以下に示す:
@original = qw[mami emi pelsia yumi];
@ordered = sort { length($a) <=> length($b) } @original;
print "@ordered\n";
実行結果:
emi mami yumi pelsia
BLOCK 省略時は文字列比較として扱われる。単純な数値比較をしたい場合には、@ordered = sort { $a <=> $b } @original とすればよい。なお、ここで利用している <=> は二項演算子であり、両辺の値を数値比較して -1, 0, 1 を返す。

上記の例では、ブロックを利用する場合について記載しているが、これ以外の指定も可能である。詳細は perldoc を参照のこと。

応用例

複雑なデータ構造を持ったリストのソート

以下はデータそのものを比較せず、各要素が持つハッシュ情報 (へのリファレンス) を元に並べ替える例である

@girls = ({name => "Morisawa Yu", age => 10},
          {name => "Pelsia", age => 11},
          {name => "Kaduki Mai", age => 11},
          {name => "Hanazono Yumi", age => 11},
          {name => "Shinohara Miho", age => 8});
@ordered = sort { $a->{age} <=> $b->{age} } @girls;
foreach my $x (@ordered) {
  print "$x->{name} $x->{age}\n";
}

実行結果:

Shinohara Miho 8
Morisawa Yu 10
Pelsia 11
Kaduki Mai 11
Hanazono Yumi 11

Schwartian Transform (シュワルツ変換)

以下のプログラムはファイル名をそのサイズ順に並べ替えるプログラムであるが、比較のたびに -s $a <=> -s $b 部が呼び出される。(-s はファイルサイズを得る単項演算子) -s は内部でシステムコールの stat を呼び出すため、コストは結構高く、ファイル数が多くなると実行時間に影響する。

@files = qw[mami.txt emi.txt pelsia.txt yumi.txt];
@ordered = sort { -s $a <=> -s $b } @files;
print "@ordered\n";

このような場合には、最初にコストが高い (呼び出し回数を減らしたい) 処理結果を保存して、それを呼び出せばよい。プログラム例を以下に示す:

@files = qw[mami.txt emi.txt pelsia.txt yumi.txt];
@tmp0 = map { [$_, -s] } @files;
@tmp1 = sort { $a->[1] <=> $b->[1] } @tmp0;
@ordered = map { $_->[0] } @tmp1;
print "@ordered\n";

ここで、@tmp0 は以下のデータ構造を持つ (数字は当然環境によって異なる):

@tmp0 = (["mami.txt", 95],
         ["emi.txt", 1211],
         ["pelsia.txt", 4811],
         ["yumi.txt", 2832]);

このコードの一時変数を排除すると下記の通りとなる。ただし、一時変数を利用する場合に比較して速度が低下する。

@files = qw[mami.txt emi.txt pelsia.txt yumi.txt];
@ordered = map { $_->[0] }
           sort { $a->[1] <=> $b->[1] }
           map { [$_, -s] } @original;
print "@ordered\n";

このような方法で行う map-sort-map は Schwartian Transform (シュワルツ変換) と呼ばれる。

# hoge 2007年07月20日(金) 午後0時35分

Schwartian TransformはRandal L. Schwartzにちなむものなので、「シュワルツ変換」と書くべきでは。

# HIRATA Yasuyuki 2007年07月20日(金) 午後7時12分

ありがとうございます。修正しました。


#  非公開コメント   

  • TB-URL(確認後に公開)  http://diary.asuka.net/011/tb/
  • 続・ソート Magical Diary, beta version HIRATA Yasuyuki
    シュワルツ変換の代替先日の記事で、一時変数を利用する場合に比較して速度が低下すると書いたが、これは使用するメモリ量が多くなること、データ構造が若干に複雑になることに起因する。(とはいえ、比較のたびにコストが高い処理を行う場合と比較すれば雲泥の差ではある。)...
© 2007 HIRATA Yasuyuki <yasu@asuka.net>, all rights reserved