ようこそゲストさん

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 『Schwartian TransformはRandal L. Schwartzにちなむものなので、「シュワルツ変換」と書くべきで...』 (2007/07/20 12:35)

# HIRATA Yasuyuki 『ありがとうございます。修正しました。』 (2007/07/20 19:12)

  • 続・ソート Magical Diary, beta version HIRATA Yasuyuki
    シュワルツ変換の代替先日の記事で、一時変数を利用する場合に比較して速度が低下すると書いたが、これは使用するメモリ量が多くなること、データ構造が若干に複雑になることに起因する。(とはいえ、比較のたびにコストが高い処理を行う場合と比較すれば雲泥の差ではある。)...

[Perl] PDFJの使い方

2007/07/19 0:44 HIRATA Yasuyuki

Perl用のフリーな日本語PDF生成モジュールとしては PDFJ がある。 組版ルールは JIS X 4051 (日本語文書の組版方法) に準拠しているので、日本語を取り扱うには非常に便利である。

以下、このモジュールを使用してPDFファイルを生成する方法について簡単に記載する。このプログラムでは省略しているが、段落を指定の高さごとに分割してページ処理をすることも可能である。なお、このまま利用するためには、ソースのエンコードを utf-8 とすること。

  1. 文書オブジェクトの生成。
    # プラグマとかモジュールとか
    use utf8;
    use strict;
    use warnings;
    use PDFJ qw[UTF8];
    
    # 文書オブジェクトの生成
    # PDFのバージョンは迷ったら1.3を指定するのが無難。(Acrobat 4以上に対応)
    # 幅と高さはポイントで指定する。
    my $paperWidth = 595;
    my $paperHeight = 842;
    my $doc = new PDFJ::Doc(1.3, $paperWidth, $paperHeight);
    
  2. ページオブジェクトの生成。
    # ページオブジェクトの生成; 文書の最後にページを追加する。
    my $page = $doc->new_page();
    
  3. フォントとテキストスタイルの定義。ここでは、和文フォントを Ryumin-Light (エンコードはUniJIS-UCS2-HW-H)、英文フォントを Times-Roman としている。
    # フォント
    my $font = $doc->new_font("Ryumin-Light", "UniJIS-UCS2-HW-H", "Times-Roman", undef);
    my $fontSize = 16;
    my $textStyle = TStyle(font => $font, fontsize => $fontSize);
    
  4. 単純にテキストを配置する。(折り返し無し)
    # テキストオブジェクト
    my $text = Text("はにゃーん", $textStyle);
    
    # (100, 100) にテキストを配置。
    $text->show($page, 100, 100);
    
  5. 段落を作成 (自動的に折り返される) して、配置する。
    # 今度は段落を作成する。
    my $haahaa = Text("ハァハァ"x100, $textStyle);
    
    # $fontSize*20 ポイント (20文字分) で折り返し。
    # 実際には 20.01 くらいにしておくと最後の1文字が行末に来る場合、
    # 余計な空行が入らなくてよさげ。
    my $pageStyle = PStyle(size => $fontSize*20, linefeed => $fontSize + 10, align => "b");
    my $para = Paragraph($haahaa, $pageStyle);
    $para->show($page, 100, 800);
    
  6. ファイルに出力。
    # pdfj.pdf に書き出し。stdoutへ出力することも可能。
    $doc->print("pdfj.pdf");
    

上記プログラムで出力されたPDFファイル

© 2007 HIRATA Yasuyuki <yasu@asuka.net>, all rights reserved