Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Решение тестового задания #22

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Project exclude paths
target/
.idea/
META-INF/
out/
*.iws
*.iml
*.ipr
74 changes: 73 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,79 @@ a30b4d51-11b4-49b2-b356-466e92a66df7 Иванов Иван Иванович 16.0
Примеры входного и выходного файлов приложены к настоящему техническому заданию.

## Автор решения

#### [Грачева Алёна Валерьевна](https://t.me/grachevalenaa)
## Описание реализации
Для выполнения данного задания было написано 4 класса.

### ru.croc.school_test.model.Employee
Класс Employee используется для хранения состояния сотрудника после чтения файла и корректного вывода в файл. Информация о UUID и датах списания в самом классе не использовалась, так как не нужна для вывода в файл.

|Поле|Тип| Описание |
|:---|:--|:-------------------------------------------------------------------------------|
|lastName| `String` | Фамилия |
|firstName| `String` | Имя |
|nameByFather| `String` | Отчество |
|hoursThisWeek| `double` | Суммарное количество списанных часов данного сотрудника |
|hoursImbalance| `double` | Дисбаланс списанных часов по сравнению с недельной нормой у данного сотрудника |

**Методы класса** (помимо конструктора, геттеров и сеттеров):

* **toString()**: Метод возвращает строку вида <Фамилия И.О.> <количество часов, составляющих дисбаланс, с указанием знака>.
* **increaseAmountOfHours(double hours)**: Метод увеличивает количество списанных за неделю часов у сотрудника.

### ru.croc.school_test.file_handler.FileHandler
Класс FileHandler используется для обработки файлов, включая чтение данных сотрудников из файла и запись обработанных данных обратно в файл.

|Поле|Тип| Описание |
|:---|:--|:------------------------------------------------------|
|filePathToRead| `String` | Путь к входному файлу для чтения исходных данных |
|filePathToWrite| `String` | Путь к выходному файлу для записи обработанных данных |

**Методы класса:** (помимо сеттеров)

* **readFile()**: Метод устанавливает недельную норму часов и читает данные сотрудников из файла, указанного в filePathToRead. Он парсит каждую строку для извлечения данных о сотрудниках и сохраняет их в мапу, где ключом является UUID, а значением - объект класса Employee. Если UUID сотрудника уже есть в мапе, то увеличиваются его списанные часы. В конце метод возвращает список уникальных сотрудников.
* **writeFile(List<Employee> list)**: Метод принимает в качестве параметра список сотрудников, вызывает метод сортировки и записывает результат в файл, указанный в filePathToWrite.

### ru.croc.school_test.data_analyzer.DataAnalyzer
Класс DataAnalyzer используется для анализа рабочего времени сотрудников: определения дисбаланса рабочих часов по сравнению с недельной нормой и сортировки сотрудников согласно техническому заданию.

|Поле|Тип|Описание|
|:---|:--|:-------|
|idealAmountOfHours| `int` |Недельная норма рабочих часов, рассчитываемая из предположения 8-часового рабочего дня|
|IMBALANCE_PORTION| `double` |Пороговй дисбаланс рабочих часов, который равен 0.1 по ТЗ|

**Методы класса:** (помимо сеттера)

* **analyzeAmountOfHours(List<Employee> employees)**: Метод анализирует список сотрудников и возвращает список тех, чьи рабочие часы отличаются от недельной нормы строго больше 10%. Метод рассчитывает дисбаланс рабочих часов каждого сотрудника и возвращает тех, кто превышает установленный порог.
* **sortEmployees(List<Employee> employees)**: Метод сортирует поданный в качестве параметра список сотрудников по следующему принципу: сначала в списке располагаются сотрудники с отрицательным дисбалансом в алфавитном порядке, после них - сотрудники с положительным дисбалансом в алфавитном порядке. Алфавитный порядок работает по принципу "слово за словом": по первой букве фамилии, при совпадении первой буквы - по второй букве и так далее, при совпадении фамилии упорядочивание происходит по имени и так далее. При полном совпадении слов также влияние оказывает длина строки (Петров раньше, чем Петрова).

### ru.croc.school_test.Main
Класс Main является точкой входа в программу. Он координирует чтение данных о сотрудниках, их анализ и последующую запись обработанных данных.

**Метод класса:**

