Поиск предложений с определёнными словами, Сниппет

anufriy

Новичок
Поиск предложений с определёнными словами, Сниппет

Понимаю, что задача кажется простой, но я довольно долго не могу её решить с помощью регвыров.
Задача состоит в том, чтобы выводить результаты поиска как в яндексе, в виде обрывков предложений с подсвеченными искомыми словами.
Для этого:

  • Передаю набор словоформ.
    Передаю текст в котором искать
    Регвыром ищу предложения с вхождениями
    Сортирую по колличеству вхождений
    Режу до приемлимой длинны учитывая, чтоб не вырезать вхождения.
    Собираю несколько лучших предложений в результат. Короче аля Яндегз.

Вот немного облегченый алгоритм:
PHP:
$text = 'Реально зарегистрированный разговор между испанцами и американцами на частоте 
	"Экстремальные ситуации в море" навигационного канала 106 в проливе Финистерра
	(Галиция) 16/10/97.
	Испанцы: (помехи на заднем фоне) ... говорит А-853, пожалуйста, поверните на 15 градусов 
	на юг, во избежание столкновения с нами. Вы движетесь прямо на нас, расстояние 25 морских миль.
	Американцы: (помехи на заднем фоне)... советуем вам повернуть на 15 градусов на север, 
	чтобы избежать столкновения с нами.
	Испанцы: Ответ отрицательный. Повторяем, поверните на 15 градусов на юг во избежание 
	столкновения.
	Американцы (другой голос): С вами говорит капитан корабля Соединенных Штатов Америки. 
	Поверните на 15 градусов на север во избежание столкновения.
	Испанцы: Мы не считаем ваше предложение ни возможным, ни адекватным, советуем вам повернуть 
	на 15 градусов на юг, чтобы не врезаться в нас.
	Американцы (на повышенных тонах): С ВАМИ ГОВОРИТ КАПИТАН РИЧАРД ДЖЕЙМС ХОВАРД, КОМАНДУЮЩИЙ 
	АВИАНОСЦА USS LINCOLN, ВОЕННО-МОРСКОГО ФЛОТА СОЕДИНЕННЫХ ШТАТОВ АМЕРИКИ, ВТОРОГО ПО ВЕЛИЧИТЕ 
	ВОЕННОГО КОРАБЛЯ АМЕРИКАНСКОГО ФЛОТА.
	НАС СОПРОВОЖДАЮТ 2 КРЕЙСЕРА, 6 ИСТРЕБИТЕЛЕЙ, 4 ПОДВОДНЫЕ ЛОДКИ И МНОГОЧИСЛЕННЫЕ КОРАБЛИ ПОДДЕРЖКИ. 
	Я ВАМ НЕ "СОВЕТУЮ", Я "ПРИКАЗЫВАЮ" ИЗМЕНИТЬ ВАШ КУРС НА 15 ГРАДУСОВ НА СЕВЕР. В ПРОТИВНОМ 
	СЛУЧАЕ МЫ БУДЕМ ВЫНУЖДЕНЫ ПРИНЯТЬ НЕОБХОДИМЫЕ МЕРЫ ДЛЯ ОБЕСПЕЧЕНИЯ БЕЗОПАСНОСТИ НАШЕГО КОРАБЛЯ. 
	ПОЖАЛУЙСТА, НЕМЕДЛЕННО УБЕРИТЕСЬ С НАШЕГО КУРСА!!!!
	Испанцы: С вами говорит Хуан Мануэль Салас Алкантара. Нас 2-е человек. Нас сопровождают пес, 
	ужин, 2 бутылки пива и канарейка, которая сейчас спит. Нас поддерживают радиостанция 
	"Cadena Dial de La Coruna" и канал 106 "Экстремальные ситуации в море". Мы не собираемся никуда 
	сворачивать, учитывая, что мы находимся на суше и являемся маяком А-853 пролива Финистерра 
	Галицийского побережья Испании. Мы не имеем ни малейшего понятия, какое место по величине мы 
	занимаем среди испанских маяков. Можете принять все е...ные меры, какие вы считаете необходимыми 
	и сделать все что угодно для обеспечения безопасности вашего е...го корабля, который разобьется 
	вдребезги об скалы. Поэтому еще раз настоятельно рекомендуем вам сделать наиболее осмысленную 
	вещь: изменить ваш курс на 15 градусов на юг во избежания столкновения.
	Американцы: Ok, принято, спасибо.';
	
	$words = array('градусов', 'корабля', 'избежание'); //Искомые слова и словоформы

	
	
	$length = 200; //Максимальная длинна предложения в выводе
		
	//убираем тэги
	$text = preg_replace('@<script[^>]*?>.*?</script>@si',' ',$text);
	$text = preg_replace('@<style[^>]*?>.*?</style>@si',' ',$text);
	$text = preg_replace('@<[\/\!]*?[^<>]*?>@si',' ',$text);

	//убираем двойные пробелы
	$text = preg_replace('/\s+/', ' ', '.'.$text);	
	$text = str_replace(array('!', '?'), '.', $text);
	
	preg_match_all('/\.[^\.]*(?:(?:('.implode(')|(',$words).')).*)+[^\.]*\./iU', $text, $matches, PREG_SET_ORDER);
	
	//Создаём массив с весом слова в предложении
	$count = array();
	foreach($matches as $k => $v)
	{		
		$d = $v[0];
		unset($v[0]);
		$v = array_flip($v);
		unset($v[NULL]);
		if(!is_numeric($count[$k])) $count[$k] = 0;
		$count[$k]+=count($v);
	}
	
	//Сортируем по колличеству вхождений
	arsort($count);
	
	//Долго и нудно уменьшаем предложения в размере
	$i = 0;
	$text = '';
	foreach($count as $k => $weight)
	{
		$text .= "<hr/>";
		if(++$i > 2) break;

		$t = $matches[$k][0];

		$last = 0;
		$last_len = 0;
		foreach($words as $word)
		{
			$v = strrpos($t, $word);
			if($v > $last) 
			{
				$last = $v;
				$last_len = strlen($word);
			}
		}
		
		if($last < $length + 10)
		{
			$text .= substr($t,0,$length);
			continue;
		}
		
		$first = strlen($t);
		foreach($words as $word)
		{
			$v = strpos($t, $word);
			if(is_numeric($v) && $first > $v) 
			$first = $v;
		}
		
		if($last - $first < $length)
		{
			$text .= '...'.substr($t,$last - $length + $last_len, $length);
			continue;
		}
		
		$text .= substr($t,$first,$length);
		
		$text .= $t;
	}
	
	//Ожирняем искомые слова
	foreach($words as $word)
	{
		$text = str_replace($word,'<b>'.$word.'</b>',$text);
	}
	
	echo $text;
В упор не пашет регулярное выражение (это самое главное)

А весь код я привёл для того чтобы узнать мнение гуру, как в данном случае уменьшить ресурсоёмкость.
Я попытался поискать аналоги в нете и на форуме, но не нашёл. Понял только что это называется "Сниппет".

PS Сорри за длинный текст в примере - это чтоб тем кто историю не знает не было обидно и они могли узнать чем закончилось.
 

anufriy

Новичок
А какая тогда идея реализации данной задачи?
Ведь например яндекс старается вывести 2 полных предложения из всего индексированного текста, в которых больше всего искомых слов и они находятся ближе всего к друг другу, а также идут в похожей последовательности.
Я не пытаюсь реализовать 2 последних пункта, но хотелось бы хотя б основное!...
 

ksnk

прохожий
Решение какое-то странное
Правильно ли, что предполагается указывать еще и словоформы, к примеру
PHP:
$words = array('градуса','градусов', 'корабля', 'кораблю', 'избежание'); //Искомые слова и словоформы
Каким образом php узнает, что корабля-кораблю - словоформа одного и того-же слова?
Так что такой подход может только определить место в тексте, с наибольшей плотностью распределения искомых слов. Соответственно строить рег таким образом - (word1)|(word2) - не имеет смысла, можно обойтись (word1|word2 ...) и т.д.
 

anufriy

Новичок
Автор оригинала: ksnk
Решение какое-то странное
Правильно ли, что предполагается указывать еще и словоформы, к примеру
PHP:
$words = array('градуса','градусов', 'корабля', 'кораблю', 'избежание'); //Искомые слова и словоформы
Каким образом php узнает, что корабля-кораблю - словоформа одного и того-же слова?
Так что такой подход может только определить место в тексте, с наибольшей плотностью распределения искомых слов.
Нет, решение вполне правильное, словоформы от поисковых слов я генерирую с помощью ispell в связке с русским soundex-ом, такчто тут проблем не возникает никаких.

Автор оригинала: ksnk Соответственно строить рег таким образом - (word1)|(word2) - не имеет смысла, можно обойтись (word1|word2 ...) и т.д.
Попробывал:
PHP:
preg_match_all('/\.[^\.]*(?:(повернуть|поверните|избежание|градусов)[^\.]*)+\./iU', $text, $matches, PREG_SET_ORDER);
В этом случае опять таки в $matches пишется только последнее найденое в предложении. Тоесть для каждого предложения $matches состоит из 1 элемента (2х вместе с оригиналом предложения).

А мне нужно чтоб в массив писались все вхождения. У меня очень плохо с регулярками - они мне не даются.

Окажите суппорт плиз!
 

ksnk

прохожий
Oops! Слона-то я и не заметил ;)
А почему такая сложная регулярка?
Вот такая - проще, и вроде, работает...
PHP:
'/\b(повернуть|поверните|избежание|градусов)\b/i'
Можно побеспокоится о начале и конце поисковой строки, если не хочестся добавлять по пробелу с начала и с концапримерно так
PHP:
'/(?:^|\b)(повернуть|поверните|избежание|градусов)(?:$|\b)/i'
 

anufriy

Новичок
Уже вроде как решил проблему.
разбил сначала по точкам на предложения, а потом пощщитал в каждом кол-во вхождений.

Регуляркой не получается никак посчитать колличество вхождений. Такчто пришлось как есть.

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

ksnk

прохожий
WP
Блин! Оппа, это что-же в php \b вмещает в себя еще и ^ c $ ? В JavaScript приходится городить мою конструкцию...
 
Сверху