среда, 27 января 2010 г.

Вывод чисел прописью

Одна из типовых задачек которые вы можете выполнять в институте на уроках информатики может быть задачей о переводе числового представление в прописное или перевод из десятичной системы в шестнадцатеричную.

Так как все эти задачки уже давно решены, вам нет необходимости изобретать велосипед, а достаточно просто обратится к исходному коду и найти готовый пример.



Так перевод чисел из различных систем исчисления в друг друга может быть найден в системной (POSIX) функции printf исходный код которой доступен в свободном доступе. Если вы не уверены в том где находится нужные вам исходники воспользуйтесь поиском codesearch.google.com и задайте в условиях требуемое имя фнукции (printf).

Перевод делается по простому алгоритму.

- В начале вы считываете входное значение и преобразуете его в числовой формат.
- Следующим шагом является перевод числа в требуемую систему исчисления
- Затем, перевод полученного значения в прописное представление:
- Пропись может иметь языковую ориентацию и иметь свои особенности перевода в русскую, английскую, немецкую или в еврит. Где по разному пишутся и взаимодействуют между собой числительные.
- Кроме того иметь различные переводы при сохранеии языковых конструкций.

Таким образом мы получаем несколько ключевых объектов:
- Входной поток (реализован через stl::iostream)
- Перевод числа в текстовое представление (реализован через Encoding)
- Языковые особенности (реализован через Lingvo_RU)
- Локализация (реализован через Locale_RU)

Комбинируя все возможные варианты представленных классов, и дописывая новые без труда можно получить перевод чисел в пропись для любого существующего языка.

Перевод строкового представления числа (уже строки а не машинного числа) в пропись делается простыми шагами. Сам стиль реализации подразумевает атомарность всех вычислений (стиль erlang) и отсутствие классов состояния\отсутствие сайд эффектов. Вычисления производятся через рекурсивный вызов одной и той же функции, каждый раз откусывающей начало массива, и передающая сама в себя конец не обработанных данных.

По просу говоря, функция обрабатывает только ту часть, которую может обработать в данный момент, и передает на следующий уровень остаток, который еще не обработан. Такая схема может предпологать создание распределенной сети вычислений. Позволяющий преобразовывать гигантские объемы входных чисел в текстовое представление. Так как большинство порядков чисел не коррелированы и одно строковое представление числа может быть преобразованно в текстовое представление на нескольких независмых машинах по схеме Map/Reduce. Но это отдельная тема.

В нашем же случае, расчет делается следующими рекурсивными шагами, каждый из которых отъедает начало списка:
- Определение разрядности
- Перевод найденного разряда в текст

например число 1000000 (один миллион) переводится в текст за следующее число операций:

первый шаг:
1(0000000) = разрядность миллион, значение 1

второй шаг:
000(000) = разрядность тысяча, значение отсутствует

третий шаг:
000() = разрядность единицы, значение отсутствует.

Еще один момент который я хотел бы рассмотреть подробно - это перевод разряда в текст.

Любое число попадающее в эту функции подвергается анализу в лингвистическом модуле. Он определяет размерность числительного и пол разряда. Как это работает?

Размерность числетельных влияет на то какое окончание будет у разряда. Например: одна тысяч(а), две тысяч(и), пять тысяч(). И в свою очередь, что бы совсем было не сладко, порядок будет влиять на числительное. Например: од(ин) миллион, од(а) тысяча и так далее до дрожи в коленках.



#include <string>
#include <algorithm>
#include <vector>
#include <locale>
#include <iostream>

class Error : public std::exception
{
public:
  Error(const char* text) : std::exception(text)
  {
  }
};

class Locale
{
public:
  enum Digits{ NONE, UNITS, TENS, HUNDRED, THOUSAND, MILLION, BILLION, TRILLION };
};

class Locale_RU : public Locale
{
public:
  enum Sex {SEXNONE, MALE, FEMALE};

