PHP задача: анализ текста
Glossary overview

PHP задача: анализ текста

Входные данные

Есть массив предложений:

$sentences = [
    "PHP is a popular general-purpose scripting language.",
    "Learning PHP can be fun and challenging.",
    "Arrays in PHP are very powerful.",
    "PHP supports object-oriented programming.",
];

Задания

1️⃣ Посчитать, сколько слов в каждом предложении
Результат:

[
  "PHP is a popular general-purpose scripting language." => 8,
  "Learning PHP can be fun and challenging." => 7,
  ...
]

2️⃣ Составить список всех уникальных слов (без повторов, игнорируя регистр, точки и запятые).

3️⃣ Найти слово, которое встречается чаще всего.

Первое решение (черновое)

<?php

$sentences = [
    "PHP is a popular general-purpose scripting language.",
    "Learning PHP can be fun and challenging.",
    "Arrays in PHP are very powerful.",
    "PHP supports object-oriented programming.",
];

$countedSentenced = [];
$uniqueWords = [];
$allWords = [];

foreach($sentences as $sentence) {
	
	$words = explode(' ', $sentence);

	$countedSentenced[$sentence] = count($words);
	
	// Составить список всех уникальных слов
	foreach($words as $word) {
		if (!in_array($word, $uniqueWords)) {
			$uniqueWords[] = $word;
		} 
		
		// Найти слово, которое встречается чаще всего
		$allWords[] = $word;
	}
}

$wordsRating = array_count_values($allWords);
arsort($wordsRating);
	
$topWord = array_key_first($wordsRating);

print_r($topWord); // PHP

Важные замечания:

  • array_count_values() – подсчитывает количество вхождений каждого отдельного значения в массиве. Вот это мы получаем с массива всех слов.
Array
(
    [PHP] => 4
    [is] => 1
    [a] => 1
    [popular] => 1
    [general-purpose] => 1
    [scripting] => 1
    [language.] => 1
    [Learning] => 1
    [can] => 1
    [be] => 1
    [fun] => 1
    [and] => 1
    [challenging.] => 1
    [Arrays] => 1
    [in] => 1
    [are] => 1
    [very] => 1
    [powerful.] => 1
    [supports] => 1
    [object-oriented] => 1
    [programming.] => 1
)
  • arsort() – Сортирует массив в порядке убывания, сохраняя ассоциацию индексов. Пример:
<?php
$scores = array("Alice" => 85, "Bob" => 92, "Charlie" => 78, "David" => 92);

arsort($scores); // Сортируем по баллам в порядке убывания

print_r($scores);

// Output
Array
(
    [Bob] => 92
    [David] => 92
    [Alice] => 85
    [Charlie] => 78
)

Рефакторинг кода.

Что стоит улучшить (ключевые моменты)

1️⃣ Очистка слов (ОЧЕНЬ ВАЖНО)

Сейчас ты работаешь с «сырыми» словами, надо очищать их от лишних символов:

Array
(
    [0] => PHP
    [1] => is
    [2] => a
    [3] => popular
    [4] => general-purpose
    [5] => scripting
    [6] => language.
)

Что делать:

  • приводить к нижнему регистру
  • убирать знаки препинания

Преподавательский совет:

Любая работа с текстом почти всегда начинается с нормализации данных.

foreach($words as $word) {
		
	/*Очищаем слова*/
	$word = strtolower($word);
	$word = trim($word, ".,-");
}

Теперь у нас чистые слова.

Array
(
    [0] => php
    [1] => is
    [2] => a
    [3] => popular
    [4] => general-purpose
    [5] => scripting
    [6] => language
)

2️⃣ in_array внутри цикла — неэффективно

$uniqueWords = [];
$allWords = [];

foreach($sentences as $sentence) {
	
	$words = explode(' ', $sentence);

	$countedSentenced[$sentence] = count($words);
	
	// Составить список всех уникальных слов
	foreach($words as $word) {
		
	        $word = strtolower($word);
		$word = trim($word, ".,-");


		if (!in_array($word, $uniqueWords)) {
			$uniqueWords[] = $word;
		} 
        }
}

📌 Почему это плохо:

  • in_arrayO(n)
  • внутри foreach → при больших данных будет медленно

💡 Как мыслит опытный PHP-разработчик:

«Если мне нужна уникальность — ключи массива лучше значений»

Как работает in_array

PHP делает следующее:

  1. Берёт первый элемент массива $uniqueWords
  2. Сравнивает с $word
  3. Если не совпало — берёт второй
  4. Потом третий, четвёртый…
  5. Пока не найдёт совпадение или не дойдёт до конца массива

👉 Это линейный поиск.

Почему это неэффективно

📈 Временная сложность (упрощённо)

  • in_array = O(n)
  • foreach ($words as $word) = O(n)

В итоге получается:

O(n) * O(n) = O(n²)

Пример на цифрах

Допустим:

  • в тексте 10 000 слов
  • уникальных слов — 5 000

Для каждого нового слова PHP в среднем:

  • проверяет ~2 500 элементов массива

Итого:

10 000 × 2 500 = 25 000 000 сравнений

А это уже заметно медленно.

Как сделать эффективно?

Использовать массив как set (множество)

В PHP ключи массива хранятся в хеш-таблице.

Проверка существования ключа:

isset($array[$key])

O(1) — почти мгновенно.

Сравнение подходов

❌ Твой вариант

if (!in_array($word, $uniqueWords)) {
    $uniqueWords[] = $word;
}
  • поиск по значениям
  • каждый раз полный обход
  • O(n²)

✅ Правильное мышление

$uniqueWords = [];

foreach ($words as $word) {
    $uniqueWords[$word] = true;
}
  • слово — ключ
  • значение не важно
  • повтор перезаписывает, не добавляет

А потом, если нужен список:

$uniqueWordList = array_keys($uniqueWords);

Когда in_array всё-таки ОК

Можно использовать, если:

  • массив маленький (10–20 элементов)
  • код не в цикле
  • важнее читаемость, чем производительность

❗ Но в задачах анализа данных — почти всегда плохая идея

В итоге получаем такой массив уникальных слов.

foreach($words as $word) {
		
	$word = strtolower($word);
	$word = trim($word, ".,-");
		
	$uniqueWords[$word] = true;
}

// Output
Array
(
    [php] => 1
    [is] => 1
    [a] => 1
    [popular] => 1
    [general-purpose] => 1
    [scripting] => 1
    [language] => 1
    [learning] => 1
    [can] => 1
    [be] => 1
    [fun] => 1
    [and] => 1
    [challenging] => 1
    [arrays] => 1
    [in] => 1
    [are] => 1
    [very] => 1
    [powerful] => 1
    [supports] => 1
    [object-oriented] => 1
    [programming] => 1
)

Можем теперь получить только ключи.

$uniqueWords = array_keys($uniqueWords);

// Output
Array
(
    [0] => php
    [1] => is
    [2] => a
    [3] => popular
    [4] => general-purpose
    [5] => scripting
    [6] => language
    [7] => learning
    [8] => can
    [9] => be
    [10] => fun
    [11] => and
    [12] => challenging
    [13] => arrays
    [14] => in
    [15] => are
    [16] => very
    [17] => powerful
    [18] => supports
    [19] => object-oriented
    [20] => programming
)

3️⃣ Архитектурное мышление

Сейчас у тебя всё в одном цикле — нормально для начала.

Следующий шаг роста:

  • выносить логику в функции
<?php

$sentences = [
    "PHP is a popular general-purpose scripting language.",
    "Learning PHP can be fun and challenging.",
    "Arrays in PHP are very powerful.",
    "PHP supports object-oriented programming.",
];

$countedSentenced = [];
$uniqueWords = [];
$allWords = [];

foreach($sentences as $sentence) {
	
	$words = explode(' ', $sentence);

	$countedSentenced[$sentence] = count($words);
	
	foreach($words as $word) {
		
		$word = clearString($word);
		
		getUniqueWords($word, $uniqueWords);
		
		getAllWords($word, $allWords);
	}
}

$uniqueWords = array_keys($uniqueWords);

$topWord = getPopularWord($allWords);

function clearString($string) {
		$string = strtolower($string);
		$string = trim($string, ".,-");
		
		return $string;
}

function getUniqueWords($word, &$uniqueWords) {
	$uniqueWords[$word] = true;
}

function getAllWords($word, &$allWords) {
	$allWords[] = $word;
}

function getPopularWord($allWords) {
	$wordsRating = array_count_values($allWords);
    arsort($wordsRating);
	
    return array_key_first($wordsRating);
}


print_r($topWord);

Важные замечания:

  • Я сначала тут хотел использовать use.
// Before
$uniqueWords = [];

function getUniqueWords($word) use($uniqueWords) { 
         $uniqueWords[$word] = true; 
}

// Error

Parse error: syntax error, unexpected token "use", expecting "{"

Ключевой момент ❗

use нельзя использовать с обычной функцией.

use работает только с анонимными функциями (closures).

$fn = function ($word) use ($uniqueWords) {
    $uniqueWords[$word] = true;
};
  • передавать внешнее значение в функцию надо по ссылке чтобы изменять его.
// Before
$uniqueWords = [];

function getUniqueWords($word, $uniqueWords) {
	$uniqueWords[$word] = true;
}

➡️ передаёт переменную по значению,
а не изменяет оригинальный $uniqueWords.

То есть:

  • внутри функции массив меняется
  • снаружи — нет

Надо передать массив по ссылке.

$uniqueWords = [];

function getUniqueWords($word, &$uniqueWords) {
	$uniqueWords[$word] = true;
}

Почему это правильно

  • &$uniqueWords — передача по ссылке
  • функция реально изменяет массив
  • это классический PHP-подход

Можно сделать еще лучше: никаких &, только return

Сейчас функция меняет внешний массив».

getUniqueWords($word, $uniqueWords);
getAllWords($word, $allWords);

Надо чтобы функция получала данные → возвращала новые данные».

Это:

  • ближе к современному стилю
  • безопаснее
  • легче тестировать
  • легче читать
<?php

$sentences = [
    "PHP is a popular general-purpose scripting language.",
    "Learning PHP can be fun and challenging.",
    "Arrays in PHP are very powerful.",
    "PHP supports object-oriented programming.",
];

$countedSentenced = [];
$uniqueWords = [];
$allWords = [];

foreach ($sentences as $sentence) {

    $words = explode(' ', $sentence);
    $countedSentenced[$sentence] = count($words);

    foreach ($words as $word) {

        $word = clearString($word);

        $uniqueWords = addUniqueWord($word, $uniqueWords);
        $allWords    = addWord($word, $allWords);
    }
}


$uniqueWords = array_keys($uniqueWords);

$topWord = getPopularWord($allWords);

function clearString(string $string): string {
    $string = strtolower($string);
    $string = trim($string, ".,-");
    return $string;
}

function addUniqueWord(string $word, array $uniqueWords): array {
    $uniqueWords[$word] = true;
    return $uniqueWords;
}

function addWord(string $word, array $allWords): array {
    $allWords[] = $word;
    return $allWords;
}

function getPopularWord(array $allWords): ?string {
    if (empty($allWords)) {
        return null;
    }

    $wordsRating = array_count_values($allWords);
    arsort($wordsRating);

    return array_key_first($wordsRating);
}



print_r($topWord);

Что здесь принципиально изменилось

1️⃣ Нет побочных эффектов

Функции:

  • не знают о внешнем коде
  • не меняют ничего «сами по себе»
  • Функции возвращают обновлённый массив.

Это чистые функции

2️⃣ Код легче тестировать

3️⃣ Это стиль фреймворков

Коротко — как это запомнить

ПодходКогда использовать
&низкоуровневый код, оптимизация
return90% бизнес-логики
global❌ никогда