Browse Source

feat: implement a majority judgment deliberator

This should work (at least, the test-suite passes).
This is our first java library, and we're newbies,
so be kind and do patch what's gouging your eyes.

This implementation is score-based, for performance.
There are many other ways of implementing MJ.

There is no support (yet) for a default grade,
but you can do it yourself while building the tally.

Special thanks to @plguhur for the assistance,
and the whole MieuxVoter operational team.

/spend 36h
pull/8/head
Dominique Merle 1 year ago
commit
7d9b24927e
  1. 8
      .gitignore
  2. 21
      LICENSE.md
  3. 77
      README.md
  4. 16
      src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java
  5. 119
      src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java
  6. 26
      src/main/java/fr/mieuxvoter/mj/ProposalResult.java
  7. 21
      src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java
  8. 45
      src/main/java/fr/mieuxvoter/mj/ProposalTally.java
  9. 204
      src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java
  10. 22
      src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java
  11. 15
      src/main/java/fr/mieuxvoter/mj/Result.java
  12. 12
      src/main/java/fr/mieuxvoter/mj/ResultInterface.java
  13. 39
      src/main/java/fr/mieuxvoter/mj/Tally.java
  14. 11
      src/main/java/fr/mieuxvoter/mj/TallyInterface.java
  15. 47
      src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java
  16. 167
      src/test/java/fr/mieuxvoter/mj/ProposalTallyAnalysisTest.java

8
.gitignore

@ -0,0 +1,8 @@
/.settings
/.classpath
/.project
/.gradle
/build
*.class

21
LICENSE.md

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 MieuxVoter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

77
README.md

@ -0,0 +1,77 @@
# Majority Judgment Library for Java
Test-driven java library to help deliberate using Majority Judgment.
## Example Usage
Collect the **tallies** for each Proposal (aka. Candidate) by your own means,
provide them to the `MajorityJudgmentDeliberator`, and get back the **rank** of each Proposal.
Let's say you have the following tally:
| | To Reject | Poor | Passable | Somewhat Good | Good | Very Good | Excellent |
|------------|-----------|------|----------|---------------|------|-----------|-----------|
| Proposal A | 4 | 5 | 2 | 1 | 3 | 1 | 2 |
| Proposal B | 3 | 6 | 2 | 2 | 2 | 1 | 2 |
| … | | | | | | | |
| | | | | | | | |
``` java
DeliberatorInterface mj = new MajorityJudgmentDeliberator();
TallyInterface tally = new Tally(new ProposalTallyInterface[] {
// Amounts of judgments received for each grade, from "worst" grade to "best" grade
new ProposalTally(new Integer[]{4, 5, 2, 1, 3, 1, 2}), // Proposal A
new ProposalTally(new Integer[]{3, 6, 2, 1, 3, 1, 2}), // Proposal B
// …
});
ResultInterface result = mj.deliberate(tally);
// Each proposal result has a rank, and results are returned by input order
assert(2 == result.getProposalResults().length);
assert(2 == result.getProposalResults()[0].getRank()); // Proposal A
assert(1 == result.getProposalResults()[1].getRank()); // Proposal B
```
## Run the test-suite
`CTRL+F11` in Eclipse.
## Roadmap
- [ ] Unit-Tests
- [ ] Deliberation algorithm
- [ ] Release v0.1.0
- [ ] Allow choosing a default grade
- [ ] Release v0.2.0
- [ ] Publish on package repositories
- [ ] Maven
- [ ] … ? (please share your knowledge to help us!)
- [ ] Release v0.3.0
- [ ] Use it somewhere in another app, adjust API as needed (one last time)
- [ ] Release v1.0.0
## Gondor calls for Help!
We are not accustomed to Java library development and we'd love reviews from seasoned veterans !
Feel free to fork and request merges for your contributions and active readings !
## License
[MIT](./LICENSE.md) → _Do whatever you want except complain._
Majority Judgment itself is part of the Commons, obviously.
## Fund us
We'd love to invest more energy in Majority Judgment development.
Please consider funding us, every bit helps : https://www.paypal.com/donate/?hosted_button_id=QD6U4D323WV4S

