В 2023 году, в эпоху взрывного роста нейросетей, вся IT-отрасль продолжает давать кандидатам в стажёры (и не только) домашние тестовые задания. Компании тратят время на то, чтобы придумать эти задания, студенты тратят время на то, чтобы их сделать, потом компании снова тратят время на проверку… В этой небольшой заметке я продемонстрирую, что все задачи последних лет решаются за несколько минут при использовании нейронки, доступ к которой может получить каждый. Я возьму тестовые задания на Python, C++, Go и C#  и буду использовать только открытые источники, убрав названия компаний и прямые ссылки, чтобы исключить любые подозрения в предвзятости. А в качестве нейронки использую ChatGPT Plus (GPT 4). Она всё еще очень глупа, много выдумывает и постоянно смешно ошибается, но как раз для таких задач годится хорошо. И да, вместо дисклеймера. Если вы хоть немного успели «посотрудничать» с ChatGPT, то статья может показаться вам глубоко капитанскоочевидной (она такая и есть, безусловно). Но если вы вместо хайпа по нейронкам предпочитаете работать, то у вас нет времени на такие развлечения. Так что, возможно, несколько минут, которые вы сейчас потратите на прочтение, сэкономят вам много часов инвестиций в «ненастоящих стажеров» и «ненастоящих джунов». И соответственно, помогут не наделать ошибок при найме. Скорее всего, все ваши задания решаются нейронкой. Если вы думаете, что ваше задание слишком комплексное, чтобы его могла сгенерировать нейронка, смотрите: Если вы думаете, что уникальность (вы сами его придумали) задания, обеспечивает его нерешаемость нейронкой, смотрите: На вход программе подаются два массива целых чисел, разделённых пробелом. Необходимо найти число вхождений второго массива в первый. То есть посчитать, сколько раз последовательность чисел второго массива (без перестановок) встречается в первом. Данное задание можно решить различными способами, но одним из наиболее простых и наглядных является использование простого алгоритма, который проходит по всем элементам первого массива и сравнивает их с элементами второго массива. Вот пример решения на Python: Этот код создаёт функцию count_subarray, которая принимает два массива в качестве аргументов. Функция инициализирует счётчик вхождений count значением 0. Затем она проходит по всем элементам array1 и сравнивает подмассив длины array2, начинающийся с текущего элемента, с array2. Если они равны, увеличивается счётчик count. В конце функция возвращает значение count, которое представляет количество вхождений array2 в array1. Обратите внимание, что данный подход будет работать для неотсортированных массивов, поскольку мы ищем точные последовательности чисел. Итог: ответ верный. Дана строка, которая является абсолютным путем к файлу или директории в системе Unix. Нужно упростить эту строку до каноничного пути. Каноничный путь — это тот путь, который будет максимально простым и строгим, а именно: На вход подается путь к файлу или директории в системе Unix. На выходе ожидается каноничный путь. Пример: абсолютный путь: /home/abc/../abc/file.txt каноничный путь: /home/abc/file.txt Программа должна возвращать упрощенный каноничный путь до файла или директории Sample Input:
/../
Sample Output:
/ Задача может быть решена с помощью стека. Мы проходим по каждому компоненту пути (директории или файлу), разделяя строку по слешам. Если текущий компонент — это "..", то мы выходим из последней директории (если она существует). Если текущий компонент — это "." или пустая строка, мы пропускаем этот компонент. Иначе, мы входим в данную директорию (добавляем её в стек). В конце, стек содержит каноничный путь, который мы объединяем в одну строку. Обратите внимание, что этот подход работает только для абсолютных путей и не обрабатывает относительные пути. Если вам нужно обработать и относительные пути, вам понадобится дополнительная логика для обработки таких ситуаций.   Итог: На мой взгляд решение хорошее, все условия соблюдены. Дан лог сервера, который содержит логи операций с машинами. Для идентификации машин используются IPv4 адреса. Нужно определить частоту встречаемости IP адресов. На вход принимается лог. На выходе ожидается список адресов с количеством упоминаний адреса (список IP-адресов, отсортированных в порядке убывания количества упоминаний). Если несколько IP-адресов имеют одинаковое количество упоминаний, то к ним применяется сортировка строк по убыванию. Важно:
В логе может встречаться текст похожий на IP, но не являющимся им, например, 0.0.0.1000
Также в логах могут встречаться маски подсети, например 192.168.0.0/16. Их не нужно считать как ip.
Строки логов могут быть пустые.
Строка лога не обязательно содержит ip, но в логах обязательно содержится хотя бы один ip. Например, если лог содержит следующие записи: Программа должна вывести на экран: Sample Input: Sample Output: Эта задача может быть решена с помощью регулярных выражений и словаря для подсчёта вхождений каждого IP-адреса. Вот пример решения на Python: В этом коде мы используем регулярное выражение для поиска всех IP-адресов в каждом логе. Затем мы проверяем, что каждая часть адреса находится в допустимом диапазоне (от 0 до 255). Если адрес действителен, мы увеличиваем его счётчик в Counter. В конце мы возвращаем список IP-адресов и их количества, отсортированный в порядке убывания количества и по самому адресу, преобразованному в число, если количество совпадает.   Видим прокол: не учтены дополнительные условия — подсети попадут в ответ. Но если кандидат потратит хотя бы минуту на чтение задания и тестирование, он это увидит.
Напоминаем роботу про дополнительные условия, получаем новое решение. Итог: задача решена, дополнительные условия учтены. Дана последовательность h содержащая n целочисленных высот. На координатной плоскости нарисовано n вертикальных линий,
i-й элемент определяет линию от точки (i, 0) до точки (i, h[i]). Найдите две линии, которые образуют вместе с осью x "контейнер",
который может содержать наибольшее количество "двумерной воды" так,
чтобы она не выливалась за его пределы. Контейнер наклонять нельзя. Вроде какая-то распространённая классическая задача. Эта задача — классическая задача в области алгоритмов, которую можно решить с помощью метода двух указателей. Суть алгоритма заключается в том, чтобы начать с самого широкого контейнера и пробовать уменьшить ширину контейнера в надежде на то, что мы найдем контейнер с большей высотой. Здесь важно заметить, что минимальная высота определяет максимальный объем контейнера. Следующий код на C++ реализует этот алгоритм: Функция maxArea принимает вектор height, содержащий высоты линий. Она инициализирует переменные max_area (для хранения максимальной площади контейнера), left и right (для хранения индексов левой и правой границ контейнера). Затем в цикле функция вычисляет площадь контейнера, используя текущие границы, и обновляет max_area, если текущая площадь больше максимальной. Если левая граница меньше правой, функция сдвигает левую границу вправо, иначе она сдвигает правую границу влево. Функция возвращает max_area после завершения цикла.   Итог: подробно не проверял, но с виду ответ правильный.  Решение кажется тоже достаточно эффективным. Логичнее было вернуть две линии, а не площадь, но это минорное исправление, которое сам робот легко сделает по просьбе человека. Написать клиент и сервер, которые бесконечно обмениваются друг с другом ping-pong-сообщениями. Необходимо описать сборку решения на cmake и использовать какую-то популярную библиотеку для логирования. Здесь два простых примера клиента и сервера, написанных на C++ с использованием библиотеки Boost.Asio для сетевых операций и spdlog для логирования. Они бесконечно обмениваются сообщениями Ping и Pong. Server.cpp Client.cpp Для компиляции этих двух файлов с помощью CMake, вы можете создать следующий файл CMakeLists.txt: Чтобы собрать проект: Запустите сервер, а затем клиент. Они начнут обмениваться сообщениями Ping и Pong, и эти сообщения будут логироваться.   Итог: вроде примерно то, что надо. Требуется реализовать утилиту на языке C++, работающую из командной строки, выполняющую сканирование файлов в директории, с целью нахождения в ней “подозрительных” (suspicious) файлов. В рамках задачи определено 3 типа “подозрительного” содержимого в файле: После завершения выполнения утилиты пользователю должен быть выведен отчет о сканировании, в котором присутствует следующая информация: Пример исполнения утилиты из командной строки: scan_util ~/Downloads Для упрощения задачи условимся: В директории находятся только файлы, вложенных директорий нет; В каждом файле присутствует только один тип “подозрительного” содержимого. Рекомендуется максимальное использование (утилизация) вычислительных ресурсов устройства, на котором выполняется утилита. Реализация этой утилиты может быть выполнена с использованием различных компонентов стандартной библиотеки C++. Заметим, что для максимального использования вычислительных ресурсов можно использовать многопоточность с помощью библиотеки <thread> и <future>.
Вот пример реализации, который использует многопоточность для ускорения процесса сканирования файлов: Замечания по коду: Итог: в коде точно есть как минимум одна проблема — локальная переменная entry захватывается по ссылке, что приводит к ошибке.
Если исправить на захват по значению, то кажется решение становится правильным. Необходимо реализовать сервис на Go для генерации случайных значений. Сервис реализует JSON API работающее по HTTP. Каждой генерации присваивать уникальный id, по которому можно получить результат генерации методом retrieve. Реализовать методы: POST /api/generate/ — генерация случайного значения и его идентификатора GET /api/retrieve/ — получение значения по id, которое вернулось в методе generate Проект должен быть работоспособным и до конца реализованным, должна быть приведена структура файлов проекта. Не должно быть TODO, все необходимо реализовать до конца. Я немного сократил и конкретизировал задание, но при желании все допусловия также могут быть выполнены GPT. Отлично, ваше приложение может состоять из трех основных частей: Ниже представлен простой пример реализации вашего приложения на Go. Файловая структура: main.go: controller/controller.go: service/service.go: Обратите внимание, что в этом примере мы используем Gorilla Mux для управления роутами. Это очень популярная библиотека, но вы можете использовать любую другую по своему усмотрению. Этот код хранит сгенерированные данные в памяти и не сохраняет их после перезагрузки сервера. Если вам нужно сохранять данные между перезагрузками, вы должны использовать некоторую форму постоянного хранилища, такую как базу данных.  Итог: как минимум это решение работает: Также, отдельным сообщением я попросил его сгенерировать Dockerfile Сначала вам нужно будет создать файл go.mod в корне вашего проекта, если вы его ещё не создали, это можно сделать с помощью команды go mod init <module-name>. Это поможет Go определить ваш проект как модуль и управлять зависимостями. Затем вы можете создать Dockerfile в корне вашего проекта. Он может выглядеть следующим образом: Теперь вы можете собрать Docker-образ с помощью следующей команды: docker build -t my-go-app . И запустить его: docker run -p 8080:8080 my-go-app Обратите внимание, что вместо "Your Name <your-email@domain.com>" и my-go-app вы должны использовать свои собственные значения. Этот Dockerfile создает образ Docker с вашим приложением на Go, устанавливает все зависимости и выполняет приложение при запуске контейнера. Сервер будет доступен на порту 8080. Задание необходимо выполнить на C# Графическое приложение для чтения RSS ленты с сайта. В файле настроек хранятся: Лента, которую обрабатывает фидер. По умолчанию это (https://habr.com/rss/interesting/). Частота обновления ленты приложением. Основное окно должно содержать список элементов ленты (item): Каждый элемент ленты должен отображать название и дату публикации (поля item->title и item->pubDate ). Дата публикации статьи должна отображаться в удобном для чтения пользователем формате. При нажатии на заголовок должен открываться браузер с переходом на выбранную статью (или в уже открытом браузере должна открываться ссылка). Пользователь должен иметь возможность посмотреть описание статьи (поле item->description).
Содержание описания выводится в виде обычного текста (как есть), без форматирования по тегам.
Данные должны периодически обновляться.
Частота обновления должна быть взята из файла конфигурации. Файл настроек имеет формат XML. Выведи структуру решения, затем выведи содержимое каждого файла.
Пиши ответ с учетом того, что мне нужно собрать под mono в linux. Последнее допусловие от меня, так как мне надо это все собрать, чтобы проверить. Принимая во внимание ваш запрос, ниже приведено решение на основе Windows Forms (System.Windows.Forms) и XmlReader для чтения RSS-ленты. Данный пример включает простую форму с ListBox для отображения заголовков новостей и TextBox для отображения описания выбранной новости. Структура решения: Program.cs RssFeed.cs RssItem.cs Settings.cs MainForm.cs После того как все файлы созданы, вы можете собрать проект с помощью команды: mcs -out:RssReader.exe *.cs -r:System.Windows.Forms.dll -r:System.Drawing.dll -r:System.Xml.Linq.dll А затем запустить его: mono RssReader.exe Примечание: Пожалуйста, убедитесь, что у вас установлен Mono и что вы находитесь в правильной директории, когда выполняете эти команды.   Собираем, запускаем  Получилась какая-то ерунда. Окно, в котором каждая строчка — просто слово RssItem.
Помним, что мы не умеем сами править код, пишем текстом претензию роботу. Извините за путаницу. Очевидно, в ListBox нужно отобразить не сами объекты RssItem, а их заголовки. Для этого вам нужно изменить метод LoadRssItems() в файле MainForm.cs следующим образом: В данном случае в ListBox добавляются только заголовки новостей (свойство Title объектов RssItem). Однако, чтобы отобразить описание новости и открыть ее в браузере, вам потребуется сам объект RssItem. Поэтому рекомендуется добавить в класс RssItem переопределение метода ToString(): В таком случае ListBox будет отображать заголовки новостей, но при выборе элемента вы сможете получить полный объект RssItem с его описанием и ссылкой.  Правим, перезапускаем  Итог: теперь похоже на правду. Конечно это еще не ридер, так как показывает только заголовки, но это дело получаса и пяти промптов добиться нужного функционала, без всякого знания C#. Добавить функционал и кнопочки тоже не составит большого труда. То есть выполнить задание за один промпт мы не смогли, но приблизились к этому максимально. Кажется, пора перестать давать тестовые задания на дом. Это становится явлением из вашего прошлого, и ему пора остаться в истории навсегда. Если вы всё же хотите это делать, то вероятно стоит: Скорее всего всё это будет менее эффективной тратой вашего времени, чем просто провести собес со всеми интересными кандидатами. Я смог придумать две задачи, на которые Gpt3.5 не мог дать решение. Как водится, когда он не может сделать нормальное решение, он выдумывает несуществующие конструкции. Правда уже через пару недель у нас появилась Gpt4, и оказалось, что она решает эти задачи. Для примера приведу свою задачу, которую не удалось решить Gpt3.5 Задача Задание несложное, и Gpt3.5, конечно, сделал декоратор, но вот написать на него тесты он не смог. Не придумал, как протестировать собственно само кэширование.
Gpt4 уже не испытывает никаких сложностей с этим. Ваш декоратор для кэширования может выглядеть следующим образом: Здесь cache — это словарь, который используется для хранения результатов вызова функции. Ключами этого словаря являются кортежи, состоящие из аргументов и именованных аргументов, переданных в функцию. Значениями словаря являются результаты вызова функции с данными аргументами. Когда декорированная функция вызывается, декоратор сначала проверяет, есть ли результат для переданных аргументов в кэше. Если результат найден в кэше, он возвращается и сама функция не вызывается. Если результат в кэше не найден, функция вызывается, ее результат сохраняется в кэше и затем возвращается. Если количество элементов в кэше достигает maxsize, перед сохранением нового значения удаляется одно из старых значений. Обратите внимание, что этот декоратор работает только для функций, аргументы которых могут быть хешированы, поскольку они используются в качестве ключей словаря. Тесты для данного декоратора могут проверять следующие аспекты: Здесь тесты, использующие библиотеку pytest: Запустить тесты можно с помощью команды pytest your_test_module.py, где your_test_module.py.  Решение достаточно хорошее, по большому счёту и не прикопаешься ни к чему. Видимо, у вас попросту больше нет возможности удаленно отличить кандидата, хорошо пишущего код, от человека, хорошо пишущего промпты GPT — только дав ему написать что-то очень большое, что не помещается в рамки тестового задания. Поэтому предлагаю поменять практики отбора кандидатов в соответствии с ходом времени. И продолжить эксперименты с ChatGPT, чтобы вовремя выявить новые задачи, с которыми она может справиться за вас (ну или ваших соискателей). Если вам было интересно, то приходите ко мне в команду KasperskyOS Tooling Development — будем вместе следить за нейросетями и другими технологиями, которые способны упростить жизнь разработчика. И разбираться, как можно применять их для создания крутых и удобных инструментов.  А если хотите удостовериться в (без)опасности какого-то своего задания — кидайте в комменты, проверим :) Инженер-программист