RankAwarePromoter.java revision fdb80c2962c88ac62dcd7ee7f2fab1857b61506b
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.quicksearchbox;
18
19import com.google.common.annotations.VisibleForTesting;
20
21import android.util.Log;
22
23import java.util.Iterator;
24import java.util.LinkedList;
25
26/**
27 * A promoter that gives preference to suggestions from higher ranking corpora.
28 */
29public class RankAwarePromoter implements Promoter {
30
31    private static final boolean DBG = false;
32    private static final String TAG = "QSB.RankAwarePromoter";
33
34    private final Config mConfig;
35
36    public RankAwarePromoter(Config config) {
37        mConfig = config;
38    }
39
40    protected Config getConfig() {
41        return mConfig;
42    }
43
44    public void pickPromoted(Suggestions suggestions,
45            int maxPromoted, ListSuggestionCursor promoted) {
46        promoteSuggestions(suggestions.getCorpusResults(), maxPromoted, promoted);
47    }
48
49    @VisibleForTesting
50    void promoteSuggestions(Iterable<CorpusResult> suggestions, int maxPromoted,
51            ListSuggestionCursor promoted) {
52        if (DBG) Log.d(TAG, "Available results: " + suggestions);
53
54        // Split non-empty results into default sources and other, positioned at first suggestion
55        LinkedList<CorpusResult> defaultResults = new LinkedList<CorpusResult>();
56        LinkedList<CorpusResult> otherResults = new LinkedList<CorpusResult>();
57        for (CorpusResult result : suggestions) {
58            if (result.getCount() > 0) {
59                result.moveTo(0);
60                Corpus corpus = result.getCorpus();
61                if (corpus == null || corpus.isCorpusDefaultEnabled()) {
62                    defaultResults.add(result);
63                } else {
64                    otherResults.add(result);
65                }
66            }
67        }
68
69        int slotsLeft = Math.max(0, maxPromoted - promoted.getCount());
70
71        // Share the top slots equally among each of the default corpora
72        if (slotsLeft > 0 && !defaultResults.isEmpty()) {
73            int slotsToFill = Math.min(getSlotsAboveKeyboard() - promoted.getCount(), slotsLeft);
74            if (slotsToFill > 0) {
75                int stripeSize = Math.max(1, slotsToFill / defaultResults.size());
76                slotsLeft -= roundRobin(defaultResults, slotsToFill, stripeSize, promoted);
77            }
78        }
79
80        // Then try to fill with the remaining promoted results
81        if (slotsLeft > 0 && !defaultResults.isEmpty()) {
82            int stripeSize = Math.max(1, slotsLeft / defaultResults.size());
83            slotsLeft -= roundRobin(defaultResults, slotsLeft, stripeSize, promoted);
84            // We may still have a few slots left
85            slotsLeft -= roundRobin(defaultResults, slotsLeft, slotsLeft, promoted);
86        }
87
88        // Then try to fill with the rest
89        if (slotsLeft > 0 && !otherResults.isEmpty()) {
90            int stripeSize = Math.max(1, slotsLeft / otherResults.size());
91            slotsLeft -= roundRobin(otherResults, slotsLeft, stripeSize, promoted);
92            // We may still have a few slots left
93            slotsLeft -= roundRobin(otherResults, slotsLeft, slotsLeft, promoted);
94        }
95
96        if (DBG) Log.d(TAG, "Returning " + promoted.toString());
97    }
98
99    private int getSlotsAboveKeyboard() {
100        return mConfig.getNumSuggestionsAboveKeyboard();
101    }
102
103    /**
104     * Promotes "stripes" of suggestions from each corpus.
105     *
106     * @param results     the list of CorpusResults from which to promote.
107     *                    Exhausted CorpusResults are removed from the list.
108     * @param maxPromoted maximum number of suggestions to promote.
109     * @param stripeSize  number of suggestions to take from each corpus.
110     * @param promoted    the list to which promoted suggestions are added.
111     * @return the number of suggestions actually promoted.
112     */
113    private int roundRobin(LinkedList<CorpusResult> results, int maxPromoted, int stripeSize,
114            ListSuggestionCursor promoted) {
115        int count = 0;
116        if (maxPromoted > 0 && !results.isEmpty()) {
117            for (Iterator<CorpusResult> iter = results.iterator();
118                 count < maxPromoted && iter.hasNext();) {
119                CorpusResult result = iter.next();
120                count += promote(result, stripeSize, promoted);
121                if (result.getPosition() == result.getCount()) {
122                    iter.remove();
123                }
124            }
125        }
126        return count;
127    }
128
129    /**
130     * Copies suggestions from a SuggestionCursor to the list of promoted suggestions.
131     *
132     * @param cursor from which to copy the suggestions
133     * @param count maximum number of suggestions to copy
134     * @param promoted the list to which to add the suggestions
135     * @return the number of suggestions actually copied.
136     */
137    private int promote(SuggestionCursor cursor, int count, ListSuggestionCursor promoted) {
138        if (count < 1 || cursor.getPosition() >= cursor.getCount()) {
139            return 0;
140        }
141        int addedCount = 0;
142        do {
143            if (accept(cursor)) {
144                promoted.add(new SuggestionPosition(cursor));
145                addedCount++;
146            }
147        } while (cursor.moveToNext() && addedCount < count);
148        return addedCount;
149    }
150
151    /**
152     * Determines if a suggestion should be added to the promoted suggestion list.
153     *
154     * @param s The suggestion in question
155     * @return true to include it in the results
156     */
157    protected boolean accept(Suggestion s) {
158        return true;
159    }
160
161}
162