Skip to content

lang_tools.exercises.pair_matching

Pair matching exercise (from brazilian-bites).

Given N words, the user matches each one to its translation drawn from a shuffled column. Each submit call evaluates a single (left, right) tap.

Classes:

MissingTranslationError

MissingTranslationError(word: Word, target_language: str)

Bases: KeyError

Raised when a word does not carry a translation in the target language.

Initialize with the offending word and the missing target language.

Parameters:

  • word (Word) –

    The Word that lacks a translation.

  • target_language (str) –

    Target ISO 639-1 code that was requested.

Source code in src/lang_tools/exercises/pair_matching.py
def __init__(self, word: Word, target_language: str) -> None:
    """Initialize with the offending word and the missing target language.

    Args:
        word: The `Word` that lacks a translation.
        target_language: Target ISO 639-1 code that was requested.
    """
    super().__init__(
        f"Word {word.text!r} has no translation in {target_language!r}",
    )
    self.word = word
    self.target_language = target_language

PairMatchingExercise dataclass

PairMatchingExercise(
    target_language: str = "en",
    rng: Random | None = None,
    **kwargs: object,
)

Bases: _BaseExercise

Pair-matching round factory.

Attributes:

  • target_language (str) –

    ISO 639-1 code for the translations side.

  • rng (Random) –

    Optional random.Random for deterministic shuffles.

Initialize with the translation language and an optional RNG.

Parameters:

  • target_language (str, default: 'en' ) –

    ISO 639-1 code used to look up Word.translations.

  • rng (Random | None, default: None ) –

    Optional random.Random; defaults to random.SystemRandom.

  • **kwargs (object, default: {} ) –

    Forwarded to _BaseExercise.

Methods:

  • start

    Build a round from a list of words.

  • submit

    Score a single (left_word, right_word) tap.

Source code in src/lang_tools/exercises/pair_matching.py
def __init__(
    self,
    target_language: str = "en",
    rng: random.Random | None = None,
    **kwargs: object,
) -> None:
    """Initialize with the translation language and an optional RNG.

    Args:
        target_language: ISO 639-1 code used to look up `Word.translations`.
        rng: Optional `random.Random`; defaults to `random.SystemRandom`.
        **kwargs: Forwarded to `_BaseExercise`.
    """
    super().__init__(exercise_type="pair_matching", **kwargs)  # type: ignore[arg-type]
    self.target_language = target_language
    self.rng = rng or random.SystemRandom()

start

start(words: list[Word]) -> ExerciseRound

Build a round from a list of words.

Parameters:

  • words (list[Word]) –

    The words to match. Each must have a translation in target_language.

Returns:

  • ExerciseRound

    ExerciseRound whose prompt carries the left/right columns and

  • ExerciseRound

    expected the correct mapping {left_text: right_text}.

Raises:

Source code in src/lang_tools/exercises/pair_matching.py
def start(self, words: list[Word]) -> ExerciseRound:
    """Build a round from a list of words.

    Args:
        words: The words to match. Each must have a translation in
            `target_language`.

    Returns:
        `ExerciseRound` whose `prompt` carries the left/right columns and
        `expected` the correct mapping ``{left_text: right_text}``.

    Raises:
        MissingTranslationError: When any word lacks a translation in
            `target_language`.
    """
    self._ensure_started()
    pairs: dict[str, str] = {}
    word_by_text: dict[str, Word] = {}
    for word in words:
        translation = word.translations.get(self.target_language)
        if translation is None:
            raise MissingTranslationError(word, self.target_language)
        pairs[word.text] = translation
        word_by_text[word.text] = word

    right_column = list(pairs.values())
    self.rng.shuffle(right_column)
    return ExerciseRound(
        prompt={
            "left_words": list(pairs.keys()),
            "right_words": right_column,
        },
        expected={"pairs": pairs, "word_by_text": word_by_text},
    )

submit

submit(
    round_: ExerciseRound, selected_pair: tuple[str, str]
) -> RoundResult

Score a single (left_word, right_word) tap.

Parameters:

  • round_ (ExerciseRound) –

    The round returned by start.

  • selected_pair (tuple[str, str]) –

    The user's chosen (left, right) pair.

Returns:

  • RoundResult

    RoundResult reflecting whether the right value matches the

  • RoundResult

    expected translation for the left word. Includes one WordResult

  • RoundResult

    for the left word.

Source code in src/lang_tools/exercises/pair_matching.py
def submit(
    self,
    round_: ExerciseRound,
    selected_pair: tuple[str, str],
) -> RoundResult:
    """Score a single ``(left_word, right_word)`` tap.

    Args:
        round_: The round returned by `start`.
        selected_pair: The user's chosen ``(left, right)`` pair.

    Returns:
        `RoundResult` reflecting whether the right value matches the
        expected translation for the left word. Includes one `WordResult`
        for the left word.
    """
    left, right = selected_pair
    expected_pairs: dict[str, str] = round_.expected["pairs"]
    word_by_text: dict[str, Word] = round_.expected["word_by_text"]
    correct = expected_pairs.get(left) == right
    word_results: list[WordResult] = []
    if left in word_by_text:
        word_results.append(
            WordResult(word_id=word_by_text[left].id, correct=correct),
        )
    result = RoundResult(
        correct=correct,
        feedback=None if correct else "Wrong pair.",
        word_results=word_results,
    )
    self._bookkeep(result)
    return result