2009-09-24 114 views
3

使用在Windows中通過批處理文件調用的Perl刪除重複數據刪除 Windows中的DOS窗口通過批處理文件調用。 批處理文件調用執行操作的Perl腳本。我有批處理文件。 我的工作重複數據的代碼腳本是刪除,只要數據文件不是太大。 需要解決的問題是數據文件較大(2 GB或更多),如果嘗試將完整文件加載到數組以刪除重複數據,則會發生此文件大小的內存錯誤。 在子程序時發生存儲器錯誤: -如何從Perl的大文件中刪除非唯一的行?

@contents_of_the_file = <INFILE>; 

(A完全不同的方法是可接受的,只要它解決此問題,請建議)。 子程序是: -

sub remove_duplicate_data_and_file 
{ 
open(INFILE,"<" . $output_working_directory . $output_working_filename) or dienice ("Can't open $output_working_filename : INFILE :$!"); 
    if ($test ne "YES") 
    { 
    flock(INFILE,1); 
    } 
    @contents_of_the_file = <INFILE>; 
    if ($test ne "YES") 
    { 
    flock(INFILE,8); 
    } 
close (INFILE); 
### TEST print "$#contents_of_the_file\n\n"; 
@unique_contents_of_the_file= grep(!$unique_contents_of_the_file{$_}++, @contents_of_the_file); 

open(OUTFILE,">" . $output_restore_split_filename) or dienice ("Can't open $output_restore_split_filename : OUTFILE :$!"); 
if ($test ne "YES") 
    { 
    flock(OUTFILE,1); 
    } 
for($element_number=0;$element_number<=$#unique_contents_of_the_file;$element_number++) 
    { 
    print OUTFILE "$unique_contents_of_the_file[$element_number]\n"; 
    } 
if ($test ne "YES") 
    { 
    flock(OUTFILE,8); 
    } 
} 

回答

6

您不必要的存儲原始文件的完整副本@contents_of_the_file和 - 如果重複量相對較低的文件大小 - %unique_contents_of_the_file@unique_contents_of_the_file近兩年其他完整副本。正如ire_and_curses指出的那樣,您可以通過對數據進行兩次傳遞來降低存儲要求:(1)分析文件,存儲有關非重複行的行數的信息;和(2)再次處理該文件以向輸出文件寫入非牟取文件。

下面是一個例子。我不知道我是否選擇了散列函數的最佳模塊(Digest::MD5);也許其他人會對此發表評論。還請注意您應該使用的3參數形式open()

use strict; 
use warnings; 

use Digest::MD5 qw(md5); 

my (%seen, %keep_line_nums); 
my $in_file = 'data.dat'; 
my $out_file = 'data_no_dups.dat'; 

open (my $in_handle, '<', $in_file) or die $!; 
open (my $out_handle, '>', $out_file) or die $!; 

while (defined(my $line = <$in_handle>)){ 
    my $hashed_line = md5($line); 
    $keep_line_nums{$.} = 1 unless $seen{$hashed_line}; 
    $seen{$hashed_line} = 1; 
} 

seek $in_handle, 0, 0; 
$. = 0; 
while (defined(my $line = <$in_handle>)){ 
    print $out_handle $line if $keep_line_nums{$.}; 
}  

close $in_handle; 
close $out_handle; 
+1

+1用於實際構建代碼。 – 2009-09-24 17:43:07

+2

只要被哈希的行是16個字符或更大,這將是一個勝利。如果行長度小於16,則使用該行本身而不是'%seen'鍵。 my $ hashed_line = length($ line)> 15? md5($ line):$ line; 將做的伎倆。另請參閱'Bit :: Vector'作爲'%keep_line_num'的替代,以減少內存佔用。 – dland 2009-09-26 17:15:55

2

Perl並英勇事大文件,但2GB的可能是DOS/Windows的的限制。

你有多少RAM?

如果您的操作系統沒有抱怨,最好一次讀取一行文件,並立即寫入輸出。

我想使用鑽石運算符< >但我不願意推薦任何代碼,因爲在我發佈代碼的時候,我冒犯了SO上的Perl專家。

我寧願不冒險。我希望佩爾騎兵馬上就會到來。

與此同時,here's的一個鏈接。

+2

無論操作系統是否抱怨或其他原因,嗅探2GB文件總是一個糟糕的主意。 – 2009-09-24 11:22:00

+0

你可以在我的代碼中提出修改嗎? – 2009-09-24 11:24:53

+1

pavium,不用擔心冒犯Perl大師。這是學習的好方法,如果人們評論,那不是你,而是你的代碼。不是一回事。 Perl的格言之一是「玩得開心」。 – dland 2009-09-26 17:18:57

4

你應該能夠使用哈希高效地做到這一點。您不需要存儲來自行的數據,只需確定哪些是相同的。所以...

  • 不要sl - - 一次只讀一行。
  • 哈希線。
  • 將散列行表示形式存儲爲列表的Perl哈希中的鍵。將行號存儲爲列表的第一個值。
  • 如果該鍵已經存在,請將重複的行號附加到與該值對應的列表中。

在這個過程結束時,您將擁有一個標識所有重複行的數據結構。然後您可以再次通過文件來刪除這些重複項。

+0

+1爲一般的想法。但是,除非我忽略了某些事情,否則將dup信息存儲爲列表的散列似乎並不方便,只要第二次傳遞數據 - 沒有快速的方法來知道是否打印該行。似乎更加容易,用想要的行號建立一個Perl散列作爲散列鍵。 – FMc 2009-09-24 16:35:49

+0

@FM:是的,我明白你的意思。我試圖避免使用行號的第二個散列來減少內存使用量,但我的權衡是,與您的解決方案相比,從我的表示中重建文件相當複雜。我更喜歡你的方法。 ;) – 2009-09-24 17:42:31

