#candidate #voting #trie #tries #choice #votes #rcv

trie_rcv

使用 Tries 在 Rust 中实现的排名选择投票(RCV)

8 个稳定版本

1.2.0 2024 年 5 月 12 日
1.1.4 2024 年 5 月 6 日
1.1.2 2024 年 5 月 5 日
1.0.1 2024 年 5 月 3 日

#115 in 科学

Download history 73/week @ 2024-04-27 432/week @ 2024-05-04 214/week @ 2024-05-11 19/week @ 2024-05-18 1/week @ 2024-05-25 3/week @ 2024-07-06

每月 367 次下载

Apache-2.0

42KB
734

trie_rcv

https://crates.io/crates/trie_rcv
排名选择投票(RCV)在 Rust 中的实现。

RCV 与正常的第一票过半投票不同之处在于,选民可以按从最优先到最不优先的顺序对候选人进行排序。为了确定 RCV 投票的获胜者,每轮投票中最低得票数的候选人的选票将转移到各自排序中的下一位候选人,直到有候选人获得有效多数。

示例用法

use trie_rcv;
use trie_rcv::RankedChoiceVoteTrie;
use trie_rcv::vote::RankedVote;

fn main() {
    let mut rcv = RankedChoiceVoteTrie::new();
    // remove all candidates with the lowest number of votes each round
    rcv.set_elimination_strategy(EliminationStrategies::EliminateAll);
    rcv.insert_vote(RankedVote::from_vector(&vec![1, 2, 3, 4]).unwrap());
    rcv.insert_vote(RankedVote::from_vector(&vec![1, 2, 3]).unwrap());
    rcv.insert_vote(RankedVote::from_vector(&vec![3]).unwrap());
    rcv.insert_vote(RankedVote::from_vector(&vec![3, 2, 4]).unwrap());
    rcv.insert_vote(RankedVote::from_vector(&vec![4, 1]).unwrap());
    let winner = rcv.determine_winner();
    println!("WINNER = {:?}", winner);
    assert_eq!(winner, Some(1));
    
    // alternatively:
    let votes = RankedVote::from_vectors(&vec![
        vec![1, 2, 3, 4],
        vec![1, 2, 3],
        vec![3],
        vec![3, 2, 4],
        vec![4, 1]
    ]).unwrap();

    let winner2 = rcv.run_election(votes);
    println!("WINNER = {:?}", winner2);
    assert_eq!(winner2, Some(1));
}

此实现还支持以 SpecialVotes::WITHHOLDSpecialVotes::ABSTAIN 值结束的排名投票

  1. 特殊投票::WITHHOLD
    允许选民明确表示对任何候选人都不感兴趣。
    从本质上讲,这允许选民表示不信任。
    通过 SpecialVotes::WITHHOLD.to_int() 序列化为 -1
  2. 特殊投票::ABSTAIN
    允许选民明确表示对任何候选人都不感兴趣,同时自愿将自己从投票中移除。
    从本质上讲,这允许选民表示他希望某个候选人获胜,但自己无法做出决定,因此希望将决定权委托给其他选民。
    通过 SpecialVotes::ABSTAIN.to_int() 序列化为 -2
use trie_rcv;
use trie_rcv::RankedChoiceVoteTrie;
use trie_rcv::vote::{RankedVote, SpecialVotes};

fn main() {
    let rcv = RankedChoiceVoteTrie::new();

    let votes_round1 = RankedVote::from_vectors(&vec![
        vec![1, SpecialVotes::WITHHOLD.to_int()],
        vec![2, 1],
        vec![3, 2],
        vec![3]
    ]).unwrap();

    let winner_round1 = rcv.run_election(votes_round1);
    println!("WINNER = {:?}", winner);
    assert_eq!(
        winner_round1, None, concat![
        "Candidate 1's vote should not count after round 1, ",
        "no one should have majority"
    ]);
    
    let votes_round2 = RankedVote::from_vectors(&vec![
        vec![1, SpecialVotes::ABSTAIN.to_int()],
        vec![2, 1],
        vec![3, 2],
        vec![3]
    ]).unwrap();

    let winner_round2 = rcv.run_election(votes_round2);
    println!("WINNER = {:?}", winner_round2);
    assert_eq!(
       winner_round2, Some(3), concat![
       "First vote is ignored in round 2, candidate 3 wins"
    ]);
}

淘汰策略

从技术上来说,RCV 算法规范没有说明在 RCV 某一轮中有多位候选人的得票数相同且为最低时应该怎么办。

elimination_strategy 设置处理每轮中要淘汰的候选人。
从技术角度看,RCV算法规范并未明确说明在多轮RCV中,如果存在多个候选人得票数相同且为最低的情况应该怎么处理 - EliminationStrategies::ElimnateAllEliminationStrategies::DowdallScoringEliminationStrategies::RankedPairs 提供了不同的方法来解决这个边缘情况。

  1. EliminationStrategies::ElimnateAll
    每轮移除所有得票数最低的候选人。
  2. EliminationStrategies::DowdallScoring(默认)
    在每轮得票数最低的多个候选人中,按照dowdall得分对候选人进行排序,并移除得分最低的候选人。每个候选人的dowdall得分是通过将每个排名投票的排名(从1开始)的倒数相加得出的。如果排名投票中没有包含候选人,则不计入dowdall得分。
  3. EliminationStrategies::RankedPairs
    在每轮得票数最低的多个候选人中,通过ranked-pair比较尝试构建一个有向无环图,以建立候选人偏好的优先顺序,其中候选人A被认为比候选人B好,如果存在更多的投票将A排名高于B,反之亦然,并移除在优先顺序底部的候选人(即没有其他候选人比它“好”,并且至少有一个候选人可以说在优先顺序中“更好”)。
    1. 注意,如果排名投票明确将A排名高于B,但没有将B排名,则这也计为将A排名高于B。
    2. 如果无法建立有向无环的偏好图(例如,当候选人之间存在循环偏好时),则移除行为将默认为每轮移除得票数相同且最低的候选人,即回退到EliminationStrategies::ElimnateAll的行为。
  4. EliminationStrategies::CondorcetRankedPairs
    (根据这篇论文实施多数规则)
    在每轮得票数最低和第二低的情况下,尝试通过ranked-pair比较构建一个有向无环图,以建立候选人偏好的优先顺序,并移除在优先顺序底部的候选人。这确保如果投票结果中存在Condorcet获胜者,获胜候选人将是Condorcet获胜者,如果无法构建偏好图,则将回退到EliminationStrategies::ElimnateAll

构建说明

使用cargo build构建crate,使用cargo test运行集成测试

依赖关系

~2.5MB
~38K SLoC