Author: tchemit Date: 2012-09-29 23:18:12 +0200 (Sat, 29 Sep 2012) New Revision: 3710 Url: http://chorem.org/repositories/revision/pollen/3710 Log: refs #590: Refactor votecounting module (improve code) Removed: trunk/pollen-votecounting-api/src/test/java/org/chorem/pollen/votecounting/model/ChoiceScoreTest.java Modified: trunk/pollen-ui-struts2/src/main/java/org/chorem/pollen/ui/actions/poll/ResultForPoll.java trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/AbstractVoteCounting.java trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/AbstractVoteCountingStrategy.java trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/ChoiceScore.java trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/GroupOfVoter.java trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/VoteCountingResult.java trunk/pollen-votecounting-borda/src/main/java/org/chorem/pollen/votecounting/BordaVoteCountingStrategy.java trunk/pollen-votecounting-condorcet/src/main/java/org/chorem/pollen/votecounting/CondorcetVoteCountingStrategy.java trunk/pollen-votecounting-coombs/src/main/java/org/chorem/pollen/votecounting/CoombsVoteCountingStrategy.java trunk/pollen-votecounting-instant-runoff/src/main/java/org/chorem/pollen/votecounting/InstantRunoffVoteCountingStrategy.java trunk/pollen-votecounting-normal/src/main/java/org/chorem/pollen/votecounting/NormalVoteCountingStrategy.java trunk/pollen-votecounting-normal/src/test/java/org/chorem/pollen/votecounting/NormalVoteCountingStrategyTest.java trunk/pollen-votecounting-number/src/main/java/org/chorem/pollen/votecounting/NumberVoteCountingStrategy.java trunk/pollen-votecounting-percentage/src/main/java/org/chorem/pollen/votecounting/PercentageVoteCountingStrategy.java Modified: trunk/pollen-ui-struts2/src/main/java/org/chorem/pollen/ui/actions/poll/ResultForPoll.java =================================================================== --- trunk/pollen-ui-struts2/src/main/java/org/chorem/pollen/ui/actions/poll/ResultForPoll.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-ui-struts2/src/main/java/org/chorem/pollen/ui/actions/poll/ResultForPoll.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -44,13 +44,14 @@ import org.chorem.pollen.ui.actions.PollenActionSupport; import org.chorem.pollen.ui.actions.PollenUserSecurityAware; import org.chorem.pollen.ui.converters.DateConverter; +import org.chorem.pollen.votecounting.VoteCounting; import org.chorem.pollen.votecounting.model.ChoiceScore; import org.chorem.pollen.votecounting.model.GroupVoteCountingResult; import org.chorem.pollen.votecounting.model.VoteCountingResult; -import org.chorem.pollen.votecounting.VoteCounting; import org.nuiton.topia.persistence.TopiaId; import java.net.URL; +import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; @@ -221,7 +222,7 @@ public String getVictoryMessage() { - Set<ChoiceScore> topRanking = pollResult.getTopRanking(); + Collection<ChoiceScore> topRanking = pollResult.getTopRanking(); String victoryMessage; if (CollectionUtils.isEmpty(topRanking)) { Modified: trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/AbstractVoteCounting.java =================================================================== --- trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/AbstractVoteCounting.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/AbstractVoteCounting.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -12,19 +12,23 @@ */ public abstract class AbstractVoteCounting<S extends VoteCountingStrategy> implements VoteCounting<S> { - protected final int strategyId; + /** Unique id of the vote counting type. */ + protected final int id; + /** Type of strategy used to vote count. */ protected final Class<S> strategyType; + /** I18n key for name of this vote counting type. */ protected final String i18nName; + /** I18n Key for help of this vote counting type. */ protected final String i18nHelp; - public AbstractVoteCounting(int strategyId, + protected AbstractVoteCounting(int id, Class<S> strategyType, String i18nName, String i18nHelp) { - this.strategyId = strategyId; + this.id = id; this.strategyType = strategyType; this.i18nName = i18nName; this.i18nHelp = i18nHelp; @@ -55,7 +59,7 @@ @Override public final int getId() { - return strategyId; + return id; } @Override Modified: trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/AbstractVoteCountingStrategy.java =================================================================== --- trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/AbstractVoteCountingStrategy.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/AbstractVoteCountingStrategy.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -25,6 +25,8 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; import org.chorem.pollen.votecounting.model.ChoiceIdAble; import org.chorem.pollen.votecounting.model.ChoiceScore; @@ -34,14 +36,13 @@ import org.chorem.pollen.votecounting.model.VoteForChoice; import org.chorem.pollen.votecounting.model.Voter; -import java.io.Serializable; import java.math.BigDecimal; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.SortedMap; /** * Base abstract implementation of a {@link VoteCountingStrategy}. @@ -53,6 +54,22 @@ public static final BigDecimal ZERO_D = BigDecimal.valueOf(0.); + protected final Comparator<VoteForChoice> voteValueComparator = new Comparator<VoteForChoice>() { + + @Override + public int compare(VoteForChoice o1, VoteForChoice o2) { + Double v1 = o1.getVoteValue(); + Double v2 = o2.getVoteValue(); + if (v1 == null) { + v1 = Double.MAX_VALUE; + } + if (v2 == null) { + v2 = Double.MAX_VALUE; + } + return v1.intValue() - v2.intValue(); + } + }; + @Override public final GroupVoteCountingResult votecount(GroupOfVoter group) { @@ -64,11 +81,11 @@ return result; } - public SortedMap<String, ChoiceScore> votersToResult(Set<Voter> voters) { + public Map<String, ChoiceScore> newEmptyChoiceScoreMap(Set<Voter> voters) { // get all choice Id Set<String> choiceIds = getAllChoiceIds(voters); - SortedMap<String, ChoiceScore> resultByChoice = Maps.newTreeMap(); + Map<String, ChoiceScore> resultByChoice = Maps.newHashMap(); // creates all empty result for choice for (String choiceId : choiceIds) { @@ -78,16 +95,37 @@ return resultByChoice; } - public VoteCountingResult resultToList(Map<String, ChoiceScore> resultByChoice) { - List<ChoiceScore> score = toChoiceScore(resultByChoice); - return VoteCountingResult.newResult(score); - } + protected VoteCountingResult orderByValues(Collection<ChoiceScore> scores) { - public List<ChoiceScore> toChoiceScore(Map<String, ChoiceScore> resultByChoice) { - List<ChoiceScore> score = Lists.newArrayList(resultByChoice.values()); - Collections.sort(score); - Collections.reverse(score); - return score; + // get scores by score value + Multimap<BigDecimal, ChoiceScore> map = Multimaps.index( + scores, ChoiceScore.SCORE_BY_VALUE + ); + + // get all distinct score values + List<BigDecimal> values = Lists.newArrayList(map.asMap().keySet()); + Collections.sort(values); + Collections.reverse(values); + + // compute rank for each scores + int rank = 0; + for (BigDecimal value : values) { + + Collection<ChoiceScore> scoresForValue = map.get(value); + for (ChoiceScore score : scoresForValue) { + score.setScoreOrder(rank); + } + rank++; + } + // get all scores + List<ChoiceScore> orderedScores = Lists.newArrayList(map.values()); + + // sort them by their rank + Collections.sort(orderedScores); + + // transform map of result to list of them (and sort them) + VoteCountingResult result = VoteCountingResult.newResult(orderedScores); + return result; } public Set<String> getAllChoiceIds(Set<Voter> voters) { @@ -104,24 +142,21 @@ public Map<Voter, List<Set<String>>> buildVoterSortedChoices(Set<Voter> voters) { - VoteForChoiceComparator comparator = new VoteForChoiceComparator(); - Map<Voter, List<Set<String>>> voterSortedChoices = Maps.newHashMap(); for (Voter voter : voters) { List<Set<String>> sortedChoices = sortVoteForChoices( - voter.getVoteForChoices(), comparator); + voter.getVoteForChoices()); voterSortedChoices.put(voter, sortedChoices); } return voterSortedChoices; } - public List<Set<String>> sortVoteForChoices(Set<VoteForChoice> voteForChoices, - Comparator<VoteForChoice> comparator) { + public List<Set<String>> sortVoteForChoices(Set<VoteForChoice> voteForChoices) { // get sort vote for choices List<VoteForChoice> sortedChoices = Lists.newArrayList(voteForChoices); - Collections.sort(sortedChoices, comparator); + Collections.sort(sortedChoices, voteValueComparator); // build ranks List<Set<String>> result = Lists.newArrayList(); @@ -131,7 +166,7 @@ VoteForChoice lastVoteForChoice = null; for (VoteForChoice voteForChoice : sortedChoices) { if (lastVoteForChoice != null && - comparator.compare(lastVoteForChoice, voteForChoice) != 0) { + voteValueComparator.compare(lastVoteForChoice, voteForChoice) != 0) { // new rank found // register it @@ -166,22 +201,4 @@ // store the result for this group group.setResult(voteCountingResult); } - - public static class VoteForChoiceComparator implements Comparator<VoteForChoice>, Serializable { - - private static final long serialVersionUID = 1L; - - @Override - public int compare(VoteForChoice o1, VoteForChoice o2) { - Double v1 = o1.getVoteValue(); - Double v2 = o2.getVoteValue(); - if (v1 == null) { - v1 = Double.MAX_VALUE; - } - if (v2 == null) { - v2 = Double.MAX_VALUE; - } - return v1.intValue() - v2.intValue(); - } - } } Modified: trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/ChoiceScore.java =================================================================== --- trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/ChoiceScore.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/ChoiceScore.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -22,11 +22,16 @@ */ package org.chorem.pollen.votecounting.model; +import com.google.common.base.Function; + import java.io.Serializable; import java.math.BigDecimal; /** * Score for a given choice. + * <p/> + * Rank is given by the field {@link #scoreOrder} and this class is + * comparable of this data. * * @author tchemit <chemit@codelutin.com> * @since 1.4.5 @@ -35,13 +40,33 @@ private static final long serialVersionUID = 1L; + public static final Function<ChoiceScore, Integer> SCORE_BY_ORDER = new Function<ChoiceScore, Integer>() { + @Override + public Integer apply(ChoiceScore input) { + return input.getScoreOrder(); + } + }; + + protected static final BigDecimal MIN_VALUE = BigDecimal.valueOf(Double.MIN_VALUE); + + public static final Function<ChoiceScore, BigDecimal> SCORE_BY_VALUE = new Function<ChoiceScore, BigDecimal>() { + @Override + public BigDecimal apply(ChoiceScore input) { + BigDecimal value = input.getScoreValue(); + return value == null ? MIN_VALUE : value; + } + }; + /** Id of the choice. */ private String choiceId; - /** Global score for this choice (in some poll does not mean a lot...). */ + /** + * Global score for this choice (in some vote count it does not mean a + * lot (and it should be improved to take account of the notion of round)). + */ private BigDecimal scoreValue; - /** Order of this score (0 is winner, 1, second,...). */ + /** Order of this score (0 is winner, 1 is second,...). */ private int scoreOrder; public static ChoiceScore newScore(String choiceId, BigDecimal scoreValue) { @@ -89,8 +114,6 @@ @Override public int compareTo(ChoiceScore o) { - int i1 = scoreValue == null ? -1 : scoreValue.intValue(); - int i2 = o.scoreValue == null ? -1 : o.scoreValue.intValue(); - return i1 - i2; + return scoreOrder - o.scoreOrder; } } Modified: trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/GroupOfVoter.java =================================================================== --- trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/GroupOfVoter.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/GroupOfVoter.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -24,6 +24,7 @@ import com.google.common.collect.Sets; +import java.util.Collection; import java.util.Iterator; import java.util.Set; @@ -106,7 +107,7 @@ public void setResult(VoteCountingResult result) { this.result = result; - Set<ChoiceScore> winners = result.getTopRanking(); + Collection<ChoiceScore> winners = result.getTopRanking(); for (ChoiceScore choiceScore : result.getScores()) { double score = winners.contains(choiceScore) ? 1d : 0d; Modified: trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/VoteCountingResult.java =================================================================== --- trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/VoteCountingResult.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-api/src/main/java/org/chorem/pollen/votecounting/model/VoteCountingResult.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -23,14 +23,13 @@ package org.chorem.pollen.votecounting.model; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import org.apache.commons.collections.CollectionUtils; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import java.io.Serializable; -import java.math.BigDecimal; +import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Set; /** * Contains results for a vote. @@ -50,8 +49,12 @@ */ private List<ChoiceScore> scores; - /** Get the winners (could be more than one of it). */ - private Set<ChoiceScore> topRanking; + /** + * Result for each choice order by their winning rank. + * + * @see ChoiceScore#getScoreOrder() + */ + private transient Multimap<Integer, ChoiceScore> scoresByRank; public static VoteCountingResult newResult(List<ChoiceScore> scores) { VoteCountingResult result = new VoteCountingResult(); @@ -75,25 +78,17 @@ } public void setScores(List<ChoiceScore> scores) { - this.scores = scores; - List<ChoiceScore> ranking = Lists.newArrayList(getScores()); + this.scores = Lists.newArrayList(scores); + } - Collections.sort(ranking); - Collections.reverse(ranking); + public Collection<ChoiceScore> getTopRanking() { + return getScoresByRank().get(0); + } - topRanking = Sets.newHashSet(); - - if (CollectionUtils.isNotEmpty(ranking)) { - BigDecimal winValue = ranking.get(0).getScoreValue(); - for (ChoiceScore r : ranking) { - if (winValue.equals(r.getScoreValue())) { - topRanking.add(r); - } - } + public Multimap<Integer, ChoiceScore> getScoresByRank() { + if (scoresByRank == null) { + scoresByRank = Multimaps.index(scores, ChoiceScore.SCORE_BY_ORDER); } + return scoresByRank; } - - public Set<ChoiceScore> getTopRanking() { - return topRanking; - } } Deleted: trunk/pollen-votecounting-api/src/test/java/org/chorem/pollen/votecounting/model/ChoiceScoreTest.java =================================================================== --- trunk/pollen-votecounting-api/src/test/java/org/chorem/pollen/votecounting/model/ChoiceScoreTest.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-api/src/test/java/org/chorem/pollen/votecounting/model/ChoiceScoreTest.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -1,81 +0,0 @@ -/* - * #%L - * Pollen :: VoteCounting Api - * $Id$ - * $HeadURL$ - * %% - * Copyright (C) 2009 - 2012 CodeLutin - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * #L% - */ -package org.chorem.pollen.votecounting.model; - -import org.junit.Assert; -import org.junit.Test; - -import java.math.BigDecimal; - -/** - * Tests the {@link ChoiceScore}. - * - * @author tchemit <chemit@codelutin.com> - * @since 1.4.5 - */ -public class ChoiceScoreTest { - - @Test - public void compareTo() throws Exception { - - ChoiceScore c1; - ChoiceScore c2; - - // c1 == c2 - c1 = ChoiceScore.newScore("c1", null); - c2 = ChoiceScore.newScore("c2", null); - Assert.assertTrue(c1.compareTo(c2) == 0); - - // c1 < c2 - c1 = ChoiceScore.newScore("c1", null); - c2 = ChoiceScore.newScore("c2", BigDecimal.ZERO); - Assert.assertTrue(c1.compareTo(c2) < 0); - - // c1 > c2 - c1 = ChoiceScore.newScore("c1", BigDecimal.ZERO); - c2 = ChoiceScore.newScore("c2", null); - Assert.assertTrue(c1.compareTo(c2) > 0); - - // c1 == c2 - c1 = ChoiceScore.newScore("c1", BigDecimal.ZERO); - c2 = ChoiceScore.newScore("c2", BigDecimal.ZERO); - Assert.assertTrue(c1.compareTo(c2) == 0); - - - // c1 > c2 - c1 = ChoiceScore.newScore("c1", BigDecimal.ONE); - c2 = ChoiceScore.newScore("c2", BigDecimal.ZERO); - Assert.assertTrue(c1.compareTo(c2) > 0); - - // c1 == c2 - c1 = ChoiceScore.newScore("c1", BigDecimal.ONE); - c2 = ChoiceScore.newScore("c2", BigDecimal.ONE); - Assert.assertTrue(c1.compareTo(c2) == 0); - - // c1 < c2 - c1 = ChoiceScore.newScore("c1", BigDecimal.ONE); - c2 = ChoiceScore.newScore("c2", BigDecimal.TEN); - Assert.assertTrue(c1.compareTo(c2) < 0); - - } -} Modified: trunk/pollen-votecounting-borda/src/main/java/org/chorem/pollen/votecounting/BordaVoteCountingStrategy.java =================================================================== --- trunk/pollen-votecounting-borda/src/main/java/org/chorem/pollen/votecounting/BordaVoteCountingStrategy.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-borda/src/main/java/org/chorem/pollen/votecounting/BordaVoteCountingStrategy.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -48,10 +48,10 @@ public VoteCountingResult votecount(Set<Voter> voters) { // get empty result by choice - Map<String, ChoiceScore> resultByChoice = votersToResult(voters); + Map<String, ChoiceScore> scores = newEmptyChoiceScoreMap(voters); // nb of choices - int nbChoices = resultByChoice.keySet().size(); + int nbChoices = scores.keySet().size(); if (log.isDebugEnabled()) { log.debug("Nb choices: " + nbChoices); @@ -73,16 +73,14 @@ for (String choiceId : sortedChoiceId) { - resultByChoice.get(choiceId).addScoreValue(choiceWeight); + scores.get(choiceId).addScoreValue(choiceWeight); } choiceWeight -= weight; } } - // transform map of result to list of them (and sort them) - VoteCountingResult result = resultToList(resultByChoice); + // order scores (using their value) and return result + VoteCountingResult result = orderByValues(scores.values()); return result; } - - } Modified: trunk/pollen-votecounting-condorcet/src/main/java/org/chorem/pollen/votecounting/CondorcetVoteCountingStrategy.java =================================================================== --- trunk/pollen-votecounting-condorcet/src/main/java/org/chorem/pollen/votecounting/CondorcetVoteCountingStrategy.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-condorcet/src/main/java/org/chorem/pollen/votecounting/CondorcetVoteCountingStrategy.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -22,20 +22,13 @@ */ package org.chorem.pollen.votecounting; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.chorem.pollen.votecounting.model.ChoiceIdAble; import org.chorem.pollen.votecounting.model.ChoiceScore; import org.chorem.pollen.votecounting.model.VoteCountingResult; import org.chorem.pollen.votecounting.model.VoteForChoice; import org.chorem.pollen.votecounting.model.Voter; -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.List; import java.util.Map; import java.util.Set; @@ -55,193 +48,53 @@ public VoteCountingResult votecount(Set<Voter> voters) { // get empty result by choice - Map<String, ChoiceScore> resultByChoice = votersToResult(voters); + Map<String, ChoiceScore> scores = newEmptyChoiceScoreMap(voters); - // get order over choices (needed to get coordinates over matrix) - List<String> choiceIds = Lists.newArrayList(resultByChoice.keySet()); + for (Voter voter : voters) { - // nb of choices - int nbChoices = choiceIds.size(); - - //matrix of pairwise - double[][] matrix = new double[nbChoices][nbChoices]; - - // compute pairwise battle matrix and try to have a direct winner(s) - Set<String> winners = computePairWiseMatrix(choiceIds, voters, matrix); - - // compute nb battles wins for each choice - // and store it as choice score value - computeNbBattlesByChoice(resultByChoice, choiceIds, matrix); - - // get choice score ordered by their number of win battles - List<ChoiceScore> choiceScores = toChoiceScore(resultByChoice); - - if (winners != null) { - - // there is winner(s), re-add them to - - if (log.isDebugEnabled()) { - log.debug("Direct winners : " + winners); - } - for (String choiceId : winners) { - ChoiceScore choiceScore = resultByChoice.get(choiceId); - choiceScores.remove(choiceScore); - choiceScores.add(0, choiceScore); - } - } else { - - // no direct winner, must resolv conflicts - - resolvConflicts(choiceScores, matrix); + // add this voter votes to result + addVoterChoices(voter, scores); } - // transform map of result to list of them (and sort them) - VoteCountingResult result = resultToList(resultByChoice); + // order scores (using their value) and return result + VoteCountingResult result = orderByValues(scores.values()); return result; } - protected void resolvConflicts(List<ChoiceScore> choiceScores, - double[][] matrix) { + protected void addVoterChoices(Voter voter, + Map<String, ChoiceScore> scores) { - // for the moment we use only number of win battles - // so nothing more to do here... - } - - protected void computeNbBattlesByChoice(Map<String, ChoiceScore> resultByChoice, - List<String> choiceIds, - double[][] matrix) { - - int nbChoices = choiceIds.size(); - - for (String choiceId : choiceIds) { - - int i = choiceIds.indexOf(choiceId); - - double nbBattles = 0; - for (int j = 0; j < nbChoices; j++) { - double aRow = matrix[i][j]; - nbBattles += aRow; - } - - resultByChoice.get(choiceId).setScoreValue( - BigDecimal.valueOf(nbBattles)); - - if (log.isDebugEnabled()) { - log.debug("Nb battle wins for choice " + choiceId + ": " + nbBattles); - } + if (log.isDebugEnabled()) { + log.debug("Start count for voter " + voter.getVoterId()); } - } + double voterWeight = voter.getWeight(); - private Set<String> computePairWiseMatrix(List<String> choiceIds, - Set<Voter> voters, - double[][] matrix) { + for (VoteForChoice voteForChoiceX : voter.getVoteForChoices()) { - int nbChoices = choiceIds.size(); + String choiceIdX = voteForChoiceX.getChoiceId(); - Set<String> winners = null; + double score = 0; - VoteForChoiceComparator comparator = new VoteForChoiceComparator(); + for (VoteForChoice voteForChoiceY : voter.getVoteForChoices()) { - double[] currentVoteWinner = new double[choiceIds.size()]; - - boolean firstVoter = true; - for (Voter voter : voters) { - - if (log.isDebugEnabled()) { - log.debug("Start count for voter " + voter.getVoterId()); - } - double voterWeight = voter.getWeight(); - - Arrays.fill(currentVoteWinner, 0); - - Map<String, VoteForChoice> voteByChoiceIds = - Maps.uniqueIndex(voter.getVoteForChoices(), - new ChoiceIdAble.ChoiceIdAbleById()); - - double maxBattleWins = 0; - - for (String choiceId : choiceIds) { - - // get vote for this choice - VoteForChoice voteForChoice = voteByChoiceIds.get(choiceId); - - int x = choiceIds.indexOf(choiceId); - - for (VoteForChoice voteForChoice1 : voter.getVoteForChoices()) { - - String choiceId1 = voteForChoice1.getChoiceId(); - if (choiceId.equals(choiceId1)) { - - // can not battle same choice - continue; - } - - int y = choiceIds.indexOf(choiceId1); - - int compare = comparator.compare(voteForChoice, - voteForChoice1); - - if (compare < 0) { - - // voteForChoice wins his battle - - int pos = x * nbChoices + y; - - matrix[x][y] = matrix[x][y] + voterWeight; - if (log.isTraceEnabled()) { - log.trace("battle [" + choiceId + "<" + - voteForChoice.getVoteValue() + "> - " + - choiceId1 + "<" + - voteForChoice1.getVoteValue() + - ">] wins (store to " + pos + - ", new value=" + matrix[x][y] + ")"); - } - maxBattleWins = Math.max( - maxBattleWins, - currentVoteWinner[x] = - currentVoteWinner[x] + voterWeight); - } + if (choiceIdX.equals(voteForChoiceY.getChoiceId())) { + // no battle for same choice + continue; } - } + int compare = voteValueComparator.compare(voteForChoiceX, + voteForChoiceY); - if (firstVoter || winners != null) { + if (compare < 0) { - // still have a winner, le'ts compute current winner - Set<String> currentVoteWinners = Sets.newHashSet(); - - // find winners of this vote - for (int i = 0; i < currentVoteWinner.length; i++) { - if (maxBattleWins == currentVoteWinner[i]) { - - // this is a winner for this vote - String choiceId = choiceIds.get(i); - currentVoteWinners.add(choiceId); - } + // X wins over Y; + score += voterWeight; } - if (log.isDebugEnabled()) { - log.debug("Winners of this vote : " + currentVoteWinners); - } - - - if (firstVoter) { - - // keep this first result - winners = currentVoteWinners; - } else if (!winners.equals(currentVoteWinners)) { - - // current vote has not same winners than previous, - // so will need resolution - winners = null; - - if (log.isDebugEnabled()) { - log.debug("Dismatch winners (will need resolv...)"); - } - } } - firstVoter = false; + // store new score for this choice + ChoiceScore choiceScore = scores.get(choiceIdX); + choiceScore.addScoreValue(score); } - return winners; } } Modified: trunk/pollen-votecounting-coombs/src/main/java/org/chorem/pollen/votecounting/CoombsVoteCountingStrategy.java =================================================================== --- trunk/pollen-votecounting-coombs/src/main/java/org/chorem/pollen/votecounting/CoombsVoteCountingStrategy.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-coombs/src/main/java/org/chorem/pollen/votecounting/CoombsVoteCountingStrategy.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -35,7 +35,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.SortedMap; /** * Coombs vote counting strategy. @@ -49,7 +48,7 @@ public VoteCountingResult votecount(Set<Voter> voters) { // get empty result by choice - SortedMap<String, ChoiceScore> resultByChoice = votersToResult(voters); + Map<String, ChoiceScore> scores = newEmptyChoiceScoreMap(voters); // calcul du score minimum pour atteindre la majorité absolue double totalWeight = 0.; @@ -63,24 +62,24 @@ buildVoterSortedChoices(voters); Set<String> choiceIdsToExclude = Sets.newHashSet(); - Set<String> choiceIdsToKeep = Sets.newHashSet(resultByChoice.keySet()); + Set<String> choiceIdsToKeep = Sets.newHashSet(scores.keySet()); + //FIXME Must use round of elemination to order scores round(topRankChoices, choiceIdsToExclude, choiceIdsToKeep, - resultByChoice, + scores, totalWeight); - - // transform map of result to list of them (and sort them) - VoteCountingResult result = resultToList(resultByChoice); + // order scores (using their value) and return result + VoteCountingResult result = orderByValues(scores.values()); return result; } - public void round(Map<Voter, List<Set<String>>> topRankChoices, + protected void round(Map<Voter, List<Set<String>>> topRankChoices, Set<String> idsToExclude, Set<String> idsToInclude, - SortedMap<String, ChoiceScore> resultByChoice, + Map<String, ChoiceScore> resultByChoice, double totalWeight) { List<ChoiceScore> results = applyScores(topRankChoices, @@ -117,10 +116,10 @@ } } - public List<ChoiceScore> applyScores(Map<Voter, List<Set<String>>> topRankChoices, + protected List<ChoiceScore> applyScores(Map<Voter, List<Set<String>>> topRankChoices, Set<String> idsToExclude, Set<String> idsToInclude, - SortedMap<String, ChoiceScore> resultByChoice) { + Map<String, ChoiceScore> resultByChoice) { if (CollectionUtils.isNotEmpty(idsToExclude)) { @@ -188,7 +187,7 @@ return results; } - public void guessChoiceIdsToRemove(Set<String> idsToInclude, + protected void guessChoiceIdsToRemove(Set<String> idsToInclude, Set<String> idsToExclude, Map<Voter, List<Set<String>>> results) { Modified: trunk/pollen-votecounting-instant-runoff/src/main/java/org/chorem/pollen/votecounting/InstantRunoffVoteCountingStrategy.java =================================================================== --- trunk/pollen-votecounting-instant-runoff/src/main/java/org/chorem/pollen/votecounting/InstantRunoffVoteCountingStrategy.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-instant-runoff/src/main/java/org/chorem/pollen/votecounting/InstantRunoffVoteCountingStrategy.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -34,7 +34,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.SortedMap; /** * InstantRunoff. @@ -48,7 +47,7 @@ public VoteCountingResult votecount(Set<Voter> voters) { // get empty result by choice - SortedMap<String, ChoiceScore> resultByChoice = votersToResult(voters); + Map<String, ChoiceScore> scores = newEmptyChoiceScoreMap(voters); // calcul du score minimum pour atteindre la majorité absolue double totalWeight = 0.; @@ -62,23 +61,24 @@ buildVoterSortedChoices(voters); Set<String> choiceIdsToExclude = Sets.newHashSet(); - Set<String> choiceIdsToKeep = Sets.newHashSet(resultByChoice.keySet()); + Set<String> choiceIdsToKeep = Sets.newHashSet(scores.keySet()); + //FIXME Must use round of elemination to order scores round(topRankChoices, choiceIdsToExclude, choiceIdsToKeep, - resultByChoice, + scores, totalWeight); - // transform map of result to list of them (and sort them) - VoteCountingResult result = resultToList(resultByChoice); + // order scores (using their value) and return result + VoteCountingResult result = orderByValues(scores.values()); return result; } - public void round(Map<Voter, List<Set<String>>> topRankChoices, + protected void round(Map<Voter, List<Set<String>>> topRankChoices, Set<String> idsToExclude, Set<String> idsToInclude, - SortedMap<String, ChoiceScore> resultByChoice, + Map<String, ChoiceScore> resultByChoice, double totalWeight) { List<ChoiceScore> results = applyScores(topRankChoices, @@ -116,10 +116,10 @@ } } - public List<ChoiceScore> applyScores(Map<Voter, List<Set<String>>> topRankChoices, + protected List<ChoiceScore> applyScores(Map<Voter, List<Set<String>>> topRankChoices, Set<String> idsToExclude, Set<String> idsToInclude, - SortedMap<String, ChoiceScore> resultByChoice) { + Map<String, ChoiceScore> resultByChoice) { if (CollectionUtils.isNotEmpty(idsToExclude)) { @@ -187,7 +187,7 @@ return results; } - public void guessChoiceIdsToRemove(Set<String> idsToExclude, + protected void guessChoiceIdsToRemove(Set<String> idsToExclude, List<ChoiceScore> results) { double min = results.get(0).getScoreValue().doubleValue(); Modified: trunk/pollen-votecounting-normal/src/main/java/org/chorem/pollen/votecounting/NormalVoteCountingStrategy.java =================================================================== --- trunk/pollen-votecounting-normal/src/main/java/org/chorem/pollen/votecounting/NormalVoteCountingStrategy.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-normal/src/main/java/org/chorem/pollen/votecounting/NormalVoteCountingStrategy.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -31,7 +31,9 @@ import java.util.Set; /** - * Condorcet. + * Vote counting strategy for {@link NormalVoteCounting}. + * <p/> + * This is a very basic vote counting: * * @author tchemit <chemit@codelutin.com> * @since 1.4.5 @@ -42,19 +44,21 @@ public VoteCountingResult votecount(Set<Voter> voters) { // get empty result by choice - Map<String, ChoiceScore> resultByChoice = votersToResult(voters); + Map<String, ChoiceScore> scores = newEmptyChoiceScoreMap(voters); for (Voter voter : voters) { + // add this voter votes to result - addVoterChoices(voter, resultByChoice); + addVoterChoices(voter, scores); } - // transform map of result to list of them (and sort them) - VoteCountingResult result = resultToList(resultByChoice); + + // order scores (using their value) and return result + VoteCountingResult result = orderByValues(scores.values()); return result; } - public void addVoterChoices(Voter voter, - Map<String, ChoiceScore> resultByChoice) { + protected void addVoterChoices(Voter voter, + Map<String, ChoiceScore> scores) { double voterWeight = voter.getWeight(); @@ -63,14 +67,13 @@ if (voteValue != null) { // get score for choice - String choiceId = voteForChoice.getChoiceId(); - ChoiceScore choiceScore = resultByChoice.get(choiceId); + ChoiceScore score = scores.get(voteForChoice.getChoiceId()); // compute score to add double scoreToAdd = voteValue * voterWeight; // add to score this weighted vote - choiceScore.addScoreValue(scoreToAdd); + score.addScoreValue(scoreToAdd); } } } Modified: trunk/pollen-votecounting-normal/src/test/java/org/chorem/pollen/votecounting/NormalVoteCountingStrategyTest.java =================================================================== --- trunk/pollen-votecounting-normal/src/test/java/org/chorem/pollen/votecounting/NormalVoteCountingStrategyTest.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-normal/src/test/java/org/chorem/pollen/votecounting/NormalVoteCountingStrategyTest.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -103,9 +103,9 @@ Assert.assertNotNull(scores); Assert.assertEquals(3, scores.size()); - assertChoiceScore(scores.get(0), CHOICE_B, BigDecimal.valueOf(2.)); + assertChoiceScore(scores.get(0), CHOICE_B, BigDecimal.valueOf(2.), 0); - assertChoiceScoreEquals(BigDecimal.valueOf(1.), + assertChoiceScoreEquals(BigDecimal.valueOf(1.), 1, Sets.newHashSet(scores.get(1), scores.get(2)), CHOICE_A, CHOICE_C); @@ -166,9 +166,9 @@ Assert.assertNotNull(scores); Assert.assertEquals(3, scores.size()); - assertChoiceScore(scores.get(0), CHOICE_B, BigDecimal.valueOf(2.)); + assertChoiceScore(scores.get(0), CHOICE_B, BigDecimal.valueOf(2.), 0); - assertChoiceScoreEquals(BigDecimal.valueOf(1.), + assertChoiceScoreEquals(BigDecimal.valueOf(1.), 1, Sets.newHashSet(scores.get(1), scores.get(2)), CHOICE_A, CHOICE_C); @@ -204,9 +204,9 @@ Assert.assertNotNull(scores); Assert.assertEquals(3, scores.size()); - assertChoiceScore(scores.get(0), CHOICE_A, BigDecimal.valueOf(3.)); - assertChoiceScore(scores.get(1), CHOICE_B, BigDecimal.valueOf(2.)); - assertChoiceScore(scores.get(2), CHOICE_C, null); + assertChoiceScore(scores.get(0), CHOICE_A, BigDecimal.valueOf(3.), 0); + assertChoiceScore(scores.get(1), CHOICE_B, BigDecimal.valueOf(2.), 1); + assertChoiceScore(scores.get(2), CHOICE_C, null, 2); } @Test @@ -239,11 +239,11 @@ Assert.assertNotNull(scores); Assert.assertEquals(3, scores.size()); - assertChoiceScoreEquals(BigDecimal.valueOf(3.), + assertChoiceScoreEquals(BigDecimal.valueOf(3.), 0, Sets.newHashSet(scores.get(0), scores.get(1)), CHOICE_A, CHOICE_B); - assertChoiceScore(scores.get(2), CHOICE_C, null); + assertChoiceScore(scores.get(2), CHOICE_C, null, 1); } @Test @@ -276,11 +276,11 @@ Assert.assertNotNull(scores); Assert.assertEquals(3, scores.size()); - assertChoiceScoreEquals(BigDecimal.valueOf(2.), + assertChoiceScoreEquals(BigDecimal.valueOf(2.), 0, Sets.newHashSet(scores.get(0), scores.get(1)), CHOICE_A, CHOICE_B); - assertChoiceScore(scores.get(2), CHOICE_C, BigDecimal.valueOf(1.)); + assertChoiceScore(scores.get(2), CHOICE_C, BigDecimal.valueOf(1.), 1); } @Test @@ -313,20 +313,23 @@ Assert.assertNotNull(scores); Assert.assertEquals(3, scores.size()); - assertChoiceScore(scores.get(0), CHOICE_B, BigDecimal.valueOf(4.)); - assertChoiceScore(scores.get(1), CHOICE_C, BigDecimal.valueOf(3.)); - assertChoiceScore(scores.get(2), CHOICE_A, BigDecimal.valueOf(2.)); + assertChoiceScore(scores.get(0), CHOICE_B, BigDecimal.valueOf(4.), 0); + assertChoiceScore(scores.get(1), CHOICE_C, BigDecimal.valueOf(3.), 1); + assertChoiceScore(scores.get(2), CHOICE_A, BigDecimal.valueOf(2.), 2); } public static void assertChoiceScore(ChoiceScore choiceScore, String choiceId, - BigDecimal choiceResult) { + BigDecimal choiceResult, + int rank) { Assert.assertNotNull(choiceScore); Assert.assertEquals(choiceId, choiceScore.getChoiceId()); Assert.assertEquals(choiceResult, choiceScore.getScoreValue()); + Assert.assertEquals(rank, choiceScore.getScoreOrder()); } public static void assertChoiceScoreEquals(BigDecimal choiceResult, + int rank, Set<ChoiceScore> choiceScores, String... choiceIds) { @@ -337,7 +340,7 @@ ChoiceScore choiceScore = choicesById.get(choiceId); Assert.assertNotNull(choiceScore); - assertChoiceScore(choiceScore, choiceId, choiceResult); + assertChoiceScore(choiceScore, choiceId, choiceResult, rank); } } Modified: trunk/pollen-votecounting-number/src/main/java/org/chorem/pollen/votecounting/NumberVoteCountingStrategy.java =================================================================== --- trunk/pollen-votecounting-number/src/main/java/org/chorem/pollen/votecounting/NumberVoteCountingStrategy.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-number/src/main/java/org/chorem/pollen/votecounting/NumberVoteCountingStrategy.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -42,19 +42,21 @@ public VoteCountingResult votecount(Set<Voter> voters) { // get empty result by choice - Map<String, ChoiceScore> resultByChoice = votersToResult(voters); + Map<String, ChoiceScore> scores = newEmptyChoiceScoreMap(voters); for (Voter voter : voters) { + // add this voter votes to result - addVoterChoices(voter, resultByChoice); + addVoterChoices(voter, scores); } - // transform map of result to list of them (and sort them) - VoteCountingResult result = resultToList(resultByChoice); + + // order scores (using their value) and return result + VoteCountingResult result = orderByValues(scores.values()); return result; } - public void addVoterChoices(Voter voter, - Map<String, ChoiceScore> resultByChoice) { + protected void addVoterChoices(Voter voter, + Map<String, ChoiceScore> scores) { double voterWeight = voter.getWeight(); @@ -64,7 +66,7 @@ // get score for choice String choiceId = voteForChoice.getChoiceId(); - ChoiceScore choiceScore = resultByChoice.get(choiceId); + ChoiceScore choiceScore = scores.get(choiceId); // compute score to add double scoreToAdd = voteValue * voterWeight; Modified: trunk/pollen-votecounting-percentage/src/main/java/org/chorem/pollen/votecounting/PercentageVoteCountingStrategy.java =================================================================== --- trunk/pollen-votecounting-percentage/src/main/java/org/chorem/pollen/votecounting/PercentageVoteCountingStrategy.java 2012-09-29 13:06:12 UTC (rev 3709) +++ trunk/pollen-votecounting-percentage/src/main/java/org/chorem/pollen/votecounting/PercentageVoteCountingStrategy.java 2012-09-29 21:18:12 UTC (rev 3710) @@ -40,20 +40,23 @@ @Override public VoteCountingResult votecount(Set<Voter> voters) { + // get empty result by choice - Map<String, ChoiceScore> resultByChoice = votersToResult(voters); + Map<String, ChoiceScore> scores = newEmptyChoiceScoreMap(voters); for (Voter voter : voters) { + // add this voter votes to result - addVoterChoices(voter, resultByChoice); + addVoterChoices(voter, scores); } - // transform map of result to list of them (and sort them) - VoteCountingResult result = resultToList(resultByChoice); + + // order scores (using their value) and return result + VoteCountingResult result = orderByValues(scores.values()); return result; } - public void addVoterChoices(Voter voter, - Map<String, ChoiceScore> resultByChoice) { + protected void addVoterChoices(Voter voter, + Map<String, ChoiceScore> scores) { double voterWeight = voter.getWeight(); @@ -63,7 +66,7 @@ // get score for choice String choiceId = voteForChoice.getChoiceId(); - ChoiceScore choiceScore = resultByChoice.get(choiceId); + ChoiceScore choiceScore = scores.get(choiceId); // compute score to add double scoreToAdd = voteValue * voterWeight;