  // од(ин), двадцать один миллиард().
  // дв(а), три, четыре миллиард(а).
  // пять, шесть, семь, восемь, девять, десять миллиард(ов).

  // одн(а), двадцать одина тысяч(а).
  // дв(е), три, четыри тысяч(и).
  // пять, шесть, семь, восемь, девять, десять тысяч().
  enum Count {COUNTNONE, ONE, TWOTHREEFOUR, FIVEMORE};

  static Sex get_grender(Locale::Digits type)
  {
    switch(type)
    {
    case THOUSAND:
      return FEMALE;
      break;

    case MILLION:
    case BILLION:
    case TRILLION:
      return MALE;
      break;

    default:
      return SEXNONE;
    }
  }

  static Count to_simple(char number)
  {
    int index = number - '0';

    switch(index)
    {
    case 1:
      return ONE;
      break;

    case 2:
    case 3:
    case 4:
      return TWOTHREEFOUR;
      break;

    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
    case 0:
      return FIVEMORE;

    default:
      throw Error("bad to_simple");
    }
  }

  static const char* to_simple(char number, Locale::Digits type)
  {
    std::vector<const char*> table;

    switch(get_grender(type))
    {
    case FEMALE:
      {
        const char* t[]=
          {"ноль", "одна", "две", "три", "четыре", "пять", "шесть", "семь", "восемь", "девять"};
        table.assign(&t[0], &t[sizeof(t) / sizeof(const char*)]);
      }
      break;

    case SEXNONE:
    case MALE:
      {
        const char* t[]=
          {"ноль", "один", "два", "три", "четыре", "пять", "шесть", "семь", "восемь", "девять"};
        table.assign(&t[0], &t[sizeof(t) / sizeof(const char*)]);
      }
      break;
    }

    int i = number - '0';
    return table.at(i);
  }

  static const char* to_ten(int number)
  {
    int index = number - '0';

    const char* table[]= {"десять", "одиннадцать", "двенадцать", "тринадцать", "четырнадцать",
        "пятнадцать", "шестнадцать", "семнадцать", "восемнадцать", "девятнадцать"};

    if(index < 0 || index >= sizeof(table))
      throw Error("to_hundred limit exception");

    return table[index];
  }

  static const char* to_ten_plus(int number)
  {
    int index = number - '1';

    const char* table[]= {"десять", "двадцать", "тридцать", "сорок", "пятьдесят", "шестьдесят",
        "семьдесят", "восемьдесят", "девяносто"};

    if(index < 0 || index >= sizeof(table))
      throw Error("to_hundred limit exception");

    return table[index];
  }
 
  static const char* to_hundred(char number)
  {
    int index = number - '1';

    const char* table[]= {"сто", "двести", "триста", "четыреста", "пятьсот", "шестьсот",
        "семьсот", "восемьсот", "девятьсот"};

    if(index < 0 || index >= sizeof(table))
      throw Error("to_hundred limit exception");

    return table[index];
  }

  static std::string get_end(Locale::Digits type, Locale_RU::Count count)
  {
    std::string end;

    switch(get_grender(type))
    {
    case NONE:
      break;

    case FEMALE:
      switch(count)
      {
      case ONE:
        end = "а";
        break;

      case TWOTHREEFOUR:
        end = "и";
        break;

      case FIVEMORE:
        end = "";
        break;
      }
      break;

    case MALE:

      switch(count)
      {
      case ONE:
        end = "";
        break;

      case TWOTHREEFOUR:
        end = "а";
        break;

      case FIVEMORE:
        end = "ов";
        break;

      default:
        throw Error("bad count");
      }
      break;
    }

    return end;
  }

  static std::string to_text(Locale::Digits type, Locale_RU::Count count)
  {
    std::string end;
    std::string word;

    end = get_end(type, count);

    switch(type)
    {
    case UNITS:
      break;

    case THOUSAND:
      word = "тысяч";
      break;

    case MILLION:
      word = "миллион";
      break;

    case BILLION:
      word = "миллиард";
      break;

    case TRILLION:
      word = "триллион";
      break;

    default:
      throw Error("bad type");
    }

    return word + end;
  }
};

