-
-
Save AnthonyMikh/d2a3b47b1dbeb4849eff6c415bdeea92 to your computer and use it in GitHub Desktop.
Перевод числа из числовой записи в русский язык
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mod fmt { | |
use std::convert::From; | |
struct StrBuf(String); | |
impl From<String> for StrBuf { | |
fn from(s: String) -> Self { StrBuf(s) } | |
} | |
impl From<StrBuf> for String { | |
fn from(b: StrBuf) -> Self { b.0 } | |
} | |
struct LenBuf { | |
len: usize, | |
last_is_space: bool, | |
} | |
impl LenBuf { | |
fn new() -> Self { | |
Self { len: 0, last_is_space: false } | |
} | |
fn inc(&mut self, add: usize) { | |
self.len = self.len.checked_add(add).expect("capacity overflow"); | |
} | |
fn into_inner(self) -> usize { self.len } | |
} | |
pub trait Buf { | |
fn append(&mut self, s: &str); | |
fn append_word(&mut self, s: &str); | |
} | |
impl Buf for StrBuf { | |
fn append(&mut self, s: &str) { | |
self.0.push_str(s) | |
} | |
fn append_word(&mut self, s: &str) { | |
if self.0.chars().next_back().unwrap_or(' ') != ' ' { | |
self.0.push(' ') | |
} | |
self.append(s) | |
} | |
} | |
impl Buf for LenBuf { | |
fn append(&mut self, s: &str) { | |
self.inc(s.len()); | |
if let Some(c) = s.chars().next_back() { | |
self.last_is_space = c == ' '; | |
} | |
} | |
fn append_word(&mut self, s: &str) { | |
if !self.last_is_space { | |
self.inc(1); | |
self.last_is_space = true; | |
} | |
self.append(s) | |
} | |
} | |
pub trait WriteIn { | |
fn write_in<B: Buf>(&self, buf: &mut B); | |
} | |
pub trait AsWord { | |
fn as_word(&self) -> &'static str; | |
} | |
impl AsWord for &'static str { | |
fn as_word(&self) -> &'static str { *self } | |
} | |
impl<T: AsWord> WriteIn for T { | |
fn write_in<B: Buf>(&self, buf: &mut B) { | |
buf.append_word(self.as_word()) | |
} | |
} | |
impl<T: WriteIn> WriteIn for Option<T> { | |
fn write_in<B: Buf>(&self, buf: &mut B) { | |
self.as_ref().map(|val| val.write_in(buf)); | |
} | |
} | |
pub fn fmt_russian<T: WriteIn>(val: &T) -> String { | |
let mut buf = LenBuf::new(); | |
val.write_in(&mut buf); | |
let mut buf = StrBuf::from(String::with_capacity(buf.into_inner())); | |
val.write_in(&mut buf); | |
buf.into() | |
} | |
} | |
mod grammar { | |
use digit::Digit; | |
use fmt::AsWord; | |
#[derive(Clone, Copy)] | |
pub enum Number { | |
Singular = 0, | |
Dual, | |
Plural, | |
} | |
pub enum Gender { | |
Masculine, | |
Feminine, | |
} | |
pub struct FeminineNum(pub Digit); | |
impl AsWord for FeminineNum { | |
fn as_word(&self) -> &'static str { | |
match self.0 { | |
Digit::D1 => "одна", | |
Digit::D2 => "две", | |
other => other.as_word(), | |
} | |
} | |
} | |
impl AsWord for ([&'static str; 3], Number) { | |
fn as_word(&self) -> &'static str { | |
self.0[self.1 as usize] | |
} | |
} | |
impl AsWord for ([&'static str; 3], Option<Number>) { | |
fn as_word(&self) -> &'static str { | |
self.0[self.1.unwrap_or(Number::Plural) as usize] | |
} | |
} | |
} | |
mod digit { | |
use fmt; | |
#[derive(Clone, Copy)] | |
pub enum Digit { | |
D1 = 0, D2, D3, D4, D5, D6, D7, D8, D9, | |
} | |
impl Digit { | |
pub fn new(n: u32) -> Option<Self> { | |
use self::Digit::*; | |
const ROW: [Digit; 9] = [D1, D2, D3, D4, D5, D6, D7, D8, D9]; | |
match n { | |
1..=9 => Some(ROW[n as usize - 1]), | |
_ => None, | |
} | |
} | |
pub fn grammar_number(self) -> ::grammar::Number { | |
use grammar::Number; | |
use self::Digit::*; | |
match self { | |
D1 => Number::Singular, | |
D2 | D3 | D4 => Number::Dual, | |
_ => Number::Plural, | |
} | |
} | |
} | |
const AS_WORD: [&str; 9] = | |
["один", "два", "три", "четыре", "пять", "шесть", "семь", "восемь", "девять"]; | |
impl fmt::AsWord for Digit { | |
fn as_word(&self) -> &'static str { | |
AS_WORD[*self as usize] | |
} | |
} | |
} | |
mod numerical { | |
use digit::Digit; | |
use fmt::{self, AsWord}; | |
use grammar::{Gender, FeminineNum}; | |
pub struct PlusTen(pub Digit); | |
const PLUS_TEN_AS_WORD: [&str; 9] = [ | |
"одиннадцать", | |
"двенадцать", | |
"тринадцать", | |
"четырнадцать", | |
"пятнадцать", | |
"шестнадцать", | |
"семнадцать", | |
"восемнадцать", | |
"девятнадцать" | |
]; | |
impl AsWord for PlusTen { | |
fn as_word(&self) -> &'static str { | |
PLUS_TEN_AS_WORD[self.0 as usize] | |
} | |
} | |
pub struct Tens(pub Digit); | |
const TENS_AS_WORD: [&str; 9] = [ | |
"десять", | |
"двадцать", | |
"тридцать", | |
"сорок", | |
"пятьдесят", | |
"шестьдесят", | |
"семьдесят", | |
"восемьдесят", | |
"девяносто", | |
]; | |
impl AsWord for Tens { | |
fn as_word(&self) -> &'static str { | |
TENS_AS_WORD[self.0 as usize] | |
} | |
} | |
pub struct Hundreds(pub Digit); | |
const HUNDREDS_AS_WORD: [&str; 9] = [ | |
"сто", | |
"двести", | |
"триста", | |
"четыреста", | |
"пятьсот", | |
"шестьсот", | |
"семьсот", | |
"восемьсот", | |
"девятьсот", | |
]; | |
impl AsWord for Hundreds { | |
fn as_word(&self) -> &'static str { | |
HUNDREDS_AS_WORD[self.0 as usize] | |
} | |
} | |
pub struct ThousandPart { | |
pub hundreds: Option<Digit>, | |
pub tens: Option<Digit>, | |
pub ones: Option<Digit>, | |
} | |
impl ThousandPart { | |
pub fn new(n: u32) -> Option<Self> { | |
if n >= 1000 { | |
return None | |
} | |
Some(Self { | |
hundreds: Digit::new(n / 100), | |
tens: Digit::new((n / 10) % 10), | |
ones: Digit::new(n % 10), | |
}) | |
} | |
pub fn is_zero(&self) -> bool { | |
self.hundreds.is_none() && | |
self.tens.is_none() && | |
self.ones.is_none() | |
} | |
} | |
impl<'a> fmt::WriteIn for (&'a ThousandPart, Gender) { | |
fn write_in<B: fmt::Buf>(&self, buf: &mut B) { | |
let (part, gender) = self; | |
part.hundreds.map(Hundreds).write_in(buf); | |
match (part.tens, part.ones) { | |
(Some(Digit::D1), Some(last)) => PlusTen(last).write_in(buf), | |
(tens, ones) => { | |
tens.map(Tens).write_in(buf); | |
match gender { | |
Gender::Masculine => ones.write_in(buf), | |
Gender::Feminine => ones.map(FeminineNum).write_in(buf), | |
} | |
}, | |
} | |
} | |
} | |
} | |
pub mod whole_number { | |
use numerical::ThousandPart; | |
use fmt; | |
use grammar::Gender; | |
pub struct WholeNumber { | |
millions: ThousandPart, | |
thousands: ThousandPart, | |
ones: ThousandPart, | |
} | |
impl WholeNumber { | |
pub fn new(n: u32) -> Option<Self> { | |
Some(Self { | |
millions: ThousandPart::new(n / 1_000_000)?, | |
thousands: ThousandPart::new((n / 1000) % 1000)?, | |
ones: ThousandPart::new(n % 1000)?, | |
}) | |
} | |
} | |
impl fmt::WriteIn for WholeNumber { | |
fn write_in<B: fmt::Buf>(&self, buf: &mut B) { | |
use digit::Digit; | |
let Self { millions, thousands, ones } = self; | |
(millions, Gender::Masculine).write_in(buf); | |
if !millions.is_zero() { | |
( | |
["миллион", "миллиона", "миллионов"], | |
millions.ones.map(Digit::grammar_number) | |
).write_in(buf); | |
} | |
(thousands, Gender::Feminine).write_in(buf); | |
if !thousands.is_zero() { | |
( | |
["тысяча", "тысячи","тысяч"], | |
thousands.ones.map(Digit::grammar_number) | |
).write_in(buf); | |
} | |
(ones, Gender::Masculine).write_in(buf); | |
if millions.is_zero() | |
&& thousands.is_zero() | |
&& ones.is_zero() { | |
"ноль".write_in(buf) | |
} | |
} | |
} | |
} | |
fn main() { | |
use fmt::fmt_russian; | |
use whole_number::*; | |
let num = WholeNumber::new(100_000_340).unwrap(); | |
println!("{}", fmt_russian(&num)); | |
let num = WholeNumber::new(123_123_123).unwrap(); | |
println!("{}", fmt_russian(&num)); | |
let num = WholeNumber::new(41_201_009).unwrap(); | |
println!("{}", fmt_russian(&num)); | |
let num = WholeNumber::new(0).unwrap(); | |
println!("{}", fmt_russian(&num)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment