From 3ceaf67091f6a8947e18c280daeedb94b395fc61 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Thu, 30 Sep 2021 00:48:57 +1000 Subject: [PATCH] Implement stricter validation modes for CSP input --- src/cli/convert.rs | 14 +++++++++++++- src/parser/csp.rs | 32 +++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/cli/convert.rs b/src/cli/convert.rs index bc4a1f3..64974ad 100644 --- a/src/cli/convert.rs +++ b/src/cli/convert.rs @@ -47,6 +47,18 @@ pub struct SubcmdOptions { /// Number of seats #[clap(help_heading=Some("ELECTION SPECIFICATION"), long)] seats: Option, + + /// 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 @@ -97,7 +109,7 @@ pub fn main(mut cmd_opts: SubcmdOptions) -> Result<(), i32> { } "csp" => { 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!() }; diff --git a/src/parser/csp.rs b/src/parser/csp.rs index e209d38..3ae303a 100644 --- a/src/parser/csp.rs +++ b/src/parser/csp.rs @@ -25,7 +25,7 @@ use std::collections::HashMap; use std::io::Read; /// Parse the given CSP file -pub fn parse_reader(reader: R) -> Election { +pub fn parse_reader(reader: R, require_1: bool, require_sequential: bool, require_strict_order: bool) -> Election { // Read CSV file let mut reader = ReaderBuilder::new() .has_headers(true) @@ -86,13 +86,43 @@ pub fn parse_reader(reader: R) -> Election { let mut unique_rankings: Vec = preferences.iter().map(|(r, _)| *r).unique().collect(); 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 last_ranking = None; for ranking in unique_rankings { // Filter for preferences at this ranking let prefs_this_ranking: Vec = preferences.iter() .filter_map(|(r, i)| if *r == ranking { Some(**i) } else { None }) .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); + last_ranking = Some(ranking); } ballots.push(Ballot {