Php поиск с ошибкой в слове

Время на прочтение
7 мин

Количество просмотров 30K

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

Для исправления опечаток в словах понадобится:
Расстояние Левенштейна (или расстояние Дамерау-Левенштейна — разница будет незначительной) — levenshtein()
Metaphone — metaphone()
Алгоритм Оливера — similar_text()
База русских слов (с падежами, учетом времен и т.д.).

Функция для транслитерации слов:

  1. function translitIt($str)
  2.   {
  3.     $tr = array(
  4.         "А"=>"A","Б"=>"B","В"=>"V","Г"=>"G",
  5.         "Д"=>"D","Е"=>"E","Ж"=>"J","З"=>"Z","И"=>"I",
  6.         "Й"=>"Y","К"=>"K","Л"=>"L","М"=>"M","Н"=>"N",
  7.         "О"=>"O","П"=>"P","Р"=>"R","С"=>"S","Т"=>"T",
  8.         "У"=>"U","Ф"=>"F","Х"=>"H","Ц"=>"TS","Ч"=>"CH",
  9.         "Ш"=>"SH","Щ"=>"SCH","Ъ"=>"","Ы"=>"YI","Ь"=>"",
  10.         "Э"=>"E","Ю"=>"YU","Я"=>"YA","а"=>"a","б"=>"b",
  11.         "в"=>"v","г"=>"g","д"=>"d","е"=>"e","ж"=>"j",
  12.         "з"=>"z","и"=>"i","й"=>"y","к"=>"k","л"=>"l",
  13.         "м"=>"m","н"=>"n","о"=>"o","п"=>"p","р"=>"r",
  14.         "с"=>"s","т"=>"t","у"=>"u","ф"=>"f","х"=>"h",
  15.         "ц"=>"ts","ч"=>"ch","ш"=>"sh","щ"=>"sch","ъ"=>"y",
  16.         "ы"=>"yi","ь"=>"'","э"=>"e","ю"=>"yu","я"=>"ya"
  17.       );
  18.       return strtr($str,$tr);
  19.   }

* This source code was highlighted with Source Code Highlighter.

Итак, для начала получим весь словарь из БД и запишем его в массив парами, где ключ — русское слово, значение — транслитерация.

  1.   $query = "SELECT ru_words FROM word_list";
  2.  
  3.   if($stmt = $this->conn->prepare($query))
  4.   {
  5.     $stmt->execute();
  6.     $stmt->bind_result($ru_word);
  7.     while($stmt->fetch())
  8.     {
  9.       $word_translit[$ru_word] = translitIt($ru_word);
  10.     }
  11.   }

* This source code was highlighted with Source Code Highlighter.

