fix(L1): обрезать отпечатки равномерно по тексту, а не произвольно
winnow() возвращает set, поэтому list(fp)[:LIMIT] брал случайное подмножество: у длинного документа целые куски оставались без отпечатков, и списывание именно из них не находилось. Обнаружено при разборе того, почему фрагмент статьи PMC не искался. Добавлены winnow_ordered() — отпечатки в порядке появления в тексте, и sample_evenly() — выборка каждого n-го элемента вместо первых N. Применено в add_document и store_full_text. 7 тестов на главное свойство: выборка растянута по всей длине документа, шаг ровный, порядок сохранён. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,9 @@ from app.algorithms.winnowing import (
|
||||
get_ngrams,
|
||||
hash_ngram,
|
||||
jaccard_similarity,
|
||||
sample_evenly,
|
||||
winnow,
|
||||
winnow_ordered,
|
||||
)
|
||||
|
||||
# Достаточно длинный текст, чтобы окно Winnowing реально отработало
|
||||
@@ -77,3 +79,57 @@ def test_compute_similarity_partial_overlap_is_between():
|
||||
modified = LONG + " добавлен ещё один совершенно новый хвост предложения здесь"
|
||||
sim = compute_similarity(LONG, modified)
|
||||
assert 0.0 < sim < 1.0
|
||||
|
||||
|
||||
# ─── Обрезка отпечатков по лимиту ────────────────────────────────────────────
|
||||
# Регрессия: winnow() возвращает set, и list(fp)[:limit] брал произвольное
|
||||
# подмножество — у длинного документа целые куски оставались без покрытия,
|
||||
# и списывание из них не находилось.
|
||||
|
||||
|
||||
def test_winnow_ordered_matches_winnow_by_content():
|
||||
"""Тот же набор отпечатков, что и у winnow, только с порядком."""
|
||||
assert set(winnow_ordered(LONG)) == winnow(LONG)
|
||||
|
||||
|
||||
def test_winnow_ordered_has_no_duplicates():
|
||||
ordered = winnow_ordered(LONG)
|
||||
assert len(ordered) == len(set(ordered))
|
||||
|
||||
|
||||
def test_winnow_ordered_follows_text_order():
|
||||
"""Отпечатки начала текста идут раньше отпечатков продолжения."""
|
||||
tail = " совершенно другой хвост про выпечку хлеба и закваску в тёплой печи"
|
||||
ordered = winnow_ordered(LONG + tail)
|
||||
head_prints = set(winnow_ordered(LONG))
|
||||
positions = [i for i, h in enumerate(ordered) if h in head_prints]
|
||||
# Отпечатки первой половины сосредоточены в начале списка, а не разбросаны
|
||||
assert max(positions) < len(ordered)
|
||||
assert positions[0] == 0
|
||||
|
||||
|
||||
def test_sample_evenly_keeps_everything_under_limit():
|
||||
items = [1, 2, 3]
|
||||
assert sample_evenly(items, 10) == items
|
||||
assert sample_evenly(items, 0) == items # 0 = без ограничения
|
||||
|
||||
|
||||
def test_sample_evenly_respects_limit():
|
||||
items = list(range(1000))
|
||||
assert len(sample_evenly(items, 100)) == 100
|
||||
|
||||
|
||||
def test_sample_evenly_covers_whole_document():
|
||||
"""Главное свойство: выборка растянута по всей длине, а не обрезана с начала."""
|
||||
items = list(range(1000))
|
||||
sampled = sample_evenly(items, 10)
|
||||
assert sampled[0] == 0
|
||||
assert sampled[-1] >= 900 # хвост документа тоже покрыт
|
||||
assert sampled == sorted(sampled) # порядок сохранён
|
||||
|
||||
|
||||
def test_sample_evenly_spreads_uniformly():
|
||||
items = list(range(100))
|
||||
sampled = sample_evenly(items, 10)
|
||||
gaps = [b - a for a, b in zip(sampled, sampled[1:], strict=False)]
|
||||
assert max(gaps) - min(gaps) <= 1 # шаг ровный
|
||||
|
||||
Reference in New Issue
Block a user