File Coverage

blib/lib/Plucene/Search/BooleanScorer.pm
Criterion Covered Total %
statement 78 78 100.0
branch 15 16 93.7
condition 8 9 88.8
subroutine 15 15 100.0
pod 3 3 100.0
total 119 121 98.3


line stmt bran cond sub pod time code
1             package Plucene::Search::BooleanScorer;
2              
3             =head1 NAME
4              
5             Plucene::Search::BooleanScorer - A boolean scorer
6              
7             =head1 SYNOPSIS
8              
9             # isa Plucene::Search::Scorer
10              
11             $bool_scorer->add($scorer, $required, $prohibited);
12             $bool_scorer->score($results, $max_doc);
13              
14             =head1 DESCRIPTION
15              
16             This is a scoring class for boolean scorers.
17              
18             =head1 METHODS
19              
20             =cut
21              
22 3     3   16 use strict;
  3         6  
  3         98  
23 3     3   34 use warnings;
  3         7  
  3         99  
24              
25 3     3   16 use List::Util qw(min);
  3         5  
  3         189  
26              
27 3     3   17 use Plucene::Search::Similarity;
  3         5  
  3         78  
28              
29 3     3   14 use base qw(Plucene::Search::Scorer Class::Accessor::Fast);
  3         7  
  3         1608  
30              
31             __PACKAGE__->mk_accessors(
32             qw(next_mask required_mask prohibited_mask max_coord scorers bucket_table
33             coord_factors current_doc)
34             );
35              
36             =head2 new
37              
38             my $bool_scorer = Plucene::Search::BooleanScorer->new;
39              
40             Create a new Plucene::Search::BooleanScorer object.
41              
42             =head2 next_mask / required_mask / prohibited_mask max_coord / scorers /
43             bucket_table / coord_factors / current_doc
44              
45             Get / set these attributes
46              
47             =cut
48              
49             sub new {
50 16     16 1 123 my $self = shift->SUPER::new(@_);
51 16         260 $self->max_coord(1);
52 16         176 $self->next_mask(1);
53 16         137 $self->current_doc(0);
54 16         141 $self->required_mask(0);
55 16         139 $self->prohibited_mask(0);
56 16         142 $self->scorers([]);
57 16         213 $self->bucket_table(Plucene::Search::BucketTable->new({ scorer => $self }));
58 16         138 return $self;
59             }
60              
61             =head2 add
62              
63             $bool_scorer->add($scorer, $required, $prohibited);
64              
65             =cut
66              
67             sub add {
68 32     32 1 430 my ($self, $scorer, $required, $prohibited) = @_;
69 32         74 my $mask = 0;
70 32 100 100     192 if ($required || $prohibited) {
71 15         76 $mask = $self->next_mask;
72 15         86 $self->{next_mask} <<= 1;
73             }
74              
75 32 100       118 $self->{max_coord}++ unless $prohibited;
76              
77 32 100       115 $self->{prohibited_mask} |= $mask if $prohibited;
78 32 100       110 $self->{required_mask} |= $mask if $required;
79 32         66 push @{ $self->{scorers} },
  32         161  
80             {
81             scorer => $scorer,
82             required => $required,
83             prohibited => $prohibited,
84             collector => $self->bucket_table->new_collector($mask) };
85             }
86              
87             sub _compute_coord_factors {
88 16     16   131 my $self = shift;
89 16         76 $self->coord_factors([
90             map Plucene::Search::Similarity->coord($_, $self->max_coord),
91             0 .. $self->max_coord
92             ]);
93             }
94              
95             =head2 score
96              
97             $bool_scorer->score($results, $max_doc);
98              
99             =cut
100              
101             sub score {
102 16     16 1 105 my ($self, $results, $max_doc) = @_;
103 16 50       73 $self->_compute_coord_factors if not defined $self->coord_factors;
104 16         174 while ($self->current_doc < $max_doc) {
105 16         195 $self->current_doc(
106             min(
107             $self->{current_doc} + $Plucene::Search::BucketTable::SIZE, $max_doc
108             ));
109 16         87 for my $t (@{ $self->{scorers} }) {
  16         51  
110 32         266 $t->{scorer}->score($t->{collector}, $self->current_doc);
111             }
112 16         239 $self->bucket_table->collect_hits($results);
113             }
114             }
115              
116             package Plucene::Search::BucketTable;
117             our $SIZE = 1 << 10;
118             our $MASK = $SIZE - 1;
119              
120 3     3   20 use base 'Class::Accessor::Fast';
  3         7  
  3         936  
121             __PACKAGE__->mk_accessors(qw(buckets first scorer));
122              
123             sub new {
124 16     16   89 my $self = shift->SUPER::new(@_);
125 16         211 $self->buckets([]);
126 16         159 $self;
127             }
128              
129             sub collect_hits {
130 16     16   150 my ($self, $results) = @_;
131 16         87 my $scorer = $self->scorer;
132 16         139 my $required = $scorer->required_mask;
133 16         136 my $prohibited = $scorer->prohibited_mask;
134 16         87 my @coord = @{ $scorer->coord_factors };
  16         81  
135              
136 16         174 for (my $bucket = $self->{first} ; $bucket ; $bucket = $bucket->{next}) {
137 136 100 100     1101 if ( ($bucket->{bits} & $prohibited) == 0
138             and ($bucket->{bits} & $required) == $required) {
139 56         298 $results->collect($bucket->{doc},
140             $bucket->{score} * $coord[ $bucket->{coord} ]);
141             }
142             }
143 16         203 undef $self->{first};
144             }
145              
146             sub new_collector {
147 32     32   225 my ($self, $mask) = @_;
148 32         311 return Plucene::Search::BucketCollector->new({
149             bucket_table => $self,
150             mask => $mask
151             });
152             }
153              
154             package Plucene::Search::BucketCollector;
155 3     3   324 use base (qw(Class::Accessor::Fast Plucene::Search::HitCollector));
  3         6  
  3         1145  
156              
157             __PACKAGE__->mk_accessors(qw(bucket_table mask));
158              
159             sub collect {
160 164     164   285 my ($self, $doc, $score) = @_;
161 164         302 my $table = $self->{bucket_table};
162 164         240 my $i = $doc & $Plucene::Search::BucketTable::MASK;
163 164         487 my $bucket = $table->buckets->[$i];
164 164 100       1189 $table->buckets->[$i] = $bucket = {} unless $bucket;
165              
166 164 100 66     1083 if (not defined $bucket->{doc} or $bucket->{doc} != $doc) {
167 136         252 @{$bucket}{qw(doc score bits coord)} =
  136         585  
168             ($doc, $score, $self->{mask}, 1);
169 136         477 $bucket->{next} = $table->first;
170 136         846 $table->first($bucket);
171             } else {
172 28         68 $bucket->{score} += $score;
173 28         68 $bucket->{bits} |= $self->{mask};
174 28         108 $bucket->{coord}++;
175             }
176             }
177              
178             1;
179