docs: add student occurrence index finder task
This commit is contained in:
@@ -0,0 +1,688 @@
|
|||||||
|
# Техническое задание: многопоточный поиск индексов `Student`
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Реализовать многопоточный поиск всех вхождений заданного объекта `Student` в `MyList<Student>`.
|
||||||
|
|
||||||
|
Метод **не должен выводить никаких сообщений в консоль**.
|
||||||
|
|
||||||
|
Результатом работы должен быть массив `int[]`, содержащий индексы всех элементов исходной коллекции, которые равны переданному `Student`.
|
||||||
|
|
||||||
|
Сравнение выполнять через существующий `Student.equals()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Входная информация
|
||||||
|
|
||||||
|
Основной метод принимает:
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Student> students
|
||||||
|
```
|
||||||
|
|
||||||
|
Исходная коллекция студентов.
|
||||||
|
|
||||||
|
```java
|
||||||
|
Student target
|
||||||
|
```
|
||||||
|
|
||||||
|
Студент, вхождения которого необходимо найти.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Выходная информация
|
||||||
|
|
||||||
|
Метод возвращает:
|
||||||
|
|
||||||
|
```java
|
||||||
|
int[]
|
||||||
|
```
|
||||||
|
|
||||||
|
Массив должен содержать индексы всех совпавших элементов.
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Исходная коллекция:
|
||||||
|
|
||||||
|
[Student A, Student B, Student A, Student C, Student A]
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
[0, 2, 4]
|
||||||
|
```
|
||||||
|
|
||||||
|
Индексы должны быть расположены в порядке возрастания.
|
||||||
|
|
||||||
|
При отсутствии совпадений:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[]
|
||||||
|
```
|
||||||
|
|
||||||
|
При пустой коллекции:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Название класса
|
||||||
|
|
||||||
|
```text
|
||||||
|
StudentOccurrenceIndexFinder
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Публичные методы и контракты
|
||||||
|
|
||||||
|
## `findOccurrences`
|
||||||
|
|
||||||
|
```java
|
||||||
|
public int[] findOccurrences(
|
||||||
|
MyList<Student> students,
|
||||||
|
Student target
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Метод должен:
|
||||||
|
|
||||||
|
1. Проверить входные параметры.
|
||||||
|
2. Разделить коллекцию на диапазоны индексов.
|
||||||
|
3. Передать каждый диапазон отдельной задаче.
|
||||||
|
4. Выполнить поиск в нескольких потоках.
|
||||||
|
5. Получить от каждого потока найденные индексы.
|
||||||
|
6. Объединить результаты.
|
||||||
|
7. Вернуть индексы в порядке возрастания.
|
||||||
|
8. Не изменять исходную коллекцию.
|
||||||
|
9. Не изменять объекты `Student`.
|
||||||
|
10. Не выводить ничего в консоль.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Вспомогательные методы
|
||||||
|
|
||||||
|
Количество и вид `private` методов могут быть выбраны разработчиком.
|
||||||
|
|
||||||
|
Рекомендуется выделить:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private int calculateThreadCount(int size);
|
||||||
|
```
|
||||||
|
|
||||||
|
Определяет количество рабочих потоков.
|
||||||
|
|
||||||
|
```java
|
||||||
|
private int countChunkSize(int size, int threadCount);
|
||||||
|
```
|
||||||
|
|
||||||
|
Определяет размер диапазона для одной задачи.
|
||||||
|
|
||||||
|
```java
|
||||||
|
private MyList<Integer> findInRange(
|
||||||
|
MyList<Student> students,
|
||||||
|
Student target,
|
||||||
|
int fromIndex,
|
||||||
|
int toIndex
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Обрабатывает диапазон:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[fromIndex, toIndex)
|
||||||
|
```
|
||||||
|
|
||||||
|
и возвращает индексы найденных совпадений.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Работа с многопоточностью
|
||||||
|
|
||||||
|
## Рекомендуемый подход
|
||||||
|
|
||||||
|
Не использовать общий контейнер результатов, к которому одновременно обращаются несколько потоков.
|
||||||
|
|
||||||
|
Также **не рекомендуется использовать мьютексы** (`synchronized`, `Lock`, `ReentrantLock`) для хранения результата.
|
||||||
|
|
||||||
|
Лучше использовать модель:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MyList<Student>
|
||||||
|
│
|
||||||
|
┌────────────┼────────────┐
|
||||||
|
↓ ↓ ↓
|
||||||
|
Поток 1 Поток 2 Поток 3
|
||||||
|
диапазон диапазон диапазон
|
||||||
|
│ │ │
|
||||||
|
↓ ↓ ↓
|
||||||
|
MyList<Integer> MyList<Integer> MyList<Integer>
|
||||||
|
└────────────┼────────────┘
|
||||||
|
↓
|
||||||
|
основной поток
|
||||||
|
↓
|
||||||
|
merge
|
||||||
|
↓
|
||||||
|
int[]
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый поток создаёт **собственный контейнер `MyList<Integer>`** и записывает найденные индексы только в него.
|
||||||
|
|
||||||
|
Это позволяет избежать состояния гонки без синхронизации общей структуры.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Разбиение коллекции на потоки
|
||||||
|
|
||||||
|
Количество потоков рекомендуется определять на основании:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Runtime.getRuntime().availableProcessors()
|
||||||
|
```
|
||||||
|
|
||||||
|
Количество потоков не должно превышать количество элементов:
|
||||||
|
|
||||||
|
```text
|
||||||
|
threadCount = min(
|
||||||
|
availableProcessors(),
|
||||||
|
students.size()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пример
|
||||||
|
|
||||||
|
Для коллекции из `10000` элементов и четырёх рабочих потоков:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Поток 1 → [0, 2500)
|
||||||
|
Поток 2 → [2500, 5000)
|
||||||
|
Поток 3 → [5000, 7500)
|
||||||
|
Поток 4 → [7500, 10000)
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый индекс должен принадлежать **ровно одному диапазону**.
|
||||||
|
|
||||||
|
Не допускаются:
|
||||||
|
|
||||||
|
- пропущенные индексы;
|
||||||
|
- обработка одного индекса несколькими потоками.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Поиск внутри потока
|
||||||
|
|
||||||
|
Каждая задача обрабатывает только свой диапазон.
|
||||||
|
|
||||||
|
Для каждого индекса:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Student student = students.get(index);
|
||||||
|
```
|
||||||
|
|
||||||
|
затем:
|
||||||
|
|
||||||
|
```java
|
||||||
|
target.equals(student)
|
||||||
|
```
|
||||||
|
|
||||||
|
Если результат `true`, индекс добавляется в локальный:
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Integer>
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Поток 1 → [0, 8, 15]
|
||||||
|
Поток 2 → [21, 24]
|
||||||
|
Поток 3 → [30, 42, 48]
|
||||||
|
Поток 4 → [63]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Объединение результатов
|
||||||
|
|
||||||
|
Основной поток после завершения всех задач должен получить локальные результаты и объединить их.
|
||||||
|
|
||||||
|
Рекомендуется передавать задачи в порядке возрастания диапазонов:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Результат 1 → диапазон 0
|
||||||
|
Результат 2 → диапазон 1
|
||||||
|
Результат 3 → диапазон 2
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Если каждая задача добавляет индексы в возрастающем порядке, а результаты объединяются в порядке диапазонов, итоговый массив уже будет отсортирован:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[0, 8, 15, 21, 24, 30, 42, 48, 63]
|
||||||
|
```
|
||||||
|
|
||||||
|
В этом случае дополнительная сортировка результата не требуется.
|
||||||
|
|
||||||
|
**`Arrays.sort()` и другие готовые методы сортировки использовать не следует**, поскольку порядок можно гарантировать самим алгоритмом объединения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Потокобезопасность
|
||||||
|
|
||||||
|
## Мьютексы
|
||||||
|
|
||||||
|
Использование мьютексов для результата **не требуется**.
|
||||||
|
|
||||||
|
Не рекомендуется использовать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
synchronized
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
Lock
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
ReentrantLock
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
AtomicInteger
|
||||||
|
```
|
||||||
|
|
||||||
|
для общего результата.
|
||||||
|
|
||||||
|
Причина: потоки не должны изменять одну общую структуру.
|
||||||
|
|
||||||
|
Каждый поток работает со своим:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MyList<Integer>
|
||||||
|
```
|
||||||
|
|
||||||
|
а объединение выполняется после завершения параллельной части одним потоком.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Рекомендуемые классы и импорты
|
||||||
|
|
||||||
|
Для реализации рекомендуется использовать стандартные средства Java:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
```
|
||||||
|
|
||||||
|
Рекомендуемая схема:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ExecutorService
|
||||||
|
↓
|
||||||
|
Callable<MyList<Integer>>
|
||||||
|
↓
|
||||||
|
Future<MyList<Integer>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый `Callable` обрабатывает свой диапазон и возвращает собственный `MyList<Integer>`.
|
||||||
|
|
||||||
|
После получения всех `Future` основной метод объединяет результаты.
|
||||||
|
|
||||||
|
После завершения работы `ExecutorService` должен быть корректно закрыт.
|
||||||
|
|
||||||
|
При `InterruptedException` необходимо восстановить статус прерывания текущего потока:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Использование `MyList`
|
||||||
|
|
||||||
|
Сторонние контейнеры **не допускаются**.
|
||||||
|
|
||||||
|
Запрещено использовать:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ArrayList
|
||||||
|
LinkedList
|
||||||
|
Vector
|
||||||
|
CopyOnWriteArrayList
|
||||||
|
ConcurrentLinkedQueue
|
||||||
|
и другие стандартные контейнеры
|
||||||
|
```
|
||||||
|
|
||||||
|
как хранилище промежуточных или итоговых результатов.
|
||||||
|
|
||||||
|
Для результатов каждого потока использовать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Integer>
|
||||||
|
```
|
||||||
|
|
||||||
|
Итог преобразовать в:
|
||||||
|
|
||||||
|
```java
|
||||||
|
int[]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Требуется ли изменять `MyList`
|
||||||
|
|
||||||
|
Текущего интерфейса **достаточно для реализации задачи**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface MyList<T> {
|
||||||
|
|
||||||
|
void add(T element);
|
||||||
|
|
||||||
|
T get(int index);
|
||||||
|
|
||||||
|
T set(int index, T element);
|
||||||
|
|
||||||
|
T remove(int index);
|
||||||
|
|
||||||
|
int size();
|
||||||
|
|
||||||
|
boolean isEmpty();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Дополнительные методы добавлять **не требуется**.
|
||||||
|
|
||||||
|
Для задачи достаточно:
|
||||||
|
|
||||||
|
```java
|
||||||
|
size()
|
||||||
|
get(index)
|
||||||
|
add(index)
|
||||||
|
```
|
||||||
|
|
||||||
|
Поскольку поиск выполняется только на чтение, а результаты каждого потока добавляются в его собственный `MyList<Integer>`, существующего интерфейса достаточно.
|
||||||
|
|
||||||
|
### Важно
|
||||||
|
|
||||||
|
Метод:
|
||||||
|
|
||||||
|
```java
|
||||||
|
void add(T element);
|
||||||
|
```
|
||||||
|
|
||||||
|
должен добавлять элемент в конец коллекции.
|
||||||
|
|
||||||
|
Это позволяет каждому потоку формировать свои индексы в порядке их обнаружения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Использование `Student`
|
||||||
|
|
||||||
|
Изменения в `Student` **не требуются**.
|
||||||
|
|
||||||
|
Использовать существующий метод:
|
||||||
|
|
||||||
|
```java
|
||||||
|
equals(Object obj)
|
||||||
|
```
|
||||||
|
|
||||||
|
Сравнение выполнять:
|
||||||
|
|
||||||
|
```java
|
||||||
|
target.equals(students.get(index))
|
||||||
|
```
|
||||||
|
|
||||||
|
Не использовать:
|
||||||
|
|
||||||
|
```text
|
||||||
|
==
|
||||||
|
```
|
||||||
|
|
||||||
|
Не сравнивать объекты вручную по отдельным полям.
|
||||||
|
|
||||||
|
Не использовать `toString()` для определения совпадения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# `StudentBuilder`
|
||||||
|
|
||||||
|
Изменения в `StudentBuilder` **не требуются**.
|
||||||
|
|
||||||
|
Он не участвует в алгоритме поиска.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Обработка входных данных
|
||||||
|
|
||||||
|
Если:
|
||||||
|
|
||||||
|
```java
|
||||||
|
students == null
|
||||||
|
```
|
||||||
|
|
||||||
|
выбросить:
|
||||||
|
|
||||||
|
```java
|
||||||
|
IllegalArgumentException
|
||||||
|
```
|
||||||
|
|
||||||
|
Если:
|
||||||
|
|
||||||
|
```java
|
||||||
|
target == null
|
||||||
|
```
|
||||||
|
|
||||||
|
выбросить:
|
||||||
|
|
||||||
|
```java
|
||||||
|
IllegalArgumentException
|
||||||
|
```
|
||||||
|
|
||||||
|
Для пустой коллекции вернуть:
|
||||||
|
|
||||||
|
```java
|
||||||
|
new int[0]
|
||||||
|
```
|
||||||
|
|
||||||
|
Без создания рабочих потоков.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Обобщённый алгоритм реализации
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. Проверить students и target.
|
||||||
|
2. Получить размер коллекции.
|
||||||
|
3. Если размер равен 0 → вернуть [].
|
||||||
|
4. Вычислить количество потоков.
|
||||||
|
5. Разделить индексы коллекции на непересекающиеся диапазоны.
|
||||||
|
6. Создать Callable для каждого диапазона.
|
||||||
|
7. Запустить задачи через ExecutorService.
|
||||||
|
8. Каждый поток:
|
||||||
|
- получает fromIndex и toIndex;
|
||||||
|
- проходит свой диапазон;
|
||||||
|
- сравнивает Student через equals();
|
||||||
|
- записывает найденные индексы в собственный MyList<Integer>.
|
||||||
|
9. Основной поток получает результаты через Future.
|
||||||
|
10. Объединяет MyList<Integer> в порядке диапазонов.
|
||||||
|
11. Преобразует результат в int[].
|
||||||
|
12. Возвращает массив.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Примеры тестов
|
||||||
|
|
||||||
|
## Одно совпадение
|
||||||
|
|
||||||
|
```text
|
||||||
|
[A, B, C, D]
|
||||||
|
```
|
||||||
|
|
||||||
|
Искомый:
|
||||||
|
|
||||||
|
```text
|
||||||
|
C
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидается:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[2]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Несколько совпадений
|
||||||
|
|
||||||
|
```text
|
||||||
|
[A, B, A, C, A]
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидается:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[0, 2, 4]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Нет совпадений
|
||||||
|
|
||||||
|
```text
|
||||||
|
[A, B, C]
|
||||||
|
```
|
||||||
|
|
||||||
|
Искомый:
|
||||||
|
|
||||||
|
```text
|
||||||
|
D
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидается:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Все элементы совпадают
|
||||||
|
|
||||||
|
```text
|
||||||
|
[A, A, A, A]
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидается:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[0, 1, 2, 3]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Пустая коллекция
|
||||||
|
|
||||||
|
Ожидается:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Один элемент
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[A] + A → [0]
|
||||||
|
[A] + B → []
|
||||||
|
```
|
||||||
|
|
||||||
|
## Проверка границ диапазонов потоков
|
||||||
|
|
||||||
|
Проверить коллекции размеров:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
10
|
||||||
|
100
|
||||||
|
1000
|
||||||
|
```
|
||||||
|
|
||||||
|
Особое внимание уделить совпадениям:
|
||||||
|
|
||||||
|
```text
|
||||||
|
на последнем элементе одного диапазона;
|
||||||
|
на первом элементе следующего диапазона.
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый индекс должен быть найден ровно один раз.
|
||||||
|
|
||||||
|
## Проверка порядка результата
|
||||||
|
|
||||||
|
Создать совпадения в разных диапазонах.
|
||||||
|
|
||||||
|
Проверить, что результат имеет вид:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[0, 3, 7, 12, 25, 49, 100]
|
||||||
|
```
|
||||||
|
|
||||||
|
и находится в порядке возрастания без использования дополнительной сортировки.
|
||||||
|
|
||||||
|
## Проверка `Student.equals()`
|
||||||
|
|
||||||
|
Создать два разных экземпляра `Student` с одинаковыми значениями полей.
|
||||||
|
|
||||||
|
Один поместить в коллекцию, второй передать как `target`.
|
||||||
|
|
||||||
|
Проверить, что индекс считается совпадением согласно `equals()`.
|
||||||
|
|
||||||
|
## Проверка неизменности коллекции
|
||||||
|
|
||||||
|
После выполнения метода проверить:
|
||||||
|
|
||||||
|
- размер `MyList`;
|
||||||
|
- порядок элементов;
|
||||||
|
- значения всех элементов.
|
||||||
|
|
||||||
|
## Проверка отсутствия вывода
|
||||||
|
|
||||||
|
Перехватить стандартный вывод и убедиться, что выполнение `findOccurrences()` не выводит сообщений в консоль.
|
||||||
|
|
||||||
|
## Большая коллекция
|
||||||
|
|
||||||
|
Создать коллекцию, например из `100000` студентов, с заранее известными индексами совпадений.
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- все ожидаемые индексы найдены;
|
||||||
|
- лишних индексов нет;
|
||||||
|
- индексы идут по возрастанию;
|
||||||
|
- итоговый массив имеет правильный размер.
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
Название ветки:
|
||||||
|
|
||||||
|
```text
|
||||||
|
feature/student-occurrence-index-finder
|
||||||
|
```
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Необходимо реализовать:
|
||||||
|
|
||||||
|
```text
|
||||||
|
StudentOccurrenceIndexFinder.java
|
||||||
|
```
|
||||||
|
|
||||||
|
Существующие интерфейсы и классы:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MyList.java
|
||||||
|
Student.java
|
||||||
|
StudentBuilder.java
|
||||||
|
```
|
||||||
|
|
||||||
|
изменять не требуется.
|
||||||
Reference in New Issue
Block a user