БлогNot. Длина строки в Юникоде и расстояние между строками

Длина строки в Юникоде и расстояние между строками

Совсем некогда написать даже пару мелочей, которые хочу скинуть сюда, пишу ту, что удалось сейчас доделать.

Изначальный вопрос был распространённым - как на PHP узнать длину строки, заданной в Юникоде (UTF-8). Стандартные функции strlen и mb_strlen не помогут - они возвращают число байт, но не символов. Точней, вторая-то из них поможет, но вот так:

mb_strlen($str,'UTF-8');

Второй нюанс - строка в PHP (как и во многих других языках) массивом символов не является. Поэтому некорректно и извлекать символ из строки Юникода так, как программисты привыкли делать для однобайтовых кодировок:

$c=$str[$i];

А вот так должно сработать (для символа с номером $i):

$c=mb_substr($str,$i,1,'UTF-8');

В качестве иллюстрации работы с многобайтовыми символами - небольшой онлайн-сервис на PHP в Юникоде, вычисляющий так называемое расстояние Левенштейна между двумя строками (лишние разделители удаляются, так что строки после обработки скриптом могут не совпадать с введёнными).

Вот полный исходник скрипта, основную работу выполняется функция D, вычисляющая искомое расстояние:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
 <meta http-equiv="content-type" content="text/html; charset=UTF-8">
 <title>Расстояние Левенштейна между строками</title>
</head>
<body>

<?php
 function D ($s1,$s2) {
  if ($s1==$s2) return 0;
  $m = mb_strlen($s1,'UTF-8');
  $n = mb_strlen($s2,'UTF-8');
  if ($m==0) return $n;
  else if ($n==0) return $m;
  $d0 = range(0, $n);
  for ($i=0;$i<$m;$i++) {
   $d = array($i+1);
   $c1 = mb_substr($s1,$i,1,'UTF-8');
   for ($j=0;$j<$n;$j++) {
    $M = $c1==mb_substr($s2,$j,1,'UTF-8')?0:1;
    $d[$j+1]=min($d0[$j+1]+1,$d[$j]+1,$d0[$j]+$M); //min(удаление,вставка,замена)
   }
   $d0 = $d;
  }
  return $d0[$j];
 }

 function trimall($string) { return trim(preg_replace("/\s+/"," ",$string)); }
 function magic ($path) { 
  @ini_set('magic_quotes_runtime', '0'); 
  @ini_set('magic_quotes_sybase', '0');
  if (@get_magic_quotes_gpc()=='1') $path=stripslashes($path); return $path;
 }
  
 $params = array ('w1','w2','action');
 while (list($num,$var) = each($params)) {
  if (!empty($_POST[$var])) $$var = trimall(htmlspecialchars(magic($_POST[$var])));
  else if (!empty($_GET[$var])) $$var = trimall(htmlspecialchars(magic($_GET[$var])));
  else $$var = '';
 }
 $w1=mb_convert_case($w1,MB_CASE_LOWER,'UTF-8');
 $w2=mb_convert_case($w2,MB_CASE_LOWER,'UTF-8');
 
 echo '
  <form name="f1" method="post" action="'.$_SERVER['PHP_SELF'].'">
  <p>Слово 1: <input type="text" name="w1" maxlength="20" size="21" value="'.$w1.'">
  Слово 2: <input type="text" name="w2" maxlength="20" size="21" value="'.$w2.'">
  <input type="submit" name="action" value="OK"> <a href="'.$_SERVER['PHP_SELF'].'?w1=&w2=">Очистить</a>
  </p></form>';
 if (!empty($action)) echo '
  <p>Расстояние между строками <b>'.($w1==''?'<font color="red">пусто</font>':$w1).'</b> и <b>'.
  ($w2==''?'<font color="red">пусто</font>':$w2).'</b>='.D($w1,$w2).'</p>';
?>
</body></html>

 Онлайн-сервис определения расстояния Левенштейна между строками

Для сравнения (и без исходников): http://www.planetcalc.ru/1721/

Алгоритм старается экономить память, не создавая лишних данных, статья о подобном подходе к расчёту расстояния Левенштейна есть на Хабре.

Вариант поглупей - загонять значения $d в матрицу, скажем, по формулам из Вики будет как-то так:

function M ($a,$b) { return ($a==$b?0:1); }

function D ($s1,$s2) {
 $m = mb_strlen ($s1,'UTF-8');
 $n = mb_strlen ($s2,'UTF-8');
 if ($m==0 and $n==0) return 0;
 else if ($m==0) return $n;
 else if ($n==0) return $m;
 $d[0][0]=0;
 for ($i=1; $i<$m; $i++) $d[$i][0]=$i;
 for ($j=1; $j<$n; $j++) $d[0][$j]=$j;
 for ($i=1; $i<$m; $i++)
 for ($j=1; $j<$n; $j++) {
  $c1=mb_substr($s1,$i,1,'UTF-8');
  $c2=mb_substr($s2,$j,1,'UTF-8');
  $d[$i][$j]=min($d[$i][$j-1]+1,$d[$i-1][$j]+1,$d[$i-1][$j-1]+M($c1,$c2));
 }
 return $d[$m-1][$n-1];
}

(не считал, может эта версия функции и неправильна).

Ещё глупее - считать рекурсивно, как описан алгоритм в Вики.

Разумеется, это побуквенное расстояние никакого смысла не имеет. Вот расстояние между слогами или иными пра-лексемами в должной многомерной метрике - это было бы то, что нужно :)

P.S. Пример следует понимать как учебный, на самом деле в PHP, начиная с версии 4.0.1 есть стандартная функция levenshtein

26.06.2013, 23:23 [11932 просмотра]


теги: php textprocessing программирование форматы числа алгоритм

К этой статье пока нет комментариев, Ваш будет первым