From c202efd2f3da0506f23592ea8578f41b2a48ee6d Mon Sep 17 00:00:00 2001 From: MatafonovDS Date: Sun, 30 Nov 2025 00:27:36 +0500 Subject: [PATCH 1/3] stream API example --- build.gradle.kts | 1 + src/main/java/ru/urfu/Main.java | 31 +++++--- src/main/java/ru/urfu/chart/ChartDrawer.java | 22 ++++++ src/main/java/ru/urfu/chart/ChartMapper.java | 43 ++++++++++++ src/main/java/ru/urfu/model/Player.java | 8 +++ src/main/java/ru/urfu/model/Position.java | 8 +++ src/main/java/ru/urfu/parser/CsvParser.java | 34 +++++++++ src/main/java/ru/urfu/resolver/IResolver.java | 23 +++++- src/main/java/ru/urfu/resolver/Resolver.java | 70 +++++++++++++++++++ 9 files changed, 230 insertions(+), 10 deletions(-) create mode 100644 src/main/java/ru/urfu/chart/ChartDrawer.java create mode 100644 src/main/java/ru/urfu/chart/ChartMapper.java create mode 100644 src/main/java/ru/urfu/model/Player.java create mode 100644 src/main/java/ru/urfu/model/Position.java create mode 100644 src/main/java/ru/urfu/parser/CsvParser.java create mode 100644 src/main/java/ru/urfu/resolver/Resolver.java diff --git a/build.gradle.kts b/build.gradle.kts index 6674195..bbd5574 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,7 @@ repositories { dependencies { testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") + implementation("org.jfree:jfreechart:1.5.6") } tasks.test { diff --git a/src/main/java/ru/urfu/Main.java b/src/main/java/ru/urfu/Main.java index 5845f6d..84ca895 100644 --- a/src/main/java/ru/urfu/Main.java +++ b/src/main/java/ru/urfu/Main.java @@ -1,17 +1,30 @@ 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) { + public static void main(String[] args) throws IOException { //TIP Press with your caret at the highlighted text // to see how GIGA IDE suggests fixing it. - System.out.printf("Hello and welcome!"); + var players = CsvParser.parseCsvToList("/Users/denis/IdeaProjects/Java2025_2/players.csv"); + System.out.println(players); + + Resolver resolver = new Resolver(players); + + 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(players); + 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..2c03bfa --- /dev/null +++ b/src/main/java/ru/urfu/parser/CsvParser.java @@ -0,0 +1,34 @@ +package ru.urfu.parser; + +import ru.urfu.model.Player; +import ru.urfu.model.Position; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.stream.Collectors; + +public class CsvParser { + public static List parseCsvToList(String pathString) throws IOException { + return Files.readAllLines(Paths.get(pathString)) + .stream() + .skip(1) + .map(CsvParser::parsePlayerRow) + .toList(); + } + + 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..1116d57 --- /dev/null +++ b/src/main/java/ru/urfu/resolver/Resolver.java @@ -0,0 +1,70 @@ +package ru.urfu.resolver; + +import ru.urfu.model.Player; +import ru.urfu.model.Position; + +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(List players) { + this.players = players; + } + + @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) + .collect(Collectors.toSet()); + } + + @Override + public Map getTop5TeamsByGoalsCount() { + return Map.of(); + } + + @Override + public String getAgencyWithMinPlayersCount() { + return ""; + } + + @Override + public String getTheRudestTeam() { + return ""; + } +} From 2a2f1c83e3dc790d346cd427f7230764759c778b Mon Sep 17 00:00:00 2001 From: MatafonovDS Date: Sun, 30 Nov 2025 00:39:07 +0500 Subject: [PATCH 2/3] =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=B3=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ru/urfu/Main.java | 9 +++---- src/main/java/ru/urfu/parser/CsvParser.java | 27 ++++++++++++-------- src/main/java/ru/urfu/resolver/Resolver.java | 5 ++-- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/main/java/ru/urfu/Main.java b/src/main/java/ru/urfu/Main.java index 84ca895..fc357de 100644 --- a/src/main/java/ru/urfu/Main.java +++ b/src/main/java/ru/urfu/Main.java @@ -14,17 +14,16 @@ import java.util.function.ToIntFunction; public class Main { - public static void main(String[] args) throws IOException { + public static void main(String[] args) { //TIP Press with your caret at the highlighted text // to see how GIGA IDE suggests fixing it. - var players = CsvParser.parseCsvToList("/Users/denis/IdeaProjects/Java2025_2/players.csv"); - System.out.println(players); + var parser = new CsvParser("/Users/denis/IdeaProjects/Java2025_2/players.csv"); - Resolver resolver = new Resolver(players); + Resolver resolver = new Resolver(parser); System.out.println(resolver.getTeams()); - var chartData = ChartMapper.mapDataToChart(players); + var chartData = ChartMapper.mapDataToChart(parser.parseCsvToList()); ChartDrawer.showChart(chartData); } } \ No newline at end of file diff --git a/src/main/java/ru/urfu/parser/CsvParser.java b/src/main/java/ru/urfu/parser/CsvParser.java index 2c03bfa..3ca7cbe 100644 --- a/src/main/java/ru/urfu/parser/CsvParser.java +++ b/src/main/java/ru/urfu/parser/CsvParser.java @@ -3,23 +3,28 @@ import ru.urfu.model.Player; import ru.urfu.model.Position; -import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.List; -import java.util.Scanner; -import java.util.stream.Collectors; public class CsvParser { - public static List parseCsvToList(String pathString) throws IOException { - return Files.readAllLines(Paths.get(pathString)) - .stream() - .skip(1) - .map(CsvParser::parsePlayerRow) - .toList(); + 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) { diff --git a/src/main/java/ru/urfu/resolver/Resolver.java b/src/main/java/ru/urfu/resolver/Resolver.java index 1116d57..afa12e7 100644 --- a/src/main/java/ru/urfu/resolver/Resolver.java +++ b/src/main/java/ru/urfu/resolver/Resolver.java @@ -2,6 +2,7 @@ import ru.urfu.model.Player; import ru.urfu.model.Position; +import ru.urfu.parser.CsvParser; import java.util.List; import java.util.Map; @@ -11,8 +12,8 @@ public class Resolver implements IResolver { private final List players; - public Resolver(List players) { - this.players = players; + public Resolver(CsvParser parser) { + this.players = parser.parseCsvToList(); } @Override From 4dd677fd57083ae0ac0b952413a243bdf9f57a2b Mon Sep 17 00:00:00 2001 From: MatafonovDS Date: Sun, 30 Nov 2025 23:14:39 +0500 Subject: [PATCH 3/3] tests example --- .github/workflows/tests.yml | 26 +++ build.gradle.kts | 16 ++ src/main/java/ru/urfu/resolver/Resolver.java | 1 + .../java/ru/urfu/resolver/ResolverTest.java | 185 ++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 .github/workflows/tests.yml create mode 100644 src/test/java/ru/urfu/resolver/ResolverTest.java 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 bbd5574..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,11 +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/resolver/Resolver.java b/src/main/java/ru/urfu/resolver/Resolver.java index afa12e7..898c042 100644 --- a/src/main/java/ru/urfu/resolver/Resolver.java +++ b/src/main/java/ru/urfu/resolver/Resolver.java @@ -51,6 +51,7 @@ public Map getPlayersByPosition() { public Set getTeams() { return players.stream() .map(Player::agency) + .filter(agency -> agency != null && !agency.isEmpty()) .collect(Collectors.toSet()); } 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") + ) + ); + } +}