~ubuntu-branches/ubuntu/precise/gnome-do/precise-proposed

« back to all changes in this revision

Viewing changes to Do/src/Do.Core/HistogramRelevanceProvider.cs

  • Committer: Bazaar Package Importer
  • Author(s): Christopher James Halse Rogers
  • Date: 2008-09-14 10:09:40 UTC
  • mto: (0.1.8 sid)
  • mto: This revision was merged to the branch mainline in revision 7.
  • Revision ID: james.westby@ubuntu.com-20080914100940-kyghudg7py14bu2z
Tags: upstream-0.6.0.0
ImportĀ upstreamĀ versionĀ 0.6.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* HistogramRelevanceProvider.cs
2
 
 *
3
 
 * GNOME Do is the legal property of its developers. Please refer to the
4
 
 * COPYRIGHT file distributed with this
5
 
 * inner distribution.
6
 
 *
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.
11
 
 *
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.
16
 
 *
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/>.
19
 
 */
 
1
// HistogramRelevanceProvider.cs
 
2
//
 
3
// GNOME Do is the legal property of its developers. Please refer to the
 
4
// COPYRIGHT file distributed with this source distribution.
 
5
//
 
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.
 
10
//
 
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.
 
15
//
 
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/>.
20
18
 
21
19
using System;
 
20
using System.IO;
22
21
using System.Collections.Generic;
23
 
 
24
 
using System.IO;
25
 
using System.Threading;
 
22
using System.Runtime.Serialization;
 
23
using System.Runtime.Serialization.Formatters.Binary;
26
24
 
27
25
using Do.Universe;
28
26
 
29
 
namespace Do.Core
30
 
{
31
 
        class HistogramRelevanceProvider : RelevanceProvider {
32
 
                
33
 
                int maxActionHits, maxItemHits;
34
 
                Dictionary<int, int> itemHits, actionHits;
35
 
                
36
 
                Timer serializeTimer;
 
27
namespace Do.Core {
 
28
 
 
29
        /// <summary>
 
30
        /// HistogramRelevanceProvider maintains item and action relevance using
 
31
        /// a histogram of "hit values."
 
32
        /// </summary>
 
33
        sealed class HistogramRelevanceProvider : RelevanceProvider {
 
34
                
 
35
                uint max_item_hits, max_action_hits;
 
36
                DateTime oldest_hit;
 
37
                Dictionary<string, RelevanceRecord> hits;
37
38
                const int SerializeInterval = 15*60;
38
39
 
39
 
                const int HistogramMax = 200;
40
 
                const float HistogramScaleFactor = 0.60f;
41
 
                
42
 
                static bool LetterOccursAfterDelimiter (string s, char a)
43
 
                {
44
 
                        int idx;
45
 
 
46
 
                        idx = 0;
47
 
                        while (idx < s.Length && (idx = s.IndexOf (a, idx)) > -1) {
48
 
                                if (idx == 0 ||
49
 
                                        (idx > 0 && s[idx-1] == ' ')) {
50
 
                                        return true;
51
 
                                }
52
 
                                idx++;
53
 
                        }
54
 
                        return false;
55
 
                }
56
 
 
57
40
                public HistogramRelevanceProvider ()
58
41
                {
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> ();
62
45
 
63
46
                        Deserialize ();
64
47
                        
65
 
                        // Serialize every few minutes.
66
 
                        serializeTimer = new Timer (OnSerializeTimer);
67
 
                        serializeTimer.Change (SerializeInterval*1000, SerializeInterval*1000);
 
48
                        foreach (RelevanceRecord rec in hits.Values) {
 
49
                                UpdateMaxHits (rec);
 
50
                                oldest_hit = oldest_hit.CompareTo (rec.LastHit) < 0 ?
 
51
                                        oldest_hit : rec.LastHit;
 
52
                        }
 
53
                        GLib.Timeout.Add (SerializeInterval*1000, OnSerializeTimer);
68
54
                }
69
55
                
70
 
                protected string RelevanceFile {
 
56
                void UpdateMaxHits (RelevanceRecord rec)
 
57
                {
 
58
                        if (rec.IsAction)
 
59
                                max_action_hits = Math.Max (max_action_hits, rec.Hits);
 
60
                        else
 
61
                                max_item_hits = Math.Max (max_item_hits, rec.Hits);
 
62
                }
 
63
 
 
64
                /// <value>
 
65
                /// Path of file where relevance data is serialized.
 
66
                /// </value>
 
67
                private string RelevanceFile {
71
68
                        get {
72
 
                                return Paths.Combine (Paths.ApplicationData, "relevance3");
73
 
                        }
74
 
                }
75
 
                
76
 
                private void OnSerializeTimer (object state)
77
 
                {
78
 
                        Serialize ();
79
 
                }
80
 
                        
81
 
                protected void Deserialize ()
82
 
                {
83
 
                        lock (itemHits)
84
 
                        lock (actionHits) {
85
 
                                maxItemHits = maxActionHits = 1;
86
 
                                itemHits.Clear ();
87
 
                                actionHits.Clear ();
88
 
                                try {
89
 
                                        Log.Info ("Deserializing HistogramRelevanceProvider...");
90
 
                                        bool isAction;
91
 
                                        string[] parts;
92
 
                                        int     key, value;
93
 
                                        foreach (string line in File.ReadAllLines (RelevanceFile)) {
94
 
                                                try {
95
 
                                                        parts = line.Split ('\t');
96
 
                                                        key = int.Parse (parts[0]);
97
 
                                                        value = int.Parse (parts[1]);
98
 
                                                        isAction = parts[2] == "1";
99
 
 
100
 
                                                        if (isAction) {
101
 
                                                                actionHits[key] = value;
102
 
                                                                maxActionHits = Math.Max (maxActionHits, value);
103
 
                                                        } else {
104
 
                                                                itemHits[key] = value;
105
 
                                                                maxItemHits = Math.Max (maxItemHits, value);
106
 
                                                        }
107
 
                                                } catch {
108
 
                                                        continue;
109
 
                                                }
110
 
                                        }
111
 
                                        Log.Info ("Successfully deserialized HistogramRelevanceProvider.");
112
 
                                } catch (Exception e) {
113
 
                                        Log.Error ("Deserializing HistogramRelevanceProvider failed: {0}", e.Message);
114
 
                                }
115
 
                        }
116
 
                }
117
 
                
118
 
                protected void Serialize ()
119
 
                {
120
 
                        bool shrinkItemHits, shrinkActionHits;
121
 
 
122
 
                        shrinkItemHits = maxItemHits > HistogramMax;
123
 
                        shrinkActionHits = maxActionHits > HistogramMax;
124
 
 
125
 
                        lock (itemHits)
126
 
                        lock (actionHits) {
127
 
                                try {
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);
135
 
                                                                if (hits == 0)
136
 
                                                                        continue;
137
 
                                                        }
138
 
                                                        writer.WriteLine (string.Format ("{0}\t{1}\t0",
139
 
                                                                key, hits));
140
 
                                                }
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);
146
 
                                                                if (hits == 0)
147
 
                                                                        continue;
148
 
                                                        }
149
 
                                                        writer.WriteLine (string.Format ("{0}\t{1}\t1",
150
 
                                                                key, hits));
151
 
                                                }
152
 
                                        }
153
 
                                        Log.Info ("Successfully serialized HistogramRelevanceProvider.");
154
 
                                } catch (Exception e) {
155
 
                                        Log.Error ("Serializing HistogramRelevanceProvider failed: {0}", e.Message);
156
 
                                }
