first commit
This commit is contained in:
50
src/io.rs
Normal file
50
src/io.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use super::repartition::*;
|
||||
|
||||
const FORMAT_TEXT: &'static str = "<html>
|
||||
<head>
|
||||
<script src=\"/home/jfelderh/Prog/Repartition Alpes/cytoscape.min.js\"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id=\"graph\" style=\"width: auto;height: 500px;\"></div>
|
||||
|
||||
<h3>Balance totale (doit être égal à zéro) : {}€</h3>
|
||||
|
||||
<script type=\"text/javascript\">
|
||||
var cy = cytoscape({
|
||||
|
||||
container: document.getElementById('graph'), // container to render in
|
||||
|
||||
elements:{},
|
||||
|
||||
style: [ // the stylesheet for the graph
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'background-color': '#666',
|
||||
'label': 'data(label)'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'width': 3,
|
||||
'label': 'data(label)',
|
||||
'line-color': '#ccc',
|
||||
'target-arrow-color': '#ccc',
|
||||
'target-arrow-shape': 'triangle',
|
||||
'curve-style': 'bezier',
|
||||
'classes': 'outline'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
layout: {
|
||||
name: 'cose',
|
||||
nodeDimensionsIncludeLabels: true,
|
||||
}
|
||||
|
||||
});</script>
|
||||
|
||||
</body>
|
||||
";
|
||||
76
src/main.rs
Normal file
76
src/main.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use std::fs;
|
||||
|
||||
mod io;
|
||||
pub mod repartition;
|
||||
|
||||
use argh::FromArgs;
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
#[derive(FromArgs)]
|
||||
/// Computation of repartition
|
||||
struct Arguments {
|
||||
/// file with all the user in csv format
|
||||
#[argh(option, short = 'u')]
|
||||
user_list_filename: Option<String>,
|
||||
|
||||
/// seed for random generation
|
||||
#[argh(option, short = 's')]
|
||||
seed: Option<u64>,
|
||||
|
||||
/// number of repetitions for random generation
|
||||
#[argh(option, short = 'n', default = "1")]
|
||||
number_repetition: usize,
|
||||
|
||||
/// file with all the transferts in json format
|
||||
#[argh(option, short = 't')]
|
||||
transfert_list_filename: Option<String>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
use repartition::*;
|
||||
let args: Arguments = argh::from_env();
|
||||
if let Some(filename) = args.user_list_filename {
|
||||
let seed = args.seed.unwrap_or(42);
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
|
||||
let repartition_file = fs::File::open(filename).expect("not able to read file");
|
||||
let mut csv_reader = csv::ReaderBuilder::new()
|
||||
.has_headers(false)
|
||||
.delimiter(b';')
|
||||
.from_reader(repartition_file);
|
||||
|
||||
let user_list = csv_reader
|
||||
.records()
|
||||
.map(Result::unwrap)
|
||||
.map(|record| User {
|
||||
name: record[0].parse::<String>().unwrap().trim_end().to_string(),
|
||||
balance_cents: (record[1].parse::<f64>().unwrap() * 100.) as i32,
|
||||
email: record[2].parse::<String>().unwrap().trim_end().to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut best_repartition =
|
||||
RandomRepartitionBuilder::new(&user_list, &mut rng).build_repartition();
|
||||
let mut best_evaluation = best_repartition.evaluate();
|
||||
for _ in 1..args.number_repetition {
|
||||
let repartition =
|
||||
RandomRepartitionBuilder::new(&user_list, &mut rng).build_repartition();
|
||||
let evaluation = repartition.evaluate();
|
||||
|
||||
if evaluation < best_evaluation {
|
||||
best_evaluation = evaluation;
|
||||
best_repartition = repartition;
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&best_repartition).unwrap()
|
||||
);
|
||||
} else if let Some(filename) = args.transfert_list_filename {
|
||||
let repart: Repartition =
|
||||
serde_json::from_reader(fs::File::open(filename).expect("not able to read file"))
|
||||
.expect("unable to read file");
|
||||
} else {
|
||||
println!("No parameter given, exiting");
|
||||
}
|
||||
}
|
||||
96
src/repartition.rs
Normal file
96
src/repartition.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use std::cmp::min;
|
||||
|
||||
use rand::prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
pub balance_cents: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RandomRepartitionBuilder {
|
||||
payers: Vec<User>,
|
||||
|
||||
receivers: Vec<User>,
|
||||
}
|
||||
|
||||
impl RandomRepartitionBuilder {
|
||||
pub fn new<R>(user_list: &[User], rng: &mut R) -> Self
|
||||
where
|
||||
R: Rng + ?Sized,
|
||||
{
|
||||
let mut payers = user_list
|
||||
.iter()
|
||||
.filter(|u| u.balance_cents < 0)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let mut receivers = user_list
|
||||
.iter()
|
||||
.filter(|u| u.balance_cents > 0)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
payers.shuffle(rng);
|
||||
receivers.shuffle(rng);
|
||||
let total_balance_payers: i32 = payers.iter().map(|p| p.balance_cents).sum();
|
||||
let total_balance_receivers: i32 = receivers.iter().map(|r| r.balance_cents).sum();
|
||||
assert_eq!(total_balance_payers, -total_balance_receivers);
|
||||
Self { payers, receivers }
|
||||
}
|
||||
|
||||
fn get_next_receiver(&mut self) -> User {
|
||||
self.receivers.pop().unwrap()
|
||||
}
|
||||
|
||||
fn get_next_payer(&mut self) -> User {
|
||||
self.payers.pop().unwrap()
|
||||
}
|
||||
|
||||
pub fn build_repartition(mut self) -> Repartition {
|
||||
let mut rep = Repartition(Vec::new());
|
||||
|
||||
while !self.payers.is_empty() || !self.receivers.is_empty() {
|
||||
let mut payer = self.get_next_payer();
|
||||
let mut receiver = self.get_next_receiver();
|
||||
let amount = min(-payer.balance_cents, receiver.balance_cents);
|
||||
dbg!(amount);
|
||||
let transfert = Transfer {
|
||||
from: payer.clone(),
|
||||
to: receiver.clone(),
|
||||
amount: amount as u32,
|
||||
};
|
||||
payer.balance_cents += amount;
|
||||
receiver.balance_cents -= amount;
|
||||
rep.0.push(transfert);
|
||||
if payer.balance_cents < 0 {
|
||||
self.payers.push(payer);
|
||||
}
|
||||
if receiver.balance_cents > 0 {
|
||||
self.receivers.push(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
rep
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Transfer {
|
||||
pub from: User,
|
||||
pub to: User,
|
||||
pub amount: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Repartition(Vec<Transfer>);
|
||||
|
||||
type Evaluation = (usize);
|
||||
|
||||
impl Repartition {
|
||||
pub fn evaluate(&self) -> Evaluation {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user