<# Превращает частотную таблицу английских биграмм в компактный C#-файл. Каждая из 676 биграмм кодируется одним печатным ASCII-символом (квантованный log10 вероятности), поэтому вся модель — одна строка примерно в 700 байт вместо мегабайтных словарей и без единой внешней зависимости. Источник частот: practicalcryptography.com (корпус английских текстов). #> param( [string]$In = "$PSScriptRoot\english_bigrams.txt", [string]$Out = "$PSScriptRoot\..\src\EnglishBigrams.cs" ) $ErrorActionPreference = 'Stop' $counts = @{} [double]$total = 0 foreach ($line in Get-Content $In) { if ($line -notmatch '^\s*([A-Za-z]{2})\s+(\d+)\s*$') { continue } $bg = $Matches[1].ToUpperInvariant() $n = [double]$Matches[2] $counts[$bg] = $n $total += $n } Write-Host "биграмм: $($counts.Count), суммарная частота: $total" # Квантование: log10(p) укладываем в печатный ASCII с шагом 1/12 порядка величины. $LO = -9.0; $STEP = 12.0 $sb = New-Object Text.StringBuilder $minLog = 0.0 foreach ($i in 0..25) { foreach ($j in 0..25) { $bg = [char](65 + $i) + [string][char](65 + $j) $n = if ($counts.ContainsKey($bg)) { $counts[$bg] } else { 0 } # ненаблюдаемой биграмме даём вероятность заметно ниже самой редкой наблюдаемой $p = if ($n -gt 0) { $n / $total } else { 1e-9 } $lg = [Math]::Log10($p) if ($lg -lt $minLog) { $minLog = $lg } $q = [int][Math]::Round(($lg - $LO) * $STEP) if ($q -lt 0) { $q = 0 } if ($q -gt 88) { $q = 88 } [void]$sb.Append([char](34 + $q)) # с 34, чтобы не попасть на кавычку и обратный слэш } } Write-Host "минимальный log10(p): $minLog" $data = $sb.ToString() $chunks = @() for ($i = 0; $i -lt $data.Length; $i += 130) { $len = [Math]::Min(130, $data.Length - $i) $chunks += ' "' + $data.Substring($i, $len).Replace('\', '\\') + '"' } $joined = $chunks -join " +`r`n" $code = @" using System; namespace ScreenTranslator { /// Модель правдоподобия английского текста по частотам буквенных пар. /// /// Решает задачу, с которой не справляются ни словарь, ни алфавит: английская модель OCR, /// прочитав русскую надпись, выдаёт правдоподобное на вид латинское слово («Профиль» → /// «npocbwlb»). Но пары букв выдают подделку сразу — сочетаний «wl», «cb», «bw» в английском /// практически не бывает, тогда как «th», «he», «in» составляют его основу. /// /// Таблица — 676 квантованных log10-вероятностей, по символу на пару, около 700 байт. /// Сгенерирована tools/make-bigrams.ps1 из частот корпуса английских текстов /// (practicalcryptography.com). Правится только генератором, не руками. internal static class EnglishBigrams { const double Lo = $LO, Step = $STEP; const string Table = $joined; /// Средний log10 вероятности пар букв в слове. Около -2,5 у обычного английского /// слова, ниже -4 у случайного набора букв. NaN, если букв слишком мало для оценки. public static double WordScore(string word) { if (string.IsNullOrEmpty(word)) return double.NaN; double sum = 0; int n = 0; for (int i = 0; i + 1 < word.Length; i++) { int a = Index(word[i]), b = Index(word[i + 1]); if (a < 0 || b < 0) continue; sum += Lo + (Table[a * 26 + b] - 34) / Step; n++; } return n == 0 ? double.NaN : sum / n; } static int Index(char c) { if (c >= 'a' && c <= 'z') return c - 'a'; if (c >= 'A' && c <= 'Z') return c - 'A'; return -1; } /// Оценка текста целиком: усреднение по словам длиной от четырёх букв — /// на более коротких пары букв ничего не доказывают. NaN, если оценивать нечего. public static double TextScore(string text) { if (string.IsNullOrEmpty(text)) return double.NaN; double sum = 0; int n = 0; foreach (var w in text.Split(new[] { ' ', '\t', '\n', '\r', '.', ',', ':', ';', '!', '?', '(', ')', '[', ']', '"', '\'', '/', '\\', '-' }, StringSplitOptions.RemoveEmptyEntries)) { if (w.Length < 4) continue; double s = WordScore(w); if (double.IsNaN(s)) continue; sum += s; n++; } return n == 0 ? double.NaN : sum / n; } } } "@ $code | Set-Content $Out -Encoding UTF8 Write-Host "записано: $Out ($($data.Length) символов таблицы)"