From 749af572d5089c114e158c9f5e1d43c9e034ffee Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Sat, 8 Jul 2023 16:46:24 +0200 Subject: [PATCH] Store nomination within voting result --- .../wahlrecht/model/ElectedResult.java | 9 +- .../wahlrecht/model/Nomination.java | 5 - .../wahlrecht/model/SeatResult.java | 8 +- .../wahlrecht/model/VotingResult.java | 4 +- .../wahlrecht/service/CalculationService.java | 132 ++--- .../service/CalculationServiceTest.java | 514 ++++++++---------- 6 files changed, 302 insertions(+), 370 deletions(-) diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/model/ElectedResult.java b/module-server/src/main/java/de/twomartens/wahlrecht/model/ElectedResult.java index 33b2b43..9630838 100644 --- a/module-server/src/main/java/de/twomartens/wahlrecht/model/ElectedResult.java +++ b/module-server/src/main/java/de/twomartens/wahlrecht/model/ElectedResult.java @@ -3,10 +3,17 @@ package de.twomartens.wahlrecht.model; import java.util.Collection; import java.util.Deque; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; import lombok.Builder; @Builder -public record ElectedResult(Map> electedCandidates, +public record ElectedResult(Map> electedCandidates, Deque usedElectionNumbers) { + public Map> electedCandidatesByNomination() { + return electedCandidates.entrySet().stream() + .collect(Collectors.toMap(entry -> entry.getKey().getNomination(), Entry::getValue)); + } + } diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/model/Nomination.java b/module-server/src/main/java/de/twomartens/wahlrecht/model/Nomination.java index 002fd10..bf8b289 100644 --- a/module-server/src/main/java/de/twomartens/wahlrecht/model/Nomination.java +++ b/module-server/src/main/java/de/twomartens/wahlrecht/model/Nomination.java @@ -6,9 +6,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.Setter; import lombok.ToString; import lombok.ToString.Include; @@ -21,9 +19,6 @@ public class Nomination { @Include private final String name; private final boolean supportVotesOnNomination; - @Getter - @Setter - private VotingResult votingResult; private void addCandidate(Candidate candidate) { candidates.add(candidate); diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/model/SeatResult.java b/module-server/src/main/java/de/twomartens/wahlrecht/model/SeatResult.java index 2a0049e..c6647c9 100644 --- a/module-server/src/main/java/de/twomartens/wahlrecht/model/SeatResult.java +++ b/module-server/src/main/java/de/twomartens/wahlrecht/model/SeatResult.java @@ -2,10 +2,16 @@ package de.twomartens.wahlrecht.model; import java.util.Deque; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; import lombok.Builder; @Builder -public record SeatResult(Map seatsPerNomination, +public record SeatResult(Map seatsPerResult, Deque usedElectionNumbers) { + public Map seatsPerNomination() { + return seatsPerResult.entrySet().stream() + .collect(Collectors.toMap(entry -> entry.getKey().getNomination(), Entry::getValue)); + } } diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/model/VotingResult.java b/module-server/src/main/java/de/twomartens/wahlrecht/model/VotingResult.java index 59290f9..5ed5536 100644 --- a/module-server/src/main/java/de/twomartens/wahlrecht/model/VotingResult.java +++ b/module-server/src/main/java/de/twomartens/wahlrecht/model/VotingResult.java @@ -10,14 +10,16 @@ public class VotingResult { private final int votesOnNomination; private final int votesThroughHealing; private final Map votesPerPosition; + private final Nomination nomination; private Integer totalVotes = null; @Builder public VotingResult(int votesOnNomination, int votesThroughHealing, - Map votesPerPosition) { + Map votesPerPosition, Nomination nomination) { this.votesOnNomination = votesOnNomination; this.votesThroughHealing = votesThroughHealing; this.votesPerPosition = votesPerPosition; + this.nomination = nomination; } public int getTotalVotes() { diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/service/CalculationService.java b/module-server/src/main/java/de/twomartens/wahlrecht/service/CalculationService.java index aebf413..81ae60d 100644 --- a/module-server/src/main/java/de/twomartens/wahlrecht/service/CalculationService.java +++ b/module-server/src/main/java/de/twomartens/wahlrecht/service/CalculationService.java @@ -29,39 +29,37 @@ public class CalculationService { private final Deque electionNumberHistory = new ArrayDeque<>(); public ElectedResult calculateConstituency(@NonNull Constituency constituency, - @NonNull Collection nominations) { + @NonNull Collection votingResults) { electionNumberHistory.clear(); - int totalVotes = nominations.stream() - .map(Nomination::getVotingResult) + int totalVotes = votingResults.stream() .map(VotingResult::getTotalVotes) .reduce(0, Integer::sum); int numberOfSeats = constituency.numberOfSeats(); double initialElectionNumber = totalVotes / (double) numberOfSeats; - Map assignedSeatsPerNomination = calculateAssignedSeatsPerNomination( - nominations, numberOfSeats, initialElectionNumber); + Map assignedSeatsPerResult = calculateAssignedSeatsPerResult( + votingResults, numberOfSeats, initialElectionNumber); return ElectedResult.builder() - .electedCandidates(findElectedCandidates(assignedSeatsPerNomination, Collections.emptyMap())) + .electedCandidates(findElectedCandidates(assignedSeatsPerResult, Collections.emptyMap())) .usedElectionNumbers(electionNumberHistory) .build(); } public SeatResult calculateOverallSeatDistribution(@NonNull Election election, - @NonNull Collection nominations) { + @NonNull Collection votingResults) { electionNumberHistory.clear(); - int totalVotes = nominations.stream() - .map(Nomination::getVotingResult) + int totalVotes = votingResults.stream() .map(VotingResult::getTotalVotes) .reduce(0, Integer::sum); - List validNominations = new ArrayList<>(); + List validVotingResults = new ArrayList<>(); int totalIgnoredVotes = 0; - for (Nomination nomination : nominations) { - if (passesVotingThreshold(election, totalVotes, nomination)) { - validNominations.add(nomination); + for (VotingResult votingResult : votingResults) { + if (passesVotingThreshold(election, totalVotes, votingResult)) { + validVotingResults.add(votingResult); } else { - totalIgnoredVotes += nomination.getVotingResult().getTotalVotes(); + totalIgnoredVotes += votingResult.getTotalVotes(); } } totalVotes -= totalIgnoredVotes; @@ -69,17 +67,17 @@ public class CalculationService { int numberOfSeats = election.totalNumberOfSeats(); double initialElectionNumber = totalVotes / (double) numberOfSeats; - Map assignedSeatsPerNomination = calculateAssignedSeatsPerNomination( - validNominations, numberOfSeats, initialElectionNumber); + Map assignedSeatsPerResult = calculateAssignedSeatsPerResult( + validVotingResults, numberOfSeats, initialElectionNumber); return SeatResult.builder() - .seatsPerNomination(assignedSeatsPerNomination) + .seatsPerResult(assignedSeatsPerResult) .usedElectionNumbers(electionNumberHistory) .build(); } - public ElectedResult calculateElectedOverallCandidates(Map seatsPerNomination, - Map> electedCandidates) { + public ElectedResult calculateElectedOverallCandidates(Map seatsPerNomination, + Map> electedCandidates) { electionNumberHistory.clear(); return ElectedResult.builder() @@ -89,30 +87,35 @@ public class CalculationService { } private static boolean passesVotingThreshold(@NonNull Election election, int totalVotes, - @NonNull Nomination nomination) { + @NonNull VotingResult votingResult) { return totalVotes * election.votingThreshold().getMultiplier() - <= nomination.getVotingResult().getTotalVotes(); + <= votingResult.getTotalVotes(); } @NonNull - private Map> findElectedCandidates( - @NonNull Map assignedSeatsPerNomination, - Map> alreadyElectedCandidates) { + private Map> findElectedCandidates( + @NonNull Map assignedSeatsPerNomination, + Map> alreadyElectedCandidates) { - Map> electedCandidates = new HashMap<>(); - assignedSeatsPerNomination.entrySet().stream() - .filter(entry -> entry.getValue() > 0) - .forEach(entry -> - electedCandidates.put(entry.getKey(), findCandidates(entry.getKey(), entry.getValue(), - alreadyElectedCandidates.getOrDefault(entry.getKey(), Collections.emptyList())))); + Map> electedCandidates = new HashMap<>(); + for (Entry entry : assignedSeatsPerNomination.entrySet()) { + if (entry.getValue() > 0) { + Collection candidates = findCandidates( + entry.getKey(), + entry.getValue(), + alreadyElectedCandidates.getOrDefault(entry.getKey(), Collections.emptyList()) + ); + electedCandidates.put(entry.getKey(), candidates); + } + } return electedCandidates; } @NonNull - private Collection findCandidates(@NonNull Nomination nomination, + private Collection findCandidates(@NonNull VotingResult votingResult, int numberOfSeats, Collection alreadyElectedCandidates) { - List individualVotesOrder = nomination.getVotingResult().getVotesPerPosition() + List individualVotesOrder = votingResult.getVotesPerPosition() .entrySet().stream() .sorted(Comparator.comparing(Entry::getValue).reversed()) .map(Entry::getKey) @@ -123,8 +126,9 @@ public class CalculationService { Map electedCandidateMap = alreadyElectedCandidates.stream() .collect(Collectors.toMap(ElectedCandidate::candidate, Function.identity())); + Nomination nomination = votingResult.getNomination(); if (nomination.supportsVotesOnNomination()) { - int seatsByNomination = calculateSeatsByNominationOrder(nomination, numberOfSeats); + int seatsByNomination = calculateSeatsByNominationOrder(votingResult, numberOfSeats); seatsByVoteOrder = numberOfSeats - seatsByNomination; List candidates = nomination.getCandidates(); int electedByNominationOrder = 0; @@ -157,72 +161,72 @@ public class CalculationService { return elected; } - private static int calculateSeatsByNominationOrder(@NonNull Nomination nomination, int numberOfSeats) { - int votesOnNomination = nomination.getVotingResult().getVotesOnNomination(); - int totalVotes = nomination.getVotingResult().getTotalVotesWithoutHealing(); + private static int calculateSeatsByNominationOrder(@NonNull VotingResult votingResult, int numberOfSeats) { + int votesOnNomination = votingResult.getVotesOnNomination(); + int totalVotes = votingResult.getTotalVotesWithoutHealing(); return (int) Math.round((numberOfSeats * votesOnNomination) / (double) totalVotes); } - private Map calculateAssignedSeatsPerNomination( - @NonNull Collection nominations, + private Map calculateAssignedSeatsPerResult( + @NonNull Collection votingResults, int numberOfSeats, Double initialElectionNumber) { double electionNumber = initialElectionNumber; long assignedSeats; - Map assignedSeatsPerNomination; + Map assignedSeatsPerVotingResult; do { electionNumberHistory.add(electionNumber); - Map seatsPerNomination = new HashMap<>(); - for (Nomination nomination : nominations) { + Map seatsPerVotingResult = new HashMap<>(); + for (VotingResult votingResult : votingResults) { double seatNumber = calculateAssignedSeatNumber(electionNumber, - nomination.getVotingResult().getTotalVotes()); - seatsPerNomination.put(nomination, seatNumber); + votingResult.getTotalVotes()); + seatsPerVotingResult.put(votingResult, seatNumber); } - assignedSeatsPerNomination = seatsPerNomination.entrySet().stream() + assignedSeatsPerVotingResult = seatsPerVotingResult.entrySet().stream() .map(entry -> Map.entry(entry.getKey(), (int) Math.round(entry.getValue()))) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); - assignedSeats = assignedSeatsPerNomination.values().stream().reduce(0, Integer::sum); + assignedSeats = assignedSeatsPerVotingResult.values().stream().reduce(0, Integer::sum); if (assignedSeats < numberOfSeats) { // election number was too big, decrease - electionNumber = calculateLowerElectionNumber(initialElectionNumber, seatsPerNomination, - assignedSeatsPerNomination); + electionNumber = calculateLowerElectionNumber(initialElectionNumber, seatsPerVotingResult, + assignedSeatsPerVotingResult); } else if (assignedSeats > numberOfSeats) { // election number was too small, increase - electionNumber = calculateHigherElectionNumber(initialElectionNumber, seatsPerNomination, - assignedSeatsPerNomination); + electionNumber = calculateHigherElectionNumber(initialElectionNumber, seatsPerVotingResult, + assignedSeatsPerVotingResult); } } while (assignedSeats != numberOfSeats); - return assignedSeatsPerNomination; + return assignedSeatsPerVotingResult; } private static double calculateHigherElectionNumber(Double initialElectionNumber, - @NonNull Map seatsPerNomination, - Map assignedSeatsPerNomination) { + @NonNull Map seatsPerVotingResult, + Map assignedSeatsPerVotingResult) { double electionNumber; - electionNumber = seatsPerNomination.entrySet().stream() + electionNumber = seatsPerVotingResult.entrySet().stream() .map(entry -> Map.entry(entry.getKey(), - entry.getValue() - assignedSeatsPerNomination.get(entry.getKey()))) - .min(Comparator.comparing(Entry::getValue)) - .map(entry -> entry.getKey().getVotingResult().getTotalVotes() - / (assignedSeatsPerNomination.get(entry.getKey()) - 0.5)) + entry.getValue() - assignedSeatsPerVotingResult.get(entry.getKey()))) + .min(Comparator.comparing(Entry::getValue)) + .map(entry -> entry.getKey().getTotalVotes() + / (assignedSeatsPerVotingResult.get(entry.getKey()) - 0.5)) .orElse(initialElectionNumber); return electionNumber; } private static double calculateLowerElectionNumber(Double initialElectionNumber, - @NonNull Map seatsPerNomination, - Map assignedSeatsPerNomination) { + @NonNull Map seatsPerVotingResult, + Map assignedSeatsPerVotingResult) { double electionNumber; - electionNumber = seatsPerNomination.entrySet().stream() + electionNumber = seatsPerVotingResult.entrySet().stream() .map(entry -> Map.entry(entry.getKey(), - entry.getValue() - assignedSeatsPerNomination.get(entry.getKey()))) - .max(Comparator.comparing(Entry::getValue)) - .map(entry -> entry.getKey().getVotingResult().getTotalVotes() - / (assignedSeatsPerNomination.get(entry.getKey()) + 0.5)) + entry.getValue() - assignedSeatsPerVotingResult.get(entry.getKey()))) + .max(Comparator.comparing(Entry::getValue)) + .map(entry -> entry.getKey().getTotalVotes() + / (assignedSeatsPerVotingResult.get(entry.getKey()) + 0.5)) .orElse(initialElectionNumber); return electionNumber; } diff --git a/module-server/src/test/java/de/twomartens/wahlrecht/service/CalculationServiceTest.java b/module-server/src/test/java/de/twomartens/wahlrecht/service/CalculationServiceTest.java index 4dcb03e..cd16a89 100644 --- a/module-server/src/test/java/de/twomartens/wahlrecht/service/CalculationServiceTest.java +++ b/module-server/src/test/java/de/twomartens/wahlrecht/service/CalculationServiceTest.java @@ -14,9 +14,11 @@ import java.time.LocalDate; import java.time.Month; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -25,28 +27,6 @@ import org.springframework.lang.NonNull; class CalculationServiceTest { - private static final Constituency EIMSBUETTEL_NORD = new Constituency("Eimsbüttel-Nord", 3); - private static final Constituency EIMSBUETTEL_SUED = new Constituency("Eimsbüttel-Süd", 5); - private static final Constituency HARO = new Constituency("Rotherbaum/Harvestehude", 4); - private static final Constituency LOKSTEDT = new Constituency("Lokstedt", 3); - private static final Constituency NIENDORF = new Constituency("Niendorf", 5); - private static final Constituency SCHNELSEN = new Constituency("Schnelsen", 3); - private static final Constituency EIDELSTEDT = new Constituency("Eidelstedt", 4); - private static final Constituency STELLINGEN = new Constituency("Stellingen", 3); - private static final Nomination SPD_NIENDORF = buildNomination("SPD-Niendorf", false); - private static final Nomination CDU_NIENDORF = buildNomination("CDU-Niendorf", false); - private static final Nomination FDP_NIENDORF = buildNomination("FDP-Niendorf", false); - private static final Nomination LINKE_NIENDORF = buildNomination("LINKE-Niendorf", false); - private static final Nomination GRUENE_NIENDORF = buildNomination("GRUENE-Niendorf", false); - private static final Nomination AFD_NIENDORF = buildNomination("AFD-Niendorf", false); - - private static final Nomination SPD_BEZIRK = buildNomination("SPD-Bezirk", true); - private static final Nomination CDU_BEZIRK = buildNomination("CDU-Bezirk", true); - private static final Nomination FDP_BEZIRK = buildNomination("FDP-Bezirk", true); - private static final Nomination LINKE_BEZIRK = buildNomination("LINKE-Bezirk", true); - private static final Nomination GRUENE_BEZIRK = buildNomination("GRUENE-Bezirk", true); - private static final Nomination AFD_BEZIRK = buildNomination("AFD-Bezirk", true); - private static final Nomination PIRATEN_BEZIRK = buildNomination("Piraten-Bezirk", true); public static final String GOTTLIEB_GABOR = "Gottlieb, Gabor"; public static final String RIEGEL_ANN_KATHRIN = "Riegel, Ann-Kathrin"; public static final String HOEFLICH_JUTTA = "Höflich, Jutta"; @@ -68,13 +48,191 @@ class CalculationServiceTest { public static final String ERK_ARAMAK = "Erk, Aramak"; public static final String DR_FISCHER_JOST_LEONHARDT = "Dr. Fischer, Jost Leonhardt"; public static final String KUELL_GABRIELA = "Küll, Gabriela"; - + private static final Constituency EIMSBUETTEL_NORD = new Constituency("Eimsbüttel-Nord", 3); + private static final Constituency EIMSBUETTEL_SUED = new Constituency("Eimsbüttel-Süd", 5); + private static final Constituency HARO = new Constituency("Rotherbaum/Harvestehude", 4); + private static final Constituency LOKSTEDT = new Constituency("Lokstedt", 3); + private static final Constituency NIENDORF = new Constituency("Niendorf", 5); + private static final Constituency SCHNELSEN = new Constituency("Schnelsen", 3); + private static final Constituency EIDELSTEDT = new Constituency("Eidelstedt", 4); + private static final Constituency STELLINGEN = new Constituency("Stellingen", 3); + private static final Nomination SPD_NIENDORF = buildNomination("SPD-Niendorf", false); + private static final VotingResult SPD_NIENDORF_RESULT = buildVotingResult(SPD_NIENDORF, 0, 0, + 11882, 7617, 1946, 1743, 1363, 762, 895, 1598, 1786, 781); + private static final Nomination CDU_NIENDORF = buildNomination("CDU-Niendorf", false); + public static final VotingResult CDU_NIENDORF_RESULT = buildVotingResult(CDU_NIENDORF, 0, 0, 10733, + 4550, 2310, 2333, 2565); + private static final Nomination FDP_NIENDORF = buildNomination("FDP-Niendorf", false); + public static final VotingResult FDP_NIENDORF_RESULT = buildVotingResult(FDP_NIENDORF, 0, 0, 2498, + 3200, 992); + private static final Nomination LINKE_NIENDORF = buildNomination("LINKE-Niendorf", false); + public static final VotingResult LINKE_NIENDORF_RESULT = buildVotingResult(LINKE_NIENDORF, 0, 0, + 3782, 1584, 1185); + private static final Nomination GRUENE_NIENDORF = buildNomination("GRUENE-Niendorf", false); + public static final VotingResult GRUENE_NIENDORF_RESULT = buildVotingResult(GRUENE_NIENDORF, 0, 0, + 14838, 12501); + private static final Nomination AFD_NIENDORF = buildNomination("AFD-Niendorf", false); + public static final VotingResult AFD_NIENDORF_RESULT = buildVotingResult(AFD_NIENDORF, 0, 0, 6616); + private static final Nomination SPD_BEZIRK = buildNomination("SPD-Bezirk", true); + public static final VotingResult SPD_BEZIRK_RESULT = buildVotingResult(SPD_BEZIRK, 84703, 985, + 8782, 3318, 3825, 3519, 2036, 3923, 5427, 2131, 566, 895, 1425, 1924, 819, + 2590, 2190, 3568, 742, 401, 554, 1133, 1268, 580, 254, 1217, 693, 1359, 481, + 650, 230, 1544, 1080, 1879, 400, 353, 525, 469, 196, 759, 872); + private static final Nomination CDU_BEZIRK = buildNomination("CDU-Bezirk", true); + public static final VotingResult CDU_BEZIRK_RESULT = buildVotingResult(CDU_BEZIRK, 66245, 660, + 5966, 1982, 1604, 2414, 1322, 2237, 1868, 1872, 1889, 881, 1253, 2921, 598, + 2721, 618, 941, 620, 837, 378, 594, 484, 584, 472, 408, 748, 650, 282, + 315, 1397, 502); + private static final Nomination FDP_BEZIRK = buildNomination("FDP-Bezirk", true); + public static final VotingResult FDP_BEZIRK_RESULT = buildVotingResult(FDP_BEZIRK, 24435, 240, + 3528, 1964, 1633, 897, 1193, 355, 401, 1012, 496, 1242, 643, 300, 311, + 998, 505, 2181); + private static final Nomination LINKE_BEZIRK = buildNomination("LINKE-Bezirk", true); + public static final VotingResult LINKE_BEZIRK_RESULT = buildVotingResult(LINKE_BEZIRK, 47452, 330, + 4890, 875, 1532, 1293, 2321, 696, 1843, 2212, 1488, 483, 764, 572, 892); + private static final Nomination GRUENE_BEZIRK = buildNomination("GRUENE-Bezirk", true); + public static final VotingResult GRUENE_BEZIRK_RESULT = buildVotingResult(GRUENE_BEZIRK, 150842, + 900, + 10586, 5629, 12597, 2935, 3378, 735, 5662, 4021, 2065, 2469, 1561, 3557, 2290, + 1411, 2445, 1761, 1252, 1628, 652, 3524, 677, 843, 2375, 500, 1674, 3264, 1569, + 531, 531, 652, 5358, 2129); + private static final Nomination AFD_BEZIRK = buildNomination("AFD-Bezirk", true); + public static final VotingResult AFD_BEZIRK_RESULT = buildVotingResult(AFD_BEZIRK, 21625, 220, + 1226, 1950, 3007, 874, 1032, 780, 535, 221, 283); + private static final Nomination PIRATEN_BEZIRK = buildNomination("Piraten-Bezirk", true); + public static final VotingResult PIRATEN_BEZIRK_RESULT = buildVotingResult(PIRATEN_BEZIRK, 6715, + 25, + 871, 1907, 987); private CalculationService service; @BeforeAll - static void setUpOverall() { + static void setUp() { + setUpCandidatesConstituency(); setUpCandidatesOverall(); - setUpResultsOverall(); + } + + private static void setUpCandidatesConstituency() { + SPD_NIENDORF.addCandidates(new Candidate("Schwarzarius, Ines", "")); + SPD_NIENDORF.addCandidates(new Candidate("Urbanski, Torge", "")); + SPD_NIENDORF.addCandidates(new Candidate("Fischbach-Pyttel, Carola", "")); + SPD_NIENDORF.addCandidates(new Candidate("Bäcker, Guido", "")); + SPD_NIENDORF.addCandidates(new Candidate("Porr, Ulrike", "")); + SPD_NIENDORF.addCandidates(new Candidate("Petersen, Johannes", "")); + SPD_NIENDORF.addCandidates(new Candidate("Kröger, Manuela-Andrea", "")); + SPD_NIENDORF.addCandidates(new Candidate("Steppat, Wolfgang", "")); + SPD_NIENDORF.addCandidates(new Candidate("Canbay, Berfin", "")); + SPD_NIENDORF.addCandidates(new Candidate("Schumacher, Wolfgang", "")); + + CDU_NIENDORF.addCandidates(new Candidate("Kuhn, Rüdiger", "")); + CDU_NIENDORF.addCandidates(new Candidate("Ahrens, Mareike", "")); + CDU_NIENDORF.addCandidates(new Candidate("Wysocki, Michael", "")); + CDU_NIENDORF.addCandidates(new Candidate("Dönselmann, Malte", "")); + CDU_NIENDORF.addCandidates(new Candidate("Seehaber, Daniel", "")); + + LINKE_NIENDORF.addCandidates(new Candidate("Dr. Khenkhar, Malik", "")); + LINKE_NIENDORF.addCandidates(new Candidate("Radom, Claudia", "")); + LINKE_NIENDORF.addCandidates(new Candidate("Kara, Başak", "")); + + FDP_NIENDORF.addCandidates(new Candidate("Häffs, Marvin", "")); + FDP_NIENDORF.addCandidates(new Candidate("Bahmer, Larissa", "")); + FDP_NIENDORF.addCandidates(new Candidate("Patzer, Heinrich-Otto", "")); + + GRUENE_NIENDORF.addCandidates(new Candidate("Demirhan, Sina", "")); + GRUENE_NIENDORF.addCandidates(new Candidate("Schmidt, Lutz", "Redakteur")); + + AFD_NIENDORF.addCandidates(new Candidate("Hanssen, Wolfgang", "")); + } + + private static VotingResult buildVotingResult(Nomination nomination, int votesOnNomination, + int votesThroughHealing, + @NonNull int... votes) { + + return VotingResult.builder() + .votesThroughHealing(votesThroughHealing) + .votesOnNomination(votesOnNomination) + .votesPerPosition(buildVotesPerPosition(votes)) + .nomination(nomination) + .build(); + } + + private static void setUpCandidatesOverall() { + SPD_BEZIRK.addCandidates(buildCandidates( + GOTTLIEB_GABOR, "Meding, Sarah", "Harringa, Nils", "Nendza-Ammar, Charlotte", + "Rust, Rüdiger", "Köster-Marjanovic, Hannelore", RIEGEL_ANN_KATHRIN, "Rüter, Monika", + "Heise, Elk", "Fischbach-Pyttel, Carola", "Schirmer, Stephan-Philipp", + "Telljohann, Katharina", + "Röder, Wilfried", "Dr. Matiss, Claudia", "Koßel, Uwe", "Wagner, Saskia", "Bäcker, Guido", + "Tiben-Thörner, Karin", "Ptach, Stephan", "Steinbiß, Emily", "Berisha, Dafina", + "Meyer, Simon", "Porr, Ulrike", "Lange, Frederik", "Oliva, Nora", "Drossinakis, Panagiotis", + "Jacobs, Ruth", "Bernstein, Til", "Kröger, Manuela-Andrea", "Dr. Enzmann, Dirk", + "Schilling, Alexandra", "Abboud, Hussein", "Petersen, Britt", "Petersen, Johannes", + "Petersen, Doris", "Frese, Gabor", "Suh-von Bremen, Sun Won", "Steppat, Wolfgang", + "Hildebrand, Denise" + )); + + CDU_BEZIRK.addCandidates(buildCandidates( + HOEFLICH_JUTTA, "Könecke, Christian", "Birnbaum, Andreas", "Möller-Fiedler, Sybille", + "Kochmann, Franziska", "von Deutsch, Christiane", "Hoffmann, Bernd", "Holst, Christian", + "Lau, Roman", "Hille, Robert", "Ahrens, Mareike", DR_LANGHEIN_A_W_HEINRICH, + "Wichmann, Norbert", "Lau, Johanna", "Czernotzky, Birgit", "von Sawilski, Marlies", + "Dönselmann, Malte", + "Jarzembowski, Daniel", "Howe, Sönke", "Recknagel, Dennis", "Dieball, Laura", + "Josefowsky, Ernst Günther", "Wysocki, Michael", "Thomsen, Thomas", "Greshake, Sascha", + "Thiesen, Marianne", + "Schüller, Michael", "Jacobs, Jürgen", "Weiler, Johannes", "Howe, Raphaela" + )); + + LINKE_BEZIRK.addCandidates(buildCandidates( + PAGELS_MANUELA, WIEGMANN_ROLAND, "Peters, Ralf", "Hoyer, Jonas", + KLEINERT_MIKEY, "Schulte, Kolja", "Huwald, Kristin", "Dr. Ritter, Sabine", + "Pirling, David", "Reipschläger, Bernhard", "Arndt, Thomas", "Artus, Holger", + "WLaab, Helene" + )); + + GRUENE_BEZIRK.addCandidates(buildCandidates( + "Kern, Lisa", "Hadji Mir Agha, Ali", "Dr. Putz, Miriam", KUHLMANN_DIETMAR, + "Dr. Hunter, Lynne", "Schmidt-Tobler, Falk", "Demirhan, Sina", KLEIN_ROBERT, + ERK_ARAMAK, MARTENS_JIM, "Warnecke, Anne Kathrin", DR_FISCHER_JOST_LEONHARDT, + "Kost, Cornelia", "Brandt, Christopher", "Schübel, Nina Joana", "Schmidt, Lutz", + "Wolf, Rita", + "Alam, Leon", "Schwarzwald, Cristina", "Thies, Nico", "Hasselmann, Annette", + "Koriath, Jan", KUELL_GABRIELA, "Köhler, Kevin", "Hericks, Susanne", + BOHNY_CARL_MARIA, + "Fester, Emilia", "Hofmann, Klaus-Dieter", "Neumann, Bernd", "Braasch, Julian", + DORSCH_SEBASTIAN, "Hasselmann, Harald" + )); + + FDP_BEZIRK.addCandidates(buildCandidates( + KRUEGER_KLAUS, SCHWANKE_BENJAMIN, "Cleven, Martina", "Zielinski, Mathias", + "Thiele, Camilla Joyce", "Krüger, Hannelore", "Welling, Jörg", "Häffs, Marvin", + "Korb, Hendrik", "Bahmer, Larissa", "Langbehn, Marian", "Szrubarski, Sebastian", + "Patzer, Heinrich-Otto", "Sosin, Tatjana", "Damm, Renate", MUELLER_SOENKSEN_BURKHARDT + )); + + AFD_BEZIRK.addCandidates(buildCandidates( + SCHOEMER_DIRK, ZIMMERMANN_ELKE, PILLATZKE_JOERG, "Pohl, Christian", + "Hanssen, Wolfgang", "Lemke, Martin", "Cremer-Thursby, Marc", "Schultz, Thomas", + "Ziob, Peter" + )); + + PIRATEN_BEZIRK.addCandidates(buildCandidates( + "Dr. Siebert-Schütz, Martin", "Mewes, Christoph", "Brümmer, Amadeus" + )); + } + + private static Nomination buildNomination(String name, boolean supportVotesOnNomination) { + return new Nomination(name, supportVotesOnNomination); + } + + private static Collection buildCandidates(String... names) { + return Arrays.stream(names).map(name -> new Candidate(name, "")).toList(); + } + + @NonNull + private static Map buildVotesPerPosition(@NonNull int... votes) { + return IntStream.rangeClosed(1, votes.length) + .boxed() + .collect(Collectors.toMap(Function.identity(), i -> votes[i - 1])); } @BeforeEach @@ -84,20 +242,18 @@ class CalculationServiceTest { @Test void shouldCalculateConstituencyCorrectly() { - Collection nominations = List.of( - SPD_NIENDORF, - CDU_NIENDORF, - LINKE_NIENDORF, - FDP_NIENDORF, - GRUENE_NIENDORF, - AFD_NIENDORF + Collection votingResults = List.of( + SPD_NIENDORF_RESULT, + CDU_NIENDORF_RESULT, + LINKE_NIENDORF_RESULT, + GRUENE_NIENDORF_RESULT, + FDP_NIENDORF_RESULT, + AFD_NIENDORF_RESULT ); - setUpCandidatesConstituency(); - setUpResultsConstituency(); - ElectedResult elected = service.calculateConstituency(NIENDORF, nominations); + ElectedResult elected = service.calculateConstituency(NIENDORF, votingResults); - Assertions.assertThat(elected.electedCandidates()) + Assertions.assertThat(elected.electedCandidatesByNomination()) .containsKey(SPD_NIENDORF) .containsKey(CDU_NIENDORF) .containsKey(GRUENE_NIENDORF) @@ -114,16 +270,17 @@ class CalculationServiceTest { Election election = new Election("Bezirkswahl 2019", LocalDate.of(2019, Month.MAY, 26), VotingThreshold.THREE, 51, constituencies); - Collection nominations = List.of( - SPD_BEZIRK, - CDU_BEZIRK, - LINKE_BEZIRK, - FDP_BEZIRK, - GRUENE_BEZIRK, - AFD_BEZIRK + Collection votingResults = List.of( + SPD_BEZIRK_RESULT, + CDU_BEZIRK_RESULT, + LINKE_BEZIRK_RESULT, + GRUENE_BEZIRK_RESULT, + FDP_BEZIRK_RESULT, + AFD_BEZIRK_RESULT, + PIRATEN_BEZIRK_RESULT ); - SeatResult result = service.calculateOverallSeatDistribution(election, nominations); + SeatResult result = service.calculateOverallSeatDistribution(election, votingResults); Assertions.assertThat(result.seatsPerNomination()) .containsEntry(SPD_BEZIRK, 12) @@ -136,19 +293,20 @@ class CalculationServiceTest { @Test void shouldCalculateOverallCandidatesCorrectly() { - Map seatsPerNomination = Map.of( - SPD_BEZIRK, 2, - CDU_BEZIRK, 2, - LINKE_BEZIRK, 3, - GRUENE_BEZIRK, 8, - FDP_BEZIRK, 3, - AFD_BEZIRK, 3 + Map seatsPerVotingResult = Map.of( + SPD_BEZIRK_RESULT, 2, + CDU_BEZIRK_RESULT, 2, + LINKE_BEZIRK_RESULT, 3, + GRUENE_BEZIRK_RESULT, 8, + FDP_BEZIRK_RESULT, 3, + AFD_BEZIRK_RESULT, 3 ); - Map> electedCandidates = setUpConstituencyResults(); + Map> electedCandidates = setUpConstituencyResults(); - ElectedResult result = service.calculateElectedOverallCandidates(seatsPerNomination, electedCandidates); + ElectedResult result = service.calculateElectedOverallCandidates(seatsPerVotingResult, + electedCandidates); - Assertions.assertThat(result.electedCandidates()) + Assertions.assertThat(result.electedCandidatesByNomination()) .anySatisfy((nomination, candidates) -> { Assertions.assertThat(nomination).isEqualTo(SPD_BEZIRK); Assertions.assertThat(candidates) @@ -217,231 +375,9 @@ class CalculationServiceTest { }); } - private void setUpCandidatesConstituency() { - SPD_NIENDORF.addCandidates(new Candidate("Schwarzarius, Ines", "")); - SPD_NIENDORF.addCandidates(new Candidate("Urbanski, Torge", "")); - SPD_NIENDORF.addCandidates(new Candidate("Fischbach-Pyttel, Carola", "")); - SPD_NIENDORF.addCandidates(new Candidate("Bäcker, Guido", "")); - SPD_NIENDORF.addCandidates(new Candidate("Porr, Ulrike", "")); - SPD_NIENDORF.addCandidates(new Candidate("Petersen, Johannes", "")); - SPD_NIENDORF.addCandidates(new Candidate("Kröger, Manuela-Andrea", "")); - SPD_NIENDORF.addCandidates(new Candidate("Steppat, Wolfgang", "")); - SPD_NIENDORF.addCandidates(new Candidate("Canbay, Berfin", "")); - SPD_NIENDORF.addCandidates(new Candidate("Schumacher, Wolfgang", "")); - - CDU_NIENDORF.addCandidates(new Candidate("Kuhn, Rüdiger", "")); - CDU_NIENDORF.addCandidates(new Candidate("Ahrens, Mareike", "")); - CDU_NIENDORF.addCandidates(new Candidate("Wysocki, Michael", "")); - CDU_NIENDORF.addCandidates(new Candidate("Dönselmann, Malte", "")); - CDU_NIENDORF.addCandidates(new Candidate("Seehaber, Daniel", "")); - - LINKE_NIENDORF.addCandidates(new Candidate("Dr. Khenkhar, Malik", "")); - LINKE_NIENDORF.addCandidates(new Candidate("Radom, Claudia", "")); - LINKE_NIENDORF.addCandidates(new Candidate("Kara, Başak", "")); - - FDP_NIENDORF.addCandidates(new Candidate("Häffs, Marvin", "")); - FDP_NIENDORF.addCandidates(new Candidate("Bahmer, Larissa", "")); - FDP_NIENDORF.addCandidates(new Candidate("Patzer, Heinrich-Otto", "")); - - GRUENE_NIENDORF.addCandidates(new Candidate("Demirhan, Sina", "")); - GRUENE_NIENDORF.addCandidates(new Candidate("Schmidt, Lutz", "Redakteur")); - - AFD_NIENDORF.addCandidates(new Candidate("Hanssen, Wolfgang", "")); - } - - private void setUpResultsConstituency() { - SPD_NIENDORF.setVotingResult(VotingResult.builder() - .votesOnNomination(0) - .votesPerPosition(Map.of( - 1, 11882, - 2, 7617, - 3, 1946, - 4, 1743, - 5, 1363, - 6, 762, - 7, 895, - 8, 1598, - 9, 1786, - 10, 781 - )) - .build()); - - CDU_NIENDORF.setVotingResult(VotingResult.builder() - .votesOnNomination(0) - .votesPerPosition(Map.of( - 1, 10733, - 2, 4550, - 3, 2310, - 4, 2333, - 5, 2565 - )) - .build()); - - LINKE_NIENDORF.setVotingResult(VotingResult.builder() - .votesOnNomination(0) - .votesPerPosition(Map.of( - 1, 3782, - 2, 1584, - 3, 1185 - )) - .build()); - - FDP_NIENDORF.setVotingResult(VotingResult.builder() - .votesOnNomination(0) - .votesPerPosition(Map.of( - 1, 2498, - 2, 3200, - 3, 992 - )) - .build()); - - GRUENE_NIENDORF.setVotingResult(VotingResult.builder() - .votesOnNomination(0) - .votesPerPosition(Map.of( - 1, 14838, - 2, 12501 - )) - .build()); - - AFD_NIENDORF.setVotingResult(VotingResult.builder() - .votesOnNomination(0) - .votesPerPosition(Map.of( - 1, 6616 - )) - .build()); - } - - private static void setUpCandidatesOverall() { - SPD_BEZIRK.addCandidates(buildCandidates( - GOTTLIEB_GABOR, "Meding, Sarah", "Harringa, Nils", "Nendza-Ammar, Charlotte", - "Rust, Rüdiger", "Köster-Marjanovic, Hannelore", RIEGEL_ANN_KATHRIN, "Rüter, Monika", - "Heise, Elk", "Fischbach-Pyttel, Carola", "Schirmer, Stephan-Philipp", "Telljohann, Katharina", - "Röder, Wilfried", "Dr. Matiss, Claudia", "Koßel, Uwe", "Wagner, Saskia", "Bäcker, Guido", - "Tiben-Thörner, Karin", "Ptach, Stephan", "Steinbiß, Emily", "Berisha, Dafina", - "Meyer, Simon", "Porr, Ulrike", "Lange, Frederik", "Oliva, Nora", "Drossinakis, Panagiotis", - "Jacobs, Ruth", "Bernstein, Til", "Kröger, Manuela-Andrea", "Dr. Enzmann, Dirk", - "Schilling, Alexandra", "Abboud, Hussein", "Petersen, Britt", "Petersen, Johannes", - "Petersen, Doris", "Frese, Gabor", "Suh-von Bremen, Sun Won", "Steppat, Wolfgang", - "Hildebrand, Denise" - )); - - CDU_BEZIRK.addCandidates(buildCandidates( - HOEFLICH_JUTTA, "Könecke, Christian", "Birnbaum, Andreas", "Möller-Fiedler, Sybille", - "Kochmann, Franziska", "von Deutsch, Christiane", "Hoffmann, Bernd", "Holst, Christian", - "Lau, Roman", "Hille, Robert", "Ahrens, Mareike", DR_LANGHEIN_A_W_HEINRICH, - "Wichmann, Norbert", "Lau, Johanna", "Czernotzky, Birgit", "von Sawilski, Marlies", - "Dönselmann, Malte", - "Jarzembowski, Daniel", "Howe, Sönke", "Recknagel, Dennis", "Dieball, Laura", - "Josefowsky, Ernst Günther", "Wysocki, Michael", "Thomsen, Thomas", "Greshake, Sascha", - "Thiesen, Marianne", - "Schüller, Michael", "Jacobs, Jürgen", "Weiler, Johannes", "Howe, Raphaela" - )); - - LINKE_BEZIRK.addCandidates(buildCandidates( - PAGELS_MANUELA, WIEGMANN_ROLAND, "Peters, Ralf", "Hoyer, Jonas", - KLEINERT_MIKEY, "Schulte, Kolja", "Huwald, Kristin", "Dr. Ritter, Sabine", - "Pirling, David", "Reipschläger, Bernhard", "Arndt, Thomas", "Artus, Holger", - "WLaab, Helene" - )); - - GRUENE_BEZIRK.addCandidates(buildCandidates( - "Kern, Lisa", "Hadji Mir Agha, Ali", "Dr. Putz, Miriam", KUHLMANN_DIETMAR, - "Dr. Hunter, Lynne", "Schmidt-Tobler, Falk", "Demirhan, Sina", KLEIN_ROBERT, - ERK_ARAMAK, MARTENS_JIM, "Warnecke, Anne Kathrin", DR_FISCHER_JOST_LEONHARDT, - "Kost, Cornelia", "Brandt, Christopher", "Schübel, Nina Joana", "Schmidt, Lutz", - "Wolf, Rita", - "Alam, Leon", "Schwarzwald, Cristina", "Thies, Nico", "Hasselmann, Annette", - "Koriath, Jan", KUELL_GABRIELA, "Köhler, Kevin", "Hericks, Susanne", - BOHNY_CARL_MARIA, - "Fester, Emilia", "Hofmann, Klaus-Dieter", "Neumann, Bernd", "Braasch, Julian", - DORSCH_SEBASTIAN, "Hasselmann, Harald" - )); - - FDP_BEZIRK.addCandidates(buildCandidates( - KRUEGER_KLAUS, SCHWANKE_BENJAMIN, "Cleven, Martina", "Zielinski, Mathias", - "Thiele, Camilla Joyce", "Krüger, Hannelore", "Welling, Jörg", "Häffs, Marvin", - "Korb, Hendrik", "Bahmer, Larissa", "Langbehn, Marian", "Szrubarski, Sebastian", - "Patzer, Heinrich-Otto", "Sosin, Tatjana", "Damm, Renate", MUELLER_SOENKSEN_BURKHARDT - )); - - AFD_BEZIRK.addCandidates(buildCandidates( - SCHOEMER_DIRK, ZIMMERMANN_ELKE, PILLATZKE_JOERG, "Pohl, Christian", - "Hanssen, Wolfgang", "Lemke, Martin", "Cremer-Thursby, Marc", "Schultz, Thomas", - "Ziob, Peter" - )); - - PIRATEN_BEZIRK.addCandidates(buildCandidates( - "Dr. Siebert-Schütz, Martin", "Mewes, Christoph", "Brümmer, Amadeus" - )); - } - - private static void setUpResultsOverall() { - SPD_BEZIRK.setVotingResult(VotingResult.builder() - .votesOnNomination(84703) - .votesThroughHealing(985) - .votesPerPosition(buildVotesPerPosition( - 8782, 3318, 3825, 3519, 2036, 3923, 5427, 2131, 566, 895, 1425, 1924, 819, - 2590, 2190, 3568, 742, 401, 554, 1133, 1268, 580, 254, 1217, 693, 1359, 481, - 650, 230, 1544, 1080, 1879, 400, 353, 525, 469, 196, 759, 872 - )) - .build()); - - CDU_BEZIRK.setVotingResult(VotingResult.builder() - .votesOnNomination(66245) - .votesThroughHealing(660) - .votesPerPosition(buildVotesPerPosition( - 5966, 1982, 1604, 2414, 1322, 2237, 1868, 1872, 1889, 881, 1253, 2921, 598, - 2721, 618, 941, 620, 837, 378, 594, 484, 584, 472, 408, 748, 650, 282, - 315, 1397, 502 - )) - .build()); - - LINKE_BEZIRK.setVotingResult(VotingResult.builder() - .votesOnNomination(47452) - .votesThroughHealing(330) - .votesPerPosition(buildVotesPerPosition( - 4890, 875, 1532, 1293, 2321, 696, 1843, 2212, 1488, 483, 764, 572, 892 - )) - .build()); - - GRUENE_BEZIRK.setVotingResult(VotingResult.builder() - .votesOnNomination(150842) - .votesThroughHealing(900) - .votesPerPosition(buildVotesPerPosition( - 10586, 5629, 12597, 2935, 3378, 735, 5662, 4021, 2065, 2469, 1561, 3557, 2290, - 1411, 2445, 1761, 1252, 1628, 652, 3524, 677, 843, 2375, 500, 1674, 3264, 1569, - 531, 531, 652, 5358, 2129 - )) - .build()); - - FDP_BEZIRK.setVotingResult(VotingResult.builder() - .votesOnNomination(24435) - .votesThroughHealing(240) - .votesPerPosition(buildVotesPerPosition( - 3528, 1964, 1633, 897, 1193, 355, 401, 1012, 496, 1242, 643, 300, 311, - 998, 505, 2181 - )) - .build()); - - AFD_BEZIRK.setVotingResult(VotingResult.builder() - .votesOnNomination(21625) - .votesThroughHealing(220) - .votesPerPosition(buildVotesPerPosition( - 1226, 1950, 3007, 874, 1032, 780, 535, 221, 283 - )) - .build()); - - PIRATEN_BEZIRK.setVotingResult(VotingResult.builder() - .votesOnNomination(6715) - .votesThroughHealing(25) - .votesPerPosition(buildVotesPerPosition( - 871, 1907, 987 - )) - .build()); - } - - private Map> setUpConstituencyResults() { - return Map.of(GRUENE_BEZIRK, List.of( + @NonNull + private Map> setUpConstituencyResults() { + return Map.of(GRUENE_BEZIRK_RESULT, List.of( new ElectedCandidate(GRUENE_BEZIRK.getCandidate(1), Elected.CONSTITUENCY), new ElectedCandidate(GRUENE_BEZIRK.getCandidate(2), Elected.CONSTITUENCY), new ElectedCandidate(GRUENE_BEZIRK.getCandidate(3), Elected.CONSTITUENCY), @@ -455,22 +391,4 @@ class CalculationServiceTest { new ElectedCandidate(GRUENE_BEZIRK.getCandidate(22), Elected.CONSTITUENCY) )); } - - private static Nomination buildNomination(String name, boolean supportVotesOnNomination) { - return new Nomination(name, supportVotesOnNomination); - } - - private static Collection buildCandidates(String... names) { - return Arrays.stream(names).map(name -> new Candidate(name, "")).toList(); - } - - @NonNull - private static Map buildVotesPerPosition(@NonNull int... votes) { - Map votesPerPosition = new HashMap<>(); - for (int i = 0; i < votes.length; i++) { - int vote = votes[i]; - votesPerPosition.put(i + 1, vote); - } - return votesPerPosition; - } } \ No newline at end of file