From 86d893f53f4224bd43876576b5e7fe3a0ec305fe Mon Sep 17 00:00:00 2001 From: domi41 Date: Sat, 15 May 2021 22:14:14 +0200 Subject: [PATCH 1/4] feat: median default grade tally --- README.md | 8 ++-- .../fr/mieuxvoter/mj/MedianDefaultTally.java | 43 +++++++++++++++++++ .../mj/MajorityJudgmentDeliberatorTest.java | 26 ++++++++++- 3 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java diff --git a/README.md b/README.md index 681237e..0f0c9d1 100644 --- a/README.md +++ b/README.md @@ -140,17 +140,17 @@ ResultInterface result = mj.deliberate(tally); - [x] Ranking - [x] Release v0.1.0 - [x] Guess the amount of judges -- [ ] Allow defining a default grade +- [x] Allow defining a default grade - [x] Static Grade (configurable) - - [x] Normalization (using least common multiple) - - [ ] Median Grade + - [x] Normalization (using Least Common Multiple) + - [x] Median Grade - [ ] Release v0.2.0 - [ ] Publish on package repositories - [ ] Gradle - [ ] 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) +- [ ] Use it somewhere in an application, adjust API as needed (one last time) - [ ] Release v1.0.0 diff --git a/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java new file mode 100644 index 0000000..4a665f1 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java @@ -0,0 +1,43 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; + +/** + * Fill the missing judgments into the median grade. + * 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 Tally implements TallyInterface { + + 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(); + } + + protected void fillWithDefaultGrade() { + int amountOfProposals = getAmountOfProposals(); + for (int i = 0 ; i < amountOfProposals ; i++) { + ProposalTallyInterface proposal = getProposalsTallies()[i]; + ProposalTallyAnalysis analysis = new ProposalTallyAnalysis(proposal); + Integer defaultGrade = analysis.getMedianGrade(); + BigInteger amountOfJudgments = proposal.getAmountOfJudgments(); + BigInteger missingAmount = this.amountOfJudges.subtract(amountOfJudgments); + int missingSign = missingAmount.compareTo(BigInteger.ZERO); + assert(0 <= missingSign); // ERROR: More judgments than judges! + if (0 < missingSign) { + proposal.getTally()[defaultGrade] = proposal.getTally()[defaultGrade].add(missingAmount); + } + } + } + +} diff --git a/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java b/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java index 68fadb8..3e1f81c 100644 --- a/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java +++ b/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java @@ -213,7 +213,7 @@ class MajorityJudgmentDeliberatorTest { } @Test - @DisplayName("Test static default grade with thousands of proposals") + @DisplayName("Test static default grade with thousands of proposals and millions of judges") public void testStaticDefaultWithThousandsOfProposals() { int amountOfProposals = 1337; Integer amountOfJudges = 60000000; @@ -257,6 +257,30 @@ class MajorityJudgmentDeliberatorTest { // } // } + @Test + @DisplayName("Test with a median default grade") + public void testMedianDefaultGrade() { + 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() { From f4420fe15eff8c88bc80d7de85b28e2bbada0f9e Mon Sep 17 00:00:00 2001 From: domi41 Date: Sun, 16 May 2021 05:34:03 +0200 Subject: [PATCH 2/4] refacto: dry things up in the default grade tallies --- .../fr/mieuxvoter/mj/DefaultGradeTally.java | 44 +++++++++++++++++++ .../fr/mieuxvoter/mj/MedianDefaultTally.java | 25 +++++------ 2 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java diff --git a/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java b/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java new file mode 100644 index 0000000..882d7d3 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java @@ -0,0 +1,44 @@ +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. + */ +abstract public class DefaultGradeTally extends Tally implements TallyInterface { + + 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 proposal = getProposalsTallies()[i]; + Integer defaultGrade = getDefaultGrade(proposal); + BigInteger amountOfJudgments = proposal.getAmountOfJudgments(); + BigInteger missingAmount = this.amountOfJudges.subtract(amountOfJudgments); + int missingSign = missingAmount.compareTo(BigInteger.ZERO); + assert(0 <= missingSign); // ERROR: More judgments than judges! + if (0 < missingSign) { + proposal.getTally()[defaultGrade] = proposal.getTally()[defaultGrade].add(missingAmount); + } + } + } + + abstract protected Integer getDefaultGrade(ProposalTallyInterface proposalTally); + +} diff --git a/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java index 4a665f1..8ec118c 100644 --- a/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java +++ b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java @@ -7,7 +7,12 @@ import java.math.BigInteger; * 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 Tally implements TallyInterface { +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); @@ -24,20 +29,10 @@ public class MedianDefaultTally extends Tally implements TallyInterface { fillWithDefaultGrade(); } - protected void fillWithDefaultGrade() { - int amountOfProposals = getAmountOfProposals(); - for (int i = 0 ; i < amountOfProposals ; i++) { - ProposalTallyInterface proposal = getProposalsTallies()[i]; - ProposalTallyAnalysis analysis = new ProposalTallyAnalysis(proposal); - Integer defaultGrade = analysis.getMedianGrade(); - BigInteger amountOfJudgments = proposal.getAmountOfJudgments(); - BigInteger missingAmount = this.amountOfJudges.subtract(amountOfJudgments); - int missingSign = missingAmount.compareTo(BigInteger.ZERO); - assert(0 <= missingSign); // ERROR: More judgments than judges! - if (0 < missingSign) { - proposal.getTally()[defaultGrade] = proposal.getTally()[defaultGrade].add(missingAmount); - } - } + @Override + protected Integer getDefaultGrade(ProposalTallyInterface proposalTally) { + ProposalTallyAnalysis analysis = new ProposalTallyAnalysis(proposalTally); + return analysis.getMedianGrade(); } } From 093ae22354e908f10982b33344b340ada033fd3c Mon Sep 17 00:00:00 2001 From: domi41 Date: Sun, 16 May 2021 06:14:13 +0200 Subject: [PATCH 3/4] refacto: dry up the static and median default grade tallies --- .../fr/mieuxvoter/mj/DefaultGradeTally.java | 2 +- .../fr/mieuxvoter/mj/MedianDefaultTally.java | 2 +- .../mieuxvoter/mj/TallyWithDefaultGrade.java | 39 ++++++++++++------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java b/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java index 882d7d3..4c88ef8 100644 --- a/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java +++ b/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java @@ -7,7 +7,7 @@ import java.math.BigInteger; * This is an abstract class to dry code between static default grade and median default grade. */ abstract public class DefaultGradeTally extends Tally implements TallyInterface { - + public DefaultGradeTally(TallyInterface tally) { super(tally.getProposalsTallies(), tally.getAmountOfJudges()); } diff --git a/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java index 8ec118c..9d9c51e 100644 --- a/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java +++ b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java @@ -3,7 +3,7 @@ package fr.mieuxvoter.mj; import java.math.BigInteger; /** - * Fill the missing judgments into the median grade. + * 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. */ diff --git a/src/main/java/fr/mieuxvoter/mj/TallyWithDefaultGrade.java b/src/main/java/fr/mieuxvoter/mj/TallyWithDefaultGrade.java index f6e458e..4160efa 100644 --- a/src/main/java/fr/mieuxvoter/mj/TallyWithDefaultGrade.java +++ b/src/main/java/fr/mieuxvoter/mj/TallyWithDefaultGrade.java @@ -2,9 +2,27 @@ package fr.mieuxvoter.mj; import java.math.BigInteger; -public class TallyWithDefaultGrade extends Tally implements TallyInterface { +public class TallyWithDefaultGrade 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 TallyWithDefaultGrade(TallyInterface tally, Integer defaultGrade) { + super(tally.getProposalsTallies(), tally.getAmountOfJudges()); + this.defaultGrade = defaultGrade; + fillWithDefaultGrade(); + } public TallyWithDefaultGrade(ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges, Integer defaultGrade) { super(proposalsTallies, amountOfJudges); @@ -17,25 +35,16 @@ public class TallyWithDefaultGrade extends Tally implements TallyInterface { this.defaultGrade = defaultGrade; fillWithDefaultGrade(); } - + public TallyWithDefaultGrade(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges, Integer defaultGrade) { super(proposalsTallies, amountOfJudges); this.defaultGrade = defaultGrade; fillWithDefaultGrade(); } - - protected void fillWithDefaultGrade() { - int amountOfProposals = getAmountOfProposals(); - for (int i = 0 ; i < amountOfProposals ; i++) { - ProposalTallyInterface proposal = getProposalsTallies()[i]; - BigInteger amountOfJudgments = proposal.getAmountOfJudgments(); - BigInteger missingAmount = this.amountOfJudges.subtract(amountOfJudgments); - int missingSign = missingAmount.compareTo(BigInteger.ZERO); - assert(0 <= missingSign); // ERROR: More judgments than judges! - if (0 < missingSign) { - proposal.getTally()[this.defaultGrade] = proposal.getTally()[this.defaultGrade].add(missingAmount); - } - } + + @Override + protected Integer getDefaultGrade(ProposalTallyInterface proposalTally) { + return this.defaultGrade; } } From 5e165841df13970fb0b30577d0600f90392fdda6 Mon Sep 17 00:00:00 2001 From: domi41 Date: Sun, 16 May 2021 06:48:39 +0200 Subject: [PATCH 4/4] test: enable the median default grade in the JSON assertions --- .../fr/mieuxvoter/mj/DefaultGradeTally.java | 22 +++++---- .../fr/mieuxvoter/mj/MedianDefaultTally.java | 2 +- .../mieuxvoter/mj/TallyWithDefaultGrade.java | 2 +- .../mj/MajorityJudgmentDeliberatorTest.java | 16 ++++--- src/test/resources/assertions.json | 45 +++++++++++++------ 5 files changed, 58 insertions(+), 29 deletions(-) diff --git a/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java b/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java index 4c88ef8..bf9162d 100644 --- a/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java +++ b/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java @@ -8,6 +8,13 @@ import java.math.BigInteger; */ 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()); } @@ -15,11 +22,11 @@ abstract public class DefaultGradeTally extends Tally implements TallyInterface 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); } @@ -27,18 +34,17 @@ abstract public class DefaultGradeTally extends Tally implements TallyInterface protected void fillWithDefaultGrade() { int amountOfProposals = getAmountOfProposals(); for (int i = 0 ; i < amountOfProposals ; i++) { - ProposalTallyInterface proposal = getProposalsTallies()[i]; - Integer defaultGrade = getDefaultGrade(proposal); - BigInteger amountOfJudgments = proposal.getAmountOfJudgments(); + 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) { - proposal.getTally()[defaultGrade] = proposal.getTally()[defaultGrade].add(missingAmount); + BigInteger[] rawTally = proposalTally.getTally(); + rawTally[defaultGrade] = rawTally[defaultGrade].add(missingAmount); } } } - abstract protected Integer getDefaultGrade(ProposalTallyInterface proposalTally); - } diff --git a/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java index 9d9c51e..5379590 100644 --- a/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java +++ b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java @@ -30,7 +30,7 @@ public class MedianDefaultTally extends DefaultGradeTally implements TallyInterf } @Override - protected Integer getDefaultGrade(ProposalTallyInterface proposalTally) { + protected Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally) { ProposalTallyAnalysis analysis = new ProposalTallyAnalysis(proposalTally); return analysis.getMedianGrade(); } diff --git a/src/main/java/fr/mieuxvoter/mj/TallyWithDefaultGrade.java b/src/main/java/fr/mieuxvoter/mj/TallyWithDefaultGrade.java index 4160efa..8cdd93a 100644 --- a/src/main/java/fr/mieuxvoter/mj/TallyWithDefaultGrade.java +++ b/src/main/java/fr/mieuxvoter/mj/TallyWithDefaultGrade.java @@ -43,7 +43,7 @@ public class TallyWithDefaultGrade extends DefaultGradeTally implements TallyInt } @Override - protected Integer getDefaultGrade(ProposalTallyInterface proposalTally) { + protected Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally) { return this.defaultGrade; } diff --git a/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java b/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java index 3e1f81c..fb909c6 100644 --- a/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java +++ b/src/test/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberatorTest.java @@ -20,6 +20,9 @@ class MajorityJudgmentDeliberatorTest { @ParameterizedTest(name="#{index} {0}") @JsonFileSource(resources = "/assertions.json") public void testFromJson(JsonObject datum) { + // 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()); @@ -39,6 +42,8 @@ class MajorityJudgmentDeliberatorTest { TallyInterface tally; if ("StaticDefault".equalsIgnoreCase(mode)) { tally = new TallyWithDefaultGrade(tallies, amountOfParticipants, datum.getInt("default")); + } else if ("MedianDefault".equalsIgnoreCase(mode)) { + tally = new MedianDefaultTally(tallies, amountOfParticipants); } else if ("Normalized".equalsIgnoreCase(mode)) { tally = new NormalizedTally(tallies); } else { @@ -287,9 +292,6 @@ class MajorityJudgmentDeliberatorTest { // We're using primes to test the upper bounds of our LCM shenanigans. // This test takes a long time! (3 seconds) -// List generatedPrimes = sieveOfEratosthenes(15000); -// System.out.println(generatedPrimes); - int amountOfProposals = primes.length; // 1437 DeliberatorInterface mj = new MajorityJudgmentDeliberator(); ProposalTallyInterface[] tallies = new ProposalTallyInterface[amountOfProposals]; @@ -306,7 +308,7 @@ class MajorityJudgmentDeliberatorTest { assertEquals(amountOfProposals, result.getProposalResults().length); for (int i = 0 ; i < amountOfProposals ; i++) { assertEquals( - 1 + (i % primes.length), result.getProposalResults()[i].getRank(), + 1 + i, result.getProposalResults()[i].getRank(), "Rank of Proposal #" + i ); } @@ -316,7 +318,7 @@ class MajorityJudgmentDeliberatorTest { @DisplayName("Test normalized tallies with thousands of proposals") public void testNormalizedWithThousandsOfProposals() { // This test is faster than the primes one (0.4 seconds), - // since primes are the worst case-scenario for our LCM. + // since primes are the worst-case scenario for our LCM. int amountOfProposals = primes.length; // 1437 DeliberatorInterface mj = new MajorityJudgmentDeliberator(); @@ -333,12 +335,14 @@ class MajorityJudgmentDeliberatorTest { assertEquals(amountOfProposals, result.getProposalResults().length); for (int i = 0 ; i < amountOfProposals ; i++) { assertEquals( - 1 + (i % primes.length), result.getProposalResults()[i].getRank(), + 1 + i, result.getProposalResults()[i].getRank(), "Rank of Proposal #" + i ); } } + // … + // @Test // public void runBenchmarks() throws Exception { // Options options = new OptionsBuilder() diff --git a/src/test/resources/assertions.json b/src/test/resources/assertions.json index c3fadc5..d194a8a 100644 --- a/src/test/resources/assertions.json +++ b/src/test/resources/assertions.json @@ -4,11 +4,11 @@ "title": "Few participants", "participants": 3, "tallies": [ - [1, 1, 1], - [1, 0, 2], - [3, 0, 0], - [2, 0, 1], - [0, 3, 0] + [ 1, 1, 1 ], + [ 1, 0, 2 ], + [ 3, 0, 0 ], + [ 2, 0, 1 ], + [ 0, 3, 0 ] ], "ranks": [ 3, @@ -22,9 +22,9 @@ "title": "Thousands of participants", "participants": 37000, "tallies": [ - [11142, 6970, 4040, 1968, 9888, 2992], - [10141, 8971, 4043, 1965, 8884, 2996], - [14141, 8971, 1043, 1965, 7884, 2996] + [ 11142, 6970, 4040, 1968, 9888, 2992 ], + [ 10141, 8971, 4043, 1965, 8884, 2996 ], + [ 14141, 8971, 1043, 1965, 7884, 2996 ] ], "ranks": [ 1, @@ -36,11 +36,11 @@ "title": "Millions of participants", "participants": 72327456, "tallies": [ - [5272679, 19797001, 10732688, 9612936, 1379840, 16886281, 8646031], - [16354546, 11690342, 9451800, 14245973, 817593, 12461162, 7306040], - [9849171, 17970690, 14276861, 4606692, 16404594, 6760147, 2459301], - [2645563, 12907474, 1278331, 22843261, 8025412, 8964952, 15662463], - [16293252, 12277630, 38348, 14929905, 11087753, 10634266, 7066302] + [ 5272679, 19797001, 10732688, 9612936, 1379840, 16886281, 8646031 ], + [ 16354546, 11690342, 9451800, 14245973, 817593, 12461162, 7306040 ], + [ 9849171, 17970690, 14276861, 4606692, 16404594, 6760147, 2459301 ], + [ 2645563, 12907474, 1278331, 22843261, 8025412, 8964952, 15662463 ], + [ 16293252, 12277630, 38348, 14929905, 11087753, 10634266, 7066302 ] ], "ranks": [ 3, @@ -90,6 +90,25 @@ 2 ] }, + { + "title": "Median Default Grade", + "participants": 10, + "mode": "MedianDefault", + "tallies": [ + [ 2, 2, 2, 2, 2 ], + [ 2, 2, 1, 2, 2 ], + [ 0, 0, 4, 0, 0 ], + [ 0, 0, 0, 0, 2 ], + [ 0, 0, 0, 1, 1 ] + ], + "ranks": [ + 4, + 4, + 3, + 1, + 2 + ] + }, { "title": "Normalization", "participants": 10,