| package org.apache.lucene.search; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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. |
| */ |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| |
| /** |
| * Base class for Scorers that score disjunctions. |
| */ |
| abstract class DisjunctionScorer extends Scorer { |
| |
| private final boolean needsScores; |
| private final DisiPriorityQueue<Scorer> subScorers; |
| private final long cost; |
| |
| /** Linked list of scorers which are on the current doc */ |
| private DisiWrapper<Scorer> topScorers; |
| |
| protected DisjunctionScorer(Weight weight, List<Scorer> subScorers, boolean needsScores) { |
| super(weight); |
| if (subScorers.size() <= 1) { |
| throw new IllegalArgumentException("There must be at least 2 subScorers"); |
| } |
| this.subScorers = new DisiPriorityQueue<Scorer>(subScorers.size()); |
| long cost = 0; |
| for (Scorer scorer : subScorers) { |
| final DisiWrapper<Scorer> w = new DisiWrapper<>(scorer); |
| cost += w.cost; |
| this.subScorers.add(w); |
| } |
| this.cost = cost; |
| this.needsScores = needsScores; |
| } |
| |
| @Override |
| public TwoPhaseIterator asTwoPhaseIterator() { |
| float sumMatchCost = 0; |
| long sumApproxCost = 0; |
| |
| // Compute matchCost as the avarage over the matchCost of the subScorers. |
| // This is weighted by the cost, which is an expected number of matching documents. |
| for (DisiWrapper<Scorer> w : subScorers) { |
| if (w.twoPhaseView != null) { |
| long costWeight = (w.cost <= 1) ? 1 : w.cost; |
| sumMatchCost += w.twoPhaseView.matchCost() * costWeight; |
| sumApproxCost += costWeight; |
| } |
| } |
| |
| if (sumApproxCost == 0) { // no sub scorer supports approximations |
| return null; |
| } |
| |
| final float matchCost = sumMatchCost / sumApproxCost; |
| |
| // note it is important to share the same pq as this scorer so that |
| // rebalancing the pq through the approximation will also rebalance |
| // the pq in this scorer. |
| return new TwoPhaseIterator(new DisjunctionDISIApproximation<Scorer>(subScorers)) { |
| |
| @Override |
| public boolean matches() throws IOException { |
| DisiWrapper<Scorer> topScorers = subScorers.topList(); |
| // remove the head of the list as long as it does not match |
| while (topScorers.twoPhaseView != null && ! topScorers.twoPhaseView.matches()) { |
| topScorers = topScorers.next; |
| if (topScorers == null) { |
| return false; |
| } |
| } |
| // now we know we have at least one match since the first element of 'matchList' matches |
| if (needsScores) { |
| // if scores or freqs are needed, we also need to remove scorers |
| // from the top list that do not actually match |
| DisiWrapper<Scorer> previous = topScorers; |
| for (DisiWrapper<Scorer> w = topScorers.next; w != null; w = w.next) { |
| if (w.twoPhaseView != null && ! w.twoPhaseView.matches()) { |
| // w does not match, remove it |
| previous.next = w.next; |
| } else { |
| previous = w; |
| } |
| } |
| } else { |
| // since we don't need scores, let's pretend we have a single match |
| topScorers.next = null; |
| } |
| |
| // We need to explicitely set the list of top scorers to avoid the |
| // laziness of DisjunctionScorer.score() that would take all scorers |
| // positioned on the same doc as the top of the pq, including |
| // non-matching scorers |
| DisjunctionScorer.this.topScorers = topScorers; |
| return true; |
| } |
| |
| @Override |
| public float matchCost() { |
| return matchCost; |
| } |
| }; |
| } |
| |
| @Override |
| public final long cost() { |
| return cost; |
| } |
| |
| @Override |
| public final int docID() { |
| return subScorers.top().doc; |
| } |
| |
| @Override |
| public final int nextDoc() throws IOException { |
| topScorers = null; |
| DisiWrapper<Scorer> top = subScorers.top(); |
| final int doc = top.doc; |
| do { |
| top.doc = top.iterator.nextDoc(); |
| top = subScorers.updateTop(); |
| } while (top.doc == doc); |
| |
| return top.doc; |
| } |
| |
| @Override |
| public final int advance(int target) throws IOException { |
| topScorers = null; |
| DisiWrapper<Scorer> top = subScorers.top(); |
| do { |
| top.doc = top.iterator.advance(target); |
| top = subScorers.updateTop(); |
| } while (top.doc < target); |
| |
| return top.doc; |
| } |
| |
| @Override |
| public final int freq() throws IOException { |
| if (topScorers == null) { |
| topScorers = subScorers.topList(); |
| } |
| int freq = 1; |
| for (DisiWrapper<Scorer> w = topScorers.next; w != null; w = w.next) { |
| freq += 1; |
| } |
| return freq; |
| } |
| |
| @Override |
| public final float score() throws IOException { |
| if (topScorers == null) { |
| topScorers = subScorers.topList(); |
| } |
| return score(topScorers); |
| } |
| |
| /** Compute the score for the given linked list of scorers. */ |
| protected abstract float score(DisiWrapper<Scorer> topList) throws IOException; |
| |
| @Override |
| public final Collection<ChildScorer> getChildren() { |
| ArrayList<ChildScorer> children = new ArrayList<>(); |
| for (DisiWrapper<Scorer> scorer : subScorers) { |
| children.add(new ChildScorer(scorer.iterator, "SHOULD")); |
| } |
| return children; |
| } |
| |
| } |