1use crate::models::{Currency, HasName, Money};
2use chrono::{Duration, NaiveDate};
3use num::rational::BigRational;
4use num::BigInt;
5use std::cmp::Ordering;
6use std::collections::{HashMap, HashSet};
7use std::fmt;
8use std::fmt::{Display, Formatter};
9use std::rc::Rc;
10
11#[derive(Debug, Clone)]
13pub struct Price {
14 date: NaiveDate,
15 commodity: Rc<Currency>,
16 price: Money,
17}
18
19impl Price {
20 pub fn new(date: NaiveDate, commodity: Rc<Currency>, price: Money) -> Price {
21 Price {
22 date,
23 commodity,
24 price,
25 }
26 }
27 pub fn get_price(&self) -> Money {
28 self.price.clone()
29 }
30}
31
32impl Display for Price {
33 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
34 write!(f, "{} {} {}", self.date, self.commodity, self.get_price())
35 }
36}
37
38#[derive(Debug, Copy, Clone)]
39pub enum PriceType {
40 Total,
41 PerUnit,
42}
43
44pub fn conversion(
51 currency: Rc<Currency>,
52 date: NaiveDate,
53 prices: &[Price],
54) -> HashMap<Rc<Currency>, BigRational> {
55 let source = Node {
57 currency: currency.clone(),
58 date,
59 };
60 let mut graph = Graph::from_prices(prices, source);
61 let mut distances = HashMap::new();
62 let mut paths: HashMap<Rc<Node>, Vec<Rc<Edge>>> = HashMap::new();
63 let mut queue = vec![];
64
65 for node in graph.nodes.iter() {
67 if node.currency == currency {
69 distances.insert(node.clone(), Some(date - node.date));
70 } else {
73 distances.insert(node.clone(), None);
74 }
76 queue.push(node.clone());
77 }
78 while !queue.is_empty() {
79 queue.sort_by(|a, b| cmp(distances.get(b).unwrap(), distances.get(a).unwrap()));
81
82 let v = queue.pop().unwrap();
84 if distances.get(v.as_ref()).unwrap().is_none() {
86 break;
87 }
88
89 let current_path = if let Some(path) = paths.get(v.as_ref()) {
91 path.clone()
92 } else {
93 Vec::new()
94 };
95
96 for (u, e) in graph.get_neighbours(v.as_ref()).iter() {
98 let alt = distances.get(v.as_ref()).unwrap().unwrap() + e.length();
100 let distance = distances.get(u.as_ref()).unwrap();
101 let mut update = distance.is_none();
102 if !update {
103 update = alt < distance.unwrap();
104 }
105 if !update {
106 continue;
107 }
108 distances.insert(u.clone(), Some(alt));
109 let mut u_path = current_path.clone();
110 u_path.push(e.clone());
111 paths.insert(u.clone(), u_path);
112 }
113 }
114
115 let mut multipliers = HashMap::new();
117 let mut inserted = HashMap::new();
118 for (k, v) in paths.iter() {
119 let mut mult = BigRational::new(BigInt::from(1), BigInt::from(1));
120 let mut currency = k.currency.clone();
121 match inserted.get(&k.currency) {
122 Some(x) => {
123 if *x > k.date {
124 continue;
125 }
126 }
127 None => {
128 inserted.insert(currency.clone(), k.date);
129 }
130 }
131 for edge in v.iter().rev() {
132 match edge.as_ref().price.as_ref() {
133 None => (), Some(price) => {
135 if currency == edge.from.currency {
136 mult *= price.get_price().get_amount();
137 currency = edge.to.currency.clone();
138 } else {
139 mult /= price.get_price().get_amount();
140 currency = edge.from.currency.clone();
141 }
142 }
143 }
144 }
145 multipliers.insert(k.currency.clone(), mult);
146 }
147 multipliers
148}
149
150#[derive(Debug, Eq, PartialEq, Hash, Clone)]
151pub struct Node {
152 pub(crate) currency: Rc<Currency>,
153 date: NaiveDate,
154}
155
156#[derive(Debug, Clone)]
157pub struct Edge {
158 price: Option<Price>,
159 from: Rc<Node>,
160 to: Rc<Node>,
161}
162
163impl Edge {
164 fn length(&self) -> Duration {
165 if self.from.date > self.to.date {
166 self.from.date - self.to.date
167 } else {
168 self.to.date - self.from.date
169 }
170 }
171}
172
173#[derive(Debug, Clone)]
174struct NodeEdge {
175 node: Rc<Node>,
176 edge: Rc<Edge>,
177}
178#[derive(Debug, Clone)]
180struct Graph {
181 nodes: Vec<Rc<Node>>,
182 edges: Vec<Rc<Edge>>,
183}
184
185impl Graph {
186 fn from_prices(prices: &[Price], source: Node) -> Self {
190 let mut nodes = HashMap::new();
191 let mut edges = Vec::new();
192
193 let mut currency_dates = HashSet::new();
195 currency_dates.insert((source.currency.clone(), source.date));
196 let mut prices_nodup = HashMap::new();
198 for p in prices.iter() {
199 if p.date >= source.date {
201 continue;
202 };
203 let commodities =
204 if p.price.get_commodity().unwrap().get_name() < p.commodity.as_ref().get_name() {
205 (p.price.get_commodity().unwrap(), p.commodity.clone())
206 } else {
207 (p.commodity.clone(), p.price.get_commodity().unwrap())
208 };
209 match prices_nodup.get(&commodities) {
210 None => {
211 prices_nodup.insert(commodities.clone(), p.clone());
212 }
213 Some(x) => {
214 if x.date < p.date {
215 prices_nodup.insert(commodities.clone(), p.clone());
216 }
217 }
218 }
219 }
220 for (_, p) in prices_nodup.iter() {
221 let commodities =
222 if p.price.get_commodity().unwrap().get_name() < p.commodity.as_ref().get_name() {
223 (p.price.get_commodity().unwrap(), p.commodity.clone())
224 } else {
225 (p.commodity.clone(), p.price.get_commodity().unwrap())
226 };
227 let c_vec = vec![commodities.0.clone(), commodities.1.clone()];
228 for c in c_vec {
229 currency_dates.insert((c.clone(), p.date));
230 }
231 }
232 for (c, d) in currency_dates.iter() {
234 nodes.insert(
235 (c.clone(), *d),
236 Rc::new(Node {
237 currency: c.clone(),
238 date: *d,
239 }),
240 );
241 }
242 for (_, p) in prices_nodup.iter() {
244 let from = nodes.get(&(p.commodity.clone(), p.date)).unwrap().clone();
245 let to = nodes
246 .get(&(p.price.get_commodity().unwrap(), p.date))
247 .unwrap()
248 .clone();
249 edges.push(Rc::new(Edge {
250 price: Some(p.clone()),
251 from: from.clone(),
252 to: to.clone(),
253 }));
254 }
255
256 let vec_node: Vec<Rc<Node>> = nodes.iter().map(|x| x.1.clone()).collect();
259 let n = vec_node.len();
260 for i in 0..n {
261 for j in i..n {
262 if vec_node[i].currency == vec_node[j].currency {
263 edges.push(Rc::new(Edge {
264 price: None,
265 from: vec_node[i].clone(),
266 to: vec_node[j].clone(),
267 }))
268 }
269 }
270 }
271 Graph {
274 nodes: nodes.iter().map(|x| x.1.clone()).collect(),
275 edges,
276 }
277 }
278
279 fn get_neighbours(&mut self, node: &Node) -> Vec<(Rc<Node>, Rc<Edge>)> {
280 let mut neighbours = Vec::new();
281 for edge in self.edges.iter() {
282 if edge.from.as_ref() == node {
283 neighbours.push((edge.to.clone(), edge.clone()));
284 } else if edge.to.as_ref() == node {
285 neighbours.push((edge.from.clone(), edge.clone()));
286 }
287 }
288 neighbours
289 }
290}
291
292fn cmp(this: &Option<Duration>, other: &Option<Duration>) -> Ordering {
293 match this {
294 None => match other {
295 None => Ordering::Equal,
296 Some(_) => Ordering::Greater,
297 },
298 Some(s) => match other {
299 None => Ordering::Less,
300 Some(o) => s.cmp(o),
301 },
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::parser::Tokenizer;
309 use crate::CommonOpts;
310 use chrono::Utc;
311 use std::convert::TryFrom;
312 use std::path::PathBuf;
313 use structopt::StructOpt;
314
315 #[test]
316 fn test_graph() {
317 let path = PathBuf::from("tests/example_files/demo.ledger");
319 let mut tokenizer = Tokenizer::try_from(&path).unwrap();
320 let options = CommonOpts::from_iter(["", "-f", ""].iter());
321 let items = tokenizer.tokenize(&options);
322 let ledger = items.to_ledger(&options).unwrap();
323
324 let currency = ledger.commodities.get("EUR").unwrap();
325 let multipliers = conversion(
326 currency.clone(),
327 Utc::now().naive_local().date(),
328 &ledger.prices,
329 );
330 assert_eq!(multipliers.len(), 7);
331 }
332}