Added calculation of candidates on overall nomination

This commit is contained in:
2023-07-05 20:47:01 +02:00
parent 503b284b74
commit 488818319a
6 changed files with 214 additions and 38 deletions
@@ -3,5 +3,6 @@ package de.twomartens.wahlrecht.model;
public enum Elected {
NOT_ELECTED,
CONSTITUENCY,
OVERALL
OVERALL_NOMINATION_ORDER,
OVERALL_VOTE_ORDER
}
@@ -20,7 +20,6 @@ public class Nomination {
private final Map<Integer, Candidate> positionToCandidate = new HashMap<>();
@Include
private final String name;
@Getter
private final boolean supportVotesOnNomination;
@Getter
@Setter
@@ -40,6 +39,10 @@ public class Nomination {
candidates.forEach(this::addCandidate);
}
public List<Candidate> getCandidates() {
return new ArrayList<>(positionToCandidate.values());
}
public Candidate getCandidate(int position) {
return positionToCandidate.get(position);
}
@@ -47,4 +50,8 @@ public class Nomination {
public int getPosition(Candidate candidate) {
return candidateToPosition.get(candidate);
}
public boolean supportsVotesOnNomination() {
return supportVotesOnNomination;
}
}
@@ -5,7 +5,7 @@ import java.util.Map;
import lombok.Builder;
@Builder
public record SeatResult(Map<Nomination, Long> seatsPerNomination,
public record SeatResult(Map<Nomination, Integer> seatsPerNomination,
Deque<Double> usedElectionNumbers) {
}
@@ -27,4 +27,8 @@ public class VotingResult {
}
return totalVotes;
}
public int getTotalVotesWithoutHealing() {
return getTotalVotes() - votesThroughHealing;
}
}
@@ -2,6 +2,7 @@ package de.twomartens.wahlrecht.service;
import de.twomartens.wahlrecht.model.Candidate;
import de.twomartens.wahlrecht.model.Constituency;
import de.twomartens.wahlrecht.model.Elected;
import de.twomartens.wahlrecht.model.ElectedResult;
import de.twomartens.wahlrecht.model.Election;
import de.twomartens.wahlrecht.model.Nomination;
@@ -34,7 +35,7 @@ public class CalculationService {
int numberOfSeats = constituency.numberOfSeats();
double initialElectionNumber = totalVotes / (double) numberOfSeats;
Map<Nomination, Long> assignedSeatsPerNomination = calculateAssignedSeatsPerNomination(
Map<Nomination, Integer> assignedSeatsPerNomination = calculateAssignedSeatsPerNomination(
nominations, numberOfSeats, initialElectionNumber);
return ElectedResult.builder()
@@ -65,7 +66,7 @@ public class CalculationService {
int numberOfSeats = election.totalNumberOfSeats();
double initialElectionNumber = totalVotes / (double) numberOfSeats;
Map<Nomination, Long> assignedSeatsPerNomination = calculateAssignedSeatsPerNomination(
Map<Nomination, Integer> assignedSeatsPerNomination = calculateAssignedSeatsPerNomination(
validNominations, numberOfSeats, initialElectionNumber);
return SeatResult.builder()
@@ -74,6 +75,15 @@ public class CalculationService {
.build();
}
public ElectedResult calculateElectedOverallCandidates(Map<Nomination, Integer> seatsPerNomination) {
electionNumberHistory.clear();
return ElectedResult.builder()
.electedCandidates(findElectedCandidates(seatsPerNomination))
.usedElectionNumbers(electionNumberHistory)
.build();
}
private static boolean passesVotingThreshold(Election election, int totalVotes, Nomination nomination) {
return totalVotes * election.votingThreshold().getMultiplier()
<= nomination.getVotingResult().getTotalVotes();
@@ -81,7 +91,7 @@ public class CalculationService {
@NonNull
private Map<Nomination, Collection<Candidate>> findElectedCandidates(
@NonNull Map<Nomination, Long> assignedSeatsPerNomination) {
@NonNull Map<Nomination, Integer> assignedSeatsPerNomination) {
Map<Nomination, Collection<Candidate>> electedCandidates = new HashMap<>();
assignedSeatsPerNomination.entrySet().stream()
@@ -93,27 +103,59 @@ public class CalculationService {
}
@NonNull
private Collection<Candidate> findCandidates(@NonNull Nomination nomination, long numberOfSeats) {
List<Integer> positions = nomination.getVotingResult().getVotesPerPosition().entrySet().stream()
private Collection<Candidate> findCandidates(@NonNull Nomination nomination, int numberOfSeats) {
List<Integer> individualVotesOrder = nomination.getVotingResult().getVotesPerPosition()
.entrySet().stream()
.sorted(Comparator.comparing(Entry<Integer, Integer>::getValue).reversed())
.map(Entry::getKey)
.toList();
Collection<Candidate> elected = new ArrayList<>();
for (int i = 0; i < numberOfSeats; i++) {
Integer position = positions.get(i);
elected.add(nomination.getCandidate(position));
int seatsByVoteOrder = numberOfSeats;
if (nomination.supportsVotesOnNomination()) {
int seatsByNomination = calculateSeatsByNominationOrder(nomination, numberOfSeats);
seatsByVoteOrder = numberOfSeats - seatsByNomination;
List<Candidate> candidates = nomination.getCandidates();
int electedByNominationOrder = 0;
for (int i = 0; i < candidates.size() && electedByNominationOrder < seatsByNomination; i++) {
Candidate candidate = candidates.get(i);
if (candidate.getElected() == Elected.NOT_ELECTED) {
candidate.setElected(Elected.OVERALL_NOMINATION_ORDER);
elected.add(candidate);
electedByNominationOrder++;
}
}
}
int electedByVoteOrder = 0;
for (int i = 0; i < individualVotesOrder.size() && electedByVoteOrder < seatsByVoteOrder; i++) {
Integer position = individualVotesOrder.get(i);
Candidate candidate = nomination.getCandidate(position);
if (candidate.getElected() == Elected.NOT_ELECTED) {
candidate.setElected(nomination.supportsVotesOnNomination()
? Elected.OVERALL_VOTE_ORDER
: Elected.CONSTITUENCY);
elected.add(candidate);
electedByVoteOrder++;
}
}
return elected;
}
private Map<Nomination, Long> calculateAssignedSeatsPerNomination(
private static int calculateSeatsByNominationOrder(@NonNull Nomination nomination, int numberOfSeats) {
int votesOnNomination = nomination.getVotingResult().getVotesOnNomination();
int totalVotes = nomination.getVotingResult().getTotalVotesWithoutHealing();
return (int) Math.round((numberOfSeats * votesOnNomination) / (double) totalVotes);
}
private Map<Nomination, Integer> calculateAssignedSeatsPerNomination(
@NonNull Collection<Nomination> nominations,
int numberOfSeats, Double initialElectionNumber) {
double electionNumber = initialElectionNumber;
long assignedSeats;
Map<Nomination, Long> assignedSeatsPerNomination;
Map<Nomination, Integer> assignedSeatsPerNomination;
do {
electionNumberHistory.add(electionNumber);
@@ -124,9 +166,9 @@ public class CalculationService {
seatsPerNomination.put(nomination, seatNumber);
}
assignedSeatsPerNomination = seatsPerNomination.entrySet().stream()
.map(entry -> Map.entry(entry.getKey(), Math.round(entry.getValue())))
.map(entry -> Map.entry(entry.getKey(), (int) Math.round(entry.getValue())))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
assignedSeats = assignedSeatsPerNomination.values().stream().reduce(0L, Long::sum);
assignedSeats = assignedSeatsPerNomination.values().stream().reduce(0, Integer::sum);
if (assignedSeats < numberOfSeats) {
// election number was too big, decrease
@@ -145,7 +187,7 @@ public class CalculationService {
private static double calculateHigherElectionNumber(Double initialElectionNumber,
@NonNull Map<Nomination, Double> seatsPerNomination,
Map<Nomination, Long> assignedSeatsPerNomination) {
Map<Nomination, Integer> assignedSeatsPerNomination) {
double electionNumber;
electionNumber = seatsPerNomination.entrySet().stream()
.map(entry -> Map.entry(entry.getKey(),
@@ -159,7 +201,7 @@ public class CalculationService {
private static double calculateLowerElectionNumber(Double initialElectionNumber,
@NonNull Map<Nomination, Double> seatsPerNomination,
Map<Nomination, Long> assignedSeatsPerNomination) {
Map<Nomination, Integer> assignedSeatsPerNomination) {
double electionNumber;
electionNumber = seatsPerNomination.entrySet().stream()
.map(entry -> Map.entry(entry.getKey(),