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