init
This commit is contained in:
parent
1fe0b4e464
commit
add30f3515
1747
Cargo.lock
generated
Normal file
1747
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
Normal file
22
Cargo.toml
Normal file
@ -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.
|
28
flake.nix
Normal file
28
flake.nix
Normal file
@ -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";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
9
sample.json
Normal file
9
sample.json
Normal file
@ -0,0 +1,9 @@
|
||||
[
|
||||
["Alice", "Bob"],
|
||||
[""],
|
||||
["Alice"],
|
||||
["Bob"],
|
||||
["Charlie", "Bob", "Alice"],
|
||||
["David"],
|
||||
["David"]
|
||||
]
|
158
src/main.rs
Normal file
158
src/main.rs
Normal file
@ -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
Block a user