From 33de1a47781b5da579185fe78a567c9cb95df06b Mon Sep 17 00:00:00 2001 From: jfelderh Date: Mon, 28 Oct 2024 14:26:06 +0100 Subject: [PATCH] first commit --- .gitignore | 1 + Cargo.lock | 216 +++++++++++++++++++++++++++++++++++ Cargo.toml | 14 +++ repartition.json | 275 +++++++++++++++++++++++++++++++++++++++++++++ src/io.rs | 50 +++++++++ src/main.rs | 76 +++++++++++++ src/repartition.rs | 96 ++++++++++++++++ 7 files changed, 728 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 repartition.json create mode 100644 src/io.rs create mode 100644 src/main.rs create mode 100644 src/repartition.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..63cd6d0 --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..284e949 --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/repartition.json b/repartition.json new file mode 100644 index 0000000..34cf3cb --- /dev/null +++ b/repartition.json @@ -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 + } +] diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..0c22a96 --- /dev/null +++ b/src/io.rs @@ -0,0 +1,50 @@ +use super::repartition::*; + +const FORMAT_TEXT: &'static str = " + + + + +
+ +

Balance totale (doit être égal à zéro) : {}€

+ + + + +"; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..477b4e7 --- /dev/null +++ b/src/main.rs @@ -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, + + /// seed for random generation + #[argh(option, short = 's')] + seed: Option, + + /// 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, +} + +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::().unwrap().trim_end().to_string(), + balance_cents: (record[1].parse::().unwrap() * 100.) as i32, + email: record[2].parse::().unwrap().trim_end().to_string(), + }) + .collect::>(); + 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"); + } +} diff --git a/src/repartition.rs b/src/repartition.rs new file mode 100644 index 0000000..85df28f --- /dev/null +++ b/src/repartition.rs @@ -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, + + receivers: Vec, +} + +impl RandomRepartitionBuilder { + pub fn new(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::>(); + let mut receivers = user_list + .iter() + .filter(|u| u.balance_cents > 0) + .cloned() + .collect::>(); + 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); + +type Evaluation = (usize); + +impl Repartition { + pub fn evaluate(&self) -> Evaluation { + self.0.len() + } +}