Merge branch 'develop' into feature/datasource
This commit is contained in:
+21
@@ -41,11 +41,19 @@
|
|||||||
<artifactId>junit-jupiter-params</artifactId>
|
<artifactId>junit-jupiter-params</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-csv</artifactId>
|
<artifactId>commons-csv</artifactId>
|
||||||
<version>1.14.1</version>
|
<version>1.14.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.googlecode.lanterna</groupId>
|
||||||
|
<artifactId>lanterna</artifactId>
|
||||||
|
<version>3.1.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -96,5 +104,18 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>3.6.3</version>
|
||||||
|
|
||||||
|
<configuration>
|
||||||
|
<mainClass>ru.project.App</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
package ru.project;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hello world!
|
|
||||||
*/
|
|
||||||
public class App {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
System.out.println("Hello World!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package ru.project.collection;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class MyLinkedList<T> implements MyList<T> {
|
||||||
|
private Node<T> head;
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
public void add(T value) {
|
||||||
|
if (head == null) {
|
||||||
|
this.head = new Node<>(value);
|
||||||
|
} else {
|
||||||
|
Node<T> temp = head;
|
||||||
|
while (temp.getNext() != null) {
|
||||||
|
temp = temp.getNext();
|
||||||
|
}
|
||||||
|
temp.setNext(new Node<>(value));
|
||||||
|
}
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T get(int index) {
|
||||||
|
int currentIndex = 0;
|
||||||
|
Node<T> temp = head;
|
||||||
|
while (temp != null) {
|
||||||
|
if (currentIndex == index) {
|
||||||
|
return temp.getValue();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
temp = temp.getNext();
|
||||||
|
currentIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(int index) {
|
||||||
|
if (index < 0 || index >= size) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
if (index == 0) {
|
||||||
|
head = head.getNext();
|
||||||
|
size--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int currentIndex = 0;
|
||||||
|
Node<T> temp = head;
|
||||||
|
while (temp != null) {
|
||||||
|
if (currentIndex == index - 1) {
|
||||||
|
temp.setNext(temp.getNext().getNext());
|
||||||
|
size--;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
temp = temp.getNext();
|
||||||
|
currentIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T set(int index, T value) {
|
||||||
|
if (index < 0 || index >= size) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
Node<T> temp = head;
|
||||||
|
for (int i = 0; i < index; i++) {
|
||||||
|
temp = temp.getNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
T oldValue = temp.getValue();
|
||||||
|
temp.setValue(value);
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return size == 0;
|
||||||
|
}
|
||||||
|
public void addAll(MyList<? extends T> collection) {
|
||||||
|
if (collection == null || collection.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < collection.size(); i++) {
|
||||||
|
add(collection.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
Object[] res = new Object[size];
|
||||||
|
int index = 0;
|
||||||
|
Node<T> temp = head;
|
||||||
|
while (temp != null) {
|
||||||
|
res[index] = temp.getValue();
|
||||||
|
index++;
|
||||||
|
temp = temp.getNext();
|
||||||
|
}
|
||||||
|
return Arrays.toString(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Node<T> {
|
||||||
|
private T value;
|
||||||
|
private Node<T> next;
|
||||||
|
|
||||||
|
public Node(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNext(Node<T> next) {
|
||||||
|
this.next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node<T> getNext() {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package ru.project.collection;
|
||||||
|
|
||||||
|
public interface MyList<T> {
|
||||||
|
|
||||||
|
void add(T element);
|
||||||
|
|
||||||
|
T get(int index);
|
||||||
|
|
||||||
|
T set(int index, T element);
|
||||||
|
|
||||||
|
void remove(int index);
|
||||||
|
|
||||||
|
int size();
|
||||||
|
|
||||||
|
boolean isEmpty();
|
||||||
|
void addAll(MyList<? extends T> collection);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package ru.project.collectors;
|
||||||
|
import java.util.stream.Collector;
|
||||||
|
|
||||||
|
public class MyCollectors
|
||||||
|
{
|
||||||
|
private MyCollectors() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Collector<T, MyList<T>, MyList<T>> toMyList()
|
||||||
|
{
|
||||||
|
return new MyListCollector<>();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package ru.project.collectors;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collector;
|
||||||
|
public class MyListCollector <T> implements Collector<T, MyList<T>, MyList<T>>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Supplier<MyList<T>> supplier()
|
||||||
|
{
|
||||||
|
return () -> new MyLinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiConsumer<MyList<T>, T> accumulator() {
|
||||||
|
|
||||||
|
return (list, element) -> list.add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BinaryOperator<MyList<T>> combiner() {
|
||||||
|
return (list1, list2) -> {
|
||||||
|
list1.addAll(list2);
|
||||||
|
return list1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Function<MyList<T>, MyList<T>> finisher()
|
||||||
|
{
|
||||||
|
return list -> list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Collector.Characteristics> characteristics() {
|
||||||
|
return java.util.Set.of(
|
||||||
|
Collector.Characteristics.IDENTITY_FINISH
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package ru.project.finder;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
public class StudentOccurrenceIndexFinder
|
||||||
|
{
|
||||||
|
public int[] findOccurrences(MyList<Student> students, Student target) {
|
||||||
|
if (students == null) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
if (target == null) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
if (students.isEmpty()) {
|
||||||
|
return new int[0];
|
||||||
|
}
|
||||||
|
int size = students.size();
|
||||||
|
int threadCount = calculateThreadCount(size);
|
||||||
|
int chunkSize = countChunkSize(size, threadCount);
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
|
||||||
|
MyList<Future<MyList<Integer>>> futures = new MyLinkedList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < threadCount; i++) {
|
||||||
|
int fromIndex = i * chunkSize;
|
||||||
|
int toIndex = Math.min(fromIndex + chunkSize, size);
|
||||||
|
|
||||||
|
if (fromIndex >= toIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int start = fromIndex;
|
||||||
|
final int end = toIndex;
|
||||||
|
|
||||||
|
Future<MyList<Integer>> future = executor.submit(() -> {
|
||||||
|
return findInRange(students, target, start, end);
|
||||||
|
});
|
||||||
|
futures.add(future);
|
||||||
|
}
|
||||||
|
MyList<Integer> allIndices = new MyLinkedList<>();
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < futures.size(); i++) {
|
||||||
|
Future<MyList<Integer>> future = futures.get(i);
|
||||||
|
MyList<Integer> partialResult = future.get();
|
||||||
|
|
||||||
|
if (partialResult != null) {
|
||||||
|
for (int j = 0; j < partialResult.size(); j++) {
|
||||||
|
allIndices.add(partialResult.get(j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Ошибка при поиске", e);
|
||||||
|
} finally {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
sortList(allIndices);
|
||||||
|
|
||||||
|
int[] resultArray = new int[allIndices.size()];
|
||||||
|
for (int i = 0; i < allIndices.size(); i++) {
|
||||||
|
resultArray[i] = allIndices.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sortList(MyList<Integer> list) {
|
||||||
|
if (list.size() <= 1) return;
|
||||||
|
|
||||||
|
MyList<Integer> sorted = new MyLinkedList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
int value = list.get(i);
|
||||||
|
int insertIndex = 0;
|
||||||
|
while (insertIndex < sorted.size()) {
|
||||||
|
if (sorted.get(insertIndex) > value) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
insertIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
MyList<Integer> temp = new MyLinkedList<>();
|
||||||
|
for (int k = 0; k < insertIndex; k++) {
|
||||||
|
temp.add(sorted.get(k));
|
||||||
|
}
|
||||||
|
temp.add(value);
|
||||||
|
for (int k = insertIndex; k < sorted.size(); k++) {
|
||||||
|
temp.add(sorted.get(k));
|
||||||
|
}
|
||||||
|
sorted = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!list.isEmpty()) {
|
||||||
|
list.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < sorted.size(); i++) {
|
||||||
|
list.add(sorted.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateThreadCount(int size) {
|
||||||
|
int processors = Runtime.getRuntime().availableProcessors();
|
||||||
|
return Math.min(processors, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countChunkSize(int size, int threadCount) {
|
||||||
|
if (threadCount == 0) return 0;
|
||||||
|
return (size + threadCount - 1) / threadCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MyList<Integer> findInRange(MyList<Student> students, Student target, int fromIndex, int toIndex) {
|
||||||
|
MyList<Integer> foundIndices = new MyLinkedList<>();
|
||||||
|
|
||||||
|
for (int i = fromIndex; i < toIndex; i++) {
|
||||||
|
Student current = students.get(i);
|
||||||
|
if (current != null && current.equals(target)) {
|
||||||
|
foundIndices.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundIndices;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package ru.project.model;
|
||||||
|
|
||||||
|
import ru.project.collection.MyLinkedList;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
public class AppState {
|
||||||
|
|
||||||
|
private MyList<Student> mainCollection;
|
||||||
|
private MyList<Student> customDataCollection;
|
||||||
|
private String currentFilePath;
|
||||||
|
|
||||||
|
public AppState() {
|
||||||
|
this.mainCollection = new MyLinkedList<>();
|
||||||
|
this.customDataCollection = new MyLinkedList<>();
|
||||||
|
this.currentFilePath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MyList<Student> getMainCollection() {
|
||||||
|
return mainCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMainCollection(MyList<Student> currentCollection) {
|
||||||
|
this.mainCollection = currentCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MyList<Student> getCustomDataCollection() {
|
||||||
|
return customDataCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomDataCollection(MyList<Student> customDataCollection) {
|
||||||
|
|
||||||
|
this.customDataCollection = customDataCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentFilePath() {
|
||||||
|
return currentFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentFilePath(String currentFilePath) {
|
||||||
|
this.currentFilePath = currentFilePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package ru.project.search;
|
||||||
|
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
public class AverageGradeRangeCriteria implements StudentSearchCriteria {
|
||||||
|
private final double min;
|
||||||
|
private final double max;
|
||||||
|
|
||||||
|
public AverageGradeRangeCriteria(double min, double max) {
|
||||||
|
if (min > max) {
|
||||||
|
throw new IllegalArgumentException("Min grade must be <= max grade");
|
||||||
|
}
|
||||||
|
this.min = min;
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(Student student) {
|
||||||
|
double grade = student.getAverageGrade();
|
||||||
|
return grade >= min && grade <= max;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package ru.project.search;
|
||||||
|
|
||||||
|
import ru.project.student.Student;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CompositeStudentSearchCriteria implements StudentSearchCriteria {
|
||||||
|
private final SearchOperation operation;
|
||||||
|
private final List<StudentSearchCriteria> criteriaList = new ArrayList<>();
|
||||||
|
|
||||||
|
public CompositeStudentSearchCriteria(SearchOperation operation) {
|
||||||
|
if (operation == null) {
|
||||||
|
throw new IllegalArgumentException("Operation must not be null");
|
||||||
|
}
|
||||||
|
this.operation = operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(StudentSearchCriteria criteria) {
|
||||||
|
if (criteria == null) {
|
||||||
|
throw new IllegalArgumentException("Criteria must not be null");
|
||||||
|
}
|
||||||
|
criteriaList.add(criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(StudentSearchCriteria criteria) {
|
||||||
|
criteriaList.remove(criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(Student student) {
|
||||||
|
if (criteriaList.isEmpty()) {
|
||||||
|
return operation == SearchOperation.INTERSECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation == SearchOperation.UNION) {
|
||||||
|
for (StudentSearchCriteria c : criteriaList) {
|
||||||
|
if (c.matches(student)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
for (StudentSearchCriteria c : criteriaList) {
|
||||||
|
if (!c.matches(student)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ru.project.search;
|
||||||
|
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
public class GroupNumberRangeCriteria implements StudentSearchCriteria {
|
||||||
|
private final String min;
|
||||||
|
private final String max;
|
||||||
|
|
||||||
|
public GroupNumberRangeCriteria(String min, String max) {
|
||||||
|
if (min == null || max == null) {
|
||||||
|
throw new IllegalArgumentException("Min and max must not be null");
|
||||||
|
}
|
||||||
|
if (min.compareTo(max) > 0) {
|
||||||
|
throw new IllegalArgumentException("Min group must be <= max group");
|
||||||
|
}
|
||||||
|
this.min = min;
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(Student student) {
|
||||||
|
String group = student.getGroupNumber();
|
||||||
|
return group.compareTo(min) >= 0 && group.compareTo(max) <= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package ru.project.search;
|
||||||
|
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
public class RecordBookNumberRangeCriteria implements StudentSearchCriteria {
|
||||||
|
private final int min;
|
||||||
|
private final int max;
|
||||||
|
|
||||||
|
public RecordBookNumberRangeCriteria(int min, int max) {
|
||||||
|
if (min > max) {
|
||||||
|
throw new IllegalArgumentException("Min record number must be <= max");
|
||||||
|
}
|
||||||
|
this.min = min;
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(Student student) {
|
||||||
|
int number = student.getRecordBookNumber();
|
||||||
|
return number >= min && number <= max;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.project.search;
|
||||||
|
|
||||||
|
public enum SearchOperation {
|
||||||
|
UNION,
|
||||||
|
INTERSECTION
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.project.search;
|
||||||
|
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
public interface StudentSearchCriteria {
|
||||||
|
boolean matches(Student student);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package ru.project.search;
|
||||||
|
|
||||||
|
import ru.project.list.MyList;
|
||||||
|
import ru.project.list.MyLinkedList;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
public class StudentSearchService {
|
||||||
|
private final StudentSearchCriteria criteria;
|
||||||
|
|
||||||
|
public StudentSearchService(StudentSearchCriteria criteria) {
|
||||||
|
if (criteria == null) {
|
||||||
|
throw new IllegalArgumentException("Criteria must not be null");
|
||||||
|
}
|
||||||
|
this.criteria = criteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MyList<Student> find(MyList<Student> students) {
|
||||||
|
if (students == null) {
|
||||||
|
return new MyLinkedList<>();
|
||||||
|
}
|
||||||
|
MyList<Student> result = new MyLinkedList<>();
|
||||||
|
for (int i = 0; i < students.size(); i++) {
|
||||||
|
Student s = students.get(i);
|
||||||
|
if (criteria.matches(s)) {
|
||||||
|
result.add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package ru.project.sorting;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
|
||||||
|
public class BubbleSortStrategy<T> implements SortStrategy<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sort(MyList<T> list, Comparator<T> comparator) {
|
||||||
|
if (list == null || list.size() <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int n = list.size();
|
||||||
|
for (int i = 0; i < n - 1; i++) {
|
||||||
|
for (int j = 0; j < n - i - 1; j++) {
|
||||||
|
if (comparator.compare(list.get(j), list.get(j + 1)) > 0) {
|
||||||
|
T temp = list.get(j);
|
||||||
|
list.set(j, list.get(j + 1));
|
||||||
|
list.set(j + 1, temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package ru.project.sorting;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.ToIntFunction;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
|
||||||
|
public class EvenOnlyMergeSortStrategy<T> implements SortStrategy<T> {
|
||||||
|
|
||||||
|
private final ToIntFunction<T> valueExtractor;
|
||||||
|
|
||||||
|
public EvenOnlyMergeSortStrategy(ToIntFunction<T> valueExtractor) {
|
||||||
|
this.valueExtractor = valueExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sort(MyList<T> list, Comparator<T> comparator) {
|
||||||
|
if (list == null || list.size() <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> evenIndices = new ArrayList<>();
|
||||||
|
List<T> evenElements = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
T element = list.get(i);
|
||||||
|
int value = valueExtractor.applyAsInt(element);
|
||||||
|
if (value % 2 == 0) {
|
||||||
|
evenIndices.add(i);
|
||||||
|
evenElements.add(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evenElements.size() <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<T> sortedEven = mergeSort(evenElements, comparator);
|
||||||
|
|
||||||
|
for (int j = 0; j < evenIndices.size(); j++) {
|
||||||
|
list.set(evenIndices.get(j), sortedEven.get(j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<T> mergeSort(List<T> list, Comparator<T> comparator) {
|
||||||
|
if (list.size() <= 1) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mid = list.size() / 2;
|
||||||
|
List<T> left = mergeSort(list.subList(0, mid), comparator);
|
||||||
|
List<T> right = mergeSort(list.subList(mid, list.size()), comparator);
|
||||||
|
|
||||||
|
return merge(left, right, comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<T> merge(List<T> left, List<T> right, Comparator<T> comparator) {
|
||||||
|
List<T> result = new ArrayList<>();
|
||||||
|
int i = 0, j = 0;
|
||||||
|
|
||||||
|
while (i < left.size() && j < right.size()) {
|
||||||
|
if (comparator.compare(left.get(i), right.get(j)) <= 0) {
|
||||||
|
result.add(left.get(i));
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
result.add(right.get(j));
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < left.size()) {
|
||||||
|
result.add(left.get(i));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (j < right.size()) {
|
||||||
|
result.add(right.get(j));
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package ru.project.sorting;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.function.ToIntFunction;
|
||||||
|
import ru.project.collection.MyLinkedList;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
|
||||||
|
public class EvenOnlySortStrategy<T> implements SortStrategy<T> {
|
||||||
|
|
||||||
|
private SortStrategy<T> sortStrategy;
|
||||||
|
private ToIntFunction<T> valueExtractor;
|
||||||
|
|
||||||
|
public EvenOnlySortStrategy() {}
|
||||||
|
|
||||||
|
public EvenOnlySortStrategy(SortStrategy<T> sortStrategy) {
|
||||||
|
setSortStrategy(sortStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EvenOnlySortStrategy(SortStrategy<T> sortStrategy, ToIntFunction<T> valueExtractor) {
|
||||||
|
setSortStrategy(sortStrategy);
|
||||||
|
setValueExtractor(valueExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSortStrategy(SortStrategy<T> sortStrategy) {
|
||||||
|
if (sortStrategy == null) {
|
||||||
|
throw new IllegalArgumentException("Sort strategy must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sortStrategy = sortStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValueExtractor(ToIntFunction<T> valueExtractor) {
|
||||||
|
if (valueExtractor == null) {
|
||||||
|
throw new IllegalArgumentException("Value extractor must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.valueExtractor = valueExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sort(MyList<T> list, Comparator<T> comparator) {
|
||||||
|
if (sortStrategy == null) {
|
||||||
|
throw new IllegalStateException("Sort strategy is not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueExtractor == null) {
|
||||||
|
throw new IllegalStateException("Value extractor is not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list == null || list.size() <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int evenCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
T element = list.get(i);
|
||||||
|
|
||||||
|
if (valueExtractor.applyAsInt(element) % 2 == 0) {
|
||||||
|
evenCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evenCount <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MyList<T> evenElements = new MyLinkedList<>();
|
||||||
|
int[] evenIndexes = new int[evenCount];
|
||||||
|
|
||||||
|
int evenIndex = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
T element = list.get(i);
|
||||||
|
|
||||||
|
if (valueExtractor.applyAsInt(element) % 2 == 0) {
|
||||||
|
evenElements.add(element);
|
||||||
|
evenIndexes[evenIndex] = i;
|
||||||
|
evenIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortStrategy.sort(evenElements, comparator);
|
||||||
|
|
||||||
|
for (int i = 0; i < evenIndexes.length; i++) {
|
||||||
|
list.set(evenIndexes[i], evenElements.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package ru.project.sorting;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
|
||||||
|
public class MergeSortStrategy<T> implements SortStrategy<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sort(MyList<T> list, Comparator<T> comparator) {
|
||||||
|
if (list == null || list.size() <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mergeSort(list, 0, list.size() - 1, comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeSort(MyList<T> list, int left, int right, Comparator<T> comparator) {
|
||||||
|
if (left >= right) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int mid = (left + right) / 2;
|
||||||
|
mergeSort(list, left, mid, comparator);
|
||||||
|
mergeSort(list, mid + 1, right, comparator);
|
||||||
|
merge(list, left, mid, right, comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void merge(MyList<T> list, int left, int mid, int right, Comparator<T> comparator) {
|
||||||
|
int leftSize = mid - left + 1;
|
||||||
|
int rightSize = right - mid;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
T[] leftArray = (T[]) new Object[leftSize];
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
T[] rightArray = (T[]) new Object[rightSize];
|
||||||
|
|
||||||
|
for (int i = 0; i < leftSize; i++) {
|
||||||
|
leftArray[i] = list.get(left + i);
|
||||||
|
}
|
||||||
|
for (int j = 0; j < rightSize; j++) {
|
||||||
|
rightArray[j] = list.get(mid + 1 + j);
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0, j = 0, k = left;
|
||||||
|
|
||||||
|
while (i < leftSize && j < rightSize) {
|
||||||
|
if (comparator.compare(leftArray[i], rightArray[j]) <= 0) {
|
||||||
|
list.set(k, leftArray[i]);
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
list.set(k, rightArray[j]);
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < leftSize) {
|
||||||
|
list.set(k, leftArray[i]);
|
||||||
|
i++;
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
while (j < rightSize) {
|
||||||
|
list.set(k, rightArray[j]);
|
||||||
|
j++;
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package ru.project.sorting;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
|
||||||
|
public interface SortStrategy<T> {
|
||||||
|
void sort(MyList<T> list, Comparator<T> comparator);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.project.sorting;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
public class StudentAverageGradeComparator implements Comparator<Student> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Student s1, Student s2) {
|
||||||
|
return Double.compare(s1.getAverageGrade(), s2.getAverageGrade());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.project.sorting;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
public class StudentGroupComparator implements Comparator<Student> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Student s1, Student s2) {
|
||||||
|
return s1.getGroupNumber().compareTo(s2.getGroupNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.project.sorting;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
public class StudentRecordBookComparator implements Comparator<Student> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Student s1, Student s2) {
|
||||||
|
return Integer.compare(s1.getRecordBookNumber(), s2.getRecordBookNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package ru.project.student;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class Student {
|
||||||
|
|
||||||
|
public static final double MIN_AVERAGE_GRADE = 0.0;
|
||||||
|
public static final double MAX_AVERAGE_GRADE = 5.0;
|
||||||
|
public static final int MIN_RECORD_BOOK_NUMBER = 1;
|
||||||
|
public static final String GROUP_NUMBER_PATTERN = "^[A-Z][0-9]{2}$";
|
||||||
|
|
||||||
|
private final String groupNumber;
|
||||||
|
private final double averageGrade;
|
||||||
|
private final int recordBookNumber;
|
||||||
|
|
||||||
|
public Student(String groupNumber, double averageGrade, int recordBookNumber) {
|
||||||
|
if (groupNumber == null || !groupNumber.matches(GROUP_NUMBER_PATTERN)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Group number must match pattern: " + GROUP_NUMBER_PATTERN);
|
||||||
|
}
|
||||||
|
if (averageGrade < MIN_AVERAGE_GRADE || averageGrade > MAX_AVERAGE_GRADE) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Average grade must be between " + MIN_AVERAGE_GRADE + " and " + MAX_AVERAGE_GRADE);
|
||||||
|
}
|
||||||
|
if (recordBookNumber <= 0) {
|
||||||
|
throw new IllegalArgumentException("Record book number must be positive");
|
||||||
|
}
|
||||||
|
if (recordBookNumber < MIN_RECORD_BOOK_NUMBER) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Record book number must be at least " + MIN_RECORD_BOOK_NUMBER);
|
||||||
|
}
|
||||||
|
this.groupNumber = groupNumber;
|
||||||
|
this.averageGrade = averageGrade;
|
||||||
|
this.recordBookNumber = recordBookNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupNumber() {
|
||||||
|
return groupNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAverageGrade() {
|
||||||
|
return averageGrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRecordBookNumber() {
|
||||||
|
return recordBookNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Student withGroupNumber(String groupNumber) {
|
||||||
|
return new Student(groupNumber, this.averageGrade, this.recordBookNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Student withAverageGrade(double averageGrade) {
|
||||||
|
return new Student(this.groupNumber, averageGrade, this.recordBookNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Student withRecordBookNumber(int recordBookNumber) {
|
||||||
|
return new Student(this.groupNumber, this.averageGrade, recordBookNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getMinAverageGrade() {
|
||||||
|
return MIN_AVERAGE_GRADE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getMaxAverageGrade() {
|
||||||
|
return MAX_AVERAGE_GRADE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getMinRecordBookNumber() {
|
||||||
|
return MIN_RECORD_BOOK_NUMBER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getGroupNumberPattern() {
|
||||||
|
return GROUP_NUMBER_PATTERN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "Student{"
|
||||||
|
+ "groupNumber='"
|
||||||
|
+ groupNumber
|
||||||
|
+ '\''
|
||||||
|
+ ", averageGrade="
|
||||||
|
+ averageGrade
|
||||||
|
+ ", recordBookNumber="
|
||||||
|
+ recordBookNumber
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(groupNumber, averageGrade, recordBookNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.hashCode() != obj.hashCode()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Student student = (Student) obj;
|
||||||
|
return Double.compare(averageGrade, student.averageGrade) == 0
|
||||||
|
&& recordBookNumber == student.recordBookNumber
|
||||||
|
&& Objects.equals(groupNumber, student.groupNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package ru.project.student;
|
||||||
|
|
||||||
|
public class StudentBuilder {
|
||||||
|
private String groupNumber;
|
||||||
|
private double averageGrade;
|
||||||
|
private int recordBookNumber;
|
||||||
|
|
||||||
|
public StudentBuilder setGroupNumber(String groupNumber) {
|
||||||
|
this.groupNumber = groupNumber;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StudentBuilder setAverageGrade(double averageGrade) {
|
||||||
|
this.averageGrade = averageGrade;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StudentBuilder setRecordBookNumber(int recordBookNumber) {
|
||||||
|
this.recordBookNumber = recordBookNumber;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Student build() {
|
||||||
|
return new Student(groupNumber, averageGrade, recordBookNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package ru.project.ui.base;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.BasicWindow;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
|
||||||
|
public abstract class BaseModalWindow extends BasicWindow {
|
||||||
|
|
||||||
|
protected final WindowBasedTextGUI gui;
|
||||||
|
|
||||||
|
protected BaseModalWindow(String title, WindowBasedTextGUI gui) {
|
||||||
|
super(title);
|
||||||
|
|
||||||
|
this.gui = gui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showModal() {
|
||||||
|
gui.addWindowAndWait(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package ru.project.ui.common;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.TerminalSize;
|
||||||
|
import com.googlecode.lanterna.gui2.Button;
|
||||||
|
import com.googlecode.lanterna.gui2.Direction;
|
||||||
|
import com.googlecode.lanterna.gui2.EmptySpace;
|
||||||
|
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||||
|
import com.googlecode.lanterna.gui2.Panel;
|
||||||
|
import com.googlecode.lanterna.gui2.TextBox;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
import com.googlecode.lanterna.input.KeyStroke;
|
||||||
|
import com.googlecode.lanterna.input.KeyType;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
import ru.project.ui.base.BaseModalWindow;
|
||||||
|
|
||||||
|
public class CollectionDisplayWindow extends BaseModalWindow {
|
||||||
|
|
||||||
|
private final MyList<Student> collection;
|
||||||
|
|
||||||
|
private TextBox collectionTextBox;
|
||||||
|
private Button exitButton;
|
||||||
|
|
||||||
|
public CollectionDisplayWindow(WindowBasedTextGUI gui, MyList<Student> collection) {
|
||||||
|
|
||||||
|
super("Просмотр коллекции", gui);
|
||||||
|
|
||||||
|
this.collection = collection;
|
||||||
|
|
||||||
|
Panel panel = createPanel();
|
||||||
|
|
||||||
|
setComponent(panel);
|
||||||
|
|
||||||
|
exitButton.takeFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleInput(KeyStroke key) {
|
||||||
|
if (key.getKeyType() == KeyType.Escape && collectionTextBox.isFocused()) {
|
||||||
|
|
||||||
|
exitButton.takeFocus();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.handleInput(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Panel createPanel() {
|
||||||
|
Panel panel = new Panel();
|
||||||
|
|
||||||
|
panel.setLayoutManager(new LinearLayout(Direction.VERTICAL));
|
||||||
|
|
||||||
|
addCollectionDisplay(panel);
|
||||||
|
addExitButton(panel);
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCollectionDisplay(Panel panel) {
|
||||||
|
collectionTextBox = createCollectionScrollBox();
|
||||||
|
|
||||||
|
panel.addComponent(collectionTextBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextBox createCollectionScrollBox() {
|
||||||
|
TextBox textBox = new TextBox(new TerminalSize(80, 20), TextBox.Style.MULTI_LINE);
|
||||||
|
|
||||||
|
textBox.setReadOnly(true);
|
||||||
|
|
||||||
|
if (collection == null || collection.isEmpty()) {
|
||||||
|
textBox.setText("Коллекция пуста.");
|
||||||
|
} else {
|
||||||
|
textBox.setText(buildCollectionText());
|
||||||
|
}
|
||||||
|
|
||||||
|
return textBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addExitButton(Panel panel) {
|
||||||
|
panel.addComponent(new EmptySpace(new TerminalSize(0, 1)));
|
||||||
|
|
||||||
|
exitButton = new Button("Выйти", this::close);
|
||||||
|
|
||||||
|
panel.addComponent(exitButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildCollectionText() {
|
||||||
|
StringBuilder text = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < collection.size(); i++) {
|
||||||
|
Student student = collection.get(i);
|
||||||
|
|
||||||
|
text.append("[")
|
||||||
|
.append(i)
|
||||||
|
.append("] ")
|
||||||
|
.append(student == null ? "null" : student)
|
||||||
|
.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package ru.project.ui.common;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.Button;
|
||||||
|
import com.googlecode.lanterna.gui2.Label;
|
||||||
|
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||||
|
import com.googlecode.lanterna.gui2.Panel;
|
||||||
|
import com.googlecode.lanterna.gui2.TextBox;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import ru.project.ui.base.BaseModalWindow;
|
||||||
|
|
||||||
|
public class InputValueWindow extends BaseModalWindow {
|
||||||
|
|
||||||
|
private final TextBox textBox;
|
||||||
|
private final Predicate<String> onSave;
|
||||||
|
|
||||||
|
public InputValueWindow(
|
||||||
|
WindowBasedTextGUI gui,
|
||||||
|
String title,
|
||||||
|
String prompt,
|
||||||
|
String initialValue,
|
||||||
|
Predicate<String> onSave) {
|
||||||
|
|
||||||
|
super(title, gui);
|
||||||
|
|
||||||
|
this.onSave = onSave;
|
||||||
|
|
||||||
|
Panel panel = new Panel();
|
||||||
|
panel.setLayoutManager(new LinearLayout());
|
||||||
|
|
||||||
|
panel.addComponent(new Label(prompt));
|
||||||
|
|
||||||
|
textBox = new TextBox(initialValue == null ? "" : initialValue);
|
||||||
|
|
||||||
|
panel.addComponent(textBox);
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Сохранить", this::save));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Отмена", this::close));
|
||||||
|
|
||||||
|
setComponent(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void save() {
|
||||||
|
String value = textBox.getText().trim();
|
||||||
|
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
MessageWindow.showModal(gui, "Значение не может быть пустым.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onSave.test(value)) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package ru.project.ui.common;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.Button;
|
||||||
|
import com.googlecode.lanterna.gui2.Label;
|
||||||
|
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||||
|
import com.googlecode.lanterna.gui2.Panel;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
import ru.project.ui.base.BaseModalWindow;
|
||||||
|
|
||||||
|
public class MessageWindow extends BaseModalWindow {
|
||||||
|
|
||||||
|
public MessageWindow(WindowBasedTextGUI gui, String message) {
|
||||||
|
super("Информация", gui);
|
||||||
|
|
||||||
|
Panel panel = new Panel();
|
||||||
|
panel.setLayoutManager(new LinearLayout());
|
||||||
|
|
||||||
|
panel.addComponent(new Label(message));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("OK", this::close));
|
||||||
|
|
||||||
|
setComponent(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showModal(WindowBasedTextGUI gui, String message) {
|
||||||
|
new MessageWindow(gui, message).showModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package ru.project.ui.creation;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.Button;
|
||||||
|
import com.googlecode.lanterna.gui2.Label;
|
||||||
|
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||||
|
import com.googlecode.lanterna.gui2.Panel;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
import ru.project.collection.MyLinkedList;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
import ru.project.model.AppState;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
import ru.project.ui.base.BaseModalWindow;
|
||||||
|
import ru.project.ui.common.InputValueWindow;
|
||||||
|
import ru.project.ui.common.MessageWindow;
|
||||||
|
|
||||||
|
public class CreateCollectionMenu extends BaseModalWindow {
|
||||||
|
|
||||||
|
private int collectionSize;
|
||||||
|
private FillMethod selectedFillMethod;
|
||||||
|
|
||||||
|
private final Label sizeLabel;
|
||||||
|
private final Label fillMethodLabel;
|
||||||
|
|
||||||
|
private final CustomDataSettingsWindow customDataSettingsWindow;
|
||||||
|
|
||||||
|
public CreateCollectionMenu(WindowBasedTextGUI gui, AppState state) {
|
||||||
|
super("Создание новой коллекции", gui);
|
||||||
|
|
||||||
|
collectionSize = 0;
|
||||||
|
selectedFillMethod = FillMethod.CUSTOM;
|
||||||
|
|
||||||
|
customDataSettingsWindow = new CustomDataSettingsWindow(gui, state, () -> collectionSize);
|
||||||
|
|
||||||
|
Panel panel = new Panel();
|
||||||
|
panel.setLayoutManager(new LinearLayout());
|
||||||
|
|
||||||
|
sizeLabel = new Label("Размер: не задан");
|
||||||
|
|
||||||
|
fillMethodLabel = new Label("Способ заполнения: " + getFillMethodName(selectedFillMethod));
|
||||||
|
|
||||||
|
panel.addComponent(sizeLabel);
|
||||||
|
panel.addComponent(fillMethodLabel);
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Задать размер", this::showCollectionSizeInputValueWindow));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Выбрать способ заполнения", this::selectFillMethod));
|
||||||
|
|
||||||
|
panel.addComponent(
|
||||||
|
new Button("Настроить текущий способ заполнения", this::setupCurrentFillMethod));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Создать новую коллекцию", () -> updateMainCollection(state)));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Выйти", this::close));
|
||||||
|
|
||||||
|
setComponent(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCollectionSizeInputValueWindow() {
|
||||||
|
InputValueWindow window =
|
||||||
|
new InputValueWindow(
|
||||||
|
gui,
|
||||||
|
"Размер коллекции",
|
||||||
|
"Введите размер коллекции:",
|
||||||
|
collectionSize > 0 ? String.valueOf(collectionSize) : null,
|
||||||
|
this::handleSizeEntered);
|
||||||
|
|
||||||
|
window.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleSizeEntered(String value) {
|
||||||
|
try {
|
||||||
|
int enteredSize = Integer.parseInt(value);
|
||||||
|
if (enteredSize <= 0) {
|
||||||
|
MessageWindow.showModal(gui, "Размер должен быть больше 0.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
collectionSize = enteredSize;
|
||||||
|
sizeLabel.setText("Размер: " + collectionSize);
|
||||||
|
return true;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
MessageWindow.showModal(gui, "Введите целое число.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupCurrentFillMethod() {
|
||||||
|
switch (selectedFillMethod) {
|
||||||
|
case CUSTOM -> {
|
||||||
|
customDataSettingsWindow.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
case FILE ->
|
||||||
|
// TODO
|
||||||
|
MessageWindow.showModal(gui, "Не реализовано.");
|
||||||
|
|
||||||
|
case RANDOM ->
|
||||||
|
MessageWindow.showModal(
|
||||||
|
gui, "Для случайных данных дополнительные настройки не требуются.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectFillMethod() {
|
||||||
|
BaseModalWindow menu =
|
||||||
|
new FillMethodSelectionWindow(
|
||||||
|
gui,
|
||||||
|
fillMethod -> {
|
||||||
|
selectedFillMethod = fillMethod;
|
||||||
|
fillMethodLabel.setText(
|
||||||
|
"Способ заполнения: " + getFillMethodName(selectedFillMethod));
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMainCollection(AppState state) {
|
||||||
|
if (collectionSize <= 0) {
|
||||||
|
MessageWindow.showModal(gui, "Сначала задайте размер коллекции.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (selectedFillMethod) {
|
||||||
|
case CUSTOM:
|
||||||
|
createCollectionFromCustomData(state);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RANDOM:
|
||||||
|
createCollectionFromRandomData(state);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FILE:
|
||||||
|
createCollectionFromFileData(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createCollectionFromCustomData(AppState state) {
|
||||||
|
MyList<Student> customDataCollection = state.getCustomDataCollection();
|
||||||
|
|
||||||
|
MyList<Student> mainCollection = new MyLinkedList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < customDataCollection.size(); i++) {
|
||||||
|
mainCollection.add(customDataCollection.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
state.setMainCollection(mainCollection);
|
||||||
|
|
||||||
|
MessageWindow.showModal(gui, "Коллекция успешно создана.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createCollectionFromRandomData(AppState state) {
|
||||||
|
// TODO
|
||||||
|
MessageWindow.showModal(gui, "Не реализовано.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createCollectionFromFileData(AppState state) {
|
||||||
|
// TODO
|
||||||
|
MessageWindow.showModal(gui, "Не реализовано.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFillMethodName(FillMethod fillMethod) {
|
||||||
|
return switch (fillMethod) {
|
||||||
|
case CUSTOM -> "Пользовательские данные";
|
||||||
|
|
||||||
|
case RANDOM -> "Случайные данные";
|
||||||
|
|
||||||
|
case FILE -> "Данные из файла";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
package ru.project.ui.creation;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.Button;
|
||||||
|
import com.googlecode.lanterna.gui2.Label;
|
||||||
|
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||||
|
import com.googlecode.lanterna.gui2.Panel;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import ru.project.collection.MyLinkedList;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
import ru.project.model.AppState;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
import ru.project.student.StudentBuilder;
|
||||||
|
import ru.project.ui.base.BaseModalWindow;
|
||||||
|
import ru.project.ui.common.CollectionDisplayWindow;
|
||||||
|
import ru.project.ui.common.InputValueWindow;
|
||||||
|
import ru.project.ui.common.MessageWindow;
|
||||||
|
|
||||||
|
public class CustomDataSettingsWindow extends BaseModalWindow {
|
||||||
|
|
||||||
|
private final AppState state;
|
||||||
|
private final Supplier<Integer> requestedCollectionSize;
|
||||||
|
private final StudentBuilder studentBuilder;
|
||||||
|
|
||||||
|
private String groupNumber;
|
||||||
|
private Double averageGrade;
|
||||||
|
private Integer recordBookNumber;
|
||||||
|
private Integer selectedObjectIndex;
|
||||||
|
|
||||||
|
private final Label groupNumberLabel;
|
||||||
|
private final Label averageGradeLabel;
|
||||||
|
private final Label recordBookNumberLabel;
|
||||||
|
private final Label indexLabel;
|
||||||
|
|
||||||
|
private MyList<Student> preparedCustomDataCollection;
|
||||||
|
|
||||||
|
public CustomDataSettingsWindow(
|
||||||
|
WindowBasedTextGUI gui, AppState state, Supplier<Integer> collectionSize) {
|
||||||
|
|
||||||
|
super("Настройка пользовательских данных", gui);
|
||||||
|
|
||||||
|
this.state = state;
|
||||||
|
this.requestedCollectionSize = collectionSize;
|
||||||
|
|
||||||
|
this.studentBuilder = new StudentBuilder();
|
||||||
|
|
||||||
|
Panel panel = new Panel();
|
||||||
|
panel.setLayoutManager(new LinearLayout());
|
||||||
|
|
||||||
|
groupNumberLabel = new Label("Номер группы: не задан");
|
||||||
|
|
||||||
|
averageGradeLabel = new Label("Средний балл: не задан");
|
||||||
|
|
||||||
|
recordBookNumberLabel = new Label("Номер зачётной книжки: не задан");
|
||||||
|
|
||||||
|
indexLabel = new Label("Индекс объекта: не задан");
|
||||||
|
|
||||||
|
panel.addComponent(groupNumberLabel);
|
||||||
|
panel.addComponent(averageGradeLabel);
|
||||||
|
panel.addComponent(recordBookNumberLabel);
|
||||||
|
panel.addComponent(indexLabel);
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Задать номер группы", this::setGroupNumber));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Задать средний балл", this::setAverageGrade));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Задать номер зачётной книжки", this::setRecordBookNumber));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Задать индекс объекта", this::setObjectIndex));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Задать данные объекта", this::editObject));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Предпросмотр", this::preview));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Применить", this::apply));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Назад", this::close));
|
||||||
|
|
||||||
|
setComponent(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showModal() {
|
||||||
|
prepareCustomDataCollection();
|
||||||
|
super.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareCustomDataCollection() {
|
||||||
|
preparedCustomDataCollection = new MyLinkedList<>();
|
||||||
|
|
||||||
|
int targetCollectionSize = requestedCollectionSize.get();
|
||||||
|
int currentCustomDataCollectionSize = state.getCustomDataCollection().size();
|
||||||
|
|
||||||
|
int elementsToCopy = Math.min(currentCustomDataCollectionSize, targetCollectionSize);
|
||||||
|
|
||||||
|
for (int i = 0; i < elementsToCopy; i++) {
|
||||||
|
preparedCustomDataCollection.add(state.getCustomDataCollection().get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = elementsToCopy; i < targetCollectionSize; i++) {
|
||||||
|
preparedCustomDataCollection.add(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setGroupNumber() {
|
||||||
|
InputValueWindow window =
|
||||||
|
new InputValueWindow(
|
||||||
|
gui,
|
||||||
|
"Номер группы",
|
||||||
|
"Введите номер группы (например, A12):",
|
||||||
|
groupNumber,
|
||||||
|
value -> {
|
||||||
|
if (!value.matches(Student.getGroupNumberPattern())) {
|
||||||
|
MessageWindow.showModal(gui, "Номер группы должен соответствовать формату A12.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupNumber = value;
|
||||||
|
studentBuilder.setGroupNumber(value);
|
||||||
|
|
||||||
|
groupNumberLabel.setText("Номер группы: " + groupNumber);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAverageGrade() {
|
||||||
|
InputValueWindow window =
|
||||||
|
new InputValueWindow(
|
||||||
|
gui,
|
||||||
|
"Средний балл",
|
||||||
|
"Введите средний балл от 0.0 до 5.0:",
|
||||||
|
averageGrade == null ? null : String.valueOf(averageGrade),
|
||||||
|
value -> {
|
||||||
|
try {
|
||||||
|
double parsedValue = Double.parseDouble(value);
|
||||||
|
|
||||||
|
if (parsedValue < Student.getMinAverageGrade()
|
||||||
|
|| parsedValue > Student.getMaxAverageGrade()) {
|
||||||
|
|
||||||
|
MessageWindow.showModal(gui, "Средний балл должен быть от 0.0 до 5.0.");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
averageGrade = parsedValue;
|
||||||
|
|
||||||
|
studentBuilder.setAverageGrade(parsedValue);
|
||||||
|
|
||||||
|
averageGradeLabel.setText("Средний балл: " + averageGrade);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
MessageWindow.showModal(gui, "Введите корректное число.");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRecordBookNumber() {
|
||||||
|
InputValueWindow window =
|
||||||
|
new InputValueWindow(
|
||||||
|
gui,
|
||||||
|
"Номер зачётной книжки",
|
||||||
|
"Введите номер зачётной книжки:",
|
||||||
|
recordBookNumber == null ? null : String.valueOf(recordBookNumber),
|
||||||
|
value -> {
|
||||||
|
try {
|
||||||
|
int parsedValue = Integer.parseInt(value);
|
||||||
|
|
||||||
|
if (parsedValue < Student.getMinRecordBookNumber()) {
|
||||||
|
|
||||||
|
MessageWindow.showModal(gui, "Номер зачётной книжки должен быть положительным.");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordBookNumber = parsedValue;
|
||||||
|
|
||||||
|
studentBuilder.setRecordBookNumber(parsedValue);
|
||||||
|
|
||||||
|
recordBookNumberLabel.setText("Номер зачётной книжки: " + recordBookNumber);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
MessageWindow.showModal(gui, "Введите целое число.");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setObjectIndex() {
|
||||||
|
InputValueWindow window =
|
||||||
|
new InputValueWindow(
|
||||||
|
gui,
|
||||||
|
"Индекс объекта",
|
||||||
|
"Введите индекс объекта:",
|
||||||
|
selectedObjectIndex == null ? null : String.valueOf(selectedObjectIndex),
|
||||||
|
value -> {
|
||||||
|
try {
|
||||||
|
int index = Integer.parseInt(value);
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
MessageWindow.showModal(gui, "Индекс объекта не может быть отрицательным.");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = requestedCollectionSize.get();
|
||||||
|
|
||||||
|
if (index >= size) {
|
||||||
|
MessageWindow.showModal(gui, "Индекс должен быть меньше размера коллекции.");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedObjectIndex = index;
|
||||||
|
|
||||||
|
indexLabel.setText("Индекс объекта: " + selectedObjectIndex);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
MessageWindow.showModal(gui, "Введите целое число.");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void editObject() {
|
||||||
|
if (!hasStudentData()) {
|
||||||
|
MessageWindow.showModal(gui, "Сначала задайте все поля студента.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedObjectIndex == null) {
|
||||||
|
MessageWindow.showModal(gui, "Сначала задайте индекс объекта.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedObjectIndex >= preparedCustomDataCollection.size()) {
|
||||||
|
MessageWindow.showModal(gui, "Индекс объекта выходит за границы подготовленной коллекции.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Student student = studentBuilder.build();
|
||||||
|
|
||||||
|
preparedCustomDataCollection.set(selectedObjectIndex, student);
|
||||||
|
|
||||||
|
MessageWindow.showModal(gui, "Данные объекта успешно заданы.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apply() {
|
||||||
|
state.setCustomDataCollection(preparedCustomDataCollection);
|
||||||
|
prepareCustomDataCollection();
|
||||||
|
|
||||||
|
MessageWindow.showModal(gui, "Пользовательские данные успешно применены.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void preview() {
|
||||||
|
if (preparedCustomDataCollection == null || preparedCustomDataCollection.isEmpty()) {
|
||||||
|
|
||||||
|
MessageWindow.showModal(gui, "Подготовленная коллекция пуста.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionDisplayWindow window = new CollectionDisplayWindow(gui, preparedCustomDataCollection);
|
||||||
|
|
||||||
|
window.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasStudentData() {
|
||||||
|
return groupNumber != null && averageGrade != null && recordBookNumber != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.project.ui.creation;
|
||||||
|
|
||||||
|
public enum FillMethod {
|
||||||
|
CUSTOM,
|
||||||
|
RANDOM,
|
||||||
|
FILE
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package ru.project.ui.creation;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.ActionListBox;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import ru.project.ui.base.BaseModalWindow;
|
||||||
|
|
||||||
|
public class FillMethodSelectionWindow extends BaseModalWindow {
|
||||||
|
|
||||||
|
private final Consumer<FillMethod> onFillMethodSelected;
|
||||||
|
|
||||||
|
public FillMethodSelectionWindow(
|
||||||
|
WindowBasedTextGUI gui, Consumer<FillMethod> onFillMethodSelected) {
|
||||||
|
|
||||||
|
super("Выбор способа заполнения", gui);
|
||||||
|
|
||||||
|
this.onFillMethodSelected = onFillMethodSelected;
|
||||||
|
|
||||||
|
ActionListBox menu = new ActionListBox();
|
||||||
|
|
||||||
|
menu.addItem("Пользовательские данные", () -> selectFillMethod(FillMethod.CUSTOM));
|
||||||
|
|
||||||
|
menu.addItem("Случайные данные", () -> selectFillMethod(FillMethod.RANDOM));
|
||||||
|
|
||||||
|
menu.addItem("Данные из файла", () -> selectFillMethod(FillMethod.FILE));
|
||||||
|
|
||||||
|
menu.addItem("Выйти", this::close);
|
||||||
|
|
||||||
|
setComponent(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectFillMethod(FillMethod fillMethod) {
|
||||||
|
onFillMethodSelected.accept(fillMethod);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package ru.project.ui.main;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.ActionListBox;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
import ru.project.model.AppState;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
import ru.project.ui.base.BaseModalWindow;
|
||||||
|
import ru.project.ui.common.CollectionDisplayWindow;
|
||||||
|
import ru.project.ui.common.MessageWindow;
|
||||||
|
import ru.project.ui.creation.CreateCollectionMenu;
|
||||||
|
import ru.project.ui.sorting.SortCollectionMenu;
|
||||||
|
|
||||||
|
public class MainMenu extends BaseModalWindow {
|
||||||
|
|
||||||
|
private final ActionListBox menu;
|
||||||
|
|
||||||
|
private final BaseModalWindow createCollectionMenu;
|
||||||
|
private final BaseModalWindow sortCollectionMenu;
|
||||||
|
|
||||||
|
public MainMenu(WindowBasedTextGUI gui, AppState state) {
|
||||||
|
super("Главное меню", gui);
|
||||||
|
|
||||||
|
this.createCollectionMenu = new CreateCollectionMenu(gui, state);
|
||||||
|
this.sortCollectionMenu = new SortCollectionMenu(gui, state);
|
||||||
|
|
||||||
|
menu = new ActionListBox();
|
||||||
|
|
||||||
|
menu.addItem("Показать текущую коллекцию", () -> showMainCollection(state));
|
||||||
|
|
||||||
|
menu.addItem("Меню создания новой коллекции", this::showCreateCollectionMenu);
|
||||||
|
|
||||||
|
menu.addItem("Меню сохранения текущей коллекции в файл", this::saveCollection);
|
||||||
|
|
||||||
|
menu.addItem("Меню сортировки текущей коллекции", this::sortCollection);
|
||||||
|
|
||||||
|
menu.addItem("Меню поиска элемента в текущей коллекции", this::searchCollection);
|
||||||
|
|
||||||
|
menu.addItem("Меню подсчёта количества вхождений элемента", this::countOccurrences);
|
||||||
|
|
||||||
|
menu.addItem("Завершить выполнение программы", this::exit);
|
||||||
|
|
||||||
|
setComponent(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showMainCollection(AppState state) {
|
||||||
|
MyList<Student> mainCollection = state.getMainCollection();
|
||||||
|
|
||||||
|
BaseModalWindow window = new CollectionDisplayWindow(gui, mainCollection);
|
||||||
|
|
||||||
|
window.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCreateCollectionMenu() {
|
||||||
|
createCollectionMenu.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveCollection() {
|
||||||
|
// TODO
|
||||||
|
MessageWindow.showModal(gui, "Не реализовано.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sortCollection() {
|
||||||
|
sortCollectionMenu.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void searchCollection() {
|
||||||
|
// TODO
|
||||||
|
MessageWindow.showModal(gui, "Не реализовано.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void countOccurrences() {
|
||||||
|
// TODO
|
||||||
|
MessageWindow.showModal(gui, "Не реализовано.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exit() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package ru.project.ui.sorting;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.ActionListBox;
|
||||||
|
import com.googlecode.lanterna.gui2.Button;
|
||||||
|
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||||
|
import com.googlecode.lanterna.gui2.Panel;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import ru.project.ui.base.BaseModalWindow;
|
||||||
|
|
||||||
|
public class NormalSortSettingsWindow extends BaseModalWindow {
|
||||||
|
|
||||||
|
private final Consumer<SortField> onSortFieldSelected;
|
||||||
|
|
||||||
|
public NormalSortSettingsWindow(WindowBasedTextGUI gui, Consumer<SortField> onSortFieldSelected) {
|
||||||
|
|
||||||
|
super("Обычная сортировка", gui);
|
||||||
|
|
||||||
|
this.onSortFieldSelected = onSortFieldSelected;
|
||||||
|
|
||||||
|
Panel panel = new Panel();
|
||||||
|
panel.setLayoutManager(new LinearLayout());
|
||||||
|
|
||||||
|
ActionListBox menu = new ActionListBox();
|
||||||
|
|
||||||
|
menu.addItem("Сортировать по номеру группы", () -> selectSortField(SortField.GROUP_NUMBER));
|
||||||
|
|
||||||
|
menu.addItem("Сортировать по среднему баллу", () -> selectSortField(SortField.AVERAGE_GRADE));
|
||||||
|
|
||||||
|
menu.addItem(
|
||||||
|
"Сортировать по номеру зачётной книжки",
|
||||||
|
() -> selectSortField(SortField.RECORD_BOOK_NUMBER));
|
||||||
|
|
||||||
|
panel.addComponent(menu);
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Выйти", this::close));
|
||||||
|
|
||||||
|
setComponent(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectSortField(SortField sortField) {
|
||||||
|
onSortFieldSelected.accept(sortField);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package ru.project.ui.sorting;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.Button;
|
||||||
|
import com.googlecode.lanterna.gui2.Label;
|
||||||
|
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||||
|
import com.googlecode.lanterna.gui2.Panel;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
import ru.project.ui.base.BaseModalWindow;
|
||||||
|
|
||||||
|
public class ParitySortSettingsWindow extends BaseModalWindow {
|
||||||
|
|
||||||
|
public ParitySortSettingsWindow(WindowBasedTextGUI gui) {
|
||||||
|
super("Настройка сортировки по чётности", gui);
|
||||||
|
|
||||||
|
Panel panel = new Panel();
|
||||||
|
panel.setLayoutManager(new LinearLayout());
|
||||||
|
|
||||||
|
panel.addComponent(new Label("Сортировка выполняется по номеру зачётной книжки."));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Назад", this::close));
|
||||||
|
|
||||||
|
setComponent(panel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package ru.project.ui.sorting;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.Button;
|
||||||
|
import com.googlecode.lanterna.gui2.Label;
|
||||||
|
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||||
|
import com.googlecode.lanterna.gui2.Panel;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
import ru.project.model.AppState;
|
||||||
|
import ru.project.ui.base.BaseModalWindow;
|
||||||
|
import ru.project.ui.common.MessageWindow;
|
||||||
|
|
||||||
|
public class SortCollectionMenu extends BaseModalWindow {
|
||||||
|
|
||||||
|
private final AppState state;
|
||||||
|
|
||||||
|
private final Label sortMethodLabel;
|
||||||
|
private final Label sortFieldLabel;
|
||||||
|
|
||||||
|
private final SortMethodSelectionWindow sortMethodSelectionWindow;
|
||||||
|
private final NormalSortSettingsWindow normalSortSettingsWindow;
|
||||||
|
private final ParitySortSettingsWindow paritySortSettingsWindow;
|
||||||
|
|
||||||
|
private SortMethod selectedSortMethod;
|
||||||
|
private SortField selectedSortField;
|
||||||
|
|
||||||
|
public SortCollectionMenu(WindowBasedTextGUI gui, AppState state) {
|
||||||
|
|
||||||
|
super("Меню сортировки текущей коллекции", gui);
|
||||||
|
|
||||||
|
this.state = state;
|
||||||
|
this.selectedSortMethod = SortMethod.NORMAL;
|
||||||
|
this.selectedSortField = SortField.GROUP_NUMBER;
|
||||||
|
|
||||||
|
Panel panel = new Panel();
|
||||||
|
panel.setLayoutManager(new LinearLayout());
|
||||||
|
|
||||||
|
this.sortMethodLabel = new Label("Способ сортировки: " + getSortMethodName(selectedSortMethod));
|
||||||
|
|
||||||
|
this.sortFieldLabel = new Label("Сортировать по: " + getSortFieldName(selectedSortField));
|
||||||
|
|
||||||
|
this.sortMethodSelectionWindow =
|
||||||
|
new SortMethodSelectionWindow(
|
||||||
|
gui,
|
||||||
|
sortMethod -> {
|
||||||
|
selectedSortMethod = sortMethod;
|
||||||
|
|
||||||
|
sortMethodLabel.setText(
|
||||||
|
"Способ сортировки: " + getSortMethodName(selectedSortMethod));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.normalSortSettingsWindow =
|
||||||
|
new NormalSortSettingsWindow(
|
||||||
|
gui,
|
||||||
|
sortField -> {
|
||||||
|
selectedSortField = sortField;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.paritySortSettingsWindow = new ParitySortSettingsWindow(gui);
|
||||||
|
|
||||||
|
panel.addComponent(sortMethodLabel);
|
||||||
|
panel.addComponent(sortFieldLabel);
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Выбрать способ сортировки", this::selectSortMethod));
|
||||||
|
|
||||||
|
panel.addComponent(
|
||||||
|
new Button("Настроить текущий способ сортировки", this::setupCurrentSortMethod));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Сортировать текущую коллекцию", this::sortCurrentCollection));
|
||||||
|
|
||||||
|
panel.addComponent(new Button("Выйти", this::close));
|
||||||
|
|
||||||
|
setComponent(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupCurrentSortMethod() {
|
||||||
|
switch (selectedSortMethod) {
|
||||||
|
case NORMAL -> normalSortSettingsWindow.showModal();
|
||||||
|
|
||||||
|
case PARITY -> paritySortSettingsWindow.showModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectSortMethod() {
|
||||||
|
sortMethodSelectionWindow.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sortCurrentCollection() {
|
||||||
|
if (state.getMainCollection() == null || state.getMainCollection().isEmpty()) {
|
||||||
|
|
||||||
|
MessageWindow.showModal(gui, "Текущая коллекция пуста.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (selectedSortMethod) {
|
||||||
|
case NORMAL -> sortNormally();
|
||||||
|
|
||||||
|
case PARITY -> sortByParity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sortNormally() {
|
||||||
|
// TODO
|
||||||
|
MessageWindow.showModal(gui, "Обычная сортировка не реализована.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sortByParity() {
|
||||||
|
// TODO
|
||||||
|
MessageWindow.showModal(gui, "Сортировка по чётности не реализована.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSortMethodName(SortMethod sortMethod) {
|
||||||
|
return switch (sortMethod) {
|
||||||
|
case NORMAL -> "Обычная сортировка";
|
||||||
|
case PARITY -> "Сортировка по чётности";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSortFieldName(SortField sortField) {
|
||||||
|
return switch (sortField) {
|
||||||
|
case GROUP_NUMBER -> "номеру группы";
|
||||||
|
case AVERAGE_GRADE -> "среднему баллу";
|
||||||
|
case RECORD_BOOK_NUMBER -> "номеру зачётной книжки";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.project.ui.sorting;
|
||||||
|
|
||||||
|
public enum SortField {
|
||||||
|
GROUP_NUMBER,
|
||||||
|
AVERAGE_GRADE,
|
||||||
|
RECORD_BOOK_NUMBER
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.project.ui.sorting;
|
||||||
|
|
||||||
|
public enum SortMethod {
|
||||||
|
NORMAL,
|
||||||
|
PARITY
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package ru.project.ui.sorting;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.ActionListBox;
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import ru.project.ui.base.BaseModalWindow;
|
||||||
|
|
||||||
|
public class SortMethodSelectionWindow extends BaseModalWindow {
|
||||||
|
|
||||||
|
private final Consumer<SortMethod> onSortMethodSelected;
|
||||||
|
|
||||||
|
public SortMethodSelectionWindow(
|
||||||
|
WindowBasedTextGUI gui, Consumer<SortMethod> onSortMethodSelected) {
|
||||||
|
|
||||||
|
super("Выбрать способ сортировки", gui);
|
||||||
|
|
||||||
|
this.onSortMethodSelected = onSortMethodSelected;
|
||||||
|
|
||||||
|
ActionListBox menu = new ActionListBox();
|
||||||
|
|
||||||
|
menu.addItem("Обычная сортировка", () -> selectSortMethod(SortMethod.NORMAL));
|
||||||
|
|
||||||
|
menu.addItem("Сортировка по чётности", () -> selectSortMethod(SortMethod.PARITY));
|
||||||
|
|
||||||
|
menu.addItem("Выйти", this::close);
|
||||||
|
|
||||||
|
setComponent(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectSortMethod(SortMethod sortMethod) {
|
||||||
|
onSortMethodSelected.accept(sortMethod);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package ru.project;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for simple App.
|
|
||||||
*/
|
|
||||||
public class AppTest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rigorous Test :-)
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void shouldAnswerWithTrue() {
|
|
||||||
assertTrue(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package ru.test.maven.spring;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
public class MyLinkedListTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void shouldTestAdd() {
|
||||||
|
MyLinkedList<Integer> list = new MyLinkedList<>();
|
||||||
|
list.add(10);
|
||||||
|
list.add(20);
|
||||||
|
list.add(30);
|
||||||
|
|
||||||
|
assertEquals(10, list.get(0));
|
||||||
|
assertEquals(20, list.get(1));
|
||||||
|
assertEquals(30, list.get(2));
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void shouldTestGet()
|
||||||
|
{
|
||||||
|
MyLinkedList<Integer> list = new MyLinkedList<>();
|
||||||
|
list.add(10);
|
||||||
|
assertEquals(10, list.get(0));
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void shouldTestRemove() {
|
||||||
|
MyLinkedList<Integer> list = new MyLinkedList<>();
|
||||||
|
list.add(10);
|
||||||
|
list.add(20);
|
||||||
|
list.add(30);
|
||||||
|
list.remove(0);
|
||||||
|
|
||||||
|
assertEquals(2, list.size());
|
||||||
|
assertEquals(20, list.get(0));
|
||||||
|
assertEquals(30, list.get(1));
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTestSet() {
|
||||||
|
MyLinkedList<String> list = new MyLinkedList<>();
|
||||||
|
list.add("A");
|
||||||
|
list.add("B");
|
||||||
|
list.add("C");
|
||||||
|
|
||||||
|
String oldValue = list.set(1, "Z");
|
||||||
|
|
||||||
|
assertEquals("B", oldValue);
|
||||||
|
assertEquals("Z", list.get(1));
|
||||||
|
assertEquals(3, list.size());
|
||||||
|
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTestSize()
|
||||||
|
{
|
||||||
|
MyLinkedList<Integer> list = new MyLinkedList<>();
|
||||||
|
list.add(1);
|
||||||
|
list.add(2);
|
||||||
|
list.add(3);
|
||||||
|
assertEquals(3, list.size());
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTestisEmpty()
|
||||||
|
{
|
||||||
|
MyLinkedList<Integer> list = new MyLinkedList<>();
|
||||||
|
assertTrue(list.isEmpty());
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTestGet_OutOfBounds_ThrowsException() {
|
||||||
|
MyLinkedList<Integer> list = new MyLinkedList<>();
|
||||||
|
list.add(1);
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> list.get(-1));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> list.get(1)); // index == size
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> list.get(5)); // index > size
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTestSet_OutOfBounds_ThrowsException() {
|
||||||
|
MyLinkedList<Integer> list = new MyLinkedList<>();
|
||||||
|
list.add(1);
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> list.set(-1, 99));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> list.set(1, 99));
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTestRemove_IndexGreaterThanSize_ThrowsException() {
|
||||||
|
MyLinkedList<String> list = new MyLinkedList<>();
|
||||||
|
list.add("man");
|
||||||
|
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(5));
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTestRemove_NegativeIndex_ThrowsException() {
|
||||||
|
MyLinkedList<String> list = new MyLinkedList<>();
|
||||||
|
list.add("1");
|
||||||
|
list.add("2");
|
||||||
|
|
||||||
|
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1));
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTestaddAll_nonEmptySource_toEmptyList_addsAllElementsInOrder() {
|
||||||
|
MyList<String> list = new MyLinkedList<>();
|
||||||
|
MyList<String> source = new MyLinkedList<>();
|
||||||
|
source.add("A");
|
||||||
|
source.add("B");
|
||||||
|
source.add("C");
|
||||||
|
|
||||||
|
list.addAll(source);
|
||||||
|
|
||||||
|
assertEquals(3, list.size());
|
||||||
|
assertEquals("A", list.get(0));
|
||||||
|
assertEquals("B", list.get(1));
|
||||||
|
assertEquals("C", list.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTestaddAll_toNonEmptyList_appendsAtEnd() {
|
||||||
|
MyList<String> list = new MyLinkedList<>();
|
||||||
|
list.add("X");
|
||||||
|
list.add("Y");
|
||||||
|
|
||||||
|
MyList<String> source = new MyLinkedList<>();
|
||||||
|
source.add("A");
|
||||||
|
source.add("B");
|
||||||
|
|
||||||
|
list.addAll(source);
|
||||||
|
|
||||||
|
assertEquals(4, list.size());
|
||||||
|
assertEquals("X", list.get(0));
|
||||||
|
assertEquals("Y", list.get(1));
|
||||||
|
assertEquals("A", list.get(2));
|
||||||
|
assertEquals("B", list.get(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package ru.test.maven.spring;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collector;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class MyListCollectorTest
|
||||||
|
{
|
||||||
|
private final MyListCollector<Integer> collector = new MyListCollector<>();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTestCollectInOrderSequential() {
|
||||||
|
MyList<Integer> result = IntStream.range(0, 10)
|
||||||
|
.boxed()
|
||||||
|
.collect(MyCollectors.toMyList());
|
||||||
|
|
||||||
|
assertEquals(10, result.size());
|
||||||
|
for (int i = 0; i < result.size(); i++) {
|
||||||
|
assertEquals(i, (int) result.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTestCollectInOrderParallel() {
|
||||||
|
MyList<Integer> result = IntStream.range(0, 100)
|
||||||
|
.parallel()
|
||||||
|
.boxed()
|
||||||
|
.collect(MyCollectors.toMyList());
|
||||||
|
|
||||||
|
assertEquals(100, result.size());
|
||||||
|
for (int i = 0; i < result.size(); i++) {
|
||||||
|
assertEquals(i, (int) result.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTestHandleEmptyStream() {
|
||||||
|
MyList<Integer> result = IntStream.empty()
|
||||||
|
.boxed()
|
||||||
|
.collect(MyCollectors.toMyList());
|
||||||
|
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTestNotUseStandardList() {
|
||||||
|
MyList<String> result = IntStream.range(0, 5)
|
||||||
|
.mapToObj(i -> "item" + i)
|
||||||
|
.collect(MyCollectors.toMyList());
|
||||||
|
|
||||||
|
assertInstanceOf(MyList.class, result);
|
||||||
|
assertFalse(result instanceof java.util.List);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTestHaveCorrectCharacteristics() {
|
||||||
|
Set<Collector.Characteristics> characteristics = collector.characteristics();
|
||||||
|
assertTrue(characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH));
|
||||||
|
assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package ru.project.finder;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class StudentOccurrenceIndexFinderTest
|
||||||
|
{
|
||||||
|
private StudentOccurrenceIndexFinder finder;
|
||||||
|
private MyList<Student> students;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
finder = new StudentOccurrenceIndexFinder();
|
||||||
|
students = new MyLinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTesfindOccurrences_emptyList_returnsEmptyArray() {
|
||||||
|
int[] result = finder.findOccurrences(students, new Student("A12", 4.5, 1));
|
||||||
|
assertEquals(0, result.length);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTesfindOccurrences_noMatches_returnsEmptyArray() {
|
||||||
|
students.add(new Student("A12", 4.0, 101));
|
||||||
|
students.add(new Student("B23", 3.5, 102));
|
||||||
|
Student target = new Student("C34", 4.8, 103); // такого нет
|
||||||
|
|
||||||
|
int[] result = finder.findOccurrences(students, target);
|
||||||
|
assertEquals(0, result.length);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTesfindOccurrences_singleMatch_returnsCorrectIndex() {
|
||||||
|
students.add(new Student("A12", 4.0, 101));
|
||||||
|
students.add(new Student("B23", 3.5, 102)); // target здесь
|
||||||
|
students.add(new Student("C34", 4.8, 103));
|
||||||
|
|
||||||
|
Student target = new Student("B23", 3.5, 102);
|
||||||
|
int[] result = finder.findOccurrences(students, target);
|
||||||
|
|
||||||
|
assertEquals(1, result.length);
|
||||||
|
assertEquals(1, result[0]);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTesfindOccurrences_multipleMatches_returnsAllIndicesSorted() {
|
||||||
|
// индексы: 0, 2, 4 — должны вернуться в порядке возрастания
|
||||||
|
students.add(new Student("A12", 4.5, 101)); // match
|
||||||
|
students.add(new Student("B23", 3.8, 102));
|
||||||
|
students.add(new Student("A12", 4.5, 101)); // match
|
||||||
|
students.add(new Student("C34", 4.0, 103));
|
||||||
|
students.add(new Student("A12", 4.5, 101)); // match
|
||||||
|
|
||||||
|
Student target = new Student("A12", 4.5, 101);
|
||||||
|
int[] result = finder.findOccurrences(students, target);
|
||||||
|
|
||||||
|
assertEquals(3, result.length);
|
||||||
|
assertArrayEquals(new int[]{0, 2, 4}, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTesfindOccurrences_allElementsMatch_returnsAllIndices() {
|
||||||
|
Student s = new Student("X99", 5.0, 999);
|
||||||
|
students.add(s);
|
||||||
|
students.add(s);
|
||||||
|
students.add(s);
|
||||||
|
|
||||||
|
int[] result = finder.findOccurrences(students, s);
|
||||||
|
assertEquals(3, result.length);
|
||||||
|
assertArrayEquals(new int[]{0, 1, 2}, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTesfindOccurrences_differentAverageGrade_noMatch() {
|
||||||
|
students.add(new Student("A12", 4.50, 101));
|
||||||
|
Student target = new Student("A12", 4.51, 101); // отличается балл
|
||||||
|
|
||||||
|
int[] result = finder.findOccurrences(students, target);
|
||||||
|
assertEquals(0, result.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTesfindOccurrences_differentGroupNumber_noMatch() {
|
||||||
|
students.add(new Student("A12", 4.5, 101));
|
||||||
|
Student target = new Student("B12", 4.5, 101); // другая группа
|
||||||
|
|
||||||
|
int[] result = finder.findOccurrences(students, target);
|
||||||
|
assertEquals(0, result.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTesfindOccurrences_differentRecordBookNumber_noMatch() {
|
||||||
|
students.add(new Student("A12", 4.5, 101));
|
||||||
|
Student target = new Student("A12", 4.5, 102); // другой номер зачетки
|
||||||
|
|
||||||
|
int[] result = finder.findOccurrences(students, target);
|
||||||
|
assertEquals(0, result.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTesfindOccurrences_nullList_throwsException() {
|
||||||
|
Student target = new Student("A12", 4.5, 101);
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(null, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTesfindOccurrences_nullTarget_throwsException() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> finder.findOccurrences(students, null));
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTesfindOccurrences_resultIsSorted() {
|
||||||
|
String[] validGroups = {"A00", "A01", "B12", "C99", "X55"};
|
||||||
|
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
String group = validGroups[i % validGroups.length];
|
||||||
|
double grade = (3.0 + (i % 3)) / 2.0;
|
||||||
|
if (i % 3 == 0) {
|
||||||
|
|
||||||
|
students.add(new Student("A12", 4.5, 101));
|
||||||
|
} else {
|
||||||
|
students.add(new Student(group, grade, 200 + i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Student target = new Student("A12", 4.5, 101);
|
||||||
|
int[] result = finder.findOccurrences(students, target);
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 1; i < result.length; i++) {
|
||||||
|
assertTrue(result[i] > result[i - 1], "Результат должен быть отсортирован");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
package ru.project.search;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import ru.project.list.MyList;
|
||||||
|
import ru.project.list.MyLinkedList;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class StudentSearchTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGroupNumberRangeMatchesInside() {
|
||||||
|
StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30");
|
||||||
|
Student student = new Student("B20", 4.5, 123);
|
||||||
|
assertTrue(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGroupNumberRangeMatchesLowerBoundary() {
|
||||||
|
StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30");
|
||||||
|
Student student = new Student("A10", 4.5, 123);
|
||||||
|
assertTrue(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGroupNumberRangeMatchesUpperBoundary() {
|
||||||
|
StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30");
|
||||||
|
Student student = new Student("C30", 4.5, 123);
|
||||||
|
assertTrue(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGroupNumberRangeDoesNotMatchOutside() {
|
||||||
|
StudentSearchCriteria criteria = new GroupNumberRangeCriteria("A10", "C30");
|
||||||
|
Student student = new Student("D00", 4.5, 123);
|
||||||
|
assertFalse(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGroupNumberRangeInvalidMinMaxThrowsException() {
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new GroupNumberRangeCriteria("C30", "A10"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGroupNumberRangeNullValuesThrowsException() {
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new GroupNumberRangeCriteria(null, "A10"));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new GroupNumberRangeCriteria("A10", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAverageGradeRangeMatchesInside() {
|
||||||
|
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
Student student = new Student("A12", 4.5, 123);
|
||||||
|
assertTrue(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAverageGradeRangeMatchesLowerBoundary() {
|
||||||
|
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
Student student = new Student("A12", 4.0, 123);
|
||||||
|
assertTrue(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAverageGradeRangeMatchesUpperBoundary() {
|
||||||
|
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
Student student = new Student("A12", 5.0, 123);
|
||||||
|
assertTrue(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAverageGradeRangeDoesNotMatchBelow() {
|
||||||
|
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
Student student = new Student("A12", 3.9, 123);
|
||||||
|
assertFalse(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAverageGradeRangeDoesNotMatchAbove() {
|
||||||
|
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
Student student = new Student("A12", 5.1, 123);
|
||||||
|
assertFalse(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAverageGradeRangeInvalidMinMaxThrowsException() {
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new AverageGradeRangeCriteria(5.0, 4.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRecordBookNumberRangeMatchesInside() {
|
||||||
|
StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200);
|
||||||
|
Student student = new Student("A12", 4.5, 150);
|
||||||
|
assertTrue(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRecordBookNumberRangeMatchesLowerBoundary() {
|
||||||
|
StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200);
|
||||||
|
Student student = new Student("A12", 4.5, 100);
|
||||||
|
assertTrue(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRecordBookNumberRangeMatchesUpperBoundary() {
|
||||||
|
StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200);
|
||||||
|
Student student = new Student("A12", 4.5, 200);
|
||||||
|
assertTrue(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRecordBookNumberRangeDoesNotMatchBelow() {
|
||||||
|
StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200);
|
||||||
|
Student student = new Student("A12", 4.5, 99);
|
||||||
|
assertFalse(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRecordBookNumberRangeDoesNotMatchAbove() {
|
||||||
|
StudentSearchCriteria criteria = new RecordBookNumberRangeCriteria(100, 200);
|
||||||
|
Student student = new Student("A12", 4.5, 201);
|
||||||
|
assertFalse(criteria.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRecordBookNumberRangeInvalidMinMaxThrowsException() {
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new RecordBookNumberRangeCriteria(200, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompositeIntersectionEmptyReturnsTrue() {
|
||||||
|
CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION);
|
||||||
|
Student student = new Student("A12", 4.5, 123);
|
||||||
|
assertTrue(composite.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompositeUnionEmptyReturnsFalse() {
|
||||||
|
CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION);
|
||||||
|
Student student = new Student("A12", 4.5, 123);
|
||||||
|
assertFalse(composite.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompositeIntersectionAllTrue() {
|
||||||
|
CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION);
|
||||||
|
composite.add(new GroupNumberRangeCriteria("A10", "C30"));
|
||||||
|
composite.add(new AverageGradeRangeCriteria(4.0, 5.0));
|
||||||
|
Student student = new Student("B20", 4.5, 123);
|
||||||
|
assertTrue(composite.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompositeIntersectionOneFalse() {
|
||||||
|
CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION);
|
||||||
|
composite.add(new GroupNumberRangeCriteria("A10", "C30"));
|
||||||
|
composite.add(new AverageGradeRangeCriteria(4.0, 5.0));
|
||||||
|
Student student = new Student("B20", 3.5, 123);
|
||||||
|
assertFalse(composite.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompositeUnionOneTrue() {
|
||||||
|
CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION);
|
||||||
|
composite.add(new GroupNumberRangeCriteria("A10", "C30"));
|
||||||
|
composite.add(new AverageGradeRangeCriteria(4.0, 5.0));
|
||||||
|
Student student = new Student("D00", 4.5, 123);
|
||||||
|
assertTrue(composite.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompositeUnionAllFalse() {
|
||||||
|
CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.UNION);
|
||||||
|
composite.add(new GroupNumberRangeCriteria("A10", "C30"));
|
||||||
|
composite.add(new AverageGradeRangeCriteria(4.0, 5.0));
|
||||||
|
Student student = new Student("D00", 3.5, 123);
|
||||||
|
assertFalse(composite.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompositeWithNestedComposite() {
|
||||||
|
CompositeStudentSearchCriteria outer = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION);
|
||||||
|
outer.add(new GroupNumberRangeCriteria("A10", "C30"));
|
||||||
|
|
||||||
|
CompositeStudentSearchCriteria inner = new CompositeStudentSearchCriteria(SearchOperation.UNION);
|
||||||
|
inner.add(new AverageGradeRangeCriteria(4.0, 5.0));
|
||||||
|
inner.add(new RecordBookNumberRangeCriteria(100, 200));
|
||||||
|
|
||||||
|
outer.add(inner);
|
||||||
|
|
||||||
|
Student student = new Student("B20", 4.5, 150);
|
||||||
|
assertTrue(outer.matches(student));
|
||||||
|
student = new Student("B20", 3.5, 150);
|
||||||
|
assertFalse(outer.matches(student));
|
||||||
|
student = new Student("D00", 4.5, 150);
|
||||||
|
assertFalse(outer.matches(student));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSearchServiceWithSingleCriteria() {
|
||||||
|
MyList<Student> students = new MyLinkedList<>();
|
||||||
|
students.add(new Student("A10", 4.5, 1));
|
||||||
|
students.add(new Student("B20", 3.8, 2));
|
||||||
|
students.add(new Student("C30", 4.9, 3));
|
||||||
|
|
||||||
|
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
StudentSearchService service = new StudentSearchService(criteria);
|
||||||
|
MyList<Student> result = service.find(students);
|
||||||
|
|
||||||
|
assertEquals(2, result.size());
|
||||||
|
assertEquals("A10", result.get(0).getGroupNumber());
|
||||||
|
assertEquals("C30", result.get(1).getGroupNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSearchServiceEmptyResult() {
|
||||||
|
MyList<Student> students = new MyLinkedList<>();
|
||||||
|
students.add(new Student("A10", 3.5, 1));
|
||||||
|
students.add(new Student("B20", 3.8, 2));
|
||||||
|
|
||||||
|
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
StudentSearchService service = new StudentSearchService(criteria);
|
||||||
|
MyList<Student> result = service.find(students);
|
||||||
|
|
||||||
|
assertEquals(0, result.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSearchServiceEmptyCollection() {
|
||||||
|
MyList<Student> students = new MyLinkedList<>();
|
||||||
|
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
StudentSearchService service = new StudentSearchService(criteria);
|
||||||
|
MyList<Student> result = service.find(students);
|
||||||
|
assertEquals(0, result.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSearchServicePreservesOrder() {
|
||||||
|
MyList<Student> students = new MyLinkedList<>();
|
||||||
|
students.add(new Student("Z00", 4.5, 1));
|
||||||
|
students.add(new Student("A10", 4.5, 2));
|
||||||
|
students.add(new Student("M30", 4.5, 3));
|
||||||
|
|
||||||
|
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
StudentSearchService service = new StudentSearchService(criteria);
|
||||||
|
MyList<Student> result = service.find(students);
|
||||||
|
|
||||||
|
assertEquals(3, result.size());
|
||||||
|
assertEquals("Z00", result.get(0).getGroupNumber());
|
||||||
|
assertEquals("A10", result.get(1).getGroupNumber());
|
||||||
|
assertEquals("M30", result.get(2).getGroupNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSearchServiceDoesNotModifyOriginal() {
|
||||||
|
MyList<Student> original = new MyLinkedList<>();
|
||||||
|
original.add(new Student("A10", 4.5, 1));
|
||||||
|
original.add(new Student("B20", 3.8, 2));
|
||||||
|
|
||||||
|
MyList<Student> copy = new MyLinkedList<>();
|
||||||
|
for (int i = 0; i < original.size(); i++) {
|
||||||
|
copy.add(original.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
StudentSearchService service = new StudentSearchService(criteria);
|
||||||
|
service.find(original);
|
||||||
|
|
||||||
|
assertEquals(copy.size(), original.size());
|
||||||
|
for (int i = 0; i < copy.size(); i++) {
|
||||||
|
assertEquals(copy.get(i).getGroupNumber(), original.get(i).getGroupNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSearchServiceWithNullCriteriaThrowsException() {
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new StudentSearchService(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSearchServiceWithCompositeCriteria() {
|
||||||
|
MyList<Student> students = new MyLinkedList<>();
|
||||||
|
students.add(new Student("A10", 4.5, 1));
|
||||||
|
students.add(new Student("B20", 4.5, 2));
|
||||||
|
students.add(new Student("C30", 3.5, 3));
|
||||||
|
|
||||||
|
CompositeStudentSearchCriteria composite = new CompositeStudentSearchCriteria(SearchOperation.INTERSECTION);
|
||||||
|
composite.add(new GroupNumberRangeCriteria("A10", "B20"));
|
||||||
|
composite.add(new AverageGradeRangeCriteria(4.0, 5.0));
|
||||||
|
|
||||||
|
StudentSearchService service = new StudentSearchService(composite);
|
||||||
|
MyList<Student> result = service.find(students);
|
||||||
|
|
||||||
|
assertEquals(2, result.size());
|
||||||
|
assertEquals("A10", result.get(0).getGroupNumber());
|
||||||
|
assertEquals("B20", result.get(1).getGroupNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSearchServiceWithNullStudentListReturnsEmpty() {
|
||||||
|
StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
StudentSearchService service = new StudentSearchService(criteria);
|
||||||
|
MyList<Student> result = service.find(null);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(0, result.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
package ru.project.sorting;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import ru.project.collection.MyLinkedList;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
class EvenOnlySortStrategyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleEmptyList() {
|
||||||
|
MyList<Integer> list = new MyLinkedList<>();
|
||||||
|
|
||||||
|
EvenOnlySortStrategy<Integer> strategy =
|
||||||
|
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||||
|
|
||||||
|
strategy.sort(list, Integer::compare);
|
||||||
|
|
||||||
|
assertEquals(0, list.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldKeepSingleEvenElementUnchanged() {
|
||||||
|
MyList<Integer> list = new MyLinkedList<>();
|
||||||
|
list.add(4);
|
||||||
|
|
||||||
|
EvenOnlySortStrategy<Integer> strategy =
|
||||||
|
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||||
|
|
||||||
|
strategy.sort(list, Integer::compare);
|
||||||
|
|
||||||
|
assertEquals(4, list.get(0));
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldKeepSingleOddElementUnchanged() {
|
||||||
|
MyList<Integer> list = new MyLinkedList<>();
|
||||||
|
list.add(7);
|
||||||
|
|
||||||
|
EvenOnlySortStrategy<Integer> strategy =
|
||||||
|
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||||
|
|
||||||
|
strategy.sort(list, Integer::compare);
|
||||||
|
|
||||||
|
assertEquals(7, list.get(0));
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleDuplicateEvenValues() {
|
||||||
|
MyList<Integer> list = new MyLinkedList<>();
|
||||||
|
|
||||||
|
list.add(8);
|
||||||
|
list.add(4);
|
||||||
|
list.add(8);
|
||||||
|
list.add(2);
|
||||||
|
list.add(4);
|
||||||
|
|
||||||
|
EvenOnlySortStrategy<Integer> strategy =
|
||||||
|
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||||
|
|
||||||
|
strategy.sort(list, Integer::compare);
|
||||||
|
|
||||||
|
assertEquals(2, list.get(0));
|
||||||
|
assertEquals(4, list.get(1));
|
||||||
|
assertEquals(4, list.get(2));
|
||||||
|
assertEquals(8, list.get(3));
|
||||||
|
assertEquals(8, list.get(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldKeepOddStudentObjectsAtSameIndexes() {
|
||||||
|
Student oddFirst = new Student("A01", 4.0, 7);
|
||||||
|
Student evenFirst = new Student("A02", 4.0, 8);
|
||||||
|
Student oddSecond = new Student("A03", 4.0, 3);
|
||||||
|
Student evenSecond = new Student("A04", 4.0, 2);
|
||||||
|
Student oddThird = new Student("A05", 4.0, 5);
|
||||||
|
Student evenThird = new Student("A06", 4.0, 4);
|
||||||
|
|
||||||
|
MyList<Student> students = new MyLinkedList<>();
|
||||||
|
|
||||||
|
students.add(oddFirst);
|
||||||
|
students.add(evenFirst);
|
||||||
|
students.add(oddSecond);
|
||||||
|
students.add(evenSecond);
|
||||||
|
students.add(oddThird);
|
||||||
|
students.add(evenThird);
|
||||||
|
|
||||||
|
EvenOnlySortStrategy<Student> strategy =
|
||||||
|
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), Student::getRecordBookNumber);
|
||||||
|
|
||||||
|
strategy.sort(students, new StudentRecordBookComparator());
|
||||||
|
|
||||||
|
assertSame(oddFirst, students.get(0));
|
||||||
|
assertSame(oddSecond, students.get(2));
|
||||||
|
assertSame(oddThird, students.get(4));
|
||||||
|
|
||||||
|
assertEquals(2, students.get(1).getRecordBookNumber());
|
||||||
|
assertEquals(4, students.get(3).getRecordBookNumber());
|
||||||
|
assertEquals(8, students.get(5).getRecordBookNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSortWhenStrategyPassedThroughConstructor() {
|
||||||
|
MyList<Integer> list = new MyLinkedList<>();
|
||||||
|
|
||||||
|
list.add(8);
|
||||||
|
list.add(2);
|
||||||
|
list.add(4);
|
||||||
|
|
||||||
|
EvenOnlySortStrategy<Integer> strategy = new EvenOnlySortStrategy<>(new MergeSortStrategy<>());
|
||||||
|
|
||||||
|
strategy.setValueExtractor(value -> value);
|
||||||
|
|
||||||
|
strategy.sort(list, Integer::compare);
|
||||||
|
|
||||||
|
assertEquals(2, list.get(0));
|
||||||
|
assertEquals(4, list.get(1));
|
||||||
|
assertEquals(8, list.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSortOnlyEvenElements() {
|
||||||
|
MyList<Student> students = new MyLinkedList<>();
|
||||||
|
|
||||||
|
students.add(new Student("A01", 4.0, 7));
|
||||||
|
students.add(new Student("A02", 4.0, 8));
|
||||||
|
students.add(new Student("A03", 4.0, 3));
|
||||||
|
students.add(new Student("A04", 4.0, 2));
|
||||||
|
students.add(new Student("A05", 4.0, 5));
|
||||||
|
students.add(new Student("A06", 4.0, 4));
|
||||||
|
|
||||||
|
EvenOnlySortStrategy<Student> strategy =
|
||||||
|
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), Student::getRecordBookNumber);
|
||||||
|
|
||||||
|
strategy.sort(students, new StudentRecordBookComparator());
|
||||||
|
|
||||||
|
assertEquals(7, students.get(0).getRecordBookNumber());
|
||||||
|
assertEquals(2, students.get(1).getRecordBookNumber());
|
||||||
|
assertEquals(3, students.get(2).getRecordBookNumber());
|
||||||
|
assertEquals(4, students.get(3).getRecordBookNumber());
|
||||||
|
assertEquals(5, students.get(4).getRecordBookNumber());
|
||||||
|
assertEquals(8, students.get(5).getRecordBookNumber());
|
||||||
|
|
||||||
|
assertEquals(6, students.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSortAllEvenElements() {
|
||||||
|
MyList<Integer> list = new MyLinkedList<>();
|
||||||
|
|
||||||
|
list.add(8);
|
||||||
|
list.add(2);
|
||||||
|
list.add(6);
|
||||||
|
list.add(4);
|
||||||
|
|
||||||
|
EvenOnlySortStrategy<Integer> strategy =
|
||||||
|
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||||
|
|
||||||
|
strategy.sort(list, Integer::compare);
|
||||||
|
|
||||||
|
assertEquals(2, list.get(0));
|
||||||
|
assertEquals(4, list.get(1));
|
||||||
|
assertEquals(6, list.get(2));
|
||||||
|
assertEquals(8, list.get(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldKeepOddElementsUnchanged() {
|
||||||
|
MyList<Integer> list = new MyLinkedList<>();
|
||||||
|
|
||||||
|
list.add(7);
|
||||||
|
list.add(3);
|
||||||
|
list.add(5);
|
||||||
|
list.add(1);
|
||||||
|
|
||||||
|
EvenOnlySortStrategy<Integer> strategy =
|
||||||
|
new EvenOnlySortStrategy<>(new MergeSortStrategy<>(), value -> value);
|
||||||
|
|
||||||
|
strategy.sort(list, Integer::compare);
|
||||||
|
|
||||||
|
assertEquals(7, list.get(0));
|
||||||
|
assertEquals(3, list.get(1));
|
||||||
|
assertEquals(5, list.get(2));
|
||||||
|
assertEquals(1, list.get(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowWhenSortStrategyIsNotSet() {
|
||||||
|
EvenOnlySortStrategy<Integer> strategy = new EvenOnlySortStrategy<>();
|
||||||
|
|
||||||
|
strategy.setValueExtractor(value -> value);
|
||||||
|
|
||||||
|
MyList<Integer> list = new MyLinkedList<>();
|
||||||
|
list.add(2);
|
||||||
|
list.add(4);
|
||||||
|
|
||||||
|
assertThrows(IllegalStateException.class, () -> strategy.sort(list, Integer::compare));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectNullSortStrategy() {
|
||||||
|
EvenOnlySortStrategy<Integer> strategy = new EvenOnlySortStrategy<>();
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> strategy.setSortStrategy(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDelegateSortingToProvidedStrategy() {
|
||||||
|
MyList<Integer> list = new MyLinkedList<>();
|
||||||
|
|
||||||
|
list.add(7);
|
||||||
|
list.add(8);
|
||||||
|
list.add(3);
|
||||||
|
list.add(2);
|
||||||
|
|
||||||
|
RecordingSortStrategy recordingStrategy = new RecordingSortStrategy();
|
||||||
|
|
||||||
|
EvenOnlySortStrategy<Integer> strategy =
|
||||||
|
new EvenOnlySortStrategy<>(recordingStrategy, value -> value);
|
||||||
|
|
||||||
|
strategy.sort(list, Integer::compare);
|
||||||
|
|
||||||
|
assertTrue(recordingStrategy.wasCalled);
|
||||||
|
assertEquals(2, recordingStrategy.receivedSize);
|
||||||
|
assertEquals(8, recordingStrategy.firstReceivedElement);
|
||||||
|
assertEquals(2, recordingStrategy.secondReceivedElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseNewStrategyAfterReplacement() {
|
||||||
|
RecordingSortStrategy firstStrategy = new RecordingSortStrategy();
|
||||||
|
|
||||||
|
RecordingSortStrategy secondStrategy = new RecordingSortStrategy();
|
||||||
|
|
||||||
|
EvenOnlySortStrategy<Integer> strategy =
|
||||||
|
new EvenOnlySortStrategy<>(firstStrategy, value -> value);
|
||||||
|
|
||||||
|
MyList<Integer> firstList = new MyLinkedList<>();
|
||||||
|
firstList.add(4);
|
||||||
|
firstList.add(2);
|
||||||
|
|
||||||
|
strategy.sort(firstList, Integer::compare);
|
||||||
|
|
||||||
|
assertTrue(firstStrategy.wasCalled);
|
||||||
|
|
||||||
|
strategy.setSortStrategy(secondStrategy);
|
||||||
|
|
||||||
|
MyList<Integer> secondList = new MyLinkedList<>();
|
||||||
|
secondList.add(8);
|
||||||
|
secondList.add(6);
|
||||||
|
|
||||||
|
strategy.sort(secondList, Integer::compare);
|
||||||
|
|
||||||
|
assertTrue(secondStrategy.wasCalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RecordingSortStrategy implements SortStrategy<Integer> {
|
||||||
|
|
||||||
|
private boolean wasCalled;
|
||||||
|
private int receivedSize;
|
||||||
|
private Integer firstReceivedElement;
|
||||||
|
private Integer secondReceivedElement;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sort(MyList<Integer> list, Comparator<Integer> comparator) {
|
||||||
|
wasCalled = true;
|
||||||
|
receivedSize = list.size();
|
||||||
|
|
||||||
|
if (list.size() > 0) {
|
||||||
|
firstReceivedElement = list.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.size() > 1) {
|
||||||
|
secondReceivedElement = list.get(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package ru.project.sorting;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import ru.project.collection.MyLinkedList;
|
||||||
|
import ru.project.collection.MyList;
|
||||||
|
import ru.project.student.Student;
|
||||||
|
|
||||||
|
class SortStrategyTest {
|
||||||
|
|
||||||
|
private MyList<Student> students;
|
||||||
|
private final Comparator<Student> groupComparator = new StudentGroupComparator();
|
||||||
|
private final Comparator<Student> gradeComparator = new StudentAverageGradeComparator();
|
||||||
|
private final Comparator<Student> recordComparator = new StudentRecordBookComparator();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
students = new MyLinkedList<>();
|
||||||
|
students.add(new Student("A12", 4.5, 12345));
|
||||||
|
students.add(new Student("B32", 3.8, 12346));
|
||||||
|
students.add(new Student("Z00", 4.9, 12347));
|
||||||
|
students.add(new Student("A12", 4.2, 12348));
|
||||||
|
students.add(new Student("B32", 5.0, 12349));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMergeSortByGroupNumber() {
|
||||||
|
SortStrategy<Student> strategy = new MergeSortStrategy<>();
|
||||||
|
strategy.sort(students, groupComparator);
|
||||||
|
assertOrder(students, "A12", "A12", "B32", "B32", "Z00");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMergeSortByAverageGrade() {
|
||||||
|
SortStrategy<Student> strategy = new MergeSortStrategy<>();
|
||||||
|
strategy.sort(students, gradeComparator);
|
||||||
|
assertOrder(students, 3.8, 4.2, 4.5, 4.9, 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMergeSortByRecordBookNumber() {
|
||||||
|
SortStrategy<Student> strategy = new MergeSortStrategy<>();
|
||||||
|
strategy.sort(students, recordComparator);
|
||||||
|
assertOrder(students, 12345, 12346, 12347, 12348, 12349);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBubbleSortByGroupNumber() {
|
||||||
|
SortStrategy<Student> strategy = new BubbleSortStrategy<>();
|
||||||
|
strategy.sort(students, groupComparator);
|
||||||
|
assertOrder(students, "A12", "A12", "B32", "B32", "Z00");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBubbleSortByAverageGrade() {
|
||||||
|
SortStrategy<Student> strategy = new BubbleSortStrategy<>();
|
||||||
|
strategy.sort(students, gradeComparator);
|
||||||
|
assertOrder(students, 3.8, 4.2, 4.5, 4.9, 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBubbleSortByRecordBookNumber() {
|
||||||
|
SortStrategy<Student> strategy = new BubbleSortStrategy<>();
|
||||||
|
strategy.sort(students, recordComparator);
|
||||||
|
assertOrder(students, 12345, 12346, 12347, 12348, 12349);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEvenOnlySortByRecordBookNumber() {
|
||||||
|
MyList<Student> list = new MyLinkedList<>();
|
||||||
|
list.add(new Student("A01", 4.5, 7));
|
||||||
|
list.add(new Student("A02", 4.5, 8));
|
||||||
|
list.add(new Student("A03", 4.5, 3));
|
||||||
|
list.add(new Student("A04", 4.5, 2));
|
||||||
|
list.add(new Student("A05", 4.5, 5));
|
||||||
|
list.add(new Student("A06", 4.5, 4));
|
||||||
|
|
||||||
|
EvenOnlyMergeSortStrategy<Student> strategy =
|
||||||
|
new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber);
|
||||||
|
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||||
|
|
||||||
|
int[] expected = {7, 2, 3, 4, 5, 8};
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
assertEquals(expected[i], list.get(i).getRecordBookNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEvenOnlyWithNoEvenElements() {
|
||||||
|
MyList<Student> list = new MyLinkedList<>();
|
||||||
|
list.add(new Student("A01", 4.5, 1));
|
||||||
|
list.add(new Student("A02", 4.5, 3));
|
||||||
|
list.add(new Student("A03", 4.5, 5));
|
||||||
|
|
||||||
|
EvenOnlyMergeSortStrategy<Student> strategy =
|
||||||
|
new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber);
|
||||||
|
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||||
|
|
||||||
|
int[] expected = {1, 3, 5};
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
assertEquals(expected[i], list.get(i).getRecordBookNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEvenOnlyWithAllEvenElements() {
|
||||||
|
MyList<Student> list = new MyLinkedList<>();
|
||||||
|
list.add(new Student("A01", 4.5, 4));
|
||||||
|
list.add(new Student("A02", 4.5, 2));
|
||||||
|
list.add(new Student("A03", 4.5, 6));
|
||||||
|
|
||||||
|
EvenOnlyMergeSortStrategy<Student> strategy =
|
||||||
|
new EvenOnlyMergeSortStrategy<>(Student::getRecordBookNumber);
|
||||||
|
strategy.sort(list, Comparator.comparingInt(Student::getRecordBookNumber));
|
||||||
|
|
||||||
|
int[] expected = {2, 4, 6};
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
assertEquals(expected[i], list.get(i).getRecordBookNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSortEmptyList() {
|
||||||
|
MyList<Student> empty = new MyLinkedList<>();
|
||||||
|
SortStrategy<Student> strategy = new MergeSortStrategy<>();
|
||||||
|
strategy.sort(empty, groupComparator);
|
||||||
|
assertEquals(0, empty.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSortSingleElement() {
|
||||||
|
MyList<Student> single = new MyLinkedList<>();
|
||||||
|
single.add(new Student("A12", 4.5, 12345));
|
||||||
|
SortStrategy<Student> strategy = new MergeSortStrategy<>();
|
||||||
|
strategy.sort(single, groupComparator);
|
||||||
|
assertEquals(1, single.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOrder(MyList<Student> list, String... groups) {
|
||||||
|
assertEquals(groups.length, list.size());
|
||||||
|
for (int i = 0; i < groups.length; i++) {
|
||||||
|
assertEquals(groups[i], list.get(i).getGroupNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOrder(MyList<Student> list, double... grades) {
|
||||||
|
assertEquals(grades.length, list.size());
|
||||||
|
for (int i = 0; i < grades.length; i++) {
|
||||||
|
assertEquals(grades[i], list.get(i).getAverageGrade(), 0.001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOrder(MyList<Student> list, int... records) {
|
||||||
|
assertEquals(records.length, list.size());
|
||||||
|
for (int i = 0; i < records.length; i++) {
|
||||||
|
assertEquals(records[i], list.get(i).getRecordBookNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package ru.project.student;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class StudentTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testValidStudentCreation() {
|
||||||
|
Student student = new Student("A12", 4.5, 12345);
|
||||||
|
assertEquals("A12", student.getGroupNumber());
|
||||||
|
assertEquals(4.5, student.getAverageGrade());
|
||||||
|
assertEquals(12345, student.getRecordBookNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testValidGroupNumbers() {
|
||||||
|
assertDoesNotThrow(() -> new Student("A12", 4.5, 1));
|
||||||
|
assertDoesNotThrow(() -> new Student("B32", 4.5, 1));
|
||||||
|
assertDoesNotThrow(() -> new Student("Z00", 4.5, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInvalidGroupNumbers() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("a12", 4.5, 1));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("A1", 4.5, 1));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("A123", 4.5, 1));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("AA1", 4.5, 1));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("а1А", 4.5, 1));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("аа!!а123", 4.5, 1));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("23фи!12", 4.5, 1));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("№№###*()", 4.5, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testValidAverageGrades() {
|
||||||
|
assertDoesNotThrow(() -> new Student("A12", 0.0, 1));
|
||||||
|
assertDoesNotThrow(() -> new Student("A12", 5.0, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInvalidAverageGrades() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("A12", -1.0, 1));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("A12", 6.0, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testValidRecordBookNumbers() {
|
||||||
|
assertDoesNotThrow(() -> new Student("A12", 4.5, 1));
|
||||||
|
assertDoesNotThrow(() -> new Student("A12", 4.5, 12345));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInvalidRecordBookNumbers() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, 0));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new Student("A12", 4.5, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWithGroupNumber() {
|
||||||
|
Student original = new Student("A12", 4.5, 12345);
|
||||||
|
Student updated = original.withGroupNumber("B32");
|
||||||
|
|
||||||
|
assertNotSame(original, updated);
|
||||||
|
assertEquals("B32", updated.getGroupNumber());
|
||||||
|
assertEquals(4.5, updated.getAverageGrade());
|
||||||
|
assertEquals(12345, updated.getRecordBookNumber());
|
||||||
|
|
||||||
|
assertEquals("A12", original.getGroupNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWithAverageGrade() {
|
||||||
|
Student original = new Student("A12", 4.5, 12345);
|
||||||
|
Student updated = original.withAverageGrade(4.8);
|
||||||
|
|
||||||
|
assertNotSame(original, updated);
|
||||||
|
assertEquals(4.8, updated.getAverageGrade());
|
||||||
|
assertEquals("A12", updated.getGroupNumber());
|
||||||
|
assertEquals(12345, updated.getRecordBookNumber());
|
||||||
|
|
||||||
|
assertEquals(4.5, original.getAverageGrade());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWithRecordBookNumber() {
|
||||||
|
Student original = new Student("A12", 4.5, 12345);
|
||||||
|
Student updated = original.withRecordBookNumber(99999);
|
||||||
|
|
||||||
|
assertNotSame(original, updated);
|
||||||
|
assertEquals(99999, updated.getRecordBookNumber());
|
||||||
|
assertEquals("A12", updated.getGroupNumber());
|
||||||
|
assertEquals(4.5, updated.getAverageGrade());
|
||||||
|
|
||||||
|
assertEquals(12345, original.getRecordBookNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWithInvalidGroupNumber() {
|
||||||
|
Student original = new Student("A12", 4.5, 12345);
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> original.withGroupNumber("a12"));
|
||||||
|
assertEquals("A12", original.getGroupNumber()); // не изменился
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWithInvalidAverageGrade() {
|
||||||
|
Student original = new Student("A12", 4.5, 12345);
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> original.withAverageGrade(6.0));
|
||||||
|
assertEquals(4.5, original.getAverageGrade());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWithInvalidRecordBookNumber() {
|
||||||
|
Student original = new Student("A12", 4.5, 12345);
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> original.withRecordBookNumber(0));
|
||||||
|
assertEquals(12345, original.getRecordBookNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEqualsAndHashCode() {
|
||||||
|
Student s1 = new Student("A12", 4.5, 12345);
|
||||||
|
Student s2 = new Student("A12", 4.5, 12345);
|
||||||
|
Student s3 = new Student("B32", 4.5, 12345);
|
||||||
|
Student s4 = new Student("A12", 4.8, 12345);
|
||||||
|
Student s5 = new Student("A12", 4.5, 99999);
|
||||||
|
|
||||||
|
assertEquals(s1, s2);
|
||||||
|
assertEquals(s1.hashCode(), s2.hashCode());
|
||||||
|
|
||||||
|
assertNotEquals(s1, s3);
|
||||||
|
assertNotEquals(s1, s4);
|
||||||
|
assertNotEquals(s1, s5);
|
||||||
|
assertNotEquals(s1, null);
|
||||||
|
assertNotEquals(s1, "some string");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
Главное меню
|
||||||
|
{
|
||||||
|
Показать текущую коллекцию
|
||||||
|
|
||||||
|
Меню создания новой коллекции
|
||||||
|
{
|
||||||
|
Вернуться
|
||||||
|
|
||||||
|
Задать размер
|
||||||
|
|
||||||
|
Настроить способ заполнения
|
||||||
|
{
|
||||||
|
Пользовательские данные
|
||||||
|
{
|
||||||
|
Задать поле 'Название A'
|
||||||
|
Задать поле 'Название B'
|
||||||
|
Задать поле 'Название C'
|
||||||
|
Задать индекс изменяемого объекта
|
||||||
|
Изменить данные объекта
|
||||||
|
Предпросмотр
|
||||||
|
}
|
||||||
|
|
||||||
|
Случайные данные
|
||||||
|
{
|
||||||
|
Настройка диапазона генерации
|
||||||
|
{
|
||||||
|
Поле 'Название A'
|
||||||
|
{
|
||||||
|
Задать минимальное значение
|
||||||
|
Задать максимальное значение
|
||||||
|
}
|
||||||
|
|
||||||
|
Поле 'Название B'
|
||||||
|
{
|
||||||
|
Задать минимальное значение
|
||||||
|
Задать максимальное значение
|
||||||
|
}
|
||||||
|
|
||||||
|
Поле 'Название C'
|
||||||
|
{
|
||||||
|
Задать минимальное значение
|
||||||
|
Задать максимальное значение
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Данные из файла
|
||||||
|
{
|
||||||
|
Задать путь до файла
|
||||||
|
{
|
||||||
|
Абсолютный
|
||||||
|
Относительный
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Выбрать способ заполнения
|
||||||
|
{
|
||||||
|
Пользовательские данные *
|
||||||
|
Случайные данные
|
||||||
|
Данные из файла
|
||||||
|
}
|
||||||
|
|
||||||
|
Создать новую коллекцию
|
||||||
|
}
|
||||||
|
|
||||||
|
Меню сохранения текущей коллекции в файл
|
||||||
|
{
|
||||||
|
Вывести текущий абсолютный путь до файла
|
||||||
|
|
||||||
|
Задать путь до файла
|
||||||
|
{
|
||||||
|
Абсолютный
|
||||||
|
Относительный
|
||||||
|
}
|
||||||
|
|
||||||
|
Дописать коллекцию в текущий файл
|
||||||
|
}
|
||||||
|
|
||||||
|
Меню сортировки текущей коллекции
|
||||||
|
{
|
||||||
|
Вернуться
|
||||||
|
|
||||||
|
Настроить способ сортировки
|
||||||
|
{
|
||||||
|
Обычная сортировка
|
||||||
|
{
|
||||||
|
Сортировать по полю 'Название A'
|
||||||
|
Сортировать по полю 'Название B'
|
||||||
|
Сортировать по полю 'Название C'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Настроить способ сортировки
|
||||||
|
{
|
||||||
|
Сортировка по чётности
|
||||||
|
{
|
||||||
|
Выбрать числовое поле 'Название A'
|
||||||
|
Выбрать числовое поле 'Название B'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Выбрать способ сортировки
|
||||||
|
{
|
||||||
|
Обычная сортировка *
|
||||||
|
Сортировка по чётности
|
||||||
|
}
|
||||||
|
|
||||||
|
Сортировать текущую коллекцию
|
||||||
|
}
|
||||||
|
|
||||||
|
Меню поиска элемента в текущей коллекции
|
||||||
|
{
|
||||||
|
Вернуться
|
||||||
|
Задать значение поля 'Название A'
|
||||||
|
Задать значение поля 'Название B'
|
||||||
|
Задать значение поля 'Название C'
|
||||||
|
Найти первое вхождение в текущей коллекции
|
||||||
|
}
|
||||||
|
|
||||||
|
Меню подсчёта количества вхождений элемента
|
||||||
|
{
|
||||||
|
Задать значение поля 'Название A'
|
||||||
|
Задать значение поля 'Название B'
|
||||||
|
Задать значение поля 'Название C'
|
||||||
|
Выполнить многопоточный подсчёт
|
||||||
|
}
|
||||||
|
|
||||||
|
Завершить выполнение программы
|
||||||
|
}
|
||||||
@@ -0,0 +1,455 @@
|
|||||||
|
# Техническое задание: поиск студентов с использованием паттерна Компоновщик
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Реализовать механизм поиска студентов по диапазону значений с использованием паттерна **Компоновщик**.
|
||||||
|
|
||||||
|
Должны поддерживаться следующие критерии поиска:
|
||||||
|
|
||||||
|
- диапазон `groupNumber`;
|
||||||
|
- диапазон `averageGrade`;
|
||||||
|
- диапазон `recordBookNumber`.
|
||||||
|
|
||||||
|
Критерии должны поддерживать объединение в составной критерий.
|
||||||
|
|
||||||
|
Режим объединения критериев — **объединение (ИЛИ)** или **пересечение (И)** — задаётся в конструкторе основного класса.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Названия классов и интерфейсов
|
||||||
|
|
||||||
|
## `StudentSearchCriteria`
|
||||||
|
|
||||||
|
Интерфейс отдельного критерия поиска.
|
||||||
|
|
||||||
|
### Контракт
|
||||||
|
|
||||||
|
```java
|
||||||
|
boolean matches(Student student);
|
||||||
|
```
|
||||||
|
|
||||||
|
Метод возвращает `true`, если студент соответствует критерию.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `GroupNumberRangeCriteria`
|
||||||
|
|
||||||
|
Критерий поиска по диапазону `groupNumber`.
|
||||||
|
|
||||||
|
### Конструктор
|
||||||
|
|
||||||
|
```java
|
||||||
|
GroupNumberRangeCriteria(String min, String max);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Метод
|
||||||
|
|
||||||
|
```java
|
||||||
|
boolean matches(Student student);
|
||||||
|
```
|
||||||
|
|
||||||
|
`groupNumber` имеет тип `String`, поэтому сравнение выполнять лексикографически.
|
||||||
|
|
||||||
|
Границы диапазона включаются.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `AverageGradeRangeCriteria`
|
||||||
|
|
||||||
|
Критерий поиска по диапазону `averageGrade`.
|
||||||
|
|
||||||
|
### Конструктор
|
||||||
|
|
||||||
|
```java
|
||||||
|
AverageGradeRangeCriteria(double min, double max);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Метод
|
||||||
|
|
||||||
|
```java
|
||||||
|
boolean matches(Student student);
|
||||||
|
```
|
||||||
|
|
||||||
|
Студент соответствует критерию, если:
|
||||||
|
|
||||||
|
```text
|
||||||
|
min <= averageGrade <= max
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `RecordBookNumberRangeCriteria`
|
||||||
|
|
||||||
|
Критерий поиска по диапазону `recordBookNumber`.
|
||||||
|
|
||||||
|
### Конструктор
|
||||||
|
|
||||||
|
```java
|
||||||
|
RecordBookNumberRangeCriteria(int min, int max);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Метод
|
||||||
|
|
||||||
|
```java
|
||||||
|
boolean matches(Student student);
|
||||||
|
```
|
||||||
|
|
||||||
|
Студент соответствует критерию, если:
|
||||||
|
|
||||||
|
```text
|
||||||
|
min <= recordBookNumber <= max
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# `CompositeStudentSearchCriteria`
|
||||||
|
|
||||||
|
Компоновщик критериев поиска.
|
||||||
|
|
||||||
|
Класс реализует:
|
||||||
|
|
||||||
|
```java
|
||||||
|
StudentSearchCriteria
|
||||||
|
```
|
||||||
|
|
||||||
|
и содержит другие реализации `StudentSearchCriteria`.
|
||||||
|
|
||||||
|
### Методы
|
||||||
|
|
||||||
|
```java
|
||||||
|
void add(StudentSearchCriteria criteria);
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
void remove(StudentSearchCriteria criteria);
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
boolean matches(Student student);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Логика
|
||||||
|
|
||||||
|
`CompositeStudentSearchCriteria` должен поддерживать два режима:
|
||||||
|
|
||||||
|
```text
|
||||||
|
UNION — объединение условий, ИЛИ
|
||||||
|
INTERSECTION — пересечение условий, И
|
||||||
|
```
|
||||||
|
|
||||||
|
Режим должен задаваться при создании компоновщика:
|
||||||
|
|
||||||
|
```java
|
||||||
|
CompositeStudentSearchCriteria(
|
||||||
|
SearchOperation operation
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
где `SearchOperation` — перечисление:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum SearchOperation {
|
||||||
|
UNION,
|
||||||
|
INTERSECTION
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Для `UNION` студент подходит, если выполняется **хотя бы один** вложенный критерий.
|
||||||
|
|
||||||
|
Для `INTERSECTION` студент подходит, если выполняются **все** вложенные критерии.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# `StudentSearchService`
|
||||||
|
|
||||||
|
Основной класс поиска.
|
||||||
|
|
||||||
|
Он должен принимать интерфейс `StudentSearchCriteria`, а не конкретную реализацию критерия.
|
||||||
|
|
||||||
|
### Конструктор
|
||||||
|
|
||||||
|
```java
|
||||||
|
StudentSearchService(StudentSearchCriteria criteria);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Метод
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Student> find(MyList<Student> students);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Поведение
|
||||||
|
|
||||||
|
Метод `find()` должен:
|
||||||
|
|
||||||
|
1. Последовательно проверить студентов исходной коллекции.
|
||||||
|
2. Передать каждого студента в:
|
||||||
|
|
||||||
|
```java
|
||||||
|
criteria.matches(student)
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Добавить подходящих студентов в новую `MyList<Student>`.
|
||||||
|
2. Вернуть новую коллекцию.
|
||||||
|
|
||||||
|
`StudentSearchService` не должен содержать логику конкретных диапазонов или объединения критериев.
|
||||||
|
|
||||||
|
### Обработка отсутствующего критерия
|
||||||
|
|
||||||
|
Если `StudentSearchService` создан без критерия:
|
||||||
|
|
||||||
|
```java
|
||||||
|
new StudentSearchService(null);
|
||||||
|
```
|
||||||
|
|
||||||
|
необходимо выбросить:
|
||||||
|
|
||||||
|
```java
|
||||||
|
IllegalArgumentException
|
||||||
|
```
|
||||||
|
|
||||||
|
Класс не должен выполнять поиск без заданного критерия.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Пример использования
|
||||||
|
|
||||||
|
### Один критерий
|
||||||
|
|
||||||
|
```java
|
||||||
|
StudentSearchCriteria criteria =
|
||||||
|
new AverageGradeRangeCriteria(4.0, 5.0);
|
||||||
|
|
||||||
|
StudentSearchService service =
|
||||||
|
new StudentSearchService(criteria);
|
||||||
|
|
||||||
|
MyList<Student> result =
|
||||||
|
service.find(students);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пересечение критериев
|
||||||
|
|
||||||
|
Например, студент должен одновременно:
|
||||||
|
|
||||||
|
- находиться в группе от `A10` до `C30`;
|
||||||
|
- иметь средний балл от `4.0` до `5.0`.
|
||||||
|
|
||||||
|
```java
|
||||||
|
CompositeStudentSearchCriteria criteria =
|
||||||
|
new CompositeStudentSearchCriteria(
|
||||||
|
SearchOperation.INTERSECTION
|
||||||
|
);
|
||||||
|
|
||||||
|
criteria.add(
|
||||||
|
new GroupNumberRangeCriteria("A10", "C30")
|
||||||
|
);
|
||||||
|
|
||||||
|
criteria.add(
|
||||||
|
new AverageGradeRangeCriteria(4.0, 5.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
StudentSearchService service =
|
||||||
|
new StudentSearchService(criteria);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Объединение критериев
|
||||||
|
|
||||||
|
Студент должен соответствовать хотя бы одному условию:
|
||||||
|
|
||||||
|
```java
|
||||||
|
CompositeStudentSearchCriteria criteria =
|
||||||
|
new CompositeStudentSearchCriteria(
|
||||||
|
SearchOperation.UNION
|
||||||
|
);
|
||||||
|
|
||||||
|
criteria.add(
|
||||||
|
new AverageGradeRangeCriteria(4.5, 5.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
criteria.add(
|
||||||
|
new RecordBookNumberRangeCriteria(10000, 20000)
|
||||||
|
);
|
||||||
|
|
||||||
|
StudentSearchService service =
|
||||||
|
new StudentSearchService(criteria);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Общие требования
|
||||||
|
|
||||||
|
1. `StudentSearchService` должен работать только через `StudentSearchCriteria`.
|
||||||
|
2. `StudentSearchService` не должен знать о конкретных критериях.
|
||||||
|
3. `CompositeStudentSearchCriteria` должен реализовывать тот же интерфейс `StudentSearchCriteria`.
|
||||||
|
4. Должна поддерживаться вложенность компоновщиков.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Composite
|
||||||
|
├── GroupNumberRangeCriteria
|
||||||
|
└── Composite
|
||||||
|
├── AverageGradeRangeCriteria
|
||||||
|
└── RecordBookNumberRangeCriteria
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Исходную `MyList<Student>` не изменять.
|
||||||
|
2. Результат возвращать в виде новой `MyList<Student>`.
|
||||||
|
3. Порядок найденных студентов должен сохраняться.
|
||||||
|
4. Стандартные `List`, `ArrayList`, `LinkedList` не использовать.
|
||||||
|
5. Готовые методы поиска не использовать.
|
||||||
|
6. При отсутствии совпадений возвращать пустую `MyList<Student>`.
|
||||||
|
7. Для диапазонов `min > max` выбрасывать `IllegalArgumentException`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Примеры тестов
|
||||||
|
|
||||||
|
## `GroupNumberRangeCriteria`
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- значение внутри диапазона;
|
||||||
|
- значение равное нижней границе;
|
||||||
|
- значение равное верхней границе;
|
||||||
|
- значение вне диапазона.
|
||||||
|
|
||||||
|
## `AverageGradeRangeCriteria`
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
3.9 → false
|
||||||
|
4.0 → true
|
||||||
|
4.5 → true
|
||||||
|
5.0 → true
|
||||||
|
5.1 → false
|
||||||
|
```
|
||||||
|
|
||||||
|
## `RecordBookNumberRangeCriteria`
|
||||||
|
|
||||||
|
Проверить значения внутри и вне диапазона, включая обе границы.
|
||||||
|
|
||||||
|
## Некорректный диапазон
|
||||||
|
|
||||||
|
Для каждого критерия проверить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
min > max
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидается:
|
||||||
|
|
||||||
|
```java
|
||||||
|
IllegalArgumentException
|
||||||
|
```
|
||||||
|
|
||||||
|
## `INTERSECTION`
|
||||||
|
|
||||||
|
Создать два критерия и проверить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
критерий A = true
|
||||||
|
критерий B = true
|
||||||
|
→ true
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
критерий A = true
|
||||||
|
критерий B = false
|
||||||
|
→ false
|
||||||
|
```
|
||||||
|
|
||||||
|
## `UNION`
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A = true
|
||||||
|
B = false
|
||||||
|
→ true
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
A = false
|
||||||
|
B = true
|
||||||
|
→ true
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
A = false
|
||||||
|
B = false
|
||||||
|
→ false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Компоновщик без критериев
|
||||||
|
|
||||||
|
Проверить поведение:
|
||||||
|
|
||||||
|
```java
|
||||||
|
new CompositeStudentSearchCriteria(
|
||||||
|
SearchOperation.INTERSECTION
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
и:
|
||||||
|
|
||||||
|
```java
|
||||||
|
new CompositeStudentSearchCriteria(
|
||||||
|
SearchOperation.UNION
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Поведение должно быть явно определено и закреплено тестами.
|
||||||
|
|
||||||
|
Рекомендуется:
|
||||||
|
|
||||||
|
- пустое `INTERSECTION` → `true`;
|
||||||
|
- пустое `UNION` → `false`.
|
||||||
|
|
||||||
|
## `StudentSearchService`
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- корректный поиск по одному критерию;
|
||||||
|
- пустой результат;
|
||||||
|
- пустую исходную коллекцию;
|
||||||
|
- сохранение порядка;
|
||||||
|
- неизменность исходной коллекции;
|
||||||
|
- работу с `CompositeStudentSearchCriteria`.
|
||||||
|
|
||||||
|
## Проверка вложенного Компоновщика
|
||||||
|
|
||||||
|
Создать компоновщик с вложенным компоновщиком и проверить корректность вычисления результата.
|
||||||
|
|
||||||
|
## Проверка отсутствующего критерия
|
||||||
|
|
||||||
|
Создать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
new StudentSearchService(null);
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить `IllegalArgumentException`.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Реализовать:
|
||||||
|
|
||||||
|
```text
|
||||||
|
StudentSearchCriteria.java
|
||||||
|
GroupNumberRangeCriteria.java
|
||||||
|
AverageGradeRangeCriteria.java
|
||||||
|
RecordBookNumberRangeCriteria.java
|
||||||
|
CompositeStudentSearchCriteria.java
|
||||||
|
SearchOperation.java
|
||||||
|
StudentSearchService.java
|
||||||
|
```
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
Название ветки:
|
||||||
|
|
||||||
|
```text
|
||||||
|
feature/composite-student-search
|
||||||
|
```
|
||||||
@@ -0,0 +1,441 @@
|
|||||||
|
# Техническое задание: чтение и дозапись `Student` в CSV
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Реализовать два независимых класса для работы с объектами `Student` в формате CSV:
|
||||||
|
|
||||||
|
- `StudentCsvWriter` — дозапись студентов в файл;
|
||||||
|
- `StudentCsvReader` — последовательное чтение студентов из файла.
|
||||||
|
|
||||||
|
Для работы с форматом CSV использовать готовую библиотеку для работы с CSV. Самостоятельно реализовывать парсинг CSV запрещено.
|
||||||
|
|
||||||
|
Оба класса должны принимать **абсолютный путь к файлу**.
|
||||||
|
|
||||||
|
Исключения `Student` и `StudentBuilder` не обрабатывать.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Формат CSV-файла
|
||||||
|
|
||||||
|
Первая строка файла должна содержать заголовок:
|
||||||
|
|
||||||
|
```text
|
||||||
|
groupNumber,averageGrade,recordBookNumber
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждая следующая строка содержит данные одного студента:
|
||||||
|
|
||||||
|
```text
|
||||||
|
groupNumber,averageGrade,recordBookNumber
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```text
|
||||||
|
groupNumber,averageGrade,recordBookNumber
|
||||||
|
A12,4.5,10001
|
||||||
|
B07,3.8,10002
|
||||||
|
C25,4.9,10003
|
||||||
|
```
|
||||||
|
|
||||||
|
Для формирования и чтения CSV использовать стандартную библиотеку для работы с форматом CSV.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `StudentCsvWriter`
|
||||||
|
|
||||||
|
### Назначение
|
||||||
|
|
||||||
|
Дозаписывать одного студента в **существующий** CSV-файл.
|
||||||
|
|
||||||
|
### Контракт
|
||||||
|
|
||||||
|
Конструктор:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public StudentCsvWriter(String absoluteFilePath)
|
||||||
|
```
|
||||||
|
|
||||||
|
Метод:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void write(Student student) throws IOException
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
1. Метод принимает одного `Student`.
|
||||||
|
2. Указанный файл должен уже существовать.
|
||||||
|
3. Запись выполняется в режиме добавления.
|
||||||
|
4. Существующие записи не должны изменяться.
|
||||||
|
5. Заголовок CSV не должен записываться повторно.
|
||||||
|
6. Если файл не существует, новый файл **не создавать**.
|
||||||
|
7. Если файл не существует, метод должен выбросить `IOException`.
|
||||||
|
8. При успешной записи метод завершается без исключения.
|
||||||
|
9. Метод не возвращает значение.
|
||||||
|
10. Исключения, связанные с записью файла, не обрабатывать внутри метода.
|
||||||
|
11. Исключения `Student` и `StudentBuilder` также не обрабатывать.
|
||||||
|
|
||||||
|
## Невозможность записи
|
||||||
|
|
||||||
|
Если указанный файл не существует, метод должен выбросить `IOException`.
|
||||||
|
|
||||||
|
Также `IOException` должен возникать и передаваться вызывающему коду, если:
|
||||||
|
|
||||||
|
- файл недоступен для записи;
|
||||||
|
- отсутствуют права на запись;
|
||||||
|
- произошла ошибка файловой системы;
|
||||||
|
- запись в файл невозможна по другой причине.
|
||||||
|
|
||||||
|
Создание файла с именем вида:
|
||||||
|
|
||||||
|
```text
|
||||||
|
students-yyyy-MM-dd-HH-mm.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
`StudentCsvWriter` **не выполняет**.
|
||||||
|
|
||||||
|
## Пример теста: несуществующий файл
|
||||||
|
|
||||||
|
Передать путь к несуществующему файлу:
|
||||||
|
|
||||||
|
```java
|
||||||
|
writer.write(student);
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
IOException
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить также, что новый файл не был создан.
|
||||||
|
|
||||||
|
## Изменение теста существующего файла
|
||||||
|
|
||||||
|
Для существующего файла:
|
||||||
|
|
||||||
|
```text
|
||||||
|
groupNumber,averageGrade,recordBookNumber
|
||||||
|
A12,4.5,10001
|
||||||
|
```
|
||||||
|
|
||||||
|
выполнить:
|
||||||
|
|
||||||
|
```java
|
||||||
|
writer.write(student2);
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить, что результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
groupNumber,averageGrade,recordBookNumber
|
||||||
|
A12,4.5,10001
|
||||||
|
B07,3.8,10002
|
||||||
|
```
|
||||||
|
|
||||||
|
Заголовок присутствует только один раз.
|
||||||
|
|
||||||
|
## Дополнительный метод `StudentCsvWriter`
|
||||||
|
|
||||||
|
Кроме метода записи одного студента:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void write(Student student) throws IOException;
|
||||||
|
```
|
||||||
|
|
||||||
|
необходимо реализовать метод:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void write(MyList<Student>[] students) throws IOException;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Контракт
|
||||||
|
|
||||||
|
Метод должен:
|
||||||
|
|
||||||
|
1. Принимать массив `MyList<Student>`.
|
||||||
|
2. Последовательно обрабатывать все переданные коллекции.
|
||||||
|
3. Записывать каждого студента в указанный CSV-файл.
|
||||||
|
4. Сохранять порядок:
|
||||||
|
- коллекций в массиве;
|
||||||
|
- студентов внутри каждой `MyList`.
|
||||||
|
5. Использовать тот же формат CSV и те же правила записи, что и `write(Student)`.
|
||||||
|
6. Работать только с уже существующим файлом.
|
||||||
|
7. Не создавать файл, если он отсутствует.
|
||||||
|
8. При отсутствии файла выбрасывать `IOException`.
|
||||||
|
9. Не изменять переданные `MyList<Student>`.
|
||||||
|
10. Не обрабатывать исключения, возникающие при создании или валидации `Student`.
|
||||||
|
11. При ошибке записи прекращать операцию и передавать исключение вызывающему коду.
|
||||||
|
|
||||||
|
### Пример
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Student>[] studentLists = ...;
|
||||||
|
|
||||||
|
writer.write(studentLists);
|
||||||
|
```
|
||||||
|
|
||||||
|
Если:
|
||||||
|
|
||||||
|
```text
|
||||||
|
studentLists[0] = [Student A, Student B]
|
||||||
|
studentLists[1] = [Student C]
|
||||||
|
studentLists[2] = [Student D, Student E]
|
||||||
|
```
|
||||||
|
|
||||||
|
в файл должны последовательно добавиться:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Student A
|
||||||
|
Student B
|
||||||
|
Student C
|
||||||
|
Student D
|
||||||
|
Student E
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка пустых коллекций
|
||||||
|
|
||||||
|
Пустые `MyList<Student>` внутри массива не должны приводить к ошибке и не должны добавлять записей в файл.
|
||||||
|
|
||||||
|
### Проверка пустого массива
|
||||||
|
|
||||||
|
Для:
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Student>[] studentLists = ...; // размер 0
|
||||||
|
```
|
||||||
|
|
||||||
|
метод не должен добавлять никаких записей и не должен изменять файл.
|
||||||
|
|
||||||
|
### Проверка отсутствующего файла
|
||||||
|
|
||||||
|
Для несуществующего файла:
|
||||||
|
|
||||||
|
```java
|
||||||
|
writer.write(studentLists);
|
||||||
|
```
|
||||||
|
|
||||||
|
должен выбрасываться `IOException`. Файл создавать запрещено.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# `StudentCsvReader`
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Последовательно читать студентов из CSV-файла.
|
||||||
|
|
||||||
|
## Контракт
|
||||||
|
|
||||||
|
Класс должен реализовывать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Iterator<Student>
|
||||||
|
```
|
||||||
|
|
||||||
|
Конструктор:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public StudentCsvReader(String absoluteFilePath)
|
||||||
|
```
|
||||||
|
|
||||||
|
Методы:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public boolean hasNext()
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public Student next()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Требования к `hasNext()`
|
||||||
|
|
||||||
|
1. Проверяет наличие следующей записи `Student`.
|
||||||
|
2. Заголовок CSV не считается записью.
|
||||||
|
3. Повторный вызов `hasNext()` не должен пропускать следующую запись.
|
||||||
|
4. После окончания файла метод возвращает `false`.
|
||||||
|
|
||||||
|
### Требования к `next()`
|
||||||
|
|
||||||
|
1. Возвращает следующего `Student`.
|
||||||
|
2. `Student` создаётся через существующий `StudentBuilder`.
|
||||||
|
3. При отсутствии следующего элемента выбрасывается:
|
||||||
|
|
||||||
|
```text
|
||||||
|
NoSuchElementException
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Весь файл не должен предварительно загружаться в коллекцию.
|
||||||
|
|
||||||
|
## Невозможность прочитать студента
|
||||||
|
|
||||||
|
Если следующую запись невозможно прочитать или преобразовать в `Student`, например:
|
||||||
|
|
||||||
|
- некорректный формат CSV;
|
||||||
|
- отсутствует обязательное поле;
|
||||||
|
- значение невозможно преобразовать в нужный тип;
|
||||||
|
- данные не проходят валидацию `Student`;
|
||||||
|
|
||||||
|
исключение должно передаваться вызывающему коду.
|
||||||
|
|
||||||
|
Исключения `Student` и `StudentBuilder` **не перехватывать и не обрабатывать**.
|
||||||
|
|
||||||
|
Некорректные записи не должны молча пропускаться.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Общие требования
|
||||||
|
|
||||||
|
1. Оба класса принимают абсолютный путь к файлу.
|
||||||
|
2. Использовать готовую библиотеку для работы с CSV.
|
||||||
|
3. Не реализовывать собственный CSV-парсер.
|
||||||
|
4. Не использовать стандартные коллекции Java как основное хранилище студентов.
|
||||||
|
5. Не дублировать в этих классах валидацию `Student`.
|
||||||
|
6. Не дублировать логику создания `Student` вне `StudentBuilder`.
|
||||||
|
7. `StudentCsvWriter` отвечает только за запись.
|
||||||
|
8. `StudentCsvReader` отвечает только за чтение.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Примеры тестов
|
||||||
|
|
||||||
|
## `StudentCsvWriter`
|
||||||
|
|
||||||
|
### Создание нового файла
|
||||||
|
|
||||||
|
Передать путь к несуществующему файлу и вызвать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
writer.write(student);
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- файл создан;
|
||||||
|
- имя соответствует `students-yyyy-MM-dd-HH-mm.csv`;
|
||||||
|
- присутствует заголовок;
|
||||||
|
- студент записан;
|
||||||
|
- метод завершился без исключения.
|
||||||
|
|
||||||
|
### Дозапись
|
||||||
|
|
||||||
|
Создать файл с одним студентом и выполнить:
|
||||||
|
|
||||||
|
```java
|
||||||
|
writer.write(student2);
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить, что:
|
||||||
|
|
||||||
|
- первая запись сохранилась;
|
||||||
|
- `student2` добавлен в конец;
|
||||||
|
- заголовок присутствует только один раз.
|
||||||
|
|
||||||
|
### Несколько последовательных записей
|
||||||
|
|
||||||
|
```java
|
||||||
|
writer.write(student1);
|
||||||
|
writer.write(student2);
|
||||||
|
writer.write(student3);
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить наличие трёх записей в правильном порядке.
|
||||||
|
|
||||||
|
### Невозможность записи
|
||||||
|
|
||||||
|
Передать путь, по которому невозможно создать или открыть файл для записи.
|
||||||
|
|
||||||
|
Проверить, что `write()` выбрасывает `IOException`.
|
||||||
|
|
||||||
|
Не проверять специальное возвращаемое значение — метод ничего не возвращает.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `StudentCsvReader`
|
||||||
|
|
||||||
|
### Чтение корректного файла
|
||||||
|
|
||||||
|
Для файла:
|
||||||
|
|
||||||
|
```text
|
||||||
|
groupNumber,averageGrade,recordBookNumber
|
||||||
|
A12,4.5,10001
|
||||||
|
B07,3.8,10002
|
||||||
|
C25,4.9,10003
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- `hasNext()` возвращает `true` до последнего студента;
|
||||||
|
- `next()` возвращает студентов в правильном порядке;
|
||||||
|
- после последнего студента `hasNext()` возвращает `false`.
|
||||||
|
|
||||||
|
### Повторный вызов `hasNext()`
|
||||||
|
|
||||||
|
```java
|
||||||
|
reader.hasNext();
|
||||||
|
reader.hasNext();
|
||||||
|
Student student = reader.next();
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить, что первый студент не был пропущен.
|
||||||
|
|
||||||
|
### `next()` после окончания файла
|
||||||
|
|
||||||
|
После чтения всех студентов вызвать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
reader.next();
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить выброс:
|
||||||
|
|
||||||
|
```text
|
||||||
|
NoSuchElementException
|
||||||
|
```
|
||||||
|
|
||||||
|
### Некорректная запись
|
||||||
|
|
||||||
|
Передать файл:
|
||||||
|
|
||||||
|
```text
|
||||||
|
groupNumber,averageGrade,recordBookNumber
|
||||||
|
A12,4.5,10001
|
||||||
|
INVALID
|
||||||
|
C25,4.9,10003
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить, что ошибка при чтении некорректной записи передаётся вызывающему коду.
|
||||||
|
|
||||||
|
### Некорректные данные `Student`
|
||||||
|
|
||||||
|
Передать данные, нарушающие ограничения `Student`.
|
||||||
|
|
||||||
|
Проверить, что исключение от `Student` или `StudentBuilder` выходит наружу и не перехватывается `StudentCsvReader`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Названия файлов
|
||||||
|
|
||||||
|
```text
|
||||||
|
StudentCsvWriter.java
|
||||||
|
StudentCsvReader.java
|
||||||
|
```
|
||||||
|
|
||||||
|
# Git
|
||||||
|
|
||||||
|
Название ветки:
|
||||||
|
|
||||||
|
```text
|
||||||
|
feature/csv-student-io
|
||||||
|
```
|
||||||
|
|
||||||
|
Conventional Commit:
|
||||||
|
|
||||||
|
```text
|
||||||
|
feat: add csv student reader and writer
|
||||||
|
```
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Техническое задание: кастомный контейнер List
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Реализовать собственный контейнер для хранения объектов, не используя готовые реализации `List`, `ArrayList` и `LinkedList`.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
1. Создать интерфейс `MyList<T>`.
|
||||||
|
|
||||||
|
2. В интерфейсе определить основные операции:
|
||||||
|
|
||||||
|
- `add(...)`;
|
||||||
|
- `get(...)`;
|
||||||
|
- `set(...)`;
|
||||||
|
- `remove(...)`;
|
||||||
|
- `size()`;
|
||||||
|
- `isEmpty()`.
|
||||||
|
|
||||||
|
3. Создать класс `MyLinkedList<T>`, реализующий `MyList<T>`.
|
||||||
|
|
||||||
|
4. Реализовать контейнер на основе собственных узлов `Node<T>`.
|
||||||
|
|
||||||
|
5. Контейнер должен поддерживать:
|
||||||
|
|
||||||
|
- добавление элемента;
|
||||||
|
- получение элемента по индексу;
|
||||||
|
- замену элемента по индексу;
|
||||||
|
- удаление элемента по индексу;
|
||||||
|
- получение количества элементов;
|
||||||
|
- проверку на пустоту.
|
||||||
|
|
||||||
|
6. Обработать некорректные индексы с помощью исключений.
|
||||||
|
|
||||||
|
7. Контейнер не должен зависеть от класса `Student`, сортировки или пользовательского интерфейса.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Должны быть реализованы:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MyList.java
|
||||||
|
MyLinkedList.java
|
||||||
|
```
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
# Техническое задание: `EvenOnlySortStrategy`
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Создать класс `EvenOnlySortStrategy`, реализующий дополнительное задание по сортировке элементов с чётным значением выбранного числового поля.
|
||||||
|
|
||||||
|
Класс должен использовать **паттерн Стратегия**: он не реализует собственный алгоритм сортировки, а делегирует сортировку переданной реализации `SortStrategy`.
|
||||||
|
|
||||||
|
## Контракт
|
||||||
|
|
||||||
|
Класс:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class EvenOnlySortStrategy<T> implements SortStrategy<T>
|
||||||
|
```
|
||||||
|
|
||||||
|
Должен содержать два конструктора:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public EvenOnlySortStrategy()
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
public EvenOnlySortStrategy(SortStrategy<T> sortStrategy)
|
||||||
|
```
|
||||||
|
|
||||||
|
Также реализовать метод:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void setSortStrategy(SortStrategy<T> sortStrategy)
|
||||||
|
```
|
||||||
|
|
||||||
|
и основной метод интерфейса `SortStrategy`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public void sort(
|
||||||
|
MyList<T> list,
|
||||||
|
Comparator<T> comparator
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Для определения чётности числового поля использовать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
ToIntFunction<T>
|
||||||
|
```
|
||||||
|
|
||||||
|
Поэтому рекомендуется также передавать функцию получения числового значения через конструктор или отдельный метод настройки.
|
||||||
|
|
||||||
|
Для `Student` в качестве функции использовать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Student::getRecordBookNumber
|
||||||
|
```
|
||||||
|
|
||||||
|
## Поведение при отсутствии стратегии
|
||||||
|
|
||||||
|
Если `sortStrategy` не задан:
|
||||||
|
|
||||||
|
```java
|
||||||
|
EvenOnlySortStrategy<Student> strategy =
|
||||||
|
new EvenOnlySortStrategy<>();
|
||||||
|
```
|
||||||
|
|
||||||
|
то вызов:
|
||||||
|
|
||||||
|
```java
|
||||||
|
strategy.sort(students, comparator);
|
||||||
|
```
|
||||||
|
|
||||||
|
должен завершаться контролируемой ошибкой.
|
||||||
|
|
||||||
|
Рекомендуется выбрасывать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
IllegalStateException
|
||||||
|
```
|
||||||
|
|
||||||
|
с понятным сообщением о том, что стратегия сортировки не задана.
|
||||||
|
|
||||||
|
Не следует использовать `null` как признак успешного выполнения сортировки.
|
||||||
|
|
||||||
|
## Поведение `setSortStrategy`
|
||||||
|
|
||||||
|
Метод:
|
||||||
|
|
||||||
|
```java
|
||||||
|
setSortStrategy(SortStrategy<T> sortStrategy)
|
||||||
|
```
|
||||||
|
|
||||||
|
заменяет текущую стратегию.
|
||||||
|
|
||||||
|
После вызова метода последующая сортировка должна выполняться именно переданной стратегией.
|
||||||
|
|
||||||
|
Передача `null` должна приводить к контролируемой ошибке, например:
|
||||||
|
|
||||||
|
```java
|
||||||
|
IllegalArgumentException
|
||||||
|
```
|
||||||
|
|
||||||
|
## Основная бизнес-логика
|
||||||
|
|
||||||
|
`EvenOnlySortStrategy` **не должен содержать код какого-либо алгоритма сортировки**.
|
||||||
|
|
||||||
|
Запрещено реализовывать внутри класса:
|
||||||
|
|
||||||
|
- Merge Sort;
|
||||||
|
- Bubble Sort;
|
||||||
|
- Selection Sort;
|
||||||
|
- другие алгоритмы сортировки;
|
||||||
|
- вызовы стандартных методов сортировки Java.
|
||||||
|
|
||||||
|
Класс отвечает только за:
|
||||||
|
|
||||||
|
1. определение элементов, участвующих в специальной сортировке;
|
||||||
|
2. сохранение исходных позиций нечётных элементов;
|
||||||
|
3. передачу подходящих элементов переданной `SortStrategy`;
|
||||||
|
4. восстановление результата на исходные позиции;
|
||||||
|
5. делегирование самого алгоритма сортировки.
|
||||||
|
|
||||||
|
Таким образом:
|
||||||
|
|
||||||
|
```text
|
||||||
|
EvenOnlySortStrategy
|
||||||
|
│
|
||||||
|
├── определяет, какие элементы сортировать
|
||||||
|
│
|
||||||
|
├── передаёт сортируемые элементы
|
||||||
|
│ ↓
|
||||||
|
│ SortStrategy
|
||||||
|
│ ↓
|
||||||
|
│ MergeSortStrategy
|
||||||
|
│ QuickSortStrategy
|
||||||
|
│ ...
|
||||||
|
│
|
||||||
|
└── возвращает результат на исходные позиции
|
||||||
|
```
|
||||||
|
|
||||||
|
Один `EvenOnlySortStrategy` должен работать с **любой реализацией `SortStrategy`**.
|
||||||
|
|
||||||
|
## Рекомендуемый алгоритм
|
||||||
|
|
||||||
|
Рекомендуется использовать следующий подход.
|
||||||
|
|
||||||
|
Исходная коллекция:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[7, 8, 3, 2, 5, 4]
|
||||||
|
```
|
||||||
|
|
||||||
|
При выборе `recordBookNumber`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
нечётные: 7, 3, 5
|
||||||
|
чётные: 8, 2, 4
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Пройти исходный `MyList`.
|
||||||
|
2. С помощью `ToIntFunction<T>` определить чётность каждого элемента.
|
||||||
|
3. Запомнить позиции элементов с чётным значением.
|
||||||
|
4. Сформировать отдельный `MyList<T>` только из чётных элементов.
|
||||||
|
5. Передать эту коллекцию в `sortStrategy.sort(...)`.
|
||||||
|
6. Полученные отсортированные элементы последовательно вернуть на сохранённые позиции.
|
||||||
|
7. Элементы с нечётными значениями не изменять.
|
||||||
|
|
||||||
|
Например, переданная стратегия `MergeSortStrategy` сортирует:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[8, 2, 4]
|
||||||
|
```
|
||||||
|
|
||||||
|
в:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[2, 4, 8]
|
||||||
|
```
|
||||||
|
|
||||||
|
После возвращения элементов на исходные позиции:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[7, 2, 3, 4, 5, 8]
|
||||||
|
```
|
||||||
|
|
||||||
|
При этом `EvenOnlySortStrategy` не знает, каким способом были отсортированы `8`, `2`, `4`.
|
||||||
|
|
||||||
|
## Работа с любой `SortStrategy`
|
||||||
|
|
||||||
|
Должно быть возможно использовать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
EvenOnlySortStrategy<Student> strategy =
|
||||||
|
new EvenOnlySortStrategy<>(
|
||||||
|
new MergeSortStrategy<>(),
|
||||||
|
Student::getRecordBookNumber
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
А также после создания заменить стратегию:
|
||||||
|
|
||||||
|
```java
|
||||||
|
strategy.setSortStrategy(new AnotherSortStrategy<>());
|
||||||
|
```
|
||||||
|
|
||||||
|
После этого специальная сортировка должна использовать новую стратегию без изменения `EvenOnlySortStrategy`.
|
||||||
|
|
||||||
|
## Требования к `ToIntFunction`
|
||||||
|
|
||||||
|
Функция получения числового значения должна передаваться извне.
|
||||||
|
|
||||||
|
Для `Student`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Student::getRecordBookNumber
|
||||||
|
```
|
||||||
|
|
||||||
|
Стратегия не должна напрямую обращаться к:
|
||||||
|
|
||||||
|
```java
|
||||||
|
student.getRecordBookNumber()
|
||||||
|
```
|
||||||
|
|
||||||
|
или к другим полям `Student`.
|
||||||
|
|
||||||
|
Это позволяет использовать класс с другими типами объектов и другими числовыми полями.
|
||||||
|
|
||||||
|
## Требования к коллекциям
|
||||||
|
|
||||||
|
Работать необходимо с:
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<T>
|
||||||
|
```
|
||||||
|
|
||||||
|
Использование `ArrayList`, `LinkedList` и других стандартных коллекций для хранения элементов запрещено.
|
||||||
|
|
||||||
|
Вспомогательная коллекция чётных элементов также должна быть `MyList<T>`.
|
||||||
|
|
||||||
|
## Тесты
|
||||||
|
|
||||||
|
### Переданная стратегия используется
|
||||||
|
|
||||||
|
Передать `MergeSortStrategy`.
|
||||||
|
|
||||||
|
Проверить, что сортировка чётных элементов выполняется переданной стратегией.
|
||||||
|
|
||||||
|
### Замена стратегии
|
||||||
|
|
||||||
|
1. Создать `EvenOnlySortStrategy` с одной реализацией `SortStrategy`.
|
||||||
|
2. Выполнить сортировку.
|
||||||
|
3. Вызвать `setSortStrategy(...)`.
|
||||||
|
4. Выполнить сортировку повторно.
|
||||||
|
5. Проверить, что используется новая стратегия.
|
||||||
|
|
||||||
|
### Конструктор без стратегии
|
||||||
|
|
||||||
|
Создать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
new EvenOnlySortStrategy<>();
|
||||||
|
```
|
||||||
|
|
||||||
|
Вызвать `sort()`.
|
||||||
|
|
||||||
|
Проверить выброс `IllegalStateException`.
|
||||||
|
|
||||||
|
### Конструктор со стратегией
|
||||||
|
|
||||||
|
Создать объект с `MergeSortStrategy`.
|
||||||
|
|
||||||
|
Проверить успешное выполнение сортировки.
|
||||||
|
|
||||||
|
### Передача `null` в `setSortStrategy`
|
||||||
|
|
||||||
|
Проверить выброс `IllegalArgumentException`.
|
||||||
|
|
||||||
|
### Все элементы нечётные
|
||||||
|
|
||||||
|
```text
|
||||||
|
[7, 3, 5, 1]
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат должен полностью совпадать с исходной коллекцией.
|
||||||
|
|
||||||
|
### Все элементы чётные
|
||||||
|
|
||||||
|
```text
|
||||||
|
[8, 2, 6, 4]
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидаемый результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[2, 4, 6, 8]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Смешанные значения
|
||||||
|
|
||||||
|
```text
|
||||||
|
[7, 8, 3, 2, 5, 4]
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидаемый результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[7, 2, 3, 4, 5, 8]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Повторяющиеся значения
|
||||||
|
|
||||||
|
Проверить корректность сортировки при наличии одинаковых чётных значений.
|
||||||
|
|
||||||
|
### Один элемент
|
||||||
|
|
||||||
|
Проверить отдельно чётный и нечётный элемент.
|
||||||
|
|
||||||
|
### Пустая коллекция
|
||||||
|
|
||||||
|
Сортировка не должна приводить к ошибке.
|
||||||
|
|
||||||
|
### Проверка сохранения нечётных элементов
|
||||||
|
|
||||||
|
Проверить не только значения, но и то, что объекты с нечётным `recordBookNumber` остались на тех же индексах.
|
||||||
|
|
||||||
|
### Проверка разных реализаций
|
||||||
|
|
||||||
|
Использовать `EvenOnlySortStrategy` с каждой доступной реализацией `SortStrategy`.
|
||||||
|
|
||||||
|
Проверить, что результат специальной сортировки соответствует контракту независимо от конкретного алгоритма.
|
||||||
|
|
||||||
|
### Проверка делегирования
|
||||||
|
|
||||||
|
Создать тестовую реализацию `SortStrategy`, которая фиксирует факт вызова `sort()`.
|
||||||
|
|
||||||
|
Передать её в `EvenOnlySortStrategy`.
|
||||||
|
|
||||||
|
Проверить, что:
|
||||||
|
|
||||||
|
- `sort()` был вызван;
|
||||||
|
- в стратегию передана коллекция только с чётными элементами;
|
||||||
|
- сама `EvenOnlySortStrategy` не выполняет сортировку самостоятельно.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Реализовать:
|
||||||
|
|
||||||
|
```text
|
||||||
|
EvenOnlySortStrategy.java
|
||||||
|
```
|
||||||
|
|
||||||
|
Класс должен зависеть от абстракции:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SortStrategy<T>
|
||||||
|
MyList<T>
|
||||||
|
Comparator<T>
|
||||||
|
ToIntFunction<T>
|
||||||
|
```
|
||||||
|
|
||||||
|
и **не должен содержать реализации конкретного алгоритма сортировки**.
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
Ветка:
|
||||||
|
|
||||||
|
```text
|
||||||
|
feature/even-only-sort-strategy
|
||||||
|
```
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
# Техническое задание: генератор случайных студентов
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Реализовать класс для формирования `MyList<Student>` со случайными значениями.
|
||||||
|
|
||||||
|
По умолчанию ограничения диапазона для генерации не задаются. В таком случае значения должны генерироваться во всём допустимом диапазоне, установленном правилами класса `Student`.
|
||||||
|
|
||||||
|
Генерация объектов должна выполняться через существующий `StudentBuilder`.
|
||||||
|
|
||||||
|
## Название класса
|
||||||
|
|
||||||
|
```text id="m9s0q1"
|
||||||
|
RandomStudentGenerator
|
||||||
|
```
|
||||||
|
|
||||||
|
## Контракт класса
|
||||||
|
|
||||||
|
Класс должен хранить текущие ограничения генерации для каждого поля `Student`.
|
||||||
|
|
||||||
|
Для каждого поля необходимо предоставить по два метода: левое и правое ограничение.
|
||||||
|
|
||||||
|
### `groupNumber`
|
||||||
|
|
||||||
|
Тип поля: `String`.
|
||||||
|
|
||||||
|
```java id="t2z8k4"
|
||||||
|
RandomStudentGenerator setMinGroupNumber(String minGroupNumber);
|
||||||
|
```
|
||||||
|
|
||||||
|
```java id="q7x5n1"
|
||||||
|
RandomStudentGenerator setMaxGroupNumber(String maxGroupNumber);
|
||||||
|
```
|
||||||
|
|
||||||
|
Ограничения применяются при генерации `groupNumber`.
|
||||||
|
|
||||||
|
Сравнение границ выполнять в соответствии с правилами, используемыми для `groupNumber` в `Student`.
|
||||||
|
|
||||||
|
### `averageGrade`
|
||||||
|
|
||||||
|
Тип поля: `double`.
|
||||||
|
|
||||||
|
```java id="v4n2j8"
|
||||||
|
RandomStudentGenerator setMinAverageGrade(double minAverageGrade);
|
||||||
|
```
|
||||||
|
|
||||||
|
```java id="r5c1p9"
|
||||||
|
RandomStudentGenerator setMaxAverageGrade(double maxAverageGrade);
|
||||||
|
```
|
||||||
|
|
||||||
|
Диапазон должен быть включительным:
|
||||||
|
|
||||||
|
```text id="u3k7w2"
|
||||||
|
minAverageGrade <= averageGrade <= maxAverageGrade
|
||||||
|
```
|
||||||
|
|
||||||
|
### `recordBookNumber`
|
||||||
|
|
||||||
|
Тип поля: `int`.
|
||||||
|
|
||||||
|
```java id="d8m4x6"
|
||||||
|
RandomStudentGenerator setMinRecordBookNumber(int minRecordBookNumber);
|
||||||
|
```
|
||||||
|
|
||||||
|
```java id="e1q9s5"
|
||||||
|
RandomStudentGenerator setMaxRecordBookNumber(int maxRecordBookNumber);
|
||||||
|
```
|
||||||
|
|
||||||
|
Диапазон должен быть включительным:
|
||||||
|
|
||||||
|
```text id="y6p2v8"
|
||||||
|
minRecordBookNumber <= recordBookNumber <= maxRecordBookNumber
|
||||||
|
```
|
||||||
|
|
||||||
|
## Метод генерации
|
||||||
|
|
||||||
|
Основной метод:
|
||||||
|
|
||||||
|
```java id="k3w7m1"
|
||||||
|
MyList<Student> generate(int size);
|
||||||
|
```
|
||||||
|
|
||||||
|
Контракт:
|
||||||
|
|
||||||
|
- создаёт ровно `size` объектов `Student`;
|
||||||
|
- все объекты должны быть валидными;
|
||||||
|
- каждый объект создаётся через `StudentBuilder`;
|
||||||
|
- результат возвращается как `MyList<Student>`;
|
||||||
|
- стандартные `List`, `ArrayList`, `LinkedList` и другие контейнеры использовать нельзя.
|
||||||
|
|
||||||
|
## Поведение диапазонов по умолчанию
|
||||||
|
|
||||||
|
Если для поля ограничение не задано, используется весь допустимый диапазон этого поля.
|
||||||
|
|
||||||
|
Необходимо учитывать существующие ограничения `Student`, в частности:
|
||||||
|
|
||||||
|
```text id="z4r8t2"
|
||||||
|
averageGrade:
|
||||||
|
getMinAverageGrade()
|
||||||
|
getMaxAverageGrade()
|
||||||
|
|
||||||
|
recordBookNumber:
|
||||||
|
getMinRecordBookNumber()
|
||||||
|
```
|
||||||
|
|
||||||
|
Для `groupNumber` использовать существующий шаблон:
|
||||||
|
|
||||||
|
```text id="n5c7y3"
|
||||||
|
getGroupNumberPattern()
|
||||||
|
```
|
||||||
|
|
||||||
|
Генерируемое значение `groupNumber` должно соответствовать требованиям `Student`.
|
||||||
|
|
||||||
|
## Проверка ограничений
|
||||||
|
|
||||||
|
При установке границ необходимо проверять их корректность.
|
||||||
|
|
||||||
|
Недопустимые случаи:
|
||||||
|
|
||||||
|
```text id="h2k6p4"
|
||||||
|
min > max
|
||||||
|
```
|
||||||
|
|
||||||
|
Для них выбрасывать:
|
||||||
|
|
||||||
|
```java id="c7v3n9"
|
||||||
|
IllegalArgumentException
|
||||||
|
```
|
||||||
|
|
||||||
|
Ограничение также не должно выходить за допустимые значения `Student`.
|
||||||
|
|
||||||
|
## Рекомендуемая реализация
|
||||||
|
|
||||||
|
Класс должен хранить шесть ограничений:
|
||||||
|
|
||||||
|
```text id="b5q9x2"
|
||||||
|
minGroupNumber
|
||||||
|
maxGroupNumber
|
||||||
|
|
||||||
|
minAverageGrade
|
||||||
|
maxAverageGrade
|
||||||
|
|
||||||
|
minRecordBookNumber
|
||||||
|
maxRecordBookNumber
|
||||||
|
```
|
||||||
|
|
||||||
|
Для каждого поля значения генерируются независимо.
|
||||||
|
|
||||||
|
Рекомендуется использовать стандартный генератор случайных чисел Java, например:
|
||||||
|
|
||||||
|
```java id="a8f4k1"
|
||||||
|
Random
|
||||||
|
```
|
||||||
|
|
||||||
|
или:
|
||||||
|
|
||||||
|
```java id="p3m7d5"
|
||||||
|
ThreadLocalRandom
|
||||||
|
```
|
||||||
|
|
||||||
|
При создании `Student`:
|
||||||
|
|
||||||
|
```text id="w6j2r9"
|
||||||
|
сгенерировать поля
|
||||||
|
↓
|
||||||
|
StudentBuilder
|
||||||
|
↓
|
||||||
|
build()
|
||||||
|
↓
|
||||||
|
Student
|
||||||
|
↓
|
||||||
|
MyList<Student>
|
||||||
|
```
|
||||||
|
|
||||||
|
Дополнительную валидацию, дублирующую правила `Student`, в генераторе не реализовывать.
|
||||||
|
|
||||||
|
## Примеры использования
|
||||||
|
|
||||||
|
Без настройки диапазонов:
|
||||||
|
|
||||||
|
```java id="s2k8m4"
|
||||||
|
RandomStudentGenerator generator =
|
||||||
|
new RandomStudentGenerator();
|
||||||
|
|
||||||
|
MyList<Student> students =
|
||||||
|
generator.generate(100);
|
||||||
|
```
|
||||||
|
|
||||||
|
С настройкой диапазонов:
|
||||||
|
|
||||||
|
```java id="f7q3n1"
|
||||||
|
RandomStudentGenerator generator =
|
||||||
|
new RandomStudentGenerator()
|
||||||
|
.setMinGroupNumber("A00")
|
||||||
|
.setMaxGroupNumber("Z99")
|
||||||
|
.setMinAverageGrade(3.0)
|
||||||
|
.setMaxAverageGrade(5.0)
|
||||||
|
.setMinRecordBookNumber(10000)
|
||||||
|
.setMaxRecordBookNumber(99999);
|
||||||
|
|
||||||
|
MyList<Student> students =
|
||||||
|
generator.generate(100);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Тесты
|
||||||
|
|
||||||
|
### Генерация без ограничений
|
||||||
|
|
||||||
|
```java id="r1w5p8"
|
||||||
|
MyList<Student> result = generator.generate(100);
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- размер равен `100`;
|
||||||
|
- все элементы не равны `null`;
|
||||||
|
- каждый `Student` валиден.
|
||||||
|
|
||||||
|
### Ограничение `averageGrade`
|
||||||
|
|
||||||
|
Задать:
|
||||||
|
|
||||||
|
```text id="x4n7k2"
|
||||||
|
3.0 — 4.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить, что все значения находятся в указанном диапазоне.
|
||||||
|
|
||||||
|
### Ограничение `recordBookNumber`
|
||||||
|
|
||||||
|
Задать:
|
||||||
|
|
||||||
|
```text id="q9m3v6"
|
||||||
|
10000 — 20000
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить диапазон и включение границ.
|
||||||
|
|
||||||
|
### Ограничение `groupNumber`
|
||||||
|
|
||||||
|
Задать левую и правую границы.
|
||||||
|
|
||||||
|
Проверить, что все сгенерированные значения находятся между ними согласно правилам сравнения `groupNumber`.
|
||||||
|
|
||||||
|
### Граничные значения
|
||||||
|
|
||||||
|
Установить:
|
||||||
|
|
||||||
|
```text id="p8c4s1"
|
||||||
|
min == max
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить, что все сгенерированные значения равны указанной границе.
|
||||||
|
|
||||||
|
### Некорректный диапазон
|
||||||
|
|
||||||
|
Проверить выброс `IllegalArgumentException`, если:
|
||||||
|
|
||||||
|
```text id="z6t2h5"
|
||||||
|
min > max
|
||||||
|
```
|
||||||
|
|
||||||
|
### Некорректное ограничение
|
||||||
|
|
||||||
|
Передать диапазон, выходящий за допустимые значения `Student`.
|
||||||
|
|
||||||
|
Проверить выброс исключения.
|
||||||
|
|
||||||
|
### Генерация пустой коллекции
|
||||||
|
|
||||||
|
Для:
|
||||||
|
|
||||||
|
```java id="j5r8w3"
|
||||||
|
generate(0)
|
||||||
|
```
|
||||||
|
|
||||||
|
ожидается пустой `MyList<Student>`.
|
||||||
|
|
||||||
|
### Некорректный размер
|
||||||
|
|
||||||
|
Для отрицательного размера:
|
||||||
|
|
||||||
|
```java id="n2k7p4"
|
||||||
|
generate(-1)
|
||||||
|
```
|
||||||
|
|
||||||
|
ожидается `IllegalArgumentException`.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Необходимо реализовать:
|
||||||
|
|
||||||
|
```text id="v8m3q6"
|
||||||
|
RandomStudentGenerator.java
|
||||||
|
```
|
||||||
|
|
||||||
|
Класс должен использовать существующие:
|
||||||
|
|
||||||
|
```text id="f1j9x4"
|
||||||
|
Student.java
|
||||||
|
StudentBuilder.java
|
||||||
|
MyList.java
|
||||||
|
```
|
||||||
|
|
||||||
|
Изменять интерфейсы `Student`, `StudentBuilder` и `MyList` для выполнения данного задания не требуется.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Техническое задание: сортировка слиянием
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Реализовать паттерн Strategy и собственный алгоритм сортировки слиянием объектов `Student`.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
1. Создать интерфейс `SortStrategy<T>` с методом:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sort(MyList<T> list, Comparator<T> comparator)
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Создать класс `MergeSortStrategy<T>`, реализующий `SortStrategy<T>`.
|
||||||
|
|
||||||
|
2. Реализовать алгоритм сортировки слиянием самостоятельно.
|
||||||
|
|
||||||
|
3. Готовые методы сортировки Java не использовать.
|
||||||
|
|
||||||
|
4. Сортировка должна работать с `MyList<T>`.
|
||||||
|
|
||||||
|
5. Сортировка должна использовать переданный `Comparator<T>`.
|
||||||
|
|
||||||
|
6. Реализовать сортировку `Student` по каждому из трёх полей:
|
||||||
|
|
||||||
|
- `groupNumber`;
|
||||||
|
- `averageGrade`;
|
||||||
|
- `recordBookNumber`.
|
||||||
|
|
||||||
|
7. Создать отдельные компараторы:
|
||||||
|
|
||||||
|
- `StudentGroupComparator`;
|
||||||
|
- `StudentAverageGradeComparator`;
|
||||||
|
- `StudentRecordBookComparator`.
|
||||||
|
|
||||||
|
8. `MergeSortStrategy` не должен зависеть от класса `Student`.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
```text
|
||||||
|
SortStrategy.java
|
||||||
|
MergeSortStrategy.java
|
||||||
|
|
||||||
|
StudentGroupComparator.java
|
||||||
|
StudentAverageGradeComparator.java
|
||||||
|
StudentRecordBookComparator.java
|
||||||
|
```
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
# Техническое задание: заполнение `MyList<Student>` посредством Stream API
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Реализовать механизм заполнения собственной коллекции `MyList<Student>` посредством Java Stream API без использования стандартных коллекций Java в качестве промежуточного или итогового хранилища.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
1. Итоговым результатом работы потока должна быть:
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Student>
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Для сбора элементов потока непосредственно в `MyList<Student>` реализовать собственный `Collector`.
|
||||||
|
|
||||||
|
2. Стандартные коллекции Java (`List`, `ArrayList`, `LinkedList` и другие реализации `List`) запрещено использовать:
|
||||||
|
|
||||||
|
- как итоговую коллекцию;
|
||||||
|
- как промежуточное хранилище элементов перед их переносом в `MyList`.
|
||||||
|
|
||||||
|
3. `Student` должен создаваться только через уже существующий независимый класс `StudentBuilder`, если объект создаётся первоначально.
|
||||||
|
|
||||||
|
4. `MyListCollector` не должен создавать или изменять объекты `Student`. Он работает с уже созданными объектами.
|
||||||
|
|
||||||
|
5. При использовании `map` для получения нового `Student` использовать методы `with...()`, а не `StudentBuilder`.
|
||||||
|
|
||||||
|
6. Логика заполнения `MyList` должна быть отделена от логики класса `Student`.
|
||||||
|
|
||||||
|
7. Для добавления объектов использовать существующий метод:
|
||||||
|
|
||||||
|
```java
|
||||||
|
add(Student student)
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Необходимо реализовать поддержку объединения частичных результатов, необходимую для работы `Collector`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `MyListCollector<T>`
|
||||||
|
|
||||||
|
Создать универсальный класс:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MyListCollector<T>
|
||||||
|
implements Collector<T, MyList<T>, MyList<T>> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Реализовать методы:
|
||||||
|
|
||||||
|
```text
|
||||||
|
supplier()
|
||||||
|
accumulator()
|
||||||
|
combiner()
|
||||||
|
finisher()
|
||||||
|
characteristics()
|
||||||
|
```
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
|
||||||
|
- `supplier()` — создать пустой `MyList<T>`;
|
||||||
|
- `accumulator()` — добавить очередной элемент в `MyList<T>`;
|
||||||
|
- `combiner()` — объединить две частично заполненные коллекции;
|
||||||
|
- `finisher()` — вернуть готовый `MyList<T>`;
|
||||||
|
- `characteristics()` — определить характеристики сборщика.
|
||||||
|
|
||||||
|
Коллектор должен работать с `Student` без специальной логики, связанной с его полями.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `MyCollectors`
|
||||||
|
|
||||||
|
Создать вспомогательный класс:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public final class MyCollectors {
|
||||||
|
|
||||||
|
public static <T> Collector<T, MyList<T>, MyList<T>> toMyList() {
|
||||||
|
return new MyListCollector<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Класс должен предоставлять удобный способ получения собственного сборщика.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Контракт использования
|
||||||
|
|
||||||
|
Должна поддерживаться конструкция:
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Student> students = Stream.of(
|
||||||
|
student1,
|
||||||
|
student2,
|
||||||
|
student3
|
||||||
|
).collect(MyCollectors.toMyList());
|
||||||
|
```
|
||||||
|
|
||||||
|
где `student1`, `student2`, `student3` — валидные объекты `Student`, предварительно созданные через `StudentBuilder`.
|
||||||
|
|
||||||
|
Результатом должен быть именно `MyList<Student>`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Пример `map`
|
||||||
|
|
||||||
|
`map` должен использоваться для преобразования одного `Student` в новый `Student`.
|
||||||
|
|
||||||
|
Исходный объект не изменяется. Для создания нового объекта использовать `with...()`.
|
||||||
|
|
||||||
|
Одно из преобразуемых полей должно быть `String` и формироваться по заданному бизнес-правилу:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Заглавная латинская буква + двузначное число
|
||||||
|
```
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A07
|
||||||
|
B12
|
||||||
|
Z35
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Student> students = Stream.of(
|
||||||
|
student1,
|
||||||
|
student2,
|
||||||
|
student3
|
||||||
|
)
|
||||||
|
.map(student -> student.withGroupNumber(
|
||||||
|
generateGroupNumber(student)
|
||||||
|
))
|
||||||
|
.collect(MyCollectors.toMyList());
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример функции формирования значения:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private static String generateGroupNumber(Student student) {
|
||||||
|
int number = Math.abs(student.getRecordBookNumber().hashCode() % 100);
|
||||||
|
char letter = (char) ('A'
|
||||||
|
+ Math.abs(student.getRecordBookNumber().hashCode()) % 26);
|
||||||
|
|
||||||
|
return String.format("%c%02d", letter, number);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат `map` должен оставаться `Stream<Student>`, после чего он непосредственно собирается в `MyList<Student>`.
|
||||||
|
|
||||||
|
Формат создаваемого `groupNumber` должен соответствовать:
|
||||||
|
|
||||||
|
```text
|
||||||
|
^[A-Z][0-9]{2}$
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Пример `filter` совместно с `map`
|
||||||
|
|
||||||
|
Проверить совместную работу промежуточных операций:
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Student> students = Stream.of(
|
||||||
|
student1,
|
||||||
|
student2,
|
||||||
|
student3
|
||||||
|
)
|
||||||
|
.filter(student -> student.getAverageGrade() >= 4.0)
|
||||||
|
.map(student -> student.withGroupNumber(
|
||||||
|
generateGroupNumber(student)
|
||||||
|
))
|
||||||
|
.collect(MyCollectors.toMyList());
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить, что:
|
||||||
|
|
||||||
|
- в результат попали только студенты со средним баллом не ниже `4.0`;
|
||||||
|
- каждый результат является новым `Student`;
|
||||||
|
- `groupNumber` соответствует формату `[A-Z][0-9]{2}`;
|
||||||
|
- исходные объекты не изменились.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Параллельные потоки
|
||||||
|
|
||||||
|
`combiner()` должен корректно объединять две частично сформированные `MyList<Student>`.
|
||||||
|
|
||||||
|
Реалистичный пример:
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Student> students = IntStream.range(0, 10_000)
|
||||||
|
.parallel()
|
||||||
|
.mapToObj(i -> student1.withGroupNumber(
|
||||||
|
String.format("%c%02d",
|
||||||
|
'A' + (i % 26),
|
||||||
|
i % 100)
|
||||||
|
))
|
||||||
|
.collect(MyCollectors.toMyList());
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- результат содержит `10_000` объектов;
|
||||||
|
- ни один объект не потерян;
|
||||||
|
- количество объектов не превышает ожидаемого;
|
||||||
|
- каждый `groupNumber` соответствует формату `[A-Z][0-9]{2}`;
|
||||||
|
- объекты `Student` не изменяются после создания;
|
||||||
|
- `combiner()` корректно объединяет частичные результаты.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Примеры тестов
|
||||||
|
|
||||||
|
### Заполнение несколькими `Student`
|
||||||
|
|
||||||
|
Создать три валидных объекта `Student` через `StudentBuilder` и собрать их:
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Student> result = Stream.of(
|
||||||
|
student1,
|
||||||
|
student2,
|
||||||
|
student3
|
||||||
|
).collect(MyCollectors.toMyList());
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
result != null
|
||||||
|
result.size() == 3
|
||||||
|
```
|
||||||
|
|
||||||
|
и соответствие элементов исходному потоку.
|
||||||
|
|
||||||
|
### Проверка порядка
|
||||||
|
|
||||||
|
Для:
|
||||||
|
|
||||||
|
```text
|
||||||
|
student1
|
||||||
|
student2
|
||||||
|
student3
|
||||||
|
```
|
||||||
|
|
||||||
|
проверить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
result.get(0) == student1
|
||||||
|
result.get(1) == student2
|
||||||
|
result.get(2) == student3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пустой поток
|
||||||
|
|
||||||
|
```java
|
||||||
|
MyList<Student> result = Stream.<Student>empty()
|
||||||
|
.collect(MyCollectors.toMyList());
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
result != null
|
||||||
|
result.isEmpty()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка `map`
|
||||||
|
|
||||||
|
Преобразовать несколько `Student` через `withGroupNumber()`.
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- исходные объекты не изменились;
|
||||||
|
- созданные объекты отличаются от исходных;
|
||||||
|
- `groupNumber` соответствует формату `[A-Z][0-9]{2}`.
|
||||||
|
|
||||||
|
### Проверка `filter` и `map`
|
||||||
|
|
||||||
|
Проверить, что после `filter` и `map` в `MyList<Student>` попадают только необходимые студенты с корректно сформированным `groupNumber`.
|
||||||
|
|
||||||
|
### Параллельный поток
|
||||||
|
|
||||||
|
Создать поток из `10_000` объектов через `parallel()`.
|
||||||
|
|
||||||
|
Проверить размер итоговой коллекции, отсутствие потери элементов и корректность `groupNumber`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
Запрещено:
|
||||||
|
|
||||||
|
- использовать `Collectors.toList()`;
|
||||||
|
- использовать `ArrayList`, `LinkedList` или другие стандартные коллекции для накопления результата;
|
||||||
|
- сначала собирать `Student` в стандартную коллекцию, а затем переносить их в `MyList`;
|
||||||
|
- создавать `Student` внутри `MyListCollector`;
|
||||||
|
- изменять существующий `Student`;
|
||||||
|
- использовать `StudentBuilder` внутри `map` вместо `with...()`.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Реализовать:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MyListCollector.java
|
||||||
|
MyCollectors.java
|
||||||
|
```
|
||||||
|
|
||||||
|
Использовать существующие:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Student.java
|
||||||
|
StudentBuilder.java
|
||||||
|
MyList.java
|
||||||
|
MyLinkedList.java
|
||||||
|
```
|
||||||
|
|
||||||
|
### Рекомендация по архитектуре
|
||||||
|
|
||||||
|
`MyListCollector` должен зависеть только от интерфейса `MyList<T>`, а не от конкретной реализации `MyLinkedList<T>`.
|
||||||
|
|
||||||
|
`MyCollectors` должен содержать только методы создания собственных сборщиков и не должен содержать бизнес-логику.
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Техническое задание: класс `Student`
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Реализовать класс `Student` как единственную точку проверки корректности данных. Объект класса `Student` не может быть создан с некорректными данными.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
1. Класс `Student` содержит три поля:
|
||||||
|
|
||||||
|
- `groupNumber`;
|
||||||
|
- `averageGrade`;
|
||||||
|
- `recordBookNumber`.
|
||||||
|
|
||||||
|
2. Все поля должны быть `private` и неизменяемыми после создания объекта.
|
||||||
|
|
||||||
|
3. Проверка корректности данных должна находиться в классе `Student`.
|
||||||
|
|
||||||
|
4. Реализовать класс `StudentBuilder` для создания объектов `Student`.
|
||||||
|
|
||||||
|
5. В `StudentBuilder` реализовать методы:
|
||||||
|
|
||||||
|
- `setGroupNumber(...)`;
|
||||||
|
- `setAverageGrade(...)`;
|
||||||
|
- `setRecordBookNumber(...)`;
|
||||||
|
- `build()`.
|
||||||
|
|
||||||
|
6. Метод `build()` должен создавать только корректный объект `Student`. При некорректных данных должно выбрасываться исключение.
|
||||||
|
|
||||||
|
7. Реализовать для `Student`:
|
||||||
|
|
||||||
|
- методы получения значений полей;
|
||||||
|
- `toString()`;
|
||||||
|
- `equals()`;
|
||||||
|
- `hashCode()`.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Должны быть реализованы два независимых класса:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Student.java
|
||||||
|
StudentBuilder.java
|
||||||
|
```
|
||||||
|
|
||||||
|
Классы не должны зависеть от сортировки, коллекции, меню и ввода данных.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
изменять не требуется.
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
# Техническое задание: обновление `Student`
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Обновить класс `Student` и связанный с ним код в соответствии с новой моделью данных и требованиями к неизменяемости объекта.
|
||||||
|
|
||||||
|
Текущая реализация содержит:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private final String groupNumber;
|
||||||
|
private final double averageGrade;
|
||||||
|
private final String recordBookNumber;
|
||||||
|
```
|
||||||
|
|
||||||
|
Необходимо изменить тип `recordBookNumber` и добавить методы создания изменённых копий `Student`.
|
||||||
|
|
||||||
|
## Изменения в `Student`
|
||||||
|
|
||||||
|
### 1. Изменить тип `recordBookNumber`
|
||||||
|
|
||||||
|
Поле:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private final String recordBookNumber;
|
||||||
|
```
|
||||||
|
|
||||||
|
заменить на:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private final int recordBookNumber;
|
||||||
|
```
|
||||||
|
|
||||||
|
Метод:
|
||||||
|
|
||||||
|
```java
|
||||||
|
getRecordBookNumber()
|
||||||
|
```
|
||||||
|
|
||||||
|
должен возвращать `int`.
|
||||||
|
|
||||||
|
Все проверки, `equals()`, `hashCode()` и `toString()` должны быть адаптированы под новый тип.
|
||||||
|
|
||||||
|
### 2. Добавить бизнес-правило для `groupNumber`
|
||||||
|
|
||||||
|
`groupNumber` должен соответствовать строго следующему формату:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<Заглавная буква латинского алфавита><2 цифры>
|
||||||
|
```
|
||||||
|
|
||||||
|
Регулярное выражение:
|
||||||
|
|
||||||
|
```text
|
||||||
|
^[A-Z][0-9]{2}$
|
||||||
|
```
|
||||||
|
|
||||||
|
Допустимые значения:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A12
|
||||||
|
B32
|
||||||
|
Z00
|
||||||
|
```
|
||||||
|
|
||||||
|
Недопустимые значения:
|
||||||
|
|
||||||
|
```text
|
||||||
|
а12
|
||||||
|
a12
|
||||||
|
A1
|
||||||
|
A123
|
||||||
|
AA1
|
||||||
|
а1А
|
||||||
|
аа!!а123
|
||||||
|
23фи!12
|
||||||
|
№№###*()
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверка должна выполняться при создании `Student`.
|
||||||
|
|
||||||
|
`Student` не может существовать с некорректным `groupNumber`.
|
||||||
|
|
||||||
|
### 3. Проверить `averageGrade`
|
||||||
|
|
||||||
|
Сохранить правило:
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.0 <= averageGrade <= 5.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Значения `0.0` и `5.0` допустимы.
|
||||||
|
|
||||||
|
### 4. Проверить `recordBookNumber`
|
||||||
|
|
||||||
|
После преобразования в `int` установить бизнес-правило:
|
||||||
|
|
||||||
|
```text
|
||||||
|
recordBookNumber > 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Значение `0` и отрицательные значения считаются недопустимыми.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Методы `with...()`
|
||||||
|
|
||||||
|
Добавить методы, возвращающие **новый объект `Student`**, не изменяя текущий объект.
|
||||||
|
|
||||||
|
Необходимо реализовать:
|
||||||
|
|
||||||
|
```java
|
||||||
|
withGroupNumber(String groupNumber)
|
||||||
|
withAverageGrade(double averageGrade)
|
||||||
|
withRecordBookNumber(int recordBookNumber)
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый метод должен:
|
||||||
|
|
||||||
|
1. Сохранять исходный объект без изменений.
|
||||||
|
2. Изменять только указанное поле в новом объекте.
|
||||||
|
3. Сохранять остальные поля исходного объекта.
|
||||||
|
4. Повторно применять все правила валидации.
|
||||||
|
5. Возвращать новый валидный объект `Student`.
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Student updated = student.withAverageGrade(4.8);
|
||||||
|
```
|
||||||
|
|
||||||
|
После выполнения:
|
||||||
|
|
||||||
|
```text
|
||||||
|
student.getAverageGrade() == прежнее значение
|
||||||
|
updated.getAverageGrade() == 4.8
|
||||||
|
```
|
||||||
|
|
||||||
|
Если новое значение невалидно, метод должен выбросить исключение, а исходный объект не должен измениться.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# `StudentBuilder`
|
||||||
|
|
||||||
|
Обновить существующий `StudentBuilder` в соответствии с новой моделью `Student`.
|
||||||
|
|
||||||
|
Методы должны соответствовать типам:
|
||||||
|
|
||||||
|
```java
|
||||||
|
setGroupNumber(String groupNumber)
|
||||||
|
setAverageGrade(double averageGrade)
|
||||||
|
setRecordBookNumber(int recordBookNumber)
|
||||||
|
```
|
||||||
|
|
||||||
|
`build()` должен создавать только валидный `Student`.
|
||||||
|
|
||||||
|
Все новые правила валидации должны применяться при создании объекта через `StudentBuilder`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Обновление зависимого кода
|
||||||
|
|
||||||
|
Найти и обновить весь код, использующий `Student` и изменяемые методы или типы.
|
||||||
|
|
||||||
|
В частности, обновить:
|
||||||
|
|
||||||
|
- `Comparator<Student>`;
|
||||||
|
- сортировки;
|
||||||
|
- источники данных;
|
||||||
|
- тестовые классы;
|
||||||
|
- создание `Student`;
|
||||||
|
- `with...()`-операции;
|
||||||
|
- чтение и запись данных, если они используют `recordBookNumber`.
|
||||||
|
|
||||||
|
Например, существующий:
|
||||||
|
|
||||||
|
```java
|
||||||
|
implements Comparator<Student>
|
||||||
|
```
|
||||||
|
|
||||||
|
должен использовать актуальный метод:
|
||||||
|
|
||||||
|
```java
|
||||||
|
getAverageGrade()
|
||||||
|
```
|
||||||
|
|
||||||
|
вместо ошибочного `getAvarageGrade()`.
|
||||||
|
|
||||||
|
Компаратор `recordBookNumber` должен сравнивать значения как числа.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Integer.compare(
|
||||||
|
s1.getRecordBookNumber(),
|
||||||
|
s2.getRecordBookNumber()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Все места, где `recordBookNumber` рассматривался как `String`, необходимо адаптировать к `int`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Тесты
|
||||||
|
|
||||||
|
Обновить существующий `StudentTest` с учётом новой модели.
|
||||||
|
|
||||||
|
## Создание корректного `Student`
|
||||||
|
|
||||||
|
Проверить создание объекта со значениями:
|
||||||
|
|
||||||
|
```text
|
||||||
|
groupNumber = A12
|
||||||
|
averageGrade = 4.5
|
||||||
|
recordBookNumber = 12345
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить корректность всех геттеров.
|
||||||
|
|
||||||
|
## Проверка `groupNumber`
|
||||||
|
|
||||||
|
Проверить успешное создание для:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A12
|
||||||
|
B32
|
||||||
|
Z00
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить исключение для:
|
||||||
|
|
||||||
|
```text
|
||||||
|
a12
|
||||||
|
A1
|
||||||
|
A123
|
||||||
|
AA1
|
||||||
|
а1А
|
||||||
|
аа!!а123
|
||||||
|
23фи!12
|
||||||
|
№№###*()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Проверка `averageGrade`
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.0 — допустимо
|
||||||
|
5.0 — допустимо
|
||||||
|
-1.0 — недопустимо
|
||||||
|
6.0 — недопустимо
|
||||||
|
```
|
||||||
|
|
||||||
|
## Проверка `recordBookNumber`
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 — допустимо
|
||||||
|
12345 — допустимо
|
||||||
|
0 — недопустимо
|
||||||
|
-1 — недопустимо
|
||||||
|
```
|
||||||
|
|
||||||
|
## Тесты `with...()`
|
||||||
|
|
||||||
|
### `withGroupNumber()`
|
||||||
|
|
||||||
|
Проверить, что:
|
||||||
|
|
||||||
|
- возвращается новый объект;
|
||||||
|
- `groupNumber` изменён;
|
||||||
|
- `averageGrade` не изменился;
|
||||||
|
- `recordBookNumber` не изменился;
|
||||||
|
- исходный `Student` остался прежним.
|
||||||
|
|
||||||
|
### `withAverageGrade()`
|
||||||
|
|
||||||
|
Проверить те же свойства для `averageGrade`.
|
||||||
|
|
||||||
|
### `withRecordBookNumber()`
|
||||||
|
|
||||||
|
Проверить те же свойства для `recordBookNumber`.
|
||||||
|
|
||||||
|
### `with...()` с невалидным значением
|
||||||
|
|
||||||
|
Для каждого метода передать некорректное значение.
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- выбрасывается `IllegalArgumentException`;
|
||||||
|
- исходный объект не изменяется;
|
||||||
|
- не создаётся невалидный `Student`.
|
||||||
|
|
||||||
|
## `equals()` и `hashCode()`
|
||||||
|
|
||||||
|
Обновить существующие тесты с учётом `int recordBookNumber`.
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- одинаковые студенты равны;
|
||||||
|
- одинаковые студенты имеют одинаковый `hashCode`;
|
||||||
|
- изменение любого поля создаёт отличающийся объект.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Необходимо обновить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Student.java
|
||||||
|
StudentBuilder.java
|
||||||
|
StudentTest.java
|
||||||
|
```
|
||||||
|
|
||||||
|
а также **все классы проекта, зависящие от изменённого интерфейса `Student`**, включая компараторы и другие реализации, использующие `recordBookNumber`, `groupNumber`, `averageGrade` или их геттеры.
|
||||||
|
|
||||||
|
### Основной принцип
|
||||||
|
|
||||||
|
`Student` должен оставаться единственной точкой истины для валидности данных:
|
||||||
|
|
||||||
|
```text
|
||||||
|
StudentBuilder ──┐
|
||||||
|
├──> Student
|
||||||
|
with...() ───────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Ни `Builder`, ни `with...()`, ни внешние классы не должны позволять создать невалидный объект.
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# Общее техническое задание: тестирование
|
||||||
|
|
||||||
|
## Задача
|
||||||
|
|
||||||
|
Для всех реализованных классов написать автоматические тесты с использованием **JUnit**.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
1. Для каждого основного класса проекта должны быть написаны тесты.
|
||||||
|
|
||||||
|
2. Проверить:
|
||||||
|
|
||||||
|
- корректное выполнение основных методов;
|
||||||
|
- корректную обработку некорректных данных;
|
||||||
|
- граничные случаи;
|
||||||
|
- ожидаемые исключения.
|
||||||
|
|
||||||
|
3. Для `Student` проверить:
|
||||||
|
|
||||||
|
- создание корректного объекта;
|
||||||
|
- невозможность создания объекта с некорректными данными;
|
||||||
|
- работу `Builder`;
|
||||||
|
- `equals()` и `hashCode()`.
|
||||||
|
|
||||||
|
4. Для `MyLinkedList` проверить:
|
||||||
|
|
||||||
|
- `add()`;
|
||||||
|
- `get()`;
|
||||||
|
- `set()`;
|
||||||
|
- `remove()`;
|
||||||
|
- `size()`;
|
||||||
|
- `isEmpty()`;
|
||||||
|
- некорректные индексы.
|
||||||
|
|
||||||
|
5. Для `MergeSortStrategy` проверить:
|
||||||
|
|
||||||
|
- сортировку по каждому из трёх полей;
|
||||||
|
- уже отсортированную коллекцию;
|
||||||
|
- обратный порядок;
|
||||||
|
- коллекцию из одного элемента;
|
||||||
|
- пустую коллекцию.
|
||||||
|
|
||||||
|
6. Для каждого компаратора проверить правильное сравнение объектов `Student`.
|
||||||
|
|
||||||
|
7. Тесты должны быть независимыми друг от друга и воспроизводимыми.
|
||||||
|
|
||||||
|
8. Необходимо использовать JUnit и стандартные средства проверки результатов.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
Тесты размещать отдельно от основного кода, например:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
├── main/
|
||||||
|
│ └── java/
|
||||||
|
└── test/
|
||||||
|
└── java/
|
||||||
|
├── StudentTest.java
|
||||||
|
├── MyLinkedListTest.java
|
||||||
|
├── MergeSortStrategyTest.java
|
||||||
|
├── StudentGroupComparatorTest.java
|
||||||
|
├── StudentAverageGradeComparatorTest.java
|
||||||
|
└── StudentRecordBookComparatorTest.java
|
||||||
|
```
|
||||||
|
|
||||||
|
Все тесты должны успешно проходить перед слиянием ветки в `main`.
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# Работа с зависимостями между Git-ветками
|
||||||
|
|
||||||
|
## Сценарий
|
||||||
|
|
||||||
|
Предположим, мы находимся в ветке:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
В этой ветке реализуется функциональность, связанная со `Student`.
|
||||||
|
|
||||||
|
При этом у нас появляется новая, независимая по смыслу фича:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
feature/sorting
|
||||||
|
```
|
||||||
|
|
||||||
|
Для реализации сортировки нам необходим класс или интерфейс `Student`, который уже был создан в `feature/student`.
|
||||||
|
|
||||||
|
## Как создать новую ветку
|
||||||
|
|
||||||
|
Если нам необходимо продолжить разработку `feature/sorting`, используя текущее состояние `feature/student`, создаём новую ветку **от `feature/student`**.
|
||||||
|
|
||||||
|
Сначала переключаемся на `feature/student`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
Затем создаём новую ветку:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git branch feature/sorting
|
||||||
|
```
|
||||||
|
|
||||||
|
После этого история будет выглядеть примерно так:
|
||||||
|
|
||||||
|
```text
|
||||||
|
feature/sorting
|
||||||
|
/
|
||||||
|
---------------●
|
||||||
|
\
|
||||||
|
feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
Обе ветки на момент создания указывают на один и тот же коммит.
|
||||||
|
|
||||||
|
Теперь в `feature/sorting` доступен весь код, который был добавлен в `feature/student`, в том числе `Student`.
|
||||||
|
|
||||||
|
Чтобы перейти в новую ветку:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout feature/sorting
|
||||||
|
```
|
||||||
|
|
||||||
|
## Почему важно создавать ветку именно таким образом
|
||||||
|
|
||||||
|
В данном случае `feature/sorting` зависит от кода, находящегося в `feature/student`.
|
||||||
|
|
||||||
|
При этом сами фичи логически разные:
|
||||||
|
|
||||||
|
- `feature/student` — разработка `Student`;
|
||||||
|
- `feature/sorting` — реализация сортировки;
|
||||||
|
- `Student` нужен в `feature/sorting` как зависимость для реализации сортировки.
|
||||||
|
|
||||||
|
Поэтому не стоит продолжать разработку сортировки непосредственно в `feature/student`. Лучше создать отдельную ветку от того состояния, где необходимый код уже существует.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Если ветки уже разошлись
|
||||||
|
|
||||||
|
Иногда невозможно создать `feature/sorting` непосредственно от нужного места в истории `feature/student`.
|
||||||
|
|
||||||
|
Например, `feature/sorting` уже была создана раньше:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A---B---C---D feature/student
|
||||||
|
\
|
||||||
|
E---F---G feature/sorting
|
||||||
|
```
|
||||||
|
|
||||||
|
В таком случае можно получить изменения из `feature/student` посредством слияния (merge).
|
||||||
|
|
||||||
|
Находясь в `feature/sorting`, выполняем:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout feature/sorting
|
||||||
|
git merge feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
История станет примерно такой:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A---B---C---D feature/student
|
||||||
|
\ \
|
||||||
|
E---F---G---M feature/sorting
|
||||||
|
```
|
||||||
|
|
||||||
|
Коммит `M` — результат слияния двух веток.
|
||||||
|
|
||||||
|
После этого в `feature/sorting` будут доступны изменения из `feature/student`, в том числе необходимый `Student`.
|
||||||
|
|
||||||
|
## Главное правило
|
||||||
|
|
||||||
|
**Если новая фича зависит от кода из другой ветки, но при этом является самостоятельной по смыслу — создаём для неё отдельную ветку, используя нужную ветку как основу.**
|
||||||
|
|
||||||
|
Если новую ветку можно создать непосредственно от нужного состояния исходной ветки:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout feature/student
|
||||||
|
git branch feature/sorting
|
||||||
|
```
|
||||||
|
|
||||||
|
Если ветки уже разошлись и создать её от нужного состояния невозможно:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout feature/sorting
|
||||||
|
git merge feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
Таким образом, мы сохраняем логическое разделение фич и одновременно можем использовать необходимый код из другой ветки.
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
|||||||
|
# Памятка: как синхронизировать локальную ветку после `git push --force-with-lease`
|
||||||
|
|
||||||
|
Эта инструкция подходит для ситуации, когда удалённая ветка была переписана через `git push --force-with-lease`, и теперь локальная ветка расходится с `origin`.
|
||||||
|
|
||||||
|
## Если локальных изменений нет
|
||||||
|
|
||||||
|
Если у тебя **нет никаких локальных изменений, которые нужно сохранить**, это хорошо: можно безопасно заменить указатель локальной ветки на удалённую.
|
||||||
|
|
||||||
|
В таком случае ничего нужного не пропадёт.
|
||||||
|
|
||||||
|
### 1. Сделай `git fetch`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Посмотри, где расходятся ветки
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git log --oneline --decorate --color --graph --all
|
||||||
|
```
|
||||||
|
|
||||||
|
С помощью графа можно увидеть, где локальная и удалённая ветки расходятся в истории.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```text
|
||||||
|
feature/student
|
||||||
|
origin/feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
`feature/student` — локальная ветка, `origin/feature/student` — состояние этой ветки в удалённом репозитории.
|
||||||
|
|
||||||
|
### 3. Перемести указатель локальной ветки на удалённую
|
||||||
|
|
||||||
|
Сначала перейди на нужную локальную ветку:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
Затем:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git reset --hard origin/feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
После этого локальная `feature/student` будет указывать на тот же коммит, что и `origin/feature/student`.
|
||||||
|
|
||||||
|
### Весь сценарий целиком
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch
|
||||||
|
git log --oneline --decorate --color --graph --all
|
||||||
|
git checkout feature/student
|
||||||
|
git reset --hard origin/feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Если локальные изменения нужно сохранить
|
||||||
|
|
||||||
|
В этом случае **не нужно сразу делать `git reset --hard`**.
|
||||||
|
|
||||||
|
`reset --hard` изменит состояние рабочей директории и может удалить незакоммиченные изменения.
|
||||||
|
|
||||||
|
Сначала нужно сохранить свою работу.
|
||||||
|
|
||||||
|
## Вариант 1. Изменения уже закоммичены
|
||||||
|
|
||||||
|
Если твои изменения находятся в локальных коммитах, самый простой и надёжный вариант — сначала создать дополнительную ветку, которая будет указывать на текущее состояние:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout feature/student
|
||||||
|
git branch backup/feature-student
|
||||||
|
```
|
||||||
|
|
||||||
|
Теперь у тебя есть резервная ветка:
|
||||||
|
|
||||||
|
```text
|
||||||
|
backup/feature-student
|
||||||
|
↓
|
||||||
|
твои старые коммиты
|
||||||
|
|
||||||
|
feature/student
|
||||||
|
↓
|
||||||
|
твои старые коммиты
|
||||||
|
```
|
||||||
|
|
||||||
|
После этого можно безопаснее двигать `feature/student`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch
|
||||||
|
git reset --hard origin/feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
Резервная ветка `backup/feature-student` при этом никуда не исчезнет.
|
||||||
|
|
||||||
|
Если позже выяснится, что какие-то коммиты из старой истории нужны, их можно перенести в новую историю с помощью `git cherry-pick`.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git cherry-pick <commit>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Вариант 2. Есть незакоммиченные изменения
|
||||||
|
|
||||||
|
Если ты что-то изменил, но ещё не сделал `git commit`, сначала сохрани изменения.
|
||||||
|
|
||||||
|
Один из удобных вариантов — временно убрать их в `stash`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git stash push -u -m "backup before force-push recovery"
|
||||||
|
```
|
||||||
|
|
||||||
|
После этого рабочая директория станет чистой, и можно выполнять:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch
|
||||||
|
git checkout feature/student
|
||||||
|
git reset --hard origin/feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
Затем можно вернуть сохранённые изменения:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git stash pop
|
||||||
|
```
|
||||||
|
|
||||||
|
Git попытается применить их поверх новой истории.
|
||||||
|
|
||||||
|
Если возникнут конфликты, их нужно будет разрешить вручную.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Самое важное правило
|
||||||
|
|
||||||
|
Перед:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git reset --hard origin/feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
нужно ответить на вопрос:
|
||||||
|
|
||||||
|
> **Есть ли у меня локальные изменения, которые я не хочу потерять?**
|
||||||
|
|
||||||
|
### Если нет
|
||||||
|
|
||||||
|
Можно:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git reset --hard origin/feature/student
|
||||||
|
```
|
||||||
|
|
||||||
|
### Если да, но изменения закоммичены
|
||||||
|
|
||||||
|
Сначала создай резервную ветку:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git branch backup/feature-student
|
||||||
|
```
|
||||||
|
|
||||||
|
### Если да, но изменения ещё не закоммичены
|
||||||
|
|
||||||
|
Сначала используй `stash`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git stash push -u -m "backup before force-push recovery"
|
||||||
|
```
|
||||||
|
|
||||||
|
И только после этого выполняй `reset --hard`.
|
||||||
|
|
||||||
|
**Принцип простой: сначала сохранить то, что может понадобиться, потом двигать указатель ветки.**
|
||||||
Reference in New Issue
Block a user