Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- use std::collections::HashMap;
- use std::error::Error;
- use std::fmt::{Display, self};
- use std::iter::FromIterator;
- #[derive(Debug)]
- pub struct Terminal {
- prices: HashMap<String, PriceScale>,
- }
- #[derive(Debug)]
- pub struct PriceScale {
- prices: Vec<PricePoint>,
- }
- impl FromIterator<(u32, f64)> for PriceScale {
- fn from_iter<T>(it: T) -> Self
- where
- T: IntoIterator<Item = (u32, f64)>,
- {
- let mut prices = it.into_iter()
- .map(|(quantity, price)| PricePoint {quantity, price})
- .collect::<Vec<_>>();
- prices.sort_by_key(|point| std::cmp::Reverse(point.quantity));
- PriceScale {prices}
- }
- }
- #[derive(Debug)]
- pub struct PricePoint {
- quantity: u32,
- price: f64,
- }
- impl Terminal {
- pub fn new<P, S, Q>(prices: P) -> Self
- where
- P: IntoIterator<Item = (S, Q)>,
- S: Into<String>,
- Q: IntoIterator<Item = (u32, f64)>,
- {
- let prices = prices.into_iter()
- .map(|(s, p)| (s.into(), p.into_iter().collect()))
- .collect();
- Terminal {prices}
- }
- pub fn scan<I>(&self, cart: I) -> Result<f64, TerminalError>
- where
- I: IntoIterator,
- I::Item: Into<String>,
- {
- cart
- .into_iter()
- .map(Into::into)
- .fold(HashMap::new(), |mut map, product| {
- *map.entry(product).or_insert(0) += 1;
- map
- })
- .into_iter()
- .try_fold(0.0, |total, (product, quantity)| {
- let (price, quantity) = self.prices.get(&product)
- .ok_or_else(|| TerminalError::UnknownProduct(
- product.clone()))?
- .prices.iter()
- .fold((0.0, quantity), |(mut cost, mut q), price_point| {
- cost += price_point.price *
- (q / price_point.quantity) as f64;
- q %= price_point.quantity;
- (cost, q)
- });
- if quantity == 0 {
- Ok(total + price)
- } else {
- Err(TerminalError::NoUnitPrice(product))
- }
- })
- }
- }
- #[derive(Debug)]
- pub enum TerminalError {
- NoUnitPrice(String),
- UnknownProduct(String),
- }
- impl Display for TerminalError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- TerminalError::NoUnitPrice(id) =>
- write!(f, r#"No unit price for product "{}""#, id),
- TerminalError::UnknownProduct(id) =>
- write!(f, r#"Unknown product "{}""#, id),
- }
- }
- }
- impl Error for TerminalError {}
- #[cfg(test)]
- mod tests {
- use super::Terminal;
- fn new_terminal() -> Terminal {
- Terminal::new([
- (
- "A",
- [(4, 7.0), (1, 2.0)][..].iter().cloned(),
- ),
- (
- "B",
- [(1, 12.0)][..].iter().cloned(),
- ),
- (
- "C",
- [(6, 6.0), (1, 1.25)][..].iter().cloned(),
- ),
- (
- "D",
- [(1, 0.15)][..].iter().cloned(),
- ),
- ].iter().cloned())
- }
- #[test]
- fn it_works() {
- let expectations = [
- ("ABCDABAA", 32.40),
- ("CCCCCCC", 7.25),
- ("ABCD", 15.40),
- ];
- for &(purchase, expected_price) in &expectations {
- assert_eq!(
- new_terminal()
- .scan(purchase.chars().map(|c| c.to_string()))
- .unwrap(),
- expected_price,
- "Purchase {}",
- purchase,
- );
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement