статистика текста - подсчёт слов.
Не могу найти функцию, позволяющую посчитать количество повторяющихся слов в строке (массиве?!)...
Например:
$str="Привет Вася! Привет Коля! Как дела Вася? Дела не дала, сам ты вася!"
=>
Array (
Вася => 3
Привет => 2
Дела => 2 ... )
Для выявления наиболее часто повторяющихся слов.
Стандартные учебники такой информации не несут, быть может кто-то поделится опытом?
P.S. Речь шла о PHP. Так же интересует возможность сделать это посредством JavaScript.
Ну и если поиск нечувсвтительным к регистру надо сделать, то не забываем перевести целевую строку в нижний регистр.
$words = array();
$split = explode(' ', $str);
foreach ($split as $word) {
if ($word = trim($word)) {
@$words[$word]++;
}
}
sort($words);
var_dump($words);
Посчет своего метода - думал быстрее, но оказывается нет, значительно медленее,чем explode. Так что я подправил его немного и написал помесь выше указанных двух функции. В итоге в файлах размером до 2млн символов скорость поиска примерно одинакова. На 2млн символов ваш explode ищет на моем PC за "0,660624с", а мой метод за "0,887344с". Разница невелика и не особо заметна. На 8млн символах explode выполняется уже в 1.5 раза быстрее. НО! пиковой объем занятой памяти при 2млн символах с Вашим методом составляет 31 мегабайт, с моим - 2 мегабайта. Я лично не готов вешать свой сервер подобным.
Вообщем, от нефиг делать, составил я график, дабы наглядно показать, почему не стоит советовать explode и писать сразу правильно. Не все очевидное всегда лучше.
Подробности теста приводить не буду, ваша функция оставлена без изменений, только убрана сортировка. Описание обозначений:
- explode - ваш метод;
- substr_count - тот метод, что я описал в своем первом посте;
- substr_offset - модифицированный метод выше, без использования substr_count.
Сам алгоритм поиска не оптимизировал. Рез-таты даны в приложениях. Если надо исходники, то покажу.
Естесственно, explode() требует много памяти, он же всё-таки все слова разом записывает в массив. Это можно легко исправить юзая preg_match, например. Регулярка простейшая: /([^\s]+)/
Либо если хочется до минимума урезать расход памяти и текст со словами хранится в файле, то можно парсить файл прямо во время чтения, правда это будет медленнее explode/substr*/preg_*, ибо I/O, даже если читать по кускам. Ради простоты вот код по-байтово читающий из файла и считающий слова:
$words = array();
$word = '';
while (($byte = fread($textFile, 1)) !== false) {
if ($byte <= ' ') {
@$words[$word]++;
$word = '';
} else {
$word .= $byte;
}
}
fclose($textFile);
p.s: это я к тому, что надо учитывать обстоятельства каждой реализации. Судя по посту автора, он не собирается загружать пятиметровые тексты в свой скрипт, так что вариант с explode() - самый простой и быстрый.
Если что, то мой код:
$txt .= ' ';
$words = array();
$txt_offset = 0;
while( $txt_offset < strlen($txt) ) {
$end_word_offset = strpos($txt, ' ', $txt_offset); // поиск следующего вхождения ' ' в строку
if( $end_word_offset === false ) break; // если false - то ничего не найдено, т.е. найден конец строки
// получение слова
$word = substr($txt, $txt_offset, $end_word_offset - $txt_offset);
// если слово пустое, то очищаем и переходим к следующему оффсету
if( trim($word) == "" ) {
$txt_offset = $end_word_offset+1;
continue;
}
// проверка наличия уже такого слова
if( !isset($words[$word]) ) {
$words[$word] = 1;
} else {
$words[$word]++;
}
// установка следующего оффсета, с которого идет поиск
$txt_offset = $end_word_offset+1;
}
Если что, то мой код
По идее, тебе if (trim($word)) вообще не нужен, т.к. у тебя никогда $word не будет равно пробелу.
Плюс, можно убрать лишние строки и continue:
if ($word) {
// проверка наличия уже такого слова (не обязательно)
if( !isset($words[$word]) ) {
$words[$word] = 1;
} else {
$words[$word]++;
}
}
// установка следующего оффсета, с которого идет поиск
$txt_offset = $end_word_offset+1;
Ну это так, оформление.
Раз у нас тут завязался холивар на эту тему, то я продолжил твой эксперимент.
Не, ну зачем же юзать preg для поиска, когда он опять же медленнее strpos?))
Кто это сказал? Оно медленнее, потому как регулярка компилируется при каждом вызове функции. А что если мы будем использовать preg_replace_callback? ;)
Вот код тестов:
global $txt;
$words = array();
$prev_p = 0;
$matches = array(array(1 => -1));
while (preg_match('/[^\s]+/', $txt, $matches, PREG_OFFSET_CAPTURE, $matches[0][1] + 1)) {
@$words[$matches[0]]++;
}
return $words;
}
function UsingPregReplace() {
global $txt;
global $words;
$words = array();
preg_replace_callback('/[^\s]+/', 'PregReplaceCallback', $txt);
return $words;
}
function PregReplaceCallback($matches) {
global $words;
@$words[$matches[0]]++;
return $matches[0];
}
А вот результат (за 100% точность не ручаюсь, но она тут и не нужна):
--------------------------------------------------------------------------------
Benchmark of UsingExplode: 0.0999088287354 | 100.0%
Benchmark of UsingStrFunctions: 6.18728208542 | 6192.9%
Benchmark of UsingPregMatch: 6.02376008034 | 6029.3%
Benchmark of UsingPregReplace: 0.15541100502 | 155.6%
--------------------------------------------------------------------------------
381 кб
--------------------------------------------------------------------------------
Benchmark of UsingExplode: 0.225735902786 | 100.0%
Benchmark of UsingStrFunctions: 126.913938999 | 56222.3%
Benchmark of UsingPregMatch: 199.397907972 | 88332.4%
Benchmark of UsingPregReplace: 0.352046012878 | 156.0%
--------------------------------------------------------------------------------
Так что preg_match действительно медленнее, а вот если избавится от постоянной компиляции регулярки, то preg* находится очень близко к explode.
Плюс меньше кода по сравнению с твоим методом.
Кстати, UAS, у тебя похоже не слабый комп - мой, как видишь, намного дольше пазал над substr*, а файл всего 380 Кб.
Ну посчет оформления - trim нужен. Что будет, если пойдут два и более пробела подряд? Тогда скрипт отработает неправильно, учтет за слово пробел,\n,\r,\t.
Ну и так я тоже никогда из принципа не пишу, придерживаюсь типизации всегда. Т.е. если строка string, то и надо явно её сравнивать с пустой строкой, иначе тогда воспринимается $word как bool-переменная. В итоге код сложно разбирать.
Ну посчет использования global я вообще молчу, никогда так не писал и не буду, потому что опять же код читать невозможно в итоге.
А, UsingStrFunctions - это мой код что ли? Что-то не верю я, что он может а 60 раз дольше выполняться. Плюс учтите все таки в ваших методах memory_get_peak_usage()
Хорош ноут, у меня десктопный комп 1 ядро 2.21 ГГц :)
А, точно, я забыл про такой вариант.
Ну и так я тоже никогда из принципа не пишу, придерживаюсь типизации всегда.
По идее это правильно, хотя я в PHP обычно так не делаю.
Это только для тестов. Не тратить же время, чтоб писать это дело на классах и "как положено". Главное соотношения.
А, UsingStrFunctions - это мой код что ли? Что-то не верю я, что он может а 60 раз дольше выполняться. Плюс учтите все таки в ваших методах memory_get_peak_usage()
Твой:
global $txt;
$words = array();
$txt_offset = 0;
$prev_p = 0;
while( $txt_offset < strlen($txt) ) {
$end_word_offset = strpos($txt, ' ', $txt_offset);
if( $end_word_offset === false ) break;
$word = substr($txt, $txt_offset, $end_word_offset - $txt_offset);
if (trim($word)) {
if( !isset($words[$word]) ) {
$words[$word] = 1;
} else {
$words[$word]++;
}
}
$txt_offset = $end_word_offset+1;
}
echo ' Peak mem: '.memory_get_peak_usage()."\n";
return $words;
}
С памятью:
Benchmark of UsingExplode: 0.0788569450378
Peak mem: 3693464
Benchmark of UsingStrFunctions: 3.57115912437
Peak mem: 1119120
Benchmark of UsingPregMatch: 5.02114009857
Peak mem: 652024
Benchmark of UsingPregReplace: 0.133114099503
Peak mem: 1403192
Надо же, preg_match берёт меньше всего памяти, а preg_replace даже больше, чем substr*...
Не совсем в тему: давно была такая идея, написать простенькую прогу (алгоритм) но на С++ (по учебным соображеням). Суть изначально такова - анализировать лог аськи, на предмет самых используемых слов (в том числе например '=)' или ';-P'). Иными словами передаешь проге файл, он выводит например 5 самых используемых сочетаний (к примеру "1- '=)' 2- '...' 3- 'xDDD' 4- 'привет' 5 - 'лол' ") ну и тд.
P.S. : прошу прощение за офтоп ибо эта тема скорее всего должна была быть создана в разделе Си-подобных языков, но плодить такие темы неохота...
Проще чем это? o_O
foreach ($split as $word) {
if ($word = trim($word)) {
@$words[$word]++;
}
}
Ну дык оформи любой из данных методов в функцию - будет и попроще:D
Proger_XP, по поводу мелкого холивара - отпишусь позже, как будет свободное время и настроение что-то потестить))
mlt^^. Посчет анализа логов аськи - ну так вперед, пишите. Мой метод легко переложить на C/C++.
while ($w !== false)
{
$d[$w]++;
$w = strtok(' ');
}
вопрос №2:
Можно ли то же самое сделать с клиентской стороны жаба-скриптом?
+1, мы тут со своими умными разговорами совсем забыли про неё. По сравнению с explode() у меня она работает в два раза быстрее, и использует в 3 раза меньше памяти.
Можно ли то же самое сделать с клиентской стороны жаба-скриптом?
Включаем голову и идём гуглить функцию split()
для случаев, когда нужно считать конкретные слова - маньячество :)
$temp="";
for($i=0;$i<strlen($str);$i++)
{
if($str[$i]==' ')
{
$hash[$temp]++;
$temp="";
}
else
{
$temp+=$str[$i];
}
}
foreach($hash as $index=>$value)
{
echo "$index : $value<br />";
}
Давно не юзал PHP, мог с синтаксисом и функциями намудрить
Посимвольное чтение будет медленным, к тому же я этот вариант уже приводил.