// Лингвистический оптимизатор. Адаптирует числовое предсталвение произношению\письму\языку.
class Lingvo
{
public:
  std::string concat_word(const std::string& w1, const std::string& w2)
  {
    std::string str(w1);

    static std::locale loc = std::locale("");

    int len = str.length();
    if(str.length() > 0 && w2.length() > 0)
    {
      if(!std::isspace(str[len - 1], loc))
        str += " ";
    }

    str += w2;

    return str;
  }
};

template <class L>
class Lingvo_RU : public Lingvo
{
public:

  std::string to_simple(const std::string &str, Locale::Digits type)
  {
    std::string ret = "";

    ret = concat_word(ret, L::to_simple(str[0], type));

    Locale_RU::Count count = L::to_simple(str[0]);
    ret = concat_word(ret, L::to_text(type, count));

    return ret;
  }

  std::string to_ten(const std::string &str, Locale::Digits type, int cut_len)
  {
    int len = str.length();

    std::string head = "";
    std::string tail = "";
    std::string ret = "";

    head = str.substr(0, cut_len);
    tail = str.substr(0 + cut_len, len - cut_len);

    ret = concat_word(ret, to_ten(tail, type));
   
    return ret;
  }

  std::string to_ten(const std::string &str, Locale::Digits type)
  {
    int len = str.length();
    std::string ret = "";

    switch(len)
    {
      //10-99
    case 2:
      switch(str[0])
      {
      case '0':
        ret = concat_word(ret, to_ten(str, type, 1));
        break;

      case '1':
        ret = concat_word(ret, L::to_ten(str[1]));
        ret = concat_word(ret, L::to_text(type, Locale_RU::FIVEMORE));
        break;

      default:
        ret = concat_word(ret, L::to_ten_plus(str[0]));
        ret = concat_word(ret, to_ten(str, type, 1));
        break;
      }
      break;

      // 0-9
    case 1:
      switch(str[0])
      {
      case '0':
        ret = concat_word(ret, to_ten(str, type, 1));
        break;

      default:
        ret = concat_word(ret, to_simple(str, type));
        break;
      }
      break;

    case 0:
      ret = concat_word(ret, L::to_text(type, Locale_RU::FIVEMORE));
      break;

    default:
      throw Error("wrong to_ten len");
    }

    return ret;
  }

  std::string to_hundred(const std::string &str, Locale::Digits type, int cut_len)
  {
    int len = str.length();

    std::string head = "";
    std::string tail = "";
    std::string ret = "";

    head = str.substr(0, cut_len);
    tail = str.substr(0 + cut_len, len - cut_len);

    ret = concat_word(ret, to_hundred(tail, type));
   
    return ret;
  }

  std::string to_hundred(const std::string &str, Locale::Digits type)
  {
    int len = str.length();
    std::string ret = "";

    switch(len)
    {
      //000-900
    case 3:
     
      if(str == "000")
        return ret;

      switch(str[0])
      {
      case '0':
        ret = concat_word(ret, to_hundred(str, type, 1));
        break;

      default:
        ret = concat_word(ret, L::to_hundred(str[0]));
        ret = concat_word(ret, to_hundred(str, type, 1));
        break;
      }
      break;

      //10-99
    case 2:
      switch(str[0])
      {
      case '0':
        ret = concat_word(ret, to_hundred(str, type, 1));
        break;

      default:
        ret = concat_word(ret, to_ten(str, type));
        break;
      }
      break;

      // 0-9
    case 1:
      switch(str[0])
      {
      case '0':
        ret = concat_word(ret, to_hundred(str, type, 1));
        break;

      default:
        ret = concat_word(ret, to_simple(str, type));
        break;
      }
      break;

    case 0:
      ret = concat_word(ret, L::to_text(type, Locale_RU::FIVEMORE));
      break;

    default:
      throw Error("wrong hundred len");
    }

    return ret;
  }

