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

@ -1,23 +1,18 @@
package fr.mieuxvoter.mj; package fr.mieuxvoter.mj;
/** /**
* A Deliberator takes in a poll's Tally, * A Deliberator takes in a poll's Tally, which holds the amount of judgments of each grade received
* which holds the amount of judgments of each grade received by each Proposal, * by each Proposal, and outputs that poll's Result, that is the final rank of 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
* Ranks start at 1 ("best"), and increment towards "worst". * rank, in extreme equality cases.
* Two proposal may share the same rank, in extreme equality cases. *
* * <p>This is the main API of this library.
* This is the main API of this library. *
* * <p>See MajorityJudgmentDeliberator for an implementation. One could implement other deliberators,
* See MajorityJudgmentDeliberator for an implementation. * such as: - CentralJudgmentDeliberator - UsualJudgmentDeliberator
* One could implement other deliberators, such as:
* - CentralJudgmentDeliberator
* - UsualJudgmentDeliberator
*/ */
public interface DeliberatorInterface { 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; 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 { class IncoherentTallyException extends InvalidTallyException {
private static final long serialVersionUID = 5858986651601202903L; private static final long serialVersionUID = 5858986651601202903L;
@Override @Override
public String getMessage() { public String getMessage() {
return ( return ("The provided tally holds negative values, or infinity. "
"The provided tally holds negative values, or infinity. " + (null == super.getMessage() ? "" : super.getMessage()));
+ }
(null == super.getMessage() ? "" : super.getMessage()) }
);
}
}

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

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

@ -4,76 +4,72 @@ import java.math.BigInteger;
import java.security.InvalidParameterException; import java.security.InvalidParameterException;
/** /**
* The deliberator expects the proposals' tallies to hold the same amount of judgments. * The deliberator expects the proposals' tallies to hold the same amount of judgments. This
* This NormalizedTally accepts tallies with disparate amounts of judgments per proposal, * NormalizedTally accepts tallies with disparate amounts of judgments per proposal, and normalizes
* and normalizes them to their least common multiple, which amounts to using percentages, * them to their least common multiple, which amounts to using percentages, except we don't use
* except we don't use floating-point arithmetic. * floating-point arithmetic.
* *
* This is useful when there are too many proposals for judges to be expected to judge them all, * <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. * and all the proposals received reasonably similar amounts of judgments.
*/ */
public class NormalizedTally extends Tally implements TallyInterface { public class NormalizedTally extends Tally implements TallyInterface {
public NormalizedTally(ProposalTallyInterface[] proposalsTallies) { public NormalizedTally(ProposalTallyInterface[] proposalsTallies) {
super(proposalsTallies); super(proposalsTallies);
initializeFromProposalsTallies(proposalsTallies); initializeFromProposalsTallies(proposalsTallies);
} }
public NormalizedTally(TallyInterface tally) { public NormalizedTally(TallyInterface tally) {
super(tally.getProposalsTallies()); super(tally.getProposalsTallies());
initializeFromProposalsTallies(tally.getProposalsTallies()); initializeFromProposalsTallies(tally.getProposalsTallies());
} }
protected void initializeFromProposalsTallies(ProposalTallyInterface[] proposalsTallies) { protected void initializeFromProposalsTallies(ProposalTallyInterface[] proposalsTallies) {
Integer amountOfProposals = getAmountOfProposals(); 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);
}
/** // Compute the Least Common Multiple
* Least Common Multiple BigInteger amountOfJudges = BigInteger.ONE;
* for (ProposalTallyInterface proposalTally : proposalsTallies) {
* http://en.wikipedia.org/wiki/Least_common_multiple amountOfJudges = lcm(amountOfJudges, proposalTally.getAmountOfJudgments());
* }
* 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();
}
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; package fr.mieuxvoter.mj;
public class ProposalResult implements ProposalResultInterface { public class ProposalResult implements ProposalResultInterface {
protected Integer rank; protected Integer rank;
protected String score;
protected ProposalTallyAnalysis analysis; protected String score;
public Integer getRank() { protected ProposalTallyAnalysis analysis;
return rank;
}
public void setRank(Integer rank) { public Integer getRank() {
this.rank = rank; return rank;
} }
public String getScore() { public void setRank(Integer rank) {
return score; this.rank = rank;
} }
public void setScore(String score) { public String getScore() {
this.score = score; return score;
} }
public ProposalTallyAnalysis getAnalysis() { public void setScore(String score) {
return analysis; this.score = score;
} }
public void setAnalysis(ProposalTallyAnalysis analysis) { public ProposalTallyAnalysis getAnalysis() {
this.analysis = analysis; return analysis;
} }
public void setAnalysis(ProposalTallyAnalysis analysis) {
this.analysis = analysis;
}
} }

@ -1,27 +1,21 @@
package fr.mieuxvoter.mj; package fr.mieuxvoter.mj;
public interface ProposalResultInterface { public interface ProposalResultInterface {
/** /**
* Rank starts at 1 ("best" proposal), and goes upwards. * Rank starts at 1 ("best" proposal), and goes upwards. Multiple Proposals may receive the same
* Multiple Proposals may receive the same rank, * rank, in the extreme case where they received the exact same judgments, or the same judgment
* in the extreme case where they received the exact same judgments, * repartition in normalized tallies.
* or the same judgment repartition in normalized tallies. */
*/ public Integer getRank();
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();
/** /**
* Get more data about the proposal tally, such as the median grade. * 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
public ProposalTallyAnalysis getAnalysis(); * 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 { public class ProposalTally implements ProposalTallyInterface {
/** /**
* Amounts of judgments received per grade, from "worst" grade to "best" grade. * Amounts of judgments received per grade, from "worst" grade to "best" grade. Those are
* Those are BigIntegers because of our LCM-based normalization shenanigans. * BigIntegers because of our LCM-based normalization shenanigans.
*/ */
protected BigInteger[] tally; protected BigInteger[] tally;
public ProposalTally() {} public ProposalTally() {}
public ProposalTally(String[] tally) { public ProposalTally(String[] tally) {
setTally(tally); setTally(tally);
} }
public ProposalTally(Integer[] tally) { public ProposalTally(Integer[] tally) {
setTally(tally); setTally(tally);
} }
public ProposalTally(Long[] tally) { public ProposalTally(Long[] tally) {
setTally(tally); setTally(tally);
} }
public ProposalTally(BigInteger[] tally) { public ProposalTally(BigInteger[] tally) {
setTally(tally); setTally(tally);
} }
public ProposalTally(ProposalTallyInterface proposalTally) { public ProposalTally(ProposalTallyInterface proposalTally) {
setTally(Arrays.copyOf(proposalTally.getTally(), proposalTally.getTally().length)); setTally(Arrays.copyOf(proposalTally.getTally(), proposalTally.getTally().length));
} }
public void setTally(String[] tally) { public void setTally(String[] tally) {
int tallyLength = tally.length; int tallyLength = tally.length;
BigInteger[] bigTally = new BigInteger[tallyLength]; BigInteger[] bigTally = new BigInteger[tallyLength];
for (int i = 0 ; i < tallyLength ; i++) { for (int i = 0; i < tallyLength; i++) {
bigTally[i] = new BigInteger(tally[i]); bigTally[i] = new BigInteger(tally[i]);
} }
setTally(bigTally); setTally(bigTally);
} }
public void setTally(Integer[] tally) { public void setTally(Integer[] tally) {
int tallyLength = tally.length; int tallyLength = tally.length;
BigInteger[] bigTally = new BigInteger[tallyLength]; BigInteger[] bigTally = new BigInteger[tallyLength];
for (int i = 0 ; i < tallyLength ; i++) { for (int i = 0; i < tallyLength; i++) {
bigTally[i] = BigInteger.valueOf(tally[i]); bigTally[i] = BigInteger.valueOf(tally[i]);
} }
setTally(bigTally); setTally(bigTally);
} }
public void setTally(Long[] tally) { public void setTally(Long[] tally) {
int tallyLength = tally.length; int tallyLength = tally.length;
BigInteger[] bigTally = new BigInteger[tallyLength]; BigInteger[] bigTally = new BigInteger[tallyLength];
for (int i = 0 ; i < tallyLength ; i++) { for (int i = 0; i < tallyLength; i++) {
bigTally[i] = BigInteger.valueOf(tally[i]); bigTally[i] = BigInteger.valueOf(tally[i]);
} }
setTally(bigTally); setTally(bigTally);
} }
public void setTally(BigInteger[] tally) { public void setTally(BigInteger[] tally) {
this.tally = tally; this.tally = tally;
} }
@Override @Override
public BigInteger[] getTally() { public BigInteger[] getTally() {
return this.tally; return this.tally;
} }
@Override @Override
public ProposalTallyInterface duplicate() { public ProposalTallyInterface duplicate() {
return new ProposalTally(Arrays.copyOf(this.tally, this.tally.length)); return new ProposalTally(Arrays.copyOf(this.tally, this.tally.length));
} }
@Override @Override
public void moveJudgments(Integer fromGrade, Integer intoGrade) { public void moveJudgments(Integer fromGrade, Integer intoGrade) {
this.tally[intoGrade] = this.tally[intoGrade].add(this.tally[fromGrade]); this.tally[intoGrade] = this.tally[intoGrade].add(this.tally[fromGrade]);
this.tally[fromGrade] = BigInteger.ZERO; this.tally[fromGrade] = BigInteger.ZERO;
} }
@Override @Override
public BigInteger getAmountOfJudgments() { public BigInteger getAmountOfJudgments() {
BigInteger sum = BigInteger.ZERO; BigInteger sum = BigInteger.ZERO;
int tallyLength = this.tally.length; int tallyLength = this.tally.length;
for (int i = 0 ; i < tallyLength ; i++) { for (int i = 0; i < tallyLength; i++) {
sum = sum.add(this.tally[i]); sum = sum.add(this.tally[i]);
} }
return sum; return sum;
} }
} }

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

@ -3,35 +3,27 @@ package fr.mieuxvoter.mj;
import java.math.BigInteger; import java.math.BigInteger;
/** /**
* Also known as the merit profile of a proposal (aka. candidate), * Also known as the merit profile of a proposal (aka. candidate), this holds the amounts of
* this holds the amounts of judgments received per grade. * judgments received per grade.
*/ */
public interface ProposalTallyInterface { public interface ProposalTallyInterface {
/** /**
* The tallies of each Grade, that is * The tallies of each Grade, that is the amount of judgments received for each Grade by the
* the amount of judgments received for each Grade by the Proposal, * Proposal, from "worst" ("most conservative") Grade to "best" Grade.
* from "worst" ("most conservative") Grade to "best" Grade. */
*/ public BigInteger[] getTally();
public BigInteger[] getTally();
/** /**
* Should be the sum of getTally() * Should be the sum of getTally()
* *
* @return The total amount of judgments received by this proposal. * @return The total amount of judgments received by this proposal.
*/ */
public BigInteger getAmountOfJudgments(); public BigInteger getAmountOfJudgments();
/** /** Homemade factory to skip the clone() shenanigans. Used by the score calculus. */
* Homemade factory to skip the clone() shenanigans. public ProposalTallyInterface duplicate();
* 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);
/** 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 { public class Result implements ResultInterface {
protected ProposalResultInterface[] proposalResults; protected ProposalResultInterface[] proposalResults;
public ProposalResultInterface[] getProposalResults() {
return proposalResults;
}
public void setProposalResults(ProposalResultInterface[] proposalResults) { public ProposalResultInterface[] getProposalResults() {
this.proposalResults = proposalResults; return proposalResults;
} }
public void setProposalResults(ProposalResultInterface[] proposalResults) {
this.proposalResults = proposalResults;
}
} }

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

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

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

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

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

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

Loading…
Cancel
Save