Implement stricter validation modes for CSP input

This commit is contained in:
RunasSudo 2021-09-30 00:48:57 +10:00
parent 047a53d0d9
commit 3ceaf67091
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
2 changed files with 44 additions and 2 deletions

View File

@ -47,6 +47,18 @@ pub struct SubcmdOptions {
/// Number of seats /// Number of seats
#[clap(help_heading=Some("ELECTION SPECIFICATION"), long)] #[clap(help_heading=Some("ELECTION SPECIFICATION"), long)]
seats: Option<usize>, seats: Option<usize>,
/// Require 1st preference
#[clap(help_heading=Some("PREFERENCE VALIDATION"), long)]
require_1: bool,
/// Require sequential preferences
#[clap(help_heading=Some("PREFERENCE VALIDATION"), long)]
require_sequential: bool,
/// Require strict ordering of preferences
#[clap(help_heading=Some("PREFERENCE VALIDATION"), long)]
require_strict_order: bool,
} }
/// Entrypoint for subcommand /// Entrypoint for subcommand
@ -97,7 +109,7 @@ pub fn main(mut cmd_opts: SubcmdOptions) -> Result<(), i32> {
} }
"csp" => { "csp" => {
let file = File::open(cmd_opts.infile).expect("IO Error"); let file = File::open(cmd_opts.infile).expect("IO Error");
election = parser::csp::parse_reader(file); election = parser::csp::parse_reader(file, cmd_opts.require_1, cmd_opts.require_sequential, cmd_opts.require_strict_order);
} }
_ => unreachable!() _ => unreachable!()
}; };

View File

@ -25,7 +25,7 @@ use std::collections::HashMap;
use std::io::Read; use std::io::Read;
/// Parse the given CSP file /// Parse the given CSP file
pub fn parse_reader<R: Read, N: Number>(reader: R) -> Election<N> { pub fn parse_reader<R: Read, N: Number>(reader: R, require_1: bool, require_sequential: bool, require_strict_order: bool) -> Election<N> {
// Read CSV file // Read CSV file
let mut reader = ReaderBuilder::new() let mut reader = ReaderBuilder::new()
.has_headers(true) .has_headers(true)
@ -86,13 +86,43 @@ pub fn parse_reader<R: Read, N: Number>(reader: R) -> Election<N> {
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();
if require_1 {
if unique_rankings.first().map(|r| *r == 1).unwrap_or(false) == false {
// No #1 preference
ballots.push(Ballot {
orig_value: value,
preferences: vec![],
});
continue;
}
}
let mut sorted_preferences = Vec::with_capacity(preferences.len()); let mut sorted_preferences = Vec::with_capacity(preferences.len());
let mut last_ranking = None;
for ranking in unique_rankings { for ranking in unique_rankings {
// Filter for preferences at this ranking // Filter for preferences at this ranking
let prefs_this_ranking: Vec<usize> = preferences.iter() let prefs_this_ranking: Vec<usize> = preferences.iter()
.filter_map(|(r, i)| if *r == ranking { Some(**i) } else { None }) .filter_map(|(r, i)| if *r == ranking { Some(**i) } else { None })
.collect(); .collect();
if require_strict_order {
if prefs_this_ranking.len() != 1 {
// Duplicate rankings
break;
}
}
if require_sequential {
if let Some(r) = last_ranking {
if ranking != r + 1 {
// Not sequential
break;
}
}
}
sorted_preferences.push(prefs_this_ranking); sorted_preferences.push(prefs_this_ranking);
last_ranking = Some(ranking);
} }
ballots.push(Ballot { ballots.push(Ballot {