From 2f6ed8b6ccd17978f802cc7c7bd5175a6363f118 Mon Sep 17 00:00:00 2001 From: domi41 Date: Thu, 17 Jun 2021 12:28:49 +0200 Subject: [PATCH] chore: lint using google formatter 1.10 (AOSP version) https://github.com/google/google-java-format/releases/ --- .../java/fr/mieuxvoter/mj/CollectedTally.java | 110 +- .../fr/mieuxvoter/mj/DefaultGradeTally.java | 83 +- .../mieuxvoter/mj/DeliberatorInterface.java | 27 +- .../mj/IncoherentTallyException.java | 22 +- .../mieuxvoter/mj/InvalidTallyException.java | 9 +- .../mj/MajorityJudgmentDeliberator.java | 383 +++--- .../fr/mieuxvoter/mj/MedianDefaultTally.java | 57 +- .../fr/mieuxvoter/mj/NormalizedTally.java | 124 +- .../java/fr/mieuxvoter/mj/ProposalResult.java | 44 +- .../mj/ProposalResultInterface.java | 34 +- .../java/fr/mieuxvoter/mj/ProposalTally.java | 169 ++- .../mieuxvoter/mj/ProposalTallyAnalysis.java | 339 ++--- .../mieuxvoter/mj/ProposalTallyInterface.java | 42 +- src/main/java/fr/mieuxvoter/mj/Result.java | 15 +- .../fr/mieuxvoter/mj/ResultInterface.java | 16 +- .../fr/mieuxvoter/mj/StaticDefaultTally.java | 89 +- src/main/java/fr/mieuxvoter/mj/Tally.java | 103 +- .../java/fr/mieuxvoter/mj/TallyInterface.java | 7 +- .../mj/UnbalancedTallyException.java | 29 +- .../mj/MajorityJudgmentDeliberatorTest.java | 1206 +++++++++-------- .../mj/ProposalTallyAnalysisTest.java | 264 ++-- 21 files changed, 1566 insertions(+), 1606 deletions(-) diff --git a/src/main/java/fr/mieuxvoter/mj/CollectedTally.java b/src/main/java/fr/mieuxvoter/mj/CollectedTally.java index 62c44fe..2af1cd7 100644 --- a/src/main/java/fr/mieuxvoter/mj/CollectedTally.java +++ b/src/main/java/fr/mieuxvoter/mj/CollectedTally.java @@ -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); + } } diff --git a/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java b/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java index bf9162d..613de2a 100644 --- a/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java +++ b/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java @@ -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); - - // /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); + + // /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); + } + } + } } diff --git a/src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java b/src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java index bf080f5..12120ce 100644 --- a/src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java +++ b/src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java @@ -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. + * + *

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 */ public interface DeliberatorInterface { - public ResultInterface deliberate(TallyInterface tally) throws InvalidTallyException; - + public ResultInterface deliberate(TallyInterface tally) throws InvalidTallyException; } diff --git a/src/main/java/fr/mieuxvoter/mj/IncoherentTallyException.java b/src/main/java/fr/mieuxvoter/mj/IncoherentTallyException.java index acb0a76..315c8d7 100644 --- a/src/main/java/fr/mieuxvoter/mj/IncoherentTallyException.java +++ b/src/main/java/fr/mieuxvoter/mj/IncoherentTallyException.java @@ -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()) - ); - } - -} \ No newline at end of file + @Override + public String getMessage() { + return ("The provided tally holds negative values, or infinity. " + + (null == super.getMessage() ? "" : super.getMessage())); + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/InvalidTallyException.java b/src/main/java/fr/mieuxvoter/mj/InvalidTallyException.java index 74ac596..b055758 100644 --- a/src/main/java/fr/mieuxvoter/mj/InvalidTallyException.java +++ b/src/main/java/fr/mieuxvoter/mj/InvalidTallyException.java @@ -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; - -} \ No newline at end of file + private static final long serialVersionUID = 3033391835216704620L; +} diff --git a/src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java b/src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java index bcc510c..43c91de 100644 --- a/src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java +++ b/src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java @@ -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 + * + *

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 * https://fr.wikipedia.org/wiki/Jugement_majoritaire - * - * Should this class be `final`? + * + *

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() { - @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() { + @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(); + } } diff --git a/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java index 5379590..1b6c47a 100644 --- a/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java +++ b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java @@ -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(); + } } diff --git a/src/main/java/fr/mieuxvoter/mj/NormalizedTally.java b/src/main/java/fr/mieuxvoter/mj/NormalizedTally.java index 61f0b42..bab5224 100644 --- a/src/main/java/fr/mieuxvoter/mj/NormalizedTally.java +++ b/src/main/java/fr/mieuxvoter/mj/NormalizedTally.java @@ -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. + * + *

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 + * + *

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(); + } } diff --git a/src/main/java/fr/mieuxvoter/mj/ProposalResult.java b/src/main/java/fr/mieuxvoter/mj/ProposalResult.java index 4882ced..8e04165 100644 --- a/src/main/java/fr/mieuxvoter/mj/ProposalResult.java +++ b/src/main/java/fr/mieuxvoter/mj/ProposalResult.java @@ -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; + } } diff --git a/src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java b/src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java index cbefd9d..d956a42 100644 --- a/src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java +++ b/src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java @@ -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(); } diff --git a/src/main/java/fr/mieuxvoter/mj/ProposalTally.java b/src/main/java/fr/mieuxvoter/mj/ProposalTally.java index 8f13f6c..1e1ee45 100644 --- a/src/main/java/fr/mieuxvoter/mj/ProposalTally.java +++ b/src/main/java/fr/mieuxvoter/mj/ProposalTally.java @@ -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; + } } diff --git a/src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java b/src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java index 967bf0f..84da69d 100644 --- a/src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java +++ b/src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java @@ -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. + * + *

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; + } } diff --git a/src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java b/src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java index 70a18b0..e0f23b4 100644 --- a/src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java +++ b/src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java @@ -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); } diff --git a/src/main/java/fr/mieuxvoter/mj/Result.java b/src/main/java/fr/mieuxvoter/mj/Result.java index 76b1a4c..7acca07 100644 --- a/src/main/java/fr/mieuxvoter/mj/Result.java +++ b/src/main/java/fr/mieuxvoter/mj/Result.java @@ -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; + } } diff --git a/src/main/java/fr/mieuxvoter/mj/ResultInterface.java b/src/main/java/fr/mieuxvoter/mj/ResultInterface.java index 79c011a..06e163a 100644 --- a/src/main/java/fr/mieuxvoter/mj/ResultInterface.java +++ b/src/main/java/fr/mieuxvoter/mj/ResultInterface.java @@ -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(); } diff --git a/src/main/java/fr/mieuxvoter/mj/StaticDefaultTally.java b/src/main/java/fr/mieuxvoter/mj/StaticDefaultTally.java index e750b5e..80b6a86 100644 --- a/src/main/java/fr/mieuxvoter/mj/StaticDefaultTally.java +++ b/src/main/java/fr/mieuxvoter/mj/StaticDefaultTally.java @@ -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. + * + *

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; + } } diff --git a/src/main/java/fr/mieuxvoter/mj/Tally.java b/src/main/java/fr/mieuxvoter/mj/Tally.java index 91273aa..0331a0d 100644 --- a/src/main/java/fr/mieuxvoter/mj/Tally.java +++ b/src/main/java/fr/mieuxvoter/mj/Tally.java @@ -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); + } } diff --git a/src/main/java/fr/mieuxvoter/mj/TallyInterface.java b/src/main/java/fr/mieuxvoter/mj/TallyInterface.java index 70d53d8..33a41f1 100644 --- a/src/main/java/fr/mieuxvoter/mj/TallyInterface.java +++ b/src/main/java/fr/mieuxvoter/mj/TallyInterface.java @@ -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(); } diff --git a/src/main/java/fr/mieuxvoter/mj/UnbalancedTallyException.java b/src/main/java/fr/mieuxvoter/mj/UnbalancedTallyException.java index da4ee3b..abd590d 100644 --- a/src/main/java/fr/mieuxvoter/mj/UnbalancedTallyException.java +++ b/src/main/java/fr/mieuxvoter/mj/UnbalancedTallyException.java @@ -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()) - ); - } -} \ No newline at end of file + 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())); + } +} diff --git a/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java b/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java index 7105505..a7738ee 100644 --- a/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java +++ b/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java @@ -2,611 +2,621 @@ package fr.mieuxvoter.mj; import static org.junit.jupiter.api.Assertions.*; -import java.math.BigInteger; - -import javax.json.JsonArray; -import javax.json.JsonObject; -import javax.json.JsonValue; +import net.joshka.junit.json.params.JsonFileSource; -import org.junit.function.ThrowingRunnable; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; -import net.joshka.junit.json.params.JsonFileSource; +import java.math.BigInteger; + +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonValue; class MajorityJudgmentDeliberatorTest { - @DisplayName("Test majority judgment deliberation from JSON assertions") - @ParameterizedTest(name="#{index} {0}") - @JsonFileSource(resources = "/assertions.json") - public void testFromJson(JsonObject datum) throws Throwable { - // This test uses the JSON file in test/resources/ - // It also allows testing the various modes of default grades. - - JsonArray jsonTallies = datum.getJsonArray("tallies"); - int amountOfProposals = jsonTallies.size(); - BigInteger amountOfParticipants = new BigInteger(datum.get("participants").toString()); - ProposalTallyInterface[] tallies = new ProposalTallyInterface[amountOfProposals]; - for (int i = 0; i < amountOfProposals; i++) { - JsonArray jsonTally = jsonTallies.getJsonArray(i); - int amountOfGrades = jsonTally.size(); - BigInteger[] tally = new BigInteger[amountOfGrades]; - for (int g = 0; g < amountOfGrades; g++) { - JsonValue amountForGrade = jsonTally.get(g); - tally[g] = new BigInteger(amountForGrade.toString()); - } - tallies[i] = new ProposalTally(tally); - } - - String mode = datum.getString("mode", "None"); - TallyInterface tally; - if ("StaticDefault".equalsIgnoreCase(mode)) { - tally = new StaticDefaultTally(tallies, amountOfParticipants, datum.getInt("default", 0)); - } else if ("MedianDefault".equalsIgnoreCase(mode)) { - tally = new MedianDefaultTally(tallies, amountOfParticipants); - } else if ("Normalized".equalsIgnoreCase(mode)) { - tally = new NormalizedTally(tallies); - } else { - tally = new Tally(tallies, amountOfParticipants); - } - - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - ResultInterface result = mj.deliberate(tally); - - assertNotNull(result); - JsonArray jsonRanks = datum.getJsonArray("ranks"); - for (int i = 0; i < amountOfProposals; i++) { - assertEquals( - jsonRanks.getInt(i), - result.getProposalResults()[i].getRank(), - "Rank of tally #"+i - ); - } - } - - @Test - @DisplayName("Test the basic demo usage of the README") - public void testDemoUsage() throws Throwable { - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - TallyInterface tally = new Tally(new ProposalTallyInterface[] { - new ProposalTally(new Integer[]{4, 5, 2, 1, 3, 1, 2}), - new ProposalTally(new Integer[]{3, 6, 2, 1, 3, 1, 2}), - }); - - ResultInterface result = mj.deliberate(tally); - - assertNotNull(result); - assertEquals(2, result.getProposalResults().length); - assertEquals(2, result.getProposalResults()[0].getRank()); - assertEquals(1, result.getProposalResults()[1].getRank()); - } - - @Test - @DisplayName("Test the basic demo usage with billions of participants") - public void testDemoUsageWithBigNumbers() throws Throwable { - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - TallyInterface tally = new Tally(new ProposalTallyInterface[] { - new ProposalTally(new Long[]{11312415004L, 21153652410L, 24101523299L, 18758623562L}), - new ProposalTally(new Long[]{11312415004L, 21153652400L, 24101523299L, 18758623572L}), -// new ProposalTally(new Long[]{14526586452L, 40521123260L, 14745623120L, 40526235129L}), - }); - ResultInterface result = mj.deliberate(tally); - -// System.out.println("Score 0: "+result.getProposalResults()[0].getScore()); -// System.out.println("Score 1: "+result.getProposalResults()[1].getScore()); - - assertNotNull(result); - assertEquals(2, result.getProposalResults().length); - assertEquals(2, result.getProposalResults()[0].getRank()); - assertEquals(1, result.getProposalResults()[1].getRank()); - } - - @Test - @DisplayName("Test the collect demo usage of the README") - public void testDemoUsageCollectedTally() throws Throwable { - Integer amountOfProposals = 3; - Integer amountOfGrades = 4; - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - CollectedTally tally = new CollectedTally(amountOfProposals, amountOfGrades); - - Integer firstProposal = 0; - Integer secondProposal = 1; - Integer thirdProposal = 2; - Integer gradeReject = 0; - Integer gradePassable = 1; - Integer gradeGood = 2; - Integer gradeExcellent = 3; - - tally.collect(firstProposal, gradeReject); - tally.collect(firstProposal, gradeReject); - tally.collect(firstProposal, gradePassable); - tally.collect(firstProposal, gradePassable); - tally.collect(firstProposal, gradePassable); - tally.collect(firstProposal, gradeExcellent); - tally.collect(firstProposal, gradeExcellent); - tally.collect(firstProposal, gradeExcellent); - - tally.collect(secondProposal, gradeReject); - tally.collect(secondProposal, gradeReject); - tally.collect(secondProposal, gradeGood); - tally.collect(secondProposal, gradeGood); - tally.collect(secondProposal, gradeGood); - tally.collect(secondProposal, gradeGood); - tally.collect(secondProposal, gradeExcellent); - tally.collect(secondProposal, gradeExcellent); - - tally.collect(thirdProposal, gradeReject); - tally.collect(thirdProposal, gradeReject); - tally.collect(thirdProposal, gradePassable); - tally.collect(thirdProposal, gradeGood); - tally.collect(thirdProposal, gradeGood); - tally.collect(thirdProposal, gradeGood); - tally.collect(thirdProposal, gradeExcellent); - tally.collect(thirdProposal, gradeExcellent); - - ResultInterface result = mj.deliberate(tally); - - assertNotNull(result); - assertEquals(3, result.getProposalResults().length); - assertEquals(3, result.getProposalResults()[0].getRank()); - assertEquals(1, result.getProposalResults()[1].getRank()); - assertEquals(2, result.getProposalResults()[2].getRank()); - } - - @Test - @DisplayName("Test the normalized collect demo usage of the README") - public void testDemoUsageNormalizedCollectedTally() throws Throwable { - Integer amountOfProposals = 4; - Integer amountOfGrades = 3; - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - CollectedTally tally = new CollectedTally(amountOfProposals, amountOfGrades); - - Integer firstProposal = 0; - Integer secondProposal = 1; - Integer thirdProposal = 2; - Integer fourthProposal = 3; - Integer gradeReject = 0; - Integer gradePassable = 1; - Integer gradeGood = 2; - - tally.collect(firstProposal, gradeReject); - tally.collect(firstProposal, gradeReject); - tally.collect(firstProposal, gradePassable); - tally.collect(firstProposal, gradePassable); - tally.collect(firstProposal, gradeGood); - tally.collect(firstProposal, gradeGood); - - tally.collect(secondProposal, gradeReject); - tally.collect(secondProposal, gradePassable); - tally.collect(secondProposal, gradeGood); - - tally.collect(thirdProposal, gradePassable); - - tally.collect(fourthProposal, gradeGood); - - ResultInterface result = mj.deliberate( - new NormalizedTally(tally) - ); - - assertNotNull(result); - assertEquals(4, result.getProposalResults().length); - assertEquals(3, result.getProposalResults()[0].getRank()); - assertEquals(3, result.getProposalResults()[1].getRank()); - assertEquals(2, result.getProposalResults()[2].getRank()); - assertEquals(1, result.getProposalResults()[3].getRank()); - } - - @Test - @DisplayName("Test with a static default grade (\"worst grade\" == 0)") - public void testWithStaticDefaultGrade() throws Throwable { - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - Long amountOfJudges = 3L; - Integer defaultGrade = 0; - TallyInterface tally = new StaticDefaultTally(new ProposalTallyInterface[] { - new ProposalTally(new Integer[]{ 0, 0, 1 }), - new ProposalTally(new Integer[]{ 0, 3, 0 }), - new ProposalTally(new Integer[]{ 2, 0, 1 }), - }, amountOfJudges, defaultGrade); - - ResultInterface result = mj.deliberate(tally); - - assertNotNull(result); - assertEquals(3, result.getProposalResults().length); - assertEquals(2, result.getProposalResults()[0].getRank()); - assertEquals(1, result.getProposalResults()[1].getRank()); - assertEquals(2, result.getProposalResults()[2].getRank()); - } - - @Test - @DisplayName("Test static default grade with thousands of proposals and millions of judges") - public void testStaticDefaultWithThousandsOfProposals() throws Throwable { - int amountOfProposals = 1337; - Integer amountOfJudges = 60000000; - Integer defaultGrade = 0; - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - ProposalTallyInterface[] tallies = new ProposalTallyInterface[amountOfProposals]; - for (int i = 0 ; i < amountOfProposals ; i++) { - tallies[i] = new ProposalTally(new Integer[]{ 7, 204, 107 }); - } - TallyInterface tally = new StaticDefaultTally(tallies, amountOfJudges, defaultGrade); - - ResultInterface result = mj.deliberate(tally); - - assertNotNull(result); - assertEquals(amountOfProposals, result.getProposalResults().length); - for (int i = 0 ; i < amountOfProposals ; i++) { - assertEquals(1, result.getProposalResults()[i].getRank()); - } - } - - // /!. This crashes -// @Test -// @DisplayName("Test static default grade with millions of proposals") -// public void testStaticDefaultWithMillionsOfProposals() { -// int amountOfProposals = 13375111; -// Integer amountOfJudges = 60000000; -// Integer defaultGrade = 0; -// DeliberatorInterface mj = new MajorityJudgmentDeliberator(); -// ProposalTallyInterface[] tallies = new ProposalTallyInterface[amountOfProposals]; -// for (int i = 0 ; i < amountOfProposals ; i++) { -// tallies[i] = new ProposalTally(new Integer[]{ 7, 204, 107 }); -// } -// TallyInterface tally = new TallyWithDefaultGrade(tallies, amountOfJudges, defaultGrade); -// -// ResultInterface result = mj.deliberate(tally); -// -// assertNotNull(result); -// assertEquals(amountOfProposals, result.getProposalResults().length); -// for (int i = 0 ; i < amountOfProposals ; i++) { -// assertEquals(1, result.getProposalResults()[i].getRank()); -// } -// } - - @Test - @DisplayName("Test with a median default grade") - public void testMedianDefaultGrade() throws Throwable { - Integer amountOfJudges = 42; - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - TallyInterface tally = new MedianDefaultTally(new ProposalTallyInterface[] { - new ProposalTally(new Integer[]{ 0, 0, 1 }), - new ProposalTally(new Integer[]{ 0, 1, 0 }), - new ProposalTally(new Integer[]{ 1, 1, 1 }), - new ProposalTally(new Integer[]{ 1, 0, 1 }), - new ProposalTally(new Integer[]{ 1, 0, 0 }), - }, amountOfJudges); - - ResultInterface result = mj.deliberate(tally); - - assertNotNull(result); - assertEquals(5, result.getProposalResults().length); - assertEquals(1, result.getProposalResults()[0].getRank()); - assertEquals(2, result.getProposalResults()[1].getRank()); - assertEquals(3, result.getProposalResults()[2].getRank()); - assertEquals(4, result.getProposalResults()[3].getRank()); - assertEquals(5, result.getProposalResults()[4].getRank()); - } - - @Test - @DisplayName("Test normalized tallies with thousands of (prime) proposals") - public void testNormalizedWithThousandsOfPrimeProposals() throws Throwable { - // We're using primes to test the upper bounds of our LCM shenanigans. - // This test takes a long time! (3 seconds) - - int amountOfProposals = primes.length; // 1437 - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - ProposalTallyInterface[] tallies = new ProposalTallyInterface[amountOfProposals]; - - for (int i = 0 ; i < amountOfProposals ; i++) { - Integer prime = primes[i % primes.length]; - tallies[i] = new ProposalTally(new Integer[]{ prime-1, 1, 0 }); - } - TallyInterface tally = new NormalizedTally(tallies); - - ResultInterface result = mj.deliberate(tally); - - assertNotNull(result); - assertEquals(amountOfProposals, result.getProposalResults().length); - for (int i = 0 ; i < amountOfProposals ; i++) { - assertEquals( - 1 + i, result.getProposalResults()[i].getRank(), - "Rank of Proposal #" + i - ); - } - } - - @Test - @DisplayName("Test normalized tallies with thousands of proposals") - public void testNormalizedWithThousandsOfProposals() throws Throwable { - // This test is faster than the primes one (0.4 seconds), - // since primes are the worst-case scenario for our LCM. - - int amountOfProposals = primes.length; // 1437 - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - ProposalTallyInterface[] tallies = new ProposalTallyInterface[amountOfProposals]; - - for (int i = 0 ; i < amountOfProposals ; i++) { - tallies[i] = new ProposalTally(new Integer[]{ i, 1, 0 }); - } - TallyInterface tally = new NormalizedTally(tallies); - - ResultInterface result = mj.deliberate(tally); - - assertNotNull(result); - assertEquals(amountOfProposals, result.getProposalResults().length); - for (int i = 0 ; i < amountOfProposals ; i++) { - assertEquals( - 1 + i, result.getProposalResults()[i].getRank(), - "Rank of Proposal #" + i - ); - } - } - - @Test - @DisplayName("Test favoring adhesion") - public void testFavoringAdhesion() throws Exception { - boolean favorContestation = false; - Integer amountOfJudges = 4; - DeliberatorInterface mj = new MajorityJudgmentDeliberator(favorContestation); - TallyInterface tally = new Tally(new ProposalTallyInterface[] { - new ProposalTally(new Integer[]{ 2, 0, 2 }), - new ProposalTally(new Integer[]{ 0, 2, 2 }), - new ProposalTally(new Integer[]{ 2, 1, 1 }), - }, amountOfJudges); - - ResultInterface result = mj.deliberate(tally); - - assertNotNull(result); - assertEquals(3, result.getProposalResults().length); - assertEquals(2, result.getProposalResults()[0].getRank()); - assertEquals(1, result.getProposalResults()[1].getRank()); - assertEquals(3, result.getProposalResults()[2].getRank()); - assertEquals(2, result.getProposalResults()[0].getAnalysis().getMedianGrade()); - assertEquals(2, result.getProposalResults()[1].getAnalysis().getMedianGrade()); - assertEquals(1, result.getProposalResults()[2].getAnalysis().getMedianGrade()); - } - - @Test - @DisplayName("Test numeric score") - public void testNumericScore() throws Exception { - boolean favorContestation = true; - boolean numerizeScore = true; - - Integer amountOfJudges = 3; - DeliberatorInterface mj = new MajorityJudgmentDeliberator(favorContestation, numerizeScore); - TallyInterface tally = new Tally(new ProposalTallyInterface[] { - new ProposalTally(new Integer[]{ 1, 0, 2 }), - new ProposalTally(new Integer[]{ 0, 2, 1 }), - }, amountOfJudges); - - ResultInterface result = mj.deliberate(tally); - - assertNotNull(result); - assertEquals("202003003", result.getProposalResults()[0].getScore()); - assertEquals("104203003", result.getProposalResults()[1].getScore()); - } - - @Test - @DisplayName("Fail on unbalanced tallies") - public void testFailureOnUnbalancedTallies() { - Integer amountOfJudges = 2; - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - TallyInterface tally = new Tally(new ProposalTallyInterface[] { - new ProposalTally(new Integer[]{ 0, 0, 2 }), - new ProposalTally(new Integer[]{ 0, 1, 0 }), - new ProposalTally(new Integer[]{ 2, 0, 0 }), - }, amountOfJudges); - -// assertThrows(null, null, null); - assertThrows( - UnbalancedTallyException.class, - new Executable() { - @Override - public void execute() throws Throwable { - mj.deliberate(tally); - } - }, - "An exception is raised" - ); - - boolean caught = false; - try { - mj.deliberate(tally); - } catch (UnbalancedTallyException e) { - caught = true; - assertNotNull(e.getLocalizedMessage()); - } - assertTrue(caught, "An exception is raised"); - } - - @Test - @DisplayName("Fail on negative tallies") - public void testFailureOnNegativeTallies() { - Integer amountOfJudges = 2; - DeliberatorInterface mj = new MajorityJudgmentDeliberator(); - TallyInterface tally = new Tally(new ProposalTallyInterface[] { - new ProposalTally(new Integer[]{ 0, 4, -2 }), - new ProposalTally(new Integer[]{ 0, 2, 0 }), - }, amountOfJudges); - - assertThrows( - IncoherentTallyException.class, - new Executable() { - @Override - public void execute() throws Throwable { - mj.deliberate(tally); - } - }, - "An exception is raised" - ); - - boolean caught = false; - try { - mj.deliberate(tally); - } catch (IncoherentTallyException e) { - caught = true; - assertNotNull(e.getLocalizedMessage()); - } - assertTrue(caught, "An exception is raised"); - } - - - // … - - -// @Test -// public void runBenchmarks() throws Exception { -// Options options = new OptionsBuilder() -// .include(this.getClass().getName() + ".*") -// .mode(Mode.AverageTime) -// .warmupTime(TimeValue.seconds(1)) -// .warmupIterations(6) -// .threads(1) -// .measurementIterations(6) -// .forks(1) -// .shouldFailOnError(true) -// .shouldDoGC(true) -// .build(); -// -// new Runner(options).run(); -// } - - /** - * Helps us test extreme situations (upper bounds) in normalized tallies, - * since we use the LCM (Least Common Multiple) to avoid floating-point arithmetic. - */ - protected Integer[] primes = new Integer[] { - 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, - 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, - 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, - 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, - 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, - 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, - 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, - 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, - 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, - 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, - 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, - 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, - 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, - 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, - 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, - 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, - 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, - 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, - 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, - 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, - 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, - 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, - 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, - 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, - 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, - 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, - 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, - 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, - 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, - 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, - 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 2927, 2939, 2953, 2957, - 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, - 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, - 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, - 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, - 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, - 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, - 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, - 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, - 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, - 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, - 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, - 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, - 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, - 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, - 4483, 4493, 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583, 4591, - 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, - 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, - 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, - 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, - 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, - 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, - 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, - 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, - 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, - 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, - 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, - 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, - 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, 6007, 6011, 6029, - 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, - 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, - 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337, - 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, - 6469, 6473, 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, - 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, - 6703, 6709, 6719, 6733, 6737, 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, - 6827, 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, - 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, 7001, 7013, 7019, - 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 7151, 7159, - 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, - 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, - 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, - 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, - 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, 7727, - 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, - 7877, 7879, 7883, 7901, 7907, 7919, 7927, 7933, 7937, 7949, 7951, 7963, 7993, - 8009, 8011, 8017, 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, - 8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219, 8221, 8231, 8233, - 8237, 8243, 8263, 8269, 8273, 8287, 8291, 8293, 8297, 8311, 8317, 8329, 8353, - 8363, 8369, 8377, 8387, 8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, - 8501, 8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597, 8599, 8609, - 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677, 8681, 8689, 8693, 8699, 8707, - 8713, 8719, 8731, 8737, 8741, 8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, - 8821, 8831, 8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929, 8933, - 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, 9013, 9029, 9041, 9043, - 9049, 9059, 9067, 9091, 9103, 9109, 9127, 9133, 9137, 9151, 9157, 9161, 9173, - 9181, 9187, 9199, 9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283, - 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377, 9391, 9397, 9403, - 9413, 9419, 9421, 9431, 9433, 9437, 9439, 9461, 9463, 9467, 9473, 9479, 9491, - 9497, 9511, 9521, 9533, 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, - 9631, 9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733, 9739, 9743, - 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811, 9817, 9829, 9833, 9839, 9851, - 9857, 9859, 9871, 9883, 9887, 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, - 9973, 10007, 10009, 10037, 10039, 10061, 10067, 10069, 10079, 10091, 10093, - 10099, 10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, 10169, 10177, - 10181, 10193, 10211, 10223, 10243, 10247, 10253, 10259, 10267, 10271, 10273, - 10289, 10301, 10303, 10313, 10321, 10331, 10333, 10337, 10343, 10357, 10369, - 10391, 10399, 10427, 10429, 10433, 10453, 10457, 10459, 10463, 10477, 10487, - 10499, 10501, 10513, 10529, 10531, 10559, 10567, 10589, 10597, 10601, 10607, - 10613, 10627, 10631, 10639, 10651, 10657, 10663, 10667, 10687, 10691, 10709, - 10711, 10723, 10729, 10733, 10739, 10753, 10771, 10781, 10789, 10799, 10831, - 10837, 10847, 10853, 10859, 10861, 10867, 10883, 10889, 10891, 10903, 10909, - 10937, 10939, 10949, 10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, - 11057, 11059, 11069, 11071, 11083, 11087, 11093, 11113, 11117, 11119, 11131, - 11149, 11159, 11161, 11171, 11173, 11177, 11197, 11213, 11239, 11243, 11251, - 11257, 11261, 11273, 11279, 11287, 11299, 11311, 11317, 11321, 11329, 11351, - 11353, 11369, 11383, 11393, 11399, 11411, 11423, 11437, 11443, 11447, 11467, - 11471, 11483, 11489, 11491, 11497, 11503, 11519, 11527, 11549, 11551, 11579, - 11587, 11593, 11597, 11617, 11621, 11633, 11657, 11677, 11681, 11689, 11699, - 11701, 11717, 11719, 11731, 11743, 11777, 11779, 11783, 11789, 11801, 11807, - 11813, 11821, 11827, 11831, 11833, 11839, 11863, 11867, 11887, 11897, 11903, - 11909, 11923, 11927, 11933, 11939, 11941, 11953, 11959, 11969, 11971, 11981 - }; - -// public static List sieveOfEratosthenes(int n) { -// boolean prime[] = new boolean[n + 1]; -// Arrays.fill(prime, true); -// for (int p = 2; p * p <= n; p++) { -// if (prime[p]) { -// for (int i = p * 2; i <= n; i += p) { -// prime[i] = false; -// } -// } -// } -// List primeNumbers = new LinkedList<>(); -// for (int i = 2; i <= n; i++) { -// if (prime[i]) { -// primeNumbers.add(i); -// } -// } -// return primeNumbers; -// } + @DisplayName("Test majority judgment deliberation from JSON assertions") + @ParameterizedTest(name = "#{index} {0}") + @JsonFileSource(resources = "/assertions.json") + public void testFromJson(JsonObject datum) throws Throwable { + // This test uses the JSON file in test/resources/ + // It also allows testing the various modes of default grades. + + JsonArray jsonTallies = datum.getJsonArray("tallies"); + int amountOfProposals = jsonTallies.size(); + BigInteger amountOfParticipants = new BigInteger(datum.get("participants").toString()); + ProposalTallyInterface[] tallies = new ProposalTallyInterface[amountOfProposals]; + for (int i = 0; i < amountOfProposals; i++) { + JsonArray jsonTally = jsonTallies.getJsonArray(i); + int amountOfGrades = jsonTally.size(); + BigInteger[] tally = new BigInteger[amountOfGrades]; + for (int g = 0; g < amountOfGrades; g++) { + JsonValue amountForGrade = jsonTally.get(g); + tally[g] = new BigInteger(amountForGrade.toString()); + } + tallies[i] = new ProposalTally(tally); + } + + String mode = datum.getString("mode", "None"); + TallyInterface tally; + if ("StaticDefault".equalsIgnoreCase(mode)) { + tally = + new StaticDefaultTally( + tallies, amountOfParticipants, datum.getInt("default", 0)); + } else if ("MedianDefault".equalsIgnoreCase(mode)) { + tally = new MedianDefaultTally(tallies, amountOfParticipants); + } else if ("Normalized".equalsIgnoreCase(mode)) { + tally = new NormalizedTally(tallies); + } else { + tally = new Tally(tallies, amountOfParticipants); + } + + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + ResultInterface result = mj.deliberate(tally); + + assertNotNull(result); + JsonArray jsonRanks = datum.getJsonArray("ranks"); + for (int i = 0; i < amountOfProposals; i++) { + assertEquals( + jsonRanks.getInt(i), + result.getProposalResults()[i].getRank(), + "Rank of tally #" + i); + } + } + + @Test + @DisplayName("Test the basic demo usage of the README") + public void testDemoUsage() throws Throwable { + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + TallyInterface tally = + new Tally( + new ProposalTallyInterface[] { + new ProposalTally(new Integer[] {4, 5, 2, 1, 3, 1, 2}), + new ProposalTally(new Integer[] {3, 6, 2, 1, 3, 1, 2}), + }); + + ResultInterface result = mj.deliberate(tally); + + assertNotNull(result); + assertEquals(2, result.getProposalResults().length); + assertEquals(2, result.getProposalResults()[0].getRank()); + assertEquals(1, result.getProposalResults()[1].getRank()); + } + + @Test + @DisplayName("Test the basic demo usage with billions of participants") + public void testDemoUsageWithBigNumbers() throws Throwable { + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + TallyInterface tally = + new Tally( + new ProposalTallyInterface[] { + new ProposalTally( + new Long[] { + 11312415004L, 21153652410L, 24101523299L, 18758623562L + }), + new ProposalTally( + new Long[] { + 11312415004L, 21153652400L, 24101523299L, 18758623572L + }), + // new ProposalTally(new Long[]{14526586452L, 40521123260L, + // 14745623120L, 40526235129L}), + }); + ResultInterface result = mj.deliberate(tally); + + // System.out.println("Score 0: "+result.getProposalResults()[0].getScore()); + // System.out.println("Score 1: "+result.getProposalResults()[1].getScore()); + + assertNotNull(result); + assertEquals(2, result.getProposalResults().length); + assertEquals(2, result.getProposalResults()[0].getRank()); + assertEquals(1, result.getProposalResults()[1].getRank()); + } + + @Test + @DisplayName("Test the collect demo usage of the README") + public void testDemoUsageCollectedTally() throws Throwable { + Integer amountOfProposals = 3; + Integer amountOfGrades = 4; + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + CollectedTally tally = new CollectedTally(amountOfProposals, amountOfGrades); + + Integer firstProposal = 0; + Integer secondProposal = 1; + Integer thirdProposal = 2; + Integer gradeReject = 0; + Integer gradePassable = 1; + Integer gradeGood = 2; + Integer gradeExcellent = 3; + + tally.collect(firstProposal, gradeReject); + tally.collect(firstProposal, gradeReject); + tally.collect(firstProposal, gradePassable); + tally.collect(firstProposal, gradePassable); + tally.collect(firstProposal, gradePassable); + tally.collect(firstProposal, gradeExcellent); + tally.collect(firstProposal, gradeExcellent); + tally.collect(firstProposal, gradeExcellent); + + tally.collect(secondProposal, gradeReject); + tally.collect(secondProposal, gradeReject); + tally.collect(secondProposal, gradeGood); + tally.collect(secondProposal, gradeGood); + tally.collect(secondProposal, gradeGood); + tally.collect(secondProposal, gradeGood); + tally.collect(secondProposal, gradeExcellent); + tally.collect(secondProposal, gradeExcellent); + + tally.collect(thirdProposal, gradeReject); + tally.collect(thirdProposal, gradeReject); + tally.collect(thirdProposal, gradePassable); + tally.collect(thirdProposal, gradeGood); + tally.collect(thirdProposal, gradeGood); + tally.collect(thirdProposal, gradeGood); + tally.collect(thirdProposal, gradeExcellent); + tally.collect(thirdProposal, gradeExcellent); + + ResultInterface result = mj.deliberate(tally); + + assertNotNull(result); + assertEquals(3, result.getProposalResults().length); + assertEquals(3, result.getProposalResults()[0].getRank()); + assertEquals(1, result.getProposalResults()[1].getRank()); + assertEquals(2, result.getProposalResults()[2].getRank()); + } + + @Test + @DisplayName("Test the normalized collect demo usage of the README") + public void testDemoUsageNormalizedCollectedTally() throws Throwable { + Integer amountOfProposals = 4; + Integer amountOfGrades = 3; + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + CollectedTally tally = new CollectedTally(amountOfProposals, amountOfGrades); + + Integer firstProposal = 0; + Integer secondProposal = 1; + Integer thirdProposal = 2; + Integer fourthProposal = 3; + Integer gradeReject = 0; + Integer gradePassable = 1; + Integer gradeGood = 2; + + tally.collect(firstProposal, gradeReject); + tally.collect(firstProposal, gradeReject); + tally.collect(firstProposal, gradePassable); + tally.collect(firstProposal, gradePassable); + tally.collect(firstProposal, gradeGood); + tally.collect(firstProposal, gradeGood); + + tally.collect(secondProposal, gradeReject); + tally.collect(secondProposal, gradePassable); + tally.collect(secondProposal, gradeGood); + + tally.collect(thirdProposal, gradePassable); + + tally.collect(fourthProposal, gradeGood); + + ResultInterface result = mj.deliberate(new NormalizedTally(tally)); + + assertNotNull(result); + assertEquals(4, result.getProposalResults().length); + assertEquals(3, result.getProposalResults()[0].getRank()); + assertEquals(3, result.getProposalResults()[1].getRank()); + assertEquals(2, result.getProposalResults()[2].getRank()); + assertEquals(1, result.getProposalResults()[3].getRank()); + } + + @Test + @DisplayName("Test with a static default grade (\"worst grade\" == 0)") + public void testWithStaticDefaultGrade() throws Throwable { + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + Long amountOfJudges = 3L; + Integer defaultGrade = 0; + TallyInterface tally = + new StaticDefaultTally( + new ProposalTallyInterface[] { + new ProposalTally(new Integer[] {0, 0, 1}), + new ProposalTally(new Integer[] {0, 3, 0}), + new ProposalTally(new Integer[] {2, 0, 1}), + }, + amountOfJudges, + defaultGrade); + + ResultInterface result = mj.deliberate(tally); + + assertNotNull(result); + assertEquals(3, result.getProposalResults().length); + assertEquals(2, result.getProposalResults()[0].getRank()); + assertEquals(1, result.getProposalResults()[1].getRank()); + assertEquals(2, result.getProposalResults()[2].getRank()); + } + + @Test + @DisplayName("Test static default grade with thousands of proposals and millions of judges") + public void testStaticDefaultWithThousandsOfProposals() throws Throwable { + int amountOfProposals = 1337; + Integer amountOfJudges = 60000000; + Integer defaultGrade = 0; + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + ProposalTallyInterface[] tallies = new ProposalTallyInterface[amountOfProposals]; + for (int i = 0; i < amountOfProposals; i++) { + tallies[i] = new ProposalTally(new Integer[] {7, 204, 107}); + } + TallyInterface tally = new StaticDefaultTally(tallies, amountOfJudges, defaultGrade); + + ResultInterface result = mj.deliberate(tally); + + assertNotNull(result); + assertEquals(amountOfProposals, result.getProposalResults().length); + for (int i = 0; i < amountOfProposals; i++) { + assertEquals(1, result.getProposalResults()[i].getRank()); + } + } + + // /!. This crashes + // @Test + // @DisplayName("Test static default grade with millions of proposals") + // public void testStaticDefaultWithMillionsOfProposals() { + // int amountOfProposals = 13375111; + // Integer amountOfJudges = 60000000; + // Integer defaultGrade = 0; + // DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + // ProposalTallyInterface[] tallies = new ProposalTallyInterface[amountOfProposals]; + // for (int i = 0 ; i < amountOfProposals ; i++) { + // tallies[i] = new ProposalTally(new Integer[]{ 7, 204, 107 }); + // } + // TallyInterface tally = new TallyWithDefaultGrade(tallies, amountOfJudges, defaultGrade); + // + // ResultInterface result = mj.deliberate(tally); + // + // assertNotNull(result); + // assertEquals(amountOfProposals, result.getProposalResults().length); + // for (int i = 0 ; i < amountOfProposals ; i++) { + // assertEquals(1, result.getProposalResults()[i].getRank()); + // } + // } + + @Test + @DisplayName("Test with a median default grade") + public void testMedianDefaultGrade() throws Throwable { + Integer amountOfJudges = 42; + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + TallyInterface tally = + new MedianDefaultTally( + new ProposalTallyInterface[] { + new ProposalTally(new Integer[] {0, 0, 1}), + new ProposalTally(new Integer[] {0, 1, 0}), + new ProposalTally(new Integer[] {1, 1, 1}), + new ProposalTally(new Integer[] {1, 0, 1}), + new ProposalTally(new Integer[] {1, 0, 0}), + }, + amountOfJudges); + + ResultInterface result = mj.deliberate(tally); + + assertNotNull(result); + assertEquals(5, result.getProposalResults().length); + assertEquals(1, result.getProposalResults()[0].getRank()); + assertEquals(2, result.getProposalResults()[1].getRank()); + assertEquals(3, result.getProposalResults()[2].getRank()); + assertEquals(4, result.getProposalResults()[3].getRank()); + assertEquals(5, result.getProposalResults()[4].getRank()); + } + + @Test + @DisplayName("Test normalized tallies with thousands of (prime) proposals") + public void testNormalizedWithThousandsOfPrimeProposals() throws Throwable { + // We're using primes to test the upper bounds of our LCM shenanigans. + // This test takes a long time! (3 seconds) + + int amountOfProposals = primes.length; // 1437 + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + ProposalTallyInterface[] tallies = new ProposalTallyInterface[amountOfProposals]; + + for (int i = 0; i < amountOfProposals; i++) { + Integer prime = primes[i % primes.length]; + tallies[i] = new ProposalTally(new Integer[] {prime - 1, 1, 0}); + } + TallyInterface tally = new NormalizedTally(tallies); + + ResultInterface result = mj.deliberate(tally); + + assertNotNull(result); + assertEquals(amountOfProposals, result.getProposalResults().length); + for (int i = 0; i < amountOfProposals; i++) { + assertEquals(1 + i, result.getProposalResults()[i].getRank(), "Rank of Proposal #" + i); + } + } + + @Test + @DisplayName("Test normalized tallies with thousands of proposals") + public void testNormalizedWithThousandsOfProposals() throws Throwable { + // This test is faster than the primes one (0.4 seconds), + // since primes are the worst-case scenario for our LCM. + + int amountOfProposals = primes.length; // 1437 + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + ProposalTallyInterface[] tallies = new ProposalTallyInterface[amountOfProposals]; + + for (int i = 0; i < amountOfProposals; i++) { + tallies[i] = new ProposalTally(new Integer[] {i, 1, 0}); + } + TallyInterface tally = new NormalizedTally(tallies); + + ResultInterface result = mj.deliberate(tally); + + assertNotNull(result); + assertEquals(amountOfProposals, result.getProposalResults().length); + for (int i = 0; i < amountOfProposals; i++) { + assertEquals(1 + i, result.getProposalResults()[i].getRank(), "Rank of Proposal #" + i); + } + } + + @Test + @DisplayName("Test favoring adhesion") + public void testFavoringAdhesion() throws Exception { + boolean favorContestation = false; + Integer amountOfJudges = 4; + DeliberatorInterface mj = new MajorityJudgmentDeliberator(favorContestation); + TallyInterface tally = + new Tally( + new ProposalTallyInterface[] { + new ProposalTally(new Integer[] {2, 0, 2}), + new ProposalTally(new Integer[] {0, 2, 2}), + new ProposalTally(new Integer[] {2, 1, 1}), + }, + amountOfJudges); + + ResultInterface result = mj.deliberate(tally); + + assertNotNull(result); + assertEquals(3, result.getProposalResults().length); + assertEquals(2, result.getProposalResults()[0].getRank()); + assertEquals(1, result.getProposalResults()[1].getRank()); + assertEquals(3, result.getProposalResults()[2].getRank()); + assertEquals(2, result.getProposalResults()[0].getAnalysis().getMedianGrade()); + assertEquals(2, result.getProposalResults()[1].getAnalysis().getMedianGrade()); + assertEquals(1, result.getProposalResults()[2].getAnalysis().getMedianGrade()); + } + + @Test + @DisplayName("Test numeric score") + public void testNumericScore() throws Exception { + boolean favorContestation = true; + boolean numerizeScore = true; + + Integer amountOfJudges = 3; + DeliberatorInterface mj = new MajorityJudgmentDeliberator(favorContestation, numerizeScore); + TallyInterface tally = + new Tally( + new ProposalTallyInterface[] { + new ProposalTally(new Integer[] {1, 0, 2}), + new ProposalTally(new Integer[] {0, 2, 1}), + }, + amountOfJudges); + + ResultInterface result = mj.deliberate(tally); + + assertNotNull(result); + assertEquals("202003003", result.getProposalResults()[0].getScore()); + assertEquals("104203003", result.getProposalResults()[1].getScore()); + } + + @Test + @DisplayName("Fail on unbalanced tallies") + public void testFailureOnUnbalancedTallies() { + Integer amountOfJudges = 2; + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + TallyInterface tally = + new Tally( + new ProposalTallyInterface[] { + new ProposalTally(new Integer[] {0, 0, 2}), + new ProposalTally(new Integer[] {0, 1, 0}), + new ProposalTally(new Integer[] {2, 0, 0}), + }, + amountOfJudges); + + // assertThrows(null, null, null); + assertThrows( + UnbalancedTallyException.class, + new Executable() { + @Override + public void execute() throws Throwable { + mj.deliberate(tally); + } + }, + "An exception is raised"); + + boolean caught = false; + try { + mj.deliberate(tally); + } catch (UnbalancedTallyException e) { + caught = true; + assertNotNull(e.getLocalizedMessage()); + } + assertTrue(caught, "An exception is raised"); + } + + @Test + @DisplayName("Fail on negative tallies") + public void testFailureOnNegativeTallies() { + Integer amountOfJudges = 2; + DeliberatorInterface mj = new MajorityJudgmentDeliberator(); + TallyInterface tally = + new Tally( + new ProposalTallyInterface[] { + new ProposalTally(new Integer[] {0, 4, -2}), + new ProposalTally(new Integer[] {0, 2, 0}), + }, + amountOfJudges); + + assertThrows( + IncoherentTallyException.class, + new Executable() { + @Override + public void execute() throws Throwable { + mj.deliberate(tally); + } + }, + "An exception is raised"); + + boolean caught = false; + try { + mj.deliberate(tally); + } catch (IncoherentTallyException e) { + caught = true; + assertNotNull(e.getLocalizedMessage()); + } + assertTrue(caught, "An exception is raised"); + } + + // … + + // @Test + // public void runBenchmarks() throws Exception { + // Options options = new OptionsBuilder() + // .include(this.getClass().getName() + ".*") + // .mode(Mode.AverageTime) + // .warmupTime(TimeValue.seconds(1)) + // .warmupIterations(6) + // .threads(1) + // .measurementIterations(6) + // .forks(1) + // .shouldFailOnError(true) + // .shouldDoGC(true) + // .build(); + // + // new Runner(options).run(); + // } + + /** + * Helps us test extreme situations (upper bounds) in normalized tallies, since we use the LCM + * (Least Common Multiple) to avoid floating-point arithmetic. + */ + protected Integer[] primes = + new Integer[] { + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, + 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, + 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, + 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, + 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, + 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, + 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, + 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, + 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, + 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, + 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, + 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, + 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, + 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, + 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, + 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, + 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, + 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, + 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, + 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, + 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, + 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, + 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, + 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, + 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, + 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, + 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 2833, + 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 2927, 2939, 2953, + 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, + 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, + 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, + 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, + 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, + 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, + 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, + 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, + 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, + 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, + 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, + 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, + 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, 4447, 4451, 4457, 4463, + 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583, 4591, + 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, + 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, + 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951, 4957, + 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, + 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, + 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, 5323, + 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, + 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, + 5563, 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, + 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, 5801, + 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, 5879, 5881, + 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, + 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, + 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, + 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, 6373, + 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, 6481, 6491, 6521, 6529, 6547, + 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, + 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, 6763, 6779, 6781, + 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, + 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, 7001, + 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 7151, + 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, + 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, + 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, + 7549, 7559, 7561, 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, + 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, + 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, + 7919, 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, 8039, 8053, 8059, + 8069, 8081, 8087, 8089, 8093, 8101, 8111, 8117, 8123, 8147, 8161, 8167, 8171, 8179, + 8191, 8209, 8219, 8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291, 8293, + 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387, 8389, 8419, 8423, 8429, 8431, + 8443, 8447, 8461, 8467, 8501, 8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, + 8597, 8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677, 8681, 8689, 8693, + 8699, 8707, 8713, 8719, 8731, 8737, 8741, 8747, 8753, 8761, 8779, 8783, 8803, 8807, + 8819, 8821, 8831, 8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929, 8933, + 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, 9013, 9029, 9041, 9043, 9049, + 9059, 9067, 9091, 9103, 9109, 9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, + 9199, 9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283, 9293, 9311, 9319, + 9323, 9337, 9341, 9343, 9349, 9371, 9377, 9391, 9397, 9403, 9413, 9419, 9421, 9431, + 9433, 9437, 9439, 9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533, 9539, + 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631, 9643, 9649, 9661, 9677, 9679, + 9689, 9697, 9719, 9721, 9733, 9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, + 9811, 9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887, 9901, 9907, 9923, + 9929, 9931, 9941, 9949, 9967, 9973, 10007, 10009, 10037, 10039, 10061, 10067, 10069, + 10079, 10091, 10093, 10099, 10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, + 10169, 10177, 10181, 10193, 10211, 10223, 10243, 10247, 10253, 10259, 10267, 10271, + 10273, 10289, 10301, 10303, 10313, 10321, 10331, 10333, 10337, 10343, 10357, 10369, + 10391, 10399, 10427, 10429, 10433, 10453, 10457, 10459, 10463, 10477, 10487, 10499, + 10501, 10513, 10529, 10531, 10559, 10567, 10589, 10597, 10601, 10607, 10613, 10627, + 10631, 10639, 10651, 10657, 10663, 10667, 10687, 10691, 10709, 10711, 10723, 10729, + 10733, 10739, 10753, 10771, 10781, 10789, 10799, 10831, 10837, 10847, 10853, 10859, + 10861, 10867, 10883, 10889, 10891, 10903, 10909, 10937, 10939, 10949, 10957, 10973, + 10979, 10987, 10993, 11003, 11027, 11047, 11057, 11059, 11069, 11071, 11083, 11087, + 11093, 11113, 11117, 11119, 11131, 11149, 11159, 11161, 11171, 11173, 11177, 11197, + 11213, 11239, 11243, 11251, 11257, 11261, 11273, 11279, 11287, 11299, 11311, 11317, + 11321, 11329, 11351, 11353, 11369, 11383, 11393, 11399, 11411, 11423, 11437, 11443, + 11447, 11467, 11471, 11483, 11489, 11491, 11497, 11503, 11519, 11527, 11549, 11551, + 11579, 11587, 11593, 11597, 11617, 11621, 11633, 11657, 11677, 11681, 11689, 11699, + 11701, 11717, 11719, 11731, 11743, 11777, 11779, 11783, 11789, 11801, 11807, 11813, + 11821, 11827, 11831, 11833, 11839, 11863, 11867, 11887, 11897, 11903, 11909, 11923, + 11927, 11933, 11939, 11941, 11953, 11959, 11969, 11971, 11981 + }; + + // public static List sieveOfEratosthenes(int n) { + // boolean prime[] = new boolean[n + 1]; + // Arrays.fill(prime, true); + // for (int p = 2; p * p <= n; p++) { + // if (prime[p]) { + // for (int i = p * 2; i <= n; i += p) { + // prime[i] = false; + // } + // } + // } + // List primeNumbers = new LinkedList<>(); + // for (int i = 2; i <= n; i++) { + // if (prime[i]) { + // primeNumbers.add(i); + // } + // } + // return primeNumbers; + // } } diff --git a/src/test/java/fr/mieuxvoter/mj/ProposalTallyAnalysisTest.java b/src/test/java/fr/mieuxvoter/mj/ProposalTallyAnalysisTest.java index f98df10..f08277f 100644 --- a/src/test/java/fr/mieuxvoter/mj/ProposalTallyAnalysisTest.java +++ b/src/test/java/fr/mieuxvoter/mj/ProposalTallyAnalysisTest.java @@ -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 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)); + } }