Определение языка и кодировки. Компонент для CakePHP
Для моего текущего проекта необходимо определять на каком языке пользователь вводит информацию. Причём это не сложный выбор между PHP и Perl, а, например, между английским и испанским. Сначала я хотел составить список самых распространённых слов в популярных языках - предлоги, частые глаголы и т.д. Почти сразу я понял, что точность будет небольшая, а работы - очень много, даже, если её буду делать не я :).
Поэтому пришлось думать дальше. Мне больше всего понравился способ, в котором учитывается частотность букв, двух-, трёх- и четырёхбуквенных сочетаний.
Сначала система обучается - ей скармливается много текста и указывается, какой это язык. Она разбирает их на части и запоминает наиболее часто встречаемые как эталонные для этого языка.
Потом берётся текст с неизвестным языком и определяются наиболее часто встречаемые части в нём. Эти части сравниваются с эталонными и на основе этого определяется вероятность каждого из языков.
Я взял за основу код с http://boxoffice.ch/pseudo/ng.php, удалил лишнее, немного оптимизировал алгоритм и сделал код красивый и подходящий для CakePHP.
Определим язык для текста
plain text
$language = $this->LangDetect->detect(’Учи олбанский и убей сибя ап стену.’);
Не каждый человек сможет понять, что это за язык :). А компонент понял - russian-utf8.
Кстати, сразу видно бонус - определяется кодировка. Для русского поддерживаются ISO, KOI8-R, UTF-8, Windows-1251.
Если надо определить несколько наиболее вероятных языков, то надо указать второй параметр true.
plain text
$language = $this->LangDetect->detect(’Учи олбанский и убей сибя ап стену.’, true);
Это выдаст:
plain text
Array
(
[russian-utf8] => 30366
[bulgarian-utf8] => 31619
[ukrainian-utf8] => 33273
[serbian_cyrillic-utf8] => 33878
[belarusian-utf8] => 35596
…
Чем меньше значение, тем более вероятно, что это тот язык.
Полный список поддерживаемых языков (можно получить с помощью $this->LangDetect->listLanguages()):
afrikaans, albanian, alemannic, amharic-utf8, arabic-iso8859_6, arabic-utf8, arabic-windows1256, armenian, armenian-utf8, basque, belarusian-utf8, belarusian-windows1251, bosnian, breton, bulgarian-iso8859_5, bulgarian-utf8, catalan, chinese-big5, chinese-gb2312, chinese-utf8, croatian-ascii, czech-iso8859_2, czech-utf8, danish, dutch, english, esperanto, estonian, finnish, french, frisian, georgian, georgian-utf8, german, greek-iso8859_7, greek-utf8, hawaian, hebrew-iso8859_8, hebrew-utf8, hindi, hindi-utf8, hungarian, icelandic, indonesian, irish_gaelic, italian, japanese-euc_jp, japanese-shift_jis, japanese-utf8, korean, korean-utf8, latin, latvian, lithuanian, malay, manx, marathi, marathi-utf8, middlefrisian, mingo_iroquois, nepali, nepali-utf8, norwegian, persian, persian_farsi-utf8, persian_farsi-windows1256, polish-iso8859_2, polish-utf8, portuguese_brazil, portuguese_europe, quechua, romanian, rumantsch, russian-iso8859_5, russian-koi8_r, russian-utf8, russian-windows1251, sanskrit, scots, scots_gaelic, serbian-ascii, serbian_cyrillic-utf8, slovak-ascii, slovak-utf8, slovak-windows1250, slovenian-ascii, slovenian-iso8859_2, spanish, swahili, swedish, tagalog, tamil, tamil-utf8, thai, thai-utf8, turkish, turkish-utf8, ukrainian-koi8_u, ukrainian-utf8, vietnamese, welsh, yiddish-utf8
Чтобы компонент заработал, нужно скачать скомпилированную информацию о языках и положить этот fingerprint.dat в app/vendors.
А вот и сам компонент:
<?php
/**
* Language detection component
*
* Most of code was copied from http://boxoffice.ch/pseudo/code_expl/code_class.php
* but a lot of things were simplified and beautified
*
* @link http://php.southpark.com.ua
* @author Vladimir Luchaninov
* @version 1.0 (3 Dec 2007)
*
*/
class LangDetectComponent {
protected $fingerprint = null;
protected $ngrams = array();
//reasonable defaults
public $ngramCount = 350; //default nb of ngrams created from analyzed text
public $maxDelta = 140000; //stop evaluation deviate strongly
function startup(&$controller) {
$this->fingerprint = unserialize(file_get_contents(APP . ‘vendors’ . DS . ‘fingerprint.dat’));
}
/**
* Main function
*
* @param string $text Text with unknown language
* @param bool $onlyBest
* true - detect the best language
* false - detect all languages with possibilities
* @return LangDetect
*/
function detect($text, $onlyBest = true) {
if (empty($text)) {
trigger_error(’Text should not be empty’);
return false;
}
$this->createNGrams($text);
if ($onlyBest){
return $this->compareNGramsOne();
} else {
return $this->compareNGrams();
}
}
/**
* Get list of the available languages
*
* @return array List of languages
*/
function listLanguages() {
$languages = array_keys($this->fingerprint);
sort($languages);
return $languages;
}
/**
* Create ngram-array of given string
*
* @param string $text
*
*/
protected function createNGrams($text) {
$array_words = explode(” “, $text);
$ngrams = array();
foreach($array_words as $word) {
$word = “_”. $word . “_”;
$wordLength = strlen($word);
for ($i=0; $i <$wordLength; $i++) { //start position within word
for ($s=1; $s <4+1; $s++) { //length of ngram
if (($i + $s) <$wordLength + 1) { //length depends on postion
$ngrams[] = substr($word, $i, $s);
}
}
}
}
//count-> value(frequency, int)… key(ngram, string)
$blub = array_count_values($ngrams);
//sort array by value(frequency) desc
arsort($blub);
//use only top frequent ngrams (def by $ng_number)
$top = array_slice($blub, 0, $this->ngramCount);
$this->ngrams = array();
foreach ($top as $keyvar => $valvar){
$this->ngrams[] = $keyvar;
}
}
/**
* Compare ngrams: Textinput vs lm-files.
*
* @return array of languages with lowest deviation
*/
protected function compareNGrams() {
$limit = $this->maxDelta;
foreach ($this->fingerprint as $basename => $language) {
$delta = 0;
//compare each ngram of input text to current lm-array
foreach ($this->ngrams as $key => $ngram){
//match
if(in_array($ngram, $language)) {
$delta += abs($key - array_search($ngram, $language));
//no match
} else {
$delta += 400;
}
//abort: this language already differs too much
if ($delta> $this->maxDelta) {
break;
}
} // End comparison with current language
//include only non-aborted languages in result array
if ($delta <($this->maxDelta)-400) {
$result[$basename] = $delta;
}
} //End comparison all languages
if(!isset($result)) {
$result = array(’unknown’=>0);
} else {
asort($result);
}
return $result;
}
/**
* Variation - COMPARE ng’s - Return 1 LANGUAGE only
*
* @return string Most probable language
*/
protected function compareNGramsOne() {
$limit = 160000;
foreach ($this->fingerprint as $basename => $language) {
$delta = 0;
foreach ($this->ngrams as $key => $ngram){
if (in_array($ngram, $language)) {
$delta += abs($key - array_search($ngram, $language));
} else {
$delta += 400;
}
if ($delta> $limit) {
break;
}
}
if ($delta <$limit) {
$result[$basename] = $delta;
$limit = $delta; //lower limit
}
}
if (!isset($result)) {
return ‘unknown’;
} else {
asort($result);
//basename of best matching lm file
list($result_first, $ignore) = each($result);
}
return $result_first;
}
}
?>
Источник: http://php.southpark.com.ua
Tags: php, кодировкаДобавить комментарий Март 5, 2008
