Skip to content

Instantly share code, notes, and snippets.

@AnthonyMikh
Last active March 8, 2019 17:24
Show Gist options
  • Save AnthonyMikh/d2a3b47b1dbeb4849eff6c415bdeea92 to your computer and use it in GitHub Desktop.
Save AnthonyMikh/d2a3b47b1dbeb4849eff6c415bdeea92 to your computer and use it in GitHub Desktop.
Перевод числа из числовой записи в русский язык
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