安定なソートと不安定なソート(前編)

Perlのソートはマージソートらしいので性質は安定なのですが、
その性質を確認する方法を調べてみようと思います。

まずは、ありがちなソートから。

use strict;
use warnings;
use v5.10;

my @w = qw/0 0 1 1 1 2 2/;
my @v = qw/a b c d e f g/;

my @data = ();
while ( @w ) {
    push @data, [ shift @w, shift @v ];
}

say '-- before --';
say join ',', (map { $_->[1] } @data);

say '-- after  --';
@data = shuffle( @data );
say join ',', (map { $_->[1] } @data);

say '-- sorted --';
@data = sort { $a->[0] <=> $b->[0] } @data;
say join ',', (map { $_->[1] } @data);

sub shuffle {
    my @data = @_;

    my $n = scalar @data;
    foreach my $i ( 0..($n - 1) ) {
        my $j = int( rand $n );
        @data[$i,$j] = @data[$j,$i];
    }

    return @data;
}

実行結果はこんな感じ。
$ perl aaa.pl
-- before --
a,b,c,d,e,f,g
-- after --
d,e,c,b,g,a,f
-- sorted --
b,a,d,e,c,g,f

何がダメって、シャッフルがダメですね。
これじゃ、ソートの性質が分からないです。

なので、安定なシャッフルを実装します。

use strict;
use warnings;
use v5.10;

my @w = qw/0 0 1 1 1 2 2/;
my @v = qw/a b c d e f g/;

my @data = ();
while ( @w ) {
    push @data, [ shift @w, shift @v ];
}

say 'before';
say join ',', (map { $_->[1] } @data);

say 'after';
@data = shuffle( @data );
say join ',', (map { $_->[1] } @data);

say 'sorted';
@data = sort { $a->[0] <=> $b->[0] } @data;
say join ',', (map { $_->[1] } @data);

sub shuffle {
    my @tmp = @_;

    my @src = ();
    while ( @tmp ) {
        my @wk = ( shift @tmp );
        if ( @tmp ) {
            while ( $wk[0]->[0] == $tmp[0]->[0] ) {
                push @wk, ( shift @tmp );
                last unless @tmp;
            }
        }

        push @src, \@wk;
    }

    my @dst = ();
    while ( @src ) {
        my $i = int( rand scalar(@src) );
        push @dst, shift $src[$i];
        if ( scalar(@{$src[$i]}) == 0 ) {
            @src = grep { @{$_}; } @src;
        }
    }

    return @dst;
}

実行結果はこんな感じ。
$ perl bbb.pl
before
a,b,c,d,e,f,g
after
f,c,d,g,e,a,b
sorted
a,b,c,d,e,f,g

これなら、元に戻りますね。
元に戻るということは、ソートの性質が安定であることを意味します。

でも、このままじゃ、
「安定なシャッフルって何ですか?日本語おかしくないですか?」
ってなりますよね??

そう、安定なシャッフルなんて日本語はさっき思い付いたし、
そもそも、シャッフルなんかしてません。
正確に表現するなら、マージですね。
ソート時の重み単位でグループ分けをして、
ランダムに選んだグループの先頭から要素を取り出して並べています。
一見、シャッフルされているように見えるけど、
グループ単位ではソートされたままです。

という訳で、今回実装したshuffleは、
ソート済みの配列が入力されることを想定しているので、
n回ソートするみたいな芸当はできないですが、
ソートの性質を探るには使えそうな気がします。

あと、これに入力するソートされた配列も自動生成したいですね。

続く。

Leave a Comment