  // cut and parse
  std::string to_digits(const std::string &str, Locale::Digits type, int cut_len)
  {
    int len = str.length();

    std::string head = "";
    std::string tail = "";
    std::string ret = "";

    head = str.substr(0, cut_len);
    tail = str.substr(0 + cut_len, len - cut_len);

    ret = concat_word(ret, to_hundred(head, type));

    ret = concat_word(ret, to_string(tail));
   
    return ret;
  }

  std::string to_digits(const std::string &str, Locale::Digits type)
  {
    int len = str.length();
    std::string ret = "";

    switch(len)
    {
      // триллион 1 000 000 000 000 - 100 000 000 000 000
    case 15:
    case 14:
    case 13:
      ret = concat_word(ret, to_digits(str, Locale::TRILLION, len - 12));
      break;

      // миллиард 1 000 000 000 - 100 000 000 000
    case 12:
    case 11:
    case 10:
      ret = concat_word(ret, to_digits(str, Locale::BILLION, len - 9));
      break;

      // миллион 1 000 000 - 100 000 000
    case 9:
    case 8:
    case 7:
      ret = concat_word(ret, to_digits(str, Locale::MILLION, len - 6));
      break;

      // тысяча 1 000 - 100 000
    case 6:
    case 5:
    case 4:
      ret = concat_word(ret, to_digits(str, Locale::THOUSAND, len - 3));
      break;

      // сотня 100
    case 3:
      ret = concat_word(ret, to_digits(str, Locale::UNITS, len - 0));
      break;

      // 10
    case 2:
      ret = concat_word(ret, to_ten(str, type));
      break;

      // 0-9
    case 1:
      ret = concat_word(ret, to_simple(str, type));
      break;

    case 0:
      break;

    default:
      throw Error("big value");
    }

    return ret;
  }

  std::string to_string(const std::string &str)
  {
    return to_digits(str, Locale::UNITS);
  }
};

class Encoding
{
public:
  const char to_char(int i)
  {
    static const char c[] = {"0123456789ABCDEF"};
  
    if(i >= sizeof(c))
      throw Error("to_char limit exception");

    return c[i];
  }
};

class Encoding_DEC : public Encoding
{
public:

  template <class T>
  std::string convert(T number)
  {
    std::string str = "";

    while (number >= 10)
    {
      str += to_char(number % 10);
      number /= 10;
    }
    str += to_char((int)number);

    std::reverse(str.begin(), str.end());
    return str;
  }
};

class Encoding_OCT : public Encoding
{
public:

  template <class T>
  std::string convert(T number)
  {
    std::string str = "";

    char last = 0;

    do {
      last = to_char(number & 7);
      str += last;
      number >>= 3;
    } while (number);

    if ( last != '0')
            number += '0';

    std::reverse(str.begin(), str.end());
    return str;
  }
};

int _tmain(int argc, _TCHAR* argv[])
{
  long long i = 111111111111111;

  setlocale(LC_ALL, "Russian");

  while(true)
  {
    std::cout<<"Вводим: "<<std::endl;

    std::cin>>i;

    std::cout<<"Выводим: "<<std::endl;

    {
      Encoding_DEC enc;
      std::string s = enc.convert(i);
      Lingvo_RU<Locale_RU> locale;
      std::string verb = locale.to_string(s);
      std::cout<<verb<<" в десятичной системе;"<<std::endl;
    }

    {
      Encoding_OCT enc;
      std::string s = enc.convert(i);
      Lingvo_RU<Locale_RU> locale;
      std::string verb = locale.to_string(s);
      std::cout<<verb<<" в восьмеричной системе."<<std::endl;
    }

    std::cout<<std::endl;
  }

  return 0;
}

2 коммент.: