chore: lint using google formatter 1.10 (AOSP version)

https://github.com/google/google-java-format/releases/
pull/14/head^2 0.3.0
Dominique Merle 3 years ago
parent 85e0c3e419
commit 2f6ed8b6cc

@ -4,69 +4,69 @@ import java.math.BigInteger;
public class CollectedTally implements TallyInterface {
Integer amountOfProposals = 0;
Integer amountOfGrades = 0;
Integer amountOfProposals = 0;
Integer amountOfGrades = 0;
ProposalTally[] proposalsTallies;
ProposalTally[] proposalsTallies;
public CollectedTally(Integer amountOfProposals, Integer amountOfGrades) {
setAmountOfProposals(amountOfProposals);
setAmountOfGrades(amountOfGrades);
proposalsTallies = new ProposalTally[amountOfProposals];
for (int i = 0; i < amountOfProposals; i++) {
ProposalTally proposalTally = new ProposalTally();
Integer[] tally = new Integer[amountOfGrades];
for (int j = 0; j < amountOfGrades; j++) {
tally[j] = 0;
}
proposalTally.setTally(tally);
proposalsTallies[i] = proposalTally;
}
}
public CollectedTally(Integer amountOfProposals, Integer amountOfGrades) {
setAmountOfProposals(amountOfProposals);
setAmountOfGrades(amountOfGrades);
@Override
public ProposalTallyInterface[] getProposalsTallies() {
return proposalsTallies;
}
proposalsTallies = new ProposalTally[amountOfProposals];
for (int i = 0; i < amountOfProposals; i++) {
ProposalTally proposalTally = new ProposalTally();
Integer[] tally = new Integer[amountOfGrades];
for (int j = 0; j < amountOfGrades; j++) {
tally[j] = 0;
}
proposalTally.setTally(tally);
proposalsTallies[i] = proposalTally;
}
}
@Override
public BigInteger getAmountOfJudges() {
return guessAmountOfJudges();
}
@Override
public ProposalTallyInterface[] getProposalsTallies() {
return proposalsTallies;
}
@Override
public Integer getAmountOfProposals() {
return this.amountOfProposals;
}
@Override
public BigInteger getAmountOfJudges() {
return guessAmountOfJudges();
}
public void setAmountOfProposals(Integer amountOfProposals) {
this.amountOfProposals = amountOfProposals;
}
@Override
public Integer getAmountOfProposals() {
return this.amountOfProposals;
}
public Integer getAmountOfGrades() {
return amountOfGrades;
}
public void setAmountOfProposals(Integer amountOfProposals) {
this.amountOfProposals = amountOfProposals;
}
public void setAmountOfGrades(Integer amountOfGrades) {
this.amountOfGrades = amountOfGrades;
}
public Integer getAmountOfGrades() {
return amountOfGrades;
}
protected BigInteger guessAmountOfJudges() {
BigInteger amountOfJudges = BigInteger.ZERO;
for (ProposalTallyInterface proposalTally : getProposalsTallies()) {
amountOfJudges = proposalTally.getAmountOfJudgments().max(amountOfJudges);
}
return amountOfJudges;
}
public void setAmountOfGrades(Integer amountOfGrades) {
this.amountOfGrades = amountOfGrades;
}
public void collect(Integer proposal, Integer grade) {
assert(0 <= proposal);
assert(amountOfProposals > proposal);
assert(0 <= grade);
assert(amountOfGrades > grade);
BigInteger[] tally = proposalsTallies[proposal].getTally();
tally[grade] = tally[grade].add(BigInteger.ONE);
}
protected BigInteger guessAmountOfJudges() {
BigInteger amountOfJudges = BigInteger.ZERO;
for (ProposalTallyInterface proposalTally : getProposalsTallies()) {
amountOfJudges = proposalTally.getAmountOfJudgments().max(amountOfJudges);
}
return amountOfJudges;
}
public void collect(Integer proposal, Integer grade) {
assert (0 <= proposal);
assert (amountOfProposals > proposal);
assert (0 <= grade);
assert (amountOfGrades > grade);
BigInteger[] tally = proposalsTallies[proposal].getTally();
tally[grade] = tally[grade].add(BigInteger.ONE);
}
}

@ -3,48 +3,45 @@ package fr.mieuxvoter.mj;
import java.math.BigInteger;
/**
* Fill the missing judgments into the grade defined by `getDefaultGrade()`.
* This is an abstract class to dry code between static default grade and median default grade.
* Fill the missing judgments into the grade defined by `getDefaultGrade()`. This is an abstract
* class to dry code between static default grade and median default grade.
*/
abstract public class DefaultGradeTally extends Tally implements TallyInterface {
/**
* Override this to choose the default grade for a given proposal.
*/
abstract protected Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally);
// <domi41> /me is confused with why we need constructors in an abstract class?
public DefaultGradeTally(TallyInterface tally) {
super(tally.getProposalsTallies(), tally.getAmountOfJudges());
}
public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) {
super(proposalsTallies, amountOfJudges);
}
public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) {
super(proposalsTallies, amountOfJudges);
}
public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) {
super(proposalsTallies, amountOfJudges);
}
protected void fillWithDefaultGrade() {
int amountOfProposals = getAmountOfProposals();
for (int i = 0 ; i < amountOfProposals ; i++) {
ProposalTallyInterface proposalTally = getProposalsTallies()[i];
Integer defaultGrade = getDefaultGradeForProposal(proposalTally);
BigInteger amountOfJudgments = proposalTally.getAmountOfJudgments();
BigInteger missingAmount = this.amountOfJudges.subtract(amountOfJudgments);
int missingSign = missingAmount.compareTo(BigInteger.ZERO);
assert(0 <= missingSign); // ERROR: More judgments than judges!
if (0 < missingSign) {
BigInteger[] rawTally = proposalTally.getTally();
rawTally[defaultGrade] = rawTally[defaultGrade].add(missingAmount);
}
}
}
public abstract class DefaultGradeTally extends Tally implements TallyInterface {
/** Override this to choose the default grade for a given proposal. */
protected abstract Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally);
// <domi41> /me is confused with why we need constructors in an abstract class?
public DefaultGradeTally(TallyInterface tally) {
super(tally.getProposalsTallies(), tally.getAmountOfJudges());
}
public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) {
super(proposalsTallies, amountOfJudges);
}
public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) {
super(proposalsTallies, amountOfJudges);
}
public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) {
super(proposalsTallies, amountOfJudges);
}
protected void fillWithDefaultGrade() {
int amountOfProposals = getAmountOfProposals();
for (int i = 0; i < amountOfProposals; i++) {
ProposalTallyInterface proposalTally = getProposalsTallies()[i];
Integer defaultGrade = getDefaultGradeForProposal(proposalTally);
BigInteger amountOfJudgments = proposalTally.getAmountOfJudgments();
BigInteger missingAmount = this.amountOfJudges.subtract(amountOfJudgments);
int missingSign = missingAmount.compareTo(BigInteger.ZERO);
assert (0 <= missingSign); // ERROR: More judgments than judges!
if (0 < missingSign) {
BigInteger[] rawTally = proposalTally.getTally();
rawTally[defaultGrade] = rawTally[defaultGrade].add(missingAmount);
}
}
}
}

@ -1,23 +1,18 @@
package fr.mieuxvoter.mj;
/**
* A Deliberator takes in a poll's Tally,
* which holds the amount of judgments of each grade received by each Proposal,
* and outputs that poll's Result, that is the final rank of each Proposal.
*
* Ranks start at 1 ("best"), and increment towards "worst".
* Two proposal may share the same rank, in extreme equality cases.
*
* This is the main API of this library.
*
* See MajorityJudgmentDeliberator for an implementation.
* One could implement other deliberators, such as:
* - CentralJudgmentDeliberator
* - UsualJudgmentDeliberator
* A Deliberator takes in a poll's Tally, which holds the amount of judgments of each grade received
* by each Proposal, and outputs that poll's Result, that is the final rank of each Proposal.
*
* <p>Ranks start at 1 ("best"), and increment towards "worst". Two proposal may share the same
* rank, in extreme equality cases.
*
* <p>This is the main API of this library.
*
* <p>See MajorityJudgmentDeliberator for an implementation. One could implement other deliberators,
* such as: - CentralJudgmentDeliberator - UsualJudgmentDeliberator
*/
public interface DeliberatorInterface {
public ResultInterface deliberate(TallyInterface tally) throws InvalidTallyException;
public ResultInterface deliberate(TallyInterface tally) throws InvalidTallyException;
}

@ -1,19 +1,13 @@
package fr.mieuxvoter.mj;
/**
* Raised when the provided tally holds negative values, or infinity.
*/
/** Raised when the provided tally holds negative values, or infinity. */
class IncoherentTallyException extends InvalidTallyException {
private static final long serialVersionUID = 5858986651601202903L;
private static final long serialVersionUID = 5858986651601202903L;
@Override
public String getMessage() {
return (
"The provided tally holds negative values, or infinity. "
+
(null == super.getMessage() ? "" : super.getMessage())
);
}
}
@Override
public String getMessage() {
return ("The provided tally holds negative values, or infinity. "
+ (null == super.getMessage() ? "" : super.getMessage()));
}
}

@ -2,11 +2,8 @@ package fr.mieuxvoter.mj;
import java.security.InvalidParameterException;
/**
* Raised when the provided tally is invalid.
*/
/** Raised when the provided tally is invalid. */
class InvalidTallyException extends InvalidParameterException {
private static final long serialVersionUID = 3033391835216704620L;
}
private static final long serialVersionUID = 3033391835216704620L;
}

@ -4,202 +4,199 @@ import java.math.BigInteger;
import java.util.Arrays;
import java.util.Comparator;
/**
* Deliberate using Majority Judgment.
*
* Sorts Proposals by their median Grade.
* When two proposals share the same median Grade,
* give reason to the largest group of people that did not give the median Grade.
*
* This algorithm is score-based, for performance (and possible parallelization).
* Each Proposal gets a score, higher (lexicographically) is "better" (depends of the meaning of the Grades).
* We use Strings instead of Integers or raw Bits for the score. Improve if you feel like it and can benchmark things.
*
* https://en.wikipedia.org/wiki/Majority_judgment
*
* <p>Sorts Proposals by their median Grade. When two proposals share the same median Grade, give
* reason to the largest group of people that did not give the median Grade.
*
* <p>This algorithm is score-based, for performance (and possible parallelization). Each Proposal
* gets a score, higher (lexicographically) is "better" (depends of the meaning of the Grades). We
* use Strings instead of Integers or raw Bits for the score. Improve if you feel like it and can
* benchmark things.
*
* <p>https://en.wikipedia.org/wiki/Majority_judgment
* https://fr.wikipedia.org/wiki/Jugement_majoritaire
*
* Should this class be `final`?
*
* <p>Should this class be `final`?
*/
final public class MajorityJudgmentDeliberator implements DeliberatorInterface {
protected boolean favorContestation = true;
protected boolean numerizeScore = false;
public MajorityJudgmentDeliberator() {}
public MajorityJudgmentDeliberator(boolean favorContestation) {
this.favorContestation = favorContestation;
}
public MajorityJudgmentDeliberator(boolean favorContestation, boolean numerizeScore) {
this.favorContestation = favorContestation;
this.numerizeScore = numerizeScore;
}
@Override
public ResultInterface deliberate(TallyInterface tally) throws InvalidTallyException {
checkTally(tally);
ProposalTallyInterface[] tallies = tally.getProposalsTallies();
BigInteger amountOfJudges = tally.getAmountOfJudges();
Integer amountOfProposals = tally.getAmountOfProposals();
Result result = new Result();
ProposalResult[] proposalResults = new ProposalResult[amountOfProposals];
// I. Compute the scores of each Proposal
for (int proposalIndex = 0; proposalIndex < amountOfProposals; proposalIndex++) {
ProposalTallyInterface proposalTally = tallies[proposalIndex];
String score = computeScore(proposalTally, amountOfJudges);
ProposalTallyAnalysis analysis = new ProposalTallyAnalysis(proposalTally, this.favorContestation);
ProposalResult proposalResult = new ProposalResult();
proposalResult.setScore(score);
proposalResult.setAnalysis(analysis);
//proposalResult.setRank(???); // rank is computed below, AFTER the score pass
proposalResults[proposalIndex] = proposalResult;
}
// II. Sort Proposals by score (lexicographical inverse)
ProposalResult[] proposalResultsSorted = proposalResults.clone();
assert(proposalResultsSorted[0].hashCode() == proposalResults[0].hashCode()); // we need a shallow clone
Arrays.sort(proposalResultsSorted, new Comparator<ProposalResultInterface>() {
@Override
public int compare(ProposalResultInterface p0, ProposalResultInterface p1) {
return p1.getScore().compareTo(p0.getScore());
}
});
// III. Attribute a rank to each Proposal
Integer rank = 1;
for (int proposalIndex = 0; proposalIndex < amountOfProposals; proposalIndex++) {
ProposalResult proposalResult = proposalResultsSorted[proposalIndex];
Integer actualRank = rank;
if (proposalIndex > 0) {
ProposalResult proposalResultBefore = proposalResultsSorted[proposalIndex-1];
if (proposalResult.getScore().contentEquals(proposalResultBefore.getScore())) {
actualRank = proposalResultBefore.getRank();
}
}
proposalResult.setRank(actualRank);
rank += 1;
}
result.setProposalResults(proposalResults);
return result;
}
protected void checkTally(TallyInterface tally) throws UnbalancedTallyException {
if ( ! isTallyCoherent(tally)) {
throw new IncoherentTallyException();
}
if ( ! isTallyBalanced(tally)) {
throw new UnbalancedTallyException();
}
}
protected boolean isTallyCoherent(TallyInterface tally) {
boolean coherent = true;
for (ProposalTallyInterface proposalTally : tally.getProposalsTallies()) {
for (BigInteger gradeTally : proposalTally.getTally()) {
if (-1 == gradeTally.compareTo(BigInteger.ZERO)) {
coherent = false; // negative tallies are not coherent
}
}
}
return coherent;
}
protected boolean isTallyBalanced(TallyInterface tally) {
boolean balanced = true;
BigInteger amountOfJudges = BigInteger.ZERO;
boolean firstProposal = true;
for (ProposalTallyInterface proposalTally : tally.getProposalsTallies()) {
if (firstProposal) {
amountOfJudges = proposalTally.getAmountOfJudgments();
firstProposal = false;
} else {
if (0 != amountOfJudges.compareTo(proposalTally.getAmountOfJudgments())) {
balanced = false;
}
}
}
return balanced;
}
/**
* @see computeScore() below
*/
protected String computeScore(ProposalTallyInterface tally, BigInteger amountOfJudges) {
return computeScore(tally, amountOfJudges, this.favorContestation, this.numerizeScore);
}
/**
* A higher score means a better rank.
* Assumes that grades' tallies are provided from "worst" grade to "best" grade.
*
* @param tally Holds the tallies of each Grade for a single Proposal
* @param amountOfJudges
* @param favorContestation Use the lower median, for example
* @param onlyNumbers Do not use separation characters, match `^[0-9]+$`
* @return the score of the proposal
*/
protected String computeScore(
ProposalTallyInterface tally,
BigInteger amountOfJudges,
Boolean favorContestation,
Boolean onlyNumbers
) {
ProposalTallyAnalysis analysis = new ProposalTallyAnalysis();
int amountOfGrades = tally.getTally().length;
int digitsForGrade = countDigits(amountOfGrades);
int digitsForGroup = countDigits(amountOfJudges) + 1;
ProposalTallyInterface currentTally = tally.duplicate();
String score = "";
for (int i = 0; i < amountOfGrades; i++) {
analysis.reanalyze(currentTally, favorContestation);
if (0 < i && ! onlyNumbers) {
score += "/";
}
score += String.format(
"%0" + digitsForGrade + "d",
analysis.getMedianGrade()
);
if ( ! onlyNumbers) {
score += "_";
}
score += String.format(
"%0" + digitsForGroup + "d",
// We offset by amountOfJudges to keep a lexicographical order (no negatives)
// amountOfJudges + secondMedianGroupSize * secondMedianGroupSign
amountOfJudges.add(
analysis.getSecondMedianGroupSize().multiply(
BigInteger.valueOf(analysis.getSecondMedianGroupSign())
)
)
);
currentTally.moveJudgments(analysis.getMedianGrade(), analysis.getSecondMedianGrade());
}
return score;
}
protected int countDigits(int number) {
return ("" + number).length();
}
protected int countDigits(BigInteger number) {
return ("" + number).length();
}
public final class MajorityJudgmentDeliberator implements DeliberatorInterface {
protected boolean favorContestation = true;
protected boolean numerizeScore = false;
public MajorityJudgmentDeliberator() {}
public MajorityJudgmentDeliberator(boolean favorContestation) {
this.favorContestation = favorContestation;
}
public MajorityJudgmentDeliberator(boolean favorContestation, boolean numerizeScore) {
this.favorContestation = favorContestation;
this.numerizeScore = numerizeScore;
}
@Override
public ResultInterface deliberate(TallyInterface tally) throws InvalidTallyException {
checkTally(tally);
ProposalTallyInterface[] tallies = tally.getProposalsTallies();
BigInteger amountOfJudges = tally.getAmountOfJudges();
Integer amountOfProposals = tally.getAmountOfProposals();
Result result = new Result();
ProposalResult[] proposalResults = new ProposalResult[amountOfProposals];
// I. Compute the scores of each Proposal
for (int proposalIndex = 0; proposalIndex < amountOfProposals; proposalIndex++) {
ProposalTallyInterface proposalTally = tallies[proposalIndex];
String score = computeScore(proposalTally, amountOfJudges);
ProposalTallyAnalysis analysis =
new ProposalTallyAnalysis(proposalTally, this.favorContestation);
ProposalResult proposalResult = new ProposalResult();
proposalResult.setScore(score);
proposalResult.setAnalysis(analysis);
// proposalResult.setRank(???); // rank is computed below, AFTER the score pass
proposalResults[proposalIndex] = proposalResult;
}
// II. Sort Proposals by score (lexicographical inverse)
ProposalResult[] proposalResultsSorted = proposalResults.clone();
assert (proposalResultsSorted[0].hashCode()
== proposalResults[0].hashCode()); // we need a shallow clone
Arrays.sort(
proposalResultsSorted,
new Comparator<ProposalResultInterface>() {
@Override
public int compare(ProposalResultInterface p0, ProposalResultInterface p1) {
return p1.getScore().compareTo(p0.getScore());
}
});
// III. Attribute a rank to each Proposal
Integer rank = 1;
for (int proposalIndex = 0; proposalIndex < amountOfProposals; proposalIndex++) {
ProposalResult proposalResult = proposalResultsSorted[proposalIndex];
Integer actualRank = rank;
if (proposalIndex > 0) {
ProposalResult proposalResultBefore = proposalResultsSorted[proposalIndex - 1];
if (proposalResult.getScore().contentEquals(proposalResultBefore.getScore())) {
actualRank = proposalResultBefore.getRank();
}
}
proposalResult.setRank(actualRank);
rank += 1;
}
result.setProposalResults(proposalResults);
return result;
}
protected void checkTally(TallyInterface tally) throws UnbalancedTallyException {
if (!isTallyCoherent(tally)) {
throw new IncoherentTallyException();
}
if (!isTallyBalanced(tally)) {
throw new UnbalancedTallyException();
}
}
protected boolean isTallyCoherent(TallyInterface tally) {
boolean coherent = true;
for (ProposalTallyInterface proposalTally : tally.getProposalsTallies()) {
for (BigInteger gradeTally : proposalTally.getTally()) {
if (-1 == gradeTally.compareTo(BigInteger.ZERO)) {
coherent = false; // negative tallies are not coherent
}
}
}
return coherent;
}
protected boolean isTallyBalanced(TallyInterface tally) {
boolean balanced = true;
BigInteger amountOfJudges = BigInteger.ZERO;
boolean firstProposal = true;
for (ProposalTallyInterface proposalTally : tally.getProposalsTallies()) {
if (firstProposal) {
amountOfJudges = proposalTally.getAmountOfJudgments();
firstProposal = false;
} else {
if (0 != amountOfJudges.compareTo(proposalTally.getAmountOfJudgments())) {
balanced = false;
}
}
}
return balanced;
}
/** @see computeScore() below */
protected String computeScore(ProposalTallyInterface tally, BigInteger amountOfJudges) {
return computeScore(tally, amountOfJudges, this.favorContestation, this.numerizeScore);
}
/**
* A higher score means a better rank. Assumes that grades' tallies are provided from "worst"
* grade to "best" grade.
*
* @param tally Holds the tallies of each Grade for a single Proposal
* @param amountOfJudges
* @param favorContestation Use the lower median, for example
* @param onlyNumbers Do not use separation characters, match `^[0-9]+$`
* @return the score of the proposal
*/
protected String computeScore(
ProposalTallyInterface tally,
BigInteger amountOfJudges,
Boolean favorContestation,
Boolean onlyNumbers) {
ProposalTallyAnalysis analysis = new ProposalTallyAnalysis();
int amountOfGrades = tally.getTally().length;
int digitsForGrade = countDigits(amountOfGrades);
int digitsForGroup = countDigits(amountOfJudges) + 1;
ProposalTallyInterface currentTally = tally.duplicate();
String score = "";
for (int i = 0; i < amountOfGrades; i++) {
analysis.reanalyze(currentTally, favorContestation);
if (0 < i && !onlyNumbers) {
score += "/";
}
score += String.format("%0" + digitsForGrade + "d", analysis.getMedianGrade());
if (!onlyNumbers) {
score += "_";
}
score +=
String.format(
"%0" + digitsForGroup + "d",
// We offset by amountOfJudges to keep a lexicographical order (no
// negatives)
// amountOfJudges + secondMedianGroupSize * secondMedianGroupSign
amountOfJudges.add(
analysis.getSecondMedianGroupSize()
.multiply(
BigInteger.valueOf(
analysis.getSecondMedianGroupSign()))));
currentTally.moveJudgments(analysis.getMedianGrade(), analysis.getSecondMedianGrade());
}
return score;
}
protected int countDigits(int number) {
return ("" + number).length();
}
protected int countDigits(BigInteger number) {
return ("" + number).length();
}
}

@ -3,36 +3,35 @@ package fr.mieuxvoter.mj;
import java.math.BigInteger;
/**
* Fill the missing judgments into the median grade of each proposal.
* Useful when the proposals have not received the exact same amount of votes and
* the median grade is considered a sane default.
* Fill the missing judgments into the median grade of each proposal. Useful when the proposals have
* not received the exact same amount of votes and the median grade is considered a sane default.
*/
public class MedianDefaultTally extends DefaultGradeTally implements TallyInterface {
public MedianDefaultTally(TallyInterface tally) {
super(tally.getProposalsTallies(), tally.getAmountOfJudges());
fillWithDefaultGrade();
}
public MedianDefaultTally(ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) {
super(proposalsTallies, amountOfJudges);
fillWithDefaultGrade();
}
public MedianDefaultTally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) {
super(proposalsTallies, amountOfJudges);
fillWithDefaultGrade();
}
public MedianDefaultTally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) {
super(proposalsTallies, amountOfJudges);
fillWithDefaultGrade();
}
@Override
protected Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally) {
ProposalTallyAnalysis analysis = new ProposalTallyAnalysis(proposalTally);
return analysis.getMedianGrade();
}
public MedianDefaultTally(TallyInterface tally) {
super(tally.getProposalsTallies(), tally.getAmountOfJudges());
fillWithDefaultGrade();
}
public MedianDefaultTally(
ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) {
super(proposalsTallies, amountOfJudges);
fillWithDefaultGrade();
}
public MedianDefaultTally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) {
super(proposalsTallies, amountOfJudges);
fillWithDefaultGrade();
}
public MedianDefaultTally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) {
super(proposalsTallies, amountOfJudges);
fillWithDefaultGrade();
}
@Override
protected Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally) {
ProposalTallyAnalysis analysis = new ProposalTallyAnalysis(proposalTally);
return analysis.getMedianGrade();
}
}

@ -4,76 +4,72 @@ import java.math.BigInteger;
import java.security.InvalidParameterException;
/**
* The deliberator expects the proposals' tallies to hold the same amount of judgments.
* This NormalizedTally accepts tallies with disparate amounts of judgments per proposal,
* and normalizes them to their least common multiple, which amounts to using percentages,
* except we don't use floating-point arithmetic.
*
* This is useful when there are too many proposals for judges to be expected to judge them all,
* The deliberator expects the proposals' tallies to hold the same amount of judgments. This
* NormalizedTally accepts tallies with disparate amounts of judgments per proposal, and normalizes
* them to their least common multiple, which amounts to using percentages, except we don't use
* floating-point arithmetic.
*
* <p>This is useful when there are too many proposals for judges to be expected to judge them all,
* and all the proposals received reasonably similar amounts of judgments.
*/
public class NormalizedTally extends Tally implements TallyInterface {
public NormalizedTally(ProposalTallyInterface[] proposalsTallies) {
super(proposalsTallies);
initializeFromProposalsTallies(proposalsTallies);
}
public NormalizedTally(ProposalTallyInterface[] proposalsTallies) {
super(proposalsTallies);
initializeFromProposalsTallies(proposalsTallies);
}
public NormalizedTally(TallyInterface tally) {
super(tally.getProposalsTallies());
initializeFromProposalsTallies(tally.getProposalsTallies());
}
public NormalizedTally(TallyInterface tally) {
super(tally.getProposalsTallies());
initializeFromProposalsTallies(tally.getProposalsTallies());
}
protected void initializeFromProposalsTallies(ProposalTallyInterface[] proposalsTallies) {
Integer amountOfProposals = getAmountOfProposals();
// Compute the Least Common Multiple
BigInteger amountOfJudges = BigInteger.ONE;
for (ProposalTallyInterface proposalTally : proposalsTallies) {
amountOfJudges = lcm(amountOfJudges, proposalTally.getAmountOfJudgments());
}
if (0 == amountOfJudges.compareTo(BigInteger.ZERO)) {
throw new InvalidParameterException("Cannot normalize: one or more proposals have no judgments.");
}
// Normalize proposals to the LCM
ProposalTally[] normalizedTallies = new ProposalTally[amountOfProposals];
for (int i = 0 ; i < amountOfProposals ; i++) {
ProposalTallyInterface proposalTally = proposalsTallies[i];
ProposalTally normalizedTally = new ProposalTally(proposalTally);
BigInteger factor = amountOfJudges.divide(proposalTally.getAmountOfJudgments());
Integer amountOfGrades = proposalTally.getTally().length;
BigInteger[] gradesTallies = normalizedTally.getTally();
for (int j = 0 ; j < amountOfGrades; j++) {
gradesTallies[j] = gradesTallies[j].multiply(factor);
}
normalizedTallies[i] = normalizedTally;
}
setProposalsTallies(normalizedTallies);
setAmountOfJudges(amountOfJudges);
}
protected void initializeFromProposalsTallies(ProposalTallyInterface[] proposalsTallies) {
Integer amountOfProposals = getAmountOfProposals();
/**
* Least Common Multiple
*
* http://en.wikipedia.org/wiki/Least_common_multiple
*
* lcm( 6, 9 ) = 18
* lcm( 4, 9 ) = 36
* lcm( 0, 9 ) = 0
* lcm( 0, 0 ) = 0
*
* @author www.java2s.com
* @param a first integer
* @param b second integer
* @return least common multiple of a and b
*/
public static BigInteger lcm(BigInteger a, BigInteger b) {
if (a.signum() == 0 || b.signum() == 0)
return BigInteger.ZERO;
return a.divide(a.gcd(b)).multiply(b).abs();
}
// Compute the Least Common Multiple
BigInteger amountOfJudges = BigInteger.ONE;
for (ProposalTallyInterface proposalTally : proposalsTallies) {
amountOfJudges = lcm(amountOfJudges, proposalTally.getAmountOfJudgments());
}
if (0 == amountOfJudges.compareTo(BigInteger.ZERO)) {
throw new InvalidParameterException(
"Cannot normalize: one or more proposals have no judgments.");
}
// Normalize proposals to the LCM
ProposalTally[] normalizedTallies = new ProposalTally[amountOfProposals];
for (int i = 0; i < amountOfProposals; i++) {
ProposalTallyInterface proposalTally = proposalsTallies[i];
ProposalTally normalizedTally = new ProposalTally(proposalTally);
BigInteger factor = amountOfJudges.divide(proposalTally.getAmountOfJudgments());
Integer amountOfGrades = proposalTally.getTally().length;
BigInteger[] gradesTallies = normalizedTally.getTally();
for (int j = 0; j < amountOfGrades; j++) {
gradesTallies[j] = gradesTallies[j].multiply(factor);
}
normalizedTallies[i] = normalizedTally;
}
setProposalsTallies(normalizedTallies);
setAmountOfJudges(amountOfJudges);
}
/**
* Least Common Multiple
*
* <p>http://en.wikipedia.org/wiki/Least_common_multiple
*
* <p>lcm( 6, 9 ) = 18 lcm( 4, 9 ) = 36 lcm( 0, 9 ) = 0 lcm( 0, 0 ) = 0
*
* @author www.java2s.com
* @param a first integer
* @param b second integer
* @return least common multiple of a and b
*/
public static BigInteger lcm(BigInteger a, BigInteger b) {
if (a.signum() == 0 || b.signum() == 0) return BigInteger.ZERO;
return a.divide(a.gcd(b)).multiply(b).abs();
}
}

@ -1,36 +1,34 @@
package fr.mieuxvoter.mj;
public class ProposalResult implements ProposalResultInterface {
protected Integer rank;
protected String score;
protected Integer rank;
protected ProposalTallyAnalysis analysis;
protected String score;
public Integer getRank() {
return rank;
}
protected ProposalTallyAnalysis analysis;
public void setRank(Integer rank) {
this.rank = rank;
}
public Integer getRank() {
return rank;
}
public String getScore() {
return score;
}
public void setRank(Integer rank) {
this.rank = rank;
}
public void setScore(String score) {
this.score = score;
}
public String getScore() {
return score;
}
public ProposalTallyAnalysis getAnalysis() {
return analysis;
}
public void setScore(String score) {
this.score = score;
}
public void setAnalysis(ProposalTallyAnalysis analysis) {
this.analysis = analysis;
}
public ProposalTallyAnalysis getAnalysis() {
return analysis;
}
public void setAnalysis(ProposalTallyAnalysis analysis) {
this.analysis = analysis;
}
}

@ -1,27 +1,21 @@
package fr.mieuxvoter.mj;
public interface ProposalResultInterface {
/**
* Rank starts at 1 ("best" proposal), and goes upwards.
* Multiple Proposals may receive the same rank,
* in the extreme case where they received the exact same judgments,
* or the same judgment repartition in normalized tallies.
*/
public Integer getRank();
/**
* This score was used to compute the rank.
* It is made of integer characters, with zeroes for padding.
* Inverse lexicographical order: "higher" is "better".
* You're probably never going to need this, but it's here anyway.
*/
public String getScore();
/**
* Rank starts at 1 ("best" proposal), and goes upwards. Multiple Proposals may receive the same
* rank, in the extreme case where they received the exact same judgments, or the same judgment
* repartition in normalized tallies.
*/
public Integer getRank();
/**
* Get more data about the proposal tally, such as the median grade.
*/
public ProposalTallyAnalysis getAnalysis();
/**
* This score was used to compute the rank. It is made of integer characters, with zeroes for
* padding. Inverse lexicographical order: "higher" is "better". You're probably never going to
* need this, but it's here anyway.
*/
public String getScore();
/** Get more data about the proposal tally, such as the median grade. */
public ProposalTallyAnalysis getAnalysis();
}

@ -5,89 +5,88 @@ import java.util.Arrays;
public class ProposalTally implements ProposalTallyInterface {
/**
* Amounts of judgments received per grade, from "worst" grade to "best" grade.
* Those are BigIntegers because of our LCM-based normalization shenanigans.
*/
protected BigInteger[] tally;
public ProposalTally() {}
public ProposalTally(String[] tally) {
setTally(tally);
}
public ProposalTally(Integer[] tally) {
setTally(tally);
}
public ProposalTally(Long[] tally) {
setTally(tally);
}
public ProposalTally(BigInteger[] tally) {
setTally(tally);
}
public ProposalTally(ProposalTallyInterface proposalTally) {
setTally(Arrays.copyOf(proposalTally.getTally(), proposalTally.getTally().length));
}
public void setTally(String[] tally) {
int tallyLength = tally.length;
BigInteger[] bigTally = new BigInteger[tallyLength];
for (int i = 0 ; i < tallyLength ; i++) {
bigTally[i] = new BigInteger(tally[i]);
}
setTally(bigTally);
}
public void setTally(Integer[] tally) {
int tallyLength = tally.length;
BigInteger[] bigTally = new BigInteger[tallyLength];
for (int i = 0 ; i < tallyLength ; i++) {
bigTally[i] = BigInteger.valueOf(tally[i]);
}
setTally(bigTally);
}
public void setTally(Long[] tally) {
int tallyLength = tally.length;
BigInteger[] bigTally = new BigInteger[tallyLength];
for (int i = 0 ; i < tallyLength ; i++) {
bigTally[i] = BigInteger.valueOf(tally[i]);
}
setTally(bigTally);
}
public void setTally(BigInteger[] tally) {
this.tally = tally;
}
@Override
public BigInteger[] getTally() {
return this.tally;
}
@Override
public ProposalTallyInterface duplicate() {
return new ProposalTally(Arrays.copyOf(this.tally, this.tally.length));
}
@Override
public void moveJudgments(Integer fromGrade, Integer intoGrade) {
this.tally[intoGrade] = this.tally[intoGrade].add(this.tally[fromGrade]);
this.tally[fromGrade] = BigInteger.ZERO;
}
@Override
public BigInteger getAmountOfJudgments() {
BigInteger sum = BigInteger.ZERO;
int tallyLength = this.tally.length;
for (int i = 0 ; i < tallyLength ; i++) {
sum = sum.add(this.tally[i]);
}
return sum;
}
/**
* Amounts of judgments received per grade, from "worst" grade to "best" grade. Those are
* BigIntegers because of our LCM-based normalization shenanigans.
*/
protected BigInteger[] tally;
public ProposalTally() {}
public ProposalTally(String[] tally) {
setTally(tally);
}
public ProposalTally(Integer[] tally) {
setTally(tally);
}
public ProposalTally(Long[] tally) {
setTally(tally);
}
public ProposalTally(BigInteger[] tally) {
setTally(tally);
}
public ProposalTally(ProposalTallyInterface proposalTally) {
setTally(Arrays.copyOf(proposalTally.getTally(), proposalTally.getTally().length));
}
public void setTally(String[] tally) {
int tallyLength = tally.length;
BigInteger[] bigTally = new BigInteger[tallyLength];
for (int i = 0; i < tallyLength; i++) {
bigTally[i] = new BigInteger(tally[i]);
}
setTally(bigTally);
}
public void setTally(Integer[] tally) {
int tallyLength = tally.length;
BigInteger[] bigTally = new BigInteger[tallyLength];
for (int i = 0; i < tallyLength; i++) {
bigTally[i] = BigInteger.valueOf(tally[i]);
}
setTally(bigTally);
}
public void setTally(Long[] tally) {
int tallyLength = tally.length;
BigInteger[] bigTally = new BigInteger[tallyLength];
for (int i = 0; i < tallyLength; i++) {
bigTally[i] = BigInteger.valueOf(tally[i]);
}
setTally(bigTally);
}
public void setTally(BigInteger[] tally) {
this.tally = tally;
}
@Override
public BigInteger[] getTally() {
return this.tally;
}
@Override
public ProposalTallyInterface duplicate() {
return new ProposalTally(Arrays.copyOf(this.tally, this.tally.length));
}
@Override
public void moveJudgments(Integer fromGrade, Integer intoGrade) {
this.tally[intoGrade] = this.tally[intoGrade].add(this.tally[fromGrade]);
this.tally[fromGrade] = BigInteger.ZERO;
}
@Override
public BigInteger getAmountOfJudgments() {
BigInteger sum = BigInteger.ZERO;
int tallyLength = this.tally.length;
for (int i = 0; i < tallyLength; i++) {
sum = sum.add(this.tally[i]);
}
return sum;
}
}

