|
::Главная страница ::Delphi/Паскаль :: Статьи |
В этом выпуске речь пойдет о вычислении выражений, заданных строкой. Этот алгоритм основан на бинарном дереве и является одним из самых быстрых. Смысл алгоритма в том, что каждая операция проводится над одним или двумя числами. Например: (1+2)*3 - умножается сумма на 3, сумма - складываются 1 и 2. 1+2+3 тоже можно представить, как (1+2)+3, то есть сумму суммы 1 и 2 с 3. Таким образом, каждая операция выполняется над одной или двумя операциями (число здесь рассматривается как операция, возвращающая значение этого числа). Можно написать функцию (назовем ее Count), которая бы выполняла заданную операцию с двумя заданными подоперациями. Причем, чтобы вычислить значения подопераций, эта функция будет вызывать себя же. Например: Count(1+2) = Count(1) + Count(2). Или: Count(1*2+3*4) = Count(1*2) + Count(3*4) = Count(Count(Count(1) * Count(2)) + Count(Count(3) * Count(4))). Если значение выражения нужно посчитать несколько раз (подставляя разный x), удобно один раз разобрать выражение по бинарному дереву, а потом уже с большой скорость вычислять. Для этого нужно создать рекуррентную структуру, то есть каждая операция содержит одну или две подоперации. В свою очередь каждая подоперация содержит свои подоперации и т.д.
Теперь о реализации. Тип TOperation определя операцию (+, -, *, ...). Запись TTreeTop - это и есть операция с подоперациями. Конструкция case позволяет для разных операций хранить разные поля, не затрачивая лишней памяти. Если операция - число, в записи хранится это число, если x - хранится адрес переменной x. Такой подход позволяет наиболее удобно подсталять различные значения x. OperStr - это строковые аналоги операций (плюс - '+', синус - 'sin' и т.д.). OperPr - это приоритет операций. LastOper - последняя операция данного приоритета. FillTree ищет справа налево операцию с наименьшем приоритетом. Эта операция должна находиться вне скобок. Поэтому заведен счетчик открытых-закрытых скобок, а в начале процедуры из строки удаляются лишние скобки. Как работает Count было рассказано выше. Единственное, что о чем здесь нужно еще сказать, это об особенности дебаггера Delphi. Когда Вы запускаете программу из-под Delphi, дебаггер, несмотря на предусмотренную Вами обработку исключений try-except, при каждой ошибке выдает свое сообщение. Эта проблема исчезает, когда Вы запускаете программу другим способом (например, из Проводника). Также существует возможность выключить эту функцию. Для этого нужно выключить флажок Integrating Debugging (в Delphi3 он находится Tools|Enviroment Options|Preferences|Debugging, а в Delphi5 Tools| Option|Debugger Options).
Button1 строит график y=f(x) - f(x) задается в Edit1. Button2 проводит миллион вычислений f(x) и выводит затраченное время в заголовок окна.
Скачать необходимые для компиляции файлы проекта можно на program.dax.ru. Достапны проекты для различных версий Delphi.
uses Math; { Эта функция скопирована из модуля SysUtils пятого Delphi. Необходима в более ранних версиях Delphi: } function AnsiSameText(const S1, S2: string): Boolean; begin result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, PChar(S1), length(S1), PChar(S2), length(S2)) = 2; end; type pdouble = ^double; TOperation = (opValue, opX, opUnMinus, opPlus, opMinus, opMultiply, opDivide, opPower, opSin, opCos, opTg, opArcsin, opArccos, opArctg, opInt, opAbs); PTreeTop = ^TTreeTop; TTreeTop = record case operation: TOperation of opValue: (v: double); opX: (xaddr: pdouble); opPlus, opMinus, opMultiply, opDivide, opPower: (left, right: PTreeTop); opUnMinus, opSin, opCos, opTg, opArcsin, opArccos, opArctg, opInt, opAbs: (arg: PTreeTop); end; const OperStr: array [opPlus..opAbs] of string = ('+', '-', '*', '/', '^', 'sin', 'cos', 'tg', 'arcsin', 'arccos', 'arctg', 'int', 'abs'); OperPr: array [opUnMinus..opAbs] of integer = (1, 1, 1, 2, 2, 3, 4, 4, 4, 4, 4, 4, 4, 4); LastOper: array [1..5] of TOperation = (opX, opMinus, opDivide, opPower, opAbs); // Удаление лишних скобок, например: "((1+2)+3)" -> "(1+2)+3" procedure DeleteBrackets(var s: string); var i, br, min: integer; begin if length(s) <= 0 then Exit; s := trim(s); // Удаление из начала и конца строки пробелов min := length(s); // Заведомо больше br := 0; for i := 1 to length(s) do case s[i] of '(': inc(br); ')': dec(br); else if (s[i] <> ' ') and (min > br) then min := br; end; // Удаление из строки первых и последних min символов s := trim(copy(s, min + 1, length(s) - min * 2)); end; procedure FillTree(top: PTreeTop; s: string; XAddr: pdouble); var op, oper: TOperation; i, code, MinPrior, OperPos, br: integer; begin DeleteBrackets(s); // Удаление лишних скобок oper := opValue; OperPos := 1; MinPrior := 5; br := 0; // Поиск операции: for i := length(s) downto 1 do case s[i] of ')': inc(br); '(': dec(br); else if br = 0 then { Если количество открытых скобок равно количеству закрытых скобок: } for op := opUnMinus to LastOper[MinPrior] do begin { Сравнение куска текста с операцией без учета регистра: } if (AnsiSameText(copy(s, i, length(OperStr[op])), OperStr[op])) then begin MinPrior := OperPr[op]; oper := op; OperPos := i; break; end; end; end; // Если нужно - из минуса сделать унарный минус: if (oper = opMinus) and (OperPos = 1) then oper := opUnMinus; case oper of // операции одного аргумента: opUnMinus, opSin..opInt: begin new(top^.arg); inc(OperPos, length(OperStr[oper])); FillTree(top^.arg, copy(s, OperPos, length(s) - OperPos + 1), XAddr); end; // операции двух аргументов: opPlus, opMinus, opMultiply, opDivide, opPower: begin new(top^.left); FillTree(top^.left, copy(s, 1, OperPos - 1), XAddr); new(top^.right); inc(OperPos, length(OperStr[oper])); FillTree(top^.right, copy(s, OperPos, length(s) - OperPos + 1), XAddr); end; else begin if LowerCase(s) = 'x' then begin // x oper := opX; top^.operation := opX; top^.XAddr := XAddr; end else begin // Число oper := opValue; top^.operation := opValue; val(s, top^.v, code); end; end; end; top^.operation := oper; end; // Освобождение памяти: procedure FreeTree(top: PTreeTop); begin case top^.operation of opPlus, opMinus, opMultiply, opDivide, opPower: begin FreeTree(top^.left); FreeTree(top^.right); end; opUnMinus, opSin, opCos, opTg, opArcsin, opArccos, opArctg, opInt, opAbs: FreeTree(top^.arg); end; dispose(top); end; // Вычисление выражения: function Count(top: PTreeTop; var error: boolean): double; register; var y: double; begin try case top^.operation of opX: result := top^.XAddr^; opUnMinus: result := -Count(top^.arg, error); opPlus: result := Count(top^.left, error) + Count(top^.right, error); opMinus: result := Count(top^.left, error) - Count(top^.right, error); opMultiply: result := Count(top^.left, error) * Count(top^.right, error); opDivide: result := Count(top^.left, error) / Count(top^.right, error); opPower: begin y := Count(top^.right, error); if abs(round(y) - y) < 1E-10 then result := IntPower(Count(top^.left, error), round(y)) else result := Power(Count(top^.left, error), y); end; opSin: result := sin(Count(top^.arg, error)); opCos: result := cos(Count(top^.arg, error)); opTg: result := tan(Count(top^.arg, error)); opArcsin: result := arcsin(Count(top^.arg, error)); opArccos: result := arccos(Count(top^.arg, error)); opArctg: result := arctan(Count(top^.arg, error)); opInt: result := int(Count(top^.arg, error)); opAbs: result := abs(Count(top^.arg, error)); else result := top^.v; end; except // Проихошла ошибка: result := 0; error := true; end; end; procedure TForm1.Button1Click(Sender: TObject); const left = -15; // Наименьший x right = 15; // Наибольший x k = 10; // Колиство точек на пиксел экрана var x, y: double; i: integer; error: boolean; top: PTreeTop; Scale: single; begin //5*cos(2*x)+12*sin(2*x)-12*sin(x)-18*cos(x)+17 new(top); // Выделение памяти под главную вершину FillTree(top, Edit1.Text, @x); // Заполнение бинарного дерева Scale := Image1.Width / (right - left); // Вычисление масштаба // Вывод осей: with Image1.Canvas do begin Rectangle(0, 0, Image1.Width, Image1.Height); MoveTo(round(-left * Scale), Image1.Height); LineTo(round(-left * Scale), 0); MoveTo(0, Image1.Height div 2); LineTo(Image1.Width, Image1.Height div 2); end; error := false; for i := round(left * Scale * k) to round(right * Scale * k) do begin x := i / Scale / k; { В ячейку памяти с x кладем значение аргумента } y := Count(top, error); // Вычисление выражения if error // Если ошибка: then error := false // Восстановить значение флага // Иначе рисуется точка: else Image1.Canvas.Pixels[(round(i / k - left * Scale)), round(Image1.Height / 2 - y * Scale)] := clRed; end; FreeTree(top); end; {$HINTS OFF} { Поскольку это тестирующая процедура, предупреждения здесь не нужны } procedure TForm1.Button2Click(Sender: TObject); var x, y: double; i: integer; error: boolean; top: PTreeTop; t: cardinal; begin new(top); // Выделение памяти под главную вершину FillTree(top, Edit1.Text, @x); // Заполнение бинарного дерева t := GetTickCount; // Засечение времени for i := 1 to 1000000 do y := Count(top, error); // Вычисление выражения Form1.Caption := IntToStr(GetTickCount - t); // Вывод времени FreeTree(top); // Освобождение памяти end; {$HINTS ON}
procedure TForm1.Button1Click(Sender: TObject); begin if AnsiSameText(Edit1.Text, Edit2.Text) then Form1.Caption := 'same' else Form1.Caption := 'different'; end;
Всего доброго,
Даниил Карапетян.
Тематические
ссылки
|
Ваша ссылка | Ваша ссылка |
Обмен кнопками, ведение статистики, реклама. |
|||