Далее проверяем наше введенное слово на наличие в словаре, если нету — делаем его транслитерацию:

  1. if(isset($word_list[$myWord]))
  2.       {
  3.         $correct[] .= $myWord;
  4.       }
  5.       else
  6.       {
  7.   $myWord = $this->translitIt($myWord);

* This source code was highlighted with Source Code Highlighter.

После этого запускаем цикл, который будет выбирать из массива те слова, расстояние Левенштейна между «метафонами» которых не будет превышать половину «метафона» введенного слова (грубо говоря, допускается до половины неправильно написанных согласных букв), потом, среди выбранных вариантов, снова проверяем расстояние, но по всему слову, а не по его «метафону» и подошедшие слова записываем в массив:

  1. foreach($word_translit as $n=>$k)
  2.     {
  3.       if(levenshtein(metaphone($myWord), metaphone($k)) < mb_strlen(metaphone($myWord))/2)
  4.       {
  5.         if(levenshtein($myWord, $k) < mb_strlen($myWord)/2)
  6.         {
  7.           $possibleWord[$n] = $k;
  8.         }
  9.       }
  10.     }

* This source code was highlighted with Source Code Highlighter.

Теперь зададим переменные, где расстояние Левенштейна будет равно заведомо большому числу, а «similar text» — заведомо малое число.

  1.     $similarity = 0;
  2.     $meta_similarity = 0;
  3.     $min_levenshtein = 1000;
  4.     $meta_min_levenshtein = 1000;

* This source code was highlighted with Source Code Highlighter.

Это нужно для определения максимального значения «подобности» между нашим словом и словами в массиве, а также минимального расстояния Левенштейна. Для начала найдем минимальное расстояние Левенштейна:

  1. foreach($possibleWord as $n)
  2. {
  3.   $min_levenshtein = min($min_levenshtein, levenshtein($n, $myWord));
  4. }

* This source code was highlighted with Source Code Highlighter.

И, аналогично, ищем максимальное значение «подобности» для тех слов, в которых расстояние Левенштейна будет минимальным:

  1. foreach($possibleWord as $n)
  2. {
  3.   if(levenshtein($k, $myWord) == $min_levenshtein)
  4.   {
  5.    $similarity = max($similarity, similar_text($n, $myWord));
  6.   }
  7. }

* This source code was highlighted with Source Code Highlighter.

Теперь запускаем цикл, который выберет все слова с наименьшим расстоянием Левенштейна и наибольшим значением «подобности» одновременно:

  1. foreach($possibleWord as $n=>$k)
  2. {
  3.   if(levenshtein($k, $myWord) <= $min_levenshtein)
  4.   {
  5.   if(similar_text($k, $myWord) >= $similarity)
  6.   {
  7.     $result[$n] = $k;
  8.   }
  9.   }
  10. }

* This source code was highlighted with Source Code Highlighter.

После этого определяем максимальное значение «подобности» между «метафонами» нашего слова и слов в массиве, и минимальное расстояние Левенштейна:

  1. foreach($result as $n)
  2. {
  3.   $meta_min_levenshtein = min($meta_min_levenshtein, levenshtein(metaphone($n), metaphone($myWord)));
  4. }
  5.  
  6. foreach($result as $n)
  7. {
  8.   if(levenshtein($k, $myWord) == $meta_min_levenshtein)
  9.   {
  10.    $meta_similarity = max($meta_similarity, similar_text(metaphone($n), metaphone($myWord)));
  11.   }
  12. }

* This source code was highlighted with Source Code Highlighter.

И получаем окончательный массив, который, в идеале, должен содержать одно слово:

  1. foreach($result as $n=>$k)
  2. {
  3.   if(levenshtein(metaphone($k), metaphone($myWord)) <= $meta_min_levenshtein)
  4.   {
  5.    if(similar_text(metaphone($k), metaphone($myWord)) >= $meta_similarity)
  6.    {
  7.    $meta_result[$n] = $k;
  8.    }
  9.   }
  10. }

* This source code was highlighted with Source Code Highlighter.

И возвращаем правильное слово, которое хранится как ключ:

  1. return key($meta_result);

* This source code was highlighted with Source Code Highlighter.

Плюс:

Точность определения слова довольно высокая, даже учитывая то, что я использовал словарь на 100 000 слов, который включает только нулевую форму и в списке слишком много слов, которые используются крайне редко (точнее, о которых я впервые слышу). Это, безусловно, портит результат.

Результат:

  • халадильнег -> холодильник
  • аффтамабэль -> автомобиль
  • матоцыгл -> мотоцикл
  • вэласэпэд -> велосипед
  • аформеть -> оформить
  • шына -> шина
  • превет -> привет
  • Но: пгевед -> перед

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

Минус:

Низкое быстродействие:

  • Вытянуть из базы 100 000 слов и записать из в массив: 0.322146177292
  • Первичный поиск по всему массиву из 100 000 слов: 0.995674848557
  • Поиск по массиву из того что осталось с учетом всего слова: 1.97887420654E-5
  • Поиск по массиву из того что осталось с учетом «метафонов» слова: 1.81198120117E-5

Тестировалось на: C2D E6550 (2.33GHz), 4Gb (DDR2-800).
Думаю, что это можно частично решить вытаскиванием из базы только тех слов, которые по длине отличаются от введенного на 1-2 символа.

Буду рад услышать от хабрасообщества более рациональные варианты использования фонетических алгоритмов, либо идеи по улучшению данного метода.

Ссылки:

Тут можно скачать весь код в одном классе.
А вот здесь базу русских слов, которую я использовал для тестов.
Благодарим пользователя Karroplan за отличнейшую базу, которая содержит 4 588 867 слов и словоформ.

Бывает, что нужно найти в списке слов наиболее похожие на заданное слово.
Например, если у вас есть список городов, и нужно найти среди них наиболее похожий на то, что ввёл пользователь, даже если при вводе пользователь допустил ошибки.

При этом, разумеется, есть мощные профессиональные системы поиска, такие как Sphinx, ElasticSearch, но не всегда имеет смысл их разворачивать и настраивать, ради одной небольшой задачи, либо когда вам только нужно сделать прототип поиска, чтобы проверить, даст ли он требуемые преимущества.

Для таких случаев я написал на php инструмент для нечеткого поиска среди массива строк.

В каких случаях может пригодится данный инструмент:

1. Поиск среди списка стран мира

2. Поиск среди названий страниц вашего сайта в админ-панели

3. Поиск среди уникальных имён ваших пользователей

и в других случаях, где количество записей списка не превышает несколько тысяч.

Я, например, написал этот инструмент для того, чтобы разделять поле ФИО, в которое люди вписывают имя, фамилию и отчество в произвольном формате, на отдельные поля Имя, Фамилия и Отчество, с соответствующими значениями.

Где взять:

Можно взять класс FastFuzzySearch прямо из репозитория:
https://github.com/MihanEntalpo/FastFuzzySearch

Также, можно установить FastFuzzySearch с использованием composer:

В вашем файле composer.json нужно прописать:

{
    "require": {
        "mihanentalpo/fast-fuzzy-search": "*"
    }
}

Установить:

Использование:

<?php
//Подключаем файл
require_once("FastFuzzySearch.php");
//Формируем список слов, среди которых будем искать (мужские русские имена на букву "А"):
$words = array(
    "Абакум", "Абрам", "Абросим", "Аввакум", "Август", "Авдей", "Авдий", 
    "Авель", "Авенир", "Аверий", "Аверкий", "Аверьян", "Авксентий", "Авраам", 
    "Авраамий", "Аврам", "Аврамий", "Аврелиан", "Автоном", "Агап", "Агапий", 
    "Агапит", "Агафангел", "Агафон", "Аггей", "Адам", "Адриан", "Азар", 
    "Азарий", "Акакий", "Акила", "Аким", "Акиндин", "Акинф", "Акинфий", 
    "Аксён", "Аксентий", "Александр", "Алексей", "Алексий", "Альберт", 
    "Альфред", "Амвросий", "Амос", "Амфилохий", "Ананий", "Анастасий", 
    "Анатолий", "Андрей", "Андриан", "Андрон", "Андроний", "Андроник", 
    "Анект", "Анемподист", "Аникей", "Аникий", "Аникита", "Анисий", 
    "Анисим", "Антиох", "Антип", "Антипа", "Антипий", "Антон", "Антонин", 
    "Антроп", "Антропий", "Ануфрий", "Аполлинарий", "Аполлон", "Аполлос", 
    "Ардалион", "Ареф", "Арефий", "Арий", "Аристарх", "Аристид", "Аркадий", 
    "Арнольд", "Арон", "Арсен", "Арсений", "Арсентий", "Артамон", "Артём", 
    "Артемий", "Артур", "Архип", "Асаф", "Асафий", "Аскольд", "Афанасий", 
    "Афиноген", "Афинодор", "Африкан"
);
//Создаём объект для быстрого поиска:
$ffs = new FastFuzzySearch($words);
 
//Вот что ввёл пользователь (да, с ошибкой):
$input = "аниисий";
 
//Находим 5 наиболее похожих результатов:
$results = $ffs->find($input, 5);
 
//Выводим результат:
echo "Наиболее похожие на введённое слово '$input':n";
foreach($results as $res)
{
    echo $res['word'] . ", процент сходства: " . round($res['percent']*100, 2) . "%n";
}

Программа выведет:

Наиболее похожие на введённое слово 'аниисий':
анисий, процент сходства: 66.67%
аникий, процент сходства: 25%
алексий, процент сходства: 20%
анастасий, процент сходства: 19.05%
аникей, процент сходства: 16.67%

Принцип действия

Работа программы основана на алгоритме, основанном на так называемых n-gram’ах, похожем на алгоритм шинглов.

Для понимания её работы можно взглянуть на вот такую простую функцию для нечёткого сравнения двух строк (приведена для примера, в FastFuzzySearch она не используется):

/**
 * Нечеткое сравнение строк
 * @param первая строка $str1
 * @param вторая строка $str2
 * @param минимальный кусочек интервала сравнения $minSize
 * @param максимальный кусочек интервала сравнения $maxSize - если равен 0, то будет задан автоматически
 * @param string $encoding кодировка текста (обычно - utf-8)
 * @return float число от 0 до 1 - процент совпадения.
 */
function fuzzyCompare($str1,$str2,$minSize=2,$maxSize=4,$encoding='utf-8', $minPercent=1)
{
    $s1 = mb_strlen($str1,$encoding);
    $s2 = mb_strlen($str2,$encoding);
    if ($maxSize==0) $maxSize = min($s1,$s2);
    $maxSize = min($maxSize,$s1,$s2);
    $cnt = 0;
    $num = 0;
    if ($s1>$s2)
    {
        $from = $str1;
        $where = $str2;
        $S = $s1;
    }
    else
    {
        $from = $str2;
        $where = $str1;
        $S = $s2;
    }
 
    for($size = $minSize; $size<$maxSize; $size++)
    {
        for ($i=0;$i<$S - $size+1; $i++)
        {
            $cnt+=1;
            $part = mb_substr( $from,$i,$size,$encoding);
            if ( mb_strpos($where,$part,0,$encoding)!==FALSE )
            {
                    $num+=1;
            }
        }
    }
 
    if ($cnt==0) return 0;
    return $num/$cnt;
}

принцип её действия таков:
1) Разбиваем одну строку (более длинную) на кусочки переменной длины, например, из 2, 3, 4 последовательных символов, считаем количество этих кусочков. Например, слово «алгоритм», при разбитии на кусочки от 2-х до 4-х байт даст нам: «ал», «лг», «го», «ор», «ри», «ит», «тм», «алг», «лго», «гор», «ори», «рит», «итм», «алго», «лгор», «гори», «орит», «ритм» (итого 18 штук)
2) Считаем, какие из кусочков есть во второй строке. Например, в слове «алкоголь», из представленных выше кусочков есть «ал» и «го» (2 штуки)
3) Делим количество кусочков, которые есть во второй строке, на общее их количество, и получаем результирующий «процент сходства». Считаем 2/18 = 0.11111 или 11.1% сходства между словами «алгоритм» и «алкоголь».

Изменяя параметры $minSize, $maxSize можно настраивать качество сравнения (для каждой задачи эти параметры могут быть разными).

Разумеется, можно было бы построить механизм поиска наиболее схожих слов на базе этой функции, и в первой версии так оно и было.

Сейчас же алгоритм FastFuzzyCompare, хотя и аналогичен этой функции, но ориентирован на быстрый поиск похожих слов среди заданного массива, благодаря построенному индексу:
1. При инициализации класса (в конструкторе, или в функции init) строится полный индекс по всем кусочкам всех слов, среди которых надо искать похожие.
2. При вызове функции find анализируемое слово разбивается на кусочки, и среди индекса определяется, какие из них существуют в базе, и для них составляется список слов, содержащих данные кусочки. После этого, проводится суммирование найденных кусочков для каждого слова, и слова сортируются по убыванию отношения найденных кусочков к общему количеству кусочков в данном слове.

Данный подход позволяет добиться высокого быстродействия, в десятки раз превышающего метод последовательного сравнения слов с помощью функции fuzzy_compare, а также с помощью функций levenshtein и similar_text (которые, кстати, дают совсем небольшое ускорение по отношению к fuzzy_compare)

Ускорение инициализации

Поскольку время инициализации (формирования исходного индекса) пропорционально количеству слов, а также, квадрату средней длины слова, при больших наборах слов и длинных словах инициализация может занимать большее время чем хотелось бы. Для решения этой проблемы существует механизм сериализации индекса.
Для этого есть две функции — serializeIndex, для преобразования индекса в строку и unserializeIndex для обратного преобразования из строки во внутреннее состояние. Использование сериализации индекса позволяет многократно увеличить скорость инициализации поиска.
Использование:

