web-dev-qa-db-ja.com

テキストファイル内の重複行を削除するにはどうすればよいですか?

私の巨大な(2 GiBまでの)テキストファイルには、すべての行の約100の正確な複製が含まれています(ファイルはCSVのようなデータテーブルであるため、私の場合は無用です)。

私が必要なのは、元のシーケンスの順序を維持しながら(できれば、パフォーマンスを大幅に向上させるためにこれを犠牲にすることができます)すべての繰り返しを削除することです。その結果、各行は一意になります。 100の等しい行があった場合(通常、重複はファイル全体に広がり、隣人にはなりません)、残っている種類は1つだけです。

これを実装するためのプログラムをScala(Scalaについて知らない場合はJavaと見なしてください)で記述しました。しかし、これをより速く実行できる、より高速なC記述のネイティブツールがあるのでしょうか。

更新:ファイルが2 GiBに近いかぎり、awk '!seen[$0]++' filenameソリューションは私にとっては問題なく機能しているように見えましたが、今は8 GiBファイルをクリーンアップするためです。もう働きません。 4 GiB RAMを搭載したMacと4 GiB RAMおよび6 GiBスワップを搭載した64ビットWindows 7 PCで、無限大になっているようですメモリの。そして、私はこの経験から、Linux [4 GiB RAMで試してみることに熱心ではありません。

141
Ivan

#bash(Freenode)で見られるawkソリューション:

awk '!seen[$0]++' filename
234
enzotib

sortを実行する以外に大容量のメモリを必要としない標準ユーティリティを使用する簡単な(明らかなことではありません)方法があります。 。この方法の利点は、特別な目的のユーティリティ内のすべての行をループするだけで、インタープリター言語内ではループしないことです。

<input nl -b a -s : |           # number the lines
sort -t : -k 2 -u |             # sort and uniquify ignoring the line numbers
sort -t : -k 1n |               # sort according to the line numbers
cut -d : -f 2- >output          # remove the line numbers

すべての行が非空白文字で始まる場合は、いくつかのオプションを省略できます。

<input nl | sort -k 2 -u | sort -k 1n | cut -f 2- >output

大量の複製の場合、各行の単一のコピーをメモリに格納するだけで済む方法は、パフォーマンスが向上します。ある程度の解釈オーバーヘッドがあり、そのための非常に簡潔なawkスクリプトがあります(すでに enzotibによる投稿 )。

<input awk '!seen[$0]++'

簡潔さ:!seen[$0] {print} {seen[$0] += 1}、つまり、現在の行がまだ表示されていない場合は出力し、この行のseenカウンターをインクリメントします(初期化されていない変数または配列要素の数値は0です)。

長い行の場合、各行の偽装できないチェックサム(暗号化ダイジェストなど)のみを保持することでメモリを節約できます。たとえば、SHA-1を使用すると、必要なのは20バイトと、1行あたりの一定のオーバーヘッドだけです。しかし、ダイジェストの計算はかなり遅いです。この方法は、高速なCPU(特に、ダイジェストを計算するためのハードウェアアクセラレータを備えたCPU)があり、ファイルのサイズに比べて大量のメモリと十分に長い行がない場合にのみ成功します。各行のチェックサムを計算できる基本的なユーティリティはありません。 Perl/Python/Ruby /…の解釈オーバーヘッドを負担するか、専用のコンパイル済みプログラムを作成する必要があります。

<input Perl -MDigest::MD5 -ne '$seen{Digest::MD5::md5($_)}++ or print' >output
sort -u big-csv-file.csv > duplicates-removed.csv

出力ファイルはソートされることに注意してください。

26

重複排除されたファイルと同じくらいメモリに保存できる余裕があると仮定すると(データが実際に100倍に複製されている場合、約20MiB +オーバーヘッドになるはずです)、Perlでこれを非常に簡単に行うことができます。

$ Perl -ne 'print unless $dup{$_}++;' input_file > output_file

これにより、順序も保持されます。

必要に応じて、追加の無料ボーナスとして、%dupハッシュから各行の出現回数を抽出できます。

awkを使用する場合は、これも実行する必要があります(Perlバージョンと同じロジック、同じ順序、dup変数で収集される同じデータ):

$ awk '{if (++dup[$0] == 1) print $0;}' input_file > output_file
19
Mat

他の回答がインプレースサポートを提供していないので、ここに1つあります。

gawk -i inplace '!a[$0]++' file
7
rindeal

uniqを使用できます http://www.computerhope.com/unix/uuniq.htm

uniqは、ファイル内の繰り返し行を報告またはフィルターで除外します。

3
Mahmoud Zalt

Python Oneライナー:

python -c "import sys; lines = sys.stdin.readlines(); print ''.join(sorted(set(lines)))" < InputFile
2
Rahul Patil

ここでの答えはMacではうまくいかなかったので、簡単なpythonスクリプトが私のために動作するように記述しました。先頭/末尾の空白は無視し、メモリの消費も気にしません。

import sys

inputfile = sys.argv[1]
outputfile = sys.argv[2]

with open(inputfile) as f:
    content = f.readlines()

content = [x.strip() for x in content]

my_list = list(set(content))

with open(outputfile, 'w') as output:
    for item in my_list:
        output.write("%s\n" % item)

上記をunique.pyに保存し、次のように実行します。

python unique.py inputfile.txt outputfile.txt
0
Jared