Skip to content

Commit

Permalink
Fixed score when using skips on non-daily habit
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxet1000 committed May 27, 2023
1 parent 499eb46 commit 19ab70b
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 3 deletions.
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -124,8 +149,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) {
Expand Down
Expand Up @@ -28,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

open class BaseScoreListTest : BaseUnitTest() {
protected lateinit var habit: Habit
Expand Down Expand Up @@ -145,6 +146,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
Expand Down Expand Up @@ -268,7 +356,7 @@ class YesNoScoreListTest : BaseScoreListTest() {

private fun addSkip(day: Int) {
val entries = habit.originalEntries
entries.add(Entry(today.minus(day), Entry.SKIP))
entries.add(Entry(today.minus(day), SKIP))
}
}

Expand Down

0 comments on commit 19ab70b

Please sign in to comment.