#
Превращает частотную таблицу английских биграмм в компактный 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) символов таблицы)"