大きな数に下の位から3桁ごとに色をつける


何桁もある数を一瞬で把握するにはどうしたら良い?

何ギガバイトもあるような大きなデータを、コマンドライン上で扱っていると、大きな桁数の数を目にすることになる。さて、8桁も9桁もある数をどうやったら一目で何千万とか何億とか分かるだろうか?場合によっては、兆、京単位の数も現れることもあるだろう。SQLの結果であれ、行数を数える wc -l の出力であれ、大きな数を目にすることが近年多い。この問題に適切に対処する方法はまだあまり世に知られて無いかもしれないので、自分で解決策を考えてみた。

コンマで無くて色を付けたら良いと思われる。

上記を実現するプログラム

上記は、0から9までの数字が並んでいれば、その下の位から3桁ずつ色を付けている。なお、コンマで区切る方法も考えられるが、数としての意味のあるところだけコンマを挿入しようにもなかなかうまくいかない。数字の並びだけを単純に3桁ごとにコンマで区切ると意味のない所にコンマが入るし、そして難しいアルゴリズムを考え出したらキリが無いだろう。日付を日付とうまく判断して、いつでも適切な記号を付加できる簡単な方法はあるだろうか?そもそも大概の場合、文字領域のレイアウトが崩れたりして、読みにくくなる。

やっぱり下の図のように色を付けた方が良いだろう。ファイル名の中に数が現れてそこに色がついても、あまり不自然ではない。

上記の画像のような色を付けることを実現するプログラムは、この記事の最後にあるPerlプログラムである。
このプログラムについては、Macのターミナルでも、WindowsのコマンドプロンプトでもCygwinでも、Amazon Linux でもうまく色が付くことを既に確かめてある。プログラムは下記のようになる。プログラム名は初めの頃は、color, colors, coloring といろいろ変えたが、今の所は colorplus としている。

なお、下記のプログラムは、3桁ごとに下位から色を付けるだけではなくて、4桁ごとを色を付けたり、色があったら色を消す機能を付けたり、タブによる区切りで色をつけたりする機能があったりする。

colorplus.pl
#!/usr/bin/perl
use 5.014 ; use strict ; use warnings ;  
use Getopt::Std ; getopts "e:l:t:v034,:" , \my%o ; 
use Term::ANSIColor qw/color colored colorstrip/ ;
$o{','} //= "\t" ; 
$| = 1 if $o{v} ;  
my %except ; $except {$_} =1 for split /,/ , $o{e}//'' ;
while ( <> ) { 
    print $_ if $except{$.} ; # 色情報の消去
    $_ = colorstrip ($_) if $o{0} ; 
    $_ = &ColorDigits ($_)  if $o{3} || $o{4} ; 
    $_ = &ColorTabbing ($_,$o{t})  if $o{t} ; 
    $_ = &ColorLines ($_,$o{t})  if $o{l} ; 
    print $_ ;
}
exit 0 ; 

# 指定された数の列のタブごとに、青い背景色をつける。
sub ColorLines ($$) { 
    $. % $o{l} == 1 ? colored ( $_ , "on_blue" ) : colored ( $_ , "on_black" ) ; 
}

sub ColorTabbing ($$) { 
    chomp ;
    my @F = split /$o{','}/ , $_[0]  ;
    my @out  ;  
    for ( 0..scalar @F -1){
         push @out,  color( ['on_black','on_blue']->[int($_/$_[1])%2]  ) . $F[$_]  ;
    } 
    return join ($o{','},@out).color('reset')."\n" ;
}

## 数字に色をつける。
sub ColorDigits ($) { 
    my @accum = &wellSplit ( $_[0] ) ; 
    my $outStr = join '' , map { &wellPrint ($_) } @accum ; 
    return $outStr ; 
}

sub wellPrint ($){
    my $outstr = '' ; 
    sub loc ($) {
        my $x=$_[0]-1;
        my $l = $o{3} ? 3 : $o{4} ? 4 : 5 ; # 5は使わないだろう
        ($x-($x% $l ))/$l %3 
        } ; 
    my $str = $_[0] ; 
    if ( $str !~ /^\d/ ) {
        $outstr .=  $_ ; 
        return $outstr ;
    }
    my @wStr = split //, $str ,0  ; 
    for ( -scalar @wStr .. -1 ) { 
        $outstr .= ${[color('cyan'),color('green'),color('yellow')]} [ &loc (-$_) ] . $wStr[$_] ;
    }
    return $outstr.color('white') ;
}

sub wellSplit ($)  { 
    my @wholeStr = split // , $_[0] , -1 ;
    my @accum ;
    my $tmpstr = '' ;  
    my $dflag = 0 ; 
    for ( 0 .. scalar @wholeStr -1 ) { 
        my $c = $wholeStr [ $_ ] ;
        my $dflag0 = $c ge "0" & $c le "9" ; 
        if ( $dflag0 xor $dflag ) { 
            push @accum , $tmpstr  if $_ ; 
            $tmpstr = $c ;
        $dflag = $dflag0 ; 
        } 
        else { 
            $tmpstr .= $c ;  
        }
    }
    push @accum, $tmpstr ;
    return @accum ; 
}

sub VERSION_MESSAGE {}
sub HELP_MESSAGE{
    $0=~s|.*/|| ; $ARGV[1] //= '' ;
    while(<DATA>){
        s/\$0/$0/g ;
        print $_ if $ARGV[1] =~ /opt/ ? m/^\ +\-/ : s/^=head1// .. s/^=cut// ; 
    }
    exit 0 ;
}

__END__ 
=encoding utf8

=head1 color.pl

 色( Term::ANSIColor ) についてのいろいろなユーティリティー

 -0 で入力の色情報を消去する。
 -3 数を下位から3桁ごとに塗り分ける
 -4 数を下位から4桁ごとに塗り分ける。
 -e linenumbers   コンマ(,)区切りの数を並べると、それにより指定された行については表示はするが着色などは何もしない。
 -l num  :  num行周期で最初の行に背景を青くする。
 -t num タブ区切り num 列ごとに青の背景色をつける。
 -v 処理結果をすぐに書き出す。バッファに貯めない。
 -, str : 列の塗り分けをする際に、列の区切りを str で指定する。未指定なら、タブ文字。

=cut