Skip to content

Commit

Permalink
bump internal vote candidate value datatype from u16 to u32
Browse files Browse the repository at this point in the history
  • Loading branch information
milselarch committed Oct 23, 2024
1 parent 78ebb02 commit a62b6cd
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 53 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "trie_rcv"
version = "1.2.2"
version = "1.3.0"
edition = "2021"
description = "Ranked Choice Voting implementation using Tries in Rust"
license = "Apache-2.0"
Expand Down
64 changes: 32 additions & 32 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ impl TrieNode {

pub struct RankedChoiceVoteTrie {
root: TrieNode,
dowdall_score_map: HashMap<u16, f32>,
dowdall_score_map: HashMap<u32, f32>,
elimination_strategy: EliminationStrategies,
unique_candidates: HashSet<u16>
unique_candidates: HashSet<u32>
}

struct VoteTransfer<'a> {
next_candidate: u16,
next_candidate: u32,
next_node: &'a TrieNode,
num_votes: u64
}
Expand Down Expand Up @@ -85,7 +85,7 @@ pub enum EliminationStrategies {
CondorcetRankedPairs
}

fn is_graph_acyclic(graph: &DiGraph<u16, u64>) -> bool {
fn is_graph_acyclic(graph: &DiGraph<u32, u64>) -> bool {
/*
checks if there doesn't exist any path of directed edges
from some edge in the graph back to itself
Expand All @@ -96,7 +96,7 @@ fn is_graph_acyclic(graph: &DiGraph<u16, u64>) -> bool {

fn dfs_find_cycle(
node: &NodeIndex, path: &mut Vec<NodeIndex>,
explored: &mut HashSet<NodeIndex>, graph: &DiGraph<u16, u64>
explored: &mut HashSet<NodeIndex>, graph: &DiGraph<u32, u64>
) -> bool {
// use DFS to see if a cycle can be created from paths starting from node
explored.insert(*node);
Expand Down Expand Up @@ -134,7 +134,7 @@ fn is_graph_acyclic(graph: &DiGraph<u16, u64>) -> bool {
true
}

fn is_graph_weakly_connected(graph: &DiGraph<u16, u64>) -> bool {
fn is_graph_weakly_connected(graph: &DiGraph<u32, u64>) -> bool {
/*
checks if there is a path from every node to every other
node when all the edges are converted from directed to undirected
Expand Down Expand Up @@ -280,10 +280,10 @@ impl RankedChoiceVoteTrie {
}

fn find_condorcet_ranked_pairs_weakest(
&self, candidate_vote_counts: &HashMap<u16, u64>,
ranked_pairs_map: &HashMap<(u16, u16), u64>,
lowest_vote_candidates: Vec<u16>
) -> Vec<u16> {
&self, candidate_vote_counts: &HashMap<u32, u64>,
ranked_pairs_map: &HashMap<(u32, u32), u64>,
lowest_vote_candidates: Vec<u32>
) -> Vec<u32> {
println!("CC_PRE_RANK_FILTER {:?}", candidate_vote_counts);
println!("CC_PAIRS_MAP {:?}", ranked_pairs_map);
let mut vote_counts: Vec<u64> =
Expand All @@ -305,7 +305,7 @@ impl RankedChoiceVoteTrie {

// find candidates with less than or equal to the
// second-lowest number of effective votes
let mut weak_candidates: Vec<u16> = Vec::new();
let mut weak_candidates: Vec<u32> = Vec::new();
for (candidate, num_votes) in candidate_vote_counts {
if *num_votes <= vote_threshold {
weak_candidates.push(*candidate);
Expand All @@ -324,25 +324,25 @@ impl RankedChoiceVoteTrie {
}

fn find_ranked_pairs_weakest(
&self, candidates: Vec<u16>,
ranked_pairs_map: &HashMap<(u16, u16), u64>
) -> (Vec<u16>, bool) {
&self, candidates: Vec<u32>,
ranked_pairs_map: &HashMap<(u32, u32), u64>
) -> (Vec<u32>, bool) {
/*
Finds the candidates that perform the worst in pairwise
head-to-head comparison.
Returns the worst performing candidates, and whether it was possible
to construct a preference graph
*/
let mut graph = DiGraph::<u16, u64>::new();
let mut node_map = HashMap::<u16, NodeIndex>::new();
let mut graph = DiGraph::<u32, u64>::new();
let mut node_map = HashMap::<u32, NodeIndex>::new();

/*
Determines whether candidate1 is preferred over candidate2 overall,
or vice versa, or there is no net preference between the two.
Also returns the net number of votes along said overall preference
*/
let get_preference = |
candidate1: u16, candidate2: u16
candidate1: u32, candidate2: u32
| -> (PairPreferences, u64) {
let preferred_over_votes =
ranked_pairs_map.get(&(candidate1, candidate2))
Expand Down Expand Up @@ -376,9 +376,9 @@ impl RankedChoiceVoteTrie {
};

fn get_or_create_node (
graph: &mut DiGraph<u16, u64>,
node_map: &mut HashMap<u16, NodeIndex>,
candidate: u16
graph: &mut DiGraph<u32, u64>,
node_map: &mut HashMap<u32, NodeIndex>,
candidate: u32
) -> NodeIndex {
// println!("NODE_MAP_PRE {:?}", (candidate, &node_map, &graph));
let node = match node_map.get(&candidate) {
Expand Down Expand Up @@ -449,13 +449,13 @@ impl RankedChoiceVoteTrie {
(weakest_candidates, true)
}

fn find_dowdall_weakest(&self, candidates: Vec<u16>) -> Vec<u16> {
fn find_dowdall_weakest(&self, candidates: Vec<u32>) -> Vec<u32> {
/*
returns the subset of candidates from the input candidates vector
that score the lowest according the dowdall scoring criteria
*/
let mut min_score = f32::MAX;
let mut weakest_candidates: Vec<u16> = Vec::new();
let mut weakest_candidates: Vec<u32> = Vec::new();

for candidate in &candidates {
let score = self.dowdall_score_map.get(candidate)
Expand All @@ -474,7 +474,7 @@ impl RankedChoiceVoteTrie {
weakest_candidates
}

pub fn run_election(&self, votes: Vec<RankedVote>) -> Option<u16> {
pub fn run_election(&self, votes: Vec<RankedVote>) -> Option<u32> {
let mut rcv = RankedChoiceVoteTrie {
root: Default::default(),
dowdall_score_map: Default::default(),
Expand All @@ -486,9 +486,9 @@ impl RankedChoiceVoteTrie {
}

fn build_ranked_pairs_map(
node: &TrieNode, search_path: &mut Vec<u16>,
ranked_pairs_map: &mut HashMap<(u16, u16), u64>,
unique_candidates: &HashSet<u16>
node: &TrieNode, search_path: &mut Vec<u32>,
ranked_pairs_map: &mut HashMap<(u32, u32), u64>,
unique_candidates: &HashSet<u32>
) {
let kv_pairs_vec: Vec<(&VoteValues, &TrieNode)> =
node.children.iter().map(|(vote_value, node)| {
Expand Down Expand Up @@ -527,7 +527,7 @@ impl RankedChoiceVoteTrie {
// println!("UNIQUE {:?}", unique_candidates);
// println!("TERMINATE {:?}", (&search_path, terminating_votes));
// candidates who weren't explicitly listed in current vote path
let search_path: &Vec<u16> = search_path;
let search_path: &Vec<u32> = search_path;
let mut unspecified_candidates = unique_candidates.clone();
for candidate in search_path {
unspecified_candidates.remove(candidate);
Expand All @@ -544,11 +544,11 @@ impl RankedChoiceVoteTrie {
}
}

pub fn determine_winner(&self) -> Option<u16> {
pub fn determine_winner(&self) -> Option<u32> {
// println!("RUN_ELECTION_START");
let mut candidate_vote_counts: HashMap<u16, u64> = HashMap::new();
let mut candidate_vote_counts: HashMap<u32, u64> = HashMap::new();
let mut frontier_nodes:
HashMap<u16, Vec<&TrieNode>> = HashMap::new();
HashMap<u32, Vec<&TrieNode>> = HashMap::new();
// total number of voters (who have no abstained from vote)
let mut effective_total_votes: u64 = 0;
// total number of votes that go to candidates
Expand All @@ -572,7 +572,7 @@ impl RankedChoiceVoteTrie {
};
}

let mut ranked_pairs_map: HashMap<(u16, u16), u64> = HashMap::new();
let mut ranked_pairs_map: HashMap<(u32, u32), u64> = HashMap::new();
let strategy = self.elimination_strategy;
if
(strategy == EliminationStrategies::RankedPairs) ||
Expand Down Expand Up @@ -601,7 +601,7 @@ impl RankedChoiceVoteTrie {
}

// find candidates with the lowest number of effective votes
let mut lowest_vote_candidates: Vec<u16> = Vec::new();
let mut lowest_vote_candidates: Vec<u32> = Vec::new();
for (candidate, num_votes) in &candidate_vote_counts {
if *num_votes == min_candidate_votes {
lowest_vote_candidates.push(*candidate);
Expand Down
38 changes: 22 additions & 16 deletions src/vote.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashSet;
use std::fmt;
use petgraph::matrix_graph::Zero;

#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum SpecialVotes {
Expand All @@ -9,7 +10,7 @@ pub enum SpecialVotes {

#[derive(Clone, Copy, Eq, PartialEq, Hash)]
pub enum VoteValues {
Candidate(u16),
Candidate(u32),
SpecialVote(SpecialVotes)
}

Expand Down Expand Up @@ -37,10 +38,12 @@ impl fmt::Display for VoteErrors {
}

impl VoteValues {
pub fn to_int(self) -> i32 {
pub fn to_int(self) -> i64 {
match self {
VoteValues::Candidate(choice) => { i32::from(choice) }
VoteValues::SpecialVote(special_vote) => { special_vote.to_int() }
VoteValues::Candidate(choice) => { i64::from(choice) }
VoteValues::SpecialVote(special_vote) => {
i64::from(special_vote.to_int())
}
}
}

Expand All @@ -50,7 +53,7 @@ impl VoteValues {
return Ok(VoteValues::SpecialVote(special_vote));
}

let cast_result = u16::try_from(raw_value);
let cast_result = u32::try_from(raw_value);

match cast_result {
Err(_) => { Err(VoteErrors::InvalidCastToCandidate) },
Expand All @@ -77,7 +80,7 @@ impl SpecialVotes {
}

pub struct RankedVote {
rankings: Vec<u16>,
rankings: Vec<u32>,
special_vote: Option<SpecialVotes>
}

Expand Down Expand Up @@ -128,7 +131,7 @@ impl RankedVote {
}

pub fn from_candidates(
candidates: &[u16]
candidates: &[u32]
) -> Result<RankedVote, VoteErrors> {
Self::from_vector(
&candidates.iter().map(|x| *x as i32).collect()
Expand All @@ -140,7 +143,7 @@ impl RankedVote {
raw_ranked_vote: &Vec<i32>
) -> Result<RankedVote, VoteErrors> {
// println!("INSERT {:?}", raw_rankings);
let mut candidates: Vec<u16> = Vec::new();
let mut candidates: Vec<u32> = Vec::new();
let mut special_vote_value: Option<SpecialVotes> = None;
let mut unique_values = HashSet::new();

Expand Down Expand Up @@ -169,8 +172,11 @@ impl RankedVote {
}
}
} else {
assert!(raw_ranked_vote_value.is_positive());
let cast_result = u16::try_from(*raw_ranked_vote_value);
assert!(
raw_ranked_vote_value.is_positive() ||
raw_ranked_vote_value.is_zero()
);
let cast_result = u32::try_from(*raw_ranked_vote_value);
match cast_result {
Ok(candidate) => { candidates.push(candidate) }
Err(_) => {
Expand All @@ -190,20 +196,20 @@ impl RankedVote {
})
}

pub fn to_vector(&self) -> Vec<i32> {
let mut all_rankings: Vec<i32> = Vec::new();
pub fn to_vector(&self) -> Vec<i64> {
let mut all_rankings: Vec<i64> = Vec::new();
for ranking in &self.rankings {
all_rankings.push(i32::from(*ranking));
all_rankings.push(i64::from(*ranking));
}
if let Some(special_vote) = &self.special_vote {
all_rankings.push(special_vote.to_int())
all_rankings.push(i64::from(special_vote.to_int()))
}
all_rankings
}
}

pub struct VoteStructIterator<'a> {
rankings_iter: std::slice::Iter<'a, u16>,
rankings_iter: std::slice::Iter<'a, u32>,
special_vote: Option<&'a SpecialVotes>,
}

Expand Down Expand Up @@ -293,7 +299,7 @@ mod tests {
let raw_ranked_vote = vec![1, 2, 6, 3];
assert_eq!(
RankedVote::from_vector(&raw_ranked_vote).unwrap().to_vector(),
raw_ranked_vote
raw_ranked_vote.iter().map(|&x| x as i64).collect::<Vec<i64>>()
)
}
}
8 changes: 4 additions & 4 deletions tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ fn test_spoiler_vote() {
rcv.set_elimination_strategy(EliminationStrategies::RankedPairs);
let winner = rcv.run_election(votes);
println!("WINNER = {:?}", winner);
assert_eq!(winner, Some(T as u16));
assert_eq!(winner, Some(T as u32));
}

#[test]
Expand All @@ -202,9 +202,9 @@ fn test_condorcet_vote() {
let rcv_vote_type4 = vec![vec![T, B, S]];

fn repeat(num_votes: u64, vote_type: Vec<Vec<i32>>) -> Vec<Vec<i32>> {
return (0..num_votes)
(0..num_votes)
.flat_map(|_| vote_type.clone())
.collect::<Vec<_>>();
.collect::<Vec<_>>()
}

let mut raw_votes: Vec<Vec<i32>> = vec![];
Expand All @@ -218,5 +218,5 @@ fn test_condorcet_vote() {
rcv.set_elimination_strategy(EliminationStrategies::CondorcetRankedPairs);
let winner = rcv.run_election(votes);
println!("WINNER = {:?}", winner);
assert_eq!(winner, Some(B as u16));
assert_eq!(winner, Some(B as u32));
}

0 comments on commit a62b6cd

Please sign in to comment.