Added calculation of overall seat allocation

This commit is contained in:
2023-07-02 21:33:15 +02:00
parent 30d2c39580
commit 503b284b74
6 changed files with 408 additions and 155 deletions
@@ -2,6 +2,7 @@ package de.twomartens.wahlrecht.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -35,6 +36,10 @@ public class Nomination {
Arrays.stream(candidates).forEach(this::addCandidate);
}
public void addCandidates(Collection<Candidate> candidates) {
candidates.forEach(this::addCandidate);
}
public Candidate getCandidate(int position) {
return positionToCandidate.get(position);
}
@@ -0,0 +1,11 @@
package de.twomartens.wahlrecht.model;
import java.util.Deque;
import java.util.Map;
import lombok.Builder;
@Builder
public record SeatResult(Map<Nomination, Long> seatsPerNomination,
Deque<Double> usedElectionNumbers) {
}
@@ -8,18 +8,21 @@ import lombok.Getter;
public class VotingResult {
private final int votesOnNomination;
private final int votesThroughHealing;
private final Map<Integer, Integer> votesPerPosition;
private Integer totalVotes = null;
@Builder
public VotingResult(int votesOnNomination, Map<Integer, Integer> votesPerPosition) {
public VotingResult(int votesOnNomination, int votesThroughHealing,
Map<Integer, Integer> votesPerPosition) {
this.votesOnNomination = votesOnNomination;
this.votesThroughHealing = votesThroughHealing;
this.votesPerPosition = votesPerPosition;
}
public int getTotalVotes() {
if (totalVotes == null) {
totalVotes = votesOnNomination + votesPerPosition.values().stream()
totalVotes = votesOnNomination + votesThroughHealing + votesPerPosition.values().stream()
.reduce(0, Integer::sum);
}
return totalVotes;
@@ -3,7 +3,9 @@ package de.twomartens.wahlrecht.service;
import de.twomartens.wahlrecht.model.Candidate;
import de.twomartens.wahlrecht.model.Constituency;
import de.twomartens.wahlrecht.model.ElectedResult;
import de.twomartens.wahlrecht.model.Election;
import de.twomartens.wahlrecht.model.Nomination;
import de.twomartens.wahlrecht.model.SeatResult;
import de.twomartens.wahlrecht.model.VotingResult;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -19,7 +21,7 @@ import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
@Service
public class ConstituencyCalculationService {
public class CalculationService {
private final Deque<Double> electionNumberHistory = new ArrayDeque<>();
public ElectedResult calculateConstituency(@NonNull Constituency constituency,
@@ -41,6 +43,42 @@ public class ConstituencyCalculationService {
.build();
}
public SeatResult calculateOverallSeatDistribution(@NonNull Election election,
@NonNull Collection<Nomination> nominations) {
electionNumberHistory.clear();
int totalVotes = nominations.stream()
.map(Nomination::getVotingResult)
.map(VotingResult::getTotalVotes)
.reduce(0, Integer::sum);
List<Nomination> validNominations = new ArrayList<>();
int totalIgnoredVotes = 0;
for (Nomination nomination : nominations) {
if (passesVotingThreshold(election, totalVotes, nomination)) {
validNominations.add(nomination);
} else {
totalIgnoredVotes += nomination.getVotingResult().getTotalVotes();
}
}
totalVotes -= totalIgnoredVotes;
int numberOfSeats = election.totalNumberOfSeats();
double initialElectionNumber = totalVotes / (double) numberOfSeats;
Map<Nomination, Long> assignedSeatsPerNomination = calculateAssignedSeatsPerNomination(
validNominations, numberOfSeats, initialElectionNumber);
return SeatResult.builder()
.seatsPerNomination(assignedSeatsPerNomination)
.usedElectionNumbers(electionNumberHistory)
.build();
}
private static boolean passesVotingThreshold(Election election, int totalVotes, Nomination nomination) {
return totalVotes * election.votingThreshold().getMultiplier()
<= nomination.getVotingResult().getTotalVotes();
}
@NonNull
private Map<Nomination, Collection<Candidate>> findElectedCandidates(
@NonNull Map<Nomination, Long> assignedSeatsPerNomination) {