AAA √лавна€
ѕримеры PHP ѕримеры JavaScript ѕримеры Ajax ѕримеры CSS,HTML

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";

¬з€то с http://p2k.ru/archives/57.

≈ще один вариант 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 = array();
// ассоциативный массив букв
// параметры звуков гласный / согласный

// дл€ гласных переход буквы в звук(и), редуцированный/нет, предполагаемые правила ударени€ исход€ из кол-ва слогов (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.


.

© Copyright 2008-2016 by KDG