Pythonを使用して大きなバイナリファイルから文字シーケンスを削除する
-
03-07-2019 - |
質問
Pythonのバイナリファイルから同じ値の長いシーケンスをトリムします。簡単な方法は、ファイルを読み込んで、re.subを使用して不要なシーケンスを置き換えることです。もちろん、これは大きなバイナリファイルでは機能しません。 numpyのようなものでそれを行うことができますか?
解決
実行するメモリがないopen("big.file").read()
の場合、numpyは本当に役立ちません。Python変数と同じメモリを使用します(1GBのRAMがある場合、1GBのデータしかnumpyにロードできません)
解決策は簡単です。ファイルをチャンクで読み取ります。f = open("big.file", "rb")
、一連のf.read(500)
を実行し、シーケンスを削除して、別のファイルオブジェクトに書き戻します。 Cでファイルの読み取り/書き込みを行う方法はほとんどありません。
問題は、置換するパターンを見逃した場合です。例:
target_seq = "567"
input_file = "1234567890"
target_seq.read(5) # reads 12345, doesn't contain 567
target_seq.read(5) # reads 67890, doesn't contain 567
明らかな解決策は、ファイルの最初の文字から開始し、len(target_seq)
文字をチェックしてから、1文字先に進み、再度先にチェックすることです。
例(擬似コード!):
while cur_data != "":
seek_start = 0
chunk_size = len(target_seq)
input_file.seek(offset = seek_start, whence = 1) #whence=1 means seek from start of file (0 + offset)
cur_data = input_file.read(chunk_size) # reads 123
if target_seq == cur_data:
# Found it!
out_file.write("replacement_string")
else:
# not it, shove it in the new file
out_file.write(cur_data)
seek_start += 1
これは最も効率的な方法ではありませんが、機能します。ファイルのコピーをメモリ(または2つ)に保持する必要はありません。
他のヒント
2つのコピーがメモリに収まる場合、簡単にコピーを作成できます。 2番目のコピーは圧縮バージョンです。もちろん、numpyを使用できますが、 arrayも使用できます。 パッケージ。さらに、ビッグバイナリオブジェクトをバイト文字列として扱い、それを直接操作できます。
ファイルのサイズは本当に大きく、2つのコピーをメモリに収めることができないようです。 (多くの詳細を提供しなかったので、これは単なる推測に過ぎません。)チャンクで圧縮を行う必要があります。チャンクを読み取り、そのチャンクでいくつかの処理を行ってから書き出します。繰り返しますが、numpy、配列、またはバイトの単純な文字列は正常に動作します。
dbrの解決策は良い考えですが、次のチャンクを読む前に、ファイルポインターを検索しているシーケンスの長さに巻き戻すだけで少し複雑になります。
def ReplaceSequence(inFilename, outFilename, oldSeq, newSeq):
inputFile = open(inFilename, "rb")
outputFile = open(outFilename, "wb")
data = ""
chunk = 1024
while 1:
data = inputFile.read(chunk)
data = data.replace(oldSeq, newSeq)
outputFile.write(data)
inputFile.seek(-len(oldSequence), 1)
outputFile.seek(-len(oldSequence), 1)
if len(data) < chunk:
break
inputFile.close()
outputFile.close()
AJMayorgaの提案は、置換文字列のサイズが異なる場合を除き問題ありません。または、置換文字列はチャンクの最後にあります。
このように修正しました:
def ReplaceSequence(inFilename, outFilename, oldSeq, newSeq):
inputFile = open(inFilename, "rb")
outputFile = open(outFilename, "wb")
data = ""
chunk = 1024
oldSeqLen = len(oldSeq)
while 1:
data = inputFile.read(chunk)
dataSize = len(data)
seekLen= dataSize - data.rfind(oldSeq) - oldSeqLen
if seekLen > oldSeqLen:
seekLen = oldSeqLen
data = data.replace(oldSeq, newSeq)
outputFile.write(data)
inputFile.seek(-seekLen, 1)
outputFile.seek(-seekLen, 1)
if dataSize < chunk:
break
inputFile.close()
outputFile.close()
質問をより正確にする必要があります。事前にトリミングしたい値を知っていますか?
実行すると仮定すると、おそらくsubprocess
を使用して一致するセクションを検索し、<!> quot; fgrep -o -b <search string>
<!> quot;を実行します。 python file
オブジェクトのseek
、read
およびwrite
メソッドを使用して、ファイルの関連セクションを変更します。
このジェネレーターベースのバージョンは、一度に1文字のファイルコンテンツのみをメモリに保持します。
私はあなたの質問のタイトルを非常に文字通り取っていることに注意してください-あなたは同じ文字の実行を単一の文字に減らしたいです。一般にパターンを置き換える場合、これは機能しません:
import StringIO
def gen_chars(stream):
while True:
ch = stream.read(1)
if ch:
yield ch
else:
break
def gen_unique_chars(stream):
lastchar = ''
for char in gen_chars(stream):
if char != lastchar:
yield char
lastchar=char
def remove_seq(infile, outfile):
for ch in gen_unique_chars(infile):
outfile.write(ch)
# Represents a file open for reading
infile = StringIO.StringIO("1122233333444555")
# Represents a file open for writing
outfile = StringIO.StringIO()
# Will print "12345"
remove_seq(infile, outfile)
outfile.seek(0)
print outfile.read()