Support feature derived user factors
authorVitaliy.Bibaev <vitaliy.bibaev@jetbrains.com>
Wed, 13 Dec 2017 18:48:07 +0000 (21:48 +0300)
committerVitaliy.Bibaev <vitaliy.bibaev@jetbrains.com>
Wed, 13 Dec 2017 18:48:07 +0000 (21:48 +0300)
12 files changed:
plugins/stats-collector/src/com/intellij/stats/completion/LookupCompletedTracker.kt
plugins/stats-collector/src/com/intellij/stats/personalization/UserFactor.kt
plugins/stats-collector/src/com/intellij/stats/personalization/UserFactorDescriptions.kt
plugins/stats-collector/src/com/intellij/stats/personalization/UserFactorsManager.kt
plugins/stats-collector/src/com/intellij/stats/personalization/impl/BinaryFeatureFactors.kt [new file with mode: 0644]
plugins/stats-collector/src/com/intellij/stats/personalization/impl/CategorialFeatureFactors.kt [new file with mode: 0644]
plugins/stats-collector/src/com/intellij/stats/personalization/impl/CompletionTypeFactors.kt
plugins/stats-collector/src/com/intellij/stats/personalization/impl/DailyAggregatedDoubleFactor.kt
plugins/stats-collector/src/com/intellij/stats/personalization/impl/DoubleFeatureFactors.kt [new file with mode: 0644]
plugins/stats-collector/src/com/intellij/stats/personalization/impl/FactorsUtil.kt [new file with mode: 0644]
plugins/stats-collector/src/com/intellij/stats/personalization/impl/TimeBetweenTypingFactors.kt
plugins/stats-collector/src/com/intellij/stats/personalization/impl/UserFactorsManagerImpl.kt

index 507f56a2df3074d960000c73d97da35e853dd97b..514e2653c0f370c17f6b74812648dd8e316863ae 100644 (file)
@@ -20,9 +20,9 @@ import com.intellij.codeInsight.lookup.LookupAdapter
 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
@@ -49,8 +49,28 @@ class LookupCompletedTracker : LookupAdapter() {
         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) {
index 1f34129611637bdd02493735412a2775e7256cfb..8bc4aae9b39a219e7e6984039e0e5d038ec311f3 100644 (file)
@@ -9,8 +9,4 @@ interface UserFactor {
     val id: String
 
     fun compute(storage: UserFactorStorage): String?
-
-    interface FeatureFactor : UserFactor {
-        fun update(value: Any?)
-    }
 }
index df72c19a152a3923fdb293376743fdc44286f892..d93b1fef61a776282d88f5e8d68cc12a910c50de 100644 (file)
@@ -1,6 +1,9 @@
 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
@@ -15,6 +18,20 @@ object UserFactorDescriptions {
     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,
index 6b331cc02d8980411c03a98a2030ea0788571b74..c03731337c49ee9e55729a030bb46a6bb8ff0b18 100644 (file)
@@ -16,7 +16,5 @@ interface UserFactorsManager {
 
   fun getAllFactors(): List<UserFactor>
 
-  fun getFeatureFactor(featureName: String): UserFactor.FeatureFactor?
-
   fun getFactor(id: String): UserFactor
 }
diff --git a/plugins/stats-collector/src/com/intellij/stats/personalization/impl/BinaryFeatureFactors.kt b/plugins/stats-collector/src/com/intellij/stats/personalization/impl/BinaryFeatureFactors.kt
new file mode 100644 (file)
index 0000000..5b9fbbe
--- /dev/null
@@ -0,0 +1,35 @@
+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()
+    }
+}
diff --git a/plugins/stats-collector/src/com/intellij/stats/personalization/impl/CategorialFeatureFactors.kt b/plugins/stats-collector/src/com/intellij/stats/personalization/impl/CategorialFeatureFactors.kt
new file mode 100644 (file)
index 0000000..265f85c
--- /dev/null
@@ -0,0 +1,52 @@
+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
+    }
+}
index 3e6d6f3d5fd09a3003c7a72cdfe9de66db1ac47b..5b3f6c58cf722884628c02f4a0b3a9e51da68fcd 100644 (file)
@@ -10,9 +10,6 @@ class CompletionTypeReader(private val factor: DailyAggregatedDoubleFactor) : Fa
     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()
 }
 
@@ -31,4 +28,3 @@ class CompletionTypeRatio(private val type: CompletionType) : UserFactor {
         return if (total == 0.0) "0.0" else (reader.getCompletionCountByType(type) / total).toString()
     }
 }
-
index c41cd05f39df240a5cb6f721f8b37969a439b1bb..0ed3fcfbed52050a5137ca264135e2a8920b960e 100644 (file)
@@ -49,7 +49,7 @@ fun DailyAggregatedDoubleFactor.aggregateAverage(): Map<String, Double> {
                 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
diff --git a/plugins/stats-collector/src/com/intellij/stats/personalization/impl/DoubleFeatureFactors.kt b/plugins/stats-collector/src/com/intellij/stats/personalization/impl/DoubleFeatureFactors.kt
new file mode 100644 (file)
index 0000000..1f72359
--- /dev/null
@@ -0,0 +1,71 @@
+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
diff --git a/plugins/stats-collector/src/com/intellij/stats/personalization/impl/FactorsUtil.kt b/plugins/stats-collector/src/com/intellij/stats/personalization/impl/FactorsUtil.kt
new file mode 100644 (file)
index 0000000..6f7dfd3
--- /dev/null
@@ -0,0 +1,46 @@
+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
index 7177df1cdc996c538763e9e779461ec3c5180cdb..91bfde4d7b4236b890f4cfbebaee915ac9e0456b 100644 (file)
@@ -7,45 +7,16 @@ import com.intellij.stats.personalization.*
  */
 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
index 75ac5db6ffba0a32cac062219a8e37805b18781d..ed919e5670f4b8ab2f270bae02f96d43d60c4af0 100644 (file)
@@ -6,6 +6,10 @@ import com.intellij.openapi.components.ProjectComponent
 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
@@ -16,9 +20,6 @@ class UserFactorsManagerImpl : UserFactorsManager, ProjectComponent {
     }
     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))
@@ -39,6 +40,30 @@ class UserFactorsManagerImpl : UserFactorsManager, ProjectComponent {
         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()
@@ -47,10 +72,6 @@ class UserFactorsManagerImpl : UserFactorsManager, ProjectComponent {
 
     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) {