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