package Plucene::Search::BooleanScorer; =head1 NAME Plucene::Search::BooleanScorer - A boolean scorer =head1 SYNOPSIS # isa Plucene::Search::Scorer $bool_scorer->add($scorer, $required, $prohibited); $bool_scorer->score($results, $max_doc); =head1 DESCRIPTION This is a scoring class for boolean scorers. =head1 METHODS =cut use strict; use warnings; use List::Util qw(min); use Plucene::Search::Similarity; use base qw(Plucene::Search::Scorer Class::Accessor); __PACKAGE__->mk_accessors( qw(next_mask required_mask prohibited_mask max_coord scorers bucket_table coord_factors current_doc) ); =head2 new my $bool_scorer = Plucene::Search::BooleanScorer->new; Create a new Plucene::Search::BooleanScorer object. =head2 next_mask / required_mask / prohibited_mask max_coord / scorers / bucket_table / coord_factors / current_doc Get / set these attributes =cut sub new { my $self = shift->SUPER::new(@_); $self->max_coord(1); $self->next_mask(1); $self->current_doc(0); $self->required_mask(0); $self->prohibited_mask(0); $self->scorers([]); $self->bucket_table(Plucene::Search::BucketTable->new({ scorer => $self })); return $self; } =head2 add $bool_scorer->add($scorer, $required, $prohibited); =cut sub add { my ($self, $scorer, $required, $prohibited) = @_; my $mask = 0; if ($required || $prohibited) { $mask = $self->next_mask; $self->{next_mask} <<= 1; } $self->{max_coord}++ unless $prohibited; $self->{prohibited_mask} |= $mask if $prohibited; $self->{required_mask} |= $mask if $required; push @{ $self->{scorers} }, { scorer => $scorer, required => $required, prohibited => $prohibited, collector => $self->bucket_table->new_collector($mask) }; } sub _compute_coord_factors { my $self = shift; $self->coord_factors([ map Plucene::Search::Similarity->coord($_, $self->max_coord), 0 .. $self->max_coord ]); } =head2 score $bool_scorer->score($results, $max_doc); =cut sub score { my ($self, $results, $max_doc) = @_; $self->_compute_coord_factors if not defined $self->coord_factors; while ($self->current_doc < $max_doc) { $self->current_doc( min( $self->{current_doc} + $Plucene::Search::BucketTable::SIZE, $max_doc )); for my $t (@{ $self->{scorers} }) { $t->{scorer}->score($t->{collector}, $self->current_doc); } $self->bucket_table->collect_hits($results); } } package Plucene::Search::BucketTable; our $SIZE = 1 << 10; our $MASK = $SIZE - 1; use base 'Class::Accessor'; __PACKAGE__->mk_accessors(qw(buckets first scorer)); sub new { my $self = shift->SUPER::new(@_); $self->buckets([]); $self; } sub collect_hits { my ($self, $results) = @_; my $scorer = $self->scorer; my $required = $scorer->required_mask; my $prohibited = $scorer->prohibited_mask; my @coord = @{ $scorer->coord_factors }; for my $bucket (@{ $self->{buckets} || [] }) { next unless exists $bucket->{doc}; $results->collect($bucket->{doc}, $bucket->{score} * $coord[ $bucket->{coord} ]) if !($bucket->{bits} & $prohibited) and ($bucket->{bits} & $required) == $required; } } sub new_collector { my ($self, $mask) = @_; return Plucene::Search::BucketCollector->new({ bucket_table => $self, mask => $mask }); } package Plucene::Search::BucketCollector; use base (qw(Class::Accessor Plucene::Search::HitCollector)); __PACKAGE__->mk_accessors(qw(bucket_table mask)); sub collect { my ($self, $doc, $score) = @_; my $table = $self->bucket_table; my $i = $doc & $Plucene::Search::BucketTable::MASK; my $bucket = $table->buckets->[$i]; $table->buckets->[$i] = $bucket = {} unless $bucket; if (not defined $bucket->{doc} or $bucket->{doc} != $doc) { @{$bucket}{qw(doc score bits coord)} = ($doc, $score, $self->mask, 1); } else { $bucket->{score} += $score; $bucket->{bits} |= $self->mask; $bucket->{coord}++; } } 1;