Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

статистика текста - подсчёт слов.

369
01 мая 2010 года
Kesano
451 / / 09.10.2007
Привет всем. Давно не появлялся и не создавал глупых тем и давно не агрился на сочувствующих. :) (Привет UAS, squirL )

Не могу найти функцию, позволяющую посчитать количество повторяющихся слов в строке (массиве?!)...

Например:
$str="Привет Вася! Привет Коля! Как дела Вася? Дела не дала, сам ты вася!"
=>
Array (
Вася => 3
Привет => 2
Дела => 2 ... )

Для выявления наиболее часто повторяющихся слов.

Стандартные учебники такой информации не несут, быть может кто-то поделится опытом?

P.S. Речь шла о PHP. Так же интересует возможность сделать это посредством JavaScript.
244
01 мая 2010 года
UAS
2.0K / / 19.07.2006
Юзать substr_count, при этом использовать 3 параметр offset, дабы для каждого слова поиск не шел с самого начала строки (ведь поиск идет перебором с первой строки, т.е. она уже раньше была проверена. А так в итоге лишнее время тратится на проверку просмотренного).

Ну и если поиск нечувсвтительным к регистру надо сделать, то не забываем перевести целевую строку в нижний регистр.
253
01 мая 2010 года
Proger_XP
1.5K / / 07.08.2004
Не совсем понял, к чему здесь использовать substr_count(), ну да ладно. Я бы сделал через explode + sort:
Код:
$str = 'а б ц д ц б а';

$words = array();
$split = explode(' ', $str);
foreach ($split as $word) {
  if ($word = trim($word)) {
    @$words[$word]++;
  }
}

sort($words);
var_dump($words);
244
01 мая 2010 года
UAS
2.0K / / 19.07.2006
Про explode - это самое элементарное. Я его никогда не предлагал бы, потому что это дикий расход памяти. Плюс использовать надо asort.

Посчет своего метода - думал быстрее, но оказывается нет, значительно медленее,чем explode. Так что я подправил его немного и написал помесь выше указанных двух функции. В итоге в файлах размером до 2млн символов скорость поиска примерно одинакова. На 2млн символов ваш explode ищет на моем PC за "0,660624с", а мой метод за "0,887344с". Разница невелика и не особо заметна. На 8млн символах explode выполняется уже в 1.5 раза быстрее. НО! пиковой объем занятой памяти при 2млн символах с Вашим методом составляет 31 мегабайт, с моим - 2 мегабайта. Я лично не готов вешать свой сервер подобным.

Вообщем, от нефиг делать, составил я график, дабы наглядно показать, почему не стоит советовать explode и писать сразу правильно. Не все очевидное всегда лучше.

Подробности теста приводить не буду, ваша функция оставлена без изменений, только убрана сортировка. Описание обозначений:
- explode - ваш метод;
- substr_count - тот метод, что я описал в своем первом посте;
- substr_offset - модифицированный метод выше, без использования substr_count.

Сам алгоритм поиска не оптимизировал. Рез-таты даны в приложениях. Если надо исходники, то покажу.
253
01 мая 2010 года
Proger_XP
1.5K / / 07.08.2004
Гм, что-то ты больно серьёзно подошёл к этому вопросу (разве что действительно от нечего делать) :)

Естесственно, explode() требует много памяти, он же всё-таки все слова разом записывает в массив. Это можно легко исправить юзая preg_match, например. Регулярка простейшая: /([^\s]+)/

Либо если хочется до минимума урезать расход памяти и текст со словами хранится в файле, то можно парсить файл прямо во время чтения, правда это будет медленнее explode/substr*/preg_*, ибо I/O, даже если читать по кускам. Ради простоты вот код по-байтово читающий из файла и считающий слова:
Код:
$textFile = fopen('text.txt', 'r');

  $words = array();
  $word = '';
  while (($byte = fread($textFile, 1)) !== false) {
    if ($byte <= ' ') {
      @$words[$word]++;
      $word = '';
    } else {
      $word .= $byte;
    }
  }

fclose($textFile);


p.s: это я к тому, что надо учитывать обстоятельства каждой реализации. Судя по посту автора, он не собирается загружать пятиметровые тексты в свой скрипт, так что вариант с explode() - самый простой и быстрый.
244
02 мая 2010 года
UAS
2.0K / / 19.07.2006
Действительно, делать было нефиг =) Не, ну зачем же юзать preg для поиска, когда он опять же медленнее strpos?))

Если что, то мой код:
Код:
$txt = "asdf qwerty sde asdf sde sde z";
$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;
}
253
02 мая 2010 года
Proger_XP
1.5K / / 07.08.2004
Цитата: UAS

Если что, то мой код


По идее, тебе 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? ;)
Вот код тестов:

Код:
function UsingPregMatch() {
  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% точность не ручаюсь, но она тут и не нужна):
Код:
166 кб
--------------------------------------------------------------------------------

  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 Кб.
244
02 мая 2010 года
UAS
2.0K / / 19.07.2006
Да че там, ноутбук 2 ядра, 2Ghz каждый + оперативы 2Гб.

