rust-clippy linting

This commit is contained in:
RunasSudo 2021-10-27 19:52:51 +11:00
parent 69bc30b333
commit 15614a4e8f
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
21 changed files with 192 additions and 213 deletions

View File

@ -248,7 +248,7 @@ fn maybe_load_constraints<N: Number>(election: &mut Election<N>, constraints: &O
if let Some(c) = constraints { if let Some(c) = constraints {
let file = File::open(c).expect("IO Error"); let file = File::open(c).expect("IO Error");
let lines = io::BufReader::new(file).lines(); let lines = io::BufReader::new(file).lines();
election.constraints = Some(Constraints::from_con(lines.map(|r| r.expect("IO Error").to_string()).into_iter())); election.constraints = Some(Constraints::from_con(lines.map(|r| r.expect("IO Error"))));
} }
} }
@ -267,7 +267,7 @@ where
cmd_opts.round_votes, cmd_opts.round_votes,
cmd_opts.round_quota, cmd_opts.round_quota,
cmd_opts.sum_surplus_transfers.into(), cmd_opts.sum_surplus_transfers.into(),
cmd_opts.meek_surplus_tolerance.into(), cmd_opts.meek_surplus_tolerance,
cmd_opts.normalise_ballots, cmd_opts.normalise_ballots,
cmd_opts.quota.into(), cmd_opts.quota.into(),
cmd_opts.quota_criterion.into(), cmd_opts.quota_criterion.into(),
@ -324,7 +324,7 @@ where
let total_ballots = election.ballots.iter().fold(N::zero(), |acc, b| { acc + &b.orig_value }); let total_ballots = election.ballots.iter().fold(N::zero(), |acc, b| { acc + &b.orig_value });
print!("Count computed by OpenTally (revision {}). Read {:.0} ballots from \"{}\" for election \"{}\". There are {} candidates for {} vacancies. ", crate::VERSION, total_ballots, filename, election.name, election.candidates.len(), election.seats); print!("Count computed by OpenTally (revision {}). Read {:.0} ballots from \"{}\" for election \"{}\". There are {} candidates for {} vacancies. ", crate::VERSION, total_ballots, filename, election.name, election.candidates.len(), election.seats);
let opts_str = opts.describe::<N>(); let opts_str = opts.describe::<N>();
if opts_str.len() > 0 { if !opts_str.is_empty() {
println!("Counting using options \"{}\".", opts_str); println!("Counting using options \"{}\".", opts_str);
} else { } else {
println!("Counting using default options."); println!("Counting using default options.");

View File

@ -40,47 +40,11 @@ impl Constraints {
let mut constraints = Constraints(Vec::new()); let mut constraints = Constraints(Vec::new());
for line in lines { for line in lines {
let mut bits = line.split(" ").peekable(); let mut bits = line.split(' ').peekable();
// Read constraint category // Read constraint category and group
let mut constraint_name = String::new(); let constraint_name = read_quoted_string(&mut bits);
let x = bits.next().expect("Syntax Error"); let group_name = read_quoted_string(&mut bits);
if x.starts_with('"') {
if x.ends_with('"') {
constraint_name.push_str(&x[1..x.len()-1]);
} else {
constraint_name.push_str(&x[1..]);
while !bits.peek().expect("Syntax Error").ends_with('"') {
constraint_name.push_str(" ");
constraint_name.push_str(bits.next().unwrap());
}
let x = bits.next().unwrap();
constraint_name.push_str(" ");
constraint_name.push_str(&x[..x.len()-1]);
}
} else {
constraint_name.push_str(x);
}
// Read constraint group
let mut group_name = String::new();
let x = bits.next().expect("Syntax Error");
if x.starts_with('"') {
if x.ends_with('"') {
group_name.push_str(&x[1..x.len()-1]);
} else {
group_name.push_str(&x[1..]);
while !bits.peek().expect("Syntax Error").ends_with('"') {
group_name.push_str(" ");
group_name.push_str(bits.next().unwrap());
}
let x = bits.next().unwrap();
group_name.push_str(" ");
group_name.push_str(&x[..x.len()-1]);
}
} else {
group_name.push_str(x);
}
// Read min, max // Read min, max
let min: usize = bits.next().expect("Syntax Error").parse().expect("Syntax Error"); let min: usize = bits.next().expect("Syntax Error").parse().expect("Syntax Error");
@ -93,7 +57,7 @@ impl Constraints {
} }
// Insert constraint/group // Insert constraint/group
let constraint = match constraints.0.iter_mut().filter(|c| c.name == constraint_name).next() { let constraint = match constraints.0.iter_mut().find(|c| c.name == constraint_name) {
Some(c) => { c } Some(c) => { c }
None => { None => {
let c = Constraint { let c = Constraint {
@ -111,9 +75,9 @@ impl Constraints {
constraint.groups.push(ConstrainedGroup { constraint.groups.push(ConstrainedGroup {
name: group_name, name: group_name,
candidates: candidates, candidates,
min: min, min,
max: max, max,
}); });
} }
@ -123,6 +87,39 @@ impl Constraints {
} }
} }
/// Read an optionally quoted string, returning the string without quotes
fn read_quoted_string<'a, I: Iterator<Item=&'a str>>(bits: &mut I) -> String {
let x = bits.next().expect("Syntax Error");
if let Some(x1) = x.strip_prefix('"') {
if let Some(x2) = x.strip_suffix('"') {
// Complete string
return String::from(x2);
} else {
// Incomplete string
let mut result = String::from(x1);
// Read until matching "
loop {
let x = bits.next().expect("Syntax Error");
result.push(' ');
if let Some(x1) = x.strip_suffix('"') {
// End of string
result.push_str(x1);
break;
} else {
// Middle of string
result.push_str(x);
}
}
return result;
}
} else {
// Unquoted string
return String::from(x);
}
}
/// A single dimension of constraint /// A single dimension of constraint
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))] #[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))]
@ -264,7 +261,7 @@ impl ConstraintMatrix {
self.0[&idx].elected = 0; self.0[&idx].elected = 0;
// The axis along which to sum - if multiple, just pick the first, as these should agree // The axis along which to sum - if multiple, just pick the first, as these should agree
let zero_axis = (0..idx.ndim()).filter(|d| idx[*d] == 0).next().unwrap(); let zero_axis = (0..idx.ndim()).find(|d| idx[*d] == 0).unwrap();
// Traverse along the axis and sum the candidates // Traverse along the axis and sum the candidates
let mut idx2 = idx.clone(); let mut idx2 = idx.clone();
@ -374,87 +371,87 @@ impl fmt::Display for ConstraintMatrix {
// TODO: >2 dimensions // TODO: >2 dimensions
if shape.len() == 1 { if shape.len() == 1 {
result.push_str("+"); result.push('+');
for _ in 0..shape[0] { for _ in 0..shape[0] {
result.push_str("-------------+"); result.push_str("-------------+");
} }
result.push_str("\n"); result.push('\n');
result.push_str("|"); result.push('|');
for x in 0..shape[0] { for x in 0..shape[0] {
result.push_str(&format!(" Elected: {:2}", self[&[x]].elected)); result.push_str(&format!(" Elected: {:2}", self[&[x]].elected));
result.push_str(if x == 0 { "" } else { " |" }); result.push_str(if x == 0 { "" } else { " |" });
} }
result.push_str("\n"); result.push('\n');
result.push_str("|"); result.push('|');
for x in 0..shape[0] { for x in 0..shape[0] {
result.push_str(&format!(" Min: {:2}", self[&[x]].min)); result.push_str(&format!(" Min: {:2}", self[&[x]].min));
result.push_str(if x == 0 { "" } else { " |" }); result.push_str(if x == 0 { "" } else { " |" });
} }
result.push_str("\n"); result.push('\n');
result.push_str("|"); result.push('|');
for x in 0..shape[0] { for x in 0..shape[0] {
result.push_str(&format!(" Max: {:2}", self[&[x]].max)); result.push_str(&format!(" Max: {:2}", self[&[x]].max));
result.push_str(if x == 0 { "" } else { " |" }); result.push_str(if x == 0 { "" } else { " |" });
} }
result.push_str("\n"); result.push('\n');
result.push_str("|"); result.push('|');
for x in 0..shape[0] { for x in 0..shape[0] {
result.push_str(&format!(" Cands: {:2}", self[&[x]].cands)); result.push_str(&format!(" Cands: {:2}", self[&[x]].cands));
result.push_str(if x == 0 { "" } else { " |" }); result.push_str(if x == 0 { "" } else { " |" });
} }
result.push_str("\n"); result.push('\n');
result.push_str("+"); result.push('+');
for _ in 0..shape[0] { for _ in 0..shape[0] {
result.push_str("-------------+"); result.push_str("-------------+");
} }
result.push_str("\n"); result.push('\n');
} else if shape.len() == 2 { } else if shape.len() == 2 {
for y in 0..shape[1] { for y in 0..shape[1] {
result.push_str("+"); result.push('+');
for _ in 0..shape[0] { for _ in 0..shape[0] {
result.push_str(if y == 1 { "=============+" } else { "-------------+" }); result.push_str(if y == 1 { "=============+" } else { "-------------+" });
} }
result.push_str("\n"); result.push('\n');
result.push_str("|"); result.push('|');
for x in 0..shape[0] { for x in 0..shape[0] {
result.push_str(&format!(" Elected: {:2}", self[&[x, y]].elected)); result.push_str(&format!(" Elected: {:2}", self[&[x, y]].elected));
result.push_str(if x == 0 { "" } else { " |" }); result.push_str(if x == 0 { "" } else { " |" });
} }
result.push_str("\n"); result.push('\n');
result.push_str("|"); result.push('|');
for x in 0..shape[0] { for x in 0..shape[0] {
result.push_str(&format!(" Min: {:2}", self[&[x, y]].min)); result.push_str(&format!(" Min: {:2}", self[&[x, y]].min));
result.push_str(if x == 0 { "" } else { " |" }); result.push_str(if x == 0 { "" } else { " |" });
} }
result.push_str("\n"); result.push('\n');
result.push_str("|"); result.push('|');
for x in 0..shape[0] { for x in 0..shape[0] {
result.push_str(&format!(" Max: {:2}", self[&[x, y]].max)); result.push_str(&format!(" Max: {:2}", self[&[x, y]].max));
result.push_str(if x == 0 { "" } else { " |" }); result.push_str(if x == 0 { "" } else { " |" });
} }
result.push_str("\n"); result.push('\n');
result.push_str("|"); result.push('|');
for x in 0..shape[0] { for x in 0..shape[0] {
result.push_str(&format!(" Cands: {:2}", self[&[x, y]].cands)); result.push_str(&format!(" Cands: {:2}", self[&[x, y]].cands));
result.push_str(if x == 0 { "" } else { " |" }); result.push_str(if x == 0 { "" } else { " |" });
} }
result.push_str("\n"); result.push('\n');
} }
result.push_str("+"); result.push('+');
for _ in 0..shape[0] { for _ in 0..shape[0] {
result.push_str("-------------+"); result.push_str("-------------+");
} }
result.push_str("\n"); result.push('\n');
} else { } else {
todo!(); todo!();
} }
@ -499,7 +496,7 @@ fn candidates_in_constraint_cell<'a, N: Number>(election: &'a Election<N>, candi
} }
/// Clone and update the constraints matrix, with the state of the given candidates set to candidate_state /// Clone and update the constraints matrix, with the state of the given candidates set to candidate_state
pub fn try_constraints<N: Number>(state: &CountState<N>, candidates: &Vec<&Candidate>, candidate_state: CandidateState) -> Result<(), ConstraintError> { pub fn try_constraints<N: Number>(state: &CountState<N>, candidates: &[&Candidate], candidate_state: CandidateState) -> Result<(), ConstraintError> {
if state.constraint_matrix.is_none() { if state.constraint_matrix.is_none() {
return Ok(()); return Ok(());
} }
@ -507,11 +504,11 @@ pub fn try_constraints<N: Number>(state: &CountState<N>, candidates: &Vec<&Candi
let mut trial_candidates = state.candidates.clone(); // TODO: Can probably be optimised by not cloning CountCard::parcels let mut trial_candidates = state.candidates.clone(); // TODO: Can probably be optimised by not cloning CountCard::parcels
for candidate in candidates { for candidate in candidates {
trial_candidates.get_mut(candidate).unwrap().state = candidate_state.clone(); trial_candidates.get_mut(candidate).unwrap().state = candidate_state;
} }
// Update cands/elected // Update cands/elected
cm.update_from_state(&state.election, &trial_candidates); cm.update_from_state(state.election, &trial_candidates);
cm.recount_cands(); cm.recount_cands();
// Iterate for stable state // Iterate for stable state
@ -528,7 +525,7 @@ pub fn update_constraints<N: Number>(state: &mut CountState<N>, opts: &STVOption
let cm = state.constraint_matrix.as_mut().unwrap(); let cm = state.constraint_matrix.as_mut().unwrap();
// Update cands/elected // Update cands/elected
cm.update_from_state(&state.election, &state.candidates); cm.update_from_state(state.election, &state.candidates);
cm.recount_cands(); cm.recount_cands();
// Iterate for stable state // Iterate for stable state

View File

@ -149,7 +149,7 @@ impl<'a, N: Number> CountState<'a, N> {
/// Construct a new blank [CountState] for the given [Election] /// Construct a new blank [CountState] for the given [Election]
pub fn new(election: &'a Election<N>) -> Self { pub fn new(election: &'a Election<N>) -> Self {
let mut state = CountState { let mut state = CountState {
election: &election, election,
candidates: HashMap::new(), candidates: HashMap::new(),
exhausted: CountCard::new(), exhausted: CountCard::new(),
loss_fraction: CountCard::new(), loss_fraction: CountCard::new(),
@ -194,7 +194,7 @@ impl<'a, N: Number> CountState<'a, N> {
} }
// Fill in grand total, etc. // Fill in grand total, etc.
cm.update_from_state(&state.election, &state.candidates); cm.update_from_state(state.election, &state.candidates);
cm.init(); cm.init();
//println!("{}", cm); //println!("{}", cm);

View File

@ -16,6 +16,7 @@
*/ */
#![warn(missing_docs)] #![warn(missing_docs)]
#![allow(clippy::collapsible_else_if, clippy::collapsible_if, clippy::comparison_chain, clippy::derive_ord_xor_partial_ord, clippy::needless_bool, clippy::needless_return, clippy::new_without_default, clippy::too_many_arguments)]
//! Open source counting software for various preferential voting election systems //! Open source counting software for various preferential voting election systems

View File

@ -27,10 +27,10 @@ impl<'a> Logger<'a> {
/// If consecutive smart log entries have the same templates, they will be merged /// If consecutive smart log entries have the same templates, they will be merged
pub fn log(&mut self, entry: LogEntry<'a>) { pub fn log(&mut self, entry: LogEntry<'a>) {
if let LogEntry::Smart(mut smart) = entry { if let LogEntry::Smart(mut smart) = entry {
if self.entries.len() > 0 { if !self.entries.is_empty() {
if let LogEntry::Smart(last_smart) = self.entries.last_mut().unwrap() { if let LogEntry::Smart(last_smart) = self.entries.last_mut().unwrap() {
if last_smart.template1 == smart.template1 && last_smart.template2 == smart.template2 { if last_smart.template1 == smart.template1 && last_smart.template2 == smart.template2 {
&last_smart.data.append(&mut smart.data); last_smart.data.append(&mut smart.data);
} else { } else {
self.entries.push(LogEntry::Smart(smart)); self.entries.push(LogEntry::Smart(smart));
} }
@ -55,9 +55,9 @@ impl<'a> Logger<'a> {
/// If consecutive smart log entries have the same templates, they will be merged /// If consecutive smart log entries have the same templates, they will be merged
pub fn log_smart(&mut self, template1: &'a str, template2: &'a str, data: Vec<&'a str>) { pub fn log_smart(&mut self, template1: &'a str, template2: &'a str, data: Vec<&'a str>) {
self.log(LogEntry::Smart(SmartLogEntry { self.log(LogEntry::Smart(SmartLogEntry {
template1: template1, template1,
template2: template2, template2,
data: data, data,
})); }));
} }
@ -88,7 +88,7 @@ pub struct SmartLogEntry<'a> {
impl<'a> SmartLogEntry<'a> { impl<'a> SmartLogEntry<'a> {
/// Render the [SmartLogEntry] to a [String] /// Render the [SmartLogEntry] to a [String]
pub fn render(&self) -> String { pub fn render(&self) -> String {
if self.data.len() == 0 { if self.data.is_empty() {
panic!("Attempted to format smart log entry with no data"); panic!("Attempted to format smart log entry with no data");
} else if self.data.len() == 1 { } else if self.data.len() == 1 {
return String::from(self.template1).replace("{}", self.data.first().unwrap()); return String::from(self.template1).replace("{}", self.data.first().unwrap());
@ -99,6 +99,7 @@ impl<'a> SmartLogEntry<'a> {
} }
/// Join the given strings, with commas and terminal "and" /// Join the given strings, with commas and terminal "and"
#[allow(clippy::ptr_arg)]
pub fn smart_join(data: &Vec<&str>) -> String { pub fn smart_join(data: &Vec<&str>) -> String {
return format!("{} and {}", data[0..data.len()-1].join(", "), data.last().unwrap()); return format!("{} and {}", data[0..data.len()-1].join(", "), data.last().unwrap());
} }

View File

@ -15,6 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#![allow(clippy::needless_return)]
use opentally::cli; use opentally::cli;
use clap::Clap; use clap::Clap;
@ -27,10 +29,11 @@ struct Opts {
command: Command, command: Command,
} }
#[allow(clippy::large_enum_variant)]
#[derive(Clap)] #[derive(Clap)]
enum Command { enum Command {
Convert(cli::convert::SubcmdOptions), Convert(cli::convert::SubcmdOptions),
STV(cli::stv::SubcmdOptions), Stv(cli::stv::SubcmdOptions),
} }
fn main() { fn main() {
@ -48,6 +51,6 @@ fn main_() -> Result<(), i32> {
return match opts.command { return match opts.command {
Command::Convert(cmd_opts) => cli::convert::main(cmd_opts), Command::Convert(cmd_opts) => cli::convert::main(cmd_opts),
Command::STV(cmd_opts) => cli::stv::main(cmd_opts), Command::Stv(cmd_opts) => cli::stv::main(cmd_opts),
}; };
} }

View File

@ -149,7 +149,7 @@ impl<N: Number, D: Fallible + ?Sized> DeserializeWith<Archived<String>, N, D> fo
impl<N: Number, D: Fallible + ?Sized> DeserializeWith<Archived<String>, Option<N>, D> for SerializedOptionNumber where Archived<String>: Deserialize<String, D> { impl<N: Number, D: Fallible + ?Sized> DeserializeWith<Archived<String>, Option<N>, D> for SerializedOptionNumber where Archived<String>: Deserialize<String, D> {
fn deserialize_with(field: &Archived<String>, deserializer: &mut D) -> Result<Option<N>, D::Error> { fn deserialize_with(field: &Archived<String>, deserializer: &mut D) -> Result<Option<N>, D::Error> {
let s = field.deserialize(deserializer)?; let s = field.deserialize(deserializer)?;
if s.len() == 0 { if s.is_empty() {
return Ok(None); return Ok(None);
} else { } else {
return Ok(Some(N::parse(&s))); return Ok(Some(N::parse(&s)));

View File

@ -32,7 +32,7 @@ pub fn parse_path<P: AsRef<Path>, N: Number>(path: P) -> Election<N> {
/// Parse the given BIN file /// Parse the given BIN file
pub fn parse_bytes<N: Number>(content: &[u8]) -> Election<N> { pub fn parse_bytes<N: Number>(content: &[u8]) -> Election<N> {
let archived = unsafe { let archived = unsafe {
archived_root::<Election<N>>(&content) archived_root::<Election<N>>(content)
}; };
return archived.deserialize(&mut Infallible).unwrap(); return archived.deserialize(&mut Infallible).unwrap();
} }

View File

@ -166,7 +166,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, preferences,
}; };
self.election.ballots.push(ballot); self.election.ballots.push(ballot);
@ -218,13 +218,11 @@ impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
/// Parse a quoted or raw string /// Parse a quoted or raw string
fn string(&mut self) -> Result<String, ParseError> { fn string(&mut self) -> Result<String, ParseError> {
match self.quoted_string() { if let Ok(s) = self.quoted_string() {
Ok(s) => { return Ok(s); } return Ok(s);
Err(_) => {}
} }
match self.raw_string() { if let Ok(s) = self.raw_string() {
Ok(s) => { return Ok(s); } return Ok(s);
Err(_) => {}
} }
return Err(ParseError::Expected(self.line_no, self.col_no, self.lookahead(), "string")); return Err(ParseError::Expected(self.line_no, self.col_no, self.lookahead(), "string"));
} }
@ -396,5 +394,5 @@ pub fn parse_iterator<I: Iterator<Item=char>, N: Number>(input: Peekable<I>) ->
pub fn parse_path<P: AsRef<Path>, N: Number>(path: P) -> Result<Election<N>, ParseError> { pub fn parse_path<P: AsRef<Path>, N: Number>(path: P) -> Result<Election<N>, ParseError> {
let mut reader = BufReader::new(File::open(path).expect("IO Error")); let mut reader = BufReader::new(File::open(path).expect("IO Error"));
let chars = reader.chars().map(|r| r.expect("IO Error")).peekable(); let chars = reader.chars().map(|r| r.expect("IO Error")).peekable();
return Ok(parse_iterator(chars)?); return parse_iterator(chars);
} }

View File

@ -61,7 +61,7 @@ pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequ
match col_map.get(&csv_col) { match col_map.get(&csv_col) {
Some(cand_index) => { Some(cand_index) => {
// Preference // Preference
if preference.len() == 0 || preference == "-" { if preference.is_empty() || preference == "-" {
continue; continue;
} }
@ -85,10 +85,10 @@ pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequ
// Sort by ranking // Sort by ranking
let mut unique_rankings: Vec<usize> = preferences.iter().map(|(r, _)| *r).unique().collect(); let mut unique_rankings: Vec<usize> = preferences.iter().map(|(r, _)| *r).unique().collect();
unique_rankings.sort(); unique_rankings.sort_unstable();
if require_1 { if require_1 {
if unique_rankings.first().map(|r| *r == 1).unwrap_or(false) == false { if !unique_rankings.first().map(|r| *r == 1).unwrap_or(false) {
// No #1 preference // No #1 preference
ballots.push(Ballot { ballots.push(Ballot {
orig_value: value, orig_value: value,
@ -135,9 +135,9 @@ pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequ
return Ok(Election { return Ok(Election {
name: String::new(), name: String::new(),
seats: 0, seats: 0,
candidates: candidates, candidates,
withdrawn_candidates: Vec::new(), withdrawn_candidates: Vec::new(),
ballots: ballots, ballots,
total_votes: None, total_votes: None,
constraints: None, constraints: None,
}); });

View File

@ -28,7 +28,7 @@ impl<'r> SHARandom<'r> {
/// Return a new [SHARandom] with the given seed /// Return a new [SHARandom] with the given seed
pub fn new(seed: &'r str) -> Self { pub fn new(seed: &'r str) -> Self {
Self { Self {
seed: seed, seed,
counter: 0, counter: 0,
} }
} }

View File

@ -157,8 +157,8 @@ where
} }
match opts.surplus { match opts.surplus {
SurplusMethod::WIG | SurplusMethod::UIG | SurplusMethod::EG => { distribute_surplus(state, &opts, elected_candidate); } SurplusMethod::WIG | SurplusMethod::UIG | SurplusMethod::EG => { distribute_surplus(state, opts, elected_candidate); }
SurplusMethod::IHare | SurplusMethod::Hare => { sample::distribute_surplus(state, &opts, elected_candidate)?; } SurplusMethod::IHare | SurplusMethod::Hare => { sample::distribute_surplus(state, opts, elected_candidate)?; }
_ => unreachable!() _ => unreachable!()
} }
@ -209,7 +209,7 @@ where
for<'r> &'r N: ops::Div<&'r N, Output=N>, for<'r> &'r N: ops::Div<&'r N, Output=N>,
for<'r> &'r N: ops::Neg<Output=N> for<'r> &'r N: ops::Neg<Output=N>
{ {
state.title = StageKind::SurplusOf(&elected_candidate); state.title = StageKind::SurplusOf(elected_candidate);
state.logger.log_literal(format!("Surplus of {} distributed.", elected_candidate.name)); state.logger.log_literal(format!("Surplus of {} distributed.", elected_candidate.name));
let count_card = &state.candidates[elected_candidate]; let count_card = &state.candidates[elected_candidate];
@ -356,7 +356,7 @@ where
// Transfer exhausted votes // Transfer exhausted votes
let parcel = Parcel { let parcel = Parcel {
votes: result.exhausted.votes, votes: result.exhausted.votes,
value_fraction: value_fraction, // TODO: Reweight exhausted votes value_fraction, // TODO: Reweight exhausted votes
source_order: state.num_elected + state.num_excluded, source_order: state.num_elected + state.num_excluded,
}; };
state.exhausted.parcels.push(parcel); state.exhausted.parcels.push(parcel);
@ -382,6 +382,7 @@ where
} }
/// Perform one stage of a candidate exclusion according to the Gregory method, based on [STVOptions::exclusion] /// Perform one stage of a candidate exclusion according to the Gregory method, based on [STVOptions::exclusion]
#[allow(clippy::branches_sharing_code)]
pub fn exclude_candidates<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, excluded_candidates: Vec<&'a Candidate>) pub fn exclude_candidates<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, excluded_candidates: Vec<&'a Candidate>)
where where
for<'r> &'r N: ops::Mul<&'r N, Output=N>, for<'r> &'r N: ops::Mul<&'r N, Output=N>,
@ -479,7 +480,7 @@ where
// Group all votes of one value in single parcel // Group all votes of one value in single parcel
parcels.push(Parcel { parcels.push(Parcel {
votes: votes, votes,
value_fraction: max_value, value_fraction: max_value,
source_order: 0, // source_order is unused in this mode source_order: 0, // source_order is unused in this mode
}); });
@ -564,7 +565,7 @@ where
let mut total_ballots = N::new(); let mut total_ballots = N::new();
let mut total_votes = N::new(); let mut total_votes = N::new();
let value = match parcels.first() { Some(p) => Some(p.value_fraction.clone()), _ => None }; let value = parcels.first().map(|p| p.value_fraction.clone());
let mut transfer_table = TransferTable::new_exclusion( let mut transfer_table = TransferTable::new_exclusion(
state.election.candidates.iter().filter(|c| state.candidates[c].state == CandidateState::Hopeful || state.candidates[c].state == CandidateState::Guarded).collect(), state.election.candidates.iter().filter(|c| state.candidates[c].state == CandidateState::Hopeful || state.candidates[c].state == CandidateState::Guarded).collect(),

View File

@ -42,8 +42,8 @@ impl Table {
} }
/// Render the table as HTML /// Render the table as HTML
pub fn to_string(&self) -> String { pub fn to_html(&self) -> String {
return format!(r#"<table class="transfers">{}</table>"#, self.rows.iter().map(|r| r.to_string()).join("")); return format!(r#"<table class="transfers">{}</table>"#, self.rows.iter().map(|r| r.to_html()).join(""));
} }
} }
@ -62,8 +62,8 @@ impl Row {
} }
/// Render the row as HTML /// Render the row as HTML
fn to_string(&self) -> String { fn to_html(&self) -> String {
return format!(r#"<tr>{}</tr>"#, self.cells.iter().map(|c| c.to_string()).join("")); return format!(r#"<tr>{}</tr>"#, self.cells.iter().map(|c| c.to_html()).join(""));
} }
} }
@ -90,10 +90,10 @@ impl Cell {
if spec.contains("H2") { if spec.contains("H2") {
self.attrs.push(r#"colspan="2""#); self.attrs.push(r#"colspan="2""#);
} }
if spec.contains("c") { if spec.contains('c') {
self.attrs.push(r#"style="text-align:center""#); self.attrs.push(r#"style="text-align:center""#);
} }
if spec.contains("r") { if spec.contains('r') {
self.attrs.push(r#"style="text-align:right""#); self.attrs.push(r#"style="text-align:right""#);
} }
@ -101,7 +101,7 @@ impl Cell {
} }
/// Render the cell as HTML /// Render the cell as HTML
fn to_string(&self) -> String { fn to_html(&self) -> String {
return format!(r#"<{}>{}</td>"#, self.attrs.join(" "), html_escape::encode_text(&self.content)); return format!(r#"<{}>{}</td>"#, self.attrs.join(" "), html_escape::encode_text(&self.content));
} }
} }

View File

@ -136,13 +136,6 @@ impl<'e, N: Number> TransferTable<'e, N> {
if let Some(n) = &self.surpfrac_numer { if let Some(n) = &self.surpfrac_numer {
new_value_fraction *= n; new_value_fraction *= n;
} }
if let Some(n) = &self.surpfrac_denom {
new_value_fraction /= n;
}
// Round if required
if let Some(dps) = opts.round_values {
new_value_fraction.floor_mut(dps);
}
} else { } else {
if let Some(n) = &self.surpfrac_numer { if let Some(n) = &self.surpfrac_numer {
new_value_fraction = n.clone(); new_value_fraction = n.clone();
@ -150,13 +143,15 @@ impl<'e, N: Number> TransferTable<'e, N> {
// Transferred at original value // Transferred at original value
new_value_fraction = column.value_fraction.clone(); new_value_fraction = column.value_fraction.clone();
} }
if let Some(n) = &self.surpfrac_denom { }
new_value_fraction /= n;
} if let Some(n) = &self.surpfrac_denom {
// Round if required new_value_fraction /= n;
if let Some(dps) = opts.round_values { }
new_value_fraction.floor_mut(dps);
} // Round if required
if let Some(dps) = opts.round_values {
new_value_fraction.floor_mut(dps);
} }
} }
@ -300,7 +295,7 @@ impl<'e, N: Number> TransferTable<'e, N> {
let mut table = Table::new(); let mut table = Table::new();
set_table_format(&mut table); set_table_format(&mut table);
let show_transfers_per_ballot = !self.surpfrac.is_none() && opts.sum_surplus_transfers == SumSurplusTransfersMode::PerBallot; let show_transfers_per_ballot = self.surpfrac.is_some() && opts.sum_surplus_transfers == SumSurplusTransfersMode::PerBallot;
let num_cols; let num_cols;
if show_transfers_per_ballot { if show_transfers_per_ballot {
@ -462,7 +457,7 @@ impl<'e, N: Number> TransferTable<'e, N> {
/// Render table as plain text /// Render table as plain text
//#[cfg(not(target_arch = "wasm32"))] //#[cfg(not(target_arch = "wasm32"))]
pub fn render_text(&self, opts: &STVOptions) -> String { pub fn render_text(&self, opts: &STVOptions) -> String {
return self.render(opts).to_string(); return self.render(opts).to_html();
} }
// Render table as HTML // Render table as HTML

View File

@ -54,7 +54,7 @@ impl<'t, N: Number> BallotTree<'t, N> {
} }
/// Descend one level of the [BallotTree] /// Descend one level of the [BallotTree]
fn descend_tree(&mut self, candidates: &'t Vec<Candidate>) { fn descend_tree(&mut self, candidates: &'t [Candidate]) {
let mut next_preferences: HashMap<&Candidate, BallotTree<N>> = HashMap::new(); let mut next_preferences: HashMap<&Candidate, BallotTree<N>> = HashMap::new();
let mut next_exhausted = BallotTree::new(); let mut next_exhausted = BallotTree::new();
@ -111,7 +111,7 @@ where
let mut ballot_tree = BallotTree::new(); let mut ballot_tree = BallotTree::new();
for ballot in state.election.ballots.iter() { for ballot in state.election.ballots.iter() {
ballot_tree.ballots.push(BallotInTree { ballot_tree.ballots.push(BallotInTree {
ballot: ballot, ballot,
up_to_pref: 0, up_to_pref: 0,
}); });
ballot_tree.num_ballots += &ballot.orig_value; ballot_tree.num_ballots += &ballot.orig_value;
@ -155,7 +155,7 @@ where
} }
state.exhausted.votes = N::new(); state.exhausted.votes = N::new();
distribute_recursively(&mut state.candidates, &mut state.exhausted, state.ballot_tree.as_mut().unwrap(), N::one(), &state.election, opts); distribute_recursively(&mut state.candidates, &mut state.exhausted, state.ballot_tree.as_mut().unwrap(), N::one(), state.election, opts);
} }
/// Distribute preferences recursively /// Distribute preferences recursively
@ -166,7 +166,7 @@ where
for<'r> &'r N: ops::Mul<&'r N, Output=N>, for<'r> &'r N: ops::Mul<&'r N, Output=N>,
{ {
// Descend tree if required // Descend tree if required
if let None = tree.next_exhausted { if tree.next_exhausted.is_none() {
tree.descend_tree(&election.candidates); tree.descend_tree(&election.candidates);
} }
@ -211,8 +211,8 @@ where
exhausted.votes += &remaining_multiplier * &tree.next_exhausted.as_ref().unwrap().as_ref().num_ballots; exhausted.votes += &remaining_multiplier * &tree.next_exhausted.as_ref().unwrap().as_ref().num_ballots;
} }
fn recompute_keep_values<'s, N: Number>(state: &mut CountState<'s, N>, opts: &STVOptions, has_surplus: &Vec<&'s Candidate>) { fn recompute_keep_values<'s, N: Number>(state: &mut CountState<'s, N>, opts: &STVOptions, has_surplus: &[&'s Candidate]) {
for candidate in has_surplus.into_iter() { for candidate in has_surplus {
let count_card = state.candidates.get_mut(candidate).unwrap(); let count_card = state.candidates.get_mut(candidate).unwrap();
count_card.keep_value = Some(count_card.keep_value.take().unwrap() * state.quota.as_ref().unwrap() / &count_card.votes); count_card.keep_value = Some(count_card.keep_value.take().unwrap() * state.quota.as_ref().unwrap() / &count_card.votes);
@ -224,7 +224,7 @@ fn recompute_keep_values<'s, N: Number>(state: &mut CountState<'s, N>, opts: &ST
} }
/// Determine if the specified surpluses should be distributed, according to [STVOptions::meek_surplus_tolerance] /// Determine if the specified surpluses should be distributed, according to [STVOptions::meek_surplus_tolerance]
fn should_distribute_surpluses<'a, N: Number>(state: &CountState<'a, N>, has_surplus: &Vec<&'a Candidate>, opts: &STVOptions) -> bool fn should_distribute_surpluses<'a, N: Number>(state: &CountState<'a, N>, has_surplus: &[&'a Candidate], opts: &STVOptions) -> bool
where where
for<'r> &'r N: ops::Sub<&'r N, Output=N>, for<'r> &'r N: ops::Sub<&'r N, Output=N>,
for<'r> &'r N: ops::Div<&'r N, Output=N>, for<'r> &'r N: ops::Div<&'r N, Output=N>,

View File

@ -203,9 +203,9 @@ impl STVOptions {
flags.push(format!("--constraints {}", path)); flags.push(format!("--constraints {}", path));
if self.constraint_mode != ConstraintMode::GuardDoom { flags.push(self.constraint_mode.describe()); } if self.constraint_mode != ConstraintMode::GuardDoom { flags.push(self.constraint_mode.describe()); }
} }
if self.hide_excluded { flags.push(format!("--hide-excluded")); } if self.hide_excluded { flags.push("--hide-excluded".to_string()); }
if self.sort_votes { flags.push(format!("--sort-votes")); } if self.sort_votes { flags.push("--sort-votes".to_string()); }
if self.transfers_detail { flags.push(format!("--transfers-detail")); } if self.transfers_detail { flags.push("--transfers-detail".to_string()); }
if self.pp_decimals != 2 { flags.push(format!("--pp-decimals {}", self.pp_decimals)); } if self.pp_decimals != 2 { flags.push(format!("--pp-decimals {}", self.pp_decimals)); }
return flags.join(" "); return flags.join(" ");
} }
@ -651,19 +651,19 @@ where
state.step_all(); state.step_all();
// Finish count // Finish count
if finished_before_stage(&state) { if finished_before_stage(state) {
return Ok(true); return Ok(true);
} }
// Attempt early bulk election // Attempt early bulk election
if opts.early_bulk_elect { if opts.early_bulk_elect {
if bulk_elect(state, &opts)? { if bulk_elect(state, opts)? {
return Ok(false); return Ok(false);
} }
} }
// Continue exclusions // Continue exclusions
if continue_exclusion(state, &opts)? { if continue_exclusion(state, opts)? {
calculate_quota(state, opts); calculate_quota(state, opts);
elect_hopefuls(state, opts, true)?; elect_hopefuls(state, opts, true)?;
update_tiebreaks(state, opts); update_tiebreaks(state, opts);
@ -671,7 +671,7 @@ where
} }
// Exclude doomed candidates // Exclude doomed candidates
if exclude_doomed(state, &opts)? { if exclude_doomed(state, opts)? {
calculate_quota(state, opts); calculate_quota(state, opts);
elect_hopefuls(state, opts, true)?; elect_hopefuls(state, opts, true)?;
update_tiebreaks(state, opts); update_tiebreaks(state, opts);
@ -679,7 +679,7 @@ where
} }
// Distribute surpluses // Distribute surpluses
if distribute_surpluses(state, &opts)? { if distribute_surpluses(state, opts)? {
calculate_quota(state, opts); calculate_quota(state, opts);
elect_hopefuls(state, opts, true)?; elect_hopefuls(state, opts, true)?;
update_tiebreaks(state, opts); update_tiebreaks(state, opts);
@ -687,7 +687,7 @@ where
} }
// Attempt late bulk election // Attempt late bulk election
if bulk_elect(state, &opts)? { if bulk_elect(state, opts)? {
return Ok(false); return Ok(false);
} }
@ -700,7 +700,7 @@ where
} }
// Exclude lowest hopeful // Exclude lowest hopeful
exclude_hopefuls(state, &opts)?; // Cannot fail exclude_hopefuls(state, opts)?; // Cannot fail
calculate_quota(state, opts); calculate_quota(state, opts);
elect_hopefuls(state, opts, true)?; elect_hopefuls(state, opts, true)?;
update_tiebreaks(state, opts); update_tiebreaks(state, opts);
@ -735,19 +735,13 @@ fn next_preferences<'a, N: Number>(state: &CountState<'a, N>, votes: Vec<Vote<'a
result.total_ballots += &vote.ballot.orig_value; result.total_ballots += &vote.ballot.orig_value;
let mut next_candidate = None; let mut next_candidate = None;
while let Some(preference) = vote.next_preference() {
loop { let candidate = &state.election.candidates[preference];
match vote.next_preference() { let count_card = &state.candidates[candidate];
Some(preference) => {
let candidate = &state.election.candidates[preference]; if let CandidateState::Hopeful | CandidateState::Guarded = count_card.state {
let count_card = &state.candidates[candidate]; next_candidate = Some(candidate);
break;
if let CandidateState::Hopeful | CandidateState::Guarded = count_card.state {
next_candidate = Some(candidate);
break;
}
}
None => { break; }
} }
} }
@ -1417,12 +1411,12 @@ fn hopefuls_to_bulk_exclude<'a, N: Number>(state: &CountState<'a, N>, _opts: &ST
} }
// Do not exclude if this could change the order of exclusion // Do not exclude if this could change the order of exclusion
let total_votes = try_exclude.into_iter().fold(N::new(), |agg, (_, cc)| agg + &cc.votes); let total_votes = try_exclude.iter().fold(N::new(), |agg, (_, cc)| agg + &cc.votes);
if i != 0 && total_votes + &total_surpluses >= hopefuls[hopefuls.len()-i].1.votes { if i != 0 && total_votes + &total_surpluses >= hopefuls[hopefuls.len()-i].1.votes {
continue; continue;
} }
let try_exclude = try_exclude.into_iter().map(|(c, _)| **c).collect(); let try_exclude: Vec<&Candidate> = try_exclude.iter().map(|(c, _)| **c).collect();
// Do not exclude if this violates constraints // Do not exclude if this violates constraints
match constraints::try_constraints(state, &try_exclude, CandidateState::Excluded) { match constraints::try_constraints(state, &try_exclude, CandidateState::Excluded) {
@ -1586,9 +1580,9 @@ fn finished_before_stage<N: Number>(state: &CountState<N>) -> bool {
/// Break a tie between the given candidates according to [STVOptions::ties], selecting the highest candidate /// Break a tie between the given candidates according to [STVOptions::ties], selecting the highest candidate
/// ///
/// The given candidates are assumed to be tied in this round. /// The given candidates are assumed to be tied in this round.
pub fn choose_highest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> { pub fn choose_highest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOptions, candidates: &[&'c Candidate], prompt_text: &str) -> Result<&'c Candidate, STVError> {
for strategy in opts.ties.iter() { for strategy in opts.ties.iter() {
match strategy.choose_highest(state, opts, &candidates, prompt_text) { match strategy.choose_highest(state, opts, candidates, prompt_text) {
Ok(c) => { Ok(c) => {
return Ok(c); return Ok(c);
} }
@ -1607,9 +1601,9 @@ pub fn choose_highest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOption
/// Break a tie between the given candidates according to [STVOptions::ties], selecting the lowest candidate /// Break a tie between the given candidates according to [STVOptions::ties], selecting the lowest candidate
/// ///
/// The given candidates are assumed to be tied in this round. /// The given candidates are assumed to be tied in this round.
pub fn choose_lowest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> { pub fn choose_lowest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOptions, candidates: &[&'c Candidate], prompt_text: &str) -> Result<&'c Candidate, STVError> {
for strategy in opts.ties.iter() { for strategy in opts.ties.iter() {
match strategy.choose_lowest(state, opts, &candidates, prompt_text) { match strategy.choose_lowest(state, opts, candidates, prompt_text) {
Ok(c) => { Ok(c) => {
return Ok(c); return Ok(c);
} }
@ -1665,10 +1659,8 @@ fn init_tiebreaks<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
/// If required, update the state of the forwards or backwards tie-breaking strategies, according to [STVOptions::ties] /// If required, update the state of the forwards or backwards tie-breaking strategies, according to [STVOptions::ties]
fn update_tiebreaks<N: Number>(state: &mut CountState<N>, _opts: &STVOptions) { fn update_tiebreaks<N: Number>(state: &mut CountState<N>, _opts: &STVOptions) {
if let None = state.forwards_tiebreak { if state.forwards_tiebreak.is_none() && state.backwards_tiebreak.is_none() {
if let None = state.backwards_tiebreak { return;
return;
}
} }
// Sort candidates in this stage by votes, grouping by ties // Sort candidates in this stage by votes, grouping by ties
@ -1727,7 +1719,7 @@ fn update_tiebreaks<N: Number>(state: &mut CountState<N>, _opts: &STVOptions) {
continue; continue;
} else { } else {
// Tied in this round - refer to last round // Tied in this round - refer to last round
let mut tied_this_round: Vec<&Candidate> = group.into_iter().map(|c| *c).collect(); let mut tied_this_round: Vec<&Candidate> = group.iter().copied().collect();
tied_this_round.sort_unstable_by(|a, b| hm_orig[a].cmp(&hm_orig[b])); tied_this_round.sort_unstable_by(|a, b| hm_orig[a].cmp(&hm_orig[b]));
let tied_this_round = tied_this_round.into_iter() let tied_this_round = tied_this_round.into_iter()
.group_by(|c| hm_orig[c]); .group_by(|c| hm_orig[c]);

View File

@ -52,7 +52,7 @@ where
for<'r> &'r N: ops::Div<&'r N, Output=N>, for<'r> &'r N: ops::Div<&'r N, Output=N>,
for<'r> &'r N: ops::Neg<Output=N> for<'r> &'r N: ops::Neg<Output=N>
{ {
state.title = StageKind::SurplusOf(&elected_candidate); state.title = StageKind::SurplusOf(elected_candidate);
state.logger.log_literal(format!("Surplus of {} distributed.", elected_candidate.name)); state.logger.log_literal(format!("Surplus of {} distributed.", elected_candidate.name));
let count_card = state.candidates.get_mut(elected_candidate).unwrap(); let count_card = state.candidates.get_mut(elected_candidate).unwrap();
@ -204,7 +204,7 @@ where
let mut top_remainders = cands_by_remainder.iter().take(n_to_round).collect::<Vec<_>>(); let mut top_remainders = cands_by_remainder.iter().take(n_to_round).collect::<Vec<_>>();
// Check for tied remainders // Check for tied remainders
if candidate_transfers_remainders[top_remainders.last().unwrap()] == candidate_transfers_remainders[cands_by_remainder.iter().nth(n_to_round).unwrap()] { if candidate_transfers_remainders[top_remainders.last().unwrap()] == candidate_transfers_remainders[&cands_by_remainder[n_to_round]] {
// Get the top entry // Get the top entry
let top_entry = &candidate_transfers_remainders[top_remainders.last().unwrap()]; let top_entry = &candidate_transfers_remainders[top_remainders.last().unwrap()];
@ -237,7 +237,7 @@ where
let candidate_transfers_usize: usize = format!("{:.0}", candidate_transfers).parse().expect("Transfer overflows usize"); let candidate_transfers_usize: usize = format!("{:.0}", candidate_transfers).parse().expect("Transfer overflows usize");
let count_card = state.candidates.get_mut(candidate).unwrap(); let count_card = state.candidates.get_mut(candidate).unwrap();
count_card.transfer(&candidate_transfers); count_card.transfer(candidate_transfers);
checksum += candidate_transfers; checksum += candidate_transfers;
let parcel = Parcel { let parcel = Parcel {
@ -375,19 +375,13 @@ where
fn transfer_ballot<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, source_candidate: &Candidate, mut vote: Vote<'a, N>, ignore_nontransferable: bool) -> Result<(), STVError> { fn transfer_ballot<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, source_candidate: &Candidate, mut vote: Vote<'a, N>, ignore_nontransferable: bool) -> Result<(), STVError> {
// Get next preference // Get next preference
let mut next_candidate = None; let mut next_candidate = None;
while let Some(preference) = vote.next_preference() {
loop { let candidate = &state.election.candidates[preference];
match vote.next_preference() { let count_card = &state.candidates[candidate];
Some(preference) => {
let candidate = &state.election.candidates[preference]; if let CandidateState::Hopeful | CandidateState::Guarded = count_card.state {
let count_card = &state.candidates[candidate]; next_candidate = Some(candidate);
break;
if let CandidateState::Hopeful | CandidateState::Guarded = count_card.state {
next_candidate = Some(candidate);
break;
}
}
None => { break; }
} }
} }

View File

@ -183,10 +183,7 @@ macro_rules! impl_type {
/// Call [render_html](crate::stv::transfers::TransferTable::render_html) on [CountState::transfer_table] /// Call [render_html](crate::stv::transfers::TransferTable::render_html) on [CountState::transfer_table]
pub fn transfer_table_render_html(&self, opts: &STVOptions) -> Option<String> { pub fn transfer_table_render_html(&self, opts: &STVOptions) -> Option<String> {
return match &self.0.transfer_table { return self.0.transfer_table.as_ref().map(|tt| tt.render_text(&opts.0));
Some(tt) => Some(tt.render_text(&opts.0)), // TODO
None => None,
};
} }
} }
@ -324,7 +321,7 @@ pub fn describe_count<N: Number>(filename: String, election: &Election<N>, opts:
result.push_str(&format!(r#"). Read {:.0} ballots from &lsquo;{}&rsquo; for election &lsquo;{}&rsquo;. There are {} candidates for {} vacancies. "#, total_ballots, filename, election.name, election.candidates.len(), election.seats)); result.push_str(&format!(r#"). Read {:.0} ballots from &lsquo;{}&rsquo; for election &lsquo;{}&rsquo;. There are {} candidates for {} vacancies. "#, total_ballots, filename, election.name, election.candidates.len(), election.seats));
let opts_str = opts.describe::<N>(); let opts_str = opts.describe::<N>();
if opts_str.len() > 0 { if !opts_str.is_empty() {
result.push_str(&format!(r#"Counting using options <span style="font-family: monospace;">{}</span>.</p>"#, opts_str)) result.push_str(&format!(r#"Counting using options <span style="font-family: monospace;">{}</span>.</p>"#, opts_str))
} else { } else {
result.push_str(r#"Counting using default options.</p>"#); result.push_str(r#"Counting using default options.</p>"#);
@ -338,7 +335,7 @@ pub fn init_results_table<N: Number>(election: &Election<N>, opts: &stv::STVOpti
let mut result = String::from(r#"<tr class="stage-no"><td rowspan="3"></td></tr><tr class="stage-kind"></tr><tr class="stage-comment"></tr>"#); let mut result = String::from(r#"<tr class="stage-no"><td rowspan="3"></td></tr><tr class="stage-kind"></tr><tr class="stage-comment"></tr>"#);
if report_style == "ballots_votes" { if report_style == "ballots_votes" {
result.push_str(&r#"<tr class="hint-papers-votes"><td></td></tr>"#); result.push_str(r#"<tr class="hint-papers-votes"><td></td></tr>"#);
} }
for candidate in election.candidates.iter() { for candidate in election.candidates.iter() {
@ -375,7 +372,7 @@ pub fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>,
// Insert borders to left of new exclusions in Wright STV // Insert borders to left of new exclusions in Wright STV
let classes_o; // Outer version let classes_o; // Outer version
let classes_i; // Inner version let classes_i; // Inner version
if opts.exclusion == stv::ExclusionMethod::Wright && (if let StageKind::ExclusionOf(_) = state.title { true } else { false }) { if opts.exclusion == stv::ExclusionMethod::Wright && matches!(state.title, StageKind::ExclusionOf(_)) {
classes_o = r#" class="blw""#; classes_o = r#" class="blw""#;
classes_i = r#"blw "#; classes_i = r#"blw "#;
} else { } else {
@ -387,7 +384,7 @@ pub fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>,
let hide_xfers_trsp; let hide_xfers_trsp;
if let StageKind::FirstPreferences = state.title { if let StageKind::FirstPreferences = state.title {
hide_xfers_trsp = true; hide_xfers_trsp = true;
} else if opts.exclusion == stv::ExclusionMethod::Wright && (if let StageKind::ExclusionOf(_) = state.title { true } else { false }) { } else if opts.exclusion == stv::ExclusionMethod::Wright && matches!(state.title, StageKind::ExclusionOf(_)) {
hide_xfers_trsp = true; hide_xfers_trsp = true;
} else { } else {
hide_xfers_trsp = false; hide_xfers_trsp = false;
@ -631,7 +628,7 @@ pub fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>,
/// Get the comment for the current stage /// Get the comment for the current stage
pub fn update_stage_comments<N: Number>(state: &CountState<N>, stage_num: usize) -> String { pub fn update_stage_comments<N: Number>(state: &CountState<N>, stage_num: usize) -> String {
let mut comments = state.logger.render().join(" "); let mut comments = state.logger.render().join(" ");
if let Some(_) = state.transfer_table { if state.transfer_table.is_some() {
comments.push_str(&format!(r##" <a href="#" class="detailedTransfersLink" onclick="viewDetailedTransfers({});return false;">[View detailed transfers]</a>"##, stage_num)); comments.push_str(&format!(r##" <a href="#" class="detailedTransfersLink" onclick="viewDetailedTransfers({});return false;">[View detailed transfers]</a>"##, stage_num));
} }
return comments; return comments;

View File

@ -64,12 +64,12 @@ impl TieStrategy {
/// Break a tie between the given candidates, selecting the highest candidate /// Break a tie between the given candidates, selecting the highest candidate
/// ///
/// The given candidates are assumed to be tied in this round /// The given candidates are assumed to be tied in this round
pub fn choose_highest<'c, N: Number>(&self, state: &mut CountState<N>, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> { pub fn choose_highest<'c, N: Number>(&self, state: &mut CountState<N>, opts: &STVOptions, candidates: &[&'c Candidate], prompt_text: &str) -> Result<&'c Candidate, STVError> {
match self { match self {
Self::Forwards => { Self::Forwards => {
match &state.forwards_tiebreak { match &state.forwards_tiebreak {
Some(tb) => { Some(tb) => {
let mut candidates = candidates.clone(); let mut candidates: Vec<&Candidate> = candidates.iter().copied().collect();
// Compare b to a to sort high-to-low // Compare b to a to sort high-to-low
candidates.sort_unstable_by(|a, b| tb[b].cmp(&tb[a])); candidates.sort_unstable_by(|a, b| tb[b].cmp(&tb[a]));
if tb[candidates[0]] == tb[candidates[1]] { if tb[candidates[0]] == tb[candidates[1]] {
@ -88,7 +88,7 @@ impl TieStrategy {
Self::Backwards => { Self::Backwards => {
match &state.backwards_tiebreak { match &state.backwards_tiebreak {
Some(tb) => { Some(tb) => {
let mut candidates = candidates.clone(); let mut candidates: Vec<&Candidate> = candidates.iter().copied().collect();
candidates.sort_unstable_by(|a, b| tb[b].cmp(&tb[a])); candidates.sort_unstable_by(|a, b| tb[b].cmp(&tb[a]));
if tb[candidates[0]] == tb[candidates[1]] { if tb[candidates[0]] == tb[candidates[1]] {
return Err(STVError::UnresolvedTie); return Err(STVError::UnresolvedTie);
@ -122,10 +122,10 @@ impl TieStrategy {
/// Break a tie between the given candidates, selecting the lowest candidate /// Break a tie between the given candidates, selecting the lowest candidate
/// ///
/// The given candidates are assumed to be tied in this round /// The given candidates are assumed to be tied in this round
pub fn choose_lowest<'c, N: Number>(&self, state: &mut CountState<N>, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> { pub fn choose_lowest<'c, N: Number>(&self, state: &mut CountState<N>, opts: &STVOptions, candidates: &[&'c Candidate], prompt_text: &str) -> Result<&'c Candidate, STVError> {
match self { match self {
Self::Forwards => { Self::Forwards => {
let mut candidates = candidates.clone(); let mut candidates: Vec<&Candidate> = candidates.iter().copied().collect();
candidates.sort_unstable_by(|a, b| candidates.sort_unstable_by(|a, b|
state.forwards_tiebreak.as_ref().unwrap()[a] state.forwards_tiebreak.as_ref().unwrap()[a]
.cmp(&state.forwards_tiebreak.as_ref().unwrap()[b]) .cmp(&state.forwards_tiebreak.as_ref().unwrap()[b])
@ -138,7 +138,7 @@ impl TieStrategy {
} }
} }
Self::Backwards => { Self::Backwards => {
let mut candidates = candidates.clone(); let mut candidates: Vec<&Candidate> = candidates.iter().copied().collect();
candidates.sort_unstable_by(|a, b| candidates.sort_unstable_by(|a, b|
state.backwards_tiebreak.as_ref().unwrap()[a] state.backwards_tiebreak.as_ref().unwrap()[a]
.cmp(&state.backwards_tiebreak.as_ref().unwrap()[b]) .cmp(&state.backwards_tiebreak.as_ref().unwrap()[b])
@ -161,7 +161,7 @@ impl TieStrategy {
} }
/// Return all maximal items according to the given key /// Return all maximal items according to the given key
pub fn multiple_max_by<E: Copy, K, C: Ord>(items: &Vec<E>, key: K) -> Vec<E> pub fn multiple_max_by<E: Copy, K, C: Ord>(items: &[E], key: K) -> Vec<E>
where where
K: Fn(&E) -> C K: Fn(&E) -> C
{ {
@ -190,7 +190,7 @@ where
} }
/// Return all minimal items according to the given key /// Return all minimal items according to the given key
pub fn multiple_min_by<E: Copy, K, C: Ord>(items: &Vec<E>, key: K) -> Vec<E> pub fn multiple_min_by<E: Copy, K, C: Ord>(items: &[E], key: K) -> Vec<E>
where where
K: Fn(&E) -> C K: Fn(&E) -> C
{ {
@ -220,7 +220,7 @@ where
/// Prompt the candidate for input, depending on CLI or WebAssembly target /// Prompt the candidate for input, depending on CLI or WebAssembly target
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
fn prompt<'c, N: Number>(state: &CountState<N>, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> { fn prompt<'c, N: Number>(state: &CountState<N>, opts: &STVOptions, candidates: &[&'c Candidate], prompt_text: &str) -> Result<&'c Candidate, STVError> {
// Show intrastage progress if required // Show intrastage progress if required
if !state.logger.entries.is_empty() { if !state.logger.entries.is_empty() {
// Print stage details // Print stage details
@ -233,7 +233,7 @@ fn prompt<'c, N: Number>(state: &CountState<N>, opts: &STVOptions, candidates: &
// Print summary rows // Print summary rows
print!("{}", state.describe_summary(opts)); print!("{}", state.describe_summary(opts));
println!(""); println!();
} }
println!("Multiple tied candidates:"); println!("Multiple tied candidates:");
@ -270,7 +270,7 @@ extern "C" {
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
fn prompt<'c, N: Number>(state: &CountState<N>, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> { fn prompt<'c, N: Number>(state: &CountState<N>, opts: &STVOptions, candidates: &[&'c Candidate], prompt_text: &str) -> Result<&'c Candidate, STVError> {
let mut message = String::new(); let mut message = String::new();
// Show intrastage progress if required // Show intrastage progress if required

View File

@ -31,5 +31,5 @@ pub fn write<W: Write, N: Number>(election: Election<N>, output: W) {
// Write output // Write output
let mut output = BufWriter::new(output); let mut output = BufWriter::new(output);
output.write(&buffer).expect("IO Error"); output.write_all(&buffer).expect("IO Error");
} }

View File

@ -31,8 +31,8 @@ pub fn write<W: Write, N: Number>(election: Election<N>, output: W) {
// Write withdrawn candidates // Write withdrawn candidates
if !election.withdrawn_candidates.is_empty() { if !election.withdrawn_candidates.is_empty() {
output.write(election.withdrawn_candidates.into_iter().map(|idx| format!("-{}", idx + 1)).join(" ").as_bytes()).expect("IO Error"); output.write_all(election.withdrawn_candidates.into_iter().map(|idx| format!("-{}", idx + 1)).join(" ").as_bytes()).expect("IO Error");
output.write(b"\n").expect("IO Error"); output.write_all(b"\n").expect("IO Error");
} }
// Write ballots // Write ballots
@ -43,10 +43,10 @@ pub fn write<W: Write, N: Number>(election: Election<N>, output: W) {
output.write_fmt(format_args!(" {}", preference.into_iter().map(|p| p + 1).join("="))).expect("IO Error"); output.write_fmt(format_args!(" {}", preference.into_iter().map(|p| p + 1).join("="))).expect("IO Error");
} }
output.write(b" 0\n").expect("IO Error"); output.write_all(b" 0\n").expect("IO Error");
} }
output.write(b"0\n").expect("IO Error"); output.write_all(b"0\n").expect("IO Error");
// Write candidate names // Write candidate names
for candidate in election.candidates { for candidate in election.candidates {