Skip to content

Commit

Permalink
Prevent card id collisions (#85)
Browse files Browse the repository at this point in the history
* wait 1ms to prevent potential card_id collision

* explicitly specify card_id when creating Card objects in optimizer to prevent time.sleep

* remove section warning about batch card creation in README

* bump patch 5.0.0 -> 5.0.1

* add unit test for unique card ids
  • Loading branch information
joshdavham authored Jan 29, 2025
1 parent b92abdf commit 99c9e66
Show file tree
Hide file tree
Showing 5 changed files with 16 additions and 25 deletions.
21 changes: 0 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,27 +172,6 @@ new_card = Card.from_dict(card_dict)
new_review_log = ReviewLog.from_dict(review_log_dict)
```

### Batch card creation

If you batch create `Card` objects, ensure that you leave at least 1 millisecond between creating each individual card

```python
from fsrs import Card
import time

cards = []
for i in range(100):

card = Card()

cards.append(card)

# wait 1 millisecond
time.sleep(0.001)
```

Each `Card` object has a `card_id` attribute which is the epoch milliseconds of when the card was created. In order to keep each of the card id's unique, two cards must not be created within 1 millisecond of eachother.

## Optimizer (optional)

If you have a collection of `ReviewLog` objects, you can optionally reuse them to compute an optimal set of parameters for the `Scheduler` to make it more accurate at scheduling reviews.
Expand Down
3 changes: 3 additions & 0 deletions fsrs/fsrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from copy import copy
from enum import IntEnum
from random import random
import time

DEFAULT_PARAMETERS = (
0.40255,
Expand Down Expand Up @@ -119,6 +120,8 @@ def __init__(
if card_id is None:
# epoch milliseconds of when the card was created
card_id = int(datetime.now(timezone.utc).timestamp() * 1000)
# wait 1ms to prevent potential card_id collision on next Card creation
time.sleep(0.001)
self.card_id = card_id

self.state = state
Expand Down
6 changes: 3 additions & 3 deletions fsrs/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def _compute_batch_loss(self, parameters: list[float]) -> float:
u_rating = review[0][1]

if i == 0:
card = Card(due=x_date)
card = Card(card_id=card_id, due=x_date)

y_pred_retrievability = card.get_retrievability(x_date)
y_retrievability = torch.tensor(
Expand Down Expand Up @@ -228,7 +228,7 @@ def _num_reviews() -> int:

# if this is the first review, create the Card object
if i == 0:
card = Card(due=review_datetime)
card = Card(card_id=card_id, due=review_datetime)

# only non-same-day reviews count
if (
Expand Down Expand Up @@ -322,7 +322,7 @@ def _update_parameters(

# if this is the first review, create the Card object
if i == 0:
card = Card(due=x_date)
card = Card(card_id=card_id, due=x_date)

# predicted target
y_pred_retrievability = card.get_retrievability(x_date)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "fsrs"
version = "5.0.0"
version = "5.0.1"
description = "Free Spaced Repetition Scheduler"
readme = "README.md"
authors = [
Expand Down
9 changes: 9 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -693,3 +693,12 @@ def test_class_repr(self):
card, review_log = scheduler.review_card(card=card, rating=Rating.Good)

assert str(review_log) == repr(review_log)

def test_unique_card_ids(self):
card_ids = []
for i in range(1000):
card = Card()
card_id = card.card_id
card_ids.append(card_id)

assert len(card_ids) == len(set(card_ids))

0 comments on commit 99c9e66

Please sign in to comment.