LoginSignup
2
3

More than 5 years have passed since last update.

perl で項目数が一定しない場合の多段ソート

Last updated at Posted at 2015-01-12

perl のワンライナーに関しての、適当な解説 #1/#2/#3/#4

GRT

例えば、ここの TABLE A1 の一番右のセルみたいの。
データからの生物分類は、全て、つまり種に至るまできっちり類推されてくれれば良いのですが、「網まではわかるが後は判らん!」とか、「分類でけんかった」とか、そーいうのがざらに出ます。で、それらをソートするののメモ。

一言で言えば、「guttman rosler transform を使う。」だけなんですが。

で、文字列だけのソートだと、有難味が薄いので、数値と文字列とが混った可変長セルのソートを

まずは、例用のテーブル作り。

$ perl -le 'my %h = map { $_, 1 } <{a,b,c,,},{1,2,3,10,7,,},{a,b,c,,},{1,2,3,10,20,,}> ; print for keys %h' > example.txt

hash 出力に頼って、ランダム擬きの表を作成しました。例なので CSV です。

example.txt
c,7,,1
a,2,a,3
c,2,a,20
,,a,3
a,10,a,10
b,10,c,2
b,7,,3
c,10,b,2
c,,b,20
a,2,,

コンセプト

では、これをソート
* 優先順位は、左のカラムから
* 昇順
* 大文字小文字は区別
* 数字は当然、数値ソート
* 今回は小数点に関しては考慮してない
* 空欄は最後に
* 文字列は、可変長想定

コード

GRT.pl
#!/usr/bin/env perl
use warnings;
use strict;
my @arr ;

LINE:
while ( <> ){
    chomp;
    my @col = split /,/, $_ , 4 ;  #1
    my $sort_str = q{} ;

    for my $i ( 0 .. $#col ){      #2
        $sort_str 
            .= $i % 2  == 0 
            ?  pack q{A30}, $col[$i] || '|'  #3 
            :  pack q{L*},  $col[$i] || 100  #4
            ;
    }
    push @arr, [ $_, $sort_str ];
}

printf "%s\n",  $_->[0] for sort { $a->[1] cmp $b->[1] } @arr; #5

これを普通にコマンドラインから実行するだけ

$ perl GRT.pl example.txt
#1 CSV ですので当然、/,/split
  • LIMIT は重要。コレを付け忘れただけでドツボに嵌る
  • LIMIT 無しだと、後ろの空カラムを無視する

split 前に chomp しなきゃいいじゃん」
「アアソウデスネ」

#2 各カラムの内容を舐めます。
  • index で回す事にしたのは、今回の例の条件分岐の為
#3 ソート文字列の作成。
  • 空白は最後にもって来たいので | (ascii 124)を代替文字として pack
    • 例えば、"Unclassified" を最後から二番目にソートしたいなら、 { (ascii 123)を代替文字に
  • A* ではなく A30 にしたのは以下のコード参照
    • 運用時には文字列長に留意が必要
$ perl -le 'my @a = ( [qw(a z)], [qw(ab a)] )  ; print $_->[0] for sort { $a->[1] cmp $b->[1] } map{ [ ( join "\t", @{$_} ), ( join "", map { pack q{A*}, $_ } @{$_} )] }  @a ;'
ab      a
a       z
#4 ソート文字列の作成(数値)

Q の方が良いのかも。

整数 pack コード sSlLqQ のそれぞれは、どこでプログラムが 実行されたとしても固定長のバイト列となります
引用元

右詰め 0 でフィルされた固定長数値ならば、#3 のコードに渡してしまえば良いが、往々にしてソートの事を考えて数値入力はされていない。ファイル名とかファイル名とか、ファイル名とか、、、

#5 ソートの実行

二次元配列の '[X][1]' に入れた、ソート文字列を、文字列比較するだけ。
sort ブロックに or を重ねるのを想像したら、どれだけ楽だか。

生物の階級で。

それでは、最初のサイトで見た様な、生物の階級による表のソートをした場合

  • 中間的分類を除いて domain から 7 階級。文字列のみを期待。
  • 'Unclassified' は一回のみ、 domain に表示

のケースを想定してコードを書く。

なお最初に書いたコードは確実にトラブるコードだった、、、
ので、ついでに、より GRT っぽくリライト

$ perl -F';\s*' -Mvars='@a' -lane 'push @a, join q{}, (( map{ pack q{A30}, /^$/ ? q{|} : /^Unclassified$/ ? q{\{} : $_ } (@F, (q{}) x 6)[0 .. 6 ]), $_ )}{ print substr $_, 210 for sort @a ' SOMETHING.txt
  • '; ' は空白が入ってる。入ってないものがあるかも知れないので ';\s*' の方が適切
  • -F'; ' -a では split のリミット指定が出来ないから、 (@F, (q{}) x 6)[0 .. 6 ] で補わなきゃならない
  • 最初は、-a 使わず split /; /, $_, 7 で書いていたが split 失敗時(区切り文字が出現しない)に、要素数 1 のリストになるので、 (@F, (q{}) x 6)[0 .. 6 ] の方にせざるを得ない
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3