* **pulbic static void main(String[] args)** Главный метод приложения, который выполняет следующие шаги:
* Считывает пути к файлам для чтения и записи из аргументов командной строки.
* Устанавливает пути файлов в FileHandler.
* Вызывает метод чтения данных класса FileHandler.
* Вызывает метод класса DataAnalyzer для анализа рабочих часов сотрудников на предмет дисбаланса более 10%.
* Вызывает метод записи обработанных данных о сотрудниках в файл класса FileHandler.
## Инструкция по сборке и запуску решения
Вам потребуется java не ниже 17 версии.
Для запуска вы можете скачать [school-test.jar](https://github.com/grachevalenaa/school2024-test-task6/blob/master/school-test.jar), который располагается по адресу:
```bash
https://github.com/grachevalenaa/school2024-test-task6/blob/master/school-test.jar
```
Затем вам необходимо, перейти в папку с jar архивом, поместить в эту папку входной файл с данными и выходной файл для вывода результата (необязательно пустой) и прописать следующую команду:
```bash
java -jar school-test.jar <входной файл> <выходной файл>
```
Например, так:
```bash
java -jar school-test.jar report.txt result.txt
```
Можно использовать абсолютные пути:
```bash
java -jar /your/directrory/school-test.jar /another/directory/report.txt /another/directory/result.txt
```
Один и тот же файл можно использовать в качестве выходного для проверки разных входных тестовых данных.
17 changes: 17 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>ru.croc.school-test</groupId>
<artifactId>school-test</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

</project>
13 changes: 0 additions & 13 deletions report.txt

This file was deleted.

2 changes: 0 additions & 2 deletions result.txt

This file was deleted.

Binary file added school-test.jar
Binary file not shown.
31 changes: 31 additions & 0 deletions src/main/java/ru/croc/school_test/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ru.croc.school_test;

import ru.croc.school_test.data_analyzer.DataAnalyzer;
import ru.croc.school_test.file_handler.FileHandler;
import ru.croc.school_test.model.Employee;

import java.io.IOException;
import java.util.List;

public class Main {

public static void main(String[] args) throws IOException {
// отдельно считываем пути к файлам с командной строки (для читаемости кода)
String inputFileName = args[0];
String outputFileName = args[1];

FileHandler.setFilePathToRead(inputFileName);
FileHandler.setFilePathToWrite(outputFileName);

// чтение файла
List<Employee> employees = FileHandler.readFile();

// получение списка сотрудников с дисбалансом более 10%
List<Employee> newEmployees = DataAnalyzer.analyzeAmountOfHours(employees);

// запись в файл
FileHandler.writeFile(newEmployees);

}

}
51 changes: 51 additions & 0 deletions src/main/java/ru/croc/school_test/data_analyzer/DataAnalyzer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ru.croc.school_test.data_analyzer;

import ru.croc.school_test.model.Employee;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

public class DataAnalyzer {

private static int idealAmountOfHours; // недельная норма списания, составляется из расчета 8-часового дня по ТЗ,
// поэтому тип был выбран не в пользу float/double

private static final double IMBALANCE_PORTION = 0.1; // часть дисбаланса (10% по ТЗ)

public static List<Employee> analyzeAmountOfHours(List<Employee> employees) {
double limitHoursImbalance = idealAmountOfHours * IMBALANCE_PORTION;

List<Employee> differentFromIdeal = new ArrayList<>();
for (Employee employee: employees) {
employee.setHoursImbalance(employee.getHoursThisWeek() - idealAmountOfHours);
// так как дисбаланс должен составлять "более 10%" для вывода в файл
// (а не "не менее 10%"), считаем знак строгим
if (Math.abs(employee.getHoursImbalance()) > limitHoursImbalance) {
differentFromIdeal.add(employee);
}
}

return differentFromIdeal;
}

public static void sortEmployees(List<Employee> employees) {
Collator collator = Collator.getInstance(new Locale("ru", "RU"));
// Collator.SECONDARY позволяет различать буквы е, ё
collator.setStrength(Collator.SECONDARY);

// сначала производится сортировка по дисбалансу (согласно ТЗ у отрицательного дисбаланса наивысший приоритет)
// далее список сортируется по ФИО отдельно в каждой группе по знаку дисбаланса
employees.sort(Comparator
.comparing((Employee e) -> e.getHoursImbalance() >= 0)
.thenComparing(Employee::getLastName, collator)
.thenComparing(Employee::getFirstName, collator)
.thenComparing(Employee::getNameByFather, collator));
}

public static void setIdealAmountOfHours(int idealAmountOfHours) {
DataAnalyzer.idealAmountOfHours = idealAmountOfHours;
}
}
74 changes: 74 additions & 0 deletions src/main/java/ru/croc/school_test/file_handler/FileHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package ru.croc.school_test.file_handler;

import ru.croc.school_test.data_analyzer.DataAnalyzer;
import ru.croc.school_test.model.Employee;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

public class FileHandler {

private static String filePathToRead;

private static String filePathToWrite;

public static List<Employee> readFile() throws IOException {
Path path = Paths.get(filePathToRead);
// будем запоминать уникальных сотрудников по UUID в мапе
Map<String, Employee> employeeMapByUUID = new HashMap<>();

try (Scanner scanner = new Scanner(path)) {
// запоминаем недельную норму списания часов
DataAnalyzer.setIdealAmountOfHours(Integer.parseInt(scanner.nextLine()));
while (scanner.hasNext()) {
String line = scanner.nextLine();
String[] employeeInfo = line.split(" ");
String UUID = employeeInfo[0]; // переменная для читаемости
// если UUID нет в мапе, то нужно сохранить нового сотрудника
if (!employeeMapByUUID.containsKey(UUID)) {
// так как UUID и дата списания больше не будут нужны в рамках данной задачи,
// то в сущности Employee они не используются
employeeMapByUUID.put(UUID, new Employee(
employeeInfo[1], // фамилия
employeeInfo[2], // имя
employeeInfo[3], // отчество
Double.parseDouble(employeeInfo[5]))); // его первое списание
} else {
// иначе - сотрудник уже был создан, нужно лишь увеличить количество проработанных часов
employeeMapByUUID.get(employeeInfo[0]).
increaseAmountOfHours(Double.parseDouble(employeeInfo[5]));
}
}
}

// возвращаем список уникальных сотрудников из мапы
return new ArrayList<>(employeeMapByUUID.values());
}

public static void writeFile(List<Employee> list) throws IOException {
List<Employee> employeesOutput = DataAnalyzer.analyzeAmountOfHours(list);

try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePathToWrite))) {
// перед вывоводом сотрудников с дисбалансом сортируем их согласно ТЗ
DataAnalyzer.sortEmployees(employeesOutput);

for (Employee employee : employeesOutput) {
// метод toString переопределен согласно формату вывода, обозначенному в ТЗ
writer.write(employee.toString());
writer.append('\n');
}
}
}

public static void setFilePathToRead(String filePathToRead) {
FileHandler.filePathToRead = filePathToRead;
}

public static void setFilePathToWrite(String filePathToWrite) {
FileHandler.filePathToWrite = filePathToWrite;
}
}
Loading