初始化项目,由ModelHub XC社区提供模型
Model: rpant/iolai26-solve Source: Original Platform
This commit is contained in:
98
solver/analogy.py
Normal file
98
solver/analogy.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""Proportional analogy: solve a : b :: c : x at the string level.
|
||||
|
||||
Used for (1) generating unseen inflected forms from paradigm neighbors and
|
||||
(2) the chrF-floor fallback. Transformation model: a -> b is a prefix and/or
|
||||
suffix replacement around a shared core, which covers concatenative
|
||||
morphology. The same edit is applied to c. All consistent answers are
|
||||
returned, ranked by preserved stem material.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
# A rule is (a_pre, b_pre, a_suf, b_suf): replace prefix a_pre with b_pre and
|
||||
# suffix a_suf with b_suf.
|
||||
Rule = Tuple[str, str, str, str]
|
||||
|
||||
|
||||
def _lcp(a: str, b: str) -> int:
|
||||
i = 0
|
||||
while i < min(len(a), len(b)) and a[i] == b[i]:
|
||||
i += 1
|
||||
return i
|
||||
|
||||
|
||||
def _lcsuf(a: str, b: str) -> int:
|
||||
i = 0
|
||||
while i < min(len(a), len(b)) and a[-1 - i] == b[-1 - i]:
|
||||
i += 1
|
||||
return i
|
||||
|
||||
|
||||
def edit_rules(a: str, b: str) -> List[Rule]:
|
||||
"""Candidate decompositions of the transformation a -> b."""
|
||||
rules: List[Rule] = []
|
||||
p = _lcp(a, b)
|
||||
s = _lcsuf(a, b)
|
||||
if p > 0:
|
||||
rules.append(("", "", a[p:], b[p:])) # keep shared prefix, swap suffix
|
||||
if s > 0:
|
||||
rules.append((a[: len(a) - s], b[: len(b) - s], "", "")) # swap prefix
|
||||
if p > 0 and s > 0 and p + s <= min(len(a), len(b)):
|
||||
# circumfix-ish: shared prefix AND suffix, swap the middle — model as
|
||||
# suffix swap on the part after the shared prefix
|
||||
rules.append(("", "", a[p : len(a) - s], b[p : len(b) - s]))
|
||||
if not rules:
|
||||
rules.append((a, b, "", "")) # suppletion: whole-string replacement
|
||||
return rules
|
||||
|
||||
|
||||
def apply_rule(rule: Rule, c: str) -> Optional[str]:
|
||||
a_pre, b_pre, a_suf, b_suf = rule
|
||||
out = c
|
||||
if a_pre and not out.startswith(a_pre):
|
||||
return None
|
||||
out = b_pre + out[len(a_pre):]
|
||||
if a_suf:
|
||||
if not out.endswith(a_suf):
|
||||
return None
|
||||
out = out[: len(out) - len(a_suf)] + b_suf
|
||||
else:
|
||||
out = out + b_suf
|
||||
return out
|
||||
|
||||
|
||||
def apply_rule_mid(rule: Rule, c: str) -> Optional[str]:
|
||||
"""Apply a middle-swap rule (encoded as suffix-swap) as an infix
|
||||
substitution when the plain application fails: replace the last
|
||||
occurrence of a_suf inside c."""
|
||||
_, _, a_mid, b_mid = rule
|
||||
if not a_mid or a_mid not in c:
|
||||
return None
|
||||
i = c.rfind(a_mid)
|
||||
return c[:i] + b_mid + c[i + len(a_mid):]
|
||||
|
||||
|
||||
def solve(a: str, b: str, c: str) -> List[str]:
|
||||
"""Candidate solutions x to a : b :: c : x, best first."""
|
||||
scored: Counter = Counter()
|
||||
for rule in edit_rules(a, b):
|
||||
x = apply_rule(rule, c)
|
||||
if x is None:
|
||||
x = apply_rule_mid(rule, c)
|
||||
if x:
|
||||
stem_kept = len(c) - len(rule[0]) - len(rule[2])
|
||||
scored[x] = max(scored[x], stem_kept)
|
||||
return [w for w, _ in scored.most_common()]
|
||||
|
||||
|
||||
def solve_from_pairs(pairs: List[Tuple[str, str]], c: str) -> List[str]:
|
||||
"""Given attested (form_a, form_b) pairs exhibiting one transformation,
|
||||
vote for the best x completing c : x under that transformation."""
|
||||
votes: Counter = Counter()
|
||||
for a, b in pairs:
|
||||
for rank, x in enumerate(solve(a, b, c)):
|
||||
votes[x] += 1.0 / (1 + rank)
|
||||
return [w for w, _ in votes.most_common()]
|
||||
Reference in New Issue
Block a user