1
/* HistogramRelevanceProvider.cs
3
* GNOME Do is the legal property of its developers. Please refer to the
4
* COPYRIGHT file distributed with this
7
* This program is free software: you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation, either version 3 of the License, or
10
* (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1
// HistogramRelevanceProvider.cs
3
// GNOME Do is the legal property of its developers. Please refer to the
4
// COPYRIGHT file distributed with this source distribution.
6
// This program is free software: you can redistribute it and/or modify
7
// it under the terms of the GNU General Public License as published by
8
// the Free Software Foundation, either version 3 of the License, or
9
// (at your option) any later version.
11
// This program is distributed in the hope that it will be useful,
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
// GNU General Public License for more details.
16
// You should have received a copy of the GNU General Public License
17
// along with this program. If not, see <http://www.gnu.org/licenses/>.
22
21
using System.Collections.Generic;
25
using System.Threading;
22
using System.Runtime.Serialization;
23
using System.Runtime.Serialization.Formatters.Binary;
31
class HistogramRelevanceProvider : RelevanceProvider {
33
int maxActionHits, maxItemHits;
34
Dictionary<int, int> itemHits, actionHits;
30
/// HistogramRelevanceProvider maintains item and action relevance using
31
/// a histogram of "hit values."
33
sealed class HistogramRelevanceProvider : RelevanceProvider {
35
uint max_item_hits, max_action_hits;
37
Dictionary<string, RelevanceRecord> hits;
37
38
const int SerializeInterval = 15*60;
39
const int HistogramMax = 200;
40
const float HistogramScaleFactor = 0.60f;
42
static bool LetterOccursAfterDelimiter (string s, char a)
47
while (idx < s.Length && (idx = s.IndexOf (a, idx)) > -1) {
49
(idx > 0 && s[idx-1] == ' ')) {
57
40
public HistogramRelevanceProvider ()
59
maxActionHits = maxItemHits = 1;
60
itemHits = new Dictionary<int,int> ();
61
actionHits = new Dictionary<int,int> ();
42
max_item_hits = max_action_hits = 1;
43
oldest_hit = DateTime.Now;
44
hits = new Dictionary<string, RelevanceRecord> ();
65
// Serialize every few minutes.
66
serializeTimer = new Timer (OnSerializeTimer);
67
serializeTimer.Change (SerializeInterval*1000, SerializeInterval*1000);
48
foreach (RelevanceRecord rec in hits.Values) {
50
oldest_hit = oldest_hit.CompareTo (rec.LastHit) < 0 ?
51
oldest_hit : rec.LastHit;
53
GLib.Timeout.Add (SerializeInterval*1000, OnSerializeTimer);
70
protected string RelevanceFile {
56
void UpdateMaxHits (RelevanceRecord rec)
59
max_action_hits = Math.Max (max_action_hits, rec.Hits);
61
max_item_hits = Math.Max (max_item_hits, rec.Hits);
65
/// Path of file where relevance data is serialized.
67
private string RelevanceFile {
72
return Paths.Combine (Paths.ApplicationData, "relevance3");
76
private void OnSerializeTimer (object state)
81
protected void Deserialize ()
85
maxItemHits = maxActionHits = 1;
89
Log.Info ("Deserializing HistogramRelevanceProvider...");
93
foreach (string line in File.ReadAllLines (RelevanceFile)) {
95
parts = line.Split ('\t');
96
key = int.Parse (parts[0]);
97
value = int.Parse (parts[1]);
98
isAction = parts[2] == "1";
101
actionHits[key] = value;
102
maxActionHits = Math.Max (maxActionHits, value);
104
itemHits[key] = value;
105
maxItemHits = Math.Max (maxItemHits, value);
111
Log.Info ("Successfully deserialized HistogramRelevanceProvider.");
112
} catch (Exception e) {
113
Log.Error ("Deserializing HistogramRelevanceProvider failed: {0}", e.Message);
118
protected void Serialize ()
120
bool shrinkItemHits, shrinkActionHits;
122
shrinkItemHits = maxItemHits > HistogramMax;
123
shrinkActionHits = maxActionHits > HistogramMax;
128
Log.Info ("Serializing HistogramRelevanceProvider...");
129
using (StreamWriter writer = new StreamWriter (RelevanceFile)) {
130
// Serialize item hits information:
131
foreach (int key in itemHits.Keys) {
132
int hits = itemHits [key];
133
if (shrinkItemHits) {
134
hits = (int) (hits * HistogramScaleFactor);
138
writer.WriteLine (string.Format ("{0}\t{1}\t0",
141
// Serialize action hits information:
142
foreach (int key in actionHits.Keys) {
143
int hits = actionHits [key];
144
if (shrinkActionHits) {
145
hits = (int) (hits * HistogramScaleFactor);
149
writer.WriteLine (string.Format ("{0}\t{1}\t1",
153
Log.Info ("Successfully serialized HistogramRelevanceProvider.");
154
} catch (Exception e) {
155
Log.Error ("Serializing HistogramRelevanceProvider failed: {0}", e.Message);
158
if (shrinkItemHits || shrinkActionHits) {
159
Log.Info ("Shrinking histogram...");
164
public override bool CanBeFirstResultForKeypress (DoObject r, char a)
166
return LetterOccursAfterDelimiter (r.Name.ToLower (), char.ToLower (a));
169
public override void IncreaseRelevance (DoObject r, string match, DoObject other)
172
int key = r.GetHashCode ();
173
Dictionary<int, int> hits;
176
if (r is DoTextItem) return;
178
if (!(r is IAction)) {
180
maxHits = maxItemHits;
183
maxHits = maxActionHits;
187
hits.TryGetValue (key, out rel);
190
maxHits = Math.Max (maxHits, rel);
194
maxItemHits = maxHits;
196
maxActionHits = maxHits;
199
public override void DecreaseRelevance (DoObject r, string match, DoObject other)
202
int key = r.GetHashCode ();
203
Dictionary<int, int> hits;
205
if (r is DoTextItem) return;
207
if (!(r is IAction)) {
214
hits.TryGetValue (key, out rel);
224
public override float GetRelevance (DoObject r, string match, DoObject other)
226
float relevance, score, itemReward; // These should all be between 0 and 1.
227
int key = r.GetHashCode ();
228
Dictionary<int, int> hits;
229
int objectHits, maxHits;
231
score = base.GetRelevance (r, match, other);
69
return Paths.Combine (Paths.ApplicationData, "relevance5");
74
/// Serialize timer target.
76
private bool OnSerializeTimer ()
78
Gtk.Application.Invoke (
87
/// Deserializes relevance data.
89
private void Deserialize ()
92
using (Stream s = File.OpenRead (RelevanceFile)) {
93
BinaryFormatter f = new BinaryFormatter ();
94
hits = f.Deserialize (s) as Dictionary<string, RelevanceRecord>;
96
Log.Debug ("Successfully loaded learned usage data.");
97
} catch (FileNotFoundException) {
98
} catch (Exception e) {
99
Log.Error ("Failed to load learned usage data: {0}", e.Message);
104
/// Serializes relevance data.
106
private void Serialize ()
109
using (Stream s = File.OpenWrite (RelevanceFile)) {
110
BinaryFormatter f = new BinaryFormatter ();
111
f.Serialize (s, hits);
113
Log.Debug ("Successfully saved learned usage data.");
114
} catch (Exception e) {
115
Log.Error ("Failed to save learned usage data: {0}", e.Message);
119
public override void IncreaseRelevance (DoObject o,
124
if (!hits.TryGetValue (o.UID, out rec)) {
125
rec = new RelevanceRecord (o);
129
rec.LastHit = DateTime.Now;
130
if (match.Length > 0)
131
rec.AddFirstChar (match [0]);
135
public override void DecreaseRelevance (DoObject o,
141
if (hits.TryGetValue (o.UID, out rec))
145
public override float GetRelevance (DoObject o,
149
// These should all be between 0 and 1.
150
float relevance, score;
152
// Get string similarity score.
153
score = StringScoreForAbbreviation (o.Name, match);
232
154
if (score == 0) return 0;
234
if (!(r is IAction)) {
236
maxHits = maxItemHits;
240
maxHits = maxActionHits;
245
hits.TryGetValue (key, out objectHits);
250
relevance = (float) objectHits / (float) maxHits;
254
Console.WriteLine ("{0}: {1} has relevance {2} ({3}/{4}) and score {5}.",
255
match, r, relevance, objectHits, maxHits, score);
258
return itemReward * .20f +
157
if (hits.ContainsKey (o.UID)) {
159
RelevanceRecord rec = hits [o.UID];
161
// On a scale of 0 to 1, how old is the item?
163
(float) (DateTime.Now - rec.LastHit).TotalSeconds /
164
(float) (DateTime.Now - oldest_hit).TotalSeconds;
166
// Relevance is non-zero only if the record contains first char
167
// relevance for the item.
168
if (match.Length == 0 || rec.HasFirstChar (match [0]))
169
relevance = (float) rec.Hits /
170
(float) (rec.IsAction ? max_action_hits : max_item_hits);
174
relevance *= 0.5f * (1f + age);
178
// Penalize actions that require modifier items.
179
// other != null ==> we're getting relevance for second pane.
181
(o as IAction).SupportedModifierItemTypes.Length > 0)
183
// Penalize item sources so that items are preferred.
184
if (o.Inner is IItemSource)
186
// Give the most popular actions a little leg up.
187
if (o.Inner is OpenAction ||
188
o.Inner is OpenURLAction ||
189
o.Inner is RunAction ||
190
o.Inner is EmailAction)
192
if (o.Inner is AliasAction ||
193
o.Inner is DeleteAliasAction ||
194
o.Inner is CopyToClipboard)
197
return BalanceRelevanceWithScore (o, relevance, score);
200
float BalanceRelevanceWithScore (IObject o, float rel, float score)
204
reward = o is IItem ? .1f : 0f;
212
/// RelevanceRecord keeps track of how often an item or action has been
213
/// deemed relevant (Hits) and the last time relevance was increased
217
class RelevanceRecord {
218
public DateTime LastHit;
220
public string FirstChars;
221
public bool IsAction;
223
public RelevanceRecord (IObject o)
225
LastHit = DateTime.Now;
226
FirstChars = string.Empty;
227
IsAction = o is IAction;
231
/// Add a character as a valid first keypress in a search for the item.
232
/// Searching for "Pidgin Internet Messenger" with the query "pid" will
233
/// result in 'p' being added to FirstChars for the RelevanceRecord for
234
/// "Pidgin Internet Messenger".
237
/// A <see cref="System.Char"/> to add as a first keypress.
239
public void AddFirstChar (char c)
241
if (!FirstChars.Contains (c.ToString ().ToLower ()))
242
FirstChars += c.ToString ().ToLower ();
246
/// The opposite of AddFirstChar.
249
/// A <see cref="System.Char"/> to remove from FirstChars.
251
public void RemoveFirstChar (char c)
253
if (!FirstChars.Contains (c.ToString ().ToLower ()))
255
FirstChars = FirstChars.Replace (c.ToString ().ToLower (), string.Empty);
259
/// Whether record has a given first character.
262
/// A <see cref="System.Char"/> to look for in FirstChars.
265
/// A <see cref="System.Boolean"/> indicating whether record has a
266
/// given first character.
268
public bool HasFirstChar (char c)
270
return FirstChars.Contains (c.ToString ().ToLower ());