Программа–распознаватель арифметических выражений

 

Оглавление


Введение

. Теоретическая часть

.1 История разработки вопроса разбора математических выражений

.2 Обратная польская нотация

.3 Вычисления на стеке

.4 Преобразование из инфиксной нотации

. Практическая часть

.1 Постановке задачи

.2 Описание входного языка

.3 Описание выходной информации

.4 Алгоритмические аспекты программы

.5 Алгоритм программы

.7 Работа с программой

Заключение

Литература

Приложение



Введение


Мы все знаем, что такое математическое выражение. Это формула. Формулы встречаются повсеместно в экономике, физике, биологии, и, конечно же, в математике. В общем, везде, где нужно символически представить какую-нибудь функцию или соотношение. Символическое представление, конечно, не единственное. Можно представить выражение в виде графика, или таблицы. Но все же формула является самым компактным и универсальным способом.

В данной курсовой работе будет разработана программа, которая продемонстрирует способ распознавания компьютером введённых пользователем математических формул.

Цель данной работы - разработать программу распознавания математических формул.

Для достижения цели необходимо выполнить следующие задачи:

1.Изучить теоретический материал по предмету.

2.Разработать концепцию реализации программы.

.Реализовать программу на языке программирования Delphi 7.

.Составить пояснительную записку.


1. Теоретическая часть


.1 История разработки вопроса разбора математических выражений


Существуют три вида записи выражений:

. инфиксная форма, в которой оператор расположен между операндами (например, "а + b");

. постфиксная форма, в которой оператор расположен после операндов ("а b + ");

. префиксная форма, в которой оператор расположен перед операндами ("+ а b").

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

Обратная польская нотация была разработана австралийским философом и специалистом в области теории вычислительных машин Чарльзом Хэмблином в середине 1950-х на основе польской нотации, которая была предложена в 1920 году польским математиком Яном Лукасевичем. Работа Хэмблина была представлена на конференции в июне 1957, и издана в 1957 и 1962.

Первыми компьютерами, поддерживающими обратную польскую нотацию были KDF9 от English Electric Company, который был анонсирован в 1960 и выпущен (появился в продаже) в 1963, и американский Burroughs B5000, анонсирован в 1961, выпущен в том же 1963. Один из проектировщиков B5000, Р. С. Бартон, позже написал, что разработал обратную польскую запись назависимо от Хэмблина, примерно в 1958, в процессе чтения книги по символьной логике, и до того как познакомился с работой Хэмблина.

Компания Friden перенесла ОПН в настольные калькуляторы, выпустив в июне 1964 модель EC-130. А в 1968 инженеры Hewlett-Packard разработали настольный калькулятор 9100A с поддержкой ОПН. Этот калькулятор сделал обратную польскую нотацию популярной среди учёных и инженеров, даже несмотря на то, что в ранней рекламе 9100A ОПН не упоминалась. В 1972 калькулятор HP-35 с поддержкой ОПН стал первым научным карманным калькулятором.

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

ОПН применялась в советском инженерном калькуляторе Б3-19М (совместная разработка с ГДР), выпущенном в 1976 году. Все выпускаемые в СССР вплоть до конца 1980-х годов программируемые микрокалькуляторы, за исключением «Электроника МК-85», использовали ОПН - она проще реализовывалась и позволяла обойтись в программировании вычислений меньшим числом команд, по сравнению с обычной алгебраической нотацией, а количество программной памяти в этих моделях всегда было критическим ресурсом (не более 105 ячеек, при том, что команда занимала 1-2 ячейки). ОПН используется в современных российских программируемых калькуляторах «Электроника МК-152» и «ЭЛЕКТРОНИКА МК-161», что обеспечивает их совместимость с программами, написанными для советских калькуляторов.


.2 Обратная польская нотация


Отличительной особенностью обратной польской нотации является то, что все аргументы (или операнды) расположены перед знаком операции. В общем виде запись выглядит следующим образом:

·Запись набора операций состоит из последовательности операндов и знаков операций. Операнды в выражении при письменной записи разделяются пробелами.

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

·Результатом вычисления выражения становится результат последней вычисленной операции.

Например, рассмотрим вычисление выражения 7 2 3 * - (эквивалентное выражение в инфиксной нотации: 7-2*3).

·Первый по порядку знак операции - «*», поэтому первой выполняется операция умножения над операндами 2 и 3 (они стоят последними перед знаком). Выражение при этом преобразуется к виду 7 6 - (результат умножения - 6, - заменяет тройку «2 3 *»).

