Precompute Ballot::has_equal_rankings at parse time

Before: 5.3943 +- 0.0287
After: 5.27661 +- 0.00472
This commit is contained in:
RunasSudo 2022-08-25 21:49:50 +10:00
parent 254c04b574
commit 6bb127a124
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
3 changed files with 23 additions and 10 deletions

View File

@ -71,6 +71,7 @@ impl<N: Number> Election<N> {
let new_ballot = Ballot { let new_ballot = Ballot {
orig_value: N::one(), orig_value: N::one(),
preferences: ballot.preferences.clone(), preferences: ballot.preferences.clone(),
has_equal_rankings: ballot.has_equal_rankings,
}; };
normalised_ballots.push(new_ballot); normalised_ballots.push(new_ballot);
n += &one; n += &one;
@ -81,7 +82,7 @@ impl<N: Number> Election<N> {
/// Convert ballots with equal rankings to strict-preference "minivoters" /// Convert ballots with equal rankings to strict-preference "minivoters"
pub fn realise_equal_rankings(&mut self) { pub fn realise_equal_rankings(&mut self) {
if !self.ballots.iter().any(|b| b.has_equal_rankings()) { if !self.ballots.iter().any(|b| b.has_equal_rankings) {
// No equal rankings // No equal rankings
return; return;
} }
@ -582,17 +583,14 @@ pub struct Ballot<N> {
pub orig_value: N, pub orig_value: N,
/// Indexes of candidates preferenced at each level on the ballot /// Indexes of candidates preferenced at each level on the ballot
pub preferences: Vec<Vec<usize>>, pub preferences: Vec<Vec<usize>>,
/// Whether multiple candidates are preferenced at the same level
pub has_equal_rankings: bool,
} }
impl<N: Number> Ballot<N> { impl<N: Number> Ballot<N> {
/// Check if this ballot has any equal rankings
pub fn has_equal_rankings(&self) -> bool {
return self.preferences.iter().any(|p| p.len() > 1);
}
/// Convert ballot with equal rankings to strict-preference "minivoters" /// Convert ballot with equal rankings to strict-preference "minivoters"
pub fn realise_equal_rankings_into(self, dest: &mut Vec<Ballot<N>>) { pub fn realise_equal_rankings_into(self, dest: &mut Vec<Ballot<N>>) {
if !self.has_equal_rankings() { if !self.has_equal_rankings {
dest.push(self); dest.push(self);
return; return;
} }
@ -631,7 +629,8 @@ impl<N: Number> Ballot<N> {
for minivoter in minivoters { for minivoter in minivoters {
dest.push(Ballot { dest.push(Ballot {
orig_value: weight_each.clone(), orig_value: weight_each.clone(),
preferences: minivoter preferences: minivoter,
has_equal_rankings: false,
}); });
} }
} }

View File

@ -38,6 +38,9 @@ pub struct BLTParser<N: Number, I: Iterator<Item=char>> {
/// Temporary buffer for parsing ballot values /// Temporary buffer for parsing ballot values
ballot_value_buf: String, ballot_value_buf: String,
/// Whether the current ballot has equal preferences
ballot_has_equal_rankings: bool,
/// Current line number /// Current line number
line_no: u32, line_no: u32,
/// Current column number /// Current column number
@ -139,6 +142,8 @@ impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
/// Parse a ballot /// Parse a ballot
fn ballot(&mut self) -> Result<(), ParseError> { fn ballot(&mut self) -> Result<(), ParseError> {
self.ballot_has_equal_rankings = false;
self.ballot_value()?; self.ballot_value()?;
self.delimiter_not_nl(); self.delimiter_not_nl();
@ -152,6 +157,8 @@ impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
break; break;
} else if self.lookahead() == '=' { } else if self.lookahead() == '=' {
// Equal preference // Equal preference
self.ballot_has_equal_rankings = true;
self.accept(); self.accept();
preferences.last_mut().unwrap().push(self.usize()? - 1); preferences.last_mut().unwrap().push(self.usize()? - 1);
self.delimiter_not_nl(); self.delimiter_not_nl();
@ -167,6 +174,7 @@ impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
let ballot = Ballot { let ballot = Ballot {
orig_value: N::parse(&self.ballot_value_buf), orig_value: N::parse(&self.ballot_value_buf),
preferences, preferences,
has_equal_rankings: self.ballot_has_equal_rankings,
}; };
self.election.ballots.push(ballot); self.election.ballots.push(ballot);
@ -376,6 +384,7 @@ impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
Self { Self {
chars, chars,
ballot_value_buf: String::new(), ballot_value_buf: String::new(),
ballot_has_equal_rankings: false,
line_no: 1, line_no: 1,
col_no: 1, col_no: 1,
num_candidates: 0, num_candidates: 0,

View File

@ -95,6 +95,7 @@ pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequ
ballots.push(Ballot { ballots.push(Ballot {
orig_value: value, orig_value: value,
preferences: vec![], preferences: vec![],
has_equal_rankings: false,
}); });
continue; continue;
} }
@ -102,17 +103,20 @@ pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequ
let mut sorted_preferences = Vec::with_capacity(preferences.len()); let mut sorted_preferences = Vec::with_capacity(preferences.len());
let mut last_ranking = None; let mut last_ranking = None;
let mut has_equal_rankings = false;
for ranking in unique_rankings { for ranking in unique_rankings {
// Filter for preferences at this ranking // Filter for preferences at this ranking
let prefs_this_ranking: Vec<usize> = preferences.iter() let prefs_this_ranking: Vec<usize> = preferences.iter()
.filter_map(|(r, i)| if *r == ranking { Some(**i) } else { None }) .filter_map(|(r, i)| if *r == ranking { Some(**i) } else { None })
.collect(); .collect();
if require_strict_order { if prefs_this_ranking.len() != 1 {
if prefs_this_ranking.len() != 1 { if require_strict_order {
// Duplicate rankings // Duplicate rankings
break; break;
} }
has_equal_rankings = true;
} }
if require_sequential { if require_sequential {
@ -131,6 +135,7 @@ pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequ
ballots.push(Ballot { ballots.push(Ballot {
orig_value: value, orig_value: value,
preferences: sorted_preferences, preferences: sorted_preferences,
has_equal_rankings,
}); });
} }