//Инициализируем объект как обычно
$ffs = new FastFuzzySearch($words);
//Получаем сериализованный индекс (это строка)
$index = $ffs->serializeIndex();
//Сохраняем его (не обязательно в файле, можно в memcached, redis или даже mysql)
file_put_contents("./index.cache", $index);
....
//В следующий раз создаём объект без указания массива слов:
//при этом объект находится в неинициализированном состоянии, и пользоваться им ещё нельзя.
$ffs = new FastFuzzySearch();
//Прочитаем сериализованный индекс (опять же, из memcached будет быстрее):
$index = file_get_contents("./index.cache");
//Загрузим сериализованный индекс в объект:
$ffs->unserializeIndex($index);

Быстродействие

Для проверки быстродействия был написан скрипт, формирующий массивы слов разного размера, и запускающий поиск среди них тремя способами: с помощью быстрого нечёткого поиска, с помощью расстояния левештейна и с помощью функции similar_text. Такие функции как metaphone и soundex я не стал использовать, так как назначение у них другое — находить похожие по звучанию слова. В моём же случае, поиск найдёт даже не слишком похожие по звучанию, но, при этом, близкие по нечёткому сравнению.

Как раз для этой цели в класс FastFuzzySearch добавлены функции findByLevestaine и findBySimilarText.
Сам код проверки на быстродействие находится в файле /examples/example2.php из упомянутого выше репозитория.

При проверке производится поиск 200 слов по массиву, состоящему из 21376 слов (это список всех более-менее крупных городов России, повторённый 16 раз для увеличения сложности).

Результат:

Testing FastFuzzyCompare...
Testing FindByLevenstaine...
Testing FindBySimilarText...
Поиск 200 среди 21376 слов
Результаты:
Инициализация с нуля: 3.4702 сек.
Сериализация индекса: 0.03133 сек.
Инициализация из сериализованного индекса: 0.10813 сек.
FastFuzzySearch: 0.50727 сек.
Levensteine:     21.96609 сек. (медленнее чем find в 43.3 раз)
SimilarText:     20.13451 сек. (медленнее чем find в 39.69 раз)

Здесь:
Инициализация с нуля — это построение поискового индекса FastFuzzySearch по переданному массиву.
Сериализация индекса — это преобразование поискового индекса в строку для хранения её в кэше, и быстрой загрузки (если ваш массив, по которому требуется искать, не меняется каждый раз — имеет смысл вместо перестроения индекса просто закешировать его где-то (в memcache, в файле, в базе данных), чтобы быстро загружать перед использованием)
Инициализация из сериализованного индекса — это как раз процесс загрузки сериализованного индекса, вместо построения с нуля. Как видно, этот процесс в 30 раз быстрее чем инициализация с нуля, причём, чем больше слов в индексе, тем больше этот разрыв.