·Второй знак операции - «-». Выполняется операция вычитания над операндами 7 и 6.

·Вычисление закончено. Результат последней операции равен 1, это и есть результат вычисления выражения.

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

Особенности обратной польской записи следующие:

·Порядок выполнения операций однозначно задаётся порядком следования знаков операций в выражении, поэтому отпадает необходимость использования скобок и введения приоритетов и ассоциативности операций.

·В отличие от инфиксной записи, невозможно использовать одни и те же знаки для записи унарных и бинарных операций. Так, в инфиксной записи выражение 5 * (-3 + 8) использует знак «минус» как символ унарной операции (изменение знака числа), а выражение (10 - 15) * 3 применяет этот же знак для обозначения бинарной операции (вычитание). Конкретная операция определяется тем, в какой позиции находится знак. Обратная польская запись не позволяет этого: запись 5 3 - 8 + * (условный аналог первого выражения) будет интерпретирована как ошибочная, поскольку невозможно определить, что «минус» после 5 и 3 обозначает не вычитание; в результате будет сделана попытка вычислить сначала 5 - 3, затем 2 + 8, после чего выяснится, что для операции умножения не хватает операндов. Чтобы всё же записать это выражение, придётся либо переформулировать его, либо ввести для операции изменения знака отдельное обозначение, например, «±»: 5 3 ± 8 + *.

·Так же, как и в инфиксной нотации, в ОПН одно и то же вычисление может быть записано в нескольких разных вариантах. Например, выражение (10 - 15) * 3 в ОПН можно записать как 10 15 - 3 *, а можно - как 3 10 15 - *

·Из-за отсутствия скобок обратная польская запись короче инфиксной. За этот счёт при вычислениях на калькуляторах повышается скорость работы оператора (уменьшается количество нажимаемых клавиш), а в программируемых устройствах сокращается объём тех частей программы, которые описывают вычисления. Последнее может быть немаловажно для портативных и встроенных вычислительных устройств, имеющих жёсткие ограничения на объём памяти.


.3 Вычисления на стеке


Автоматизация вычисления выражений в обратной польской нотации основана на использовании стека. Алгоритм вычисления для стековой машины элементарен:

1.Обработка входного символа.

2.Если на вход подан операнд, он помещается на вершину стека.

.Если на вход подан знак операции, то соответствующая операция выполняется над требуемым количеством значений, извлечённых из стека, взятых в порядке добавления. Результат выполненной операции кладётся на вершину стека.

.Если входной набор символов обработан не полностью, перейти к шагу 1.

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

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


.4 Преобразование из инфиксной нотации


Эдсгер Дейкстра изобрёл алгоритм для преобразования выражений из инфиксной нотации в ОПН. Алгоритм получил название «сортировочная станция», за сходство его операций с происходящим на железнодорожных сортировочных станциях. Инфиксная нотация - это форма математических записей, которую использует большинство людей (например, 3 + 4 или 3 + 4 * (2 - 1)). Как и алгоритм вычисления ОПН, алгоритм сортировочной станции основан на стеке. В преобразовании участвуют две текстовых переменных: входная и выходная строки. В процессе преобразования используется стек, хранящий ещё не добавленные к выходной строке операторы. Преобразующая программа читает входную строку последовательно символ за символом (символ - это не обязательно буква), выполняет на каждом шаге некоторые действия в зависимости от того, какой символ был прочитан.

Алгоритм

* Пока есть ещё символы для чтения:

* Читаем очередной символ.

* Если символ является числом, добавить его к выходной строке.

* Если символ является символом функции, помещаем его в стек.

* Если символ является открывающей скобкой, помещаем его в стек.

* Если символ является закрывающей скобкой:

До тех пор, пока верхним элементом стека не станет открывающая скобка, выталкиваем элементы из стека в выходную строку. При этом открывающая скобка удаляется из стека, но в выходную строку не добавляется. Если после этого шага на вершине стека оказывается символ функции, выталкиваем его в выходную строку. Если стек закончился раньше, чем мы встретили открывающую скобку, это означает, что в выражении либо неверно поставлен разделитель, либо не согласованы скобки.

* Если символ является оператором о1, тогда:

) пока…

… (если оператор o1 ассоциированный, либо лево-ассоциированный) приоритет o1 меньше либо равен приоритету оператора, находящегося на вершине стека…

