среда, 24 ноября 2010 г.

Новости Рецептория.

Открылся мой новый сайт - Рецепторий, посетить его вы можете нажав по ссылке выше.
Коротко о нем - это само собой сборник рецептов, еще один? скажет вы, зачем, ведь их и так на каждом шагу? Да вы правы, но все они как правило либо страдают дизайном в стиле 90-х, либо абсютным неудобством, перегруженностью.

Кроме этого конечно немного интересных статей, рассчитанх как на начинающих кулинаров, так и на тех кто в жизни ни одного рецепта не прочитал, и ни одного блюда не приготовил. Статьи разбиты на категорииНапример С чем подавать спагетти

И собственно зюминка сайта - это справочник по продуктам питания, посмотрите например вот страница описания коньяка Jerez oloroso.
Смысл во всем этом следующий, пользователи могут голосовать на каждый продукт, и оставлять комментарии, в результате у продуктов составляется рейтинг, и в итоге другой пользователь который хочет выбрать вино зайдет в интересующий его раздел и сможет сделать для себя какие то выводы опираясь на цену и отзывы о товаре. Получается некий аналог Яндекс маркета, но для продуктов питания. Может это и не так востребовано, я не знаю, но будем надеятся что кому нибудь это все же пригодится! Я например частенько приходя в магазин думаю что лучше купить, как здорово если бы был такой ресурс.

На этом сайте я постарался реализовать все для удобного и непринужденного общения на тему кулинарии, ничего лишнего, но все неободимое есть. Я постарался сделать для вас максимально удобный поиск (почитать о его реализации можно тут), ведь, как вы наверно не раз замечали поиск сейчас зачастую ставят для галочки, ибо найти там что либо совершенно нереально.

Если вам понравился проект вы можете поддержать его развитие, поставить оценки каким нибудь рецептам, или добавить свой. Сообщить о нем своим друзьям или поставить в на подпись в любимой социальной сети или на форуме. Кстати для форумов вы можете использовать эту картинку

http://receptoriy.ru/img/userbar.jpg

понедельник, 15 ноября 2010 г.

Простой поисковый движок на PHP / MySQL


Данную статью я писал на хабр в песочницу. Автором статьи являюсь я, так что если будет копипастить, ставьте на меня ссылку. =)

Встала передо мной задача, написать более менее вменяемый поиск для сайта с mysql базой в условиях шаред хостинга. Первым делом конечно же решил поскать в инете на эту тему, какие есть там готовые решения или хотя бы подсказки. Вариантов решения оказалось бы множество, однако — часть из них совершенно неприемлема в силу своей несуразнозности и бесполезности, другая часть (например тот же Sphynx) тоже не подходит, тк хостинг, как мы уже условились — шаред. Итак, раз мы ничего не нашли, надо писать свое решение.

Постановка задачи
  1. Пользователь мог найти то что искал
  2. Поиск шел в базе данных
  3. Исправлять неправильную раскладку на клавиатуре
  4. Исправлять ошибки в орфографии
  5. Учет морфологии

Предположим есть таблица table1 по которой идет поиск. Давайте сразу разберем запрос. Кроме этого нам еще понадобится таблица ictionary. Пусть пользователь ищет Martini asti. Как же будет выглядеть запрос?

SELECT id, head, type, rate, img, country, ing,
MATCH (head) AGAINST ('martini asti' IN BOOLEAN MODE) as relev FROM table
WHERE( head LIKE '%martini%asti%'&& approve = '1')
ORDER BY relev DESC

Пока что ничего особенного, все делается предельно просто, можно вставить в запросе вместо пробелов знаки %. Теперь вспомним что наша основная задача исправлять ошибки, поэтому вспомним что MATCH AGAINST сам по себе исправлять ошибки не умеет, учитывает только целые слова и игнорирует слова меньше чем в 4 знака, а значит пишем функцию:

