Fixed score when using skips on non-daily habit

pull/1713/head
Maxet1000 2 years ago
parent fc9cc423d0
commit 393880a047

@ -1,6 +1,6 @@
plugins {
val kotlinVersion = "1.9.22"
id("com.android.application") version "8.4.0" apply (false)
id("com.android.application") version "8.5.1" apply (false)
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

@ -19,7 +19,7 @@
plugins {
id("com.github.triplet.play") version "3.8.6"
id("com.android.application") version "8.4.0"
id("com.android.application") version "8.5.1"
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.kapt")
id("org.jlleitschuh.gradle.ktlint")

@ -61,6 +61,31 @@ class ScoreList {
return result
}
/**
* Returns the number of skips after the offset in the interval used to calculate
* the percentage of completed days.
*
* If skips are found in the interval, it is expanded until the interval has the size of the
* sum of the denominator and the number of skips within the interval.
*/
@Synchronized
fun getNumberOfSkipsByInterval(
values: IntArray,
firstIndexToCheck: Int,
lastIndexToCheck: Int
): Int {
if (lastIndexToCheck < firstIndexToCheck) return 0
var nbOfSkips = 0
var nextLastIndex = lastIndexToCheck
for (i in firstIndexToCheck..lastIndexToCheck) {
if (values[i] == Entry.SKIP) {
nbOfSkips++
if (lastIndexToCheck + nbOfSkips < values.size) nextLastIndex++
}
}
return nbOfSkips + getNumberOfSkipsByInterval(values, lastIndexToCheck + 1, nextLastIndex)
}
/**
* Recomputes all scores between the provided [from] and [to] timestamps.
*/
@ -125,8 +150,12 @@ class ScoreList {
rollingSum += 1.0
}
if (offset + denominator < values.size) {
if (values[offset + denominator] == Entry.YES_MANUAL) {
rollingSum -= 1.0
val nbOfSkips =
getNumberOfSkipsByInterval(values, offset, offset + denominator)
if (offset + denominator + nbOfSkips < values.size) {
if (values[offset + denominator + nbOfSkips] == Entry.YES_MANUAL) {
if (values[offset] != Entry.SKIP) rollingSum -= 1.0
}
}
}
if (values[offset] != Entry.SKIP) {

@ -18,6 +18,7 @@
*/
package org.isoron.uhabits.core.models
import junit.framework.Assert.assertTrue
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.number.IsCloseTo
import org.hamcrest.number.OrderingComparison
@ -27,6 +28,7 @@ import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
import org.junit.Before
import org.junit.Test
import java.util.ArrayList
import kotlin.test.assertEquals
import kotlin.test.assertTrue
open class BaseScoreListTest : BaseUnitTest() {
@ -145,6 +147,93 @@ class YesNoScoreListTest : BaseScoreListTest() {
checkScoreValues(expectedValues)
}
@Test
fun test_getNumberOfSkipsByInterval_NoSkips() {
val vars = intArrayOf(-1, -1, -1, -1, 3, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, 2)
val nbOfSkips = habit.scores.getNumberOfSkipsByInterval(vars, 5, 13)
assertEquals(0, nbOfSkips)
}
@Test
fun test_getNumberOfSkipsByInterval_SkipsOnlyInInitialInterval() {
val vars = intArrayOf(-1, -1, -1, -1, 3, 2, 2, -1, 3, 3, -1, 2, 2, -1, 3, 2)
val nbOfSkips = habit.scores.getNumberOfSkipsByInterval(vars, 4, 9)
assertEquals(3, nbOfSkips)
}
@Test
fun test_getNumberOfSkipsByInterval_SkipsInSubsequentIntervals() {
val vars = intArrayOf(-1, -1, -1, -1, 3, 2, 2, -1, 3, 3, -1, 2, 2, -1, 3, 2)
val nbOfSkips = habit.scores.getNumberOfSkipsByInterval(vars, 4, 11)
assertEquals(4, nbOfSkips)
}
@Test
fun test_getValueNonDailyWithSkip() {
habit.frequency = Frequency(6, 7)
check(0, 18)
addSkip(10)
addSkip(11)
addSkip(12)
habit.recompute()
val expectedValues = doubleArrayOf(
0.365222,
0.333100,
0.299354,
0.263899,
0.226651,
0.191734,
0.159268,
0.129375,
0.102187,
0.077839,
0.056477,
0.056477,
0.056477,
0.056477,
0.038251,
0.023319,
0.011848,
0.004014,
0.000000,
0.000000,
0.000000
)
checkScoreValues(expectedValues)
}
@Test
fun test_perfectDailyWithSkips() {
// If the habit is performed daily and the user always either completes or
// skips the habit, score should converge to 100%.
habit.frequency = Frequency(1, 1)
val values = ArrayList<Int>()
check(0, 500)
for (k in 0..99) {
addSkip(7 * k + 5)
addSkip(7 * k + 6)
}
habit.recompute()
check(values)
assertThat(habit.scores[today].value, IsCloseTo.closeTo(1.0, E))
}
@Test
fun test_perfectNonDailyWithSkips() {
// If the habit is performed six times per week and the user always either completes or
// skips the habit, score should converge to 100%.
habit.frequency = Frequency(6, 7)
val values = ArrayList<Int>()
check(0, 500)
for (k in 0..99) {
addSkip(7 * k + 5)
addSkip(7 * k + 6)
}
habit.recompute()
check(values)
assertThat(habit.scores[today].value, IsCloseTo.closeTo(1.0, E))
}
@Test
fun test_imperfectNonDaily() {
// If the habit should be performed 3 times per week and the user misses 1 repetition

Loading…
Cancel
Save