0

在 「完全不同的方法」 一類,如果你有Unix命令(如Cygwin的):

cat infile | sort | uniq > outfile 

這應該工作 - 沒有必要的Perl在所有 - 這可能,或者可能不解決你的記憶問題。但是,您將失去infile的排序(因爲outfile現在將被排序)。

編輯:另一種解決方案,它能夠更好地處理大文件可能是通過使用以下算法:

  1. 讀INFILE行由行
  2. 哈希每行一個小散(例如散列#MOD 10)
  3. 追加每一行唯一的散列數目的文件(例如TMP-1至TMP-10)
  4. 關閉INFILE
  5. 打開和排序每個tmp-#到一個新文件sortedtmp-#
  6. Mergesort sortedtmp- [1-10](即打開所有10個文件並同時讀取它們),跳過重複項並將每次迭代寫入最終輸出文件

對於非常大的文件,這會比sl sa更安全。

零件2 & 3可以更改爲隨機#,而不是一個哈希數模10

以下腳本BigSort,可以幫助(雖然我沒有測試過):

# BigSort 
# 
# sort big file 
# 
# $1 input file 
# $2 output file 
# 
# equ sort -t";" -k 1,1 $1 > $2 

BigSort() 
{ 
if [ -s $1 ]; then 
    rm $1.split.* > /dev/null 2>&1 
    split -l 2500 -a 5 $1 $1.split. 
    rm $1.sort > /dev/null 2>&1 
    touch $1.sort1 
    for FILE in `ls $1.split.*` 
    do 
    echo "sort $FILE" 
    sort -t";" -k 1,1 $FILE > $FILE.sort 
    sort -m -t";" -k 1,1 $1.sort1 $FILE.sort > $1.sort2 
    mv $1.sort2 $1.sort1 
    done 
    mv $1.sort1 $2 
    rm $1.split.* > /dev/null 2>&1 
else 
    # work for empty file ! 
    cp $1 $2 
fi 
} 
+0

如果沒有可用的整個文件進行處理,排序無法正常工作,因此會遭遇與OP原始示例相同的內存問題。儘管如此,我還沒有減去,因爲它在很多相關情況下都是有用的解決方案。 – 2009-09-24 19:13:16

0

那麼你可以使用命令行perl的內聯替換模式。

perl -i~ -ne 'print unless $seen{$_}++' uberbigfilename 
+1

儘管如此,您仍然希望將文件的全部內容存儲在RAM中,這是最初的問題。 – 2009-09-25 16:04:11

+0

非常好的一點。 – Scimon 2009-10-02 12:44:20

1

這是一個無論文件有多大都可以工作的解決方案。但它並不專門使用RAM,所以它比基於RAM的解決方案慢。你也可以指定你想要使用的RAM的數量。

該解決方案使用一個臨時文件,該程序將該文件視爲SQLite的數據庫。

#!/usr/bin/perl 

use DBI; 
use Digest::SHA 'sha1_base64'; 
use Modern::Perl; 

my $input= shift; 
my $temp= 'unique.tmp'; 
my $cache_size_in_mb= 100; 
unlink $temp if -f $temp; 
my $cx= DBI->connect("dbi:SQLite:dbname=$temp"); 
$cx->do("PRAGMA cache_size = " . $cache_size_in_mb * 1000); 
$cx->do("create table x (id varchar(86) primary key, line int unique)"); 
my $find= $cx->prepare("select line from x where id = ?"); 
my $list= $cx->prepare("select line from x order by line"); 
my $insert= $cx->prepare("insert into x (id, line) values(?, ?)"); 
open(FILE, $input) or die $!; 
my ($line_number, $next_line_number, $line, $sha)= 1; 
while($line= <FILE>) { 
    $line=~ s/\s+$//s; 
    $sha= sha1_base64($line); 
    unless($cx->selectrow_array($find, undef, $sha)) { 
    $insert->execute($sha, $line_number)} 
    $line_number++; 
} 
seek FILE, 0, 0; 
$list->execute; 
$line_number= 1; 
$next_line_number= $list->fetchrow_array; 
while($line= <FILE>) { 
    $line=~ s/\s+$//s; 
    if($next_line_number == $line_number) { 
    say $line; 
    $next_line_number= $list->fetchrow_array; 
    last unless $next_line_number; 
    } 
    $line_number++; 
} 
close FILE;