summaryrefslogtreecommitdiffstats
path: root/src/scheduler_brutal.py
blob: a476932a3f469220e0047f2274b047a444e8efc5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
"""
"""

from scheduler import Scheduler
from card import Card
from random import shuffle

HISTORY_DEPTH = 8

class SchedulerBrutal(Scheduler):
    def __init__(self, cards: dict[str, Card], state: dict):
        self._cards = cards
        self._state = {}

        # Synchronise state with current card collection
        for id, card in self._cards.items():
            history = state.get(id, [None] * HISTORY_DEPTH)

            # Adjust history if depth has changed
            if len(history) > HISTORY_DEPTH:
                history = history[-HISTORY_DEPTH:]
            elif len(history) < HISTORY_DEPTH:
                history = ([None] * (HISTORY_DEPTH - len(history))) + history

            self._state[id] = history

    def practice(self, size: int) -> list[str]:
        return self._schedule(size)

    def test(self, size: int) -> list[str]:
        return self._schedule(size)

    def update(self, results: dict[str, int]) -> None:
        # Add card result to sliding window, or None if card was not shown
        self._state = {id: history[1:] + [results.get(id, None)] 
                       for id, history in self._state.items()}

    def getState(self) -> dict:
        return self._state

    # Consolidation index is a measure of how well the card has been memorised
    @staticmethod
    def _consolidationIndex(history: list, weights: range) -> float:
        relevant_history = [(h, w) for h, w in zip(history, weights) if h is not None]
        weighted_history = sum([h * w for h, w in relevant_history])
        total_weights = sum([w for h, w in relevant_history])
        return weighted_history / total_weights if total_weights > 0 else 0.0

    # Exposure index is a measure of how much and how recently a card has been shown
    @staticmethod
    def _exposureIndex(history: list) -> float:
        return sum([i + 1 for i, h in enumerate(history) if h is not None])

    def _schedule(self, size: int) -> list[str]:
        weights = range(10, 10 + HISTORY_DEPTH)
        cards = [id for id, card in self._cards.items()]

        # First sort by consolidation index
        cards.sort(key=lambda id: SchedulerBrutal._consolidationIndex(self._state[id], weights))

        # Next sort by exposure index
        cards.sort(key=lambda id: SchedulerBrutal._exposureIndex(self._state[id]))

        # Return least exposed and least consolidated cards, shuffled
        cards = cards[0:size]

        shuffle(cards)

        return cards