December 25, 2024

Java: Senior. Практическое задание hh.ru

Данная статья является продолжением публикации Java: Senior, посвящённых углублённому изучению программирования на Java. В рамках предыдущего материала мы подробно рассматривали аспекты тестирования и отладки сложных приложений, что является ключевым навыком для разработчиков уровня Senior.

Текстовый файл с примерами кода задачи будет прикреплен к посту 👉Telegram

Это наш финальный разбор тестовых и практических материалов в 2024 году. Мы тщательно проработали все темы и уровни — от английского языка до Java, разобрав в общей сложности 59 тестов.

👉🏻Навигацию по материалам вы найдете в Telegram-канале

Задача 1

Лотерейный билет

Вы работаете над сервисом для проведения лотерей. Ваша программа предназначена для обработки последовательности цифр в лотерейном билете и основана на подсчете этих цифр. На основе этого подсчета оценивается сумма выигрыша конкретного билета.

Программа должна подсчитать количество каждой цифры в последовательности, увеличить это количество на один и вернуть результат в виде строки.

Формат ввода:
Одна строка с цифрами.

Формат вывода:
Одна строка, в которой выводится подсчет цифр по заданному алгоритму. Порядок цифр при этом сохраняется.

Пример 1

Входные данные:
13133
Выходные данные:
1:3,3:4

Пример 2

Входные данные:
99999
Выходные данные:
9:6

Пример 3

Входные данные:
99
Выходные данные:
9:3

Пример 4

Входные данные:
13123
Выходные данные:
1:3,3:3,2:2

📌Код:

import java.util.*;

class LotteryTicket { public String countDigits(String digits) { Map<Character, Integer> countMap = new LinkedHashMap<>(); // Подсчёт вхождений цифр for (char c : digits.toCharArray()) { countMap.put(c, countMap.getOrDefault(c, 0) + 1); } // Формирование результата с заменой ; на , StringBuilder result = new StringBuilder(); for (Map.Entry<Character, Integer> entry : countMap.entrySet()) { result.append(entry.getKey()).append(":").append(entry.getValue() + 1).append(","); } // Удаляем последний символ ',' if (result.length() > 0) { result.setLength(result.length() - 1); } return result.toString(); } }

Пример использования:

public class Main { public static void main(String[] args) { LotteryTicket lt = new LotteryTicket(); System.out.println(lt.countDigits("13133")); // Output: 1:3;3:4 System.out.println(lt.countDigits("99999")); // Output: 9:6 System.out.println(lt.countDigits("99")); // Output: 9:3 System.out.println(lt.countDigits("13123"));// Output: 1:3;3:3;2:2 } }

Вывод:
Для данных тестовых случаев:

  • Ввод: 13133 → Вывод: 1:3,3:4
  • Ввод: 99999 → Вывод: 9:6
  • Ввод: 99 → Вывод: 9:3
  • Ввод: 13123 → Вывод: 1:3,3:3,2:2


Задача 2

Отчёт о продажах за квартал

Вы работаете над модулем для CRM-системы, который помогает менеджерам по продажам готовить автоматизированные отчёты. Данные о продажах поступают в формате Дата:Продукт:Количество;Дата:Продукт:Количество;....

Напишите программу, которая принимает информацию о продажах по датам и возвращает отчёт о продажах за каждый квартал.

Формат ввода:
Одна строка с данными в формате Дата:Продукт:Количество;Дата:Продукт:Количество;....

  • Дата в формате YYYY-MM-DD.
  • Через двоеточие указано название продукта на русском языке.
  • Через ещё одно двоеточие — положительное число, указывающее количество проданных товаров.

Данные по продуктам разделены точкой с запятой.

Формат вывода:
Набор строк, в котором выводится информация о продажах товаров:

  1. Сначала даётся номер квартала с двоеточием (например, Q1:).
  2. На последующих строках — маркированный список с дефисом с названием товара и количеством продаж через двоеточие.

Количество продаж по одному продукту в одном квартале суммируется. Каждый пункт списка начинается с новой строки.

Кварталы идут по порядку, а товары внутри квартала сортируются по алфавиту.
Если в каком-то квартале не было продаж, он никак не отображается в отчёте.

Пример 1
Входные данные:
2023-01-15:Книга:10;2023-04-20:Флешка:5;2023-07-05:Наушники:8
Выходные данные:

Q1:
- Книга: 10
Q2:
- Флешка: 5
Q3:
- Наушники: 8

Пример 2
Входные данные:
2023-02-05:Шляпа:4;2023-03-20:Кольцо:7;2023-04-25:Браслет:6;2023-04-26:Браслет:12
Выходные данные:

Q1:
- Кольцо: 7
- Шляпа: 4
Q2:
- Браслет: 18

Решение:

  1. Разбиение данных: Данные разбиваются по точке с запятой ; для получения отдельных записей.
  2. Определение квартала: На основе месяца из даты определяется номер квартала (Q1, Q2, Q3, Q4).
  3. Агрегация данных: Для каждого продукта суммируется количество продаж в рамках квартала.
  4. Сортировка: Товары внутри квартала сортируются по алфавиту.
  5. Форматирование результата: Формируется строка отчёта в нужном формате.

📌Код:

import java.util.*;

class SalesReport { public String generateReport(String salesData) { String[] records = salesData.split(";"); Map<String, Map<String, Integer>> quarterSales = new TreeMap<>(); // Обработка данных for (String record : records) { String[] parts = record.split(":"); String date = parts[0]; String product = parts[1]; int quantity = Integer.parseInt(parts[2]); // Получаем номер квартала int month = Integer.parseInt(date.split("-")[1]); String quarter = getQuarter(month); // Агрегируем данные quarterSales.putIfAbsent(quarter, new TreeMap<>()); Map<String, Integer> productSales = quarterSales.get(quarter); productSales.put(product, productSales.getOrDefault(product, 0) + quantity); } // Формируем результат StringBuilder report = new StringBuilder(); for (String quarter : quarterSales.keySet()) { report.append(quarter).append(":\n"); for (Map.Entry<String, Integer> entry : quarterSales.get(quarter).entrySet()) { report.append("- ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } } return report.toString().trim(); } private String getQuarter(int month) { if (month >= 1 && month <= 3) return "Q1"; if (month >= 4 && month <= 6) return "Q2"; if (month >= 7 && month <= 9) return "Q3"; if (month >= 10 && month <= 12) return "Q4"; return ""; } }

Пример ввода и вывода:

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

2023-02-05:Шляпа:4;2023-03-20:Кольцо:7;2023-04-25:Браслет:6;2023-04-26:Браслет:12
Выходные данные:

Q1: - Кольцо: 7 - Шляпа: 4 Q2: - Браслет: 18

Объяснение:

  1. Метод getQuarter определяет квартал на основе месяца.
  2. Используются TreeMap для автоматической сортировки кварталов и продуктов по алфавиту.
  3. Все данные корректно суммируются и форматируются в отчёт.


Задача 3

Рейтинг по предмету
Вы работаете над модулем «Электронная зачётка» в системе для администрирования учебного процесса регионального вуза. Каждый студент может быть записан на несколько курсов, по каждому курсу у него есть итоговый балл.

Необходимо написать программу для учебного офиса. Программа будет получать и хранить списки студентов, а также итоговый балл студента по каждому курсу. Программа также должна уметь составлять отсортированный список студентов, которые набрали строго больше проходного балла по определённому предмету. Сортировка идёт по убыванию баллов студентов за этот предмет.

Формат ввода:

  • Первая строка содержит информацию о студентах, курсах и их оценках в формате:
    <имя_студента,курс,оценка;имя_студента,курс,оценка;...>.
    В строке есть информация хотя бы об одном студенте.
  • Вторая строка содержит предмет, по которому запрошена статистика, и его проходной балл:
    <курс,проходной_балл>.

Все баллы являются целыми положительными числами, а все имена студентов уникальны.

Формат вывода:
Имена студентов и их баллы за этот предмет через запятую без пробела, каждый студент с новой строки.
Выводятся только те студенты, кто набрал строго больше проходного балла по этому предмету.