16
src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java

@ -0,0 +1,16 @@
package fr.mieuxvoter.mj;
/**
* A Deliberator takes in a poll's Tally,
* that is the amount of grades received by each Proposal,
* and outputs the poll's Result,
* that is the final rank of each Proposal.
*
* This is the main API of this library.
*
* See MajorityJudgmentDeliberator for an implementation.
*/
public interface DeliberatorInterface {
public ResultInterface deliberate(TallyInterface tally);
}

119
src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java

@ -0,0 +1,119 @@
package fr.mieuxvoter.mj;
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
* https://fr.wikipedia.org/wiki/Jugement_majoritaire
*
* Should this class be "final" ?
*/
public class MajorityJudgmentDeliberator implements DeliberatorInterface {
public ResultInterface deliberate(TallyInterface tally) {
ProposalTallyInterface[] tallies = tally.getProposalsTallies();
Long 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);
ProposalResult proposalResult = new ProposalResult();
proposalResult.setScore(score);
//proposalResult.setRank(???); // rank is computed below, AFTER the score pass
proposalResults[proposalIndex] = proposalResult;
}
// II. Sort Proposals by score
ProposalResult[] proposalResultsSorted = proposalResults.clone();
assert(proposalResultsSorted[0].hashCode() == proposalResults[0].hashCode()); // we need a shallow clone
Arrays.sort(proposalResultsSorted, new Comparator<ProposalResultInterface>() {
@Override
public int compare(ProposalResultInterface p0, ProposalResultInterface p1) {
return p1.getScore().compareTo(p0.getScore());
}
});
// III. Attribute a rank to each Proposal
Integer rank = 1;
for (int proposalIndex = 0; proposalIndex < amountOfProposals; proposalIndex++) {
ProposalResult proposalResult = proposalResultsSorted[proposalIndex];
Integer actualRank = rank;
if (proposalIndex > 0) {
ProposalResult proposalResultBefore = proposalResultsSorted[proposalIndex-1];
if (proposalResult.getScore().contentEquals(proposalResultBefore.getScore())) {
actualRank = proposalResultBefore.getRank();
}
}
proposalResult.setRank(actualRank);
rank += 1;
}
result.setProposalResults(proposalResults);
return result;
}
public String computeScore(ProposalTallyInterface tally, Long amountOfJudges) {
return computeScore(tally, amountOfJudges, true, false);
}
public String computeScore(
ProposalTallyInterface tally,
Long amountOfJudges,
Boolean favorContestation,
Boolean onlyNumbers
) {
ProposalTallyAnalysis analysis = new ProposalTallyAnalysis();
int amountOfGrades = tally.getTally().length;
int digitsForGrade = ("" + amountOfGrades).length();
int digitsForGroup = ("" + amountOfJudges).length();
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",
amountOfJudges + analysis.getSecondMedianGroupSize() * analysis.getSecondMedianGroupSign()
);
currentTally.moveJudgments(analysis.getMedianGrade(), analysis.getSecondMedianGrade());
}
return score;
}
}

26
src/main/java/fr/mieuxvoter/mj/ProposalResult.java

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

21
src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java

@ -0,0 +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.
*/
public Integer getRank();
/**
* This score was used to compute the rank.
* It is made of integer characters, with zeroes for padding.
* Reverse lexicographical order: "higher" is "better".
* You're probably never going to need this, but it's here anyway.
*/
public String getScore();
}

45
src/main/java/fr/mieuxvoter/mj/ProposalTally.java

@ -0,0 +1,45 @@
package fr.mieuxvoter.mj;
import java.util.Arrays;
public class ProposalTally implements ProposalTallyInterface {
protected Long[] tally;
// Should we allow this as well?
//public ProposalTally() {}
public ProposalTally(Integer[] tally) {
int tallyLength = tally.length;
Long[] doublesTally = new Long[tallyLength];
for (int i = 0 ; i < tallyLength ; i++) {
doublesTally[i] = Long.valueOf(tally[i]);
}
setTally(doublesTally);
}
public ProposalTally(Long[] tally) {
setTally(tally);
}
public void setTally(Long[] tally) {
this.tally = tally;
}
@Override
public Long[] 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[fromGrade];
this.tally[fromGrade] = 0L;
}
}

