1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/app_list/search/tokenized_string_match.h"
7 #include "chrome/browser/ui/app_list/search/tokenized_string_char_iterator.h"
13 // The factors below are applied when the current char of query matches
14 // the current char of the text to be matched. Different factors are chosen
15 // based on where the match happens. kIsPrefixMultiplier is used when the
16 // matched portion is a prefix of both the query and the text, which implies
17 // that the matched chars are at the same position in query and text. This is
18 // the most preferred case thus it has the highest score. When the current char
19 // of the query and the text does not match, the algorithm moves to the next
20 // token in the text and try to match from there. kIsFrontOfWordMultipler will
21 // be used if the first char of the token matches the current char of the query.
22 // Otherwise, the match is considered as weak and kIsWeakHitMultiplier is
25 // Suppose the text to be matched is 'Google Chrome'.
26 // Query 'go' would yield kIsPrefixMultiplier for each char.
27 // Query 'gc' would use kIsPrefixMultiplier for 'g' and
28 // kIsFrontOfWordMultipler for 'c'.
29 // Query 'ch' would use kIsFrontOfWordMultipler for 'c' and
30 // kIsWeakHitMultiplier for 'h'.
31 const double kIsPrefixMultiplier
= 1.0;
32 const double kIsFrontOfWordMultipler
= 0.8;
33 const double kIsWeakHitMultiplier
= 0.6;
35 // A relevance score that represents no match.
36 const double kNoMatchScore
= 0.0;
40 TokenizedStringMatch::TokenizedStringMatch()
41 : relevance_(kNoMatchScore
) {}
43 TokenizedStringMatch::~TokenizedStringMatch() {}
45 bool TokenizedStringMatch::Calculate(const TokenizedString
& query
,
46 const TokenizedString
& text
) {
47 relevance_
= kNoMatchScore
;
50 ui::Range hit
= ui::Range::InvalidRange();
52 TokenizedStringCharIterator
query_iter(query
);
53 TokenizedStringCharIterator
text_iter(text
);
55 while (!query_iter
.end() && !text_iter
.end()) {
56 if (query_iter
.Get() == text_iter
.Get()) {
57 if (query_iter
.GetArrayPos() == text_iter
.GetArrayPos())
58 relevance_
+= kIsPrefixMultiplier
;
59 else if (text_iter
.IsFirstCharOfToken())
60 relevance_
+= kIsFrontOfWordMultipler
;
62 relevance_
+= kIsWeakHitMultiplier
;
65 hit
.set_start(text_iter
.GetArrayPos());
66 hit
.set_end(text_iter
.GetArrayPos() + text_iter
.GetCharSize());
68 query_iter
.NextChar();
73 hit
= ui::Range::InvalidRange();
76 text_iter
.NextToken();
80 // No match if query is not fully consumed.
81 if (!query_iter
.end()) {
82 relevance_
= kNoMatchScore
;
90 // Using length() for normalizing is not 100% correct but should be good
91 // enough compared with using real char count of the text.
92 if (text
.text().length())
93 relevance_
/= text
.text().length();
95 return relevance_
> kNoMatchScore
;
98 bool TokenizedStringMatch::Calculate(const string16
& query
,
99 const string16
& text
) {
100 const TokenizedString
tokenized_query(query
);
101 const TokenizedString
tokenized_text(text
);
102 return Calculate(tokenized_query
, tokenized_text
);
105 } // namespace app_list