… (если оператор o1 право-ассоциированый) приоритет ''''' меньше приоритета оператора, находящегося на вершине стека…

… выталкиваем верхние элементы стека в выходную строку;

) помещаем оператор o1 в стек.

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


2. Практическая часть


.1 Постановке задачи


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

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

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

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

.Программ должна вычислять результат введённого математического выражения.

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

.Программа должна выполняться под управление ОС Windows.


.2 Описание входного языка


Программа разработана в соответствии со следующей спецификацией входной информации:

Входная информация - строка символов, являющаяся правильным математическим выражением, содержащая операнды и операторы.

Набор операторов входного языка приведён в следующей таблице:

Таблица 1

Операторы входного языка

№Обозначение в программеМатематическое обозначениеФункцияПравила использования1. 1++СложениеСкладывает два операнда находящиеся справа и слева от оператора.2. 2--ВычитаниеВычитает из операнда находящегося слева из операнда, находящегося справа от оператора.3. **ПроизведениеУмножает два операнда находящиеся справа и слева от оператора.4. /:ЧастноеДелит операнд, находящейся слева, на операнд, находящейся справа от оператора.5. ^ОснованиеСтепеньСтепеньВозводит операнд, находящийся слева, в степень по операнду, находящемуся справа от оператора

Набор символов входного языка, составляющих операнды, представлен в следующей таблице:


Таблица 2

Символы входного языка

№Обозначение в программеМатематическое обозначениеНаименование1. 11Один2. 22Два3. 33Три4. 44Четыре5. 55Пять6. 66Шесть7. 77Семь8. 88Восемь9. 99Девять10. 00Нольпрограмма распознаватель арифметический выражение

2.3 Описание выходной информации


Выходной информацией данной программы являются следующие данные:

1.Дерево разбора.

2.Результаты промежуточных вычислений.

.Конечный результат.

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

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

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


.4 Алгоритмические аспекты программы


Алгоритм программы представляет собой соединение двух алгоритмов:

1.Построение дерева разбора.

2.Построение выражение по принципу польской префиксной записи.

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

Приоритеты операндов показаны в таблице 3.

Таблица 3

Приоритеты операндов

№ОперандНаименованиеПриоритет выполнения1. +Сложение12. -Вычитание13. *Произведение24. /Деление25. ^Степень3

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


2.5 Алгоритм программы


Алгоритм программы представлен следующей схемой:


Рисунок 1 - Алгоритм программы

Рисунок 2 - Алгоритм программы

Рисунок 3 - Алгоритм программы

Рисунок 4 - Алгоритм программы

Рисунок 5 - Алгоритм программы

Рисунок 6 - Алгоритм программы

Рисунок 7 - Алгоритм программы

2.7 Работа с программой


Разработанная программа не требует инсталляции. Для её запуска достаточно запустить исполнимый файл. Вид программы предстален на рисунке.


Рисунок 8 - Окно программы.

Верхнее поле предназначено для ввода арифметического выражения.


Рисунок 9 - Введено выражение.


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

Рисунок 10 - Выполненная программа.


Чтобы очистить поля программы нажмите кнопку очистить.


Заключение


В результате работы была разработана программа, реализующая алгоритм разбора арифметического выражения, используя классические конструкции обратной польской нотации. Данная программа демонстрирует правила разбора выражения при помощи построения дерева разбора, с последующим вычислением выражения с выводом промежуточных результатов. Тестирование программы показало правильность её работы, а простота интерфейса - даёт удобство в использовании.

В процессе работы над программой мною были изучены следующие темы:

Правила создания бинарных деревьев.

Правила преобразования инфиксной записи в польскую обратную нотацию.

Литература


1.А. В. Ахо, Р. Сети, Д. Д. Ульман. Компиляторы: принципы, технологии и инструменты. М.: «Вильямс», 2003.

2.Компаниец Р.И. и др. Системное программирование.Основы построения трансляторов.- СПб.: КОРОНА принт, 2000.-256 с.

3.Компаниец Р.И., Маньков Е.В., Филатов Н.Е. Основы построения трансляторов. -СПб.: КОРОНА принт, 2000. -256 с.

4.Хантер Р. Проектирование и конструирование компиляторов. М.: Финансы и статистика. 1984 г.

.Грис Д. Конструирование компиляторов для цифровых вычислительных машин. М.: Мир,1975 г.

.Иртегов Д. Введение в операционные системы. Учебное пособие. - СПб.: БХВ-Петербург, 2002. - 624 с.

.Молчанов А.Ю. Системное программное обеспечение. Лабораторный практкум.- СПб.: Питер, 2005.- 284 с.

.Юров В.И. Assembler. Учебник для вузов. 2-е издание - СПб.: Питер.- 2004.- 637 с.

.Компаниец Р.И., Маньков Е.В., Филатов Н.Е. Системное программирование: Основы построения трансляторов + FD.- М.: КОРОНА принт.- 2004.- 255 с.

.Молчанов А.Ю. Системное программное обеспечение. Лабораторный практикум. - СПб.: Питер, 2005. - 284 с.

.Юров В.И. Assembler. Учебник для вузов. 2-е изд. - СПб: Питер, 2004. - 637 с.

.Ахо А., Ульман Дж. Теория синтаксического анализа, перевода и компиляции. - М.:Мир, 1978. - Т.1, 612 с. Т. 2, 487 с.

.Ахо А.,Сети Р., Ульман Дж. Компиляторы: принципы, технологии и инструменты: Пер. с англ. - М.: Издательский дом «Вильямс», 2003. - 768 с.

.Гордеев А.В., Молчанов А.Ю. Системное программное обеспечение. - СПб.: Питер, 2002. - 734 с.

.Компаниец Р.И., Маньков Е.В., Филатов Н.Е. Системное программирование. Основы построения трансляторов: Учебное пособие для высших средних учебных заведений. - СПб:КОРОНА принт, 2000. - 256 с.

.Гордеев А.В. Опреационные системы: Учебник для вузов. 2-е изд. - СПб.: Питер, 2004. - 416 с.


Приложение


Листинг программыUnit1;


interface


Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs, StdCtrls, math;


TForm1 = class(TForm)

Edit1: TEdit;

Button1: TButton;

Memo1: TMemo;

Label1: TLabel;

Label2: TLabel;

Memo2: TMemo;

Label3: TLabel;

Button2: TButton;

Edit2: TEdit;

procedure Button1Click(Sender: TObject);

procedure Button2Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

Form1: TForm1;

st,op1,op2:string;

mtxi,mtxpr:integer;

mtx:array of string;


{$R *.dfm}is_num(a:string):boolean;

if ((a='"') or (a='0') or (a='1') or (a='2') or (a='3') or (a='4') or (a='5') or (a='6') or (a='7') or (a='8') or (a='9'))

then is_num:=true

else is_num:=false;;

TForm1.Button1Click(Sender: TObject);i,j,fl,m,l:integer;:string;:=form1.Edit1.Text;:=0;


memo1.Lines.Add('');

fl:=1;

if (pos('^',st)>0) then

begin

fl:=0;

memo1.Lines[m]:=memo1.Lines[m]+'^ ';

i:=pos('^',st);

j:=i+1;

i:=i-1;

ss:='';

while is_num(st[i]) do

begin

ss:=ss+st[i];

i:=i-1;

end;

for l:=length(ss) downto 0 do memo1.Lines[m]:=memo1.Lines[m]+ss[l];

memo1.Lines[m]:=memo1.Lines[m]+' ';

while is_num(st[j]) do

begin

memo1.Lines[m]:=memo1.Lines[m]+st[j];

j:=j+1;

end;

delete(st,i+1,j-i-1);

insert('"'+inttostr(m)+'"',st,i+1);

edit1.text:=st;

m:=m+1;

end;fl=1;

memo1.Lines.Add('');

fl:=1;

if (pos('*',st)>0) then

begin

fl:=0;

memo1.Lines[m]:=memo1.Lines[m]+'* ';

i:=pos('*',st);

j:=i+1;

i:=i-1;

ss:='';

while is_num(st[i]) do

begin

ss:=ss+st[i];

i:=i-1;

end;

for l:=length(ss) downto 0 do memo1.Lines[m]:=memo1.Lines[m]+ss[l];

memo1.Lines[m]:=memo1.Lines[m]+' ';

while is_num(st[j]) do

begin

memo1.Lines[m]:=memo1.Lines[m]+st[j];

j:=j+1;

end;

delete(st,i+1,j-i-1);

insert('"'+inttostr(m)+'"',st,i+1);

edit1.text:=st;

m:=m+1;

end;fl=1;

memo1.Lines.Add('');

fl:=1;

if (pos('/',st)>0) then

begin

fl:=0;

memo1.Lines[m]:=memo1.Lines[m]+'/ ';

i:=pos('/',st);

j:=i+1;

i:=i-1;

ss:='';

while is_num(st[i]) do

begin

ss:=ss+st[i];

i:=i-1;

end;

for l:=length(ss) downto 0 do memo1.Lines[m]:=memo1.Lines[m]+ss[l];

memo1.Lines[m]:=memo1.Lines[m]+' ';

while is_num(st[j]) do

begin

memo1.Lines[m]:=memo1.Lines[m]+st[j];

j:=j+1;

end;

delete(st,i+1,j-i-1);

insert('"'+inttostr(m)+'"',st,i+1);

edit1.text:=st;

m:=m+1;


end;fl=1;

memo1.Lines.Add('');

fl:=1;

if (pos('+',st)>0) then

begin

fl:=0;

memo1.Lines[m]:=memo1.Lines[m]+'+ ';

i:=pos('+',st);

j:=i+1;

i:=i-1;

ss:='';

while is_num(st[i]) do

begin

ss:=ss+st[i];

i:=i-1;

end;

for l:=length(ss) downto 0 do memo1.Lines[m]:=memo1.Lines[m]+ss[l];

memo1.Lines[m]:=memo1.Lines[m]+' ';

while is_num(st[j]) do

begin

memo1.Lines[m]:=memo1.Lines[m]+st[j];

j:=j+1;

end;

delete(st,i+1,j-i-1);

insert('"'+inttostr(m)+'"',st,i+1);

edit1.text:=st;

m:=m+1;

end;fl=1;

memo1.Lines.Add('');

fl:=1;

if (pos('-',st)>0) then

begin

fl:=0;

memo1.Lines[m]:=memo1.Lines[m]+'- ';

i:=pos('-',st);

j:=i+1;

i:=i-1;

ss:='';

while is_num(st[i]) do

begin

ss:=ss+st[i];

i:=i-1;

end;

for l:=length(ss) downto 0 do memo1.Lines[m]:=memo1.Lines[m]+ss[l];

memo1.Lines[m]:=memo1.Lines[m]+' ';

while is_num(st[j]) do

begin

memo1.Lines[m]:=memo1.Lines[m]+st[j];

j:=j+1;

end;

delete(st,i+1,j-i-1);

insert('"'+inttostr(m)+'"',st,i+1);

edit1.text:=st;

m:=m+1;

end;fl=1;

memo1.Lines[memo1.Lines.Count-1]='' do

memo1.Lines.Delete(memo1.Lines.Count-1);

.Lines:=memo1.Lines;

i:=0 to memo2.Lines.Count do

if ((length(memo1.Lines[i])<>0)) then

st:=memo1.Lines[i];

case st[1] of

'^':begin


delete(st,1,2);

if (st[1]='"') then

begin

op1:=memo2.Lines[strtoint(st[2])];

delete(st,1,4);

end

else

begin

op1:=copy(st,1,pos(' ',st));

delete(st,1,pos(' ',st));

end;

if (st[1]='"') then

begin

op2:=st[2]; memo2.Lines[strtoint(st[2])];

edit2.text:=op2;

end

else

begin

op2:=st;


end;

memo2.Lines[i]:=floattostr(Power(strtofloat(op1),strtofloat(op2)));


end;

'*':begin


delete(st,1,2);

if (st[1]='"') then

begin

op1:=memo2.Lines[strtoint(st[2])];

delete(st,1,4);

end

else

begin

op1:=copy(st,1,pos(' ',st));

delete(st,1,pos(' ',st));

end;

if (st[1]='"') then

begin

op2:=st[2]; memo2.Lines[strtoint(st[2])];

edit2.text:=op2;

end

else

begin

op2:=st;

end;

memo2.Lines[i]:=floattostr(strtofloat(op1)*strtofloat(op2));

end;

'+':begin


delete(st,1,2);

if (st[1]='"') then

begin

op1:=memo2.Lines[strtoint(st[2])];

delete(st,1,4);

end

else

begin

op1:=copy(st,1,pos(' ',st));

delete(st,1,pos(' ',st));

end;

if (st[1]='"') then

begin

op2:=memo2.Lines[strtoint(st[2])]; memo2.Lines[strtoint(st[2])];

end

else

begin

op2:=st;

end;

memo2.Lines[i]:=floattostr(strtofloat(op1)+strtofloat(op2));

end;

end;

edit2.text:=memo2.Lines[memo2.Lines.count-1];;

;

TForm1.Button2Click(Sender: TObject);.Text:='';.Text:='';.Lines.Clear;.Lines.Clear;

end;

.


Оглавление Введение . Теоретическая часть .1 История разработки вопроса разбора математических выражений .2 Обратная польская нотация .3 Вычи

Больше работ по теме:

КОНТАКТНЫЙ EMAIL: [email protected]

Скачать реферат © 2017 | Пользовательское соглашение

Скачать      Реферат

ПРОФЕССИОНАЛЬНАЯ ПОМОЩЬ СТУДЕНТАМ