forget it
[migration-tools.git] / compress_fingerprints
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use open ':utf8';
5
6 use Digest::SHA1 qw(sha1_base64);
7 use Getopt::Long;
8
9 my $conf  = {}; # configuration hashref
10 initialize($conf);
11
12 my %fps  = (); # records matching each fingerprint (and the lead)
13 my %recs = (); # fingerprints belonging to each record
14
15 open FP, '<', $ARGV[0] or die "Can't open input file: $!\n";
16
17 print "Loading and ranking fingerprints\n";
18 while (<FP>) {
19     my @fields = split "\t", $_;
20     my $fp = populate_fingerprint(@fields);
21     rank_fingerprint($fp);
22     print "\r", ( int($i / $total) ), "% complete" unless ($i % 1000);
23 }
24 print "Writing matchset to disk\n";
25 dump_records();
26
27
28
29 sub populate_fingerprint {
30     my @fields = @_;
31     my %fp = (); # zero fingerprint hash each time thru
32
33     # populate fp hash -- first the simple data
34     $fp{compact} = shift @fields;
35     $fp{json}    = shift @fields;
36     $fp{id}      = shift @fields;
37     # then smash everything else together, remove non-Roman characters, and
38     # generate a SHA1 hash to represent it
39     my $stripped = join('', @fields);
40     $stripped   =~ s/[^A-Za-z0-9]//g;
41     $fp{sha1}    = sha1_base64($stripped);
42
43     # populate records hash
44     $recs{ $fp{id} }{ $fp{sha1} } = {};
45
46     return \%fp;
47 }
48
49
50 sub rank_fingerprint {
51     my ($fp) = @_;
52
53     my $sha1 = $fp->{sha1};
54     my $id   = $fp->{id};
55     my $islead = $recs{$id}{lead};
56
57     # only process records which haven't already been set as a sub
58     unless (defined $islead and $islead) {
59         unless ($fps{$sha1}) {
60             # haven't seen this fp before. create a new hashref with the current
61             # record as lead
62             $fps{$sha1} = { lead => { id    => $id,
63                                       score => $fp->{compact} },
64                             recs => [ $id ] };
65             $recs{$id}{lead} = 1;
66         } else {
67             # have seen this fp. push record id onto matchlist
68             push @{ $fps{$sha1}{recs} }, $id;
69             if ($fp->{compact} > $fps{$sha1}{lead}{score}) {
70                 # and set this record as lead if it scores higher than current lead
71                 $recs{ $fps{$sha1}{lead}{id} }{lead} = 0; # unset current
72                 $recs{ $id }{lead} = 1;                   # set new as lead
73                 $fps{$sha1}{lead}{id}    = $id;
74                 $fps{$sha1}{lead}{score} = $fp->{compact};
75             }
76         }
77     }
78 }
79
80
81 =head2 dump_records
82
83 Writes out a 2-column file of lead and subordinate records.
84
85 =cut
86
87 sub dump_records {
88     my %used = ();
89     open OUT, '>', $conf->{output}
90       or die "Can't open ", $conf->{output}, "$!\n";
91     for my $id (keys %recs) {
92         next unless $recs{$id}{lead};
93         for my $sha1 ( keys %{$recs{$id}} ) {
94             for my $subid ( @{$fps{$sha1}{recs}} ) {
95                 next if ($id == $subid);
96                 next if defined $used{$subid};
97                 $used{$subid} = 1;
98                 print OUT "$id\t$subid\n";
99             }
100         }
101     }
102 }
103
104 sub initialize {
105     my ($c) = @_;
106     my @missing = ();
107
108     # set mode on existing filehandles
109     binmode(STDIN, ':utf8');
110
111     my $rc = GetOptions( $c,
112                          'output|o=s',
113                          'help|h',
114                        );
115     show_help() unless $rc;
116     show_help() if ($c->{help});
117
118     my @keys = keys %{$c};
119     show_help() unless (@ARGV and @keys);
120     for my $key ('output')
121       { push @missing, $key unless $c->{$key} }
122     if (@missing) {
123         print "Required option: ", join(', ', @missing), " missing!\n";
124         show_help();
125     }
126 }
127
128 sub show_help {
129     print <<HELP;
130 Usage is: compress_fingerprints -o OUTPUTFILE INPUTFILE
131 HELP
132 exit;
133 }