63 lines
2.6 KiB
Python
63 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
||
"""Полигон, пакет-7: KWIC-контексты вхождений термина по субстрату чанков.
|
||
|
||
Нужен дважды. (1) Разметка кандидатов не по памяти, а по тексту: любое утверждение «это имя
|
||
персонажа / это жанровый термин» обязано опираться на вхождения, иначе это угадывание. (2) Прототип
|
||
того, что роль ТЕРМИНОЛОГ получает на вход по D39.42 п.1 («KWIC-контексты вхождений из исходника»),
|
||
— здесь видно, сколько это стоит по символам, и это цифра для сметы фазы B.
|
||
|
||
$0, stdlib. Детерминирован: вхождения идут в порядке (chapter, chunk_idx, offset).
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
|
||
def load(path: Path) -> list[dict]:
|
||
return [json.loads(l) for l in path.open(encoding="utf-8") if l.strip()]
|
||
|
||
|
||
def kwic(rows: list[dict], needle: str, width: int, limit: int) -> list[tuple[int, int, str]]:
|
||
out = []
|
||
for r in rows:
|
||
src, pos = r["source"], 0
|
||
while True:
|
||
i = src.find(needle, pos)
|
||
if i < 0:
|
||
break
|
||
a, b = max(0, i - width), min(len(src), i + len(needle) + width)
|
||
frag = src[a:b].replace("\n", " ⏎ ")
|
||
out.append((r["chapter"], r["chunk_idx"], frag))
|
||
pos = i + len(needle)
|
||
if limit and len(out) >= limit:
|
||
return out
|
||
return out
|
||
|
||
|
||
def main() -> int:
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--chunks", required=True, type=Path)
|
||
ap.add_argument("--term", action="append", required=True)
|
||
ap.add_argument("--width", type=int, default=40)
|
||
ap.add_argument("--limit", type=int, default=6)
|
||
ap.add_argument("--stats", action="store_true", help="только объём контекстов (для сметы)")
|
||
a = ap.parse_args()
|
||
|
||
rows = load(a.chunks)
|
||
for t in a.term:
|
||
hits = kwic(rows, t, a.width, 0 if a.stats else a.limit)
|
||
if a.stats:
|
||
chars = sum(len(f) for _, _, f in hits)
|
||
print(f"{t}\tвхождений={len(hits)}\tсимволов_контекста={chars}")
|
||
continue
|
||
print(f"=== {t} ({len(hits)} показано, лимит {a.limit}) ===")
|
||
for ch, ci, frag in hits:
|
||
print(f" [гл.{ch}/{ci}] …{frag}…")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|