mirror of https://github.com/iSoron/uhabits.git
parent
9bc0f44777
commit
c023711d16
@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by the
|
|
||||||
* Free Software Foundation, either version 3 of the License, or (at your
|
|
||||||
* option) any later version.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.core.models;
|
|
||||||
|
|
||||||
import androidx.annotation.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static org.isoron.uhabits.core.models.Entry.*;
|
|
||||||
|
|
||||||
public class ScoreList
|
|
||||||
{
|
|
||||||
private final HashMap<Timestamp, Score> list = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the score for a given day. If the timestamp given happens before the first
|
|
||||||
* repetition of the habit or after the last computed score, returns a score with value zero.
|
|
||||||
*/
|
|
||||||
public final synchronized Score get(Timestamp timestamp)
|
|
||||||
{
|
|
||||||
if (list.containsKey(timestamp)) return list.get(timestamp);
|
|
||||||
return new Score(timestamp, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of scores that fall within the given interval.
|
|
||||||
* <p>
|
|
||||||
* There is exactly one score per day in the interval. The endpoints of
|
|
||||||
* the interval are included. The list is ordered by timestamp (decreasing).
|
|
||||||
* That is, the first score corresponds to the newest timestamp, and the
|
|
||||||
* last score corresponds to the oldest timestamp.
|
|
||||||
*
|
|
||||||
* @param fromTimestamp timestamp of the beginning of the interval.
|
|
||||||
* @param toTimestamp timestamp of the end of the interval.
|
|
||||||
* @return the list of scores within the interval.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public List<Score> getByInterval(@NonNull Timestamp fromTimestamp,
|
|
||||||
@NonNull Timestamp toTimestamp)
|
|
||||||
{
|
|
||||||
List<Score> result = new LinkedList<>();
|
|
||||||
if (fromTimestamp.isNewerThan(toTimestamp)) return result;
|
|
||||||
Timestamp current = toTimestamp;
|
|
||||||
while(!current.isOlderThan(fromTimestamp))
|
|
||||||
{
|
|
||||||
result.add(get(current));
|
|
||||||
current = current.minus(1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void recompute(
|
|
||||||
Frequency frequency,
|
|
||||||
boolean isNumerical,
|
|
||||||
double targetValue,
|
|
||||||
EntryList computedEntries,
|
|
||||||
Timestamp from,
|
|
||||||
Timestamp to)
|
|
||||||
{
|
|
||||||
list.clear();
|
|
||||||
if (computedEntries.getKnown().isEmpty()) return;
|
|
||||||
if (from.isNewerThan(to)) return;
|
|
||||||
|
|
||||||
double rollingSum = 0.0;
|
|
||||||
int numerator = frequency.getNumerator();
|
|
||||||
int denominator = frequency.getDenominator();
|
|
||||||
final double freq = frequency.toDouble();
|
|
||||||
final Integer[] values = computedEntries
|
|
||||||
.getByInterval(from, to)
|
|
||||||
.stream()
|
|
||||||
.map(Entry::getValue)
|
|
||||||
.toArray(Integer[]::new);
|
|
||||||
|
|
||||||
// For non-daily boolean habits, we double the numerator and the denominator to smooth
|
|
||||||
// out irregular repetition schedules (for example, weekly habits performed on different
|
|
||||||
// days of the week)
|
|
||||||
if (!isNumerical && freq < 1.0)
|
|
||||||
{
|
|
||||||
numerator *= 2;
|
|
||||||
denominator *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
double previousValue = 0;
|
|
||||||
for (int i = 0; i < values.length; i++)
|
|
||||||
{
|
|
||||||
int offset = values.length - i - 1;
|
|
||||||
if (isNumerical)
|
|
||||||
{
|
|
||||||
rollingSum += values[offset];
|
|
||||||
if (offset + denominator < values.length)
|
|
||||||
{
|
|
||||||
rollingSum -= values[offset + denominator];
|
|
||||||
}
|
|
||||||
double percentageCompleted = Math.min(1, rollingSum / 1000 / targetValue);
|
|
||||||
previousValue = Score.compute(freq, previousValue, percentageCompleted);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (values[offset] == YES_MANUAL)
|
|
||||||
rollingSum += 1.0;
|
|
||||||
if (offset + denominator < values.length)
|
|
||||||
if (values[offset + denominator] == YES_MANUAL)
|
|
||||||
rollingSum -= 1.0;
|
|
||||||
if (values[offset] != SKIP)
|
|
||||||
{
|
|
||||||
double percentageCompleted = Math.min(1, rollingSum / numerator);
|
|
||||||
previousValue = Score.compute(freq, previousValue, percentageCompleted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Timestamp timestamp = from.plus(i);
|
|
||||||
list.put(timestamp, new Score(timestamp, previousValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.core.models
|
||||||
|
|
||||||
|
import org.isoron.uhabits.core.models.Score.Companion.compute
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.*
|
||||||
|
|
||||||
|
class ScoreList {
|
||||||
|
|
||||||
|
private val map = HashMap<Timestamp, Score>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the score for a given day. If the timestamp given happens before the first
|
||||||
|
* repetition of the habit or after the last computed score, returns a score with value zero.
|
||||||
|
*/
|
||||||
|
operator fun get(timestamp: Timestamp): Score {
|
||||||
|
return map[timestamp] ?: Score(timestamp, 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of scores that fall within the given interval.
|
||||||
|
*
|
||||||
|
* There is exactly one score per day in the interval. The endpoints of the interval are
|
||||||
|
* included. The list is ordered by timestamp (decreasing). That is, the first score
|
||||||
|
* corresponds to the newest timestamp, and the last score corresponds to the oldest timestamp.
|
||||||
|
*/
|
||||||
|
fun getByInterval(
|
||||||
|
fromTimestamp: Timestamp,
|
||||||
|
toTimestamp: Timestamp,
|
||||||
|
): List<Score> {
|
||||||
|
val result: MutableList<Score> = ArrayList()
|
||||||
|
if (fromTimestamp.isNewerThan(toTimestamp)) return result
|
||||||
|
var current = toTimestamp
|
||||||
|
while (!current.isOlderThan(fromTimestamp)) {
|
||||||
|
result.add(get(current))
|
||||||
|
current = current.minus(1)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recomputes all scores between the provided [from] and [to] timestamps.
|
||||||
|
*/
|
||||||
|
fun recompute(
|
||||||
|
frequency: Frequency,
|
||||||
|
isNumerical: Boolean,
|
||||||
|
targetValue: Double,
|
||||||
|
computedEntries: EntryList,
|
||||||
|
from: Timestamp,
|
||||||
|
to: Timestamp,
|
||||||
|
) {
|
||||||
|
map.clear()
|
||||||
|
if (computedEntries.getKnown().isEmpty()) return
|
||||||
|
if (from.isNewerThan(to)) return
|
||||||
|
var rollingSum = 0.0
|
||||||
|
var numerator = frequency.numerator
|
||||||
|
var denominator = frequency.denominator
|
||||||
|
val freq = frequency.toDouble()
|
||||||
|
val values = computedEntries.getByInterval(from, to).map { it.value }.toIntArray()
|
||||||
|
|
||||||
|
// For non-daily boolean habits, we double the numerator and the denominator to smooth
|
||||||
|
// out irregular repetition schedules (for example, weekly habits performed on different
|
||||||
|
// days of the week)
|
||||||
|
if (!isNumerical && freq < 1.0) {
|
||||||
|
numerator *= 2
|
||||||
|
denominator *= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousValue = 0.0
|
||||||
|
for (i in values.indices) {
|
||||||
|
val offset = values.size - i - 1
|
||||||
|
if (isNumerical) {
|
||||||
|
rollingSum += values[offset]
|
||||||
|
if (offset + denominator < values.size) {
|
||||||
|
rollingSum -= values[offset + denominator]
|
||||||
|
}
|
||||||
|
val percentageCompleted = min(1.0, rollingSum / 1000 / targetValue)
|
||||||
|
previousValue = compute(freq, previousValue, percentageCompleted)
|
||||||
|
} else {
|
||||||
|
if (values[offset] == Entry.YES_MANUAL) {
|
||||||
|
rollingSum += 1.0
|
||||||
|
}
|
||||||
|
if (offset + denominator < values.size) {
|
||||||
|
if (values[offset + denominator] == Entry.YES_MANUAL) {
|
||||||
|
rollingSum -= 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (values[offset] != Entry.SKIP) {
|
||||||
|
val percentageCompleted = Math.min(1.0, rollingSum / numerator)
|
||||||
|
previousValue = compute(freq, previousValue, percentageCompleted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val timestamp = from.plus(i)
|
||||||
|
map[timestamp] = Score(timestamp, previousValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue