use crate::request::Request;
use crate::response::best_rate_path::BestRatePath;
use crate::response::Response;
use crate::IndexMapTrait;
use floyd_warshall_alg::{FloydWarshall, FloydWarshallResult, FloydWarshallTrait};
use indexmap::map::{Entry, IndexMap};
use indexmap::IndexSet;
use num_traits::Num;
use safe_graph::{Graph, NodeTrait};
use std::clone::Clone;
use std::cmp::Ordering::{Greater, Less};
use std::fmt::{Debug, Display};
use std::ops::AddAssign;
use std::str::FromStr;
pub struct Algorithm<N, E, I> {
graph: Graph<(I, I), E>,
node_to_index: IndexMap<N, I>,
index_to_node: IndexMap<I, N>,
counter: I,
currency_exchanges: IndexMap<I, IndexSet<I>>,
}
impl<N, E, I> Algorithm<N, E, I>
where
N: Clone + Display + FromStr + IndexMapTrait + Debug,
<N as FromStr>::Err: Debug,
E: Display + FloydWarshallTrait + FromStr + Debug,
<E as FromStr>::Err: Debug,
I: NodeTrait + Num + AddAssign,
{
fn new() -> Self {
let graph = Graph::<(I, I), E>::new();
let node_to_index = IndexMap::<N, I>::new();
let index_to_node = IndexMap::<I, N>::new();
let counter = I::zero();
let currency_exchanges = IndexMap::<I, IndexSet<I>>::new();
Self {
graph,
node_to_index,
index_to_node,
counter,
currency_exchanges,
}
}
pub fn process(request: &Request<N, E>) -> Response<N, E> {
let mut alg = Algorithm::<N, E, I>::new();
alg.construct_graph(request);
let result = alg.run_customized_floyd_warshall();
alg.form_response(request, &result)
}
fn construct_graph(&mut self, request: &Request<N, E>) {
for (_, price_update) in request.get_price_updates().iter() {
let exchange_index = self.node_to_index(price_update.get_exchange().clone());
let source_currency_index =
self.node_to_index(price_update.get_source_currency().clone());
let destination_currency_index =
self.node_to_index(price_update.get_destination_currency().clone());
let a = (exchange_index, source_currency_index);
let b = (exchange_index, destination_currency_index);
self.graph
.add_edge(a, b, *price_update.get_forward_factor());
self.graph
.add_edge(b, a, *price_update.get_backward_factor());
self.collect_currency_exchanges(source_currency_index, exchange_index);
self.collect_currency_exchanges(destination_currency_index, exchange_index);
}
self.add_currency_exchanges_edges();
}
fn collect_currency_exchanges(&mut self, currency: I, exchange: I) {
match self.currency_exchanges.entry(currency) {
Entry::Occupied(o) => {
o.into_mut().insert(exchange);
}
Entry::Vacant(v) => {
let mut exchanges = IndexSet::<I>::with_capacity(1);
exchanges.insert(exchange);
v.insert(exchanges);
}
}
}
fn add_currency_exchanges_edges(&mut self) {
for (currency, exchanges) in self.currency_exchanges.iter() {
let exchanges_count = exchanges.len();
for top in 0..exchanges_count {
for below in top + 1..exchanges_count {
let a = (*exchanges.get_index(top).unwrap(), *currency);
let b = (*exchanges.get_index(below).unwrap(), *currency);
self.graph.add_edge(a, b, E::one());
self.graph.add_edge(b, a, E::one());
}
}
}
}
fn node_to_index(&mut self, s: N) -> I {
match self.node_to_index.entry(s.clone()) {
Entry::Occupied(o) => *o.get(),
Entry::Vacant(v) => {
self.counter += I::one();
v.insert(self.counter);
self.index_to_node.insert(self.counter, s);
self.counter
}
}
}
fn index_to_node(&self, i: &I) -> Option<&N> {
self.index_to_node.get(i)
}
fn run_customized_floyd_warshall(&mut self) -> FloydWarshallResult<(I, I), E> {
let mul = Box::new(|x: E, y: E| x * y);
let sharp_greater = Box::new(|x: E, y: E| x.partial_cmp(&y).unwrap_or(Less) == Greater);
let alg: FloydWarshall<E> = FloydWarshall::new_customized(mul, sharp_greater);
alg.find_paths(&self.graph)
}
fn form_response(
&mut self,
request: &Request<N, E>,
fw_result: &FloydWarshallResult<(I, I), E>,
) -> Response<N, E> {
let mut response = Response::new();
for (_, rate_request) in request.get_rate_requests().iter() {
let source_exchange_index =
self.node_to_index(rate_request.get_source_exchange().clone());
let source_currency_index =
self.node_to_index(rate_request.get_source_currency().clone());
let destination_exchange_index =
self.node_to_index(rate_request.get_destination_exchange().clone());
let destination_currency_index =
self.node_to_index(rate_request.get_destination_currency().clone());
let a = (source_exchange_index, source_currency_index);
let b = (destination_exchange_index, destination_currency_index);
let rate_raw = fw_result.get_path_rate(a, b);
let path = fw_result.collect_path_nodes(a, b);
let path = path
.into_iter()
.map(|(a, b)| {
(
self.index_to_node(&a).unwrap().clone(),
self.index_to_node(&b).unwrap().clone(),
)
})
.collect();
#[allow(clippy::single_match)]
match rate_raw {
Some(&rate) => {
let best_rate_path = BestRatePath::<N, E>::new(rate, path);
response.add_best_rate_path(best_rate_path);
}
None => {
}
}
}
response
}
}
#[cfg(test)]
mod tests {
use crate::algorithm::Algorithm;
use crate::request::Request;
use std::io::BufReader;
#[test]
fn new() {
let _alg = Algorithm::<String, f32, u32>::new();
}
#[test]
fn collect_currency_exchanges() {
let mut alg = Algorithm::<String, f32, u32>::new();
alg.collect_currency_exchanges(1, 2);
alg.collect_currency_exchanges(1, 3);
alg.collect_currency_exchanges(1, 4);
alg.collect_currency_exchanges(5, 6);
assert!(alg.currency_exchanges.get(&1).is_some());
assert!(alg.currency_exchanges.get(&5).is_some());
assert!(alg.currency_exchanges.get(&2).is_none());
assert!(alg.currency_exchanges.get(&1).unwrap().get(&2).is_some());
assert_eq!(alg.currency_exchanges.get(&1).unwrap().get(&2).unwrap(), &2);
assert!(alg.currency_exchanges.get(&1).unwrap().get(&3).is_some());
assert_eq!(alg.currency_exchanges.get(&1).unwrap().get(&3).unwrap(), &3);
assert!(alg.currency_exchanges.get(&5).unwrap().get(&6).is_some());
assert_eq!(alg.currency_exchanges.get(&5).unwrap().get(&6).unwrap(), &6);
assert!(alg.currency_exchanges.get(&1).unwrap().get(&7).is_none());
}
#[test]
fn add_currency_exchanges_edges() {
let mut alg = Algorithm::<String, f32, u32>::new();
alg.collect_currency_exchanges(1, 2);
alg.collect_currency_exchanges(1, 3);
alg.collect_currency_exchanges(1, 4);
alg.collect_currency_exchanges(5, 6);
alg.add_currency_exchanges_edges();
assert_eq!(alg.graph.edge_weight((2, 1), (3, 1)), Some(&1.0));
assert_eq!(alg.graph.edge_weight((3, 1), (2, 1)), Some(&1.0));
assert_eq!(alg.graph.edge_weight((2, 1), (4, 1)), Some(&1.0));
assert_eq!(alg.graph.edge_weight((4, 1), (2, 1)), Some(&1.0));
assert_eq!(alg.graph.edge_weight((3, 1), (4, 1)), Some(&1.0));
assert_eq!(alg.graph.edge_weight((4, 1), (3, 1)), Some(&1.0));
assert_eq!(alg.graph.contains_edge((2, 1), (6, 1)), false);
assert_eq!(alg.graph.contains_edge((6, 1), (2, 1)), false);
assert_eq!(alg.graph.contains_edge((6, 5), (2, 5)), false);
}
#[test]
fn construct_graph() {
let mut alg = Algorithm::<String, f32, u32>::new();
let text_input = "2017-11-01T09:42:23+00:00 E1 BTC USD 1000.0 0.0009
2018-11-01T09:42:23+00:00 E1 ETH USD 100.0 0.001
2018-11-01T09:42:23+00:00 E2 ETH USD 100.0 0.001
2018-11-01T09:42:23+00:00 E3 ETH BTC 100.0 0.001"
.as_bytes();
let mut input = BufReader::new(text_input);
let request = Request::<String, f32>::read_from(&mut input);
alg.construct_graph(&request);
let e1 = String::from("E1");
let e2 = String::from("E2");
let e3 = String::from("E3");
let btc = String::from("BTC");
let eth = String::from("ETH");
let usd = String::from("USD");
let e1_index = alg.node_to_index(e1.clone());
let e2_index = alg.node_to_index(e2.clone());
let e3_index = alg.node_to_index(e3.clone());
let btc_index = alg.node_to_index(btc.clone());
let eth_index = alg.node_to_index(eth.clone());
let usd_index = alg.node_to_index(usd.clone());
assert_eq!(
alg.graph
.edge_weight((e1_index, eth_index), (e2_index, eth_index)),
Some(&1.0)
);
assert_eq!(
alg.graph
.edge_weight((e2_index, eth_index), (e1_index, eth_index)),
Some(&1.0)
);
assert_eq!(
alg.graph
.edge_weight((e1_index, eth_index), (e3_index, eth_index)),
Some(&1.0)
);
assert_eq!(
alg.graph
.edge_weight((e3_index, eth_index), (e1_index, eth_index)),
Some(&1.0)
);
assert_eq!(
alg.graph
.edge_weight((e2_index, eth_index), (e3_index, eth_index)),
Some(&1.0)
);
assert_eq!(
alg.graph
.edge_weight((e3_index, eth_index), (e2_index, eth_index)),
Some(&1.0)
);
assert_eq!(
alg.graph
.edge_weight((e1_index, usd_index), (e2_index, usd_index)),
Some(&1.0)
);
assert_eq!(
alg.graph
.edge_weight((e2_index, usd_index), (e1_index, usd_index)),
Some(&1.0)
);
assert_eq!(
alg.graph
.contains_edge((e1_index, usd_index), (e3_index, usd_index)),
false
);
assert_eq!(
alg.graph
.contains_edge((e3_index, usd_index), (e1_index, usd_index)),
false
);
assert_eq!(
alg.graph
.contains_edge((e2_index, usd_index), (e3_index, usd_index)),
false
);
assert_eq!(
alg.graph
.contains_edge((e3_index, usd_index), (e2_index, usd_index)),
false
);
assert_eq!(
alg.graph
.edge_weight((e1_index, btc_index), (e3_index, btc_index)),
Some(&1.0)
);
assert_eq!(
alg.graph
.edge_weight((e3_index, btc_index), (e1_index, btc_index)),
Some(&1.0)
);
}
#[test]
fn run_customized_floyd_warshall() {
let mut alg = Algorithm::<String, f32, u32>::new();
let text_input = "2017-11-01T09:42:23+00:00 E1 BTC USD 1000.0 0.0009
2018-11-01T09:42:23+00:00 E1 ETH USD 102.0 0.009
2018-11-01T09:42:23+00:00 E2 ETH USD 100.0 0.0096
2018-11-01T09:42:23+00:00 E3 ETH BTC 0.08 10"
.as_bytes();
let mut input = BufReader::new(text_input);
let request = Request::<String, f32>::read_from(&mut input);
alg.construct_graph(&request);
let result = alg.run_customized_floyd_warshall();
let e1 = String::from("E1");
let e2 = String::from("E2");
let e3 = String::from("E3");
let btc = String::from("BTC");
let eth = String::from("ETH");
let usd = String::from("USD");
let e1_index = alg.node_to_index(e1.clone());
let e2_index = alg.node_to_index(e2.clone());
let e3_index = alg.node_to_index(e3.clone());
let _btc_index = alg.node_to_index(btc.clone());
let eth_index = alg.node_to_index(eth.clone());
let usd_index = alg.node_to_index(usd.clone());
assert_eq!(
result.get_path_rate((e1_index, eth_index), (e2_index, eth_index)),
Some(&1.0)
);
assert_eq!(
result.collect_path_nodes((e1_index, eth_index), (e2_index, eth_index)),
vec![(e1_index, eth_index), (e2_index, eth_index)]
);
assert_eq!(
result.get_path_rate((e1_index, eth_index), (e3_index, eth_index)),
Some(&1.0)
);
assert_eq!(
result.collect_path_nodes((e1_index, eth_index), (e3_index, eth_index)),
vec![(e1_index, eth_index), (e3_index, eth_index)]
);
assert_eq!(
result.get_path_rate((e2_index, eth_index), (e3_index, eth_index)),
Some(&1.0)
);
assert_eq!(
result.collect_path_nodes((e2_index, eth_index), (e3_index, eth_index)),
vec![(e2_index, eth_index), (e3_index, eth_index)]
);
assert_eq!(
result.get_path_rate((e2_index, eth_index), (e1_index, usd_index)),
Some(&102.0)
);
assert_eq!(
result.collect_path_nodes((e2_index, eth_index), (e1_index, usd_index)),
vec![
(e2_index, eth_index),
(e1_index, eth_index),
(e1_index, usd_index)
]
);
}
#[test]
fn process() {
let text_input = "2019-01-20T09:42:23+00:00 BitMEX BTC USD 3531.0 0.00026
2019-01-20T09:42:23+00:00 CoinBene BTC USD 3584.69 0.00025
2019-01-20T09:42:23+00:00 EXX BTC USD 3577.07 0.000255
2019-01-20T09:42:23+00:00 Bitfinex BTC USD 3580.60 0.000252
2019-01-20T09:42:23+00:00 OEX BTC USD 3571.26 0.00026
2019-01-20T09:42:23+00:00 Bibox ETH USD 117.36 0.0075
2019-01-20T09:42:23+00:00 Bitfinex ETH USD 117.51 0.0074
2019-01-20T09:42:23+00:00 EXX ETH USD 110.76 0.0076
2019-01-20T09:42:23+00:00 CoinBene ETH USD 117.44 0.0072
2019-01-20T09:42:23+00:00 ZBG ETH USD 117.45 0.0071
EXCHANGE_RATE_REQUEST BitMEX BTC EXX BTC
EXCHANGE_RATE_REQUEST BitMEX BTC EXX ETH
EXCHANGE_RATE_REQUEST CoinBene ETH BiBox USD"
.as_bytes();
let mut input = BufReader::new(text_input);
let request = Request::<String, f32>::read_from(&mut input);
let response = Algorithm::<String, f32, u32>::process(&request);
assert_eq!(response.get_best_rate_paths().len(), 3);
assert_eq!(response.get_best_rate_paths()[0].get_rate(), &1.0);
assert_eq!(
response.get_best_rate_paths()[0].get_path(),
&vec![
("BITMEX".to_string(), "BTC".to_string()),
("EXX".to_string(), "BTC".to_string())
]
);
assert_eq!(response.get_best_rate_paths()[1].get_rate(), &27.243645);
assert_eq!(
response.get_best_rate_paths()[1].get_path(),
&vec![
("BITMEX".to_string(), "BTC".to_string()),
("COINBENE".to_string(), "BTC".to_string()),
("COINBENE".to_string(), "USD".to_string()),
("EXX".to_string(), "USD".to_string()),
("EXX".to_string(), "ETH".to_string())
]
);
}
}