Ну посчет оформления - trim нужен. Что будет, если пойдут два и более пробела подряд? Тогда скрипт отработает неправильно, учтет за слово пробел,\n,\r,\t.

 
Код:
if ($word)

Ну и так я тоже никогда из принципа не пишу, придерживаюсь типизации всегда. Т.е. если строка string, то и надо явно её сравнивать с пустой строкой, иначе тогда воспринимается $word как bool-переменная. В итоге код сложно разбирать.
Ну посчет использования global я вообще молчу, никогда так не писал и не буду, потому что опять же код читать невозможно в итоге.

А, UsingStrFunctions - это мой код что ли? Что-то не верю я, что он может а 60 раз дольше выполняться. Плюс учтите все таки в ваших методах memory_get_peak_usage()
253
02 мая 2010 года
Proger_XP
1.5K / / 07.08.2004
Цитата: UAS
Да че там, ноутбук 2 ядра, 2Ghz каждый + оперативы 2Гб.


Хорош ноут, у меня десктопный комп 1 ядро 2.21 ГГц :)

Цитата: UAS
Ну посчет оформления - trim нужен. Что будет, если пойдут два и более пробела подряд? Тогда скрипт отработает неправильно, учтет за слово пробел,\n,\r,\t.


А, точно, я забыл про такой вариант.

Цитата: UAS

Ну и так я тоже никогда из принципа не пишу, придерживаюсь типизации всегда.


По идее это правильно, хотя я в PHP обычно так не делаю.

Цитата: UAS
Ну посчет использования global я вообще молчу, никогда так не писал и не буду, потому что опять же код читать невозможно в итоге.


Это только для тестов. Не тратить же время, чтоб писать это дело на классах и "как положено". Главное соотношения.


Цитата: UAS

А, UsingStrFunctions - это мой код что ли? Что-то не верю я, что он может а 60 раз дольше выполняться. Плюс учтите все таки в ваших методах memory_get_peak_usage()


Твой:

Код:
function UsingStrFunctions() {
  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;
}


С памятью:
 
Код:
145 кб

  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*...
44K
02 мая 2010 года
mlt^^
63 / / 01.04.2010
Нифига вы тут нахимичили ...
Не совсем в тему: давно была такая идея, написать простенькую прогу (алгоритм) но на С++ (по учебным соображеням). Суть изначально такова - анализировать лог аськи, на предмет самых используемых слов (в том числе например '=)' или ';-P'). Иными словами передаешь проге файл, он выводит например 5 самых используемых сочетаний (к примеру "1- '=)' 2- '...' 3- 'xDDD' 4- 'привет' 5 - 'лол' ") ну и тд.
P.S. : прошу прощение за офтоп ибо эта тема скорее всего должна была быть создана в разделе Си-подобных языков, но плодить такие темы неохота...
369
02 мая 2010 года
Kesano
451 / / 09.10.2007
Неужели нет конструкций по-проще?
253
02 мая 2010 года
Proger_XP
1.5K / / 07.08.2004
Цитата: Kesano
Неужели нет конструкций по-проще?


Проще чем это? o_O

 
Код:
$split = explode(' ', $str);
foreach ($split as $word) {
  if ($word = trim($word)) {
    @$words[$word]++;
  }
}
244
02 мая 2010 года
UAS
2.0K / / 19.07.2006
Цитата: Kesano
Неужели нет конструкций по-проще?


Ну дык оформи любой из данных методов в функцию - будет и попроще:D

Proger_XP, по поводу мелкого холивара - отпишусь позже, как будет свободное время и настроение что-то потестить))

mlt^^. Посчет анализа логов аськи - ну так вперед, пишите. Мой метод легко переложить на C/C++.

563
02 мая 2010 года
MrLinker
249 / / 17.09.2006
А еще есть strtok()
 
Код:
$w = strtok($str, ' ');
while ($w !== false)
{
    $d[$w]++;
    $w = strtok(' ');
}
369
02 мая 2010 года
Kesano
451 / / 09.10.2007
На первой странице там такие энциклопедии писали что мне стало страшно )...
вопрос №2:
Можно ли то же самое сделать с клиентской стороны жаба-скриптом?
253
02 мая 2010 года
Proger_XP
1.5K / / 07.08.2004
Цитата: MrLinker
А еще есть strtok()


+1, мы тут со своими умными разговорами совсем забыли про неё. По сравнению с explode() у меня она работает в два раза быстрее, и использует в 3 раза меньше памяти.

Цитата: Kesano

Можно ли то же самое сделать с клиентской стороны жаба-скриптом?


Включаем голову и идём гуглить функцию split()

536
02 мая 2010 года
alex-kniaz
382 / / 07.08.2008
я бы наверное юзал хеши (если надо подсчитать все слова) :
для случаев, когда нужно считать конкретные слова - маньячество :)
Код:
$str='я тут. да уже';

$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, мог с синтаксисом и функциями намудрить
253
03 мая 2010 года
Proger_XP
1.5K / / 07.08.2004
Цитата: alex-kniaz
я бы наверное юзал хеши (если надо подсчитать все слова)


Посимвольное чтение будет медленным, к тому же я этот вариант уже приводил.

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог