考虑到选举时间,实施2015年7月的社区挑战似乎很重要。

我决定采用这种策略模式,因为可以通过许多不同的方式来实施。我首先做了自己的实现,然后又添加了一个“ 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?

欢迎任何其他评论。

评论

我在这里用叉子开始Groovy-fying:github.com/emmanuelrosa/StackSTV

嗯...这是奇怪的....当我现在试运行的选举数据,垫的马克杯是被选为第三个,我成为了第四位。如果我将选举更改为仅需要三名候选人,它说我将是第三名,那么Mat的马克杯将不会被选举。我的代码中有奇怪的错误吗?

我依靠现有的测试,并且在我完成更改时就通过了测试。您指的是哪个版本?

@EmmanuelRosa我没有提及任何特定的修订,也没有怪您。我只是觉得这有点奇怪。我不知道将选举只改为三名候选人的真实结果。

#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