@ -3,176 +3,177 @@ package fr.mieuxvoter.mj;
import java.math.BigInteger;
/**
* Collect useful data on a proposal tally.
* Does NOT compute the rank, but provides all we need.
*
* This uses BigInteger because in a normalization scenario we use the
* smallest common multiple of the amounts of judges of proposals.
* It makes the code harder to read and understand, but it allows us
* to bypass the floating-point nightmare of the normalization of merit profiles,
* which is one way to handle default grades on some polls.
* Collect useful data on a proposal tally. Does NOT compute the rank, but provides all we need.
*
* <p>This uses BigInteger because in a normalization scenario we use the smallest common multiple
* of the amounts of judges of proposals. It makes the code harder to read and understand, but it
* allows us to bypass the floating-point nightmare of the normalization of merit profiles, which is
* one way to handle default grades on some polls.
*/
public class ProposalTallyAnalysis {
protected ProposalTallyInterface tally;
protected BigInteger totalSize = BigInteger.ZERO; // amount of judges
protected Integer medianGrade = 0;
protected BigInteger medianGroupSize = BigInteger.ZERO; // amount of judges in the median group
protected Integer contestationGrade = 0; // "best" grade of the contestation group
protected BigInteger contestationGroupSize = BigInteger.ZERO; // of lower grades than median
protected Integer adhesionGrade = 0; // "worst" grade of the adhesion group
protected BigInteger adhesionGroupSize = BigInteger.ZERO; // of higher grades than median
protected Integer secondMedianGrade = 0; // grade of the biggest group out of the median
protected BigInteger secondMedianGroupSize = BigInteger.ZERO; // either contestation or adhesion
protected Integer secondMedianGroupSign = 0; // -1 for contestation, +1 for adhesion, 0 for empty group size
public ProposalTallyAnalysis() {}
public ProposalTallyAnalysis(ProposalTallyInterface tally) {
reanalyze(tally);
}
public ProposalTallyAnalysis(ProposalTallyInterface tally, Boolean favorContestation) {
reanalyze(tally, favorContestation);
}
public void reanalyze(ProposalTallyInterface tally) {
reanalyze(tally, true);
}
public void reanalyze(ProposalTallyInterface tally, Boolean favorContestation) {
this.tally = tally;
this.totalSize = BigInteger.ZERO;
this.medianGrade = 0;
this.medianGroupSize = BigInteger.ZERO;
this.contestationGrade = 0;
this.contestationGroupSize = BigInteger.ZERO;
this.adhesionGrade = 0;
this.adhesionGroupSize = BigInteger.ZERO;
this.secondMedianGrade = 0;
this.secondMedianGroupSize = BigInteger.ZERO;
this.secondMedianGroupSign = 0;
BigInteger[] gradesTallies = this.tally.getTally();
int amountOfGrades = gradesTallies.length;
for (int grade = 0; grade < amountOfGrades; grade++) {
BigInteger gradeTally = gradesTallies[grade];
//assert(0 <= gradeTally); // Negative tallies are not allowed.
this.totalSize = this.totalSize.add(gradeTally);
}
Integer medianOffset = 1;
if ( ! favorContestation) {
medianOffset = 2;
}
BigInteger medianCursor = this.totalSize.add(BigInteger.valueOf(medianOffset)).divide(BigInteger.valueOf(2));
// Long medianCursor = (long) Math.floor((this.totalSize + medianOffset) / 2.0);
BigInteger tallyBeforeCursor = BigInteger.ZERO;
BigInteger tallyCursor = BigInteger.ZERO;
Boolean foundMedian = false;
Integer contestationGrade = 0;
Integer adhesionGrade = 0;
for (int grade = 0; grade < amountOfGrades; grade++) {
BigInteger gradeTally = gradesTallies[grade];
tallyBeforeCursor = tallyCursor;
tallyCursor = tallyCursor.add(gradeTally);
if ( ! foundMedian) {
if (-1 < tallyCursor.compareTo(medianCursor)) { // tallyCursor >= medianCursor
foundMedian = true;
this.medianGrade = grade;
this.contestationGroupSize = tallyBeforeCursor;
this.medianGroupSize = gradeTally;
this.adhesionGroupSize = this.totalSize.subtract(this.contestationGroupSize).subtract(this.medianGroupSize);
} else {
if (1 == gradeTally.compareTo(BigInteger.ZERO)) { // 0 < gradeTally
contestationGrade = grade;
}
}
} else {
if (1 == gradeTally.compareTo(BigInteger.ZERO) && 0 == adhesionGrade) {
adhesionGrade = grade;
}
}
}
this.contestationGrade = contestationGrade;
this.adhesionGrade = adhesionGrade;
this.secondMedianGroupSize = this.contestationGroupSize.max(this.adhesionGroupSize);
this.secondMedianGroupSign = 0;
// if (this.contestationGroupSize < this.adhesionGroupSize) {
if (1 == this.adhesionGroupSize.compareTo(this.contestationGroupSize)) {
this.secondMedianGrade = this.adhesionGrade;
this.secondMedianGroupSign = 1;
// } else if (this.contestationGroupSize > this.adhesionGroupSize) {
} else if (1 == this.contestationGroupSize.compareTo(this.adhesionGroupSize)) {
this.secondMedianGrade = this.contestationGrade;
this.secondMedianGroupSign = -1;
} else {
if (favorContestation) {
this.secondMedianGrade = this.contestationGrade;
this.secondMedianGroupSign = -1;
} else {
this.secondMedianGrade = this.adhesionGrade;
this.secondMedianGroupSign = 1;
}
}
if (0 == this.secondMedianGroupSize.compareTo(BigInteger.ZERO)) {
this.secondMedianGroupSign = 0;
}
}
public BigInteger getTotalSize() {
return totalSize;
}
public Integer getMedianGrade() {
return medianGrade;
}
public BigInteger getMedianGroupSize() {
return medianGroupSize;
}
public Integer getContestationGrade() {
return contestationGrade;
}
public BigInteger getContestationGroupSize() {
return contestationGroupSize;
}
public Integer getAdhesionGrade() {
return adhesionGrade;
}
public BigInteger getAdhesionGroupSize() {
return adhesionGroupSize;
}
public Integer getSecondMedianGrade() {
return secondMedianGrade;
}
public BigInteger getSecondMedianGroupSize() {
return secondMedianGroupSize;
}
public Integer getSecondMedianGroupSign() {
return secondMedianGroupSign;
}
protected ProposalTallyInterface tally;
protected BigInteger totalSize = BigInteger.ZERO; // amount of judges
protected Integer medianGrade = 0;
protected BigInteger medianGroupSize = BigInteger.ZERO; // amount of judges in the median group
protected Integer contestationGrade = 0; // "best" grade of the contestation group
protected BigInteger contestationGroupSize = BigInteger.ZERO; // of lower grades than median
protected Integer adhesionGrade = 0; // "worst" grade of the adhesion group
protected BigInteger adhesionGroupSize = BigInteger.ZERO; // of higher grades than median
protected Integer secondMedianGrade = 0; // grade of the biggest group out of the median
protected BigInteger secondMedianGroupSize = BigInteger.ZERO; // either contestation or adhesion
protected Integer secondMedianGroupSign =
0; // -1 for contestation, +1 for adhesion, 0 for empty group size
public ProposalTallyAnalysis() {}
public ProposalTallyAnalysis(ProposalTallyInterface tally) {
reanalyze(tally);
}
public ProposalTallyAnalysis(ProposalTallyInterface tally, Boolean favorContestation) {
reanalyze(tally, favorContestation);
}
public void reanalyze(ProposalTallyInterface tally) {
reanalyze(tally, true);
}
public void reanalyze(ProposalTallyInterface tally, Boolean favorContestation) {
this.tally = tally;
this.totalSize = BigInteger.ZERO;
this.medianGrade = 0;
this.medianGroupSize = BigInteger.ZERO;
this.contestationGrade = 0;
this.contestationGroupSize = BigInteger.ZERO;
this.adhesionGrade = 0;
this.adhesionGroupSize = BigInteger.ZERO;
this.secondMedianGrade = 0;
this.secondMedianGroupSize = BigInteger.ZERO;
this.secondMedianGroupSign = 0;
BigInteger[] gradesTallies = this.tally.getTally();
int amountOfGrades = gradesTallies.length;
for (int grade = 0; grade < amountOfGrades; grade++) {
BigInteger gradeTally = gradesTallies[grade];
// assert(0 <= gradeTally); // Negative tallies are not allowed.
this.totalSize = this.totalSize.add(gradeTally);
}
Integer medianOffset = 1;
if (!favorContestation) {
medianOffset = 2;
}
BigInteger medianCursor =
this.totalSize.add(BigInteger.valueOf(medianOffset)).divide(BigInteger.valueOf(2));
// Long medianCursor = (long) Math.floor((this.totalSize + medianOffset) / 2.0);
BigInteger tallyBeforeCursor = BigInteger.ZERO;
BigInteger tallyCursor = BigInteger.ZERO;
Boolean foundMedian = false;
Integer contestationGrade = 0;
Integer adhesionGrade = 0;
for (int grade = 0; grade < amountOfGrades; grade++) {
BigInteger gradeTally = gradesTallies[grade];
tallyBeforeCursor = tallyCursor;
tallyCursor = tallyCursor.add(gradeTally);
if (!foundMedian) {
if (-1 < tallyCursor.compareTo(medianCursor)) { // tallyCursor >= medianCursor
foundMedian = true;
this.medianGrade = grade;
this.contestationGroupSize = tallyBeforeCursor;
this.medianGroupSize = gradeTally;
this.adhesionGroupSize =
this.totalSize
.subtract(this.contestationGroupSize)
.subtract(this.medianGroupSize);
} else {
if (1 == gradeTally.compareTo(BigInteger.ZERO)) { // 0 < gradeTally
contestationGrade = grade;
}
}
} else {
if (1 == gradeTally.compareTo(BigInteger.ZERO) && 0 == adhesionGrade) {
adhesionGrade = grade;
}
}
}
this.contestationGrade = contestationGrade;
this.adhesionGrade = adhesionGrade;
this.secondMedianGroupSize = this.contestationGroupSize.max(this.adhesionGroupSize);
this.secondMedianGroupSign = 0;
// if (this.contestationGroupSize < this.adhesionGroupSize) {
if (1 == this.adhesionGroupSize.compareTo(this.contestationGroupSize)) {
this.secondMedianGrade = this.adhesionGrade;
this.secondMedianGroupSign = 1;
// } else if (this.contestationGroupSize > this.adhesionGroupSize) {
} else if (1 == this.contestationGroupSize.compareTo(this.adhesionGroupSize)) {
this.secondMedianGrade = this.contestationGrade;
this.secondMedianGroupSign = -1;
} else {
if (favorContestation) {
this.secondMedianGrade = this.contestationGrade;
this.secondMedianGroupSign = -1;
} else {
this.secondMedianGrade = this.adhesionGrade;
this.secondMedianGroupSign = 1;
}
}
if (0 == this.secondMedianGroupSize.compareTo(BigInteger.ZERO)) {
this.secondMedianGroupSign = 0;
}
}
public BigInteger getTotalSize() {
return totalSize;
}
public Integer getMedianGrade() {
return medianGrade;
}
public BigInteger getMedianGroupSize() {
return medianGroupSize;
}
public Integer getContestationGrade() {
return contestationGrade;
}
public BigInteger getContestationGroupSize() {
return contestationGroupSize;
}
public Integer getAdhesionGrade() {
return adhesionGrade;
}
public BigInteger getAdhesionGroupSize() {
return adhesionGroupSize;
}
public Integer getSecondMedianGrade() {
return secondMedianGrade;
}
public BigInteger getSecondMedianGroupSize() {
return secondMedianGroupSize;
}
public Integer getSecondMedianGroupSign() {
return secondMedianGroupSign;
}
}