157
 
                        }
158
 
                        if (shrinkItemHits || shrinkActionHits) {
159
 
                                Log.Info ("Shrinking histogram...");
160
 
                                Deserialize ();
161
 
                        }
162
 
                }
163
 
 
164
 
                public override bool CanBeFirstResultForKeypress (DoObject r, char a)
165
 
                {
166
 
                        return LetterOccursAfterDelimiter (r.Name.ToLower (), char.ToLower (a));
167
 
                }
168
 
 
169
 
                public override void IncreaseRelevance (DoObject r, string match, DoObject other)
170
 
                {
171
 
                        int rel;
172
 
                        int key = r.GetHashCode ();
173
 
                        Dictionary<int, int> hits;
174
 
                        int maxHits;
175
 
                        
176
 
                        if (r is DoTextItem) return;
177
 
 
178
 
                        if (!(r is IAction)) {
179
 
                                hits = itemHits;
180
 
                                maxHits = maxItemHits;
181
 
                        } else {
182
 
                                hits = actionHits;
183
 
                                maxHits = maxActionHits;
184
 
                        }
185
 
 
186
 
                        lock (hits) {
187
 
                                hits.TryGetValue (key, out rel);
188
 
                                rel = rel + 1;
189
 
                                hits[key] = rel;
190
 
                                maxHits = Math.Max (maxHits, rel);
191
 
                        }
192
 
 
193
 
                        if (!(r is IAction))
194
 
                                maxItemHits = maxHits;
195
 
                        else
196
 
                                maxActionHits = maxHits;
197
 
                }
198
 
                
199
 
                public override void DecreaseRelevance (DoObject r, string match, DoObject other)
200
 
                {
201
 
                        int rel;
202
 
                        int key = r.GetHashCode ();
203
 
                        Dictionary<int, int> hits;
204
 
                        
205
 
                        if (r is DoTextItem) return;
206
 
 
207
 
                        if (!(r is IAction)) {
208
 
                                hits = itemHits;
209
 
                        } else {
210
 
                                hits = actionHits;
211
 
                        }
212
 
 
213
 
                        lock (hits) {
214
 
                                hits.TryGetValue (key, out rel);
215
 
                                rel = rel - 1;
216
 
                                if (rel == 0) {
217
 
                                        hits.Remove (key);
218
 
                                } else {
219
 
                                        hits[key] = rel;
220
 
                                }
221
 
                        }
222
 
                }
223
 
                
224
 
                public override float GetRelevance (DoObject r, string match, DoObject other)
225
 
                {
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;
230
 
 
231
 
                        score = base.GetRelevance (r, match, other);
 
69
                                return Paths.Combine (Paths.ApplicationData, "relevance5");
 
70
                        }
 
71
                }
 
72
 
 
73
                /// <summary>
 
74
                /// Serialize timer target.
 
75
                /// </summary>
 
76
                private bool OnSerializeTimer ()
 
77
                {
 
78
                        Gtk.Application.Invoke (
 
79
                            delegate {
 
80
                                    Serialize ();
 
81
                            }
 
82
                        );
 
83
                        return true;
 
84
                }
 
85
 
 
86
                /// <summary>
 
87
                /// Deserializes relevance data.
 
88
                /// </summary>
 
89
                private void Deserialize ()
 
90
                {
 
91
                        try {
 
92
                                using (Stream s = File.OpenRead (RelevanceFile)) {
 
93
                                        BinaryFormatter f = new BinaryFormatter ();
 
94
                                        hits = f.Deserialize (s) as Dictionary<string, RelevanceRecord>;
 
95
                                }
 
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);
 
100
                        }
 
101
                }
 
102
 
 
103
                /// <summary>
 
104
                /// Serializes relevance data.
 
105
                /// </summary>
 
106
                private void Serialize ()
 
107
                {
 
108
                        try {
 
109
                                using (Stream s = File.OpenWrite (RelevanceFile)) {
 
110
                                        BinaryFormatter f = new BinaryFormatter ();
 
111
                                        f.Serialize (s, hits);
 
112
                                }
 
113
                                Log.Debug ("Successfully saved learned usage data.");
 
114
                        } catch (Exception e) {
 
115
                                Log.Error ("Failed to save learned usage data: {0}", e.Message);
 
116
                        }
 
117
                }
 
118
 
 
119
                public override void IncreaseRelevance (DoObject o,
 
120
                                                                                                string match,
 
121
                                                                                                DoObject other)
 
122
                {
 
123
                        RelevanceRecord rec;
 
124
                        if (!hits.TryGetValue (o.UID, out rec)) {
 
125
                                rec = new RelevanceRecord (o);
 
126
                                hits [o.UID] = rec;
 
127
                        }
 
128
                        rec.Hits++;
 
129
                        rec.LastHit = DateTime.Now;
 
130
                        if (match.Length > 0)
 
131
                                rec.AddFirstChar (match [0]);
 
132
                        UpdateMaxHits (rec);
 
133
                }
 
134
 
 
135
                public override void DecreaseRelevance (DoObject o,
 
136
                                                                                                string match,
 
137
                                                                                                DoObject other)
 
138
                {
 
139
                        RelevanceRecord rec;
 
140
                        
 
141
                        if (hits.TryGetValue (o.UID, out rec))
 
142
                                rec.Hits--;
 
143
                }
 
144
 
 
145
                public override float GetRelevance (DoObject o,
 
146
                                                                                        string match,
 
147
                                                                                        DoObject other)
 
