challenge 続・ファイル内の重複行削除

ファイル内の重複行削除(後優先) 」の続編です。

1行あたり平均60文字のデータが書き込まれた、巨大なファイルがあるとします。どのくらい巨大かというと、積んでいるメモリの10倍程度の容量があります。このファイルから、同じ内容が書かれている行を取り除くプログラムを作ってください。ただし、同じ内容が書かれている行のうち、最後に出現したものを残すものとします。

このサイズのファイルを丸ごとメモリに読み込もうとしてしまうと、 スラッシング - Wikipedia が発生することが予想されます。そこで行単位で読み込もう、というのが前回のお題の趣旨でした。

しかし、与えられたファイルが運悪く「一致する部分のないファイル」である可能性を考えてみましょう。たとえ1行ずつ読んで処理をしたとしても、「重複するかどうかの判定」のために前の行をまるごとメモリに取っておいたのでは、最終的にファイルを丸ごとメモリに乗せることになってしまいます。

こういうデータが入力されうる状況の場合にどう書くか、というお題です。前回のお題の条件3「ファイル全体を一度にメモリに読み込んで処理しないこと」を「たとえすべての行が異なるようなデータであっても、メモリの消費量をファイルサイズのおよそ10%程度に抑えること」と読み替えてください。

追記:「メモリの10倍」はさすがに条件として厳しすぎました。「ファイルのサイズは4ギガバイト未満であり、メモリの消費量をファイルサイズの半分以下に抑えること」と読み替えてください。半分以下に抑えられているのならば題意は満たすものとします。もちろん、頑張ってもっと少ないメモリで動くようにするのもアリです。

Posted feedbacks - Perl

これまたPerlカバレッジ向上委員会向け。
2pass; first passでmd5を使って重複箇所を検出し(よってfalse positiveの可能性はゼロではない)、
2nd passで重複箇所でなければprint.

Dan the Perl Monger
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/local/bin/perl
use strict;
use warnings;
use Digest::MD5;

sub lines_to_skip {
    my $fh = shift;
    my %sum;
    my $where = 0;
    while (<$fh>) {
        my $sum = Digest::MD5::md5($_);
        $sum{$sum} =
            ref $sum{$sum} ? [ @{ $sum{$sum} }, $where ]
          : exists $sum{$sum} ? [ $sum{$sum}, $where ]
          :                     $where;
        $where = tell $fh;
    }
    my %skip;
    for (%sum) {
        ref $sum{$_} or next;
        pop @{ $sum{$_} };
        $skip{$_}++ for ( @{ $sum{$_} } );
    }
    return \%skip;
}

sub lastuniq{
    my $filename = shift;
    open my $fh, '<', $filename or die "$filename : $!";
    my $skip = lines_to_skip($fh);
    seek $fh, 0, 0;
    my $where = 0;
    while(<$fh>){
        print unless $skip->{$where};
        $where = tell $fh;
    }
}

lastuniq(shift || die "$0 filename");

Index

Feed

Other

Link

Pathtraq

loading...