人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

PERLで巨大ファイル(10GB程度)の重複行を削除する方法を教えてください。
また、下記のように、一度全てをメモリに読み込んで行う方法ではパソコンが落ちてしまいました。
そのため、これ以外の、非力なパソコンでもできるような、メモリを節約した方法を教えてください。
宜しくお願い致します。

open(FH,"<file");
my @array = <FH>;
close(FH);
my %count;
@array = grep {!$count{$_}++} @array;
open(FH,">file2");
print @array;
close(FH);

__file__
1234
ASDF
1234
A1234
以下続く

__file2__
ASDF
1234
A1234
以下続く

●質問者: wrash_d
●カテゴリ:インターネット ウェブ制作
✍キーワード:asdf grep open Perl print
○ 状態 :終了
└ 回答数 : 5/5件

▽最新の回答へ

1 ● nagase
●10ポイント

巨大なファイルをそのまま扱うのは効率が悪いと思うので、行の並びが変わってもいいのとディスクスペースが十分に空いていると言う前提のアイデアですが。

(1) ファイルを走査し各行の先頭文字毎のファイルに分割する。
(2) 分割したファイルがオンメモリで処理可能なサイズでなければ、各行の2番目の文字毎にファイルを分割する
(3) 全ての分割ファイルが適当なサイズになるまで繰り返す
(4) 分割ファイルごとに重複行を削除
(5) 最後に全てのファイルを結合

こんな感じの処理でどうでしょうか。

もっと上手い方法があるかもしれませんが。

後はDBにまるごとファイルを突っ込んで、クエリー発行するとか。

◎質問者からの返答

ディスクスペースは十分あります。

また、1行の文字数は、10文字(A-Z0-9)+改行(2バイト)です。

ただ、行数が膨大で困っています。

この場合、実際にperlで動くプログラムはどのようになりますか?


2 ● pahoo
●10ポイント

各行のハッシュ値を計算し配列に入れていき、それを比較するという方法はどうでしょうか。

ハッシュ値には SHA1, MD5 など、幾つかの種類があります。SHA1のハッシュ長は20バイトなので、各行がそれより長ければ、メモリの節約になります。

ただし、ハッシュ値の計算には少し時間がかかります。


◎質問者からの返答

ディスクスペースは十分あります。

また、1行の文字数は、10文字(A-Z0-9)+改行(2バイト)です。

ただ、行数が膨大で困っています。

この場合、実際にperlで動くプログラムはどのようになりますか?


3 ● zzz_1980
●10ポイント

まさに sort <file1 |uniq >file2

でできますが、/tmp に 10Gbyte以上のあきが必要です。

(メモリのかわりにファイルシステム上に中間ファイルをつくりますので…)

◎質問者からの返答

ディスクスペースは十分あります。

また、1行の文字数は、10文字(A-Z0-9)+改行(2バイト)です。

ただ、行数が膨大で困っています。

この場合、実際にperl「特にwindows環境」で動くプログラムはどのようになりますかね?


4 ● cicupo
●140ポイント ベストアンサー

以下のスクリプトでいかがでしょうか。

Windows環境でテストしていませんし、スペックも不明なのでどうか分かりませんが。

でもフリーソフトの windows 版 uniq もたくさんありますねー。

#!/usr/bin/perl
use strict;
use warnings;

open(FH,"<file");
my @tmpfiles;
my %check;
my $count = 0;
while (<FH>) {
 if ($check{$_}) { next; }
 $check{$_} = 1;
 $count++;
 if ($count > 1000000) {
 my $tmpfile = "tmp_$#tmpfiles";
 push (@tmpfiles, $tmpfile);
 print $tmpfile, "\n";
 open(TMP,">$tmpfile");
 print TMP sort(keys(%check));
 close(TMP);
 %check = ();
 $count = 0;
 }
}
close(FH);

open(RESULT,">result");
print RESULT sort(keys(%check));
close(RESULT);
foreach my $tmpfile (@tmpfiles) {
 open(RESULT,"<result");
 open(TMP,"<$tmpfile");
 open(RESULT2,">result2");
 my $s1 = <RESULT>;
 my $s2 = <TMP>;
 while ($s1 && $s2) {
 if ($s1 < $s2) {
 print RESULT2 $s1;
 $s1 = <RESULT>;
 }
 elsif ($s1 > $s2) {
 print RESULT2 $s2;
 $s2 = <TMP>;
 }
 else {
 print RESULT2 $s1;
 $s1 = <RESULT>;
 $s2 = <TMP>;
 }
 }
 while ($s1) {
 print RESULT2 $s1;
 $s1 = <RESULT>;
 }
 while ($s2) {
 print RESULT2 $s2;
 $s2 = <TMP>;
 }
 close(RESULT);
 close(RESULT2);
 close(TMP);
 rename "result2", "result";
 unlink $tmpfile;
}

◎質問者からの返答

回答いただきありがとうございます。なかなか良い感じなのですが残念ながらエラーが出ます。

エラーは、Argument "189X\n" isn't numeric in numeric it (<) at test.pl line 37, <TMP> line 1.

数字のみで行うと正常に動作するのですが、英字が混じると正常に動作していないようです。

以下は動作確認に使ったファイルです。

テストのため、1000000を2に変更しています。

改良できますでしょうか?

―file―

1890

1890

189X

189X

188X

X789

X678

X789

X789

1890

1890

189X

189X

188X

0000

X789

X678

X789

X789

―――

$count > 2

―――


5 ● tombe
●10ポイント

tieを使うやる方です。

速度とのトレードオフはキャッシュサイズで調整してください。

use strict;
use Fcntl 'O_RDONLY';
use Tie::File;
my $INPUT_FILE = 'file';
my $OUTPUT_FILE = 'file2';
my $CACHE_SIZE = 2000000; # キャッシュ = 2M(デフォルト?)

open(OUT,">$OUTPUT_FILE");
tie my @farry,
 'Tie::File',
 $INPUT_FILE,
 mode => O_RDONLY,
 memory => $CACHE_SIZE
 or die "Cant't open input file\n";;
for (my $linep = 0;$linep < ($#farry + 1);$linep++)
{
 my $searchp = 0;
 for (;$searchp < $linep;$searchp++)
 {
 last if $farry[$searchp] eq $farry[$linep];
 }
 print OUT $farry[$linep],"\n" if $searchp == $linep;
}
close(OUT);
◎質問者からの返答

回答いただきありがとうございます。

残念ながら、キャッシュサイズを500KBなど小さくしても、メモリの使用量がどんどん増え、エラーで終了します。

なお、当方の環境は、windows xp sp3,MEM 3GBです。

関連質問


●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