次のファイルがあるとします(sample.txtと呼ばれます)。
Row1,10
Row2,20
Row3,30
Row4,40
私はこのファイルからストリームを処理できるようにしたいです。これは基本的に4行すべてをペアで組み合わせたものです(したがって、合計16行にする必要があります)。たとえば、出力は次のようなストリーミング(つまり効率的な)コマンドを探しています。
Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row1,20 Row2,20
...
Row4,40 Row4,40
私の使用例は、この出力を別のコマンド(awkなど)にストリーミングして、このペアの組み合わせに関するいくつかのメトリックを計算したいと思います。
awkでこれを行う方法はありますが、END {}ブロックを使用することは、基本的にファイルを出力する前にファイル全体をメモリに保存することです。サンプルコード:
awk '{arr[$1]=$1} END{for (a in arr){ for (a2 in arr) { print arr[a] " " arr[a2]}}}' samples/rows.txt
Row3,30 Row3,30
Row3,30 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row1,10 Row1,10
Row1,10 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
ファイルをメモリに保存してからENDブロックに出力せずにこれを行うための効率的なストリーミング方法はありますか?
答え1
ファイル全体を配列に保存する必要がないように、awkで実行する方法は次のとおりです。これは基本的にterdonのアルゴリズムと同じです。
必要に応じて、コマンドラインで複数のファイル名を指定することもでき、各ファイルを個別に処理して結果を一緒にリンクします。
#!/usr/bin/awk -f
#Cartesian product of records
{
file = FILENAME
while ((getline line <file) > 0)
print $0, line
close(file)
}
私のシステムでは、terdonのPerlソリューション時間の約3分の2で実行されます。
答え2
これがメモリで実行するよりも良いかどうかはわかりませんが、内部ファイルのすべての行を読み取るsed
内部r
ファイルと、H
古いスペースをインターリーブする入力行を持つパイプの反対側に別のファイルがあります。
cat <<\IN >/tmp/tmp
Row1,10
Row2,20
Row3,30
Row4,40
IN
</tmp/tmp sed -e 'i\
' -e 'r /tmp/tmp' |
sed -n '/./!n;h;N;/\n$/D;G;s/\n/ /;P;D'
出力
Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row3,30 Row3,30
Row3,30 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
私は別の方法でやった。それは救う一部メモリに - 次の文字列を保存します。
"$1" -
...ファイルの各行。
pairs(){ [ -e "$1" ] || return
set -- "$1" "$(IFS=0 n=
case "${0%sh*}" in (ya|*s) n=-1;; (mk|po) n=+1;;esac
printf '"$1" - %s' $(printf "%.$(($(wc -l <"$1")$n))d" 0))"
eval "cat -- $2 </dev/null | paste -d ' \n' -- $2"
}
非常に高速です。cat
ファイル内の行数だけファイルに行があります|pipe
。パイプの反対側では、ファイル内の行数だけ入力がファイル自体とマージされます。
これらcase
は単に移植性のためです。yash
どちらもzsh
分割に要素を追加し、両方ともmksh
要素posh
を失います。ksh
、dash
およびbusybox
すべてbash
印刷されたゼロだけ多くのフィールドに分割されますprintf
。作成したとおり、上記のすべてのシェルは私のコンピュータで同じ結果をレンダリングします。
ファイル非常に非常に長い場合、パラメータが多すぎると問題が発生する可能性があります。$ARGMAX
この場合は、パラメータを導入または類似する必要がありますxargs
。
出力以前に使用したのと同じ入力を考慮すると、出力は同じです。しかし、私が大きくなれば…
seq 10 10 10000 | nl -s, >/tmp/tmp
以前に使用したファイルとほぼ同じファイルが生成されます。(「ライン」なし)- ところで1000行目です。どれくらい速いかを直接確認できます。
time pairs /tmp/tmp |wc -l
1000000
pairs /tmp/tmp 0.20s user 0.07s system 110% cpu 0.239 total
wc -l 0.05s user 0.03s system 32% cpu 0.238 total
1000行では、シェル間のパフォーマンスにわずかな違いがあります。bash
常に最も遅いです。しかし、シェルが実行する唯一のことは、引数文字列を生成することだけです。(1000部filename -
)効果はわずかです。zsh
上記のように、ここでのパフォーマンスの違いはbash
100分の1秒です。
任意の長さのファイルで動作する別のバージョンは次のとおりです。
pairs2()( [ -e "$1" ] || exit
rpt() until [ "$((n+=1))" -gt "$1" ]
do printf %s\\n "$2"
done
[ -n "${1##*/*}" ] || cd -P -- "${1%/*}" || exit
: & set -- "$1" "/tmp/pairs$!.ln" "$(wc -l <"$1")"
ln -s "$PWD/${1##*/}" "$2" || exit
n=0 rpt "$3" "$2" | xargs cat | { exec 3<&0
n=0 rpt "$3" p | sed -nf - "$2" | paste - /dev/fd/3
}; rm "$2"
)
/tmp
奇妙なファイル名に縛られないように、半ランダム名を使用して最初のパラメータへのソフトリンクを作成します。cat
引数がパイプを通して渡されるので、これは重要ですxargs
。 while rintsの出力は、ファイルの行数だけ最初の引数の各行cat
に格納され、そのスクリプトもここにパイプされます。入力を再マージしますが、今回は標準入力とリンク名には2つの引数しか必要ありません。<&3
sed
p
paste
-
/dev/fd/3
最後の方法である接続は、/dev/fd/[num]
すべてのLinuxシステムおよび他のシステムで機能しますが、名前付きmkfifo
パイプを作成して使用しない場合でも機能する必要があります。
最後に行うことは、rm
終了する前にソフトリンクを作成することです。
このバージョンは実際により速く私のシステムに。より多くのアプリケーションの実行中にパラメータを一度にすべて渡し始めるためです。一方、パラメータを最初に積む前にはそうです。
time pairs2 /tmp/tmp | wc -l
1000000
pairs2 /tmp/tmp 0.30s user 0.09s system 178% cpu 0.218 total
wc -l 0.03s user 0.02s system 26% cpu 0.218 total
答え3
さて、シェルではいつでもこれを行うことができます。
while read i; do
while read k; do echo "$i $k"; done < sample.txt
done < sample.txt
あなたのソリューションよりはるかに遅いですがawk
(私のコンピュータでは1000行が約11秒かかりましたが、約0.3秒かかりますawk
)、少なくともメモリに数行以上を保持することはできません。
上記のループは、例の非常に単純なデータに対して機能します。バックスラッシュをブロックし、末尾と先行スペースを食べます。同じもののより強力なバージョンは次のとおりです。
while IFS= read -r i; do
while IFS= read -r k; do printf "%s %s\n" "$i" "$k"; done < sample.txt
done < sample.txt
別のオプションは、次のものを使用することですperl
。
perl -lne '$line1=$_; open(A,"sample.txt");
while($line2=<A>){printf "$line1 $line2"} close(A)' sample.txt
上記のスクリプトは、-ln
入力ファイル()の各行を読み取り、別の名前で保存してから、もう一度開き$l
、sample.txt
一緒に各行を印刷します$l
。結果はすべてペアごとの組み合わせですが、メモリには2行だけが保存されます。私のシステムでは、1000行を処理するのに0.6
数秒しかかかりませんでした。
答え4
これをコンパイルできます出版社:Xiaoma Geかなり速い結果を得るためのコードです。
1000行ファイルの場合、約0.19 - 0.27秒以内に完了します。
現在は10000
ラインをメモリに読み込み(画面の印刷速度を上げるため)、ライン1000
当たりの文字がある場合、メモリより少ないメモリを使うので10mb
問題にならないようです。ただし、そのセクションを完全に削除して問題が発生した場合は、画面に直接印刷できます。
以下を使用してコンパイルできます。保存するファイルの名前はg++ -o "NAME" "NAME.cpp"
どこにあり、このコードが保存されるファイルはどこにありますか?NAME
NAME.cpp
CTEST.cpp:
#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include <sstream>
int main(int argc,char *argv[])
{
if(argc != 2)
{
printf("You must provide at least one argument\n"); // Make sure only one arg
exit(0);
}
std::ifstream file(argv[1]),file2(argv[1]);
std::string line,line2;
std::stringstream ss;
int x=0;
while (file.good()){
file2.clear();
file2.seekg (0, file2.beg);
getline(file, line);
if(file.good()){
while ( file2.good() ){
getline(file2, line2);
if(file2.good())
ss << line <<" "<<line2 << "\n";
x++;
if(x==10000){
std::cout << ss.rdbuf();
ss.clear();
ss.str(std::string());
}
}
}
}
std::cout << ss.rdbuf();
ss.clear();
ss.str(std::string());
}
デモ
$ g++ -o "Stream.exe" "CTEST.cpp"
$ seq 10 10 10000 | nl -s, > testfile
$ time ./Stream.exe testfile | wc -l
1000000
real 0m0.243s
user 0m0.210s
sys 0m0.033s