  • Если студенты набрали одинаковый балл, они сортируются в порядке ввода.
  • Если никто не набрал проходной балл, выводится слово «Никто» (без кавычек).

Пример 1
Входные данные:
Анна,Математика,85;Анна,Химия,90;Борис,Математика,75;Борис,История,80;Евгений,Математика,95;Евгений,История,85
Математика,80
Выходные данные:

Евгений,95  
Анна,85  

Пример 2
Входные данные:
Анна,Психология,8;Алексей,Психология,6
Психология,9
Выходные данные:

Никто  

Решение:

  1. Разбиение данных: Разделить первую строку по ; для получения информации о каждом студенте.
  2. Извлечение условий: Разделить вторую строку для получения предмета и проходного балла.
  3. Фильтрация студентов: Оставить только тех, кто соответствует предмету и набрал больше проходного балла.
  4. Сортировка: Студенты сортируются по убыванию баллов. Если баллы равны, сохраняется порядок ввода.
  5. Форматирование результата: Имена и баллы студентов выводятся с новой строки. Если подходящих студентов нет, выводится "Никто".

📌Код:

import java.util.*;

class StudentRating { public String getStudentRating(String studentData, String courseInfo) { String[] students = studentData.split(";"); String[] courseParts = courseInfo.split(","); String targetCourse = courseParts[0]; int passingScore = Integer.parseInt(courseParts[1]);

// Список для хранения студентов, прошедших отбор List<String[]> filteredStudents = new ArrayList<>();

// Разбор данных студентов for (String student : students) { String[] parts = student.split(","); String name = parts[0]; String course = parts[1]; int score = Integer.parseInt(parts[2]);

if (course.equals(targetCourse) && score > passingScore) { filteredStudents.add(new String[]{name, String.valueOf(score)}); } }

// Сортировка по убыванию баллов, сохранение порядка ввода при равных баллах filteredStudents.sort((a, b) -> Integer.compare(Integer.parseInt(b[1]), Integer.parseInt(a[1])));

// Формирование результата if (filteredStudents.isEmpty()) { return "Никто"; } StringBuilder result = new StringBuilder(); for (String[] student : filteredStudents) { result.append(student[0]).append(",").append(student[1]).append("\n"); } return result.toString().trim(); } }

Пример использования:

public class Main { public static void main(String[] args) { StudentRating sr = new StudentRating(); // Пример 1 String studentData1 = "Анна,Математика,85;Анна,Химия,90;Борис,Математика,75;Борис,История,80;Евгений,Математика,95;Евгений,История,85"; String courseInfo1 = "Математика,80"; System.out.println(sr.getStudentRating(studentData1, courseInfo1)); // Пример 2 String studentData2 = "Анна,Психология,8;Алексей,Психология,6"; String courseInfo2 = "Психология,8"; System.out.println(sr.getStudentRating(studentData2, courseInfo2)); // Пример 3 String studentData3 = "Мария,Математика,85;Мария,Химия,90;Петр,Математика,75;Петр,История,80;Ольга,Математика,95;Ольга,История,85"; String courseInfo3 = "Химия,40"; System.out.println(sr.getStudentRating(studentData3, courseInfo3)); } }

Объяснение:

  1. Фильтрация: Проверяется соответствие предмету и превышение проходного балла.
  2. Сортировка: Используется сортировка по убыванию баллов с сохранением порядка при равных значениях.
  3. Форматирование: Имя и балл студента выводятся через запятую с новой строки.


Задача 4

Расшифровка методом Цезаря

Вы разрабатываете программу для расшифровки текстовых сообщений, зашифрованных методом Цезаря. Этот метод подразумевает сдвиг каждой буквы текста на фиксированное количество позиций в алфавите. Например, при сдвиге на 3 позиции буква А становится Г, а Я становится В. Соответственно, при расшифровке нужно двигаться в обратном порядке.

Ваша задача — написать функцию, которая принимает на вход зашифрованную по методу Цезаря строку текста и целое число (сдвиг), и возвращает расшифрованную версию. Алфавит уже задан в предкоде.

Формат ввода:

  • Входные данные состоят из двух строк:
    1. Первая строка содержит произвольный зашифрованный текст (только строчные буквы русского алфавита и пробелы).
    2. Вторая строка содержит целое число — величину сдвига (1 ≤ x ≤ 32).

Формат вывода:

  • Выходные данные должны состоять из одной строки, содержащей расшифрованный текст. Пробел расшифровывать не нужно.

Пример 1
Входные данные:

бвгдеё
1

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

абвгде

Пример 2
Входные данные:

дщх ёзтхсх счжшфхл
7

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

это яблоко красное

Алгоритм:

  1. Создание алфавита: Используем строку русского алфавита.
  2. Обработка сдвига: Так как сдвиг должен идти в обратную сторону, корректируем индекс буквы на (текущий индекс - сдвиг) % длина алфавита.
  3. Обработка пробелов: Пробелы не изменяются.
  4. Модульная арифметика: Учёт отрицательных индексов через (index + ALPHABET.length()) % ALPHABET.length().

📌Код:

class CaesarCipherDecoder { // Русский алфавит private static final String ALPHABET = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя";

public String decode(String encryptedText, int shift) { StringBuilder result = new StringBuilder(); int alphabetLength = ALPHABET.length();

for (char c : encryptedText.toCharArray()) { if (c == ' ') { result.append(c); // Пробелы остаются неизменными continue; } int currentIndex = ALPHABET.indexOf(c); int newIndex = (currentIndex - shift + alphabetLength) % alphabetLength; result.append(ALPHABET.charAt(newIndex)); } return result.toString(); } }

Пример использования:

public class Main { public static void main(String[] args) { CaesarCipherDecoder decoder = new CaesarCipherDecoder(); System.out.println(decoder.decode("бгдгё", 1)); // Пример 1: "абвгде" System.out.println(decoder.decode("дщх ёэтхсх счхшфхл", 7)); // Пример 2: "это яблоко красное" System.out.println(decoder.decode("нбмэший идсбёу г хфувпм", 1)); // Пример 3: "мальчик играет в футбол" } }

Результат:

  1. Пример 1
    Вход: бвгдеё, Сдвиг: 1
    Выход: абвгде
  2. Пример 2
    Вход: дщх ёзтхсх счжшфхл, Сдвиг: 7
    Выход: это яблоко красное
  3. Пример 3
    Вход: нбмэший идсбёу г хфувпм, Сдвиг: 1
    Выход: мальчик играет в футбол

Объяснение:

  1. Алгоритм использует модуль % для корректировки сдвига на отрицательные индексы.
  2. Обрабатываются только буквы русского алфавита; пробелы остаются неизменными.


Задача 5

Вы работаете над внутрикорпоративной системой доступа пользователей. Реализуйте класс UserManager, который будет управлять доступом пользователей к системе.

У класса должны быть методы для добавления нового пользователя, удаления пользователя, повышения и понижения уровня доступа пользователя, а также для получения списка всех пользователей. Уровни доступа пользователей – это целые числа (0 ≤ x).

Класс UserManager должен поддерживать следующие методы, которые должны быть цепочками (кроме get_users):

  • add_user – добавляет нового пользователя с указанным уровнем доступа или уровнем доступа по умолчанию, равным 1;
  • remove_user – удаляет пользователя;
  • promote – увеличивает уровень доступа пользователя на 1;
  • demote – уменьшает уровень доступа на 1, если уровень доступа больше 0. Иначе ничего не происходит.
  • get_users – вернет список всех пользователей с их текущим уровнем доступа.

Формат ввода
Несколько строк, состоящих из команд add_user, remove_user, promote, demote, get_users. Входные данные гарантированно завершаются командой get_users.

Формат вывода
Несколько строк, в которой может быть один из вариантов:

  • Пользователи с уровнем доступа, при этом исходный порядок пользователей сохраняется;
  • «Не найдено», если пользователей в списке не осталось.

Пример 1
Входные данные:

add_user Alice 4  
add_user Bob 3  
add_user Charlie 2  
get_users  

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

Alice: 4  
Bob: 3  
Charlie: 2  

Пример 2
Входные данные:

add_user Alice 1  
add_user Bob 3  
add_user Charlie 3  
promote Alice  
promote Alice  
demote Alice  
get_users  

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

Alice: 2  
Bob: 3  
Charlie: 3

Пример 3
Входные данные:

add_user Bob 3  
add_user Charlie 2  
remove_user Bob  
remove_user Charlie  
get_users

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

Не найдено

📌Код:

import java.util.ArrayList; import java.util.List;

public class CommandLineProcessor { private UserManager userManager = new UserManager();

/** * Обработка одной строки. * * @param commandLine строка команды. Например: "add_user Alice 4". Другой пример: "get_users" * @return вывод команды, или null если команда не возвращает строку */ public String processInput(String commandLine) { String[] parts = commandLine.split(" "); String command = parts[0];

switch (command) { case "add_user": if (parts.length == 3) { // С указанным уровнем доступа String name = parts[1]; int level = Integer.parseInt(parts[2]); userManager.addUser(name, level); } else if (parts.length == 2) { // С уровнем по умолчанию String name = parts[1]; userManager.addUser(name); } break; case "remove_user": userManager.removeUser(parts[1]); break; case "promote": userManager.promote(parts[1]); break; case "demote": userManager.demote(parts[1]); break; case "get_users": return userManager.getUsers(); default: return "Неверная команда"; } return null; }

public static void main(String[] args) { CommandLineProcessor processor = new CommandLineProcessor();

// Тест 1 String[] commands1 = { "add_user Alice 4", "add_user Bob 3", "add_user Charlie 2", "get_users" }; processCommands(processor, commands1);

// Тест 2 String[] commands2 = { "add_user Alice 1", "add_user Bob 3", "add_user Charlie 3", "promote Alice", "promote Alice", "demote Alice", "get_users" }; processCommands(processor, commands2);

// Тест 3 String[] commands3 = { "add_user Bob 3", "add_user Charlie 2", "remove_user Bob", "remove_user Charlie", "get_users" }; processCommands(processor, commands3); }

private static void processCommands(CommandLineProcessor processor, String[] commands) { for (String command : commands) { String output = processor.processInput(command); if (output != null) { System.out.println(output); } } } }

class UserManager { private List<User> users;

public UserManager() { users = new ArrayList<>(); }

public UserManager addUser(String name, int accessLevel) { users.add(new User(name, accessLevel)); return this; }

public UserManager addUser(String name) { return addUser(name, 1); // Уровень доступа по умолчанию = 1 }

public UserManager removeUser(String name) { users.removeIf(user -> user.getName().equals(name)); return this; }

public UserManager promote(String name) { for (User user : users) { if (user.getName().equals(name)) { user.setAccessLevel(user.getAccessLevel() + 1); break; } } return this; }

public UserManager demote(String name) { for (User user : users) { if (user.getName().equals(name) && user.getAccessLevel() > 0) { user.setAccessLevel(user.getAccessLevel() - 1); break; } } return this; }

public String getUsers() { if (users.isEmpty()) { return "Не найдено"; }

StringBuilder result = new StringBuilder(); for (User user : users) { result.append(user.getName()).append(": ").append(user.getAccessLevel()).append("\n"); } return result.toString().trim(); } }

class User { private String name; private int accessLevel;

public User(String name, int accessLevel) { this.name = name; this.accessLevel = accessLevel; }

public String getName() { return name; }

public int getAccessLevel() { return accessLevel; }

public void setAccessLevel(int accessLevel) { this.accessLevel = accessLevel; } }

Описание:

  1. Метод processInput:
    • Обрабатывает команды: add_user, remove_user, promote, demote и get_users.
    • Возвращает результат для get_users.
  2. Методы цепочки:
    • Методы addUser, removeUser, promote, и demote возвращают this для поддержания цепочки вызовов.
  3. Класс User:
    • Добавлен метод setAccessLevel для изменения уровня доступа пользователя.
  4. Тестирование:
    • В main представлены три тестовых случая с ожидаемым выводом.

Как это работает:

  1. add_user: Добавляет пользователя с указанным или уровнем доступа по умолчанию.
  2. remove_user: Удаляет пользователя из списка.
  3. promote: Повышает уровень доступа.
  4. demote: Понижает уровень доступа, но не ниже 0.
  5. get_users: Возвращает список пользователей или сообщение Не найдено, если список пуст.