UserHistoryForgettingCurveUtils.java revision bc5688506229bd5cd5e6f4dcdc73c21dc6b80ecb
1/*
2 * Copyright (C) 2012 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.inputmethod.latin;
18
19import android.text.format.DateUtils;
20import android.util.Log;
21
22public class UserHistoryForgettingCurveUtils {
23    private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
24    private static final boolean DEBUG = false;
25    private static final int FC_FREQ_MAX = 127;
26    /* package */ static final int COUNT_MAX = 3;
27    private static final int FC_LEVEL_MAX = 3;
28    /* package */ static final int ELAPSED_TIME_MAX = 15;
29    private static final int ELAPSED_TIME_INTERVAL_HOURS = 6;
30    private static final long ELAPSED_TIME_INTERVAL_MILLIS = ELAPSED_TIME_INTERVAL_HOURS
31            * DateUtils.HOUR_IN_MILLIS;
32    private static final int HALF_LIFE_HOURS = 48;
33
34    private UserHistoryForgettingCurveUtils() {
35        // This utility class is not publicly instantiable.
36    }
37
38    public static class ForgettingCurveParams {
39        private byte mFc;
40        long mLastTouchedTime = 0;
41
42        private void updateLastTouchedTime() {
43            mLastTouchedTime = System.currentTimeMillis();
44        }
45
46        public ForgettingCurveParams() {
47            // TODO: Check whether this word is valid or not
48            this(System.currentTimeMillis());
49        }
50
51        private ForgettingCurveParams(long now) {
52            this((int)pushCount((byte)0, false), now, now);
53        }
54
55        public ForgettingCurveParams(int fc, long now, long last) {
56            mFc = (byte)fc;
57            mLastTouchedTime = last;
58            updateElapsedTime(now);
59        }
60
61        public byte getFc() {
62            updateElapsedTime(System.currentTimeMillis());
63            return mFc;
64        }
65
66        public int getFrequency() {
67            updateElapsedTime(System.currentTimeMillis());
68            return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
69        }
70
71        public int notifyTypedAgainAndGetFrequency() {
72            updateLastTouchedTime();
73            // TODO: Check whether this word is valid or not
74            mFc = pushCount(mFc, false);
75            return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
76        }
77
78        private void updateElapsedTime(long now) {
79            final int elapsedTimeCount =
80                    (int)((now - mLastTouchedTime) / ELAPSED_TIME_INTERVAL_MILLIS);
81            if (elapsedTimeCount <= 0) {
82                return;
83            }
84            for (int i = 0; i < elapsedTimeCount; ++i) {
85                mLastTouchedTime += ELAPSED_TIME_INTERVAL_MILLIS;
86                mFc = pushElapsedTime(mFc);
87            }
88        }
89    }
90
91    /* package */ static  int fcToElapsedTime(byte fc) {
92        return fc & 0x0F;
93    }
94
95    /* package */ static int fcToCount(byte fc) {
96        return (fc >> 4) & 0x03;
97    }
98
99    /* package */ static int fcToLevel(byte fc) {
100        return (fc >> 6) & 0x03;
101    }
102
103    private static int calcFreq(int elapsedTime, int count, int level) {
104        if (level <= 0) {
105            // Reserved words, just return -1
106            return -1;
107        }
108        if (count == COUNT_MAX) {
109            // Temporary promote because it's frequently typed recently
110            ++level;
111        }
112        final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
113        final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
114        return MathUtils.SCORE_TABLE[l - 1][et];
115    }
116
117    /* pakcage */ static byte calcFc(int elapsedTime, int count, int level) {
118        final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
119        final int c = Math.min(COUNT_MAX, Math.max(0, count));
120        final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
121        return (byte)(et | (c << 4) | (l << 6));
122    }
123
124    public static int fcToFreq(byte fc) {
125        final int elapsedTime = fcToElapsedTime(fc);
126        final int count = fcToCount(fc);
127        final int level = fcToLevel(fc);
128        return calcFreq(elapsedTime, count, level);
129    }
130
131    public static byte pushElapsedTime(byte fc) {
132        int elapsedTime = fcToElapsedTime(fc);
133        int count = fcToCount(fc);
134        int level = fcToLevel(fc);
135        if (elapsedTime >= ELAPSED_TIME_MAX) {
136            // Downgrade level
137            elapsedTime = 0;
138            count = COUNT_MAX;
139            --level;
140        } else {
141            ++elapsedTime;
142        }
143        return calcFc(elapsedTime, count, level);
144    }
145
146    public static byte pushCount(byte fc, boolean isValid) {
147        final int elapsedTime = fcToElapsedTime(fc);
148        int count = fcToCount(fc);
149        int level = fcToLevel(fc);
150        if ((elapsedTime == 0 && count >= COUNT_MAX) || (isValid && level == 0)) {
151            // Upgrade level
152            ++level;
153            count = 0;
154            if (DEBUG) {
155                Log.d(TAG, "Upgrade level.");
156            }
157        } else {
158            ++count;
159        }
160        return calcFc(0, count, level);
161    }
162
163    // TODO: isValid should be false for a word whose frequency is 0,
164    // or that is not in the dictionary.
165    /**
166     * Check wheather we should save the bigram to the SQL DB or not
167     */
168    public static boolean needsToSave(byte fc, boolean isValid, boolean addLevel0Bigram) {
169        int level = fcToLevel(fc);
170        if (level == 0) {
171            if (isValid || !addLevel0Bigram) {
172                return false;
173            }
174        }
175        final int elapsedTime = fcToElapsedTime(fc);
176        return (elapsedTime < ELAPSED_TIME_MAX - 1 || level > 0);
177    }
178
179    private static class MathUtils {
180        public static final int[][] SCORE_TABLE = new int[FC_LEVEL_MAX][ELAPSED_TIME_MAX + 1];
181        static {
182            for (int i = 0; i < FC_LEVEL_MAX; ++i) {
183                final double initialFreq;
184                if (i >= 2) {
185                    initialFreq = (double)FC_FREQ_MAX;
186                } else if (i == 1) {
187                    initialFreq = (double)FC_FREQ_MAX / 2;
188                } else if (i == 0) {
189                    initialFreq = (double)FC_FREQ_MAX / 4;
190                } else {
191                    continue;
192                }
193                for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
194                    final double elapsedHour = j * ELAPSED_TIME_INTERVAL_HOURS;
195                    final double freq =
196                            initialFreq * Math.pow(initialFreq, elapsedHour / HALF_LIFE_HOURS);
197                    final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
198                    SCORE_TABLE[i][j] = intFreq;
199                }
200            }
201        }
202    }
203}
204