File Coverage

blib/lib/Algorithm/SetSimilarity/Jaccard.pm
Criterion Covered Total %
statement 139 141 98.5
branch 61 72 84.7
condition 26 42 61.9
subroutine 10 10 100.0
pod 0 4 0.0
total 236 269 87.7


line stmt bran cond sub pod time code
1             package Algorithm::SetSimilarity::Jaccard;
2              
3 1     1   43858 use 5.008005;
  1         3  
  1         45  
4 1     1   6 use strict;
  1         2  
  1         37  
5 1     1   5 use warnings;
  1         2  
  1         59  
6              
7             our $VERSION = "0.0.0_03";
8              
9 1     1   7 use Scalar::Util;
  1         2  
  1         1435  
10              
11             sub new {
12 9     9 0 14350 my ($class, $param) = @_;
13 9         18 my %hash = ();
14 9 50       30 $hash{"is_sorted"} = $param->{is_sorted} if (exists $param->{is_sorted});
15 9 100       25 $hash{"data_type"} = $param->{data_type} if (exists $param->{data_type});
16 9         38 bless \%hash, $class;
17             }
18              
19             sub estimate_data_type {
20 6     6 0 1401 my ($self, $set1, $set2) = @_;
21 6         8 my $is_estimate = 0;
22 6         11 my ($type1, $type2) = ("string","string");
23              
24 6 100       27 if (Scalar::Util::looks_like_number($set1->[0])) {
25 2         3 $type1 = "number";
26             }
27 6 100       20 if (Scalar::Util::looks_like_number($set2->[0])) {
28 2         2 $type2 = "number";
29             }
30              
31 6 100       16 if ($type1 eq $type2) {
32 4         15 $self->{data_type} = $type1;
33 4         6 $is_estimate = 1;
34             }
35 6         14 return $is_estimate;
36             }
37              
38             sub get_similarity {
39 21     21 0 8230 my ($self, $set1, $set2, $threshold) = @_;
40 21         26 my $score = -1.0;
41 21 100 33     209 if ((ref $set1 eq "ARRAY") && (ref $set2 eq "ARRAY") && (@$set1) && (@$set2)) {
      66        
      100        
42 18 100 66     65 if ((defined $threshold) && ($threshold > 0.0)) {
43 10         24 $score = $self->filt_by_threshold($set1, $set2, $threshold);
44             } else{
45 8         22 ($set1, $set2) = $self->_swap_set_descending_order($set1, $set2);
46 8         16 my $s1 = $#$set1 + 1;
47 8         13 my $s2 = $#$set2 + 1;
48 8 100       23 my $is_estimate = $self->estimate_data_type($set1, $set2) unless (exists $self->{data_type});
49 8 50 66     33 if (($is_estimate) || (exists $self->{data_type})) {
50 8 50       18 unless ( $self->{is_sorted} ) {
51 8 100       19 if ($self->{data_type} eq "number") {
52 2         7 @{$set1} = sort {$a <=> $b} @$set1;
  2         6  
  38         33  
53 2         4 @{$set2} = sort {$a <=> $b} @$set2;
  2         8  
  38         38  
54             } else {
55 6         17 @{$set1} = sort {$a cmp $b} @$set1;
  6         16  
  17         43  
56 6         13 @{$set2} = sort {$a cmp $b} @$set2;
  6         14  
  17         21  
57             }
58             }
59 8         15 my $match_num = 0;
60 8   66     42 for (my ($att1, $att2) = (0, 0); ($att1 < $s2) && ($att2 < $s2);) {
61 45         46 my $judge = -1;
62 45 100       82 if ($self->{data_type} eq "number") {
63 25         32 $judge = ($set1->[$att1] <=> $set2->[$att2]);
64             } else {
65 20         29 $judge = ($set1->[$att1] cmp $set2->[$att2]);
66             }
67              
68 45 100       73 if ($judge == -1) {
    100          
69 13         48 $att1++;
70             } elsif ($judge == 1) {
71 8         28 $att2++;
72             } else {
73 24         28 $match_num++;
74 24         22 $att1++;
75 24         75 $att2++;
76             }
77             }
78 8         28 my $diff_num = ($s1 - $match_num) + ($s2 - $match_num);
79 8         19 $score = $match_num / ($match_num + $diff_num);
80             }
81             }
82             }
83 21         49 return $score;
84             }
85              
86             sub _swap_set_ascending_order {
87 2     2   794 my ($self, $set1, $set2) = @_;
88 2 100       9 if ($#$set1 > $#$set2) {
89 1         2 my $tmp_ref = $set1;
90 1         2 $set1 = $set2;
91 1         2 $set2 = $tmp_ref;
92             }
93 2 50       10 return wantarray ? ($set1, $set2) : [$set1, $set2];
94             }
95              
96             sub _swap_set_descending_order {
97 64     64   669 my ($self, $set1, $set2) = @_;
98 64 100       152 if ($#$set1 < $#$set2) {
99 1         3 my $tmp_ref = $set1;
100 1         2 $set1 = $set2;
101 1         3 $set2 = $tmp_ref;
102             }
103 64 50       206 return wantarray ? ($set1, $set2) : [$set1, $set2];
104             }
105              
106             sub filt_by_threshold {
107 55     55 0 19268 my ($self, $set1, $set2, $threshold) = @_;
108 55         66 my $score = -1.0;
109 55 100 33     682 if ((ref $set1 eq "ARRAY") && (ref $set2 eq "ARRAY") &&
      33        
      33        
      66        
      66        
110             (@$set1) && (@$set2) &&
111             ($threshold > 0.0) && ($threshold <= 1.0)) {
112              
113 54         119 for(my $r = 0; $r <= 0; $r++) {
114 54         104 ($set1, $set2) = $self->_swap_set_descending_order($set1, $set2);
115 54         88 my $s1 = $#$set1 + 1;
116 54         64 my $s2 = $#$set2 + 1;
117              
118 54 50       153 last unless (($s1 * $threshold) <= $s2); #size filtering
119              
120 54 100       113 my $is_estimate = $self->estimate_data_type($set1, $set2) unless (exists $self->{data_type});
121 54 50 66     200 if (($is_estimate) || (exists $self->{data_type})) {
122 54 50       113 unless ( $self->{is_sorted} ) {
123 54 100       102 if ($self->{data_type} eq "number") {
124 4         15 @{$set1} = sort { $a <=> $b } @$set1;
  4         13  
  76         75  
125 4         10 @{$set2} = sort { $a <=> $b } @$set2;
  4         11  
  76         70  
126             } else {
127 50         122 @{$set1} = sort { $a cmp $b} @$set1;
  50         130  
  102         141  
128 50         91 @{$set2} = sort { $a cmp $b} @$set2;
  50         126  
  102         118  
129             }
130             }
131              
132 54         127 my $min_overlap = int(($threshold / (1 + $threshold)) * ($s1 + $s2));
133 54         68 my $alpha = $s2 - ($min_overlap + 1) + 1;
134 54         60 my $match_num = 0;
135 54         75 my ($att1, $att2) = (0, 0);
136              
137 54   100     203 while (($att2 < $alpha) && ($att1 < $s1)) {
138 127         636 my $judge = -1;
139 127 100       228 if ($self->{data_type} eq "number") {
140 31         38 $judge = ($set1->[$att1] <=> $set2->[$att2]);
141             } else {
142 96         162 $judge = ($set1->[$att1] cmp $set2->[$att2]);
143             }
144              
145 127 100       229 if ($judge == -1) {
    100          
146 30         106 $att1++;
147             } elsif ($judge == 1) {
148 22         69 $att2++;
149             } else {
150 75         68 $match_num++;
151 75         61 $att1++;
152 75         197 $att2++;
153             }
154             }
155 54         59 my $min = ($s2 - $att2);
156 54 100       103 $min = ($s1 - $att1) if ($min > ($s1 - $att1));
157 54 50       90 if (($match_num) + $min < $min_overlap) {
158 0         0 $match_num = 0;
159 0         0 last;
160             }
161              
162 54 100       89 last unless ($match_num >= 1);
163 53   66     172 while (($att1 < $s1) && ($att2 < $s2)) {
164 63         106 my $judge = -1;
165 63 100       112 if ($self->{data_type} eq "number") {
166 9         12 $judge = ($set1->[$att1] <=> $set2->[$att2]);
167             } else {
168 54         79 $judge = ($set1->[$att1] cmp $set2->[$att2]);
169             }
170              
171 63 100       159 if ($judge == -1) {
    100          
172 5 50       10 last if ($match_num + ($s1 - $att1) < $min_overlap);
173 5         16 $att1++;
174             } elsif ($judge == 1) {
175 8 50       15 last if ($match_num + ($s2 - $att2) < $min_overlap);
176 8         29 $att2++;
177             } else {
178 50         47 $match_num++;
179 50         43 $att1++;
180 50         133 $att2++;
181             }
182             }
183 53 100       97 last unless ($match_num >= $min_overlap + 1);
184 44         58 my $diff_num = ($s1 - $match_num) + ($s2 - $match_num);
185 44         114 $score = $match_num / ($match_num + $diff_num);
186             }
187             }
188             }
189 55         119 return $score;
190             }
191              
192             1;
193              
194             __END__