diff --git a/app/src/main/java/ru/project/MyCollectors.java b/app/src/main/java/ru/project/MyCollectors.java new file mode 100644 index 0000000..20840c3 --- /dev/null +++ b/app/src/main/java/ru/project/MyCollectors.java @@ -0,0 +1,10 @@ +package ru.test.maven.spring; +import java.util.stream.Collector; +public class MyCollectors +{ + private MyCollectors() { + } + public static Collector, MyList> toMyList() { + return new MyListCollector<>(); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/MyListCollector.java b/app/src/main/java/ru/project/MyListCollector.java new file mode 100644 index 0000000..f731379 --- /dev/null +++ b/app/src/main/java/ru/project/MyListCollector.java @@ -0,0 +1,41 @@ +package ru.test.maven.spring; +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 implements Collector, MyList> +{ + @Override + public Supplier> supplier() + { + return () -> new MyLinkedList<>(); + } + @Override + public BiConsumer, T> accumulator() { + + return (list, element) -> list.add(element); + } + @Override + public BinaryOperator> combiner() { + return (list1, list2) -> { + for (int i = 0; i < list2.size(); i++) { + list1.add(list2.get(i)); + } + return list1; + }; + } + @Override + public Function, MyList> finisher() + { + return list -> list; + } + @Override + public Set characteristics() { + return java.util.Set.of( + Collector.Characteristics.IDENTITY_FINISH + ); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/collectors/MyCollectors.java b/app/src/main/java/ru/project/collectors/MyCollectors.java new file mode 100644 index 0000000..d0d7cf5 --- /dev/null +++ b/app/src/main/java/ru/project/collectors/MyCollectors.java @@ -0,0 +1,15 @@ +package ru.project.collectors; +import java.util.stream.Collector; + +public class MyCollectors +{ + private MyCollectors() { + + } + + public static Collector, MyList> toMyList() + { + return new MyListCollector<>(); + + } +} diff --git a/app/src/main/java/ru/project/collectors/MyListCollector.java b/app/src/main/java/ru/project/collectors/MyListCollector.java new file mode 100644 index 0000000..bb168e4 --- /dev/null +++ b/app/src/main/java/ru/project/collectors/MyListCollector.java @@ -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 implements Collector, MyList> +{ + @Override + public Supplier> supplier() + { + return () -> new MyLinkedList<>(); + } + + @Override + public BiConsumer, T> accumulator() { + + return (list, element) -> list.add(element); + } + + @Override + public BinaryOperator> combiner() { + return (list1, list2) -> { + list1.addAll(list2); + return list1; + }; + } + + @Override + public Function, MyList> finisher() + { + return list -> list; + } + + @Override + public Set characteristics() { + return java.util.Set.of( + Collector.Characteristics.IDENTITY_FINISH + ); + } +} diff --git a/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java b/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java new file mode 100644 index 0000000..7e7c3d2 --- /dev/null +++ b/app/src/main/java/ru/project/finder/StudentOccurrenceIndexFinder.java @@ -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 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>> 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> future = executor.submit(() -> { + return findInRange(students, target, start, end); + }); + futures.add(future); + } + MyList allIndices = new MyLinkedList<>(); + try { + for (int i = 0; i < futures.size(); i++) { + Future> future = futures.get(i); + MyList 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 list) { + if (list.size() <= 1) return; + + MyList 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 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 findInRange(MyList students, Student target, int fromIndex, int toIndex) { + MyList 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; + } +} diff --git a/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java b/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java new file mode 100644 index 0000000..a53a7c5 --- /dev/null +++ b/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java b/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java new file mode 100644 index 0000000..063d087 --- /dev/null +++ b/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java @@ -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 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; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java b/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java new file mode 100644 index 0000000..61ea406 --- /dev/null +++ b/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java b/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java new file mode 100644 index 0000000..41131b4 --- /dev/null +++ b/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/SearchOperation.java b/app/src/main/java/ru/project/search/SearchOperation.java new file mode 100644 index 0000000..d616f97 --- /dev/null +++ b/app/src/main/java/ru/project/search/SearchOperation.java @@ -0,0 +1,6 @@ +package ru.project.search; + +public enum SearchOperation { + UNION, + INTERSECTION +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/StudentSearchCriteria.java b/app/src/main/java/ru/project/search/StudentSearchCriteria.java new file mode 100644 index 0000000..f157982 --- /dev/null +++ b/app/src/main/java/ru/project/search/StudentSearchCriteria.java @@ -0,0 +1,7 @@ +package ru.project.search; + +import ru.project.student.Student; + +public interface StudentSearchCriteria { + boolean matches(Student student); +} \ No newline at end of file diff --git a/app/src/main/java/ru/project/search/StudentSearchService.java b/app/src/main/java/ru/project/search/StudentSearchService.java new file mode 100644 index 0000000..4ced3c9 --- /dev/null +++ b/app/src/main/java/ru/project/search/StudentSearchService.java @@ -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 find(MyList students) { + if (students == null) { + return new MyLinkedList<>(); + } + MyList 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; + } +} \ No newline at end of file diff --git a/app/src/test/java/ru/project/MyListCollectorTest.java b/app/src/test/java/ru/project/MyListCollectorTest.java new file mode 100644 index 0000000..6155d57 --- /dev/null +++ b/app/src/test/java/ru/project/MyListCollectorTest.java @@ -0,0 +1,63 @@ +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 collector = new MyListCollector<>(); + + @Test + void shouldTestCollectInOrderSequential() { + MyList 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 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 result = IntStream.empty() + .boxed() + .collect(MyCollectors.toMyList()); + + assertTrue(result.isEmpty()); + + } + @Test + void shouldTestNotUseStandardList() { + MyList 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 characteristics = collector.characteristics(); + assertTrue(characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH)); + assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED)); + } + +} diff --git a/app/src/test/java/ru/project/collectors/MyListCollectorTest.java b/app/src/test/java/ru/project/collectors/MyListCollectorTest.java new file mode 100644 index 0000000..3ca8d21 --- /dev/null +++ b/app/src/test/java/ru/project/collectors/MyListCollectorTest.java @@ -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 collector = new MyListCollector<>(); + + @Test + void shouldTestCollectInOrderSequential() { + MyList 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 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 result = IntStream.empty() + .boxed() + .collect(MyCollectors.toMyList()); + + assertTrue(result.isEmpty()); + + } + @Test + void shouldTestNotUseStandardList() { + MyList 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 characteristics = collector.characteristics(); + assertTrue(characteristics.contains(java.util.stream.Collector.Characteristics.IDENTITY_FINISH)); + assertFalse(characteristics.contains(java.util.stream.Collector.Characteristics.UNORDERED)); + } + +} diff --git a/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java b/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java new file mode 100644 index 0000000..b61ed58 --- /dev/null +++ b/app/src/test/java/ru/project/finder/StudentOccurrenceIndexFinderTest.java @@ -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 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], "Результат должен быть отсортирован"); + } + } +} diff --git a/app/src/test/java/ru/project/search/StudentSearchTest.java b/app/src/test/java/ru/project/search/StudentSearchTest.java new file mode 100644 index 0000000..d12bd9c --- /dev/null +++ b/app/src/test/java/ru/project/search/StudentSearchTest.java @@ -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 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 result = service.find(students); + + assertEquals(2, result.size()); + assertEquals("A10", result.get(0).getGroupNumber()); + assertEquals("C30", result.get(1).getGroupNumber()); + } + + @Test + void testSearchServiceEmptyResult() { + MyList 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 result = service.find(students); + + assertEquals(0, result.size()); + } + + @Test + void testSearchServiceEmptyCollection() { + MyList students = new MyLinkedList<>(); + StudentSearchCriteria criteria = new AverageGradeRangeCriteria(4.0, 5.0); + StudentSearchService service = new StudentSearchService(criteria); + MyList result = service.find(students); + assertEquals(0, result.size()); + } + + @Test + void testSearchServicePreservesOrder() { + MyList 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 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 original = new MyLinkedList<>(); + original.add(new Student("A10", 4.5, 1)); + original.add(new Student("B20", 3.8, 2)); + + MyList 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 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 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 result = service.find(null); + assertNotNull(result); + assertEquals(0, result.size()); + } +} \ No newline at end of file diff --git a/docs/tasks/RandomStudentGenerator.md b/docs/tasks/RandomStudentGenerator.md new file mode 100644 index 0000000..3e902f9 --- /dev/null +++ b/docs/tasks/RandomStudentGenerator.md @@ -0,0 +1,305 @@ +# Техническое задание: генератор случайных студентов + +## Задача + +Реализовать класс для формирования `MyList` со случайными значениями. + +По умолчанию ограничения диапазона для генерации не задаются. В таком случае значения должны генерироваться во всём допустимом диапазоне, установленном правилами класса `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 generate(int size); +``` + +Контракт: + +- создаёт ровно `size` объектов `Student`; +- все объекты должны быть валидными; +- каждый объект создаётся через `StudentBuilder`; +- результат возвращается как `MyList`; +- стандартные `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`, в генераторе не реализовывать. + +## Примеры использования + +Без настройки диапазонов: + +```java id="s2k8m4" +RandomStudentGenerator generator = + new RandomStudentGenerator(); + +MyList 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 students = + generator.generate(100); +``` + +## Тесты + +### Генерация без ограничений + +```java id="r1w5p8" +MyList 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`. + +### Некорректный размер + +Для отрицательного размера: + +```java id="n2k7p4" +generate(-1) +``` + +ожидается `IllegalArgumentException`. + +## Результат + +Необходимо реализовать: + +```text id="v8m3q6" +RandomStudentGenerator.java +``` + +Класс должен использовать существующие: + +```text id="f1j9x4" +Student.java +StudentBuilder.java +MyList.java +``` + +Изменять интерфейсы `Student`, `StudentBuilder` и `MyList` для выполнения данного задания не требуется.