web-dev-qa-db-ja.com

awk配列を使用する場合は、大きな数を合計し、指定された質問のすべての小数点を使用して結果を出力します

以下の入力ファイルがあり、3列目の日付に基づいて複数のファイルに分割する必要があります。基本的に、同じ日付のトランザクションはすべて、特定の日付のファイルに分割する必要があります。分割後ヘッダーとトレーラーを作成する必要があります。トレーラーには、レコードの数と4番目の列の金額の合計(その日付の金額の合計)が含まれている必要があります。この場合、私が上で述べたように、私は非常に多くの数を持っています。どうすれば以下のコードにbcを統合できますか。

入力ファイル

H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^8|~^xxx|~^123670130.37256

出力ファイル20190305.txt

H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^4|~^xxx|~^107707.068

出力ファイル20190306.txt

H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
T|~^20200425|~^4|~^xxx|~^123562423.30456

私が使用しているコード(PS:コミュニティメンバーの1人が提案)これがawkソリューションです:

awk -F'\\|~\\^' '{ 
            if($1=="H"){ 
                head=$0
            }
            else if($1=="T"){
                foot=$1"|~^"$2
                foot4=$4
            }
            else{
                date=$3;
                sub("T.*","", date);
                data[date][NR]=$0;
                sum[date]+=$4; 
                num[date]++
            }
           }
           END{
            for(date in data){
                file=date".txt";
                gsub("-","",file); 
                print head > file; 
                for(line in data[date]){
                    print data[date][line] > file
                } 
                printf "%s|~^%s|~^%s|~^%s\n", foot, num[date], 
                                              foot4, sum[date] > file
            }
           }' file 

コードは見事に機能しています。しかし、ステップで

sum[date]+=$4;

大きな数を合計することはできません。最後のステップで%sを使用しているため、トレーラーの合計は指数値で出力されます。

printf "%s|~^%s|~^%s|~^%s\n", foot, num[date], 
                                                  foot4, sum[date] > file

ここでは、大きな数に合計を適用して、正確な合計を出力したかっただけです。 (ここでbc(bash計算機)を試しましたが、この合計は配列に基づいており、特定の日付に基づいて追加されているため、スタックしました)これを手伝ってください

また、トレーラーステップで"%.15g"を試しました

printf "%s|~^%s|~^%s|~^%.15g\n", foot, num[date], 
                                                  foot4, sum[date] > file

この場合、結果が15桁(小数を含む)の場合、正確な合計を取得できます。合計結果が15桁を超える場合、これは機能していません。親切に助けて

1
hunter

あなたの大きな数の問題を考慮に入れずに、私はawkプログラムを次のように書くでしょう:

_BEGIN {
        FS = "\\|~\\^"
        OFS= "|~^"
}

$1 == "H" {
        header = $0
}

$1 == "R" {
        name = $3
        sub("T.*", "", name)

        sum[name] += $4
        cnt[name] += 1

        if (cnt[name] == 1)
                print header >name ".txt"

        print >name ".txt"
}

$1 == "T" {
        for (name in sum)
                print $1, $2, cnt[name], $4, sum[name] >name ".txt"
}
_

便宜上、出力フィールドの区切り文字OFSを_|~^_に設定しました。これにより、出力するフィールドの間に挿入する必要がなくなります。入力のフィールドセパレータFSは、その文字列に一致する正規表現に設定されます。

次に、3つの主要なコードブロックがあります。

  1. 1つはH行を解析するためのものです。これらは1つだけであり、最初に発生すると想定されています。これは、ヘッダー行を変数headerに格納するだけです。

  2. 1つはR行を解析するためのものです。各レコードには、3番目のフィールドの出力ファイル名として使用する必要がある日付が含まれています。これは、実行するのと同じ方法で解析されます。その日付の合計が累積され、カウンターもインクリメントされます。

    カウンターが1の場合、つまり、特定の日付を初めて表示する場合は、その日付に関連付けられた出力ファイルにヘッダーを書き込みます。次に、現在のレコードをファイルに書き込みます。

  3. 最後のブロックはT行を解析します。これらは1つだけであり、最後に発生すると想定されます。これは、元のT行からのデータとともに、各日付の累積合計とカウントをその日付に関連付けられたファイルに出力するだけです。

任意の大きな数をサポートするために( 他の場所 格納するのに100ビットを超える必要があるため、awkの整数がオーバーフローする)、任意精度の計算機bcを使用します。 「コプロセス」(一種の計算サービス)。 _sum[name] += $4_という行はに置き換えられます

_if (sum[name] == "") sum[name] = 0
printf "%s + %s\n", sum[name], $4 |& "bc"
"bc" |& getline sum[name]
_

これにはGNU awk(ほとんどのUnixシステムで何らかの方法で利用可能)が必要です。

これは、この日付の合計がまだない場合、最初に現在の日付の合計をゼロに初期化することです。これを行うのは、初期合計として_0_をbcに指定する必要があるためです。

次に、bcがGNU awk- specific _|&_パイプを使用してコプロセスに書き込むために計算する必要がある式を出力します。bcユーティリティは、起動され、並行して実行されます。 awkスクリプトが計算を実行し、次のgetlinebcからの出力を別の_|&_パイプから直接_sum[name]_に読み取ります。

私が理解している限り、GNU awkは、合計ごとに個別のbcプロセスを生成しませんが、コプロセスとして実行される単一のbcプロセスを維持します。したがって、これは内部で計算を行うよりも遅くなります。 awkはネイティブですが、合計ごとに個別のbcを生成するよりもはるかに高速です。

指定されたデータに対して、次の2つのファイルが作成されます。

_$ cat 2019-03-05.txt
H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^4|~^xxx|~^107707.068
_
_$ cat 2019-03-06.txt
H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
T|~^20200425|~^4|~^xxx|~^123562423.30456
_
1
Kusalananda

私はすでに この質問を解決するためのawkコード を作成しました。これは、ここで提示しているコードよりも高速に実行されます。

あなたはすでに多くの数を合計し、過去に不正確な答えを得る問題について質問しました。この質問は、この他の質問と非常によく似ています これらの2つの合計コマンドに違いがあるのはなぜですか?

その質問のファイルは20メガバイトで、700千行以上ありました。
ファイルの順序は ファイルサイズは約500〜600 mb であるとおっしゃいました。これにより、行数が1,000万行の範囲に増加します。

問題は、追加する数値が次のことです。

  • 大きく異なる場合があります:3桁の範囲12.8から28桁1245637.34526234567299999999

  • 28桁の数字を1000万回合計すると、28 + 7 = 35桁が必要になります。そして、それは数字がすべて小数または整数ではないことを前提としています。それが起こる可能性がある場合、私たちは70桁(35の整数+ 35の小数)について話しています。

  • フロートでの表現は常に正確な数の概算になります。これはフロートの基本的な問題です。正確な合計が必要な場合は、それらすべてを整数として加算する必要があります。

問題の解決策として、GNU awkをより長い桁数で使用することもできます。awkのデフォルトの浮動小数点数は53ビットの仮数を使用し、15桁にのみ有効です。

MPFR(Multiple Precision Floating-Point Reliably)およびGMP(GNU Multiple Precision Arithmetic Library)でコンパイルされたa GNU AWKを使用する場合、その--versionテキストの結果には次の情報が含まれている必要があります。情報(実行awk --version)。その場合、より多くのビットを使用できます。 40桁のフロート(上記で計算された35桁+ある程度のセキュリティマージン)を維持できるようにするには、次のものが必要です。

b = ceil(d log2(10)) + 1

b = ceil( 40 * 3.321928 ) + 1 = 133 + 1 = 134 binary digits (bits)

したがって、awkの呼び出しは次のようになります。

 awk -M -v PREC=134 

警告:より多くの桁を使用すると、プログラムが遅くなります。

それでも同じawkプログラムを使用する

awk -M -v PREC=134 '

     BEGIN  { FS="\\|~\\^"; OFS="|~^" }
     $1=="H"{ header=$0; hdr=$2 }
     $1=="R"{
              t=gensub(/-/, "","g",$3)
              file=gensub(/T.*/,"",1,t);
              sum[file]+=$4
              if(count[file]==0){ print header >file }
              count[file]++
              print $0 >>file
            }
     END    {
              for( i in sum ){
                  printf "T %s %10d xxx %45.25f",hdr,count[i],"xxx",sum[i] >> i;
                  close(i)
                  }
            }
' "inputfile"

参考までに:あなたはほぼ同じ質問を何度も繰り返しています:

1
Isaac