Получите наиболее часто используемые слова со специальными символами

Я хочу получить наиболее часто используемое слово из массива. Единственная проблема заключается в том, что шведские символы (Å, Ä и Ö) отображаются только как �.

$string = 'This is just a test post with the Swedish characters Å, Ä, and Ö. Also as lower cased characters: å, ä, and ö.';
echo '<pre>';
print_r(array_count_values(str_word_count($string, 1, 'àáãâçêéíîóõôúÀÁÃÂÇÊÉÍÎÓÕÔÚ')));
echo '</pre>';

Этот код выведет следующее:

Array
(
    [This] => 1
    [is] => 1
    [just] => 1
    [a] => 1
    [test] => 1
    [post] => 1
    [with] => 1
    [the] => 1
    [Swedish] => 1
    [characters] => 2
    [�] => 1
    [�] => 1
    [and] => 2
    [�] => 1
    [Also] => 1
    [as] => 1
    [lower] => 1
    [cased] => 1
    [�] => 1
    [�] => 1
    [�] => 1
)

Как я могу заставить его «видеть» шведские символы и другие специальные символы?


person Airikr    schedule 24.09.2016    source источник
comment
Вас не должно удивлять, что какая-либо функция PHP с именем, начинающимся с str, не является безопасной для нескольких байтов. Комментарии пользователей в руководстве предлагают альтернативы.   -  person CBroe    schedule 24.09.2016
comment
@CBroe ...PHP function with a name starting with str... где эта функция?   -  person SaidbakR    schedule 24.09.2016
comment
попробуйте эту функцию mb_str_word_count вместо str_word_count: stackoverflow.com/a/17725577/6797531   -  person CatalinB    schedule 24.09.2016
comment
@CatalinB Спасибо, но вывод будет таким: Array([This is just a test post with the Swedish characters �, �, and Ö. Also as lower cased characters: �, �, and �.] => 1)   -  person Airikr    schedule 24.09.2016


Ответы (3)


Вот решение с регулярным выражением, использующим пунктуацию Unicode для разделения «слов», а затем просто подсчет вхождений обычного массива.

array_count_values(preg_split('/[[:punct:]\s]+/u', $string, -1, PREG_SPLIT_NO_EMPTY));

Производит:

Array
(
    [This] => 1
    [is] => 1
    [just] => 1
    [a] => 1
    [test] => 1
    [post] => 1
    [with] => 1
    [the] => 1
    [Swedish] => 1
    [characters] => 2
    [Å] => 1
    [Ä] => 1
    [and] => 2
    [Ö] => 1
    [Also] => 1
    [as] => 1
    [lower] => 1
    [cased] => 1
    [å] => 1
    [ä] => 1
    [ö] => 1
)

Это было протестировано в консоли Unicode, вы можете использовать кодировку, если используете браузер. Либо создайте тег <meta>, либо установите кодировку в своем браузере, либо отправьте заголовки PHP.

person MarZab    schedule 24.09.2016

Все это выполняется при условии, что вы используете кодировку UTF-8.

Вы можете использовать наивный подход, используя preg_split(), чтобы разделить строку на любой разделитель. , знак препинания или управляющий символ.

preg_split пример:

$split = preg_split('/[\pZ\pP\pC]/u', $string, -1, PREG_SPLIT_NO_EMPTY);
print_r(array_count_values($split));

Выход:

Array
(
    [This] => 1
    [is] => 1
    [just] => 1
    [a] => 1
    [test] => 1
    [post] => 1
    [with] => 1
    [the] => 1
    [Swedish] => 1
    [characters] => 2
    [Å] => 1
    [Ä] => 1
    [and] => 2
    [Ö] => 1
    [Also] => 1
    [as] => 1
    [lower] => 1
    [cased] => 1
    [å] => 1
    [ä] => 1
    [ö] => 1
)

Это отлично работает для заданной строки, но не обязательно разделяет слова таким образом, чтобы учитывать локаль. Например, такие сокращения, как «isn't», будут разбиты на «isn» и «t».


К счастью, расширение Intl добавляет множество функций для работы с подобными вещами в PHP 7.

План будет заключаться в следующем:

  • *Нормализуйте ввод с помощью Normalizer::normalize(), чтобы убедиться, что все графемы закодированы согласованным образом. Например, ä может быть закодировано и, следовательно, подсчитано двумя способами:

    • U+00E4 'LATIN SMALL LETTER A WITH DIAERESIS' or
    • U + 0061 «ЛАТИНСКАЯ СТРОЧНАЯ БУКВА A», за которой следует U + 0308 «КОМБИНИРОВАНИЕ ДИЭРЕЗИСА»
  • Получите IntlBreakIterator, который разбивает слова в зависимости от локали через IntlBreakIterator::createWordInstance(). Это понимает, что составляет «слово» для данной локали, включая обработку сокращений, таких как «не».

  • Получите его IntlPartsIterator через IntlBreakIterator::getPartsIterator() для простоты повторения текстовых фрагментов.

  • Пропускайте то, что вам не нужно, с помощью IntlChar::ispunct() и IntlChar::isspace()

(*Обратите внимание, что вы, вероятно, захотите выполнить нормализацию независимо от того, какой метод вы используете для разбиения строки — это было бы уместно сделать перед preg_split выше или любым другим способом, который вы решите использовать.)

Международный пример:

$string = Normalizer::normalize($string);

$iter = IntlBreakIterator::createWordInstance("sv_SE");
$iter->setText($string);
$words = $iter->getPartsIterator();

$split = [];
foreach ($words as $word) {
    // skip text fragments consisting only of a space or punctuation character
    if (IntlChar::isspace($word) || IntlChar::ispunct($word)) {
        continue;
    }
    $split[] = $word;
}

print_r(array_count_values($split));

Выход:

Array
(
    [This] => 1
    [is] => 1
    [just] => 1
    [a] => 1
    [test] => 1
    [post] => 1
    [with] => 1
    [the] => 1
    [Swedish] => 1
    [characters] => 2
    [Å] => 1
    [Ä] => 1
    [and] => 2
    [Ö] => 1
    [Also] => 1
    [as] => 1
    [lower] => 1
    [cased] => 1
    [å] => 1
    [ä] => 1
    [ö] => 1
)

Это более подробно, но может оказаться полезным, если вы предпочитаете ICU (библиотека, поддерживающая расширение Intl) делать тяжелую работу, когда дело доходит до понимания того, из чего состоит слово.

person user3942918    schedule 24.09.2016
comment
Большое спасибо за очень подробный ответ. Оба ваших ответа и ответ MarZab очень хороши. Ваше регулярное выражение будет принимать смайлики, а регулярное выражение MarZab - нет. Если бы я мог, я бы принял оба ответа, но, поскольку регулярное выражение MarZab не принимает смайлики, вместо этого я приму его ответ. - person Airikr; 24.09.2016

Мне удалось убрать знак �, добавив ÅåÄäÖö к àáãâçêéíîóõôúÀÁÃÂÇÊÉÍÎÓÕÔÚ.

person Airikr    schedule 24.09.2016