Как несложно заметить, функция FastFuzzySearch->find работает достаточно быстро, чтобы использовать её в случаях, когда требуется, например, подсказывать пользователю исправленный вариант введённого им с ошибками запроса, когда вариантов не слишком много.

Зависимость быстродействия от входных данных

С помощью скрипта из файла example3.php можно проверить, как меняется быстродействие алгоритма в зависимости от количества данных, и их свойств.

Выводы сделанные в ходе экспериментов:

1) При росте количества строк в индексе довольно быстро растёт объём индекса (в байтах), и время его построения, однако скорость поиска снижается весьма медленно.

2) При росте длины строк в индексе, вплоть до 640 байт, размер индекса растёт очень быстро, вплоть до 300МБ, полученных в эксперименте, скорость же поиска практически никак не меняется.

3) При увеличении длины искомого слова, время поиска увеличивается пропорционально квадрату длины слова.


(PHP 4 >= 4.0.2, PHP 5, PHP 7, PHP 8)

pspell_checkПроверяет слово

Описание

pspell_check() проверяет орфографию слова.

Возвращаемые значения

Возвращает true, если орфография верна, в противном случае возвращает false.

Список изменений

Версия Описание
8.1.0 Параметр dictionary теперь ожидает экземпляр PSpellDictionary; ранее ожидался ресурс (resource).

Примеры

Пример #1 Пример использования pspell_check()


<?php
$pspell
= pspell_new("en");

if (

pspell_check($pspell, "testt")) {
echo
"Это верное написание";
} else {
echo
"К сожалению, неправильное написание";
}
?>

si at youbeenx dot com

18 years ago


I felt that it would help to expand on batch spell checking using this function. The previously posted example implodes using spaces as the separator for each word. There are however situations in which doing this will not return the desired result. For example, "Hello, I like coding." will return an array with two problems, "Hello," and "coding.", both these words are spelt correctly, but pspell_check() will deem them as spelled incorrectly because a comma or a period is being passed in to the function along with the word. The following example allows you to extract only the words (using regular expressions to ignore grammar such as periods or commas) in to an array and then add in html font tags to highlight all words spelled incorrectly red before returning the string.

<?

Function SpellCheck($string) {

    $pspell_link = pspell_new("en");
    preg_match_all("/[A-Z']{1,16}/i", $string, $words);

    for ($i = 0; $i < count($words[0]); $i++) {

        if (!pspell_check($pspell_link, $words[0][$i])) {

            $string = str_replace($words[0][$i], "<font color="#FF0000">" . $words[0][$i] . "</font>", $string);       

        }

    }

    return $string;

}

?>


digit6 at gmail dot com

6 years ago


A better pattern for splitting the words of a query up is:

preg_match_all('/[^w']/+/', $query, $word)
// $words has the words.


Jcart

17 years ago


<?php//should be using explode instead of implode
//$word = implode(" ", $message);
$word = explode(" ", $message);
foreach(
$word as $k => $v) {
   if (
pspell_check($pspell_link, $v)) {
      echo
"spelled right";
   } else {
      echo
"Sorry, wrong spelling";
   };
};
?>

chris at candm dot org dot uk

18 years ago


<?php
/*
I had to write these routines to highlight spellings in a WYSIWYG editor.
pspell() barfed at HTML tags and entities, so this code deals with them.
ClearSpell() allows you to clear up the spellchecker mark up afterwards.*/
?>
<html>
<head>
<style>
acronym.spell
{
text-decoration:underline;
color:red;
cursor:help;
}
</style>
</head>
<body>
<?php
    $t
= "<font color=blue>text herre &amp; some more</font>";
    echo
"Before:$t";
   
$t = SpellCheck($t);
    echo
"<hr>After SpellCheck: $t";
   
$t = ClearSpell($t);
    echo
"<hr>After ClearSpell: $t";
?>
</body>
</html>

<?phpfunction SpellCheck($text)
{
//depends on fnSpell()
// Extracts text from HTML code. In addition to normal word separators,  HTML tags
// and HTML entities also function as word delimiters
$pspell_link = pspell_new("en"); //0. Get the dictionary
   
$strings = explode(">", $text);  //1. Split $text on '>' to give us $strings with 0 or 1 HTML tags at the end
   
$nStrings = count($strings);

    for (

$cStrings=0; $cStrings < $nStrings; $cStrings++)
    {
       
$string = $strings[$cStrings]; //2. For each string from 1if ($string =='')
            continue;
$temp  = explode('<', $string); //2.1   Split $string from $strings on '>' to give us a $tag and $cdata
       
$tag = $temp[1];
       
$cdata = $temp[0];$subCdatas = explode(";", $cdata);  //2.2 Split &cdata on ';' to give $subcdatas with 0 or 1 HTML entities on the end
       
$nSubCdatas = count($subCdatas);    //2.3   For each $subCdata from $subcdatas in 2.2for ($cSubCdatas = 0; $cSubCdatas < $nSubCdatas; $cSubCdatas++)
        {
           
$subCdata = $subCdatas[$cSubCdatas];

            if (

$subCdata == '')
                continue;
$temp = explode('&', $subCdata); //2.3.1     Split the $subCdata on '&' to give us a $subCdataEntity and a $subCdataWithNoEntities
           
$subCdataEntity = $temp[1];
           
$subCdataWithNoEntities = $temp[0];
           
$subCdataWithNoEntities = fnSpell($pspell_link, $subCdataWithNoEntities); //2.3.2     Spellcheck the $cdataWithNoEntitiesif (!$subCdataEntity ) //2.3.3        Put the $subCdataEntity, a '&' and the $cdataWithNoEntities back into the $subCdata from 2.2
               
$subCdata = $subCdataWithNoEntities;
            else
               
$subCdata = $subCdataWithNoEntities. '&' . $subCdataEntity . ';' ;$subCdatas[$cSubCdatas] = $subCdata; //2.3.4        Put the $subCdata back into the array of $subCdatas
       
}$cdata = implode("", $subCdatas); //2.4    Implode the array of $subCdatas back into the $cdataif ($tag) //2.5    Put the $tag , '>' and $cdata back into $string
           
$string = $cdata . '<' . $tag . '>';
        else
           
$string = $cdata;$strings[$cStrings] = $string; //2.6    Put $string back in its place in $strings
   
}$text = implode('', $strings);     //3  Implode the $strings back into $text
   
return $text;

}

function

fnSpell($pspell_link, $string)
{
preg_match_all("/[A-Z']{1,16}/i", $string, $words);

   for (

$i = 0; $i < count($words[0]); $i++)
   {
       
$currentword = $words[0][$i];

        if (!

pspell_check($pspell_link, $currentword))
        {
           
$wordarray = pspell_suggest($pspell_link, $currentword);
               
$words = implode(', ', $wordarray);
               
$suggest = "<acronym class='spell' title='$words'>$currentword</acronym class='spell'>";
           
$string = str_replace($currentword, $suggest, $string);
        }

    }
    return

$string;
}

function

ClearSpell($text)
{
   
$strings = explode(">", $text);
   
$nStrings = count($strings);

    for (

$cStrings=0; $cStrings < $nStrings; $cStrings++)
    {
       
$string = $strings[$cStrings];

        if (

$string =='')
            continue;
$temp  = explode('<', $string);
       
$tag = $temp[1];
       
$cdata = $temp[0];

        if (

strstr($tag, 'acronym') && strstr($tag, "class='spell'") )
           
$string = $cdata;
        else
           
$string = $cdata . '<' . $tag . '>';$strings[$cStrings] = $string;
    }
$text = implode('', $strings);
    return
$text;
}
?>


У меня есть таблица базы данных, называемая country . Он имеет две колонки: ID и name .

Когда я хочу искать 'paris' , но ошибочно записал слово 'pares' ( 'e' вместо 'i' ), я не получу никакого результата из БД.

Я хочу, чтобы система предлагала похожие слова, которые могли бы помочь в поиске.

Итак, я ищу помощь при написании скрипта, который предлагает предложения из БД, содержащие похожие слова: paris, paredes, … и т. Д.

  • PHP заменяет случайное слово строки
  • Удалите все, кроме букв из строки PHP.
  • Передача строки Javascript в php String
  • вставить массив в строку, разделенную запятыми, из запроса mysql
  • preg_replace, вызывающий удаление знаков доллара

В PHP вы должны использовать metaphone более точную, чем soundex .

Но ваша проблема заключается в получении данных из базы данных. Вы не упомянули БД. В MySQL вы можете использовать функцию SOUNDEX . Вам просто нужно изменить предложение where в запросе из

 ...where city = '$input_city' 

в

 ... where soundex(city) = soundex('$input_city') 

или даже лучше, вы можете использовать SOUNDS LIKE оператора как

 ... where city sounds like '$input_city' 

soundex вернет числовой код для слова, представляющего его звук. Слова, похожие на звук, будут иметь одинаковый код soundex. У вас может быть таблица со словами и их звуковыми кодами, которые вы могли бы использовать для поиска похожих звуковых слов. Затем вы можете отсортировать их, используя их левенштейновское расстояние.

Если вы ищете что-то более простое, и вы просто хотите обрабатывать опечатки в ваших запросах БД, вы можете сделать

select * from country where city SOUNDS LIKE 'Paris' вместо select * from country where city='Paris'

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

Моя идея:

  • Поиск пользователя по имени
  • Нет точных результатов
  • Получить все имена из db
  • Использование levenshtein вычисляет наиболее точную подсказку для возврата пользователем

Если вы используете MySQL, вам нужно использовать инструкцию MATCH() AGAINST() , где MATCH() получает список столбцов FULLTEXT разделителями- FULLTEXT а AGAINST() присваивается вашей строке, чтобы она соответствовала. Оператор возвращает релевантность вашего соответствия (от 0 до 1), которое вы можете использовать для определения того, следует ли возвращать строки.

Дополнительная информация о сайте MySQL .

Редактировать: звуковые предложения – это хорошие идеи, однако определенные орфографические ошибки полностью изменят произношение слова, и, таким образом, вы не сможете предоставить хорошие предложения, если вы используете этот метод.

Здравствуйте, стоит задача реализовать полнотекстовый поиск по сайту.
К примеру в базе данных имеется таблица products, в ней поля title, description, image. Как реализовать поиск, чтобы он искал по ключевым словам, например «Визитки» «Везитки» «Визиктки», т.е. если пользователь ввел запрос с ошибкой, ему все равно выдало нужный результат.
Заранее спасибо.

Мелкий's user avatar

Мелкий

21.3k3 золотых знака27 серебряных знаков53 бронзовых знака

задан 26 янв 2017 в 14:24

vovaxxx's user avatar

3

Вам нужен фонетический поиск. Вот реализация на php и статья о нем на Хабре

Там есть функция, преобразующая слова в фонетический код:

dmstring('Арнольд Шварцнеггер') //== 096830 479465
dmstring('Орнольд Шворцнегир') //== 096830 479465

Получаете код и заносите его в БД в отдельное поле.

Затем при поиске кодируйте поисковый запрос с помощью этой-же функции и ищите по полю с фонетическими кодами.

ответ дан 26 янв 2017 в 14:29

Crantisz's user avatar

CrantiszCrantisz

9,7102 золотых знака15 серебряных знаков49 бронзовых знаков

Понравилась статья? Поделить с друзьями:
  • Php ошибки в sql запросе
  • Php ошибка файл не найден
  • Php ошибка соединения с localhost
  • Php ошибка синтаксического анализа xml некорректно
  • Php ошибка при запуске приложения