@ -3,35 +3,27 @@ package fr.mieuxvoter.mj;
import java.math.BigInteger;
/**
* Also known as the merit profile of a proposal (aka. candidate),
* this holds the amounts of judgments received per grade.
* Also known as the merit profile of a proposal (aka. candidate), this holds the amounts of
* judgments received per grade.
*/
public interface ProposalTallyInterface {
/**
* The tallies of each Grade, that is
* the amount of judgments received for each Grade by the Proposal,
* from "worst" ("most conservative") Grade to "best" Grade.
*/
public BigInteger[] getTally();
/**
* The tallies of each Grade, that is the amount of judgments received for each Grade by the
* Proposal, from "worst" ("most conservative") Grade to "best" Grade.
*/
public BigInteger[] getTally();
/**
* Should be the sum of getTally()
*
* @return The total amount of judgments received by this proposal.
*/
public BigInteger getAmountOfJudgments();
/**
* Should be the sum of getTally()
*
* @return The total amount of judgments received by this proposal.
*/
public BigInteger getAmountOfJudgments();
/**
* Homemade factory to skip the clone() shenanigans.
* Used by the score calculus.
*/
public ProposalTallyInterface duplicate();
/**
* Move judgments that were fromGrade into intoGrade.
* Used by the score calculus.
*/
public void moveJudgments(Integer fromGrade, Integer intoGrade);
/** Homemade factory to skip the clone() shenanigans. Used by the score calculus. */
public ProposalTallyInterface duplicate();
/** Move judgments that were fromGrade into intoGrade. Used by the score calculus. */
public void moveJudgments(Integer fromGrade, Integer intoGrade);
}

@ -2,14 +2,13 @@ package fr.mieuxvoter.mj;
public class Result implements ResultInterface {
protected ProposalResultInterface[] proposalResults;
public ProposalResultInterface[] getProposalResults() {
return proposalResults;
}
protected ProposalResultInterface[] proposalResults;
public void setProposalResults(ProposalResultInterface[] proposalResults) {
this.proposalResults = proposalResults;
}
public ProposalResultInterface[] getProposalResults() {
return proposalResults;
}
public void setProposalResults(ProposalResultInterface[] proposalResults) {
this.proposalResults = proposalResults;
}
}

@ -1,12 +1,12 @@
package fr.mieuxvoter.mj;
public interface ResultInterface {
/**
* ProposalResults are not ordered by rank, they are in the order the proposals' tallies were submitted.
*
* @return an array of `ProposalResult`, in the order the `ProposalTally`s were submitted.
*/
public ProposalResultInterface[] getProposalResults();
/**
* ProposalResults are not ordered by rank, they are in the order the proposals' tallies were
* submitted.
*
* @return an array of `ProposalResult`, in the order the `ProposalTally`s were submitted.
*/
public ProposalResultInterface[] getProposalResults();
}

@ -4,47 +4,50 @@ import java.math.BigInteger;
public class StaticDefaultTally extends DefaultGradeTally implements TallyInterface {
/**
* Grades are represented as numbers, as indices in a list.
* Grades start from 0 ("worst" grade, most conservative) and go upwards.
* Values out of the range of grades defined in the tally will yield errors.
*
* Example:
*
* 0 == REJECT
* 1 == PASSABLE
* 2 == GOOD
* 3 == EXCELLENT
*/
protected Integer defaultGrade = 0;
public StaticDefaultTally(TallyInterface tally, Integer defaultGrade) {
super(tally.getProposalsTallies(), tally.getAmountOfJudges());
this.defaultGrade = defaultGrade;
fillWithDefaultGrade();
}
public StaticDefaultTally(ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges, Integer defaultGrade) {
super(proposalsTallies, amountOfJudges);
this.defaultGrade = defaultGrade;
fillWithDefaultGrade();
}
public StaticDefaultTally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges, Integer defaultGrade) {
super(proposalsTallies, amountOfJudges);
this.defaultGrade = defaultGrade;
fillWithDefaultGrade();
}
public StaticDefaultTally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges, Integer defaultGrade) {
super(proposalsTallies, amountOfJudges);
this.defaultGrade = defaultGrade;
fillWithDefaultGrade();
}
@Override
protected Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally) {
return this.defaultGrade;
}
/**
* Grades are represented as numbers, as indices in a list. Grades start from 0 ("worst" grade,
* most conservative) and go upwards. Values out of the range of grades defined in the tally
* will yield errors.
*
* <p>Example:
*
* <p>0 == REJECT 1 == PASSABLE 2 == GOOD 3 == EXCELLENT
*/
protected Integer defaultGrade = 0;
public StaticDefaultTally(TallyInterface tally, Integer defaultGrade) {
super(tally.getProposalsTallies(), tally.getAmountOfJudges());
this.defaultGrade = defaultGrade;
fillWithDefaultGrade();
}
public StaticDefaultTally(
ProposalTallyInterface[] proposalsTallies,
BigInteger amountOfJudges,
Integer defaultGrade) {
super(proposalsTallies, amountOfJudges);
this.defaultGrade = defaultGrade;
fillWithDefaultGrade();
}
public StaticDefaultTally(
ProposalTallyInterface[] proposalsTallies, Long amountOfJudges, Integer defaultGrade) {
super(proposalsTallies, amountOfJudges);
this.defaultGrade = defaultGrade;
fillWithDefaultGrade();
}
public StaticDefaultTally(
ProposalTallyInterface[] proposalsTallies,
Integer amountOfJudges,
Integer defaultGrade) {
super(proposalsTallies, amountOfJudges);
this.defaultGrade = defaultGrade;
fillWithDefaultGrade();
}
@Override
protected Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally) {
return this.defaultGrade;
}
}

