PerlとPHPのソート処理

良く書き方を忘れがちなソート。

特にPerlPHP両方使ってるとPerlはこうだけど、PHPはどうだっけ?とかよくあるのでここらでまとめをしておく。


配列を文字列で昇順・降順ソート

# 昇順
@sarray = sort @array;
@sarray = sort { $a cmp $b } @array;

# 降順
@sarray = sort { $b cmp $a } @array;

Perlのsort関数はブロック表記を省略するとデフォルトで昇順ソートになります。

<?php

// 昇順
sort( $array , SORT_STRING );

// 降順
rsort( $array , SORT_STRING );

PHPのsort関数は引数に渡された配列をソートする。

第二引数を指定しなくも動くには動くが、省略すると値に数値と文字が混在してるときにおかしくなるので明示的にしとく方が無難。


配列を数値で昇順・降順ソート

# 昇順
@sarray = sort { $a <=> $b } @array;

# 降順
@sarray = sort { $b <=> $a } @array;

文字列のソートと殆ど同じですね。cmpを使っていた部分を<=>にするだけです。

<?php

// 昇順
sort( $array , SORT_NUMERIC );

// 降順
rsort( $array , SORT_NUMERIC );

PHPの場合も楽。

第二引数をSORT_STRINGではなく、SORT_NUMERICに変更するだけ。


ハッシュ配列の、あるハッシュのキーの値でソートする

ハッシュ配列のデータ構造で、ある特定のハッシュのキーの値でソートするケースです。

# idの値を数値の昇順でソートする
@data = (
    { id => 3  , data => 'aaa' },
    { id => 10 , data => 'aaa' },
    { id => 2  , data => 'aaa' },
);
@sdata = sort { $a->{id} <=> $b->{id} } @data;

Perlの場合はsort関数が万能なので上記のような感じでカンタンにできます。

<?php

function data_sort ($a,$b) {
   // PHPには<=>演算子が無いので自作
   if ($a['id'] == $b['id']) return 0;
   return ($a['id'] < $b['id']) ? -1 : 1;
}
$data = array(
    array( 'id' => 3  , 'data' => 'aaa' ),
    array( 'id' => 10 , 'data' => 'aaa' ),
    array( 'id' => 2  , 'data' => 'aaa' ),
);
usort( $data , 'data_sort' );

PHPの場合はusort関数などを使ってユーザ定義関数を指定するとことで可能となります。

ちなみにcreate_function関数を使って動的に指定もできますが、個人的にcreate_functoin関数が嫌いなので使ってません。


ハッシュのキーを文字列で昇順・降順ソート

# 昇順
foreach my $key ( sort keys %hash ) { }

# 降順
foreach my $key ( sort { $b cmp $a } keys %hash ) { }

Perlの場合はkeysとsortを組み合わせる。

foreachを使っている理由は単純にPerlにはハッシュをソートして順序を固定するということができないからです。

ま、CPANとかでモジュール使えば可能ですが、それはこの記事の本質とは離れるので割愛ということで。

とにかく、上記のようにすることでハッシュのキーがソートされるのであとはforeachなどで参照してくださいということです。

<?php

// 昇順
ksort($hash,SORT_STRING);

// 降順
krsort($hash,SORT_STRING);

PHPの場合はksortという関数でハッシュ(PHPでは連想配列という名前)のキー名でソートすることができます。

ちなみに数値でソートする場合は先ほどの例と同じくPerlはcmpを<=>に、PHPはSORT_STRINGをSORT_NUMERICに置き換えることで可能です。


ハッシュの値を文字列で昇順・降順ソート

# 昇順
foreach my $key ( sort { $hash{$a} cmp $hash{$b} } keys %hash ) { }

# 降順
foreach my $key ( sort { $hash{$b} cmp $hash{$a} } keys %hash ) { }

Perlの場合はsort関数でハッシュの値にアクセスしてそれをcmpで比較するだけ。

<?php

// 昇順
asort($hash,SORT_STRING);

// 降順
arsort($hash,SORT_STRING);

PHPの場合はこれまた専用のasortという関数があるのでそれを使用する。

ちなみに数値でソートする場合は先ほどの例と同じ(以下略


ユーザ定義の自作ソート

sub hoge {
    return $a cmp $b;
}
@array = sort hoge @array;

Perlの場合はsort関数のブロック部分に自作関数の名前を指定するだけです。

<?php

function hoge($a,$b) {
    return strcmp($a,$b);
}
usort($array,'hoge');

PHPの場合は先ほどからもう既に自作ソートをやってましたね。

usort関数の他にもuasort関数やuksort関数などもあるのでそれらも理解しておきましょう。


シュワルツ変換

シュワルツ変換とは、ソート前にソートしやすいようにデータ構造を用意してあげてからソートする手法のことですね。

例題としてある行の一部分でソートしたいケースを考えてみます。

@line = (
    'hoge,3,bar',
    'muge,1,foo',
    'hage,4,baz',
    'mugo,2,biz',
);

@sline =
    map  { $_->[0] }
    sort { $a->[1] <=> $b->[1] }
    map  { [ $_ , (split /,/)[1] ] } @line;

Perlの場合はmapとsortの組み合わせで実に美しいシュワルツ変換を実現できます。

この書き方考えた人すげぇよ。マジで。Effective Perlに詳しい説明が載ってたと思うのでそれも参考に。

ちなみに上記の例は昇順ですが、降順にする場合はsortの部分の$aと$bを逆にしてください。

<?php

$line = array(
    'hoge,3,bar',
    'muge,1,foo',
    'hage,4,baz',
    'mugo,2,biz',
);

$sort = array();
foreach ( $line as $li ) {
    $tmp = preg_split('/,/',$li);
    $sort []= $tmp[1];
}

array_multisort($sort,SORT_ASC,SORT_NUMERIC,$line);

PHPの場合はarray_multisortという便利な関数があるのでそれを利用します。

SORT_ASCをSORT_DESCに変えると降順になります。またSORT_NUMERICをSORT_STRINGに変えると文字列比較になります。

しかしSORT_DESCとかあるのならrsortやkrsort関数とかいらないと思うんだけどその辺PHPってやっぱ関数多すぎ!の要因の一つだよね。

とまぁこんな感じでPerlPHPのソートの方法をまとめてみました。と。