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 { plugins {
val kotlinVersion = "1.9.22" 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.android") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false) id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false) id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

@ -19,7 +19,7 @@
plugins { plugins {
id("com.github.triplet.play") version "3.8.6" 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.android")
id("org.jetbrains.kotlin.kapt") id("org.jetbrains.kotlin.kapt")
id("org.jlleitschuh.gradle.ktlint") id("org.jlleitschuh.gradle.ktlint")

@ -61,6 +61,31 @@ class ScoreList {
return result 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. * Recomputes all scores between the provided [from] and [to] timestamps.
*/ */
@ -125,8 +150,12 @@ class ScoreList {
rollingSum += 1.0 rollingSum += 1.0
} }
if (offset + denominator < values.size) { if (offset + denominator < values.size) {
if (values[offset + denominator] == Entry.YES_MANUAL) { val nbOfSkips =
rollingSum -= 1.0 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) { if (values[offset] != Entry.SKIP) {

@ -18,6 +18,7 @@
*/ */
package org.isoron.uhabits.core.models package org.isoron.uhabits.core.models
import junit.framework.Assert.assertTrue
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.number.IsCloseTo import org.hamcrest.number.IsCloseTo
import org.hamcrest.number.OrderingComparison 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.Before
import org.junit.Test import org.junit.Test
import java.util.ArrayList import java.util.ArrayList
import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
open class BaseScoreListTest : BaseUnitTest() { open class BaseScoreListTest : BaseUnitTest() {
@ -145,6 +147,93 @@ class YesNoScoreListTest : BaseScoreListTest() {
checkScoreValues(expectedValues) 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 @Test
fun test_imperfectNonDaily() { fun test_imperfectNonDaily() {
// If the habit should be performed 3 times per week and the user misses 1 repetition // If the habit should be performed 3 times per week and the user misses 1 repetition

Loading…
Cancel
Save