以下には、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
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つは行を解析するために使用されます
H
。そのうちの1つだけがあり、最初に発生すると仮定します。これは単にヘッダー行を変数に格納しますheader
。一つは、
R
ライン解析のためのものです。各レコードには、3番目のフィールドで出力ファイル名として使用する必要がある日付が含まれています。それはあなたと同じように解析されます。その日付の合計が累積され、カウンタが増加します。カウンタが1の場合、つまり特定の日付を最初に見た場合は、その日付に関連する出力ファイルにヘッダを書き込みます。次に、現在のレコードをファイルに書き込みます。
最後のブロックは行を
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
次に、awk
GNU固有のパイプを使用して評価する必要がある式を印刷して|&
補助プロセスに書き込みます。このbc
ユーティリティは、スクリプトと並列に起動し、実行され、計算を実行し、awk
別getline
のパイプの出力を直接 。bc
|&
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
答え2
私はすでに一つを書いたこの問題を解決するためのawkコードここに示されているコードよりも速く実行されます。
あなたは過去に多くの数字を合計することについて質問し、不正確な答えを得たことがあります。この質問は他の質問とよく似ています。これら2つのsumコマンドの間に違いがあるのはなぜですか?。
この問題のファイルサイズは20 MBで、700行を超えています。
お客様は、ファイルの順序が次のように指定しました。ファイルサイズは約500〜600MBです。。これにより、行数が千万行の範囲に増えます。
問題は追加する番号です。
非常に多様です。範囲は3桁から
12.8
28桁です1245637.34526234567299999999
。28桁の数字を千万回加えると、28 + 7 = 35桁が必要です。これは、数値がすべて小数または整数ではないと仮定します。これが発生すると、約70桁(整数35個+小数35個)に対応します。
浮動小数点数の根本的な問題は、浮動小数点数の表現が常に正確な数の近似であることです。正確な合計が必要な場合は、すべて整数で加算する必要があります。
問題に対する解決策として、より長いビット数のGNU awkを使用することです。 awkのデフォルトの浮動小数点数は53ビットの仮数を使用し、これは15桁の数字でのみ機能します。
MPFR(Reliable Multi-Precision Floating Point)とGMP(GNU Multi-Precision Arithmetic Library)でコンパイルされたGNU AWKを使用する場合は、--versionテキストの結果にこの情報を含める必要があります(execute 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"
注:ほぼ同じ質問を繰り返してください。