2

n 個の csv ファイルがあり、合計するとサイズが 100 GB になる場合、次のルールと条件に基づいて重複する行を削除する必要があります。

  • csv ファイルには 1.csv から n.csv の番号が付けられており、各ファイルのサイズは約 50MB です。
  • 最初の列は文字列キーです。最初の列が同じ場合、2 つの行は重複していると見なされます。
  • 重複を後のファイルに保持して重複を削除したい (2.csv は 1.csv よりも遅いと見なされます)

私のアルゴリズムは次のとおりです。より良いアルゴリズムがあるかどうかを知りたいです。

  • すべてのファイルを 1 つの巨大なファイルにマージする

    cat *.csv > one.csv
    
  • csvを並べ替える

    sort one.csv >one_sorted.csv
    
  • この時点で重複を排除する方法がわかりません。uniq最初の N フィールドをスキップする -f フラグがありますが、私の場合は最初の 1 フィールドを除くすべてをスキップしたいと考えています。

最後のステップ (ソートされたファイルの重複を排除する) について助けが必要です。また、より効率的なアルゴリズムはありますか?

4

4 に答える 4

2

を使用する 1 つの方法を次に示しGNU awkます。

awk -F, '{ array[$1]=$0 } END { for (i in array) print array[i] }' $(ls -v *.csv)

説明: 数値的にソートされたファイルのグロブを読み取り、各ファイルの最初の列を、値が行全体である連想配列に追加します。このようにして、保持される重複は、最新のファイルで発生するものです。完了したら、配列のキーをループして値を出力します。関数を介してGNU awk並べ替え機能を提供しますが、出力を にパイプすると、読みやすくなり、おそらくより速く効率的になります。asort()asorti()sort

最初の列で数値の並べ替えが必要な場合は、これを行うことができます。

awk -F, '{ array[$1]=$0 } END { for (i in array) print array[i] | "sort -nk 1" }' $(ls -v *.csv)
于 2012-10-15T03:28:27.983 に答える
1

行をメモリに保持できる場合

十分なデータがメモリに収まる場合、スティーブによるawk解決策は、パイプでコマンドに書き込むか、単にシェルレベルでunadorned の出力をパイプするかによって、非常にうまくいきます。sortawkawksort

おそらく 3% の重複がある 100 GiB のデータがある場合、100 GiB のデータをメモリに格納できる必要があります。それは多くのメインメモリです。64 ビット システムでは仮想メモリで処理できる場合がありますが、実行速度がかなり遅くなる可能性があります。

キーがメモリに収まる場合

十分なデータをメモリに収めることができない場合、先のタスクははるかに難しくなり、ファイルに対して少なくとも 2 回のスキャンが必要になります。私たちは、キーが出現した回数のカウントとともに、少なくとも各キーをメモリに収めることができると想定する必要があります。

  1. スキャン 1: ファイルを読み取ります。
    • 各キーが入力に現れる回数を数えます。
    • ではawk、 を使用しますicount[$1]++
  2. スキャン 2: ファイルを再読み込みします。
    • 各キーが出現した回数を数えます。ocount[$1]++.
    • の場合icount[$1] == ocount[$1]、その行を印刷します。

(これは、キーとカウントを 2 回保存できることを前提としています。代わりicountに、両方のスキャンで (のみ) 使用し、スキャン 1 でインクリメントし、スキャン 2 でデクリメントし、カウントがゼロになったときに値を出力します。)

awkファイルを Perl で再読み込みする方が簡単であるという理由だけで、私はおそらく ではなく Perl を使用しますawk


鍵すら入らない?

キーとその数をメモリに収めることさえできない場合はどうでしょうか? 次に、いくつかの深刻な問題に直面しています。特に、スクリプト言語がメモリ不足の状態を適切に報告しない可能性があるためです。必要であることが示されるまで、この橋を渡ろうとはしません。また、必要に応じて、何が可能かを知るために、ファイル セットに関する統計データが必要になります。

  • レコードの平均長。
  • 個別のキーの数。
  • N = 1、2、... maxのそれぞれについて、N 回出現する個別のキーの数。
  • キーの長さ。
  • メモリに収まるキーとカウントの数。

そしておそらく他のいくつか...だから、私が言ったように、必要であることが示されるまで、その橋を渡ろうとしないでください.


Perl ソリューション

サンプルデータ

$ cat x000.csv
abc,123,def
abd,124,deg
abe,125,deh
$ cat x001.csv
abc,223,xef
bbd,224,xeg
bbe,225,xeh
$ cat x002.csv
cbc,323,zef
cbd,324,zeg
bbe,325,zeh
$ perl fixdupcsv.pl x???.csv
abd,124,deg
abe,125,deh
abc,223,xef
bbd,224,xeg
cbc,323,zef
cbd,324,zeg
bbe,325,zeh
$ 

ギガバイト規模のテストがないことに注意してください。

fixdupcsv.pl

これは、「カウントアップ、カウントダウン」テクニックを使用します。

#!/usr/bin/env perl
#
# Eliminate duplicate records from 100 GiB of CSV files based on key in column 1.

use strict;
use warnings;

# Scan 1 - count occurrences of each key

my %count;
my @ARGS = @ARGV;   # Preserve arguments for Scan 2

while (<>)
{
    $_ =~ /^([^,]+)/;
    $count{$1}++;
}

# Scan 2 - reread the files; count down occurrences of each key.
# Print when it reaches 0.

@ARGV = @ARGS;      # Reset arguments for Scan 2

while (<>)
{
    $_ =~ /^([^,]+)/;
    $count{$1}--;
    print if $count{$1} == 0;
}