148
                {
 
149
                        // These should all be between 0 and 1.
 
150
                        float relevance, score;
 
151
                        
 
152
                        // Get string similarity score.
 
153
                        score = StringScoreForAbbreviation (o.Name, match);
232
154
                        if (score == 0) return 0;
233
 
 
234
 
                        if (!(r is IAction)) {
235
 
                                hits = itemHits;
236
 
                                maxHits = maxItemHits;
237
 
                                itemReward = 1f;
238
 
                        } else {
239
 
                                hits = actionHits;
240
 
                                maxHits = maxActionHits;
241
 
                                itemReward = 0f;
242
 
                        }
243
 
 
244
 
                        lock (hits) {
245
 
                                hits.TryGetValue (key, out objectHits);
246
 
                        }
247
 
                        if (objectHits == 0)
248
 
                                relevance = 0f;
249
 
                        else
250
 
                                relevance = (float) objectHits / (float) maxHits;
251
 
 
252
 
                        /*
253
 
                        if (match == "")
254
 
                        Console.WriteLine ("{0}: {1} has relevance {2} ({3}/{4}) and score {5}.",
255
 
                                match, r, relevance, objectHits, maxHits, score);
256
 
                        */
257
 
 
258
 
                        return itemReward * .20f +
259
 
                                   relevance  * .30f +
260
 
                               score      * .70f;
 
155
                        
 
156
                        relevance = 0f; 
 
157
                        if (hits.ContainsKey (o.UID)) {
 
158
                                float age;
 
159
                                RelevanceRecord rec = hits [o.UID];
 
160
        
 
161
                                // On a scale of 0 to 1, how old is the item?
 
162
                                age = 1 -
 
163
                                        (float) (DateTime.Now - rec.LastHit).TotalSeconds /
 
164
                                        (float) (DateTime.Now - oldest_hit).TotalSeconds;
 
165
                                        
 
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);
 
171
                                else
 
172
                                        relevance = 0f;
 
173
                                
 
174
                                relevance *= 0.5f * (1f + age);
 
175
                    }
 
176
                        
 
177
                        
 
178
                        // Penalize actions that require modifier items.
 
179
                        // other != null ==> we're getting relevance for second pane.
 
180
                        if (o is IAction &&
 
181
                            (o as IAction).SupportedModifierItemTypes.Length > 0)
 
182
                                relevance -= 0.1f;
 
183
                        // Penalize item sources so that items are preferred.
 
184
                        if (o.Inner is IItemSource)
 
185
                                relevance -= 0.1f;
 
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)
 
191
                                relevance += 0.1f;
 
192
                        if (o.Inner is AliasAction ||
 
193
                                o.Inner is DeleteAliasAction ||
 
194
                                o.Inner is CopyToClipboard)
 
195
                                relevance = -0.1f;
 
196
                        
 
197
                        return BalanceRelevanceWithScore (o, relevance, score);
 
198
                }
 
199
 
 
200
                float BalanceRelevanceWithScore (IObject o, float rel, float score)
 
201
                {
 
202
                        float reward;
 
203
                   
 
204
                        reward = o is IItem ? .1f : 0f;
 
205
                        return reward +
 
206
                                   rel    * .20f +
 
207
                                   score  * .70f;
 
208
                }
 
209
        }
 
210
        
 
211
        /// <summary>
 
212
        /// RelevanceRecord keeps track of how often an item or action has been
 
213
        /// deemed relevant (Hits) and the last time relevance was increased
 
214
        /// (LastHit).
 
215
        /// </summary>
 
216
        [Serializable]
 
217
        class RelevanceRecord {
 
218
                public DateTime LastHit;
 
219
                public uint Hits;
 
220
                public string FirstChars;
 
221
                public bool IsAction;
 
222
                
 
223
                public RelevanceRecord (IObject o)
 
224
                {
 
225
                        LastHit = DateTime.Now;
 
226
                        FirstChars = string.Empty;
 
227
                        IsAction = o is IAction;
 
228
                }
 
229
                
 
230
                /// <summary>
 
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".
 
235
                /// </summary>
 
236
                /// <param name="c">
 
237
                /// A <see cref="System.Char"/> to add as a first keypress.
 
238
                /// </param>
 
239
                public void AddFirstChar (char c)
 
240
                {
 
241
                        if (!FirstChars.Contains (c.ToString ().ToLower ()))
 
242
                            FirstChars += c.ToString ().ToLower ();
 
243
                }
 
244
                
 
245
                /// <summary>
 
246
                /// The opposite of AddFirstChar.
 
247
                /// </summary>
 
248
                /// <param name="c">
 
249
                /// A <see cref="System.Char"/> to remove from FirstChars.
 
250
                /// </param>
 
251
                public void RemoveFirstChar (char c)
 
252
                {
 
253
                        if (!FirstChars.Contains (c.ToString ().ToLower ()))
 
254
                            return;
 
255
                        FirstChars = FirstChars.Replace (c.ToString ().ToLower (), string.Empty);
 
256
                }
 
257
                
 
258
                /// <summary>
 
259
                /// Whether record has a given first character.
 
260
                /// </summary>
 
261
                /// <param name="c">
 
262
                /// A <see cref="System.Char"/> to look for in FirstChars.
 
263
                /// </param>
 
264
                /// <returns>
 
265
                /// A <see cref="System.Boolean"/> indicating whether record has a
 
266
                /// given first character.
 
267
                /// </returns>
 
268
                public bool HasFirstChar (char c)
 
269
                {
 
270
                        return FirstChars.Contains (c.ToString ().ToLower ());
261
271
                }
262
272
        }
263
273
}