From 3f8aee3025df45d1bce96b848b507e5f16f87f24 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 13 Aug 2025 14:52:51 +0400 Subject: [PATCH] vault backup: 2025-08-13 14:52:51 --- .../Задачи/💼 Прикладная задача для Middle разработчика.md | 441 +----------------- 1 file changed, 21 insertions(+), 420 deletions(-) diff --git a/💼 Работа/Собеседования/Лето 2025/Задачи/💼 Прикладная задача для Middle разработчика.md b/💼 Работа/Собеседования/Лето 2025/Задачи/💼 Прикладная задача для Middle разработчика.md index 24b56a1..e636226 100644 --- a/💼 Работа/Собеседования/Лето 2025/Задачи/💼 Прикладная задача для Middle разработчика.md +++ b/💼 Работа/Собеседования/Лето 2025/Задачи/💼 Прикладная задача для Middle разработчика.md @@ -24,29 +24,16 @@ ### **Техническое задание:** -Реализуйте следующие компоненты: +**Опишите подход к решению задачи:** -1. **Entity классы** (Event, Participant, Registration) - -2. **Repository интерфейсы** - -3. **Service класс EventRegistrationService** с методами: - - - `registerParticipant(Long eventId, Long participantId)` - - `cancelRegistration(Long eventId, Long participantId)` - - `cancelEvent(Long eventId)` - - `getEventParticipants(Long eventId)` -4. **REST Controller** с endpoint'ами для регистрации - -5. **Обработка исключений** для всех бизнес-правил - - -### **Дополнительные требования:** - -- Использовать Spring Boot, JPA/Hibernate -- Добавить валидацию данных -- Написать unit-тесты для сервиса -- Подумать о транзакциях и concurrency +1. **Архитектуру приложения** - какие слои нужны +2. **Entity классы** - основные поля и связи +3. **Ключевые методы EventRegistrationService** +4. **Обработку бизнес-правил** - где и как валидировать +5. **REST API endpoints** - какие нужны +6. **Обработку ошибок** - типы исключений +7. **Транзакции** - где нужны и почему +8. **Тестирование** - что важно покрыть тестами **Время на выполнение:** 25 минут @@ -69,7 +56,7 @@ ``` Ввод: meetings = [[0,30],[5,10],[15,20]] Вывод: 2 -Объяснение: Можно выбрать встречи [0,30] и [15,20] ИЛИ [5,10] и [15,20] +Объяснение: Можно выбрать встречи [5,10] и [15,20] ``` **Пример 2:** @@ -104,397 +91,7 @@ --- -# ✅ Решения задач - -## 💼 Решение прикладной задачи - -
Показать решение - -### **1. Entity классы:** - -```java -@Entity -@Table(name = "events") -public class Event { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false) - private String name; - - private String description; - - @Column(name = "start_date", nullable = false) - private LocalDateTime startDate; - - @Column(name = "end_date", nullable = false) - private LocalDateTime endDate; - - @Column(name = "max_participants", nullable = false) - private Integer maxParticipants; - - @Enumerated(EnumType.STRING) - private EventStatus status; - - @OneToMany(mappedBy = "event", cascade = CascadeType.ALL, fetch = FetchType.LAZY) - private List registrations = new ArrayList<>(); - - // конструкторы, геттеры, сеттеры -} - -@Entity -@Table(name = "participants") -public class Participant { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false) - private String name; - - @Column(nullable = false, unique = true) - private String email; - - // конструкторы, геттеры, сеттеры -} - -@Entity -@Table(name = "registrations") -public class Registration { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "event_id", nullable = false) - private Event event; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "participant_id", nullable = false) - private Participant participant; - - @Column(name = "registration_date", nullable = false) - private LocalDateTime registrationDate; - - // конструкторы, геттеры, сеттеры -} - -public enum EventStatus { - DRAFT, PUBLISHED, CANCELLED -} -``` - -### **2. Repository интерфейсы:** - -```java -@Repository -public interface EventRepository extends JpaRepository { - List findByStatus(EventStatus status); -} - -@Repository -public interface ParticipantRepository extends JpaRepository { - Optional findByEmail(String email); -} - -@Repository -public interface RegistrationRepository extends JpaRepository { - boolean existsByEventIdAndParticipantId(Long eventId, Long participantId); - - Optional findByEventIdAndParticipantId(Long eventId, Long participantId); - - @Query("SELECT r.participant FROM Registration r WHERE r.event.id = :eventId") - List findParticipantsByEventId(@Param("eventId") Long eventId); - - long countByEventId(Long eventId); - - void deleteByEventId(Long eventId); -} -``` - -### **3. Service класс:** - -```java -@Service -@Transactional -public class EventRegistrationService { - - private final EventRepository eventRepository; - private final ParticipantRepository participantRepository; - private final RegistrationRepository registrationRepository; - private final NotificationService notificationService; - - public EventRegistrationService(EventRepository eventRepository, - ParticipantRepository participantRepository, - RegistrationRepository registrationRepository, - NotificationService notificationService) { - this.eventRepository = eventRepository; - this.participantRepository = participantRepository; - this.registrationRepository = registrationRepository; - this.notificationService = notificationService; - } - - public void registerParticipant(Long eventId, Long participantId) { - Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new EventNotFoundException("Event not found: " + eventId)); - - Participant participant = participantRepository.findById(participantId) - .orElseThrow(() -> new ParticipantNotFoundException("Participant not found: " + participantId)); - - validateRegistration(event, participant); - - Registration registration = new Registration(); - registration.setEvent(event); - registration.setParticipant(participant); - registration.setRegistrationDate(LocalDateTime.now()); - - registrationRepository.save(registration); - } - - private void validateRegistration(Event event, Participant participant) { - // Проверка статуса события - if (event.getStatus() != EventStatus.PUBLISHED) { - throw new RegistrationException("Event is not published"); - } - - // Проверка даты начала - if (event.getStartDate().isBefore(LocalDateTime.now())) { - throw new RegistrationException("Event has already started"); - } - - // Проверка лимита участников - long currentParticipants = registrationRepository.countByEventId(event.getId()); - if (currentParticipants >= event.getMaxParticipants()) { - throw new RegistrationException("Event is full"); - } - - // Проверка дублирования регистрации - if (registrationRepository.existsByEventIdAndParticipantId(event.getId(), participant.getId())) { - throw new RegistrationException("Participant already registered for this event"); - } - } - - public void cancelRegistration(Long eventId, Long participantId) { - Registration registration = registrationRepository.findByEventIdAndParticipantId(eventId, participantId) - .orElseThrow(() -> new RegistrationNotFoundException("Registration not found")); - - registrationRepository.delete(registration); - } - - public void cancelEvent(Long eventId) { - Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new EventNotFoundException("Event not found: " + eventId)); - - List participants = registrationRepository.findParticipantsByEventId(eventId); - - // Отмена события - event.setStatus(EventStatus.CANCELLED); - eventRepository.save(event); - - // Уведомление участников - participants.forEach(participant -> - notificationService.notifyEventCancellation(participant, event)); - } - - @Transactional(readOnly = true) - public List getEventParticipants(Long eventId) { - if (!eventRepository.existsById(eventId)) { - throw new EventNotFoundException("Event not found: " + eventId); - } - - return registrationRepository.findParticipantsByEventId(eventId); - } -} -``` - -### **4. REST Controller:** - -```java -@RestController -@RequestMapping("/api/events") -@Validated -public class EventRegistrationController { - - private final EventRegistrationService registrationService; - - public EventRegistrationController(EventRegistrationService registrationService) { - this.registrationService = registrationService; - } - - @PostMapping("/{eventId}/participants/{participantId}") - public ResponseEntity registerParticipant( - @PathVariable @Positive Long eventId, - @PathVariable @Positive Long participantId) { - - registrationService.registerParticipant(eventId, participantId); - return ResponseEntity.ok().build(); - } - - @DeleteMapping("/{eventId}/participants/{participantId}") - public ResponseEntity cancelRegistration( - @PathVariable @Positive Long eventId, - @PathVariable @Positive Long participantId) { - - registrationService.cancelRegistration(eventId, participantId); - return ResponseEntity.ok().build(); - } - - @DeleteMapping("/{eventId}") - public ResponseEntity cancelEvent(@PathVariable @Positive Long eventId) { - registrationService.cancelEvent(eventId); - return ResponseEntity.ok().build(); - } - - @GetMapping("/{eventId}/participants") - public ResponseEntity> getEventParticipants( - @PathVariable @Positive Long eventId) { - - List participants = registrationService.getEventParticipants(eventId); - List dtos = participants.stream() - .map(this::toDto) - .collect(Collectors.toList()); - - return ResponseEntity.ok(dtos); - } - - private ParticipantDto toDto(Participant participant) { - return new ParticipantDto(participant.getId(), participant.getName(), participant.getEmail()); - } -} -``` - -### **5. Exception Handling:** - -```java -@ControllerAdvice -public class GlobalExceptionHandler { - - @ExceptionHandler(EventNotFoundException.class) - public ResponseEntity handleEventNotFound(EventNotFoundException ex) { - ErrorResponse error = new ErrorResponse("EVENT_NOT_FOUND", ex.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); - } - - @ExceptionHandler(RegistrationException.class) - public ResponseEntity handleRegistrationError(RegistrationException ex) { - ErrorResponse error = new ErrorResponse("REGISTRATION_ERROR", ex.getMessage()); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); - } - - @ExceptionHandler(ConstraintViolationException.class) - public ResponseEntity handleValidation(ConstraintViolationException ex) { - ErrorResponse error = new ErrorResponse("VALIDATION_ERROR", "Invalid input parameters"); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); - } -} -``` - -### **6. Unit тесты:** - -```java -@ExtendWith(MockitoExtension.class) -class EventRegistrationServiceTest { - - @Mock - private EventRepository eventRepository; - - @Mock - private ParticipantRepository participantRepository; - - @Mock - private RegistrationRepository registrationRepository; - - @Mock - private NotificationService notificationService; - - @InjectMocks - private EventRegistrationService service; - - @Test - void registerParticipant_Success() { - // Given - Long eventId = 1L; - Long participantId = 1L; - - Event event = createPublishedEvent(); - Participant participant = createParticipant(); - - when(eventRepository.findById(eventId)).thenReturn(Optional.of(event)); - when(participantRepository.findById(participantId)).thenReturn(Optional.of(participant)); - when(registrationRepository.countByEventId(eventId)).thenReturn(5L); - when(registrationRepository.existsByEventIdAndParticipantId(eventId, participantId)).thenReturn(false); - - // When - service.registerParticipant(eventId, participantId); - - // Then - verify(registrationRepository).save(any(Registration.class)); - } - - @Test - void registerParticipant_EventNotPublished_ThrowsException() { - // Given - Event event = createDraftEvent(); - Participant participant = createParticipant(); - - when(eventRepository.findById(1L)).thenReturn(Optional.of(event)); - when(participantRepository.findById(1L)).thenReturn(Optional.of(participant)); - - // When & Then - assertThrows(RegistrationException.class, - () -> service.registerParticipant(1L, 1L)); - } - - @Test - void registerParticipant_EventFull_ThrowsException() { - // Given - Event event = createPublishedEvent(); - event.setMaxParticipants(10); - Participant participant = createParticipant(); - - when(eventRepository.findById(1L)).thenReturn(Optional.of(event)); - when(participantRepository.findById(1L)).thenReturn(Optional.of(participant)); - when(registrationRepository.countByEventId(1L)).thenReturn(10L); - - // When & Then - assertThrows(RegistrationException.class, - () -> service.registerParticipant(1L, 1L)); - } - - private Event createPublishedEvent() { - Event event = new Event(); - event.setId(1L); - event.setStatus(EventStatus.PUBLISHED); - event.setStartDate(LocalDateTime.now().plusDays(1)); - event.setMaxParticipants(10); - return event; - } - - private Event createDraftEvent() { - Event event = new Event(); - event.setId(1L); - event.setStatus(EventStatus.DRAFT); - event.setStartDate(LocalDateTime.now().plusDays(1)); - event.setMaxParticipants(10); - return event; - } - - private Participant createParticipant() { - Participant participant = new Participant(); - participant.setId(1L); - participant.setName("John Doe"); - participant.setEmail("john@example.com"); - return participant; - } -} -``` - -
- ---- - -## 🧮 Решение алгоритмической задачи +# ✅ Решение алгоритмической задачи
Показать решение @@ -556,6 +153,14 @@ public class MeetingScheduler { ### **Пошаговый пример:** +Для `meetings = [[0,30],[5,10],[15,20]]`: + +1. После сортировки по времени окончания: `[[5,10], [15,20], [0,30]]` +2. Выбираем `[5,10]`, `lastEndTime = 10` +3. Проверяем `[15,20]`: `15 >= 10` ✓, выбираем, `lastEndTime = 20` +4. Проверяем `[0,30]`: `0 < 20` ✗, пропускаем +5. Результат: 2 встречи + Для `meetings = [[1,5],[8,9],[8,9],[5,9],[9,15]]`: 1. После сортировки по времени окончания: `[[1,5], [8,9], [8,9], [5,9], [9,15]]` @@ -631,14 +236,10 @@ class MeetingSchedulerTest { } ``` -### **Альтернативные подходы:** - -1. **Dynamic Programming:** Сложность O(n²), не оптимально для этой задачи -2. **Recursive with memoization:** Также O(n²), избыточно -3. **Priority Queue:** Можно использовать, но жадный алгоритм проще и эффективнее - ### **Почему жадный алгоритм работает:** Выбирая встречу с самым ранним окончанием, мы всегда оставляем максимально возможное время для размещения остальных встреч. Это оптимальная стратегия для данной задачи. +**Доказательство корректности:** Пусть OPT - оптимальное решение, а GREEDY - решение жадного алгоритма. Можно показать, что GREEDY не хуже OPT путем замены встреч в OPT на встречи из GREEDY без ухудшения результата. +
\ No newline at end of file