sourojitdotcom commited on
Commit
9aba9fd
·
verified ·
1 Parent(s): 2d6d197

fix: ship bhc/casual.py (translit2.py imports it; the python/ tree could not be imported)

Browse files
Files changed (1) hide show
  1. python/bhc/casual.py +174 -0
python/bhc/casual.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Casual / chat-romanization keys (AksharaLP v2).
2
+
3
+ Two lossy keys are computed for a roman token and for a Devanagari word; a
4
+ casual spelling matches the intended word when the keys collide.
5
+ K1: consonant classes (aspiration, nukta, sibilants merged), vowel classes
6
+ (length merged, ai->e, au->o), every 'a'/schwa removed except word
7
+ initial, nasal dropped, repeats collapsed.
8
+ K2: K1 with all vowels removed (chat consonant-only spellings: bht, thk).
9
+ The index is built at load time from the lexicon (0 bytes stored).
10
+ Same algorithm is ported 1:1 to JS (release/runtime) - keep in sync.
11
+ """
12
+ import re
13
+
14
+ ASP = set("kgjtdpbc")
15
+ # Devanagari -> key class (nukta ignored except ड़/ढ़ -> R)
16
+ _DC = {}
17
+ for chars, k in [("कख", "k"), ("गघ", "g"), ("चछ", "c"), ("जझ", "j"), ("टठतथ", "t"),
18
+ ("डढदध", "d"), ("णनऩ", "n"), ("पफ", "p"), ("बभ", "b"), ("म", "m"),
19
+ ("य", "y"), ("रऱ", "r"), ("लळ", "l"), ("व", "v"), ("शषस", "s"), ("ह", "h")]:
20
+ for c in chars: _DC[c] = k
21
+ for c, k in zip("\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f", "kkgjRRpy"): _DC[c] = k
22
+ _DV = {}
23
+ for chars, k in [("अआा", "a"), ("इईिी", "i"), ("उऊुू", "u"), ("एऐेैॅऍ", "e"), ("ओऔऑोौॉ", "o"), ("ऋृ", "r")]:
24
+ for c in chars: _DV[c] = k
25
+ VOW = set("aeiou")
26
+
27
+
28
+ def _fin(s):
29
+ # drop non-initial a, collapse repeats
30
+ out = []
31
+ for i, ch in enumerate(s):
32
+ if ch == "a" and i > 0: continue
33
+ if out and out[-1] == ch: continue
34
+ out.append(ch)
35
+ return "".join(out)
36
+
37
+
38
+ def k2_of(k1):
39
+ s = k1[0] + "".join(c for c in k1[1:] if c not in VOW) if k1 else ""
40
+ if s and s[0] in VOW: s = s[1:] or s
41
+ out = []
42
+ for ch in s:
43
+ if out and out[-1] == ch: continue
44
+ out.append(ch)
45
+ return "".join(out)
46
+
47
+
48
+ def dev_keys(w):
49
+ """K1 keys of a Devanagari word (2 if it has ड़/ढ़: as d and as r)."""
50
+ raw = []
51
+ for i, ch in enumerate(w):
52
+ if ch == "\u093c":
53
+ if raw and raw[-1] == "d" and i > 0 and w[i - 1] in "डढ": raw[-1] = "R"
54
+ continue
55
+ if ch in _DC: raw.append(_DC[ch])
56
+ elif ch in _DV: raw.append(_DV[ch])
57
+ s = "".join(raw)
58
+ if not s: return []
59
+ ks = {_fin(s.replace("R", "d")), _fin(s.replace("R", "r"))}
60
+ return sorted(k for k in ks if k)
61
+
62
+
63
+ def _norm(t):
64
+ t = t.lower()
65
+ t = re.sub(r"[^a-z]", "", t)
66
+ return t
67
+
68
+
69
+ def rom_keys(t, cap=12):
70
+ """K1 keys of a roman token, expanded over ambiguous casual rules."""
71
+ t = _norm(t)
72
+ if not t: return [], []
73
+ t = re.sub(r"(.)\1{2,}", r"\1", t) # emphasis repeats
74
+ # deterministic letter merges
75
+ t = t.replace("chh", "c").replace("cch", "c").replace("ch", "c").replace("sh", "s")
76
+ t = t.replace("x", "ks").replace("q", "k").replace("z", "j").replace("f", "p").replace("w", "v")
77
+ # (unit list) each position: list of alternatives
78
+ alts = []
79
+ i = 0; L = len(t)
80
+ while i < L:
81
+ c = t[i]; nx = t[i + 1] if i + 1 < L else ""
82
+ prev = t[i - 1] if i > 0 else ""
83
+ if c == "h" and prev in ASP and i > 0:
84
+ alts.append(("", "h")); i += 1; continue # aspiration / separate h
85
+ if nx and c in "ae" and nx in "iy" and (i + 2 >= L or t[i + 2] not in VOW):
86
+ if c == "a" and nx == "i": alts.append(("e", "ai")); i += 2; continue
87
+ if c == "a" and nx == "y" and i + 2 >= L: alts.append(("e",)); i += 2; continue
88
+ if c == "e" and nx == "y" and i + 2 >= L: alts.append(("e",)); i += 2; continue
89
+ if nx and c in "ao" and nx in "uvw" and (i + 2 >= L or t[i + 2] not in VOW) and c + nx != "av":
90
+ alts.append(("o", c + "u")); i += 2; continue
91
+ if c == "o" and nx == "u": alts.append(("o", "u")); i += 2; continue
92
+ if c == "e" and nx == "e": alts.append(("i",)); i += 2; continue
93
+ if c == "o" and nx == "o": alts.append(("u", "o")); i += 2; continue
94
+ if c == "e" and nx == "i" and (i + 2 >= L or t[i + 2] not in VOW): alts.append(("e",)); i += 2; continue
95
+ if c == "e" and nx == "h" and i + 2 < L and t[i + 2] not in VOW and i > 0: alts.append(("e", "")); i += 1; continue
96
+ if nx and c == "i" and nx in "aeou" and i > 0: alts.append(("y", "i")); i += 1; continue
97
+ if c == "y" and i == L - 1 and prev and prev not in VOW: alts.append(("e", "i")); i += 1; continue
98
+ if c == "n" and prev in VOW and i > 0 and (i == L - 1 or (nx and nx not in VOW and nx != "y")):
99
+ alts.append(("n", "")); i += 1; continue
100
+ if c in "iu" and prev == "r" and i >= 2 and t[i - 2] not in VOW:
101
+ alts.append((c, "")); i += 1; continue
102
+ alts.append((c,)); i += 1
103
+ outs = [""]
104
+ for a in alts:
105
+ nxt = []
106
+ for o in outs:
107
+ for x in a: nxt.append(o + x)
108
+ outs = list(dict.fromkeys(nxt))[:64]
109
+ k1 = []; seen = set()
110
+ for o in outs:
111
+ k = _fin(o)
112
+ if k and k not in seen: seen.add(k); k1.append(k)
113
+ k1 = k1[:cap]
114
+ k2 = list(dict.fromkeys(k2_of(k) for k in k1 if k2_of(k)))
115
+ return k1, k2
116
+
117
+
118
+ # Irregular chat abbreviations (not derivable by rules). Values: candidate natives in prior order.
119
+ ABBR = {
120
+ "h": ["है", "हैं"], "hy": ["है"], "hn": ["हाँ", "हैं"], "hm": ["हम", "हम्म"],
121
+ "k": ["के", "की", "ओके"], "ki": ["की", "कि"], "n": ["ना", "न"], "ni": ["नहीं", "नी"], "nai": ["नहीं", "नई"],
122
+ "nhi": ["नहीं"], "nhe": ["नहीं"], "nahi": ["नहीं"], "nai": ["नहीं", "नई"],
123
+ "kr": ["कर"], "krna": ["करना"], "kro": ["करो"], "tm": ["तुम"], "tu": ["तू"], "mai": ["मैं", "में"],
124
+ "me": ["में", "मैं"], "m": ["मैं", "में"], "thk": ["ठीक"], "tk": ["ठीक", "तक"],
125
+ "yr": ["यार"], "yrr": ["यार"], "plz": ["प्लीज़"], "pls": ["प्लीज़"], "plzz": ["प्लीज़"],
126
+ "tq": ["थैंक यू"], "ty": ["थैंक यू"], "thx": ["थैंक्स"], "thnx": ["थैंक्स"], "thnks": ["थैंक्स"],
127
+ "gn": ["गुड नाइट"], "gm": ["गुड मॉर्निंग"], "ok": ["ओके"], "okk": ["ओके"], "kk": ["ओके"],
128
+ "sry": ["सॉरी"], "bro": ["ब्रो"], "msg": ["मैसेज"], "bday": ["बर्थडे"], "clg": ["कॉलेज"],
129
+ "ofc": ["ऑफ़िस"], "pic": ["पिक"], "bhai": ["भाई"], "bht": ["बहुत"], "bhot": ["बहुत"], "bohot": ["बहुत"],
130
+ "pta": ["पता"], "kb": ["कब"], "jb": ["जब"], "sb": ["सब"], "mtlb": ["मतलब"], "kch": ["कुछ"], "kuch": ["कुछ"],
131
+ }
132
+
133
+
134
+ class CasualIndex:
135
+ """K1/K2 -> lexicon ids sorted by frequency (top `top_n` lexicon words only)."""
136
+
137
+ def __init__(self, words, uni=None, top_n=300000, per_key=8):
138
+ order = range(len(words))
139
+ if uni is not None:
140
+ u = uni if callable(uni) else uni.__getitem__
141
+ order = sorted(order, key=lambda i: (-u(i), i))
142
+ self.k1 = {}; self.k2 = {}
143
+ for r, i in enumerate(order):
144
+ if r >= top_n: break
145
+ for k in dev_keys(words[i]):
146
+ b = self.k1.setdefault(k, [])
147
+ if len(b) < per_key and i not in b: b.append(i)
148
+ k2 = k2_of(k)
149
+ if k2:
150
+ b2 = self.k2.setdefault(k2, [])
151
+ if len(b2) < per_key and i not in b2: b2.append(i)
152
+
153
+ def lookup(self, roman, n1=6, n2=4):
154
+ k1, k2 = rom_keys(roman)
155
+ r1 = []; r2 = []
156
+ for k in k1:
157
+ for i in self.k1.get(k, ()):
158
+ if i not in r1: r1.append(i)
159
+ for k in k2:
160
+ for i in self.k2.get(k, ()):
161
+ if i not in r2: r2.append(i)
162
+ return r1[:n1 * 2], r2[:n2 * 2]
163
+
164
+
165
+ def squeeze_in(t, mode=0):
166
+ """Emphasis-repeat normalization of the model input. mode 0: off; 1: 3+ -> 1; 2: 3+ -> 2 for a/e/o, 1 otherwise."""
167
+ if not mode: return t
168
+ if mode == 1: return re.sub(r"([a-z])\1{2,}", r"\1", t)
169
+ return re.sub(r"([a-z])\1{2,}", lambda m: m.group(1) * (2 if m.group(1) in "aeo" else 1), t)
170
+
171
+
172
+ def squeeze(t):
173
+ """Normalize emphasis repeats (3+ same letters -> keep 1, e.g. pleaseee->please)."""
174
+ return re.sub(r"([a-zA-Z])\1{2,}", r"\1", t)