/* * Copyright (C) 2022 Aravinth Manivannan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::collections::HashMap; use std::rc::Rc; pub trait Purchasable { fn cost(&self) -> f64; fn name(&self) -> &str; fn paid_by(&self) -> &dyn Actor; } pub struct Split { purchase: Rc, split_strategy: SplitStrategy, } #[derive(Default)] pub struct SplitHashMap { shares: HashMap, f64)>, } impl SplitHashMap { pub fn iter(&self) -> SplitHashMapIter { let iter = self.shares.iter(); SplitHashMapIter { iter } } pub fn insert(&mut self, actor: Rc, amount: f64) { let name = actor.name().to_owned(); self.shares.insert(name, (actor, amount)); } pub fn get(&mut self, actor: Rc) -> Option { if let Some((_actor, cost)) = self.shares.get(actor.name()) { Some(*cost) } else { None } } } pub struct SplitHashMapIter<'a> { iter: std::collections::hash_map::Iter<'a, String, (Rc, f64)>, } impl<'a> Iterator for SplitHashMapIter<'a> { type Item = (Rc, f64); fn next(&mut self) -> Option { if let Some((_, (actor, cost))) = self.iter.next() { Some((actor.clone(), *cost)) } else { None } } } impl Split { pub fn validate(&self) -> bool { self.split_strategy.validate(self.purchase.as_ref()) } } /* * SPLITTING STRATEGIES: * Unequal: Each actor's split is specified in specific amounts * Shares: Each actor's split is specified in specific percentage * Equal: Each actor's split is computed by splitting total expense among all participating actors */ pub enum SplitStrategy { Unequal(SplitHashMap), Shares(SplitHashMap), Equal(Vec>), } impl SplitStrategy { pub fn validate(&self, item: &dyn Purchasable) -> bool { match self { SplitStrategy::Unequal(config) => { let mut total = 0.00; for (_actor, share) in config.iter() { total += share; } let cost = item.cost(); println!("Unequal sharing: expected {cost} got {total}"); total == cost } SplitStrategy::Shares(config) => { let mut total = 0.00; let cost = item.cost(); let mut total_shares = 0.00; for (_actor, share) in config.iter() { total_shares += share; total += cost * share; } println!("Unequal sharing: expected shares 1 got {total_shares}"); println!("Unequal sharing: expected {cost} got {total}"); total_shares == 1.00 && total == cost } SplitStrategy::Equal(c) => !c.is_empty(), } } } pub trait Actor { fn spends(&mut self, i: Split); fn owes(&mut self, i: Split); // name must be unique in a namespace fn name(&self) -> &str; } pub struct Person { name: String, spent: Vec, owes: Vec, } impl Person { pub fn new(name: String) -> Self { let owes = Vec::default(); let spent = Vec::default(); Self { name, spent, owes } } } impl Actor for Person { fn spends(&mut self, split: Split) { if split.purchase.paid_by().name() == self.name() { self.spent.push(split); } else { unimplemented!("Raise error: Actor didn't pay for purchase") } } fn owes(&mut self, split: Split) { if split.purchase.paid_by().name() == self.name() { unimplemented!("Raise error: Actor didn't paid for purchase; can't own money") } else { self.owes.push(split); } } fn name(&self) -> &str { &self.name } } pub struct Item { cost: f64, name: String, payer: Rc, } impl Item { pub fn new(name: String, cost: f64, payer: Rc) -> Self { Self { cost, name, payer } } } impl Purchasable for Item { fn cost(&self) -> f64 { self.cost } fn name(&self) -> &str { &self.name } fn paid_by(&self) -> &dyn Actor { self.payer.as_ref() } } //impl Actor for Person { // fn spends(&self, i: &I); // fn owes(&self, amount: usize); //} #[cfg(test)] mod tests { use super::*; #[test] fn split_works() { let person1: Rc = Rc::new(Person::new("person1".into())); let person2: Rc = Rc::new(Person::new("person2".into())); let person3: Rc = Rc::new(Person::new("person3".into())); let person4: Rc = Rc::new(Person::new("person4".into())); let item = Item::new("test item".into(), 240.00, person1.clone()); let actors = vec![ person1.clone(), person2.clone(), person3.clone(), person4.clone(), ]; // equal split let strategy = SplitStrategy::Equal(actors.clone()); assert!(strategy.validate(&item)); // share split let mut split = SplitHashMap::default(); split.insert(person1.clone(), 0.3); split.insert(person2.clone(), 0.4); split.insert(person3.clone(), 0.2); split.insert(person4.clone(), 0.1); let strategy = SplitStrategy::Shares(split); assert!(strategy.validate(&item)); // Unequal split let mut split = SplitHashMap::default(); split.insert(person1.clone(), 0.3 * item.cost()); split.insert(person2.clone(), 0.4 * item.cost()); split.insert(person3.clone(), 0.2 * item.cost()); split.insert(person4.clone(), 0.1 * item.cost()); let strategy = SplitStrategy::Unequal(split); assert!(strategy.validate(&item)); } }