function prepareSearch($quote) {
    $searc_ins['simple']= $quote;
    $temp_query_arr = explode(" ", $quote);
        $k=0;
        foreach ($temp_query_arr as $val) {
            $temp_w = main::str_split_utf8($val);
            $glue[0] = "";
            $glue[1] = "";
            $glue[2] = "";
            $glue[3] = "";
            $k=0;
            foreach ($temp_w as $kwy => $value) {
                foreach ($glue as $kwy1 => $value1) {
                    if (!empty($temp_w[$kwy-$kwy1]))
                        $glue[$kwy1]    .= $temp_w[$kwy-$kwy1];
                    else
                        $glue[$kwy1]    .= "0";
                }
                if($k==3) {
                    $k=0;
                    $glue[0] .= " ";
                    $glue[1] .= " ";
                    $glue[2] .= " ";
                    $glue[3] .= " ";
                }
                else $k++;
                $lastSymbol = $kwy;
            }
            foreach ($glue as $kwy1 => $value1) {
                $k4= $k;
                $k3 = $kwy1;
                while ($k4<=3) {
                    if (!empty($temp_w[$lastSymbol-$k3+1])) {
                        $glue[$kwy1] .= $temp_w[$lastSymbol-$k3+1];
                        $k3--;
                        //$break = false;
                    }
                    else {
                        //$break = true;
                        $glue[$kwy1] .= "0";
                    }
                    if($k4==3) {
                        if (empty($temp_w[$lastSymbol-$k3+1])) break;
                        $k4=0;
                        $glue[$kwy1] .= " ";
                    }
                    else $k4++;
                }
            }
 
            $search_words['q'][] = $glue[0];
            $search_words['alt'][] = $glue[1];
            $search_words['alt2'][] = $glue[2];
            $search_words['alt3'][] = $glue[3];
        }
        foreach (array("q","alt","alt2","alt3") as $v) {
            $fst_t = TRUE;
            foreach ($search_words[$v] as $val) {
                if (!$fst_t) $searc_ins[$v] .= " ";
                $searc_ins[$v] .= str_replace(" 0000", "", $val);
                $fst_t = FALSE;
            }
        }
 
        return $searc_ins;
}

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

mart ini0 asti ьфкн штш0 фыеш

Вместо недостающих знаков вставляем например 0. Лучше вставить какой нибудь знак, но не все знаки MATCH AGAINST воспринимаются. Но это еще не все, мы также должны учитывать снос на 1, 2 и 3 буквы. Зачем? А затем что если юзер сделал ошибку не в том месте где мы рассчитывали, а не дай бог еще и 2 ошибки, поиск загнется, и выдаст какую то чушь. Например если вместо martyni asti будет maryini asti то для нашего скрипта это будет и тоже самое что asdfini asti, то есть на выходе для запроса maryini asti мы на самом деле получаем массив $search_words из 5 значений:

martyni asti
mary ini0 asti ьфкн штш0 фыеш
0mar yini 0ast i000 0ьфк нштш 0фые ш000
00ma ryin i000 00as ti00 00ьф кншт ш000 00фы еш00
000m aryi ni00 000a sti0 000ь фкнш тш00 000ф ыеш0

Теперь вспомним что за таблица dictionary и зачем она нужна. В ее структуре 5 полей, это simple, сам поисковй запрос, и 4 альта, в них содержаться значения которые мы с вами только что получили. Ну теперь дело за малым, сформировать запрос, у нас должно получиться что то вроде

SELECT simple,
MATCH (q) AGAINST ('mary ini0 asti ьфкн штш0 фыеш' IN BOOLEAN MODE) as relev0 ,
MATCH (alt) AGAINST ('0mar yini 0ast i000 0ьфк нштш 0фые ш000' IN BOOLEAN MODE) as relev1 ,
MATCH (alt2) AGAINST ('00ma ryin i000 00as ti00 00ьф кншт ш000 00фы еш00' IN BOOLEAN MODE) as relev2 ,
MATCH (alt3) AGAINST ('000m aryi ni00 000a sti0 000ь фкнш тш00 000ф ыеш0' IN BOOLEAN MODE) as relev3
FROM dictionary
ORDER BY relev0+relev1+relev2+relev3 DESC, LENGTH(simple) ASC
LIMIT 1

В запросе мы считаем сумму релевантности для каждого поля, и делаем по ней упорядочивание, а также по длине чтобы более короткие записи попадали вперед. Но в нашей таблице еще нет значений, как их туда занести, да очень просто: когда пользователь использует поиск, и находит что то то мы вместо SELECT делаем INSERT. Этим мы убьем двух зайцев сразу, у нас в словарь не будет попадать всякий негатив, и мы будем иметь точное представление о том что же ищут наши пользователи. В словарь также можно добавить поле числовое поле сортировки, и увеличивать его после каждого удачного поиска. Вот и все. Мы получили догадку нашей поисковой машины о том что же мог искать на самом деле наш неграмотный юзер, теперь с ним можно делать уже что угодно, сразу найти результаты или сообщить пользователю что он видимо где то ошибся, и искал вероятнее всего martini asti. Или же можно к точному совпадению добавить результат догадки или догадок если их несколько. ИМХО в большинстве случаев достаточно просто выдать сообщение об опечатке. Все это в действии можно посмотреть тут.

Данная идея не является каким то супер решением, просто когда я это писал, ответа на свой вопрос не нашел.

Я писал это дело на php, сейчас уже думаю о том что слова разбивать можно на клиентской стороне, правда там есть минусы, например не получится использовать поисковые плагины для мозиллы и тд.

В дальнейшем я постараюсь эту идею развить, так что если она вам нравится, следите за моим блогом.