@ -7,56 +7,55 @@ import java.math.BigInteger;
*/
public class Tally implements TallyInterface {
protected ProposalTallyInterface[] proposalsTallies;
protected BigInteger amountOfJudges = BigInteger.ZERO;
public Tally(ProposalTallyInterface[] proposalsTallies) {
setProposalsTallies(proposalsTallies);
guessAmountOfJudges();
}
public Tally(ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) {
setProposalsTallies(proposalsTallies);
setAmountOfJudges(amountOfJudges);
}
public Tally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) {
setProposalsTallies(proposalsTallies);
setAmountOfJudges(BigInteger.valueOf(amountOfJudges));
}
public Tally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) {
setProposalsTallies(proposalsTallies);
setAmountOfJudges(BigInteger.valueOf(amountOfJudges));
}
public ProposalTallyInterface[] getProposalsTallies() {
return proposalsTallies;
}
public void setProposalsTallies(ProposalTallyInterface[] proposalsTallies) {
this.proposalsTallies = proposalsTallies;
}
public Integer getAmountOfProposals() {
return proposalsTallies.length;
}
public BigInteger getAmountOfJudges() {
return amountOfJudges;
}
public void setAmountOfJudges(BigInteger amountOfJudges) {
this.amountOfJudges = amountOfJudges;
}
protected void guessAmountOfJudges() {
BigInteger amountOfJudges = BigInteger.ZERO;
for (ProposalTallyInterface proposalTally : getProposalsTallies()) {
amountOfJudges = proposalTally.getAmountOfJudgments().max(amountOfJudges);
}
setAmountOfJudges(amountOfJudges);
}
protected ProposalTallyInterface[] proposalsTallies;
protected BigInteger amountOfJudges = BigInteger.ZERO;
public Tally(ProposalTallyInterface[] proposalsTallies) {
setProposalsTallies(proposalsTallies);
guessAmountOfJudges();
}
public Tally(ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) {
setProposalsTallies(proposalsTallies);
setAmountOfJudges(amountOfJudges);
}
public Tally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) {
setProposalsTallies(proposalsTallies);
setAmountOfJudges(BigInteger.valueOf(amountOfJudges));
}
public Tally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) {
setProposalsTallies(proposalsTallies);
setAmountOfJudges(BigInteger.valueOf(amountOfJudges));
}
public ProposalTallyInterface[] getProposalsTallies() {
return proposalsTallies;
}
public void setProposalsTallies(ProposalTallyInterface[] proposalsTallies) {
this.proposalsTallies = proposalsTallies;
}
public Integer getAmountOfProposals() {
return proposalsTallies.length;
}
public BigInteger getAmountOfJudges() {
return amountOfJudges;
}
public void setAmountOfJudges(BigInteger amountOfJudges) {
this.amountOfJudges = amountOfJudges;
}
protected void guessAmountOfJudges() {
BigInteger amountOfJudges = BigInteger.ZERO;
for (ProposalTallyInterface proposalTally : getProposalsTallies()) {
amountOfJudges = proposalTally.getAmountOfJudgments().max(amountOfJudges);
}
setAmountOfJudges(amountOfJudges);
}
}

@ -4,10 +4,9 @@ import java.math.BigInteger;
public interface TallyInterface {
public ProposalTallyInterface[] getProposalsTallies();
public ProposalTallyInterface[] getProposalsTallies();
public BigInteger getAmountOfJudges();
public Integer getAmountOfProposals();
public BigInteger getAmountOfJudges();
public Integer getAmountOfProposals();
}

@ -1,21 +1,20 @@
package fr.mieuxvoter.mj;
/**
* Raised when the provided tally does not hold the same amount of judgments
* for each proposal, and normalization is required.
* Raised when the provided tally does not hold the same amount of judgments for each proposal, and
* normalization is required.
*/
class UnbalancedTallyException extends InvalidTallyException {
private static final long serialVersionUID = 5041093000505081735L;
@Override
public String getMessage() {
return (
"The provided tally is unbalanced, " +
"as some proposals received more judgments than others. \n" +
"You need to set a strategy for balancing tallies. To that effect, \n" +
"you may use StaticDefaultTally, MedianDefaultTally, or NormalizedTally instead of Tally. \n" +
(null == super.getMessage() ? "" : super.getMessage())
);
}
}
private static final long serialVersionUID = 5041093000505081735L;
@Override
public String getMessage() {
return ("The provided tally is unbalanced, as some proposals received more judgments than"
+ " others. \n"
+ "You need to set a strategy for balancing tallies. To that effect, \n"
+ "you may use StaticDefaultTally, MedianDefaultTally, or NormalizedTally"
+ " instead of Tally. \n"
+ (null == super.getMessage() ? "" : super.getMessage()));
}
}

@ -2,154 +2,146 @@ package fr.mieuxvoter.mj;
import static org.junit.jupiter.api.Assertions.*;
import java.math.BigInteger;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.math.BigInteger;
import java.util.stream.Stream;
// CTRL+F11 in Eclipse to run
class ProposalTallyAnalysisTest {
@DisplayName("Test the proposal tally analysis")
@ParameterizedTest(name="#{index} {0} ; tally = {1}")
@ParameterizedTest(name = "#{index} {0} ; tally = {1}")
@MethodSource("testProvider")
void test(
String testName,
Integer[] rawTally,
Integer medianGrade,
BigInteger medianGroupSize,
Integer contestationGrade,
BigInteger contestationGroupSize,
Integer adhesionGrade,
BigInteger adhesionGroupSize,
Integer secondMedianGrade,
BigInteger secondMedianGroupSize,
Integer secondMedianGroupSign
) {
ProposalTally tally = new ProposalTally(rawTally);
ProposalTallyAnalysis pta = new ProposalTallyAnalysis(tally);
assertEquals(medianGrade, pta.getMedianGrade(), "Median Grade");
assertEquals(medianGroupSize, pta.getMedianGroupSize(), "Median Group Size");
assertEquals(contestationGrade, pta.getContestationGrade(), "Contestation Grade");
assertEquals(contestationGroupSize, pta.getContestationGroupSize(), "Contestation Group Size");
assertEquals(adhesionGrade, pta.getAdhesionGrade(), "Adhesion Grade");
assertEquals(adhesionGroupSize, pta.getAdhesionGroupSize(), "Adhesion Group Size");
assertEquals(secondMedianGrade, pta.getSecondMedianGrade(), "Second Median Grade");
assertEquals(secondMedianGroupSize, pta.getSecondMedianGroupSize(), "Second Median Group Size");
assertEquals(secondMedianGroupSign, pta.getSecondMedianGroupSign(), "Second Median Group Sign");
}
void test(
String testName,
Integer[] rawTally,
Integer medianGrade,
BigInteger medianGroupSize,
Integer contestationGrade,
BigInteger contestationGroupSize,
Integer adhesionGrade,
BigInteger adhesionGroupSize,
Integer secondMedianGrade,
BigInteger secondMedianGroupSize,
Integer secondMedianGroupSign) {
ProposalTally tally = new ProposalTally(rawTally);
ProposalTallyAnalysis pta = new ProposalTallyAnalysis(tally);
assertEquals(medianGrade, pta.getMedianGrade(), "Median Grade");
assertEquals(medianGroupSize, pta.getMedianGroupSize(), "Median Group Size");
assertEquals(contestationGrade, pta.getContestationGrade(), "Contestation Grade");
assertEquals(
contestationGroupSize, pta.getContestationGroupSize(), "Contestation Group Size");
assertEquals(adhesionGrade, pta.getAdhesionGrade(), "Adhesion Grade");
assertEquals(adhesionGroupSize, pta.getAdhesionGroupSize(), "Adhesion Group Size");
assertEquals(secondMedianGrade, pta.getSecondMedianGrade(), "Second Median Grade");
assertEquals(
secondMedianGroupSize, pta.getSecondMedianGroupSize(), "Second Median Group Size");
assertEquals(
secondMedianGroupSign, pta.getSecondMedianGroupSign(), "Second Median Group Sign");
}
protected static Stream<Arguments> testProvider() {
return Stream.of(
Arguments.of(
/* name */ "Very empty tallies yield zeroes",
/* tally */ new Integer[]{ 0 },
/* medianGrade */ 0,
/* medianGroupSize */ BigInteger.ZERO,
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.ZERO,
/* adhesionGrade */ 0,
/* adhesionGroupSize */ BigInteger.ZERO,
/* secondMedianGrade */ 0,
/* secondMedianGroupSize */ BigInteger.ZERO,
/* secondMedianGroupSign */ 0
),
Arguments.of(
/* name */ "Empty tallies yield zeroes",
/* tally */ new Integer[]{ 0, 0, 0, 0 },
/* medianGrade */ 0,
/* medianGroupSize */ BigInteger.ZERO,
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.ZERO,
/* adhesionGrade */ 0,
/* adhesionGroupSize */ BigInteger.ZERO,
/* secondMedianGrade */ 0,
/* secondMedianGroupSize */ BigInteger.ZERO,
/* secondMedianGroupSign */ 0
),
Arguments.of(
/* name */ "Absurd tally of 1 Grade",
/* tally */ new Integer[]{ 7 },
/* medianGrade */ 0,
/* medianGroupSize */ BigInteger.valueOf(7),
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.ZERO,
/* adhesionGrade */ 0,
/* adhesionGroupSize */ BigInteger.ZERO,
/* secondMedianGrade */ 0,
/* secondMedianGroupSize */ BigInteger.ZERO,
/* secondMedianGroupSign */ 0
),
Arguments.of(
/* name */ "Approbation",
/* tally */ new Integer[]{ 31, 72 },
/* medianGrade */ 1,
/* medianGroupSize */ BigInteger.valueOf(72),
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.valueOf(31),
/* adhesionGrade */ 0,
/* adhesionGroupSize */ BigInteger.ZERO,
/* secondMedianGrade */ 0,
/* secondMedianGroupSize */ BigInteger.valueOf(31),
/* secondMedianGroupSign */ -1
),
Arguments.of(
/* name */ "Equality favors contestation",
/* tally */ new Integer[]{ 42, 42 },
/* medianGrade */ 0,
/* medianGroupSize */ BigInteger.valueOf(42),
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.ZERO,
/* adhesionGrade */ 1,
/* adhesionGroupSize */ BigInteger.valueOf(42),
/* secondMedianGrade */ 1,
/* secondMedianGroupSize */ BigInteger.valueOf(42),
/* secondMedianGroupSign */ 1
),
Arguments.of(
/* name */ "Example with seven grades",
/* tally */ new Integer[]{ 4, 2, 0, 1, 2, 2, 3 },
/* medianGrade */ 3,
/* medianGroupSize */ BigInteger.valueOf(1),
/* contestationGrade */ 1,
/* contestationGroupSize */ BigInteger.valueOf(6),
/* adhesionGrade */ 4,
/* adhesionGroupSize */ BigInteger.valueOf(7),
/* secondMedianGrade */ 4,
/* secondMedianGroupSize */ BigInteger.valueOf(7),
/* secondMedianGroupSign */ 1
),
Arguments.of(
/* name */ "Works even if multiple grades are at zero",
/* tally */ new Integer[]{ 4, 0, 0, 1, 0, 0, 4 },
/* medianGrade */ 3,
/* medianGroupSize */ BigInteger.valueOf(1),
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.valueOf(4),
/* adhesionGrade */ 6,
/* adhesionGroupSize */ BigInteger.valueOf(4),
/* secondMedianGrade */ 0,
/* secondMedianGroupSize */ BigInteger.valueOf(4),
/* secondMedianGroupSign */ -1
),
Arguments.of(
/* name */ "Weird tally",
/* tally */ new Integer[]{ 1, 1, 1, 1, 1, 1, 1 },
/* medianGrade */ 3,
/* medianGroupSize */ BigInteger.valueOf(1),
/* contestationGrade */ 2,
/* contestationGroupSize */ BigInteger.valueOf(3),
/* adhesionGrade */ 4,
/* adhesionGroupSize */ BigInteger.valueOf(3),
/* secondMedianGrade */ 2,
/* secondMedianGroupSize */ BigInteger.valueOf(3),
/* secondMedianGroupSign */ -1
)
);
}
return Stream.of(
Arguments.of(
/* name */ "Very empty tallies yield zeroes",
/* tally */ new Integer[] {0},
/* medianGrade */ 0,
/* medianGroupSize */ BigInteger.ZERO,
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.ZERO,
/* adhesionGrade */ 0,
/* adhesionGroupSize */ BigInteger.ZERO,
/* secondMedianGrade */ 0,
/* secondMedianGroupSize */ BigInteger.ZERO,
/* secondMedianGroupSign */ 0),
Arguments.of(
/* name */ "Empty tallies yield zeroes",
/* tally */ new Integer[] {0, 0, 0, 0},
/* medianGrade */ 0,
/* medianGroupSize */ BigInteger.ZERO,
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.ZERO,
/* adhesionGrade */ 0,
/* adhesionGroupSize */ BigInteger.ZERO,
/* secondMedianGrade */ 0,
/* secondMedianGroupSize */ BigInteger.ZERO,
/* secondMedianGroupSign */ 0),
Arguments.of(
/* name */ "Absurd tally of 1 Grade",
/* tally */ new Integer[] {7},
/* medianGrade */ 0,
/* medianGroupSize */ BigInteger.valueOf(7),
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.ZERO,
/* adhesionGrade */ 0,
/* adhesionGroupSize */ BigInteger.ZERO,
/* secondMedianGrade */ 0,
/* secondMedianGroupSize */ BigInteger.ZERO,
/* secondMedianGroupSign */ 0),
Arguments.of(
/* name */ "Approbation",
/* tally */ new Integer[] {31, 72},
/* medianGrade */ 1,
/* medianGroupSize */ BigInteger.valueOf(72),
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.valueOf(31),
/* adhesionGrade */ 0,
/* adhesionGroupSize */ BigInteger.ZERO,
/* secondMedianGrade */ 0,
/* secondMedianGroupSize */ BigInteger.valueOf(31),
/* secondMedianGroupSign */ -1),
Arguments.of(
/* name */ "Equality favors contestation",
/* tally */ new Integer[] {42, 42},
/* medianGrade */ 0,
/* medianGroupSize */ BigInteger.valueOf(42),
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.ZERO,
/* adhesionGrade */ 1,
/* adhesionGroupSize */ BigInteger.valueOf(42),
/* secondMedianGrade */ 1,
/* secondMedianGroupSize */ BigInteger.valueOf(42),
/* secondMedianGroupSign */ 1),
Arguments.of(
/* name */ "Example with seven grades",
/* tally */ new Integer[] {4, 2, 0, 1, 2, 2, 3},
/* medianGrade */ 3,
/* medianGroupSize */ BigInteger.valueOf(1),
/* contestationGrade */ 1,
/* contestationGroupSize */ BigInteger.valueOf(6),
/* adhesionGrade */ 4,
/* adhesionGroupSize */ BigInteger.valueOf(7),
/* secondMedianGrade */ 4,
/* secondMedianGroupSize */ BigInteger.valueOf(7),
/* secondMedianGroupSign */ 1),
Arguments.of(
/* name */ "Works even if multiple grades are at zero",
/* tally */ new Integer[] {4, 0, 0, 1, 0, 0, 4},
/* medianGrade */ 3,
/* medianGroupSize */ BigInteger.valueOf(1),
/* contestationGrade */ 0,
/* contestationGroupSize */ BigInteger.valueOf(4),
/* adhesionGrade */ 6,
/* adhesionGroupSize */ BigInteger.valueOf(4),
/* secondMedianGrade */ 0,
/* secondMedianGroupSize */ BigInteger.valueOf(4),
/* secondMedianGroupSign */ -1),
Arguments.of(
/* name */ "Weird tally",
/* tally */ new Integer[] {1, 1, 1, 1, 1, 1, 1},
/* medianGrade */ 3,
/* medianGroupSize */ BigInteger.valueOf(1),
/* contestationGrade */ 2,
/* contestationGroupSize */ BigInteger.valueOf(3),
/* adhesionGrade */ 4,
/* adhesionGroupSize */ BigInteger.valueOf(3),
/* secondMedianGrade */ 2,
/* secondMedianGroupSize */ BigInteger.valueOf(3),
/* secondMedianGroupSign */ -1));
}
}

Loading…
Cancel
Save