init
This commit is contained in:
parent
1fe0b4e464
commit
add30f3515
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "vote-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# for web server
|
||||
rocket = { version = "0.5.0", features = ["json", "uuid"] }
|
||||
# for json serialization
|
||||
serde = "1.0.196"
|
||||
# for random number generation
|
||||
rand = "0.8.5"
|
||||
# for json serialization
|
||||
serde_json = "1.0.113"
|
||||
# for uuid generation
|
||||
uuid = { version = "1.7.0", features = ["serde", "v4"] }
|
||||
# for command line arguments
|
||||
structopt = "0.3.23"
|
||||
# for csv reading
|
||||
csv = "1.1.6"
|
30
README.md
30
README.md
|
@ -1,3 +1,29 @@
|
|||
# vote-rs
|
||||
# Vote-rs
|
||||
|
||||
This is a simple Rust program that reads a file of voting records and counts the votes for each nominee.
|
||||
|
||||
## goael
|
||||
Implement a stv vote counting system in rust.
|
||||
Could be based on rules and information found here: https://www.opavote.com/methods/single-transferable-vote
|
||||
probably implement the scottish method https://www.legislation.gov.uk/ssi/2007/42/contents/made
|
||||
because it is simpler and seems to be ok middle ground between complexity and fairness.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
To run the program, use the following command:
|
||||
|
||||
```bash
|
||||
cargo run -- sample.csv --amount 3
|
||||
```
|
||||
|
||||
Replace sample.csv with the path to your file of voting records.
|
||||
amount is the number of nominees to select from the records.
|
||||
|
||||
## Functionality
|
||||
The program reads the voting records from the specified file into a Vec<Record>. Each Record contains a Vec<Vote>, where each Vote has a name and a value.
|
||||
|
||||
The get_unique_names function is used to get a Vec<String> of the unique names in the records.
|
||||
|
||||
The count_votes function is used to count the votes for each name. It returns a Vec<Vote> where each Vote has a name and a value that is the total of the votes for that name. The Vec<Vote> is sorted in descending order by vote value.
|
||||
|
||||
stv voting system written in rust.
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
devenv.url = "github:cachix/devenv";
|
||||
|
||||
fenix.url = "github:nix-community/fenix";
|
||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, devenv, ... }@inputs:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
system = "x86_64-linux";
|
||||
};
|
||||
in {
|
||||
devShell.x86_64-linux = devenv.lib.mkShell {
|
||||
inherit inputs pkgs;
|
||||
modules = [
|
||||
{
|
||||
languages.rust = {
|
||||
enable = true;
|
||||
channel = "stable";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
["Alice", "Bob"],
|
||||
[""],
|
||||
["Alice"],
|
||||
["Bob"],
|
||||
["Charlie", "Bob", "Alice"],
|
||||
["David"],
|
||||
["David"]
|
||||
]
|
|
@ -0,0 +1,158 @@
|
|||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use structopt::StructOpt;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "example", about = "An example of StructOpt usage.")]
|
||||
struct Opt {//options
|
||||
/// File to process
|
||||
#[structopt(parse(from_os_str))]
|
||||
file: PathBuf,
|
||||
|
||||
/// Amount to select
|
||||
#[structopt(short = "a", long = "amount")]
|
||||
amount: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct Vote {
|
||||
name: String,
|
||||
value: f64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct Score {
|
||||
name: String,
|
||||
value: f64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct Record {
|
||||
votes: Vec<Vote>,
|
||||
}
|
||||
|
||||
|
||||
fn read_to_records(file: &PathBuf) -> Result<Vec<Record>, Box<dyn Error>> {
|
||||
let file = std::fs::File::open(file)?;
|
||||
let reader = io::BufReader::new(file);
|
||||
//read the json file with a list of lists with names into a vector of records, where each record is a list of votes containing a name and a value. the first name in each list has value 1, the following names have value 0
|
||||
let name_vec: Vec<Vec<String>> = serde_json::from_reader(reader)?;
|
||||
let mut records: Vec<Record> = Vec::new();
|
||||
for names in name_vec {
|
||||
let mut votes: Vec<Vote> = Vec::new();
|
||||
for (i, name) in names.iter().enumerate() {
|
||||
let vote = Vote {
|
||||
name: name.clone().to_string().to_lowercase().replace(" ", "_").replace("-","_").replace("__", "_"),
|
||||
value: if i == 0 { 1.0 } else { 0.0 },
|
||||
};
|
||||
votes.push(vote);
|
||||
}
|
||||
let record = Record { votes };
|
||||
records.push(record);
|
||||
}
|
||||
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
|
||||
//get all unique names from the records
|
||||
fn get_unique_names(records: &Vec<Record>) -> Vec<String> {
|
||||
let mut names: HashMap<String, bool> = HashMap::new();
|
||||
for record in records {
|
||||
for vote in &record.votes {
|
||||
names.entry(vote.name.clone()).or_insert(true);
|
||||
}
|
||||
}
|
||||
names.keys().cloned().collect()
|
||||
}
|
||||
|
||||
|
||||
fn count_votes(records: &Vec<Record>) -> Vec<Score> {
|
||||
let mut counts: HashMap<String, f64> = HashMap::new();
|
||||
for record in records {
|
||||
for vote in &record.votes {
|
||||
*counts.entry(vote.name.clone()).or_insert(0.0) += vote.value;
|
||||
}
|
||||
}
|
||||
let mut scores: Vec<Score> = counts.into_iter()
|
||||
.map(|(name, value)| Score { name, value })
|
||||
.collect();
|
||||
|
||||
scores.sort_by(|a, b| {
|
||||
let value_cmp = b.value.partial_cmp(&a.value).unwrap();
|
||||
if value_cmp == std::cmp::Ordering::Equal {
|
||||
a.name.cmp(&b.name)
|
||||
} else {
|
||||
value_cmp
|
||||
}
|
||||
});
|
||||
scores
|
||||
}
|
||||
|
||||
|
||||
// (3) The vote on each ballot paper transferred under paragraph (2) shall have a value ("the transfer value") calculated as follows—
|
||||
// A divided by B
|
||||
// Where
|
||||
// A = the value which is calculated by multiplying the surplus of the transferring candidate by the value of the ballot paper when received by that candidate; and
|
||||
// B = the total number of votes credited to that candidate,
|
||||
// the calculation being made to five decimal places (any remainder being ignored).
|
||||
|
||||
//TODO: implement the quota calculation
|
||||
fn exceeds_quota(score: &Score, quota: f64) -> bool {
|
||||
score.value >= quota
|
||||
}
|
||||
|
||||
//TODO: implement the stv algorithm
|
||||
fn stv(recs: &Vec<Record>, amount: usize) -> Vec<Score> {
|
||||
let mut records: Vec<_> = recs.iter().map(|x| (*x).clone()).collect();
|
||||
let mut winners: Vec<Score> = Vec::new();
|
||||
let total_votes = records.len() as f64;
|
||||
let mut round = 0;
|
||||
|
||||
while winners.len() < amount {
|
||||
let quota = total_votes / (amount as f64 + 1.0);
|
||||
round += 1;
|
||||
let scores = count_votes(&records);
|
||||
println!("Round {}: {:?}", round, scores);
|
||||
winners.append(&mut scores.iter().filter(|x| exceeds_quota(x, quota)).cloned().collect());
|
||||
if winners.len() >= amount {
|
||||
break;
|
||||
}
|
||||
|
||||
//do soemthing random for now until i actually implement the stv algorithm.
|
||||
//remove the last candidate from the records
|
||||
let names = get_unique_names(&records);
|
||||
//remove the last name from the records
|
||||
records.retain(|x| x.votes.iter().any(|y| y.name != names[names.len() - 1]));
|
||||
//add the first candidate to the winners
|
||||
winners.push(scores[0].clone());
|
||||
|
||||
|
||||
}
|
||||
winners
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
fn main() {
|
||||
let opt = Opt::from_args();
|
||||
let records = read_to_records(&opt.file).unwrap_or_else(|err| {
|
||||
println!("Error reading file: {}", err);
|
||||
process::exit(1);
|
||||
});
|
||||
println!("Records: {:?}", records);
|
||||
let nominees = get_unique_names(&records);
|
||||
println!("Nominees: {:?}", nominees);
|
||||
let counts = count_votes(&records);
|
||||
println!("Counts: {:?}", counts);
|
||||
|
||||
let winners = stv(&records, opt.amount);
|
||||
println!("Winners: {:?}", winners);
|
||||
|
||||
}
|
Loading…
Reference in New Issue