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

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

216
Cargo.lock generated Normal file
View File

@@ -0,0 +1,216 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "argh"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219"
dependencies = [
"argh_derive",
"argh_shared",
]
[[package]]
name = "argh_derive"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a"
dependencies = [
"argh_shared",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "argh_shared"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531"
dependencies = [
"serde",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "csv"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr",
]
[[package]]
name = "getrandom"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "libc"
version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]]
name = "memchr"
version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rust_repartition"
version = "0.1.0"
dependencies = [
"argh",
"csv",
"rand",
"rand_chacha",
"serde",
"serde_json",
]
[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "serde"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

14
Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "rust_repartition"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
argh = "0.1.12"
csv = "1.2.2"
rand = "0.8.5"
rand_chacha = "0.3.1"
serde = "1.0.188"
serde_json = "1.0.107"

275
repartition.json Normal file
View File

@@ -0,0 +1,275 @@
[
{
"from": {
"name": "Alice",
"email": "alice.colin.ensl@gmail.com",
"balance_cents": -10040
},
"to": {
"name": "Janelle",
"email": "janelle.gauthier@orange.fr",
"balance_cents": 31304
},
"amount": 10040
},
{
"from": {
"name": "Arnaud",
"email": "arnaud.oliveau@gmail.com",
"balance_cents": -34
},
"to": {
"name": "Janelle",
"email": "janelle.gauthier@orange.fr",
"balance_cents": 21264
},
"amount": 34
},
{
"from": {
"name": "Florine",
"email": "florine.dubourg@ens-lyon.fr",
"balance_cents": -16413
},
"to": {
"name": "Janelle",
"email": "janelle.gauthier@orange.fr",
"balance_cents": 21230
},
"amount": 16413
},
{
"from": {
"name": "Bertrand",
"email": "bertrand.carro@gmail.com",
"balance_cents": -15527
},
"to": {
"name": "Janelle",
"email": "janelle.gauthier@orange.fr",
"balance_cents": 4817
},
"amount": 4817
},
{
"from": {
"name": "Bertrand",
"email": "bertrand.carro@gmail.com",
"balance_cents": -10710
},
"to": {
"name": "Lambert",
"email": "lambertvogin@gmail.com",
"balance_cents": 1275
},
"amount": 1275
},
{
"from": {
"name": "Bertrand",
"email": "bertrand.carro@gmail.com",
"balance_cents": -9435
},
"to": {
"name": "Alix",
"email": "jeanne.carro@ens-lyon.fr",
"balance_cents": 51765
},
"amount": 9435
},
{
"from": {
"name": "Morgan",
"email": "sterb.morgan@gmail.com",
"balance_cents": -16454
},
"to": {
"name": "Alix",
"email": "jeanne.carro@ens-lyon.fr",
"balance_cents": 42330
},
"amount": 16454
},
{
"from": {
"name": "Xavier",
"email": "xavier.onfroy@protonmail.com",
"balance_cents": -1541
},
"to": {
"name": "Alix",
"email": "jeanne.carro@ens-lyon.fr",
"balance_cents": 25876
},
"amount": 1541
},
{
"from": {
"name": "Nattes",
"email": "nath.kastylevsky@proton.me",
"balance_cents": -1923
},
"to": {
"name": "Alix",
"email": "jeanne.carro@ens-lyon.fr",
"balance_cents": 24335
},
"amount": 1923
},
{
"from": {
"name": "Joël",
"email": "joel@jfelderhoff.fr",
"balance_cents": -15277
},
"to": {
"name": "Alix",
"email": "jeanne.carro@ens-lyon.fr",
"balance_cents": 22412
},
"amount": 15277
},
{
"from": {
"name": "Alban",
"email": "Alban.Faure@gmail.com",
"balance_cents": -938
},
"to": {
"name": "Alix",
"email": "jeanne.carro@ens-lyon.fr",
"balance_cents": 7135
},
"amount": 938
},
{
"from": {
"name": "Aaren",
"email": "aa.germain@yahoo.com",
"balance_cents": -20620
},
"to": {
"name": "Alix",
"email": "jeanne.carro@ens-lyon.fr",
"balance_cents": 6197
},
"amount": 6197
},
{
"from": {
"name": "Aaren",
"email": "aa.germain@yahoo.com",
"balance_cents": -14423
},
"to": {
"name": "Octave",
"email": "octave.ourcival@gmail.com",
"balance_cents": 3415
},
"amount": 3415
},
{
"from": {
"name": "Aaren",
"email": "aa.germain@yahoo.com",
"balance_cents": -11008
},
"to": {
"name": "Florian",
"email": "florian.taraveau@ens-lyon.fr",
"balance_cents": 608
},
"amount": 608
},
{
"from": {
"name": "Aaren",
"email": "aa.germain@yahoo.com",
"balance_cents": -10400
},
"to": {
"name": "Blanche",
"email": "quentinvila@laposte.net",
"balance_cents": 5364
},
"amount": 5364
},
{
"from": {
"name": "Aaren",
"email": "aa.germain@yahoo.com",
"balance_cents": -5036
},
"to": {
"name": "Adrien",
"email": "al.bert95@hotmail.fr",
"balance_cents": 36445
},
"amount": 5036
},
{
"from": {
"name": "Charline",
"email": "al.bert95@hotmail.fr",
"balance_cents": -7779
},
"to": {
"name": "Adrien",
"email": "al.bert95@hotmail.fr",
"balance_cents": 31409
},
"amount": 7779
},
{
"from": {
"name": "KT",
"email": "quentinvila@laposte.net",
"balance_cents": -10538
},
"to": {
"name": "Adrien",
"email": "al.bert95@hotmail.fr",
"balance_cents": 23630
},
"amount": 10538
},
{
"from": {
"name": "Bastien",
"email": "bastien.durain@ens-lyon.fr",
"balance_cents": -9476
},
"to": {
"name": "Adrien",
"email": "al.bert95@hotmail.fr",
"balance_cents": 13092
},
"amount": 9476
},
{
"from": {
"name": "Danaé",
"email": "danae.guiserix@ens-lyon.fr",
"balance_cents": -20939
},
"to": {
"name": "Adrien",
"email": "al.bert95@hotmail.fr",
"balance_cents": 3616
},
"amount": 3616
},
{
"from": {
"name": "Danaé",
"email": "danae.guiserix@ens-lyon.fr",
"balance_cents": -17323
},
"to": {
"name": "Laureline",
"email": "laureline.pinault@ens-lyon.fr",
"balance_cents": 17323
},
"amount": 17323
}
]

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()
}
}