pull/1713/merge
Maxet1000 2 weeks ago committed by GitHub
commit c7a104af52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -61,6 +61,32 @@ 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 expands the interval by the number of skips found
* and repeats this process for the expanded part until no skips are found in an expanded part.
*/
@Synchronized
tailrec fun getNumberOfSkipsByInterval(
values: IntArray,
firstIndexCurrentInterval: Int,
lastIndexCurrentInterval: Int,
nbSkipsIntermedSol: Int = 0
): Int {
if (lastIndexCurrentInterval < firstIndexCurrentInterval) return nbSkipsIntermedSol
var nbOfSkips = 0
var nextLastIndex = lastIndexCurrentInterval
for (i in firstIndexCurrentInterval..lastIndexCurrentInterval) {
if (values[i] == Entry.SKIP) {
nbOfSkips++
if (lastIndexCurrentInterval + nbOfSkips < values.size) nextLastIndex++
}
}
return getNumberOfSkipsByInterval(values, lastIndexCurrentInterval + 1, nextLastIndex, nbSkipsIntermedSol + nbOfSkips)
}
/** /**
* Recomputes all scores between the provided [from] and [to] timestamps. * Recomputes all scores between the provided [from] and [to] timestamps.
*/ */
@ -125,8 +151,13 @@ 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)
val lastIndexForRollingSum = offset + denominator + nbOfSkips
if (lastIndexForRollingSum < values.size) {
if (values[lastIndexForRollingSum] == Entry.YES_MANUAL) {
if (values[offset] != Entry.SKIP) rollingSum -= 1.0
}
} }
} }
if (values[offset] != Entry.SKIP) { if (values[offset] != Entry.SKIP) {

@ -18,15 +18,19 @@
*/ */
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
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Entry.Companion.SKIP import org.isoron.uhabits.core.models.Entry.Companion.SKIP
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday 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 +149,96 @@ class YesNoScoreListTest : BaseScoreListTest() {
checkScoreValues(expectedValues) checkScoreValues(expectedValues)
} }
@Test
fun test_getNumberOfSkipsByInterval_NoSkips() {
val vars = intArrayOf(UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, SKIP, YES_MANUAL, YES_MANUAL,
UNKNOWN, YES_MANUAL, YES_MANUAL, UNKNOWN, YES_MANUAL, YES_MANUAL, UNKNOWN, SKIP, YES_MANUAL)
val nbOfSkips = habit.scores.getNumberOfSkipsByInterval(vars, 5, 13)
assertEquals(0, nbOfSkips)
}
@Test
fun test_getNumberOfSkipsByInterval_SkipsOnlyInInitialInterval() {
val vars = intArrayOf(UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, SKIP, YES_MANUAL, YES_MANUAL,
UNKNOWN, SKIP, SKIP, UNKNOWN, YES_MANUAL, YES_MANUAL, UNKNOWN, SKIP, YES_MANUAL)
val nbOfSkips = habit.scores.getNumberOfSkipsByInterval(vars, 4, 9)
assertEquals(3, nbOfSkips)
}
@Test
fun test_getNumberOfSkipsByInterval_SkipsInSubsequentIntervals() {
val vars = intArrayOf(UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, SKIP, YES_MANUAL, YES_MANUAL,
UNKNOWN, SKIP, SKIP, UNKNOWN, YES_MANUAL, YES_MANUAL, UNKNOWN, SKIP, YES_MANUAL)
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