Perl x Open Food Facts Hackathon: Paris, France - May 24-25 Learn more

#!/usr/bin/perl
use 5.001 ; use strict ; use warnings ;
use Getopt::Std ; getopts 'cf:g:i:nkKqrvy:=~@:' ,\my %o ;
use List::Util qw/minstr maxstr sum/ ;
use Term::ANSIColor qw/:constants color / ; $Term::ANSIColor::AUTORESET = 1 ;
use FindBin qw[ $Script ] ;
sub Reading ( ) ;
sub Resulting ( ) ;
sub sigint1 ( ) ;
sub sigint2 ( ) ;
sub cyc_rep ( ) ;
sub y_init ( ) ;
sub y_filter ( $ ) ;
my $cyc_len = $o{'@'} // 1e7 ; # 何行毎にレポートを発生させるか。
my ( $time0 , $time00 ) = ( time ) x 2 ;
my @y_ranges ; # 出力される値の範囲が指定された場合の挙動を指定する。
my %kv ; ## $kv{ key } { value } , key が1列目。value が2列目以降。出現回数が記録される。
$o{f} //= 1 ; # どの位置(区切り文字の何列目)でキーとバリューを分けるか。
y_init ;
Reading ;
Resulting ;
exit ;
sub Reading ( ) {
# 読取り 
$SIG{INT} = \&sigint1 ;
$o{i} //= "\t" ;
my $isep = eval qq[qq[$o{i}]] ;
<> if $o{'='} ; # 先頭行は単純に読み飛ばす。
while( <> ) {
chomp ;
my @F = split /$isep/o, $_ , $o{f} + 1 ;
my $k = join $isep , splice @F , 0 , $o{f} ;
my $v = $F[0] // '' ; # 場合によっては、 // '' の計算は不要かも知れない。
($k,$v) = ($v,$k) if $o{'~'} ; # キーとバリューの反転
$v = defined $v ? $v : '#undef#' ; # <-- - 列が足りない時に現れる
$kv{$k}{$v} ++ ;
cyc_rep if $cyc_len && $. % $cyc_len == 0 ; # 最初は剰余0でなくて1としていたが、出力の理解に問題があり得たので変更。
}
}
sub Resulting ( ) {
$SIG{INT} = sub { print color('reset') ; die } ;
# 表示順序についての処理
my @k = keys %kv ;
if ( $o{n} ) { @k = sort { $kv{$a} <=> $kv{$b} } @k } # -n オプションによりコンテンツの数であらかじめ、ソートする
elsif ( $o{K} ) { @k = sort { $a <=> $b } @k } # -k オプションによりキー文字列であらかじめ、ソートする
elsif ( $o{k} || 1 ) { @k = sort { $a cmp $b } @k } # -k オプションによりキー文字列であらかじめ、ソートする
else { 1 } ;
@k = reverse @k if $o{r} ; # r オプションで逆順ソート
# 出力
my $count = 0 ;
for(@k){
my @values = keys %{$kv{$_}} ;
next unless y_filter scalar @values ; # 異なる個数が指定の値の範囲内でなければ、表示しない。
my @out ;
push @out, $_ ,scalar @values ; # , '$' ; # 各キーで異なるバリュー値の個数を表示する
push @out, ( sum values %{$kv{$_}} ) . '#' if $o{c} ; # -c オプションにより、 各キーの単純な出現回数を表示する。
push @out, minstr(@values) , '..' , maxstr(@values) if $o{v} ;
push @out, ':' , sort splice @values , 0 , $o{g} if $o{g} ; # sort はとりあえず見やすくするため必要。
print join ( "\t" , @out ) , "\n" ;
}
# 2次情報
my $sec = time - $time00 ; # このプログラムの処理にかかった秒数。比較する2個の時刻は秒単位なので、±1秒未満の誤差は発生する。
(my $Rlines = $. ) =~ s/(?<=\d)(?=(\d\d\d)+($|\D))/,/g ; # 3桁毎にコンマで区切る
print STDERR CYAN "$Rlines lines processed. ($Script ; $sec sec.)\n" unless $o{q} ;
}
# Ctrl-C が押された時の処理。1秒間待ってその後4秒以内にCtrl-C が押されたら、次の段階に進む
sub sigint1 ( ) {
alarm 0 ;
print STDERR YELLOW "$.-th line processing. ", scalar localtime() , "\n" ;
# sleep 1 ;
$SIG{ALRM} = sub { $SIG{INT} = \&sigint1 } ;
alarm 4 ;
$SIG{INT} = \&sigint2 ;
} ;
sub sigint2 ( ) {
alarm 0 ;
print color('cyan') ; Resulting ; print color('reset') ;
# sleep 1 ;
$SIG{ALRM} = sub { $SIG{INT} = \&sigint1 } ;
alarm 4 ;
$SIG{INT} = sub { die }
} ;
sub cyc_rep ( ) {
use FindBin '$Script' ;
$| = 1 ;
my $num = $. ;
$num =~ s/(?<=\d)(?=(\d\d\d)+($|\D))/,/g ; # 3桁毎にコンマで区切る
print STDERR GREEN $num , ":\t" , sprintf "%02d:%02d:%02d" , ( localtime )[2,1,0] ; # <-- 標準出力に書込み
print STDERR "\t" , GREEN time - $time0 , " sec.\t($Script)" ;
$time0 = time ;
print STDERR "\n" ;
}
# 次の2個の関数は、出力すべき値の範囲をフィルターの様に指定する。
sub y_init ( ) {
$o{y} //= '' ;
my @ranges = split /,/ , $o{y} , -1 ;
grep { $_ = $_ . ".." . $_ unless m/\.\./ } @ranges ; # .. で囲まれていない数 x は x..xに書き換え
for ( @ranges ) {
m/^(\d*)\.\.(\d*)/ ;
push @y_ranges , [ ( $1 || 1 ) , ( $2 || "Inf" ) ] ;
}
}
sub y_filter ( $ ) {
return not 0 unless @y_ranges ; # 指定が無かった場合はとにかく真を変えす。
for ( @y_ranges ) {
return not 0 if $_->[0] <= $_[0] && $_[0] <= $_->[1] ;
}
return not 1 ;
}
# ヘルプの扱い
sub VERSION_MESSAGE {}
sub HELP_MESSAGE{
use FindBin qw [ $Script ] ;
$ARGV[1] //= '' ;
open my $FH , '<' , $0 ;
while( <$FH> ){
s/\$0/$Script/g ;
print $_ if $ARGV[1] eq 'opt' ? m/^\ +\-/ : s/^=head1// .. s/^=cut// ;
}
close $FH ;
exit 0 ;
}
=encoding utf8
=head1
$0
主要な用途:
各キーが異なるバリューの値をいくつ持つかを表示する。
通常、1列目がキー列であり、2列目以降がバリュー列であると見なす。
オプション:
[基本]
-c : 各キーの出現個数も表示。
-i str ; 区切り文字の指定。未指定ならタブ文字。
-g num : 具体的な該当文字列を num個表示する。 コロン文字の列の後に。
-y ranges : 異なるバリューの個数を出力する際に、ranges で値の範囲を指定できる。範囲は,と..で指定する。
[応用]
-f num : 左から num 列とそれ以外で、キーとバリューを分離する。未指定なら num = 1 。
-n : 出現個数の昇順で並べ替え
-k : キーのアルファベット順に並べ替え
-K : キーの数値順に並べ替え
-q : 最後の2次情報を出力しない。( なお、 -@ 0 により、途中のレポートも出さない。 )
-r : 上記の表示順序を逆順にする。
-v : さらに冗長に表示する(最小と最大を表示する)
[補助]
-= : 開始行をヘッダと見なし、読み飛ばす。
-~ : 2列の並びを、キーとバリューが、それぞれ2列目、1列目と見なす。
-@ num : 一定行数を読み取る毎に、補助情報を表示。未指定なら1000万行ずつ。出力しない場合は 0 を指定。
Ctrl-C を押したときの挙動:
1. 処理中の行番号の表示。
2. そして、4秒以内に再びCtrl+C が押下されると、それまでの集計状態を表示。
3. さらにそれから4秒以内にCtrl+Cが押下されると、停止する。
開発上のメモ :
結果的に何行を出力したのか、二次情報が欲しい。
=cut