Ниже рассмотрим 6 задачек, которые мне встретились на недавнем собеседовании. Задачки типовые: проверяют нания баз данных и языка программирования (php). Без “подвоха“, н требующие подобрать оптимальные алгоритмы/структуры данных таблиц. Что ж, начнем разбор …
Задача №1: оптимизация функции flipA
Что делает эта функция?
<?php
function flipA(&$arr)
{
for ($i = 0; $i < count($arr); $i++) {
$tmp = $arr[$i];
$arr[$i] = $arr[count($arr) - $i - 1];
$arr[count($arr) - $i - 1] = $tmp;
}
return $arr;
}
Анализ: это функция “переворачивания” массива (аналог array_reverse)
В тек реализации ф-я сработает не верно, т.к. перевернет массив дважды, нужно итерацию делать до середины массива:
for ($i = 0; $i < count($arr) / 2; $i++) { ...
Вот еще несколько способов улучшить данную функцию:
-
Использовать смысловое имя функции, например
reverseArrayилиflipArray. -
Убрать передачу аргумента по ссылке в ф-ю:
function flipA($arr) -
Добавить проверку типа аргумента — убедиться, что передан массив.
-
Использовать переменную с длиной массива
$arrayLengthвместо многократного вызоваcount($arr)для оптимизации. -
Заменить цикл на алгоритм с использованием указателей для повышения производительности.
-
Добавить документирование функции в виде комментариев.
-
Вынести логику переворота массива в отдельную функцию для повторного использования.
-
Добавить проверки индексов массива на выход за границы.
-
Вернуть измененный массив вместо использования ссылки для упрощения использования.
-
Добавить обработку ошибок и возможных исключительных ситуаций.
-
Покрыть функцию юнит-тестами для проверки корректности работы.
Реализация этих улучшений сделает код функции более понятным, надежным и оптимизированным.
Мое итоговое решение:
/**
* Flip array function (new)
* @param array $arr
* @return array
*/
function flipArray(array $arr): array {
$arrayLength = count($arr);
if(!$arrayLength) {
return [];
}
for ($i = 0; $i < $arrayLength / 2; $i++) {
swapArrayElements($arr, $i, $arrayLength - $i - 1);
}
return $arr;
}
/**
* Swap 2 array elements
* @param array $arr
* @param int $i - index of first element
* @param int $j - index of second element
*/
function swapArrayElements(&$arr, $i, $j) {
$tmp = $arr[$i];
$arr[$i] = $arr[$j];
$arr[$j] = $tmp;
}
$arr2 = [1, 2, 3, 4, 5];
var_dump(flipArray($arr2));
см. тут: https://3v4l.org/ZL6eU
Задача №2: реализация ф-ии strstr
Напишите реализацию для ф-ии strstr:
<?php
function strstr(string $haystack, string $needle)
{
}
strstr('asdfgh', 'f') === 'fgh';
нельзя использовать встроенные ф-ии php: substr, strtok, ...
Мое решение:
<?php
/**
* Find substring starting in string, starting from some symbol(s)
* @param string $haystack
* @param string $needle
* @return string
*/
function my_strstr(string $haystack, string $needle): string
{
for($i = 0; $i < strlen($haystack); $i++) {
if($haystack[$i] == $needle[0]) {
$eq = true;
for($j = 0; $j < strlen($needle); $j++) {
if($haystack[$i + $j] !== $needle[$j]) {
$eq = false;
break;
}
}
if($eq) {
return substr($haystack, $i);
}
}
}
}
var_dump(my_strstr('asdfghend', 'fg')); // 'fghend';
Решение: https://3v4l.org/e2TB2
можно еще подумать, как оптимизировать скорость, чтобы работала быстрее чем О(n^2)
полагаю так: счетчик совпадений завести, т.о. избавиться от второй итерации:
<?php
/**
* Напишите реализацию для ф-ии strstr:
* function strstr(string $haystack, string $needle)
* нельзя использовать встроенные ф-ии php: substr, strtok, ...
* (*) это более удачная реализация, работает за О(n)
*/
/**
* Find substring starting in string, starting from some symbol(s)
* @param string $haystack
* @param string $needle
* @return string
*/
function my_strstr2(string $haystack, string $needle): string
{
$isFound = false;
$lenHaystack = strlen($haystack);
$lenNeedle = strlen($needle);
for($i = 0, $j = 0, $cnt = 0; $i < $lenHaystack, $j < $lenNeedle; $i++) {
if($haystack[$i + $j] == $needle[$j]) {
$j++;
} else {
$j = 0;
}
if($j >= strlen($lenNeedle)) {
$isFound = true;
break;
}
}
return ($isFound ? substr($haystack, $i) : '');
}
var_dump(my_strstr2('asdfghend', 'fg')); // 'fghend';
Запуск решения: https://3v4l.org/XCanS
Задача №3: оптимизация запроса
Как оптимизировать запрос?
# id (pk)| from_user_id | to_user_id | text select * from table where from_user_id = 987 or to_user_id = 987;
-
Какой индекс использовать тут: 2 простых или один составной?
Для ускорения данного запроса, где происходит фильтрация по полям from_user_id и to_user_id, имеет смысл добавить следующие индексы:
-
Индекс по полю from_user_id:
CREATE INDEX idx_from_user_id ON table(from_user_id);
Это позволит быстро находить записи по значению from_user_id.
-
Индекс по полю to_user_id:
CREATE INDEX idx_to_user_id ON table(to_user_id);
Для быстрого поиска по to_user_id.
-
Составной индекс по обоим полям:
CREATE INDEX idx_from_to_user_id ON table(from_user_id, to_user_id);
Такой индекс покроет оба варианта фильтрации в запросе.
При наличии этих индексов запрос будет выполняться эффективно за счет сужения сканируемого множества данных по индексированным полям.
в нашем случае составной индекс не сработает, т.к. операция OR а не AND, что эквивалентно запросу с UNION , где 2 индекса отработают независимо:
select * from table where from_user_id = 987 UNION select * from table where to_user_id = 987;
Какой твой любимый тип индексов?
-
btree~ “balanced tree index”
Помимо B-деревьев, в базах данных могут использоваться и другие структуры индексов:
-
Hash-индекс — индекс на основе хеш-таблицы, быстрый поиск по равенству.
-
R-дерево — пространственный индекс для геометрических данных.
-
Индексы на основе bitmask — устанавливают биты по определенным правилам.
-
Плоские файлы — простой индекс в виде отсортированного файла значений.
-
Индексы по триграммам — для поиска похожих текстовых значений.
-
Полнотекстовые индексы — инвертированные списки слов для поиска в тексте.
-
Multi-index — несколько вложенных индексов разных типов.
-
Индексы по выражениям — виртуальные индексы, зависящие от логики приложения.
-
Кластерные индексы — сортируют данные физически.
Выбор типа индекса зависит от типов данных, запросов и необходимой производительности.
Какие виды btree индексов есть?
Основные виды B-Tree индексов:
-
Обычный индекс (index) — наиболее распространенный вид, строится по одному или нескольким столбцам таблицы.
-
Уникальный индекс (unique index) — гарантирует уникальность значений индексированного столбца, не допускает дубликатов.
-
Составной индекс (composite index) — строится сразу по нескольким столбцам.
-
Кластерный индекс (clustered index) — физически сортирует записи таблицы по ключам индекса. Бывает только один кластерный индекс.
-
Покрывающий индекс (covering index) — содержит все столбцы, фигурирующие в запросе. Запрос выполняется только по индексу.
-
Частичный индекс (partial index) — строится только для части строк таблицы по некоторому условию.
-
Пространственный индекс (spatial index) — для работы с геоданными, например R-деревья.
Правильный подбор типов индексов позволяет оптимизировать работу СУБД для конкретных запросов.
Задача №4: поиск по словам
Как организовать поиск по точному соотв слов?
Название: Про маму
Текст:
Мама мыла раму в свой выходной. Папа работал в саду, подстригал траву. итд..
Необходимо составить структуру таблиц, используя которую можно быстро
организовать поиск документов, которые содержат определенные слова.
Словоформы не учитываем, то есть ищем точное совпадение:
мамавместомаме, мамуи т.п.
Мое решение: хеш-табл с индексами слов (верно):
phrase ----------------- id (pk) phrase (text) words -------------------------- id (pk) word (char) words_to_phrases ------------------- word_id phrase_id
Задача №5: система бронирования номеров
Задача:
Проектируется система бронирования номеров
Номер м.б. забронирован 1 чел на 1/неск суток
Как сделать интерфейс бронирования? (с точки зрения БД)
Решение:
users -------------- user_id name rooms -------------- room_id # | 1 | 2 | number # | 101 | 102 | rooms_free_dates -------------- room_id date_from # '2023-08-08' date_to bookings -------------- booking_id user_id room_id date_from date_to
Как забронировать комнату на 1е и 3е число? — сделать 2 отд. бронирования (2 записи в bookings)
Как посмотреть список свободных номеров на 4е число все свободно?
SELECT room_id FROM rooms_free_dates WHERE :date >= date_from AND :date <= date_to
Как поправить запрос для выбора диапазона дат ? т.е. найти номера, свободные с даты1 по дату2
SELECT room_id FROM rooms_free_dates WHERE :date1 >= date_from # date1 = 4 aug AND :date2 <= date_to # date2 = 16 aug
Учитываем существующие бронирования:
SELECT rfd.room_id
FROM rooms_free_dates rfd
WHERE :date1 >= rfd.date_from | date1 = 4 aug
AND :date2 <= rfd.date_to | date2 = 16 aug
AND NOT EXISTS (
SELECT 1
FROM bookings
WHERE date_from >= :date1
AND date_from <= :date2
AND room_id = rfd.room_id
)
но видно, что приходится проверять 2 таблицы, что не верно;
На деле данные в rooms_free_dates будут обновляться после каждого бронирования, так что доп. проверять booking тут не нужно; но собеседующий советовал с диапазонов свободных дат (rooms_free_dates) перейти на слоты.
Задача №6: код ревью класса Mailer
Посмотреть код, сказать что не так, исправить …
Видим что не верная иерархия наследования, нарушение принципа SRP, и др. мелкие ошибки, исправляем.
Проверь код:
<?php
class Mailer
{
public function send($to, $subject, $body)
{
// Код, производящий отправку письма
// коннект к mail серверу, авторизация, передача письма и т.д.
}
}
class MailMessage extends Mailer
{
protected $to;
protected $template;
protected $data;
public function setTo($to)
{
// ...
}
public function setTemplate($template)
{
// ...
}
public function setData($data)
{
// ...
}
public function send()
{
// Сборка параметров письма из переданных template и data
//
// $to = ...
// $subject = ...
// $body = ...
parent::send($to, $subject, $body);
}
}
Поправленный вариант:
<?php
class Mailer
{
public function send(MailMessage $mailMsg): bool
{
// Код, производящий отправку письма
// коннект к mail серверу, авторизация, передача письма и т.д.
}
}
class MailMessage
{
protected string $to;
protected string $template;
protected array $data;
public function setTo(string $to): void
{
// ...
}
public function setTemplate(string $template): void
{
// ...
}
public function setData(array $data): void
{
// ...
}
}
class MailingService
{
public function send(): bool
{
// <calc $to, $tpl, $data>
$mailMsg = new MailMessage();
$mailMsg->setTo($to);
$mailMsg->setTemplate($tpl);
$mailMsg->setData($data);
// ...
$mailer = new Mailer();
$res = $mailer->send($mailMsg);
return $res;
}
}
Веб-разработчику PHP, готовящемуся к собеседованию, будет полезно ознакомиться с моим авторским пособием, в котором подробно разбираются актуальные вопросы и задачи по теме технических собеседований. Эти материалы основаны на реальном опыте прохождения интервью за последние 5+ лет и помогут качественно подготовиться к предстоящим собеседованиям. В книге вы найдете как популярные вопросы, которые часто задают на собеседованиях PHP-разработчикам, так и практические кейсы и задачи с подробными решениями и разбором.

Нет Ответов