first commit

This commit is contained in:
jfelderh
2024-10-28 14:26:06 +01:00
commit 33de1a4778
7 changed files with 728 additions and 0 deletions

50
src/io.rs Normal file
View 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
View 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
View 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()
}
}