我决定采用这种策略模式,因为可以通过许多不同的方式来实施。我首先做了自己的实现,然后又添加了一个“ PascalElection”策略,因为该代码是从Pascal实现中翻译和改编而成的(此处不包括在此处进行审查)。
代码概述:
ElectionStrategy:战略模式的接口
Election:用于容纳有关选票和被提名人数据的类。还包含执行选举的静态方法。
ElectionResult :(在
Election
内的静态类)表示选举的结果。CandidateState :(在
Election
内的静态类)表示候选人可能处于几个州(我的策略仅使用其中的三个,而PascalElection策略使用所有的三个州)。SimonElection:我自己对选举策略的实现
回合:由于STV投票系统是一个迭代系统,因此代表了投票中的一个“回合”。这可用于在结果上绘制图形。 (生成图形的代码未包含在这篇文章中)
该项目也可以在GitHub上找到:Zomis / StackSTV
显示选举结果的图形示例:
我的策略:
pascal策略:
代码
选举策略:
interface ElectionStrategy {
Election.ElectionResult elect(Election election)
}
选举:
class Election {
final List<Candidate> candidates = new ArrayList<>()
final List<Vote> votes = new ArrayList<>()
int availablePositions
int maxChoices
private Election(int availablePositions) {
this.availablePositions = availablePositions
}
void addVote(Vote vote) {
this.votes << vote
this.maxChoices = Math.max(maxChoices, vote.preferences.length)
}
void addCandidate(String name) {
this.candidates.add(new Candidate(name: name))
}
double calculateQuota(double excess) {
(votes.size() - excess) / (availablePositions + 1)
}
static class ElectionResult {
List<Round> rounds
List<Candidate> candidateResults
List<Candidate> getCandidates(CandidateState state) {
candidateResults.stream()
.filter({it.state == state})
.collect(Collectors.toList())
}
}
ElectionResult elect(ElectionStrategy strategy) {
strategy.elect(this)
}
static enum CandidateState {
HOPEFUL, EXCLUDED, ALMOST, NEWLY_ELECTED, ELECTED
}
@ToString(includeNames = true, includePackage = false)
static class Candidate {
String name
double weighting = 1
double votes
CandidateState state = CandidateState.HOPEFUL
Candidate copy() {
new Candidate(name: name, weighting: weighting, votes: votes, state: state)
}
}
@ToString
static class Vote {
int numVotes
Candidate[] preferences
static Vote fromLine(String line, Election election) {
String[] data = line.split()
Vote vote = new Vote()
vote.numVotes = data[0] as int
int candidateVotes = data.length - 2
vote.preferences = new Candidate[candidateVotes]
for (int i = 0; i < vote.preferences.length; i++) {
int candidate = data[i + 1] as int
if (candidate > 0) {
vote.preferences[i] = election.candidates.get(candidate - 1)
}
}
vote
}
void distribute(Round round) {
double remaining = numVotes
int choiceIndex = 0
preferences.eachWithIndex { Candidate entry, int i ->
if (entry) {
double myScore = remaining * entry.weighting
entry.votes += myScore
remaining -= myScore
round.usedVotes[choiceIndex++] += myScore
}
}
round.excess += remaining
}
}
static final ElectionResult fromURL(URL url, ElectionStrategy strategy) {
BufferedReader reader = url.newReader()
String[] head = reader.readLine().split()
int candidates = head[0] as int
Election stv = new Election(head[1] as int)
for (int i = 0; i < candidates; i++) {
stv.addCandidate("Candidate $i") // use a temporary name at first. real names are at the end of the file
}
String line = reader.readLine();
while (line != '0') {
Vote vote = Vote.fromLine(line, stv)
stv.addVote(vote)
line = reader.readLine();
}
for (int i = 0; i < candidates; i++) {
String name = reader.readLine()
stv.candidates.get(i).name = name
}
stv.elect(strategy)
}
}
Simon选举:
class SimonElection implements ElectionStrategy {
@Override
Election.ElectionResult elect(Election election) {
List<Round> rounds = new ArrayList<>()
int electedCount = 0
int roundsCount = 0
double previousExcess = 0
while (electedCount < election.availablePositions) {
Round round = new Round(roundsCount, election.maxChoices)
rounds << round
double roundQuota = election.calculateQuota(previousExcess)
roundsCount++
round.quota = roundQuota
election.candidates*.votes = 0
election.votes*.distribute(round)
List<Election.Candidate> elected = election.candidates.stream()
.filter({candidate -> candidate.votes > roundQuota})
.collect(Collectors.toList())
elected.each {
if (it.state != Election.CandidateState.ELECTED) {
electedCount++
}
it.state = Election.CandidateState.ELECTED
it.weighting *= roundQuota / it.votes
}
if (elected.isEmpty()) {
Election.Candidate loser = election.candidates.stream()
.filter({it.state == Election.CandidateState.HOPEFUL})
.min(Comparator.comparingDouble({it.votes})).get()
loser.state = Election.CandidateState.EXCLUDED
loser.weighting = 0
}
round.candidates = election.candidates.collect {it.copy()}
previousExcess = round.excess
}
new Election.ElectionResult(rounds: rounds, candidateResults: election.candidates)
}
}
圆形:
@ToString
class Round {
int round
List<Election.Candidate> candidates = new ArrayList<>()
double quota
double[] usedVotes
double excess
Round(int round, int maxChoices) {
this.round = round
this.usedVotes = new double[maxChoices]
}
}
运行代码
可以在GitHub存储库上进行测试,我目前仅使用最新的Stack Overflow选举中的选举数据在测试中运行代码
主要问题
我最感兴趣的是我使用Groovy的方式以及使用Java 8可以代替(?)或除了(?)之外还可以做什么Groovy的事情,我应该使用Java而不是Groovy的东西,还是应该使用Java?我使用了更多Groovy而不是Java?
欢迎任何其他评论。
#1 楼
由于偏向于Groovy,我想做更多Groovy事情:)有很多方法可以使您的代码更加Grooooooovy。
def是您的朋友
def关键字使您可以轻松声明变量,并且使您的声明更加容易。
// Eeewwww
String s1 = 'hello'
double d1 = Math.floor(10.34)
ArrayList<Integer> l1 = new ArrayList<Integer>()
l1.add(1)
l1.add(2)
l1.add(3)
// Much better.
def s2 = 'hello'
def d2 = Math.floor(10.34)
def l2 = [1, 2, 3]
// It's all the same
assert s1 == s2 // == calls me.equals(other)
assert s1.is(s2) // me.is(other) is Java's reference equality
assert d1 == d2
assert l1 == l2
assert s1.class == s2.class
assert d1.class == d2.class
assert l1.class == l2.class
代码上面的内容还证明了Groovy确定身份的方式不同于Java。还要注意,图元是自动装箱的。实际上,Groovy没有基元。一切都是对象。
for循环基本上是没有意义的。
在Groovy中,很少有用于循环的代码,因为有很多更好的方法可以实现相同的目的。
// This is the same...
for (int i = 0; i < candidates; i++) {
String name = reader.readLine()
stv.candidates.get(i).name = name
}
// ...as this, which uses Number.times(Closure)...
candidates.times {
String name = reader.readLine()
stv.candidates.get(i).name = name
}
// ...and as this, which uses Range.each{Closure).
(0..<candidates).each {
String name = reader.readLine()
stv.candidates.get(i).name = name
}
这些结构不仅令人愉快,而且消除了错误处理增量的可能性。如果无法触摸,就无法破坏。
... Java Streams也是如此
Groovy以一种强大的方式增强了Java Collections使Java 8 Streams看起来像Fortran(只要您不需要Java 8 Streams提供的懒惰)。
Java 8 Streams
candidateResults.stream()
.filter({it.state == state})
.collect(Collectors.toList())
election.candidates.stream()
.filter({candidate -> candidate.votes > roundQuota})
.collect(Collectors.toList())
election.candidates.stream()
.filter({it.state == Election.CandidateState.HOPEFUL})
.min(Comparator.comparingDouble({it.votes})).get()
< br groovy方式
candidateResults.findAll {it.state == state}
election.candidates
.findAll {candidate -> candidate.votes > roundQuota}
election.candidates
.findAll {it.state == Election.CandidateState.HOPEFUL}
.min {it.votes}
每个文件多个类
您可以将多个Groovy类放置在同一* .groovy文件中。不再有静态内部类:)
推出自己的增强功能
正如Groovy通过其GDK增强Java一样,您可以通过元编程来增强Java和Groovy类。这是我对Election.fromURL()进行增强的示例:
之前
/* Iterates through two sections of the file.
* The first section is handled with the 'while loop',
* and the second in the 'for loop'
*/
while (line != '0') {
Vote vote = Vote.fromLine(line, stv)
stv.addVote(vote)
line = reader.readLine()
}
for (int i = 0; i < candidates; i++) {
String name = reader.readLine()
stv.candidates.get(i).name = name
}
之后
/* Also iterates through two sections of the file,
* but by using an Iterator.while(Closure condition) method
* added through meta-programming.
*
* The first section is handled with the 'while()/call() loop',
* and the second in the 'upto() loop'
*/
use(IteratorCategory) {
reader.iterator().while { line -> line != '0' }.call { line ->
stv << Vote.fromLine(line, stv)
}.upto(candidates) { line, i ->
stv.candidates.get(i).name = line
}
}
我创建此构造是因为它使查看代码的意图更加容易。
解释
添加的方法Iterator.while(Closure)需要一个Closure,该Closure在被调用时返回一个可以由Groovy Truth评估的值。 Closure返回的值用于确定是否继续迭代。
Iterator.while(Closure)方法返回另一个Closure。此闭包在被调用时启动迭代。该Closure还需要第三个Closure,该迭代由迭代器提供的每个元素调用。直到迭代终止。
最后,当迭代完成时,返回Iterator,准备进行其他迭代。
Iterator.while(Closure)(和Iterator.upto(Integer) ,Closure))通过Groovy的元编程成为可能。在这种情况下,可以通过如下所示的Groovy类别实现:
package net.zomis.meta
/*
* Adds useful methods to Iterator.
*/
@groovy.lang.Category(Iterator)
class IteratorCategory {
/*
* Returns a Closure which iterates while the condition Closure
* evaluates to true. The returned Closure expects another Closure,
* an action closure, as its single argument.
* This 'action' Closure is called during each iteration and is passed
* the Iterator.next()value.
* When the iteration is complete, the Iterator is returned.
*
* Example usage:
* use(IteratorCategory) {
* def iter = reader.iterator().while { it != 'end' }.call { println it }
* }
*
* @param condition Closure to evaluate on each iteration.
* @return a Closure
*/
Closure 'while'(Closure condition) {
{Iterator iter, Closure closure ->
while(iter.hasNext()) {
def it = iter.next()
if(condition(it)) {
closure(it)
} else {
break
}
}
return iter
}.curry(this)
}
/*
* Similar to Number.upto(Number, Closure), executes the Closure
* UP TO a specified number of times. However, instead of returning
* the Closure's return value, it returns the Iterator where
* it left off.
*
* Example usage:
* use(IteratorCategory) {
* def iter = reader.iterator().upto(5) {it, i -> println "$i - $it" }
* }
*
* @param to number of times to iterate
* @param closure to execute. Called with Iterator.next() and index.
* @return Iterator
*/
Iterator upto(int to, Closure closure) {
int i = 0
while(this.hasNext() && i < to) {
closure this.next(), i
i++
}
return this
}
}
全部完成了
我希望这可以帮助您更多地编写Groovy代码。 .. Groooovy。
在这里查看大部分已经被Groovy验证过的StackSTV。
评论
\ $ \ begingroup \ $
非常Groooovy的答案,感谢您的配合!
\ $ \ endgroup \ $
– ran
15年8月21日在22:31
评论
我在这里用叉子开始Groovy-fying:github.com/emmanuelrosa/StackSTV嗯...这是奇怪的....当我现在试运行的选举数据,垫的马克杯是被选为第三个,我成为了第四位。如果我将选举更改为仅需要三名候选人,它说我将是第三名,那么Mat的马克杯将不会被选举。我的代码中有奇怪的错误吗?
我依靠现有的测试,并且在我完成更改时就通过了测试。您指的是哪个版本?
@EmmanuelRosa我没有提及任何特定的修订,也没有怪您。我只是觉得这有点奇怪。我不知道将选举只改为三名候选人的真实结果。