diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..71c5d0d
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,26 @@
+name: Java CI with Gradle
+
+on:
+ push:
+ branches: [ master2 ]
+ pull_request:
+ branches: [ master2 ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4 # Checks out your repository under $GITHUB_WORKSPACE
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin' # Or 'adopt', 'zulu', etc.
+ cache: 'gradle' # Caches Gradle dependencies
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+ - name: Build with Gradle
+ run: ./gradlew build # This will also run the 'test' task by default
+ - name: Run Unit Tests
+ run: ./gradlew test # Explicitly run the test task if 'build' doesn't cover it
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 6674195..3681a76 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,6 +1,7 @@
plugins {
id("java")
id("application")
+ id("jacoco")
}
group = "ru.urfu"
@@ -13,10 +14,26 @@ repositories {
dependencies {
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
+ testImplementation("org.mockito:mockito-core:5.+")
+ implementation("org.jfree:jfreechart:1.5.6")
+}
+
+jacoco {
+ toolVersion = "0.8.12"
}
tasks.test {
useJUnitPlatform()
+ finalizedBy(tasks.jacocoTestReport)
+}
+
+tasks.jacocoTestReport {
+ dependsOn(tasks.test)
+ reports {
+ xml.required = false
+ csv.required = false
+ html.outputLocation = layout.buildDirectory.dir("jacocoHtml")
+ }
}
application {
diff --git a/src/main/java/ru/urfu/Main.java b/src/main/java/ru/urfu/Main.java
index 5845f6d..fc357de 100644
--- a/src/main/java/ru/urfu/Main.java
+++ b/src/main/java/ru/urfu/Main.java
@@ -1,17 +1,29 @@
package ru.urfu;
-//TIP To Run code, press or
-// click the icon in the gutter.
+import ru.urfu.chart.ChartDrawer;
+import ru.urfu.chart.ChartMapper;
+import ru.urfu.model.Player;
+import ru.urfu.parser.CsvParser;
+import ru.urfu.resolver.Resolver;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Random;
+import java.util.function.Function;
+import java.util.function.ToIntFunction;
+
public class Main {
public static void main(String[] args) {
//TIP Press with your caret at the highlighted text
// to see how GIGA IDE suggests fixing it.
- System.out.printf("Hello and welcome!");
+ var parser = new CsvParser("/Users/denis/IdeaProjects/Java2025_2/players.csv");
+
+ Resolver resolver = new Resolver(parser);
+
+ System.out.println(resolver.getTeams());
- for (int i = 1; i <= 5; i++) {
- //TIP Press to start debugging your code. We have set one breakpoint
- // for you, but you can always add more by pressing .
- System.out.println("i = " + i);
- }
+ var chartData = ChartMapper.mapDataToChart(parser.parseCsvToList());
+ ChartDrawer.showChart(chartData);
}
}
\ No newline at end of file
diff --git a/src/main/java/ru/urfu/chart/ChartDrawer.java b/src/main/java/ru/urfu/chart/ChartDrawer.java
new file mode 100644
index 0000000..f3add52
--- /dev/null
+++ b/src/main/java/ru/urfu/chart/ChartDrawer.java
@@ -0,0 +1,22 @@
+package ru.urfu.chart;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartFrame;
+import org.jfree.chart.JFreeChart;
+import org.jfree.data.xy.XYSeriesCollection;
+
+public class ChartDrawer {
+ public static void showChart(XYSeriesCollection dataset) {
+ JFreeChart chart = ChartFactory.createXYBarChart(
+ "Среднее количество голов по позициям",
+ "Позиция",
+ false,
+ "Среднее кол-во голов",
+ dataset
+ );
+
+ ChartFrame frame = new ChartFrame("Среднее число голов по позиции", chart);
+ frame.pack();
+ frame.setVisible(true);
+ }
+}
diff --git a/src/main/java/ru/urfu/chart/ChartMapper.java b/src/main/java/ru/urfu/chart/ChartMapper.java
new file mode 100644
index 0000000..56d3725
--- /dev/null
+++ b/src/main/java/ru/urfu/chart/ChartMapper.java
@@ -0,0 +1,43 @@
+package ru.urfu.chart;
+
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import ru.urfu.model.Player;
+import ru.urfu.model.Position;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ChartMapper {
+ public static XYSeriesCollection mapDataToChart(List players) {
+ Map> positionToGoals = new HashMap<>();
+
+ for (Player player : players) {
+ Position position = player.position();
+ int goals = player.goals();
+
+ positionToGoals.computeIfAbsent(position, k -> new ArrayList<>()).add(goals);
+ }
+
+ XYSeriesCollection dataset = new XYSeriesCollection();
+
+ int seriesIndex = 0;
+ for (Map.Entry> entry : positionToGoals.entrySet()) {
+ Position position = entry.getKey();
+ List cardsList = entry.getValue();
+
+ double average = cardsList.stream()
+ .mapToInt(Integer::intValue)
+ .average()
+ .orElse(0.0);
+
+ XYSeries series = new XYSeries(position);
+ series.add(seriesIndex++, average);
+ dataset.addSeries(series);
+ }
+
+ return dataset;
+ }
+}
diff --git a/src/main/java/ru/urfu/model/Player.java b/src/main/java/ru/urfu/model/Player.java
new file mode 100644
index 0000000..eda6786
--- /dev/null
+++ b/src/main/java/ru/urfu/model/Player.java
@@ -0,0 +1,8 @@
+package ru.urfu.model;
+
+public record Player(
+ String name,
+ Position position,
+ String agency,
+ int goals
+) { }
diff --git a/src/main/java/ru/urfu/model/Position.java b/src/main/java/ru/urfu/model/Position.java
new file mode 100644
index 0000000..0df3c0e
--- /dev/null
+++ b/src/main/java/ru/urfu/model/Position.java
@@ -0,0 +1,8 @@
+package ru.urfu.model;
+
+public enum Position {
+ GOALKEEPER,
+ DEFENDER,
+ MIDFIELD,
+ FORWARD
+}
diff --git a/src/main/java/ru/urfu/parser/CsvParser.java b/src/main/java/ru/urfu/parser/CsvParser.java
new file mode 100644
index 0000000..3ca7cbe
--- /dev/null
+++ b/src/main/java/ru/urfu/parser/CsvParser.java
@@ -0,0 +1,39 @@
+package ru.urfu.parser;
+
+import ru.urfu.model.Player;
+import ru.urfu.model.Position;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+
+public class CsvParser {
+ private final String path;
+
+ public CsvParser(String path) {
+ this.path = path;
+ }
+
+ public List parseCsvToList() {
+ try {
+ return Files.readAllLines(Paths.get(path))
+ .stream()
+ .skip(1)
+ .map(CsvParser::parsePlayerRow)
+ .toList();
+ } catch (IOException e) {
+ return List.of();
+ }
+ }
+
+ private static Player parsePlayerRow(String row) {
+ var cells = row.split(";");
+ return new Player(
+ cells[0],
+ Position.valueOf(cells[3]),
+ cells[5],
+ Integer.parseInt(cells[8])
+ );
+ }
+}
diff --git a/src/main/java/ru/urfu/resolver/IResolver.java b/src/main/java/ru/urfu/resolver/IResolver.java
index c806b3a..f158d30 100644
--- a/src/main/java/ru/urfu/resolver/IResolver.java
+++ b/src/main/java/ru/urfu/resolver/IResolver.java
@@ -1,15 +1,36 @@
package ru.urfu.resolver;
+import ru.urfu.model.Player;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
public interface IResolver {
+
+ List getPlayers();
+
// Выведите количество игроков, интересы которых не представляет агентство.
int getCountWithoutAgency();
- // Выведите максимальное число голов, забитых защинтником.
+ // Выведите максимальное число голов, забитых защитником.
int getMaxDefenderGoalsCount();
// Выведите русское название позиции самого дорогого немецкого игрока.
String getTheExpensiveGermanPlayerPosition();
+ // Верните имена игроков, сгруппированных по позициям, на которых они играют.
+ Map getPlayersByPosition();
+
+ // Верните множество команд, которые представлены в чемпионате.
+ Set getTeams();
+
+ // Верните топ-5 команд по количеству забитых мячей, и количество этих мячей.
+ Map getTop5TeamsByGoalsCount();
+
+ // Верните агентство, сумма игроков которого наименьшая.
+ String getAgencyWithMinPlayersCount();
+
// Выберите команду с наибольшим средним числом удалений на одного игрока.
String getTheRudestTeam();
}
diff --git a/src/main/java/ru/urfu/resolver/Resolver.java b/src/main/java/ru/urfu/resolver/Resolver.java
new file mode 100644
index 0000000..898c042
--- /dev/null
+++ b/src/main/java/ru/urfu/resolver/Resolver.java
@@ -0,0 +1,72 @@
+package ru.urfu.resolver;
+
+import ru.urfu.model.Player;
+import ru.urfu.model.Position;
+import ru.urfu.parser.CsvParser;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class Resolver implements IResolver {
+ private final List players;
+
+ public Resolver(CsvParser parser) {
+ this.players = parser.parseCsvToList();
+ }
+
+ @Override
+ public List getPlayers() {
+ return players;
+ }
+
+ @Override
+ public int getCountWithoutAgency() {
+ return (int) players.stream()
+ .filter(player -> player.agency() == null || player.agency().isEmpty())
+ .count();
+ }
+
+ @Override
+ public int getMaxDefenderGoalsCount() {
+ return players.stream()
+ .filter(player -> player.position() == Position.DEFENDER)
+ .mapToInt(Player::goals)
+ .max()
+ .orElse(0);
+ }
+
+ @Override
+ public String getTheExpensiveGermanPlayerPosition() {
+ return "";
+ }
+
+ @Override
+ public Map getPlayersByPosition() {
+ return Map.of();
+ }
+
+ @Override
+ public Set getTeams() {
+ return players.stream()
+ .map(Player::agency)
+ .filter(agency -> agency != null && !agency.isEmpty())
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Map getTop5TeamsByGoalsCount() {
+ return Map.of();
+ }
+
+ @Override
+ public String getAgencyWithMinPlayersCount() {
+ return "";
+ }
+
+ @Override
+ public String getTheRudestTeam() {
+ return "";
+ }
+}
diff --git a/src/test/java/ru/urfu/resolver/ResolverTest.java b/src/test/java/ru/urfu/resolver/ResolverTest.java
new file mode 100644
index 0000000..ca953b3
--- /dev/null
+++ b/src/test/java/ru/urfu/resolver/ResolverTest.java
@@ -0,0 +1,185 @@
+package ru.urfu.resolver;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import ru.urfu.model.Player;
+import ru.urfu.model.Position;
+import ru.urfu.parser.CsvParser;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ResolverTest {
+ private static final CsvParser mockParser = mock(CsvParser.class);
+
+ @Test
+ public void testOneDefender() {
+ when(mockParser.parseCsvToList())
+ .thenReturn(List.of(new Player("Иванов Иван", Position.DEFENDER, "", 10)));
+
+ var resolver = new Resolver(mockParser);
+
+ assertEquals(10, resolver.getMaxDefenderGoalsCount());
+ }
+
+ @Test
+ public void testNoDefender() {
+ when(mockParser.parseCsvToList())
+ .thenReturn(List.of(new Player("Иванов Иван", Position.FORWARD, "", 10)));
+
+ var resolver = new Resolver(mockParser);
+
+ assertEquals(0, resolver.getMaxDefenderGoalsCount());
+ }
+
+ @Test
+ public void testNoPlayers() {
+ when(mockParser.parseCsvToList())
+ .thenReturn(List.of());
+
+ var resolver = new Resolver(mockParser);
+
+ assertEquals(0, resolver.getMaxDefenderGoalsCount());
+ }
+
+ @Test
+ void testGetCountWithoutAgency_ZeroPlayers() {
+ when(mockParser.parseCsvToList()).thenReturn(List.of());
+
+ var resolver = new Resolver(mockParser);
+ int count = resolver.getCountWithoutAgency();
+
+ assertEquals(0, count);
+ }
+
+
+ @Test
+ void testGetCountWithoutAgency_AllHaveAgency() {
+ List players = List.of(
+ new Player("Иван", Position.FORWARD, "Агентство1", 1),
+ new Player("Петр", Position.DEFENDER, "Агентство2", 2)
+ );
+ when(mockParser.parseCsvToList()).thenReturn(players);
+
+ var resolver = new Resolver(mockParser);
+ int count = resolver.getCountWithoutAgency();
+
+ assertEquals(0, count);
+ }
+
+ @Test
+ void testGetCountWithoutAgency_SomeWithoutAgency() {
+ List players = List.of(
+ new Player("Иван", Position.FORWARD, null, 1),
+ new Player("Петр", Position.DEFENDER, "", 2),
+ new Player("Сергей", Position.MIDFIELD, "Агентство", 3)
+ );
+ when(mockParser.parseCsvToList()).thenReturn(players);
+
+ var resolver = new Resolver(mockParser);
+ int count = resolver.getCountWithoutAgency();
+
+ assertEquals(2, count); // Иван (null) + Петр (пустая строка)
+ }
+
+ @Test
+ void testGetCountWithoutAgency_AllWithoutAgency() {
+ List players = List.of(
+ new Player("Иван", Position.FORWARD, null, 1),
+ new Player("Петр", Position.DEFENDER, "", 2)
+ );
+ when(mockParser.parseCsvToList()).thenReturn(players);
+
+ var resolver = new Resolver(mockParser);
+ int count = resolver.getCountWithoutAgency();
+
+ assertEquals(2, count);
+ }
+
+ @Test
+ void testGetCountWithoutAgency_MixedNullAndEmpty() {
+ List players = List.of(
+ new Player("Иван", Position.FORWARD, null, 1),
+ new Player("Петр", Position.DEFENDER, "", 2),
+ new Player("Сергей", Position.MIDFIELD, " ", 3), // пробелы — не пусто!
+ new Player("Анна", Position.FORWARD, "Агентство", 4)
+ );
+ when(mockParser.parseCsvToList()).thenReturn(players);
+
+ var resolver = new Resolver(mockParser);
+ int count = resolver.getCountWithoutAgency();
+
+ assertEquals(2, count);
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideTestData")
+ void testGetTeams(List players, Set expected) {
+ when(mockParser.parseCsvToList()).thenReturn(players);
+
+ var resolver = new Resolver(mockParser);
+ Set result = resolver.getTeams();
+
+ assertEquals(expected, result);
+ }
+
+ private static Stream provideTestData() {
+ return Stream.of(
+ // Сценарий 1: пустой список игроков
+ Arguments.of(
+ List.of(),
+ Set.of()
+ ),
+
+ // Сценарий 2: один игрок — одна команда
+ Arguments.of(
+ List.of(new Player("Иван", Position.FORWARD, "Barcelona", 1)),
+ Set.of("Barcelona")
+ ),
+
+ // Сценарий 3: несколько игроков одной команды
+ Arguments.of(
+ List.of(
+ new Player("Иван", Position.FORWARD, "Barcelona", 1),
+ new Player("Петр", Position.DEFENDER, "Barcelona", 2)
+ ),
+ Set.of("Barcelona")
+ ),
+
+ // Сценарий 4: игроки из разных команд
+ Arguments.of(
+ List.of(
+ new Player("Иван", Position.FORWARD, "Barcelona", 1),
+ new Player("Петр", Position.DEFENDER, "Real Madrid", 2),
+ new Player("Сергей", Position.MIDFIELD, "Barcelona", 3)
+ ),
+ Set.of("Barcelona", "Real Madrid")
+ ),
+
+ // Сценарий 5: один игрок без агентства (null)
+ Arguments.of(
+ List.of(
+ new Player("Иван", Position.FORWARD, null, 1)
+ ),
+ Set.of()
+ ),
+
+ // Сценарий 6: с null и пустой строкой
+ Arguments.of(
+ List.of(
+ new Player("Иван", Position.FORWARD, null, 1),
+ new Player("Петр", Position.DEFENDER, "", 2),
+ new Player("Сергей", Position.MIDFIELD, "Barcelona", 3)
+ ),
+ Set.of("Barcelona")
+ )
+ );
+ }
+}