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