204
src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java

@ -0,0 +1,204 @@
package fr.mieuxvoter.mj;
/**
* Collect useful data on a proposal tally.
* Does NOT compute the rank, but provides all we need
*/
public class ProposalTallyAnalysis {
protected ProposalTallyInterface tally;
protected Long totalSize = 0L; // amount of judges
protected Integer medianGrade = 0;
protected Long medianGroupSize = 0L; // amount of judges in the median group
protected Integer contestationGrade = 0; // "best" grade of the contestation group
protected Long contestationGroupSize = 0L; // of lower grades than median
protected Integer adhesionGrade = 0; // "worst" grade of the adhesion group
protected Long adhesionGroupSize = 0L; // of higher grades than median
protected Integer secondMedianGrade = 0; // grade of the biggest group out of the median
protected Long secondMedianGroupSize = 0L; // 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 void reanalyze(ProposalTallyInterface tally) {
reanalyze(tally, true);
}
public void reanalyze(ProposalTallyInterface tally, Boolean favorContestation) {
this.tally = tally;
this.totalSize = 0L;
this.medianGrade = 0;
this.medianGroupSize = 0L;
this.contestationGrade = 0;
this.contestationGroupSize = 0L;
this.adhesionGrade = 0;
this.adhesionGroupSize = 0L;
this.secondMedianGrade = 0;
this.secondMedianGroupSize = 0L;
this.secondMedianGroupSign = 0;
Long[] gradesTallies = this.tally.getTally();
int amountOfGrades = gradesTallies.length;
for (int grade = 0; grade < amountOfGrades; grade++) {
Long gradeTally = gradesTallies[grade];
assert(0 <= gradeTally); // Negative tallies are not allowed.
this.totalSize += gradeTally;
}
Integer medianOffset = 1;
if ( ! favorContestation) {
medianOffset = 2;
}
Long medianCursor = (long) Math.floor((this.totalSize + medianOffset) / 2.0);
Long tallyBeforeCursor = 0L;
Long tallyCursor = 0L;
Boolean foundMedian = false;
Integer contestationGrade = 0;
Integer adhesionGrade = 0;
for (int grade = 0; grade < amountOfGrades; grade++) {
Long gradeTally = gradesTallies[grade];
tallyBeforeCursor = tallyCursor;
tallyCursor += gradeTally;
if ( ! foundMedian) {
if (tallyCursor >= medianCursor) {
foundMedian = true;
this.medianGrade = grade;
this.contestationGroupSize = tallyBeforeCursor;
this.medianGroupSize = gradeTally;
this.adhesionGroupSize = this.totalSize - this.contestationGroupSize - this.medianGroupSize;
} else {
if (0 < gradeTally) {
contestationGrade = grade;
}
}
} else {
if (0 < gradeTally && 0 == adhesionGrade) {
adhesionGrade = grade;
}
}
}
this.contestationGrade = contestationGrade;
this.adhesionGrade = adhesionGrade;
this.secondMedianGroupSize = Math.max(this.contestationGroupSize, this.adhesionGroupSize);
this.secondMedianGroupSign = 0;
if (this.contestationGroupSize < this.adhesionGroupSize) {
this.secondMedianGrade = this.adhesionGrade;
this.secondMedianGroupSign = 1;
} else if (this.contestationGroupSize > 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) {
this.secondMedianGroupSign = 0;
}
}
public Long getTotalSize() {
return totalSize;
}
public void setTotalSize(Long totalSize) {
this.totalSize = totalSize;
}
public Integer getMedianGrade() {
return medianGrade;
}
public void setMedianGrade(Integer medianGrade) {
this.medianGrade = medianGrade;
}
public Long getMedianGroupSize() {
return medianGroupSize;
}
public void setMedianGroupSize(Long medianGroupSize) {
this.medianGroupSize = medianGroupSize;
}
public Integer getContestationGrade() {
return contestationGrade;
}
public void setContestationGrade(Integer contestationGrade) {
this.contestationGrade = contestationGrade;
}
public Long getContestationGroupSize() {
return contestationGroupSize;
}
public void setContestationGroupSize(Long contestationGroupSize) {
this.contestationGroupSize = contestationGroupSize;
}
public Integer getAdhesionGrade() {
return adhesionGrade;
}
public void setAdhesionGrade(Integer adhesionGrade) {
this.adhesionGrade = adhesionGrade;
}
public Long getAdhesionGroupSize() {
return adhesionGroupSize;
}
public void setAdhesionGroupSize(Long adhesionGroupSize) {
this.adhesionGroupSize = adhesionGroupSize;
}
public Integer getSecondMedianGrade() {
return secondMedianGrade;
}
public void setSecondMedianGrade(Integer secondMedianGrade) {
this.secondMedianGrade = secondMedianGrade;
}
public Long getSecondMedianGroupSize() {
return secondMedianGroupSize;
}
public void setSecondMedianGroupSize(Long secondMedianGroupSize) {
this.secondMedianGroupSize = secondMedianGroupSize;
}
public Integer getSecondMedianGroupSign() {
return secondMedianGroupSign;
}
public void setSecondMedianGroupSign(Integer sign) {
this.secondMedianGroupSign = sign;
}
}

22
src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java

@ -0,0 +1,22 @@
package fr.mieuxvoter.mj;
public interface ProposalTallyInterface {
/**
* The amount of judgments received for each Grade, from "worst" Grade to "best" Grade.
*/
public Long[] getTally();
/**
* 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);
}

15
src/main/java/fr/mieuxvoter/mj/Result.java

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

12
src/main/java/fr/mieuxvoter/mj/ResultInterface.java

@ -0,0 +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();
}

39
src/main/java/fr/mieuxvoter/mj/Tally.java

@ -0,0 +1,39 @@
package fr.mieuxvoter.mj;
public class Tally implements TallyInterface {
protected ProposalTallyInterface[] proposalsTallies;
protected Long amountOfJudges = 0L;
public Tally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) {
setProposalsTallies(proposalsTallies);
setAmountOfJudges(amountOfJudges);
}
public Tally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) {
setProposalsTallies(proposalsTallies);
setAmountOfJudges(Long.valueOf(amountOfJudges));
}
public ProposalTallyInterface[] getProposalsTallies() {
return proposalsTallies;
}
public void setProposalsTallies(ProposalTallyInterface[] proposalsTallies) {
this.proposalsTallies = proposalsTallies;
}
public Integer getAmountOfProposals() {
return proposalsTallies.length;
}
public Long getAmountOfJudges() {
return amountOfJudges;
}
public void setAmountOfJudges(Long amountOfJudges) {
this.amountOfJudges = amountOfJudges;
}
}

11
src/main/java/fr/mieuxvoter/mj/TallyInterface.java

@ -0,0 +1,11 @@
package fr.mieuxvoter.mj;
public interface TallyInterface {
public ProposalTallyInterface[] getProposalsTallies();
public Long getAmountOfJudges();
public Integer getAmountOfProposals();
}

47
src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java

@ -0,0 +1,47 @@
package fr.mieuxvoter.mj;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class MajorityJudgmentDeliberatorTest {
@Test
void testDemoUsage() {
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}),
}, 18L);
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
void testUsageWithBigNumbers() {
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}),
}, 75326214275L);
ResultInterface result = mj.deliberate(tally);
// System.out.println("Score 0: "+result.getProposalResults()[0].getScore());
// System.out.println("Score 1: "+result.getProposalResults()[1].getScore());
// System.out.println("Total "+(11312415004L+21153652410L+24101523299L+18758623562L));
assertNotNull(result);
assertEquals(2, result.getProposalResults().length);
assertEquals(2, result.getProposalResults()[0].getRank());
assertEquals(1, result.getProposalResults()[1].getRank());
}
}

167
src/test/java/fr/mieuxvoter/mj/ProposalTallyAnalysisTest.java

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