///|
/// Read-only collection statistics for one Searcher snapshot.
///
/// Query implementations use these global values while constructing Weight.
/// A Weight can then create comparable Scorers for every immutable Segment.
pub struct SearchStatistics {
segments : ReadOnlyArray[SnapshotSegment]
}
///|
fn SearchStatistics::from_segments(
segments : ReadOnlyArray[SnapshotSegment],
) -> SearchStatistics {
{ segments, }
}
///|
pub fn SearchStatistics::segment_count(self : SearchStatistics) -> Int {
self.segments.length()
}
///|
pub fn SearchStatistics::doc_count(self : SearchStatistics) -> Int {
let mut count = 0
for snapshot in self.segments {
count += snapshot.live_doc_count()
}
count
}
///|
pub fn SearchStatistics::field_doc_count(
self : SearchStatistics,
field_id : FieldId,
) -> Int {
let mut count = 0
for snapshot in self.segments {
for doc_index in 0.. Int {
let mut count = 0
for snapshot in self.segments {
for posting in snapshot.segment.postings_for(term) {
if !snapshot.is_deleted(posting.doc_id) {
count += 1
}
}
}
count
}
///|
pub fn SearchStatistics::average_field_length(
self : SearchStatistics,
field_id : FieldId,
) -> Double {
let mut document_count = 0
let mut total_length = 0
for snapshot in self.segments {
for doc_index in 0.. ArrayScorer {
{ documents: ReadOnlyArray::from_array(documents), cursor: -1 }
}
///|
impl Scorer for ArrayScorer with fn advance(self) {
if self.cursor + 1 < self.documents.length() {
self.cursor += 1
true
} else {
false
}
}
///|
impl Scorer for ArrayScorer with fn doc(self) {
if self.cursor < 0 || self.cursor >= self.documents.length() {
DocId::new(-1)
} else {
self.documents[self.cursor].doc_id
}
}
///|
impl Scorer for ArrayScorer with fn score(self) {
if self.cursor < 0 || self.cursor >= self.documents.length() {
0.0
} else {
self.documents[self.cursor].score
}
}
///|
/// Builds single-Segment BM25 state. Retained for M1-M3 API compatibility.
pub fn Bm25Scorer::new(segment : Segment, term : Term) -> Bm25Scorer {
Bm25Scorer::from_statistics(
SearchStatistics::from_segments(
ReadOnlyArray::from_array([live_snapshot(segment)]),
),
term,
)
}
///|
/// Builds query state from the complete Searcher snapshot.
pub fn Bm25Scorer::from_statistics(
statistics : SearchStatistics,
term : Term,
) -> Bm25Scorer {
let document_count = statistics.field_doc_count(term.field_id)
let document_frequency = statistics.document_frequency(term)
let idf = if document_count <= 0 || document_frequency <= 0 {
0.0
} else {
let n = document_count.to_double()
let df = document_frequency.to_double()
@math.ln(1.0 + (n - df + 0.5) / (df + 0.5))
}
{
field_id: term.field_id,
idf,
average_length: statistics.average_field_length(term.field_id),
k1: 1.2,
b: 0.75,
}
}
///|
pub fn Bm25Scorer::score(
self : Bm25Scorer,
posting : Posting,
segment : Segment,
) -> Double {
let field_length = segment.field_length(posting.doc_id, self.field_id)
if self.idf <= 0.0 || self.average_length <= 0.0 || field_length <= 0 {
return 0.0
}
let term_frequency = posting.term_freq.to_double()
let length_normalization = 1.0 -
self.b +
self.b * field_length.to_double() / self.average_length
let denominator = term_frequency + self.k1 * length_normalization
self.idf * term_frequency * (self.k1 + 1.0) / denominator
}