' while (<>)' 表記は破棄@ARGVされます (したがって、他の操作を行う前にコピーされます) が、これは、元の値に@ARGSリセットすると、ファイルをもう一度実行することも意味します。@ARGVMac OS X 10.7.5 上の Perl 5.16.0 および 5.10.0 でテスト済み。

これは Perl です。TMTOWTDI。あなたが使用することができます:

#!/usr/bin/env perl
#
# Eliminate duplicate records from 100 GiB of CSV files based on key in column 1.

use strict;
use warnings;

my %count;

sub counter
{
    my($inc) = @_;
    while (<>)
    {
        $_ =~ /^([^,]+)/;
        $count{$1} += $inc;
        print if $count{$1} == 0;
    }
}

my @ARGS = @ARGV;   # Preserve arguments for Scan 2
counter(+1);
@ARGV = @ARGS;      # Reset arguments for Scan 2
counter(-1);

おそらくループの本体を圧縮する方法もあるでしょうが、そこにあるものは合理的に明確であり、極端な簡潔さよりも明確さを好みます。

呼び出し

fixdupcsv.pl正しい順序でファイル名を指定してスクリプトを提示する必要があります。1.csv から約 2000.csv までの番号が付けられたファイルがあるため、それらを英数字順にリストしないことが重要です。ls -v *.csv他の回答では、GNUls拡張オプションの使用が提案されています。利用可能な場合、それが最良の選択です。

perl fixdupcsv.pl $(ls -v *.csv)

それが利用できない場合は、名前に対して数値ソートを行う必要があります。

perl fixdupcsv.pl $(ls *.csv | sort -t. -k1.1n)

Awk ソリューション

awk -F, '
BEGIN   {
            for (i = 1; i < ARGC; i++)
            {
                while ((getline < ARGV[i]) > 0)
                    count[$1]++;
                close(ARGV[i]);
            }
            for (i = 1; i < ARGC; i++)
            {
                while ((getline < ARGV[i]) > 0)
                {
                    count[$1]--;
                    if (count[$1] == 0) print;
                }
                close(ARGV[i]);
            }
        }' 

これは の本来の「読み取り」ループを無視awkし、すべての読み取りを明示的に行います (BEGIN を END に置き換えても同じ結果が得られます)。このロジックは、多くの点で Perl ロジックに密接に基づいています。awkBSDと GNUの両方を搭載した Mac OS X 10.7.5 でテスト済みawk。興味深いことに、GNUは、BSDが要求しなかっawkた呼び出しの括弧を要求しました。2 番目のループを機能させるには、最初のループで呼び出しが必要です。2 番目のループの呼び出しは、対称性を維持し、整理するためにありますが、1 回の実行で数百のファイルを処理する場合にも関連する可能性があります。closeawkclose()close()

于 2012-10-15T05:10:11.000 に答える
0

私の答えはスティーブのに基づいています

awk -F, '!count[$1]++' $(ls -rv *.csv)

{print $0}awk ステートメントで暗示されます。

基本的awkに、$1 にその値が含まれている最初の行のみを出力します。.csv ファイルは自然な順序でリストされているため、$1 に同じ値を持つすべての行について、最新のファイルの 1 つだけが出力されることを意味します。

:同じファイルに重複がある場合(つまり、同じファイル内に同じキーの複数のインスタンスがある場合)、これは機能しません。

于 2012-10-15T05:03:17.290 に答える
0

並べ替え計画に関しては、個々のファイルを並べ替えてからマージする方が、連結してから並べ替えるよりも実用的かもしれません。sortプログラムを使用した並べ替えの複雑さはO(n log(n)). 50MB のファイルあたり 200000 行、2000 ファイルとnすると、約 4 億行になり、 n log(n) ~ 10^10. 代わりに、R レコードの F ファイルをそれぞれ個別に扱う場合、並べ替えのコストは でO(F*R*log(R))あり、マージのコストはO(F*R*log(R)). これらのコストは十分に高く、個別の並べ替えが必ずしも高速であるとは限りませんが、プロセスを便利なチャンクに分割できるため、物事が進むにつれて簡単にチェックできます。次に示すのは、ソート キーの区切り文字としてカンマを使用できると仮定した小規模な例です。(コンマを含む引用符で区切られたキー フィールドは、示されているように並べ替えの問題になります。)安定した並べ替えを実行するように-s指示sortし、同じ並べ替えキーを持つ行を検出された順序で残すことに注意してください。

for i in $(seq 1 8); do sort -t, -sk1,1 $i.csv > $i.tmp; done
sort -mt, -sk1,1 [1-8].tmp > 1-8.tmp

または、より慎重な場合は、中間結果が保存される可能性があります。

sort -mt, -sk1,1 [1-4].tmp > 1-4.tmp
sort -mt, -sk1,1 [5-8].tmp > 5-8.tmp
cp 1-4.tmp 5-8.tmp /backup/storage
sort -mt, -sk1,1 1-4.tmp 5-8.tmp > 1-8.tmp

また、個別の並べ替えの後にマージを実行する利点は、ワークロードを複数のプロセッサまたはシステムに簡単に分割できることです。

すべてのファイルを並べ替えて (たとえば、ファイル X に) マージした後、BEGIN で X から行を読み取り、変数 L に入れる awk プログラムを作成するのはかなり簡単です。その後、X から行を読み取るたびに、X から行を読み取るたびに$0 の最初のフィールドが L と一致しない場合、L を書き出し、L を $0 に設定します。しかし、$0 が L に一致する場合、L を $0 に設定します。END で L を書き出します。

于 2012-10-15T05:56:55.743 に答える