rust-clippy linting
This commit is contained in:
parent
69bc30b333
commit
15614a4e8f
|
@ -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.");
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -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; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 ‘{}’ for election ‘{}’. There are {} candidates for {} vacancies. "#, total_ballots, filename, election.name, election.candidates.len(), election.seats));
|
result.push_str(&format!(r#"). Read {:.0} ballots from ‘{}’ for election ‘{}’. 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;
|
||||||
|
|
22
src/ties.rs
22
src/ties.rs
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue