blob: d59c98faea92970fdd9c5487e88f56d3084a31cd [file] [edit]
// Copyright (C) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "icing/scoring/ranker.h"
#include <algorithm>
#include <vector>
#include "icing/text_classifier/lib3/utils/base/statusor.h"
#include "icing/absl_ports/canonical_errors.h"
#include "icing/scoring/scored-document-hit.h"
#include "icing/util/logging.h"
namespace icing {
namespace lib {
namespace {
// For all the heap manipulations in this file, we use a vector to represent the
// heap. The element at index 0 is the root node. For any node at index i, its
// left child node is at 2 * i + 1, its right child node is at 2 * i + 2.
// Helper function to wrap the heapify algorithm, it heapifies the target
// subtree node in place.
// TODO(b/152934343) refactor the heapify function and making it into a class.
void Heapify(
std::vector<ScoredDocumentHit>* scored_document_hits,
int target_subtree_root_index,
const ScoredDocumentHitComparator& scored_document_hit_comparator) {
const int heap_size = scored_document_hits->size();
if (target_subtree_root_index >= heap_size) {
return;
}
// Initializes subtree root as the current best node.
int best = target_subtree_root_index;
// If we represent a heap in an array/vector, indices of left and right
// children can be calculated.
const int left = target_subtree_root_index * 2 + 1;
const int right = target_subtree_root_index * 2 + 2;
// If left child is better than current best
if (left < heap_size &&
scored_document_hit_comparator(scored_document_hits->at(left),
scored_document_hits->at(best))) {
best = left;
}
// If right child is better than current best
if (right < heap_size &&
scored_document_hit_comparator(scored_document_hits->at(right),
scored_document_hits->at(best))) {
best = right;
}
// If the best is not the subtree root, swap and continue heapifying the lower
// level subtree
if (best != target_subtree_root_index) {
std::swap(scored_document_hits->at(best),
scored_document_hits->at(target_subtree_root_index));
Heapify(scored_document_hits, best, scored_document_hit_comparator);
}
}
// Heapify the given term vector from top to bottom. Call it after add or
// replace an element at the front of the vector.
void HeapifyTermDown(std::vector<TermMetadata>& scored_terms,
int target_subtree_root_index) {
int heap_size = scored_terms.size();
if (target_subtree_root_index >= heap_size) {
return;
}
// Initializes subtree root as the current minimum node.
int min = target_subtree_root_index;
// If we represent a heap in an array/vector, indices of left and right
// children can be calculated as such.
const int left = target_subtree_root_index * 2 + 1;
const int right = target_subtree_root_index * 2 + 2;
// If left child is smaller than current minimum.
if (left < heap_size &&
scored_terms.at(left).score < scored_terms.at(min).score) {
min = left;
}
// If right child is smaller than current minimum.
if (right < heap_size &&
scored_terms.at(right).score < scored_terms.at(min).score) {
min = right;
}
// If the minimum is not the subtree root, swap and continue heapifying the
// lower level subtree.
if (min != target_subtree_root_index) {
std::swap(scored_terms.at(min), scored_terms.at(target_subtree_root_index));
HeapifyTermDown(scored_terms, min);
}
}
// Heapify the given term vector from bottom to top. Call it after add an
// element at the end of the vector.
void HeapifyTermUp(std::vector<TermMetadata>& scored_terms,
int target_subtree_child_index) {
// If we represent a heap in an array/vector, indices of root can be
// calculated as such.
const int root = (target_subtree_child_index + 1) / 2 - 1;
// If the current child is smaller than the root, swap and continue heapifying
// the upper level subtree
if (root >= 0 && scored_terms.at(target_subtree_child_index).score <
scored_terms.at(root).score) {
std::swap(scored_terms.at(root),
scored_terms.at(target_subtree_child_index));
HeapifyTermUp(scored_terms, root);
}
}
TermMetadata PopRootTerm(std::vector<TermMetadata>& scored_terms) {
if (scored_terms.empty()) {
// Return an invalid TermMetadata as a sentinel value.
return TermMetadata(/*content_in=*/"", /*hit_count_in=*/-1);
}
// Steps to extract root from heap:
// 1. copy out root
TermMetadata root = scored_terms.at(0);
const size_t last_node_index = scored_terms.size() - 1;
// 2. swap root and the last node
std::swap(scored_terms.at(0), scored_terms.at(last_node_index));
// 3. remove last node
scored_terms.pop_back();
// 4. heapify root
HeapifyTermDown(scored_terms, /*target_subtree_root_index=*/0);
return root;
}
} // namespace
void BuildHeapInPlace(
std::vector<ScoredDocumentHit>* scored_document_hits,
const ScoredDocumentHitComparator& scored_document_hit_comparator) {
const int heap_size = scored_document_hits->size();
// Since we use a vector to represent the heap, [size / 2 - 1] is the index
// of the parent node of the last node.
for (int subtree_root_index = heap_size / 2 - 1; subtree_root_index >= 0;
subtree_root_index--) {
Heapify(scored_document_hits, subtree_root_index,
scored_document_hit_comparator);
}
}
void PushToTermHeap(TermMetadata term, int number_to_return,
std::vector<TermMetadata>& scored_terms_heap) {
if (scored_terms_heap.size() < number_to_return) {
scored_terms_heap.push_back(std::move(term));
// We insert at end, so we should heapify bottom up.
HeapifyTermUp(scored_terms_heap, scored_terms_heap.size() - 1);
} else if (scored_terms_heap.at(0).score < term.score) {
scored_terms_heap.at(0) = std::move(term);
// We insert at root, so we should heapify top down.
HeapifyTermDown(scored_terms_heap, /*target_subtree_root_index=*/0);
}
}
libtextclassifier3::StatusOr<ScoredDocumentHit> PopNextTopResultFromHeap(
std::vector<ScoredDocumentHit>* scored_document_hits_heap,
const ScoredDocumentHitComparator& scored_document_hit_comparator) {
if (scored_document_hits_heap->empty()) {
// An invalid ScoredDocumentHit
return absl_ports::ResourceExhaustedError("Heap is empty");
}
// Steps to extract root from heap:
// 1. copy out root
ScoredDocumentHit root = scored_document_hits_heap->at(0);
const size_t last_node_index = scored_document_hits_heap->size() - 1;
// 2. swap root and the last node
std::swap(scored_document_hits_heap->at(0),
scored_document_hits_heap->at(last_node_index));
// 3. remove last node
scored_document_hits_heap->pop_back();
// 4. heapify root
Heapify(scored_document_hits_heap, /*target_subtree_root_index=*/0,
scored_document_hit_comparator);
return root;
}
std::vector<ScoredDocumentHit> PopTopResultsFromHeap(
std::vector<ScoredDocumentHit>* scored_document_hits_heap, int num_results,
const ScoredDocumentHitComparator& scored_document_hit_comparator) {
std::vector<ScoredDocumentHit> scored_document_hit_result;
int result_size = std::min(
num_results, static_cast<int>(scored_document_hits_heap->size()));
while (result_size-- > 0) {
libtextclassifier3::StatusOr<ScoredDocumentHit> next_best_document_hit_or =
PopNextTopResultFromHeap(scored_document_hits_heap,
scored_document_hit_comparator);
if (next_best_document_hit_or.ok()) {
scored_document_hit_result.push_back(
std::move(next_best_document_hit_or).ValueOrDie());
} else {
ICING_VLOG(1) << next_best_document_hit_or.status().error_message();
}
}
return scored_document_hit_result;
}
std::vector<TermMetadata> PopAllTermsFromHeap(
std::vector<TermMetadata>& scored_terms_heap) {
std::vector<TermMetadata> top_term_result;
top_term_result.reserve(scored_terms_heap.size());
while (!scored_terms_heap.empty()) {
top_term_result.push_back(PopRootTerm(scored_terms_heap));
}
return top_term_result;
}
} // namespace lib
} // namespace icing