use std::convert::From;
use std::fmt;
use self::Numeral::*;
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum Numeral {
I,
V,
X,
L,
C,
D,
M,
}
impl Numeral {
pub fn value(self) -> i16 {
match self {
I => 1, V => 5,
X => 10, L => 50,
C => 100, D => 500,
M => 1000,
}
}
fn ascii_upper(self) -> char {
match self {
I => 'I', V => 'V', X => 'X', L => 'L',
C => 'C', D => 'D', M => 'M',
}
}
fn ascii_lower(self) -> char {
match self {
I => 'i', V => 'v', X => 'x', L => 'l',
C => 'c', D => 'd', M => 'm',
}
}
pub fn from_char(input: char) -> Option<Self> {
match input {
'I' | 'i' => Some(I), 'V' | 'v' => Some(V),
'X' | 'x' => Some(X), 'L' | 'l' => Some(L),
'C' | 'c' => Some(C), 'D' | 'd' => Some(D),
'M' | 'm' => Some(M), _ => None,
}
}
}
#[derive(PartialEq, Debug)]
pub struct Roman {
numerals: Vec<Numeral>,
}
impl Roman {
pub fn parse(input: &str) -> Option<Self> {
let mut numerals = Vec::new();
for c in input.chars() {
numerals.push(Numeral::from_char(c)?);
}
Some(Self { numerals })
}
pub fn value(&self) -> i16 {
let mut total = 0;
let mut max = 0;
for n in self.numerals.iter().map(|n| n.value()).rev() {
total += if n >= max { n } else { -n };
if max < n {
max = n;
}
}
total
}
pub fn value_checked(&self) -> Option<i16> {
let mut total: i16 = 0;
let mut max = 0;
for n in self.numerals.iter().map(|n| n.value()).rev() {
let amount_to_add = if n >= max { n } else { -n };
total = total.checked_add(amount_to_add)?;
if max < n {
max = n;
}
}
Some(total)
}
}
impl fmt::LowerHex for Roman {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for n in &self.numerals {
write!(f, "{}", n.ascii_lower())?
}
Ok(())
}
}
impl fmt::UpperHex for Roman {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for n in &self.numerals {
write!(f, "{}", n.ascii_upper())?;
}
Ok(())
}
}
impl From<Vec<Numeral>> for Roman {
fn from(numerals: Vec<Numeral>) -> Self {
Self { numerals }
}
}
impl From<i16> for Roman {
fn from(mut number: i16) -> Self {
assert!(number > 0);
let mut numerals = Vec::new();
for &(secondary, primary) in &[ (C, M), (C, D),
(X, C), (X, L),
(I, X), (I, V) ] {
while number >= primary.value() {
number -= primary.value();
numerals.push(primary);
}
let difference = primary.value() - secondary.value();
if number >= difference {
number -= difference;
numerals.push(secondary);
numerals.push(primary);
}
}
while number > 0 {
number -= 1;
numerals.push(I);
}
Self { numerals }
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_many_numbers() {
for i in 1 .. 4321 {
assert_eq!(i, Roman::from(i).value())
}
for i in 1 .. 4321 {
assert_eq!(Some(i), Roman::from(i).value_checked())
}
}
#[test]
fn test_big_numbers() {
for i in 32700 .. 32767 {
assert_eq!(i, Roman::from(i).value());
}
for i in 32700 .. 32767 {
assert_eq!(Some(i), Roman::from(i).value_checked());
}
}
#[test]
fn value_checked_err_on_large() {
assert_eq!(
Roman::parse("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM")
.unwrap()
.value_checked(),
None
);
}
#[test]
#[should_panic]
fn value_panic_on_large() {
Roman::parse("MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM")
.unwrap()
.value();
}
}