Есть отдельный класс задач, который не может обойтись без циклов — он называется агрегированием данных. К таким задачам относятся поиск максимального и минимального значения, вычисление суммы и среднего арифметического и так далее. Их главная особенность в том, что результат зависит от всего набора данных. Для расчета суммы нужно сложить все числа, для вычисления максимального нужно сравнить все числа.
С такими задачами хорошо знакомы те, кто занимаются числами — например, бухгалтеры или маркетологи. Обычно их выполняют в таблицах в Microsoft Excel или Google Sheets. Именно эту тему мы изучим в этом уроке.
Разберем самый простой пример – поиск суммы набора чисел. Реализуем функцию, которая складывает числа в указанном диапазоне, включая границы. Диапазоном в данном случае называется ряд чисел от какого-то начала до определенного конца. Например, диапазон [1, 10] включает в себя все целые числа от 1 до 10.
<?php
sumNumbersFromRange(5, 7); // 5 + 6 + 7 = 18
sumNumbersFromRange(1, 2); // 1 + 2 = 3
// [1, 1] Диапазон с одинаковым началом и концом – тоже диапазон
// Он в себя включает ровно одно число – саму границу диапазона
sumNumbersFromRange(1, 1); // 1
sumNumbersFromRange(100, 100); // 100
Для реализации этого кода нам понадобится цикл по двум причинам:
- Сложение чисел — это итеративный процесс, то есть он повторяется для каждого числа
- Количество итераций зависит от размера диапазона
Перед тем, как смотреть код, попробуйте ответить на вопросы ниже:
- Каким значением инициализировать счетчик?
- Как он будет изменяться?
- Когда цикл должен остановиться?
Попробуйте сначала подумать над этими вопросами, а затем посмотрите код ниже:
<?php
function sumNumbersFromRange ($start, $finish)
{
// Технически можно менять $start
// Но входные аргументы нужно оставлять в исходном значении
// Это сделает код проще для анализа
$i = $start;
$sum = 0; // Инициализация суммы
while ($i <= $finish) { // Двигаемся до конца диапазона
$sum = $sum + $i; // Считаем сумму для каждого числа
$i = $i + 1; // Переходим к следующему числу в диапазоне
}
// Возвращаем получившийся результат
return $sum;
};
Общая структура цикла здесь стандартна. В нем есть три компонента:
- Счетчик, который инициализируется начальным значением диапазона
- Сам цикл с условием остановки при достижении конца диапазона
- Изменение счетчика в конце тела цикла
Количество итераций в таком цикле равно $finish - $start + 1
. То есть для диапазона от 5 до 7 – это 7 - 5 + 1, то есть три итерации.
Главные отличия от обычной обработки связаны с логикой вычислений результата. В задачах на агрегацию всегда есть какая-то переменная, которая хранит внутри себя результат работы цикла. В коде выше это $sum
. На каждой итерации цикла происходит ее изменение, прибавление следующего числа в диапазоне: $sum = $sum + $i
.
Весь процесс выглядит так:
<?php
// Для вызова sumNumbersFromRange(2, 5);
$sum = 0;
$sum = $sum + 2; // 2
$sum = $sum + 3; // 5
$sum = $sum + 4; // 9
$sum = $sum + 5; // 14
// 14 – результат сложения чисел в диапазоне [2, 5]
У переменной $sum
есть начальное значение, равное 0. Зачем вообще задавать значение? Любая повторяющаяся операция начинается с какого-то значения. Нельзя просто так объявить переменную и начать с ней работать внутри цикла. Это может приводить к ошибкам:
<?php
// начальное значение не задано
// PHP автоматически делает его равным NULL
$sum;
// первая итерация цикла
$sum = $sum + 2; // ?
В результате такого вызова внутри $sum
окажется верный результат, но интерпретатор выведет ошибку: PHP Notice: Undefined variable: sum
. Она возникает из-за попытки использовать неопределенную переменную. Значит какое-то значение все же нужно. Почему в коде выше выбран 0? Очень легко проверить, что все остальные варианты приведут к неверному результату. Если начальное значение будет равно 1, то результат получится на 1 больше, чем нужно.
В математике у каждой операции существует нейтральный элемент этой операции. Операция с этим элементом не изменяет то значение, над которым проводится операция:
- Ноль при сложении: любое число + ноль = само число
- Ноль при вычитании: любое число - ноль = само число
- Пустая строка при конкатенации:
''
+'string'
будет'string'
Агрегация данных (Строки)
Агрегация применяется не только к числам, но и к строкам. Это такие задачи, в которых строка формируется динамически — то есть заранее неизвестно, какого она размера и что будет содержать.
Представьте себе функцию, которая умеет умножать строку, то есть повторять ее указанное количество раз:
<?php
repeat('hexlet', 3); // 'hexlethexlethexlet'
Принцип работы этой функции довольно простой — в цикле происходит «наращивание» строки указанное количество раз:
<?php
function repeat($text, $times)
{
// Нейтральный элемент для строк – пустая строка
$result = '';
$i = 1;
while ($i <= $times) {
// Каждый раз добавляем строку к результату
$result = "{$result}{$text}";
$i = $i + 1;
}
return $result;
}
Распишем выполнение этого кода по шагам:
<?php
// Для вызова repeat('hexlet', 3);
$result = '';
$result = "{$result}hexlet"; // hexlet
$result = "{$result}hexlet"; // hexlethexlet
$result = "{$result}hexlet"; // hexlethexlethexlet
Обход строк
Циклы подходят не только для обработки чисел, но и при работе со строками — и это благодаря возможности получить конкретный символ по его индексу. Ниже пример кода, который распечатывает буквы каждого слова на отдельной строке:
<?php
function printNameBySymbol($name)
{
$i = 0;
// Такая проверка будет выполняться до конца строки,
// включает последний символ
// Его индекс — `длина строки - 1`
while ($i < strlen($name)) {
// Обращаемся к символу по индексу
print_r("$name[$i]\n");
$i = $i + 1;
}
}
$name = 'Arya';
printNameBySymbol($name);
// => 'A'
// => 'r'
// => 'y'
// => 'a'
https://replit.com/@hexlet/php-basics-loops-using-printnamebysymbol
Самое главное в этом коде — поставить правильное условие в while
. Это можно сделать сразу двумя способами:
$i < strlen($name)
$i <= strlen($name) - 1
Оба способа приводят к одному результату.
Формирование строк в циклах
Еще одно использование циклов – формирование строк. Подобная задача нередко встречается в веб-программировании. Она сводится к обычной агрегации с применением интерполяции или конкатенации.
Есть одна задача, крайне популярная на собеседованиях — это переворот строки. Ее можно решить множеством разных способов, но именно посимвольный перебор считается базовым.
Рассмотрим пример работы этой функции:
<?php
reverse('Hexlet'); // telxeH
Общая идея переворота состоит в следующем — нужно брать символы по очереди с начала строки и соединять их в обратном порядке. Звучит довольно просто. Давайте проверим:
<?php
function reverse($str)
{
$i = 0;
// Нейтральный элемент для строк — это пустая строка
$result = '';
while ($i < strlen($str)) {
$currentChar = $str[$i];
// Соединяем в обратном порядке
$result = "{$currentChar}{$result}";
// Тоже самое через конкатенацию
// $result = $currentChar . $result;
$i = $i + 1;
}
return $result;
}
$name = 'Bran';
reverse($name); // 'narB'
// Проверка нейтрального элемента
reverse(''); // ''
https://replit.com/@hexlet/php-basics-loops-using-reverse
Единственный сложный момент в этом коде – прочувствовать, как собирается сама строка. Так как каждый следующий символ прикрепляется к результирующей строке слева, то, в конечном итоге, строка оказывается перевернута.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты