diff --git a/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/Foot.java b/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/Foot.java new file mode 100644 index 00000000..e966277a --- /dev/null +++ b/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/Foot.java @@ -0,0 +1,9 @@ +package bg.sofia.uni.fmi.mjt.football; + +public enum Foot { + LEFT, RIGHT; + + public static Foot of(String foot) { + return Foot.valueOf(foot.toUpperCase()); + } +} diff --git a/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/FootballPlayerAnalyzer.java b/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/FootballPlayerAnalyzer.java new file mode 100644 index 00000000..3c62bb8a --- /dev/null +++ b/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/FootballPlayerAnalyzer.java @@ -0,0 +1,139 @@ +package bg.sofia.uni.fmi.mjt.football; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.AbstractMap; +import java.util.AbstractMap.SimpleEntry; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class FootballPlayerAnalyzer { + + private static final int RATING_VARIANCE = 3; + private final List players; + + public FootballPlayerAnalyzer(Reader reader) { + try (var br = new BufferedReader(reader)) { + players = br.lines()// + .skip(1)// + .map(Player::of)// + .toList(); + } catch (IOException ex) { + throw new IllegalArgumentException("Could not load dataset", ex); + } + } + + public List getAllPlayers() { + return List.copyOf(players); + } + + public Set getAllNationalities() { + return players.stream()// + .map(Player::nationality)// + .collect(Collectors.toUnmodifiableSet()); + } + + public Player getHighestPaidPlayerByNationality(String nationality) { + if (null == nationality) { + throw new IllegalArgumentException("Provided nationality cannot be null"); + } + + return players.stream()// + .filter(p -> p.nationality().equals(nationality))// + .max(Comparator.comparingLong(Player::wageEuro))// + .orElseThrow(NoSuchElementException::new); + } + + public Map> groupByPosition() { + return players.stream()// + .flatMap(this::mapPlayerToPositionEntry)// + .collect(Collectors.toUnmodifiableMap(SimpleEntry::getKey, this::newSetFromPlayerEntry, + this::mergeSets)); + } + + private Stream> mapPlayerToPositionEntry(Player player) { + return player.positions()// + .stream()// + .map(pos -> new AbstractMap.SimpleEntry<>(pos, player)); + } + + private Set newSetFromPlayerEntry(SimpleEntry playerEntry) { + Set playerSet = new HashSet<>(); + playerSet.add(playerEntry.getValue()); + + return playerSet; + } + + private Set mergeSets(Set set1, Set set2) { + set1.addAll(set2); + + return set1; + } + + public Optional getTopProspectPlayerForPositionInBudget(Position position, long budget) { + if (null == position) { + throw new IllegalArgumentException("Provided position cannot be null"); + } + if (budget < 0) { + throw new IllegalArgumentException("Provided budget cannot be negative"); + } + + return players.stream()// + .filter(p -> p.positions().contains(position))// + .filter(p -> p.valueEuro() <= budget)// + .max(Comparator.comparingDouble(this::calculatePlayerProspectRating)); + } + + private double calculatePlayerProspectRating(Player player) { + return ((double) player.overallRating() + player.potential()) / player.age(); + } + + public Set getSimilarPlayers(Player player) { + if (null == player) { + throw new IllegalArgumentException("Provided player cannot be null"); + } + + return players.stream()// + .filter(p -> canPlayWithSameFoot(p, player))// + .filter(p -> canPlaySamePosition(p, player))// + .filter(p -> isSimilarRating(p, player))// + .collect(Collectors.toUnmodifiableSet()); + } + + private boolean canPlayWithSameFoot(Player player, Player targetPlayer) { + return player.preferredFoot() == targetPlayer.preferredFoot(); + } + + private boolean canPlaySamePosition(Player player, Player targetPlayer) { + return player.positions()// + .stream()// + .anyMatch(pos -> targetPlayer.positions().contains(pos)); + } + + private boolean isSimilarRating(Player player, Player targetPlayer) { + int overallRating = player.overallRating(); + int targetOverallRating = targetPlayer.overallRating(); + + return (targetOverallRating - RATING_VARIANCE) <= overallRating + && overallRating <= (targetOverallRating + RATING_VARIANCE); + } + + public Set getPlayersByFullNameKeyword(String keyword) { + if (null == keyword) { + throw new IllegalArgumentException("Provided keyword cannot be null"); + } + + return players.stream()// + .filter(p -> p.fullName().contains(keyword))// + .collect(Collectors.toUnmodifiableSet()); + } + +} diff --git a/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/Player.java b/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/Player.java new file mode 100644 index 00000000..7b8481ad --- /dev/null +++ b/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/Player.java @@ -0,0 +1,53 @@ +package bg.sofia.uni.fmi.mjt.football; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; + +public record Player(String name, String fullName, LocalDate birthDate, int age, double heightCm, double weightKg, + List positions, String nationality, int overallRating, int potential, long valueEuro, long wageEuro, + Foot preferredFoot) { + + private static final int NAME = 0; + private static final int FULL_NAME = 1; + private static final int BIRTH_DATE = 2; + private static final int AGE = 3; + private static final int HEIGHT_CM = 4; + private static final int WEIGHT_KG = 5; + private static final int POSITIONS = 6; + private static final int NATIONALITY = 7; + private static final int OVERALL_RATING = 8; + private static final int POTENTIAL = 9; + private static final int VALUE_EURO = 10; + private static final int WAGE_EURO = 11; + private static final int PREFERRED_FOOT = 12; + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("M/d/yyyy"); + + public static Player of(String line) { + String[] tokens = line.split(";"); + + String name = tokens[NAME]; + String fullName = tokens[FULL_NAME]; + LocalDate birthDate = parseDate(tokens[BIRTH_DATE]); + int age = Integer.parseInt(tokens[AGE]); + double heightCm = Double.parseDouble(tokens[HEIGHT_CM]); + double weightKg = Double.parseDouble(tokens[WEIGHT_KG]); + List positions = Arrays.stream(tokens[POSITIONS].split(",")).map(Position::valueOf).toList(); + String nationality = tokens[NATIONALITY]; + int overallRating = Integer.parseInt(tokens[OVERALL_RATING]); + int potential = Integer.parseInt(tokens[POTENTIAL]); + long valueEuro = Long.parseLong(tokens[VALUE_EURO]); + long wageEuro = Long.parseLong(tokens[WAGE_EURO]); + Foot preferredFoot = Foot.of(tokens[PREFERRED_FOOT]); + + return new Player(name, fullName, birthDate, age, heightCm, weightKg, positions, nationality, overallRating, + potential, valueEuro, wageEuro, preferredFoot); + } + + private static LocalDate parseDate(String date) { + return LocalDate.parse(date, DATE_FORMATTER); + } + +} diff --git a/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/Position.java b/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/Position.java new file mode 100644 index 00000000..4259c940 --- /dev/null +++ b/08-lambdas-and-stream-api/lab/solution/src/bg/sofia/uni/fmi/mjt/football/Position.java @@ -0,0 +1,21 @@ +package bg.sofia.uni.fmi.mjt.football; + +public enum Position { + // Diagram: https://fifauteam.com/fifa-21-positions/ + + ST, // Striker + LM, // Left Midfielder + CF, // Centre Forward + GK, // Goalkeeper + RW, // Right Winger + CM, // Centre Midfielder + LW, // Left Winger + CDM, // Defensive Midfielder + CAM, // Attacking midfielder + RB, // Right back + LB, // Left back + LWB, // Left Wing-back + RM, // Right Midfielder + RWB, // Right Wing-back + CB; // Centre Back +}