Pull to refresh

Определение кодировки текста в PHP, часть 2 — биграммы

Reading time 3 min
Views 8.1K
В прошлой статье был реализован алгоритм автоматического определения кодировки текста на основе частот распределения символов. В комментариях отметили: если использовать биграммы (триграммы), результат будет более точный. Тогда я отмахнулся, мол, и на одиночных символах неплохой результат получается. Но сейчас подумал, что неплохо было бы добавить надежности и точности в алгоритм, тем более использование биграмм вместо одиночных символов сильно кушать не просит.

Под катом — пример реализации алгоритма на биграммах, исходники и результаты его работы.


Описание алгоритма



Как всегда — будем работать только с однобайтовыми русскими кодировками. Для определения UTF-8 смысла писать такой алгоритм нету: она определяется очень просто:
$str_utf8 = 'Русский текст';
$str_cp1251 = iconv('UTF-8', 'Windows-1251', $str_utf8);
var_dump(preg_match('#.#u', $str_utf8));
var_dump(preg_match('#.#u', $str_cp1251));

m00t@m00t:~/workspace/test$ php detect_encoding.php 
int(1)
int(0)


Итак, берем достаточно большой русский текст и замеряем вместо частот букв частоты пар букв (я традиционно взял Войну и Мир). Получаем что-то в этом роде (тут показаны не частоты, а кол-во упоминаний в тексте, что фактически одно и то же):
<?php
return array (
  'аа' => 3,
  'аб' => 1127,
  'ав' => 5595,
  'аг' => 1373,
  'ад' => 3572,
  'ае' => 1483,
  'аё' => 0,
  'аж' => 1931,
....
  'яс' => 1325,
  'ят' => 2439,
  'яу' => 1,
  'яф' => 1,
  'ях' => 284,
  'яц' => 70,
  'яч' => 254,
  'яь' => 0,
  'яы' => 0,
  'яъ' => 0,
  'яэ' => 0,
  'яю' => 185,
  'яя' => 283,
);


Далее конвертируем это дело во все нужные кодировки, при этом добавляем еще варианты с разным регистром символов (заодно сконвертируем кол-во упоминаний в частоту):
<?php return array (
  'аа' => 2.5816978277594E-6,
  'Аа' => 2.5816978277594E-6,
  'аА' => 2.5816978277594E-6,
  'АА' => 2.5816978277594E-6,
  'аб' => 0.00096985781729497,
  'Аб' => 0.00096985781729497,
  'аБ' => 0.00096985781729497,
  'АБ' => 0.00096985781729497,
  'ав' => 0.0048148664487714,
  'Ав' => 0.0048148664487714,
  'аВ' => 0.0048148664487714,
  'АВ' => 0.0048148664487714,
...
  'яы' => 0,
  'Яы' => 0,
  'яЫ' => 0,
  'ЯЫ' => 0,
  'яъ' => 0,
  'Яъ' => 0,
  'яЪ' => 0,
  'ЯЪ' => 0,
  'яэ' => 0,
  'Яэ' => 0,
  'яЭ' => 0,
  'ЯЭ' => 0,
  'яю' => 0.0001592046993785,
  'Яю' => 0.0001592046993785,
  'яЮ' => 0.0001592046993785,
  'ЯЮ' => 0.0001592046993785,
  'яя' => 0.00024354016175197,
  'Яя' => 0.00024354016175197,
  'яЯ' => 0.00024354016175197,
  'ЯЯ' => 0.00024354016175197,
);

Таких файликов у меня получилось три — по одному на каждую кодировку: cp1251, koi8-r, iso8859-5.

Теперь, когда нам нужно узнать кодировку неизвестного текста, мы проходимся по нему, вычленяем все пары символов и прибавляем к «весу» каждой кодировки частоту этой пары символов по уже сгенерированным спектрам.

Результаты работы


Закомментированные массивы — суммарные весы пар символов в тексте для соответствующей кодировки, поделенные на сумму для всех подировок (см. test_detect_encoding.php:21). Т.е. можно сказать, что это вероятности того, что текст именно в этой кодировке.
$data = iconv('UTF-8', 'iso8859-5', 'Короткая русская строка');
/*
array(3) {
  ["windows-1251"]=>
  float(0.071131587263965)
  ["koi8-r"]=>
  float(0.19145038318717)
  ["iso8859-5"]=>
  float(0.73741802954887)
}
*/

$data = iconv('UTF-8', 'windows-1251', 'Короткая русская строка');
/*
array(3) {
  ["windows-1251"]=>
  float(0.95440659551352)
  ["koi8-r"]=>
  float(0.044353550201316)
  ["iso8859-5"]=>
  float(0.0012398542851665)
}
*/

$data = file_get_contents('test/cp1251_1.html'); // это какая-то сохраненная страничка в cp1251
/*
array(3) {
  ["windows-1251"]=>
  float(0.78542385878465)
  ["koi8-r"]=>
  float(0.18514302234077)
  ["iso8859-5"]=>
  float(0.029433118874583)
}
*/


Как видим, получается очень даже неплохой результат — даже на маленьких строках верная кодировка лидирует почти на порядок.

Все исходники со сгенерированными спектрами для трех однобайтовых кодировок (конечно, для русского языка) можно скачать на гитхабе. Я намеренно не оформлял все это дело в виде реюзабельной либы, потому что там кода-то всего на сотню строк. Если кому будет необходимо, он сможет сам для себя оформить как ему нравится и как навязывает тот или иной используемый фреймворк.
Tags:
Hubs:
+60
Comments 30
Comments Comments 30

Articles