import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupEvent
import com.intellij.codeInsight.lookup.impl.LookupImpl
+import com.intellij.completion.FeatureManagerImpl
import com.intellij.stats.personalization.UserFactorDescriptions
import com.intellij.stats.personalization.UserFactorStorage
-import com.intellij.stats.personalization.UserFactorsManager
/**
* @author Vitaliy.Bibaev
val relevanceObjects =
lookup.getRelevanceObjects(listOf(element), false)
val relevanceMap = relevanceObjects[element]!!.associate { it.first to it.second }
- val userFactorsManager = UserFactorsManager.getInstance(lookup.project)
- relevanceMap.forEach { name, value -> userFactorsManager.getFeatureFactor(name)?.update(value) }
+ val project = lookup.project
+ val featureManager = FeatureManagerImpl.getInstance()
+ featureManager.binaryFactors.filter { !featureManager.isUserFeature(it.name) }.forEach { feature ->
+ UserFactorStorage.applyOnBoth(project, UserFactorDescriptions.binaryFeatureDescriptor(feature))
+ { updater ->
+ updater.update(relevanceMap[feature.name])
+ }
+ }
+
+ featureManager.doubleFactors.filter { !featureManager.isUserFeature(it.name) }.forEach { feature ->
+ UserFactorStorage.applyOnBoth(project, UserFactorDescriptions.doubleFeatureDescriptor(feature))
+ { updater ->
+ updater.update(relevanceMap[feature.name])
+ }
+ }
+
+ featureManager.categorialFactors.filter { !featureManager.isUserFeature(it.name) }.forEach { feature ->
+ UserFactorStorage.applyOnBoth(project, UserFactorDescriptions.categoriealFeatureDescriptor(feature))
+ { updater ->
+ updater.update(relevanceMap[feature.name])
+ }
+ }
}
private fun processExplicitSelect(lookup: LookupImpl, element: LookupElement) {
val id: String
fun compute(storage: UserFactorStorage): String?
-
- interface FeatureFactor : UserFactor {
- fun update(value: Any?)
- }
}
package com.intellij.stats.personalization
import com.intellij.stats.personalization.impl.*
+import com.jetbrains.completion.ranker.features.BinaryFeature
+import com.jetbrains.completion.ranker.features.CatergorialFeature
+import com.jetbrains.completion.ranker.features.DoubleFeature
/**
* @author Vitaliy.Bibaev
val TIME_BETWEEN_TYPING = Descriptor("timeBetweenTyping", ::TimeBetweenTypingUpdater, ::TimeBetweenTypingReader)
val MNEMONICS_USAGE = Descriptor("mnemonicsUsage", ::MnemonicsUsageUpdater, ::MnemonicsUsageReader)
+ fun binaryFeatureDescriptor(feature: BinaryFeature): Descriptor<BinaryFeatureUpdater, BinaryFeatureReader> {
+ return Descriptor("binaryFeature:${feature.name}", ::BinaryFeatureUpdater, ::BinaryFeatureReader)
+ }
+
+ fun doubleFeatureDescriptor(feature: DoubleFeature): Descriptor<DoubleFeatureUpdater, DoubleFeatureReader> {
+ return Descriptor("doudleFeature:${feature.name}", ::DoubleFeatureUpdater, ::DoubleFeatureReader)
+ }
+
+ fun categoriealFeatureDescriptor(feature: CatergorialFeature): Descriptor<CategoryFeatureUpdater, CategoryFeatureReader> {
+ return Descriptor("categorialFeature:${feature.name}",
+ { CategoryFeatureUpdater(feature.categories, it) },
+ ::CategoryFeatureReader)
+ }
+
class Descriptor<out U : FactorUpdater, out R : FactorReader>(
override val factorId: String,
override val updaterFactory: (MutableDoubleFactor) -> U,
fun getAllFactors(): List<UserFactor>
- fun getFeatureFactor(featureName: String): UserFactor.FeatureFactor?
-
fun getFactor(id: String): UserFactor
}
--- /dev/null
+package com.intellij.stats.personalization.impl
+
+import com.intellij.stats.personalization.UserFactorBase
+import com.intellij.stats.personalization.UserFactorDescriptions
+import com.intellij.stats.personalization.UserFactorReaderBase
+import com.intellij.stats.personalization.UserFactorUpdaterBase
+import com.jetbrains.completion.ranker.features.BinaryFeature
+import com.jetbrains.completion.ranker.features.impl.FeatureUtils
+
+/**
+ * @author Vitaliy.Bibaev
+ */
+class BinaryFeatureReader(factor: DailyAggregatedDoubleFactor)
+ : UserFactorReaderBase(factor) {
+ fun calculateRatioByValue(): Map<String, Double> {
+ val sums = factor.aggregateSum()
+ val total = sums.values.sum()
+ if (total == 0.0) return emptyMap()
+ return sums.mapValues { e -> e.value / total }
+ }
+}
+
+class BinaryFeatureUpdater(factor: MutableDoubleFactor) : UserFactorUpdaterBase(factor) {
+ fun update(value: Any?) {
+ factor.incrementOnToday(value?.toString() ?: FeatureUtils.UNDEFINED)
+ }
+}
+
+class BinaryValueRatio(feature: BinaryFeature, private val valueName: String)
+ : UserFactorBase<BinaryFeatureReader>("Binary${feature.name}$valueName",
+ UserFactorDescriptions.binaryFeatureDescriptor(feature)) {
+ override fun compute(reader: BinaryFeatureReader): String {
+ return reader.calculateRatioByValue().getOrDefault(valueName, -1.0).toString()
+ }
+}
--- /dev/null
+package com.intellij.stats.personalization.impl
+
+import com.intellij.stats.personalization.UserFactorBase
+import com.intellij.stats.personalization.UserFactorDescriptions
+import com.intellij.stats.personalization.UserFactorReaderBase
+import com.intellij.stats.personalization.UserFactorUpdaterBase
+import com.jetbrains.completion.ranker.features.CatergorialFeature
+import com.jetbrains.completion.ranker.features.impl.FeatureUtils
+
+/**
+ * @author Vitaliy.Bibaev
+ */
+class CategoryFeatureReader(factor: DailyAggregatedDoubleFactor)
+ : UserFactorReaderBase(factor) {
+ fun calculateRatioByValue(): Map<String, Double> {
+ val sums = factor.aggregateSum()
+ val total = sums.values.sum()
+ if (total == 0.0) return emptyMap()
+ return sums.mapValues { e -> e.value / total }
+ }
+}
+
+class CategoryFeatureUpdater(private val knownCategories: Set<String>, factor: MutableDoubleFactor) : UserFactorUpdaterBase(factor) {
+ fun update(value: Any?) {
+ if (value == null) {
+ factor.incrementOnToday(FeatureUtils.UNDEFINED)
+ } else {
+ val category = value.toString()
+ if (category in knownCategories) {
+ factor.incrementOnToday(category)
+ } else {
+ factor.incrementOnToday(FeatureUtils.OTHER)
+ }
+ }
+ }
+}
+
+class CategoryRatio(feature: CatergorialFeature, private val categoryName: String)
+ : UserFactorBase<CategoryFeatureReader>("Category${feature.name}$categoryName",
+ UserFactorDescriptions.categoriealFeatureDescriptor(feature)) {
+ override fun compute(reader: CategoryFeatureReader): String {
+ return reader.calculateRatioByValue().getOrDefault(categoryName, -1.0).toString()
+ }
+}
+
+class MostFrequentCategory(feature: CatergorialFeature)
+ : UserFactorBase<CategoryFeatureReader>("MostFrequentCategory${feature.name}",
+ UserFactorDescriptions.categoriealFeatureDescriptor(feature)) {
+ override fun compute(reader: CategoryFeatureReader): String? {
+ return reader.calculateRatioByValue().maxBy { it.value }?.key
+ }
+}
fun getCompletionCountByType(type: CompletionType): Double =
factor.aggregateSum().getOrDefault(type.toString(), 0.0)
- fun getComletionCountByTypeOnToday(type: CompletionType): Double =
- factor.onToday().getOrDefault(type.toString(), 0.0)
-
fun getTotalCompletionCount(): Double = factor.aggregateSum().values.sum()
}
return if (total == 0.0) "0.0" else (reader.getCompletionCountByType(type) / total).toString()
}
}
-
if (old != null) {
val n = counts[key]!!.toDouble()
counts.computeIfPresent(key) { _, value -> value + 1 }
- (n / (n + 1)) * old + value / (n + 1)
+ FactorsUtil.mergeAverage(n.toInt(), old, 1, value)
} else {
counts[key] = 1
value
--- /dev/null
+package com.intellij.stats.personalization.impl
+
+import com.intellij.stats.personalization.*
+import com.jetbrains.completion.ranker.features.DoubleFeature
+import com.jetbrains.completion.ranker.features.impl.FeatureUtils
+
+/**
+ * @author Vitaliy.Bibaev
+ */
+class DoubleFeatureReader(factor: DailyAggregatedDoubleFactor)
+ : UserFactorReaderBase(factor) {
+ fun calculateAverageValue(): Double? {
+ return FactorsUtil.calculateAverageByAllDays(factor)
+ }
+
+ fun min(): Double? {
+ return factor.aggregateMin()["min"]
+ }
+
+ fun max(): Double? {
+ return factor.aggregateMax()["max"]
+ }
+
+ fun undefinedRatio(): Double? {
+ val sums = factor.aggregateSum()
+ val total = sums["count"] ?: return null
+ if (total == 0.0) return null
+
+ return sums.getOrDefault(FeatureUtils.UNDEFINED, 0.0) / total
+ }
+}
+
+class DoubleFeatureUpdater(factor: MutableDoubleFactor) : UserFactorUpdaterBase(factor) {
+ fun update(value: Any?) {
+ if (value == null) {
+ factor.incrementOnToday(FeatureUtils.UNDEFINED)
+ } else {
+ val doubleValue = value.asDouble()
+ factor.updateOnDate(DateUtil.today()) {
+ FactorsUtil.updateAverageValue(this, doubleValue)
+ compute("max", { _, old -> if (old == null) doubleValue else maxOf(old, doubleValue) })
+ compute("min", { _, old -> if (old == null) doubleValue else minOf(old, doubleValue) })
+ }
+ }
+ }
+
+ private fun Any.asDouble(): Double {
+ if (this is Number) return this.toDouble()
+ return this.toString().toDouble()
+ }
+}
+
+abstract class DoubleFeatureUserFactorBase(prefix: String, feature: DoubleFeature) :
+ UserFactorBase<DoubleFeatureReader>("${prefix}DoubleFeature${feature.name}$",
+ UserFactorDescriptions.doubleFeatureDescriptor(feature))
+
+class AverageDoubleFeatureValue(feature: DoubleFeature) : DoubleFeatureUserFactorBase("avg", feature) {
+ override fun compute(reader: DoubleFeatureReader): String? = reader.calculateAverageValue()?.toString()
+}
+
+class MinDoubleFeatureValue(feature: DoubleFeature) : DoubleFeatureUserFactorBase("min", feature) {
+ override fun compute(reader: DoubleFeatureReader): String? = reader.min()?.toString()
+}
+
+class MaxDoubleFeatureValue(feature: DoubleFeature) : DoubleFeatureUserFactorBase("max", feature) {
+ override fun compute(reader: DoubleFeatureReader): String? = reader.max()?.toString()
+}
+
+class UndefinedDoubleFeatureValueRatio(feature: DoubleFeature) : DoubleFeatureUserFactorBase("undefinedRatio", feature) {
+ override fun compute(reader: DoubleFeatureReader): String? = reader.undefinedRatio()?.toString()
+}
\ No newline at end of file
--- /dev/null
+package com.intellij.stats.personalization.impl
+
+/**
+ * @author Vitaliy.Bibaev
+ */
+object FactorsUtil {
+ fun mergeAverage(n1: Int, avg1: Double, n2: Int, avg2: Double): Double {
+ if (n1 == 0 && n2 == 0) return 0.0
+ val total = (n1 + n2).toDouble()
+ return (n1 / total) * avg1 + (n2 / total) * avg2
+ }
+
+ fun updateAverageValue(map: MutableMap<String, Double>, valueToAdd: Double) {
+ val count = map["count"]?.toInt()
+ val avg = map["average"]
+ if (count != null && avg != null) {
+ val newAverage = mergeAverage(1, valueToAdd, count, avg)
+ update(map, 1 + count, newAverage)
+ } else {
+ update(map, 1, valueToAdd)
+ }
+ }
+
+ fun calculateAverageByAllDays(factor: DailyAggregatedDoubleFactor): Double? {
+ var totalCount = 0
+ var average = 0.0
+ var present = false
+ for (onDate in factor.availableDates().mapNotNull { factor.onDate(it) }) {
+ val avg = onDate["average"]
+ val count = onDate["count"]?.toInt()
+ if (avg != null && count != null && count > 0) {
+ present = true
+ average = FactorsUtil.mergeAverage(totalCount, average, count, avg)
+ totalCount += count
+ }
+ }
+
+ return if (present) average else average
+ }
+
+
+ private fun update(map: MutableMap<String, Double>, count: Int, avg: Double) {
+ map["count"] = count.toDouble()
+ map["average"] = avg
+ }
+}
\ No newline at end of file
*/
class TimeBetweenTypingReader(factor: DailyAggregatedDoubleFactor) : UserFactorReaderBase(factor) {
fun averageTime(): Double? {
- var totalCount = 0
- var average = 0.0
- for (onDate in factor.availableDates().mapNotNull { factor.onDate(it) }) {
- val avg = onDate["average"]
- val count = onDate["count"]?.toInt()
- if (avg != null && count != null && count > 0) {
- average = mergeAverage(totalCount, average, count, avg)
- totalCount += count
- }
- }
-
- return average
+ return FactorsUtil.calculateAverageByAllDays(factor)
}
}
class TimeBetweenTypingUpdater(factor: MutableDoubleFactor) : UserFactorUpdaterBase(factor) {
fun fireTypingPerformed(delayMs: Int) {
factor.updateOnDate(DateUtil.today()) {
- val count = get("count")?.toInt()
- val avg = get("average")
- if (count != null && avg != null) {
- val newAverage = mergeAverage(1, delayMs.toDouble(), count, avg)
- update(this, 1 + count, newAverage)
- } else {
- update(this, 1, delayMs.toDouble())
- }
+ FactorsUtil.updateAverageValue(this, delayMs.toDouble())
}
}
-
- private fun update(map: MutableMap<String, Double>, count: Int, avg: Double) {
- map["count"] = count.toDouble()
- map["average"] = avg
- }
-}
-
-private fun mergeAverage(n1: Int, avg1: Double, n2: Int, avg2: Double): Double {
- if (n1 == 0 && n2 == 0) return 0.0
- val total = (n1 + n2).toDouble()
- return (n1 / total) * avg1 + (n2 / total) * avg2
}
class AverageTimeBetweenTyping
import com.intellij.openapi.diagnostic.Logger
import com.intellij.stats.personalization.UserFactor
import com.intellij.stats.personalization.UserFactorsManager
+import com.jetbrains.completion.ranker.features.BinaryFeature
+import com.jetbrains.completion.ranker.features.CatergorialFeature
+import com.jetbrains.completion.ranker.features.DoubleFeature
+import com.jetbrains.completion.ranker.features.impl.FeatureUtils
/**
* @author Vitaliy.Bibaev
}
private val userFactors = mutableMapOf<String, UserFactor>()
init {
- // TODO: register all factors here
- FeatureManagerImpl.getInstance() // TODO: register feature-derived factors
-
// user factors
register(ExplicitCompletionRatio())
register(CompletionTypeRatio(CompletionType.BASIC))
register(AverageTimeBetweenTyping())
register(MnemonicsRatio())
+
+ // feature-derived factors
+ val featureManager = FeatureManagerImpl.getInstance()
+ featureManager.binaryFactors.forEach(this::registerBinaryFeatureDerivedFactors)
+ featureManager.doubleFactors.forEach(this::registerDoubleFeatureDerivedFactors)
+ featureManager.categorialFactors.forEach(this::registerCategorialFeatureDerivedFactors)
+ }
+
+ private fun registerBinaryFeatureDerivedFactors(feature: BinaryFeature) {
+ register(BinaryValueRatio(feature, feature.availableValues.first))
+ register(BinaryValueRatio(feature, feature.availableValues.second))
+ }
+
+ private fun registerDoubleFeatureDerivedFactors(feature: DoubleFeature) {
+ register(MaxDoubleFeatureValue(feature))
+ register(MinDoubleFeatureValue(feature))
+ register(AverageDoubleFeatureValue(feature))
+ register(UndefinedDoubleFeatureValueRatio(feature))
+ }
+
+ private fun registerCategorialFeatureDerivedFactors(feature: CatergorialFeature) {
+ feature.categories.forEach { register(CategoryRatio(feature, it)) }
+ register(CategoryRatio(feature, FeatureUtils.OTHER))
+ register(MostFrequentCategory(feature))
}
override fun getAllFactors(): List<UserFactor> = userFactors.values.toList()
override fun getFactor(id: String): UserFactor = userFactors[id]!!
- override fun getFeatureFactor(featureName: String): UserFactor.FeatureFactor? {
- return null
- }
-
private fun register(factor: UserFactor) {
val old = userFactors.put(factor.id, factor)
if (old != null) {