Регистрация Войти
Войти через VK Войти через FB Войти через Google Войти через Яндекс
Войти через VK Войти через FB Войти через Google Войти через Яндекс
Поиск по сайту
soundex для русского языка
Стандартная функция PHP soundex() не работает с кирилицей.
На хабрхабре предложили алгоритм soundex'a для русского языка. В тот момент когда он мне понадобился выяснилось, что алгоритм не совершенен (см. комментарии на хабре) и часто путает совершенно разные слова. Тем не менее, как правильно заметил автор алгоритма - для фолбэка и спецсистем он вполне сгодится. Для своей же задачи я использовал как это ни странно metaphone для английского языка, предварительно транслитерировав русские слова.
Исходный код алгоритма soundex для русского языка:
function dmword($string, $is_cyrillic = true)
{
static $codes = array(
'A' => array(array(0, -1, -1),
'I' => array(array(0, 1, -1)),
'J' => array(array(0, 1, -1)),
'Y' => array(array(0, 1, -1)),
'U' => array(array(0, 7, -1))),
'B' => array(array(7, 7, 7)),
'C' => array(array(5, 5, 5), array(4, 4, 4),
'Z' => array(array(4, 4, 4),
'S' => array(array(4, 4, 4))),
'S' => array(array(4, 4, 4),
'Z' => array(array(4, 4, 4))),
'K' => array(array(5, 5, 5), array(45, 45, 45)),
'H' => array(array(5, 5, 5), array(4, 4, 4),
'S' => array(array(5, 54, 54)))),
'D' => array(array(3, 3, 3),
'T' => array(array(3, 3, 3)),
'Z' => array(array(4, 4, 4),
'H' => array(array(4, 4, 4)),
'S' => array(array(4, 4, 4))),
'S' => array(array(4, 4, 4),
'H' => array(array(4, 4, 4)),
'Z' => array(array(4, 4, 4))),
'R' => array(
'S' => array(array(4, 4, 4)),
'Z' => array(array(4, 4, 4)))),
'E' => array(array(0, -1, -1),
'I' => array(array(0, 1, -1)),
'J' => array(array(0, 1, -1)),
'Y' => array(array(0, 1, -1)),
'U' => array(array(1, 1, -1))),
'F' => array(array(7, 7, 7),
'B' => array(array(7, 7, 7))),
'G' => array(array(5, 5, 5)),
'H' => array(array(5, 5, -1)),
'I' => array(array(0, -1, -1),
'A' => array(array(1, -1, -1)),
'E' => array(array(1, -1, -1)),
'O' => array(array(1, -1, -1)),
'U' => array(array(1, -1, -1))),
'J' => array(array(4, 4, 4)),
'K' => array(array(5, 5, 5),
'H' => array(array(5, 5, 5)),
'S' => array(array(5, 54, 54))),
'L' => array(array(8, 8, 8)),
'M' => array(array(6, 6, 6),
'N' => array(array(66, 66, 66))),
'N' => array(array(6, 6, 6),
'M' => array(array(66, 66, 66))),
'O' => array(array(0, -1, -1),
'I' => array(array(0, 1, -1)),
'J' => array(array(0, 1, -1)),
'Y' => array(array(0, 1, -1))),
'P' => array(array(7, 7, 7),
'F' => array(array(7, 7, 7)),
'H' => array(array(7, 7, 7))),
'Q' => array(array(5, 5, 5)),
'R' => array(array(9, 9, 9),
'Z' => array(array(94, 94, 94), array(94, 94, 94)), // special case
'S' => array(array(94, 94, 94), array(94, 94, 94))), // special case
'S' => array(array(4, 4, 4),
'Z' => array(array(4, 4, 4),
'T' => array(array(2, 43, 43)),
'C' => array(
'Z' => array(array(2, 4, 4)),
'S' => array(array(2, 4, 4))),
'D' => array(array(2, 43, 43))),
'D' => array(array(2, 43, 43)),
'T' => array(array(2, 43, 43),
'R' => array(
'Z' => array(array(2, 4, 4)),
'S' => array(array(2, 4, 4))),
'C' => array(
'H' => array(array(2, 4, 4))),
'S' => array(
'H' => array(array(2, 4, 4)),
'C' => array(
'H' => array(array(2, 4, 4))))),
'C' => array(array(2, 4, 4),
'H' => array(array(4, 4, 4),
'T' => array(array(2, 43, 43),
'S' => array(
'C' => array(
'H' => array(array(2, 4, 4))),
'H' => array(array(2, 4, 4))),
'C' => array(
'H' => array(array(2, 4, 4)))),
'D' => array(array(2, 43, 43)))),
'H' => array(array(4, 4, 4),
'T' => array(array(2, 43, 43),
'C' => array(
'H' => array(array(2, 4, 4))),
'S' => array(
'H' => array(array(2, 4, 4)))),
'C' => array(
'H' => array(array(2, 4, 4))),
'D' => array(array(2, 43, 43)))),
'T' => array(array(3, 3, 3),
'C' => array(array(4, 4, 4),
'H' => array(array(4, 4, 4))),
'Z' => array(array(4, 4, 4),
'S' => array(array(4, 4, 4))),
'S' => array(array(4, 4, 4),
'Z' => array(array(4, 4, 4)),
'H' => array(array(4, 4, 4)),
'C' => array(
'H' => array(array(4, 4, 4)))),
'T' => array(
'S' => array(array(4, 4, 4),
'Z' => array(array(4, 4, 4)),
'C' => array(
'H' => array(array(4, 4, 4)))),
'C' => array(
'H' => array(array(4, 4, 4))),
'Z' => array(array(4, 4, 4))),
'H' => array(array(3, 3, 3)),
'R' => array(
'Z' => array(array(4, 4, 4)),
'S' => array(array(4, 4, 4)))),
'U' => array(array(0, -1, -1),
'E' => array(array(0, -1, -1)),
'I' => array(array(0, 1, -1)),
'J' => array(array(0, 1, -1)),
'Y' => array(array(0, 1, -1))),
'V' => array(array(7, 7, 7)),
'W' => array(array(7, 7, 7)),
'X' => array(array(5, 54, 54)),
'Y' => array(array(1, -1, -1)),
'Z' => array(array(4, 4, 4),
'D' => array(array(2, 43, 43),
'Z' => array(array(2, 4, 4),
'H' => array(array(2, 4, 4)))),
'H' => array(array(4, 4, 4),
'D' => array(array(2, 43, 43),
'Z' => array(
'H' => array(array(2, 4, 4))))),
'S' => array(array(4, 4, 4),
'H' => array(array(4, 4, 4)),
'C' => array(
'H' => array(array(4, 4, 4))))));
$length = strlen($string);
$output = '';
$i = 0;
$previous = -1;
while ($i < $length)
{
$current = $last = &$codes[$string[$i]];
for ($j = $k = 1; $k < 7; $k++)
{
if (!isset($string[$i + $k]) ||
!isset($current[$string[$i + $k]]))
break;
$current = &$current[$string[$i + $k]];
if (isset($current[0]))
{
$last = &$current;
$j = $k + 1;
}
}
if ($i == 0)
$code = $last[0][0];
elseif (!isset($string[$i + $j]) || ($codes[$string[$i + $j]][0][0] != 0))
$code = $is_cyrillic ? (isset($last[1]) ? $last[1][2] : $last[0][2]) : $last[0][2];
else
$code = $is_cyrillic ? (isset($last[1]) ? $last[1][1] : $last[0][1]) : $last[0][1];
if (($code != -1) && ($code != $previous))
$output .= $code;
$previous = $code;
$i += $j;
}
return str_pad(substr($output, 0, 6), 6, '0');
}
function dmstring($string)
{
$is_cyrillic = false;
if (preg_match('#[А-Яа-я]#i', $string) === 1)
{
$string = translit($string);
$is_cyrillic = true;
}
$string = preg_replace(array('#[^\w\s]|\d#i', '#\b[^\s]{1,3}\b#i', '#\s{2,}#i', '#^\s+|\s+$#i'),
array('', '', ' '), strtoupper($string));
if (!isset($string[0]))
return null;
$matches = explode(' ', $string);
foreach($matches as $key => $match)
$matches[$key] = dmword($match, $is_cyrillic);
return $matches;
}
function translit($string)
{
static $ru = array(
'А', 'а', 'Б', 'б', 'В', 'в', 'Г', 'г', 'Д', 'д', 'Е', 'е', 'Ё', 'ё', 'Ж', 'ж', 'З', 'з',
'И', 'и', 'Й', 'й', 'К', 'к', 'Л', 'л', 'М', 'м', 'Н', 'н', 'О', 'о', 'П', 'п', 'Р', 'р',
'С', 'с', 'Т', 'т', 'У', 'у', 'Ф', 'ф', 'Х', 'х', 'Ц', 'ц', 'Ч', 'ч', 'Ш', 'ш', 'Щ', 'щ',
'Ъ', 'ъ', 'Ы', 'ы', 'Ь', 'ь', 'Э', 'э', 'Ю', 'ю', 'Я', 'я'
);
static $en = array(
'A', 'a', 'B', 'b', 'V', 'v', 'G', 'g', 'D', 'd', 'E', 'e', 'E', 'e', 'Zh', 'zh', 'Z', 'z',
'I', 'i', 'J', 'j', 'K', 'k', 'L', 'l', 'M', 'm', 'N', 'n', 'O', 'o', 'P', 'p', 'R', 'r',
'S', 's', 'T', 't', 'U', 'u', 'F', 'f', 'H', 'h', 'C', 'c', 'Ch', 'ch', 'Sh', 'sh', 'Sch', 'sch',
'\'', '\'', 'Y', 'y', '\'', '\'', 'E', 'e', 'Ju', 'ju', 'Ja', 'ja'
);
$string = str_replace($ru, $en, $string);
return $string;
}
echo 'Арнольд Шварцнеггер '.implode(' ', dmstring('Арнольд Шварцнеггер'))."\n";
echo 'Arnold Schwarzenegger '.implode(' ', dmstring('Arnold Schwarzenegger'))."\n";
echo 'Орнольд Шворцнегир '.implode(' ', dmstring('Орнольд Шворцнегир'))."\n";
Еще один вариант ru_soundex:
# to Arthur
// (cc) me, 23/08/2007-27/08/2007
$str = "к скалам бурым";
print "ru_soundex($str) = ".ru_soundex($str)."<br />\r\n";
$str = "с каламбуром";
print "ru_soundex($str) = ".ru_soundex($str)."<br />\r\n";;
function ru_soundex($source)
{
$res = '';
$literal = [];
// ассоциативный массив букв
// параметры звуков гласный / согласный
// для гласных переход буквы в звук(и), редуцированный/нет, предполагаемые правила ударения исходя из кол-ва слогов (stressed syllable)
// реализована проверка предполагаемого ударения
// для согласных переход букв[ы] в звук(и), редуцируемый/нет, правила редуцирования
// vowel
$literal['А'] = array('status'=>'гласный','sound'=>'а','stressed'=>'а'); // никогда не меняется
$literal['Е'] = array('status'=>'гласный','sound'=>'и','stressed'=>'э', 'АаЕеЁёИиОоУуЭэЮюЯяЬьЫыЪъ' => 'йэ'); // - особые правила, для этой буквы, стоящей после указанных, а также в начале слов
$literal['Ё'] = array('status'=>'гласный','sound'=>'о','stressed'=>'о', 'АаЕеЁёИиОоУуЭэЮюЯяЬьЫыЪъ' => 'йо');
$literal['И'] = array('status'=>'гласный','sound'=>'и','stressed'=>'и');
$literal['О'] = array('status'=>'гласный','sound'=>'а','stressed'=>'о');
$literal['У'] = array('status'=>'гласный','sound'=>'у','stressed'=>'у');
$literal['Ы'] = array('status'=>'гласный','sound'=>'ы','stressed'=>'ы');
$literal['Э'] = array('status'=>'гласный','sound'=>'э','stressed'=>'э');
$literal['Ю'] = array('status'=>'гласный','sound'=>'у','stressed'=>'у', 'АаЕеЁёИиОоУуЭэЮюЯяЬьЫыЪъ' => 'йу');
$literal['Я'] = array('status'=>'гласный','sound'=>'а','stressed'=>'а', 'АаЕеЁёИиОоУуЭэЮюЯяЬьЫыЪъ' => 'йа'); // заяц произносится как [зайец]
$v_pattern = 'АаЕеЁёИиОоУуЭэЮюЯяЬьЫыЪъ';
// кстати, надо добавить выкусывание гласных из концов слов, заканчивающихся на согласный-гласный-звонкий согласный (-ром, -лем, итд) гласная очень часто сглатывается
// зы: это здесь не реализовано %)
// проверено: soundex и сам с этим неплохо справляется
// звонкие согласные редуцируются при удвоении.
// звонкие согласные переходят в парный глухой перед глухим
// глухие редуцируются полностью перед глухими.
// consonant
// в отличие от гласных, для согласных условие "стоит перед указанной или в конце слова"
$literal['Б'] = array('status'=>'согласный','sound'=>'б', 'КкПпСсТтФфХхЦцЧчШшЩщ' => 'п');
$literal['В'] = array('status'=>'согласный','sound'=>'в', 'КкПпСсТтФфХхЦцЧчШшЩщ' => 'ф');
$literal['Г'] = array('status'=>'согласный','sound'=>'Г', 'КкПпСсТтФфХхЦцЧчШшЩщ' => 'к');
$literal['Д'] = array('status'=>'согласный','sound'=>'д', 'КкПпСсТтФфХхЦцЧчШшЩщ' => 'т');
$literal['Ж'] = array('status'=>'согласный','sound'=>'ж', 'КкПпСсТтФфХхЦцЧчШшЩщ' => 'ш');
$literal['З'] = array('status'=>'согласный','sound'=>'з', 'КкПпСсТтФфХхЦцЧчШшЩщ' => 'с');
$literal['Й'] = array('status'=>'согласный','sound'=>'й');
$literal['К'] = array('status'=>'согласный','sound'=>'к', 'КкПпСсТтФфХхЦцЧчШшЩщ' => '');
$literal['Л'] = array('status'=>'согласный','sound'=>'л');
$literal['М'] = array('status'=>'согласный','sound'=>'м');
$literal['Н'] = array('status'=>'согласный','sound'=>'н');
$literal['П'] = array('status'=>'согласный','sound'=>'п', 'КкПпСсТтФфХхЦцЧчШшЩщ' => '');
$literal['Р'] = array('status'=>'согласный','sound'=>'р');
$literal['С'] = array('status'=>'согласный','sound'=>'с'); // а вот С не хочет редуцироваться, на первый взгляд...
$literal['Т'] = array('status'=>'согласный','sound'=>'т', 'КкПпСсТтФфХхЦцЧчШшЩщ' => '');
$literal['Ф'] = array('status'=>'согласный','sound'=>'ф', 'КкПпСсТтФфХхЦцЧчШшЩщ' => ''); // спорно
$literal['Х'] = array('status'=>'согласный','sound'=>'х');
$literal['Ц'] = array('status'=>'согласный','sound'=>'ц');
$literal['Ч'] = array('status'=>'согласный','sound'=>'чь'); // всегда мягкий
$literal['Ш'] = array('status'=>'согласный','sound'=>'ш');
$literal['Щ'] = array('status'=>'согласный','sound'=>'щь');
// спецсимволы
$literal['Ъ'] = array('status'=>'знак','sound'=>' '); // только разделительный. делит жёстко
$literal['Ь'] = array('status'=>'знак','sound'=>'ь'); // даже если делит, то мягко
$literal['ТС'] = array('status'=>'сочетание','sound'=>'ц');
$literal['ТЬС'] = $literal['ТС'];
$literal['ШЬ'] = array('status'=>'сочетание','sound'=>'ш'); // всегда твёрдый. и это не единстенный рудимент языка
$literal['СОЛНЦ'] = array('status'=>'сочетание','sound'=>'сонц');
$literal['ЯИЧНИЦ'] = array('status'=>'сочетание','sound'=>'еишниц');
$literal['КОНЕЧНО'] = array('status'=>'сочетание','sound'=>'канешно');
$literal['ЧТО'] = array('status'=>'сочетание','sound'=>'што');
$literal['ЗАЯ'] = array('status'=>'сочетание','sound'=>'зайэ'); // да-да. не только [зайэц], но и [зайэвльэнийэ]
$sound = str_to_upper($source);
// сначала сочетания
foreach( array_filter($literal,
create_function('$item','if( $item["status"] === "сочетание") return true; return false;'))
as $sign => $translate )
$sound = str_replace($sign,$translate["sound"],$sound);
// потом знаки
foreach( array_filter($literal,
create_function('$item','if( $item["status"] === "знак") return true; return false;'))
as $sign => $translate )
$sound = str_replace($sign,$translate["sound"],$sound);
// разделяем на слова, определяем кол-во слогов, заменяем ударный/безударный гласный (единственный или предполагая второй в двух-трёхсложном слове, предпредпоследний - в остальных)
$words = preg_split('~[,.\~`1234567890-=\~!@#$%^&*()_+|{}\]\];:\'"<>/? ]~', $sound, -1, PREG_SPLIT_NO_EMPTY);
// гласные
foreach( array_filter($literal,
create_function('$item','if( $item["status"] === "гласный") return true; return false;'))
as $sign => $translate )
{
// для каждого слова
foreach( $words as &$word )
{
// кол-во гласных
$vowel = preg_match_all("~[$v_pattern]~", $word, $del_me );
// готовим
$cur_pos = 0;
$cur_vowel = 0;
while( false !== $cur_pos = strpos($word,$sign,$cur_pos) )
{
$cur_vowel++;
// print $cur_pos.' = '.$sound[$cur_pos]."<br />\r\n";
if( sizeof($translate)==4 && ($cur_pos === 0 || strpos( $v_pattern , $word[$cur_pos-1] )))
{
$word = substr_replace($word,$translate[$v_pattern],$cur_pos,1);
}
elseif( 1 == $vowel )
$word = substr_replace($word,$translate["stressed"],$cur_pos,1); //
elseif( 2 == $vowel && 1 == $cur_vowel )
$word = substr_replace($word,$translate["stressed"],$cur_pos,1); // предполагаем, что в двухсложных словах первый слог ударный
elseif( 3 <= $vowel && $cur_vowel == $vowel - 2 )
$word = substr_replace($word,$translate["stressed"],$cur_pos,1); // предполагаем, что слог ударный предпредпоследний
else
$word = substr_replace($word,$translate["sound"],$cur_pos,1);
$cur_pos++;
}
}
}
$sound = implode( $words, ' ' ); // клеим обратно
// согласные
foreach( array_filter($literal,
create_function('$item','if( $item["status"] === "согласный") return true; return false;'))
as $sign => $translate )
{
// готовим
$cur_pos = 0;
while( false !== $cur_pos = strpos($sound,$sign,$cur_pos) )
{
// print $cur_pos.' = '.$sound[$cur_pos]."<br />\r\n";
if( sizeof($translate)==3 )
{
$x = array_pop(array_keys($translate)); // снимаем третий элемент
if( strpos( $x, $sound[$cur_pos+1] ) || $cur_pos === strlen($sound) )
{
$sound = substr_replace($sound,$translate[$x],$cur_pos,1);
} elseif ( $sound[$cur_pos] === $sound[$cur_pos+1] )
$sound = substr_replace($sound,$translate["sound"],$cur_pos,2); // все двойные редуцируются
else
$sound = substr_replace($sound,$translate["sound"],$cur_pos,1);
} else
{
$sound = substr_replace($sound,$translate["sound"],$cur_pos,1);
}
$cur_pos++;
}
}
// алес. фонемы привели к одному виду
// дальше используем любой алгоритм для вычисления числового эквивалента
// но остаётся сомнение. очень хочется расстаться с глухими предлогами перед глухими согласными ("к скалам")
$sound = preg_replace('~[,.\~`1234567890-=\~!@#$%^&*()_+|{}\]\];:\'"<>/? ]~','',$sound) ;
// print $sound;
// print str_to_translit($sound);
// print soundex(str_to_translit($sound));
$res = str_to_upper($source[0]).substr(soundex(str_to_translit($sound)),1);
return $res;
}
// (c) http://ru2.php.net/manual/en/function.strtoupper.php#74574
//Russian
function str_to_upper($str){
return strtr($str,
"abcdefghijklmnopqrstuvwxyz".
"\xE0\xE1\xE2\xE3\xE4\xE5".
"\xb8\xe6\xe7\xe8\xe9\xea".
"\xeb\xeC\xeD\xeE\xeF\xf0".
"\xf1\xf2\xf3\xf4\xf5\xf6".
"\xf7\xf8\xf9\xfA\xfB\xfC".
"\xfD\xfE\xfF",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ".
"\xC0\xC1\xC2\xC3\xC4\xC5".
"\xA8\xC6\xC7\xC8\xC9\xCA".
"\xCB\xCC\xCD\xCE\xCF\xD0".
"\xD1\xD2\xD3\xD4\xD5\xD6".
"\xD7\xD8\xD9\xDA\xDB\xDC".
"\xDD\xDE\xDF");
}
function str_to_translit($str){
return strtr($str,
"abcdefghijklmnopqrstuvwxyz".
"\xE0\xE1\xE2\xE3\xE4\xE5".
"\xb8\xe6\xe7\xe8\xe9\xea".
"\xeb\xeC\xeD\xeE\xeF\xf0".
"\xf1\xf2\xf3\xf4\xf5\xf6".
"\xf7\xf8\xf9\xfA\xfB\xfC".
"\xfD\xfE\xfF",
"abcdefghijklmnopqrstuvwxyz".
"abvgde".
"?*ziik".
"lmnopr".
"stufhc".
"4ww\"y`".
"eua");
}
Взято отсюда: http://ru-php.livejournal.com/1062493.html.
.
Прокомментировать/Отблагодарить