RRRRR - 54.159.129.152

Обсуждения-аналоги

Скрыть / Показать Сортировать по дате
2010-01-22 15:10:40
Сычев С.В., Лебедев К.А. » Всем

16. ВОТ И УКРОЩАЕМ

Очевидно, мы имеем Противоречие 6. И это противоречие типовое ("Много - мало"). Например, такое же точно, как Противоречие 3.


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


В этом и заключается типовой прием устранения Противоречия "Много - мало", который мы уже не раз применили.


Иными словами, поступим так же, как мы поступили с алгоритмом преобразования выражения в инфиксной нотации в выражение в нотации ПОЛИЗ.


1. ОБЪЯВИМ ТИПЫ ТОКЕНОВ

У нас уже есть справочник типов токенов - им и воспользуемся:


enum TTokenType //Это наш справочник типов токенов 
{     
  eTokenOperand = 0,       // операнд
  eTokenPlus,              // операция сложение
  eTokenMinus,             // операция вычитание
  eTokenMultiply,          // операция умножение
  eTokenDivide,            // операция деление
  eTokenOpeningBracket,    // открывающая скобка
  eTokenClosingBracket,    // закрывающая скобка
  eTokenEndOfSequence,     // конец последовательности
  eTokenFunction,          // функция
  eTokenDelimiter,         // разделитель
  eTokenTypeCount          // количество значений токенов
};

    

Все или почти все типы токенов предположительно исчерпываются данным списком. Два типа "операнд" и "функция" - универсальные токены, покрывающие любые операнды и функции, 4 действия арифметики вряд ли расширятся; то же касается скобок, разделителя и числа значений. В любом, случае, если это предположение неверно, несложно будет вставить строчку.


Поэтому токен "Функция" и методы его обработки мы опишем отдельно. Это нам позволит для удобства создать 1 Пользовательский файл, где Пользователь будет регистрировать новые конкретные функции, которых нет в общем списке. Мы хотим добиться того, чтобы он их просто туда вписывал, и калькулятор после перекомпиляции программы сразу начинал их обрабатывать - т.е., алгоритму было бы "все равно", а точность его не менялась.



2. ТОГДА ОПИШЕМ МЕТОДЫ ОБРАБОТКИ ТОКЕНОВ, КРОМЕ "ФУНКЦИИ"


Вот так обработаем операнд:


TErrorType my_operand (const TToken & rToken, std::vector<double> & rStack)
{
       rStack.push_back(rToken.GetValue());
       return eNoError;
}


Вот так обработаем операцию "плюс":


TErrorType my_plus (const TToken & rToken, std::vector<double> & rStack);
{
       if (rStack.size() < 2)
           return eStackUnderflow;

       double fltArg2 = rStack.back();
       rStack.pop_back();


       double fltArg1 = rStack.back();
       rStack.pop_back();


       rStack.push_back(fltArg1 + fltArg2);

   
       return eNoError;
}


Вот так обработаем операцию "минус":


TErrorType my_minus (const TToken & rToken, std::vector<double> & rStack)
{
       if (rStack.size() < 2)
           return eStackUnderflow;

       double fltArg2 = rStack.back();
       rStack.pop_back();

   
       double fltArg1 = rStack.back();
       rStack.pop_back();

   
       rStack.push_back(fltArg1 - fltArg2);

   
       return eNoError;
}


Вот так обработаем операцию "умножить":


TErrorType my_multiply (const TToken & rToken, std::vector<double> & rStack)

{
       if (rStack.size() < 2)
           return eStackUnderflow;

   
       double fltArg2 = rStack.back();
       rStack.pop_back();

   
       double fltArg1 = rStack.back();
       rStack.pop_back();

   
       rStack.push_back(fltArg1 * fltArg2);


       return eNoError;
}


Вот так обработаем операцию "разделить":


TErrorType my_divide (const TToken & rToken, std::vector<double> & rStack)
{
       if (rStack.size() < 2)
           return eStackUnderflow;

       double fltArg2 = rStack.back();
       rStack.pop_back();

       double fltArg1 = rStack.back();
       rStack.pop_back();

       rStack.push_back(fltArg1 / fltArg2);

       return eNoError;
}


Вот так обработаем скобку:


TErrorType my_bracket (const TToken & rToken, std::vector<double> & rStack)
{
       return eBracketMismatch;
}


Вот так обработаем разделитель:


TErrorType my_delimiter (const TToken & rToken, std::vector<double> & rStack)
{
       return eWrongSymbol;
}


И этот перечень, предположительно, уже не расширится.



3. УКАЖЕМ КАКОЙ МЕТОД КАКОЙ ТОКЕН ОБРАБАТЫВАЕТ

     std::map<TTokenType, TFunction> handlers;
     handlers[eTokenOperand] = my_operand;
     handlers[eTokenPlus] = my_plus;
     handlers[eTokenMinus] = my_minus;
     handlers[eTokenMultiply] = my_multiply;
     handlers[eTokenDivide] = my_divide;
     handlers[eTokenOpeningBracket] = my_bracket;
     handlers[eTokenClosingBracket] = my_bracket;
     handlers[eTokenEndOfSequence] = my_delimiter;
     handlers[eTokenFunction] = my_function;
     handlers[eTokenDelimiter] = my_delimiter;

        

И этот перечень уже не расширится.



4. И, НАКОНЕЦ, СДЕЛАЕМ РЕАЛИЗАЦИЮ МЕТОДА ВЫЗОВА ТОКЕНОВ ПРИ ПОМОЩИ ОБРАЩЕНИЯ К СПРАВОЧНОЙ ТАБЛИЦЕ


Т.о., основной алгоритм во время вычисления не использует методы обработки конкретных токенов напрямую - так что мы нигде в алгоритме не увидим вызовов, например, my_plus и т.д. А пользуется только абстрактным вызовом my_token_handler


TErrorType my_token_handler (const TToken & rToken, std::vector<double> & rStack)
{
       //Ищем ссылку на метод для обработки конкретного токена
   
        if (handlers.find(rToken.GetType()) == handlers.end())
        return eWrongSymbol;

   
       //Получаем ссылку на метод для обработки конкретного токена

   
       TFunction pfn = handlers[rToken.GetType()];

   
       //Выполняем полученный метод и возвращаем результат вычисления

    
       return (*pfn)(rToken, rStack);
}


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



5. ТЕПЕРЬ АНАЛОГИЧНЫМ ОБРАЗОМ ПОСТУПИМ И С "ФУНКЦИЯМИ"


Создадим "место", где будем регистрировать новые Пользовательские функции:


// Новая функция должна быть зарегистрирована здесь
      std::map<std::string, TFunction> g_functions;
      g_functions["sin"] =    my_sin;
      g_functions["cos"] =    my_cos;
      g_functions["sqrt"] =   my_sqrt;


Опишем методы обработки конкретных функций:


// Алгоритм вычисления синуса


TErrorType my_sin (const TToken & rToken, std::vector<double> & rStack)

{
       if (rStack.size() < 1)
       return eStackUnderflow;

   
       double fltValue = rStack.back();
       rStack.pop_back();

   
       rStack.push_back(sin(DegreeToRadian(fltValue)));

      
       return eNoError;
}


// Алгоритм вычисления косинуса


TErrorType my_cos (const TToken & rToken, std::vector<double> & rStack)
{
       if (rStack.size() < 1)
           return eStackUnderflow;

   
       double fltValue = rStack.back();
       rStack.pop_back();

   
       rStack.push_back(cos(DegreeToRadian(fltValue)));

   
       return eNoError;
}


// Алгоритм вычисления квадратного корня


TErrorType my_sqrt (const TToken & rToken, std::vector<double> & rStack)

{
       if (rStack.size() < 1)
           return eStackUnderflow;

    
       double fltValue = rStack.back();
       rStack.pop_back();

   
       rStack.push_back(sqrt(fltValue));


       return eNoError;
}


//Напишите алгоритм вычисления Вашей функции

......................................................
......................................................


Для удобства сохраним обработки функций в отдельном файле, куда Пользователь будет вносить новые функции и/или вписывать их алгоритмы.



6. И, НАКОНЕЦ, ДЕЛАЕМ РЕАЛИЗАЦИЮ МЕТОДА ВЫЗОВА ПОЛЬЗОВАТЕЛЬСКОЙ ФУНКЦИИ ПРИ ПОМОЩИ ОБРАЩЕНИЯ К СПРАВОЧНОЙ ТАБЛИЦЕ


Т.о. основной алгоритм во время вычисления не использует напрямую методы обработки вызовов конкретных функций - так что мы нигде в алгоритме не увидим вызовов, например, my_cos и т.д. А пользуется только абстрактным my_function


TErrorType my_function (const TToken & rToken, std::vector<double> & rStack)
{
       // Ищем зарегистрированную функцию
       if (g_functions.find(rToken.GetText()) == g_functions.end())
           return eUndefinedFunction;


       // Получаем зарегистрированную функцию
       TFunction pfn = g_functions[rToken.GetText()];


       // Выполняем найденную функцию и возвращаем результат.
       return (*pfn)(rToken, rStack);
}


Итак, мы видим:


1. Алгоритм интерпретации "со свитчами" теперь не нужен (и как это поначалу он показался нам простым?). Разные сущности вновь ушли во внешние таблицы.


Вот весь новый алгоритм интерпретации:
 
            while (GetToken(&token))//Получаем очередной токен из ПОЛИЗа
            {
           
//Выполняем вычисление "чего бы то ни было" (если оно зарегистрировано)
                   my_token_handler(token.GetType(), stack); 
        
            }


Как мы видим вновь получилась функция, приближенная к идеальной: "Получи, что велено, откуда прикажут, и отправь куда следует" (в данном случае, получаем токен из ПОЛИЗа, который является внешним по отношению к алгоритму интерпретации).


Весь калькулятор состоит из трех таких функций.


2. "Распухания" кода не происходит, т.к.:


a. при добавлении нового типа токена нам всего лишь нужно добавить новый обработчик токена и зарегистрировать его в справочной таблице обработчиков токенов;


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


Далее...

Уважаемые Коллеги!

Если Вам нравится наш Форум, Вы можете поддержать его, отправив любую сумму (тогда выберите опцию "Спасибо за Форум").

Вы также можете поддержать конкретное обсуждение и получить гарантированный ответ от наших специалистов (тогда выберите опцию "Прошу эксперта ответить в этой теме").
Задайте Ваш вопрос здесь.

Большое Спасибо!


Яндекс.Метрика