次のようなファイル(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ブロックに出力する必要なく、これを行う効率的なストリーミング方法はありますか?
ファイル全体を配列に格納する必要がないように、awkでこれを行う方法は次のとおりです。これは基本的にterdonのものと同じアルゴリズムです。
必要に応じて、コマンドラインで複数のファイル名を指定することもできます。各ファイルは個別に処理され、結果が連結されます。
#!/usr/bin/awk -f
#Cartesian product of records
{
file = FILENAME
while ((getline line <file) > 0)
print $0, line
close(file)
}
私のシステムでは、これはterdonのPerlソリューションの約2/3の時間で実行されます。
これがメモリで実行するよりも良いかどうかはわかりませんが、sed
を使用すると、r
は、ファイル内のすべての行と、パイプの反対側にあり、入力行とH
oldスペースが交互に入れ替わります...
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
これは別の方法で行いました。 someをメモリに保存します-次のような文字列を保存します:
"$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"
}
とても速いです。ファイルに|pipe
への行が含まれている回数だけ、cat
を使用します。パイプの反対側では、その入力はファイル内の行と同じ回数だけファイル自体とマージされます。
case
のものは移植性のためだけのものです-yash
とzsh
は両方とも1つの要素を分割に追加しますが、mksh
とposh
は両方とも1つを失います。 ksh
、dash
、busybox
、およびbash
はすべて、printf
によって出力されるゼロと同じ数のフィールドに分割されます。書かれているように、上記は私のマシン上の上記のシェルのすべてに対して同じ結果をレンダリングします。
ファイルがvery longである場合、$ARGMAX
の問題が引数が多すぎる可能性があります。その場合は、xargs
などを導入する必要があります。
出力が同じになる前に使用したのと同じ入力が与えられます。でももっと大きくなると...
seq 10 10 10000 | nl -s, >/tmp/tmp
これにより、以前使用したものとほぼ同じファイルが生成されます(sans 'Row')-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
は常に最も遅いです-しかし、とにかく彼らが行う唯一の作業はarg文字列を生成することです(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
を介してパイプ経由で供給されるため、これは重要です。 cat
の出力は<&3
に保存されますが、sed
p
rintsは、最初のargのすべての行を、そのファイル内の行と同じ回数だけ出力します。また、そのスクリプトもパイプを介してフィードされます。再びpaste
はその入力をマージしますが、今回は、標準入力とリンク名-
に対して2つの引数/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
まあ、あなたはいつでもあなたのシェルでそれをすることができます:
while read i; do
while read k; do echo "$i $k"; done < sample.txt
done < sample.txt
awk
ソリューションよりもかなり遅いです(私のマシンでは、1000行で約11秒かかりましたが、awk
では約0.3秒かかりました)が、少なくとも、メモリ内の数行。
上記のループは、例にある非常に単純なデータに対して機能します。それはバックスラッシュで窒息し、それは末尾と先頭のスペースを食べるでしょう。同じことのより堅牢なバージョンは次のとおりです。
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
秒しかかかりませんでした。
この c ++ コードをコンパイルすると、非常に迅速な結果が得られます。
1000行のファイルで約0.19〜0.27秒で完了します。
現在、10000
行をメモリに読み込みます(画面への印刷を高速化するため)。1行に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
zsh
の場合:
a=(
Row1,10
Row2,20
Row3,30
Row4,40
)
printf '%s\n' $^a' '$^a
配列の$^a
は、配列のブレースのような展開({elt1,elt2}
のように)をオンにします。
join -j 2 file.txt file.txt | cut -c 2-
フィールド2は空であり、file.txt内のすべての要素で等しいため、join
は各要素を他のすべての要素と連結します。実際にはデカルト積を計算しています。
Pythonの1つのオプションは、ファイルを memory-map にして、Python正規表現ライブラリが機能するという事実を利用することです。メモリマップトファイルを直接使用します。これはファイルに対してネストされたループを実行しているように見えますが、メモリマッピングにより、OSが利用可能な物理RAMを最適に機能させることができます。
import mmap
import re
with open('test.file', 'rt') as f1, open('test.file') as f2:
with mmap.mmap(f1.fileno(), 0, flags=mmap.MAP_SHARED, access=mmap.ACCESS_READ) as m1,\
mmap.mmap(f2.fileno(), 0, flags=mmap.MAP_SHARED, access=mmap.ACCESS_READ) as m2:
for line1 in re.finditer(b'.*?\n', m1):
for line2 in re.finditer(b'.*?\n', m2):
print('{} {}'.format(line1.group().decode().rstrip(),
line2.group().decode().rstrip()))
m2.seek(0)
代わりに、Pythonでの迅速な解決策ですが、メモリ効率は依然として懸念事項である可能性があります
from itertools import product
with open('test.file') as f:
for a, b in product(f, repeat=2):
print('{} {}'.format(a.rstrip(), b.rstrip()))
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
Bashでは、シェルビルトインのみを使用して、kshも同様に機能するはずです。
#!/bin/bash
# we require array support
d=( $(< sample.txt) )
# quote arguments and
# build up brace expansion string
d=$(printf -- '%q,' "${d[@]}")
d=$(printf -- '%s' "{${d%,}}' '{${d%,}}")
eval printf -- '%s\\n' "$d"
これにより、シェル変数のメモリ内のファイル全体が保持されますが、ファイルへの単一の読み取りアクセスのみが必要になることに注意してください。
sed
ソリューション。
_line_num=$(wc -l < input.txt)
sed 'r input.txt' input.txt | sed -re "1~$((line_num + 1)){h;d}" -e 'G;s/(.*)\n(.*)/\2 \1/'
_
説明:
sed 'r file2' file1
_- file1のすべての行について、file2のすべてのファイルの内容を読み取ります。1~i
_は1行目を意味し、次に1 + i行、1 + 2 * i、1 + 3 * iなどを意味します。したがって、1~$((line_num + 1)){h;d}
はh
old尖った行を意味します。バッファにd
eleteパターンスペースを追加し、新しいサイクルを開始します。'G;s/(.*)\n(.*)/\2 \1/'
-前の手順で選択したものを除くすべての行について、次に実行します。ホールドバッファからG
et行を実行し、現在の行に追加します。次に、行の場所を入れ替えます。 _current_line\nbuffer_line\n
_でしたが、_buffer_line\ncurrent_line\n
_になりました出力
_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
_