diff --git a/tex/dart/graph/bfs.dart b/tex/dart/graph/bfs.dart new file mode 100644 index 0000000..b2fd7c3 --- /dev/null +++ b/tex/dart/graph/bfs.dart @@ -0,0 +1,52 @@ +List? bfsPath(int startDot, int goalDot) { + if (startDot == goalDot) return [startDot]; + startDot--; + goalDot--; + List>? graph = getLenTable(); + List used = []; + List dst = []; + List pr = []; + + for (int i = 0; i < _amount; i++) { + dst.add(-1); + used.add(false); + pr.add(0); + } + + List q = []; + q.add(startDot); + used[startDot] = true; + dst[startDot] = 0; + pr[startDot] = -1; //у вершины нет предыдущей. + + while (q.isNotEmpty) { + int cur = q.removeAt(0); + int x = 0; + for (int neighbor in graph![cur]) { + if (neighbor != -1) { + if (!used[x]) { + q.add(x); + used[x] = true; + dst[x] = dst[cur] + 1; + pr[x] = cur; //сохранение предыдущей вершины + } + } + x++; + } + } + + //Восстановление кратчайшиего путьи + List path = []; + int cur = goalDot; + path.add(cur + 1); + while (pr[cur] != -1) { + cur = pr[cur]; + path.add(cur + 1); + } + + path = path.reversed.toList(); + if (path[0] == (startDot + 1) && + path[1] == (goalDot + 1) && + !_dots[startDot].hasConnection(goalDot + 1)) return null; + return path; + } \ No newline at end of file diff --git a/tex/dart/graph/dfs.dart b/tex/dart/graph/dfs.dart new file mode 100644 index 0000000..c9db34c --- /dev/null +++ b/tex/dart/graph/dfs.dart @@ -0,0 +1,19 @@ +List? dfsIterative(int v) { + v--; + List label = []; + for (int i = 0; i < _amount; i++) { + label.add(false); + } + List stack = []; + stack.add(v); + while (stack.isNotEmpty) { + v = stack.removeLast(); + if (!label[v]) { + label[v] = true; + for (int i in _dots[v].getL().keys) { + stack.add(i - 1); + } + } + } + return label; + } \ No newline at end of file diff --git a/tex/dart/graph/dijkstra.dart b/tex/dart/graph/dijkstra.dart new file mode 100644 index 0000000..55f5c9e --- /dev/null +++ b/tex/dart/graph/dijkstra.dart @@ -0,0 +1,29 @@ +List dijkstra(int from) { + List d = List.filled(_amount, intMax); + List p = List.filled(_amount, -1); + + d[from - 1] = 0; + List u = List.filled(_amount, false); + for (int i = 0; i < _amount; ++i) { + int v = -1; + for (int j = 0; j < _amount; ++j) { + if (!u[j] && (v == -1 || d[j]! < d[v]!)) { + v = j; + } + } + if (d[v] == intMax) break; + u[v] = true; + for (int to in _dots[v].getL().keys) { + int len = _dots[v].getL()[to]!; + if (!_useLength && len == 0) len = 1; + if (d[v]! + len < d[to - 1]!) { + d[to - 1] = d[v]! + len; + p[to - 1] = v; + } + } + } + for (int i = 0; i < d.length; i++) { + if (d[i] == intMax) d[i] = null; + } + return d; + } \ No newline at end of file diff --git a/tex/dart/graph/dot.dart b/tex/dart/graph/dot.dart new file mode 100644 index 0000000..498a81e --- /dev/null +++ b/tex/dart/graph/dot.dart @@ -0,0 +1,6 @@ +class Dot { + //Data + String _name = ""; + int num = -1; + Map _ln = {}; + *** diff --git a/tex/dart/graph/dot_constr.dart b/tex/dart/graph/dot_constr.dart new file mode 100644 index 0000000..87f4f98 --- /dev/null +++ b/tex/dart/graph/dot_constr.dart @@ -0,0 +1,24 @@ +Dot([String name = "Undefined", int n = -1]) { + _name = name; + num = n; + _ln = {}; + } + Dot.fromTwoLists(String name, List num0, List length, + [int n = -1]) { + _name = name; + num = n; + Map nw = {}; + if (num0.length != length.length) { + print("Error in lists"); + } else { + for (var i = 0; i < num0.length; i++) { + nw[num0[i]] = length[i]; + _ln = nw; + } + } + } + Dot.fromMap(String name, Map l, [int n = -1]) { + _name = name; + num = n; + _ln = l; + } \ No newline at end of file diff --git a/tex/dart/graph/dot_manip.dart b/tex/dart/graph/dot_manip.dart new file mode 100644 index 0000000..6d066c4 --- /dev/null +++ b/tex/dart/graph/dot_manip.dart @@ -0,0 +1,5 @@ +void setName(String n) => _name = n; + +void addPath(int inp, int length) => _ln[inp] = length; + +void delPath(int n) => _ln.removeWhere((key, value) => key == n); diff --git a/tex/dart/graph/graph.dart b/tex/dart/graph/graph.dart new file mode 100644 index 0000000..de0f479 --- /dev/null +++ b/tex/dart/graph/graph.dart @@ -0,0 +1,6 @@ +String _name = "Undefined"; //Имя +int _amount = 0; //Количество вершин +List _dots = []; //Список смежности вершин +Map _nameTable = {}; //Список вершин по именам +bool _useLength = false; //Взвешенность +bool _oriented = false; //Ориентированность \ No newline at end of file diff --git a/tex/dart/graph/kruscal.dart b/tex/dart/graph/kruscal.dart new file mode 100644 index 0000000..adecdcc --- /dev/null +++ b/tex/dart/graph/kruscal.dart @@ -0,0 +1,28 @@ +Graphs? kruskal() { + List g = getSortedPathList(); + //int cost = 0; + List res = []; + for (int i = 0; i < _amount; i++) { + res.add(Dot(_dots[i].getName(), _dots[i].num)); + } + List treeId = List.filled(_amount, 0); + for (int i = 0; i < _amount; ++i) { + treeId[i] = i; + } + for (int i = 0; i < g.length; ++i) { + int a = g[i].d - 1, b = g[i].p - 1; + int l = g[i].l; + if (treeId[a] != treeId[b]) { + //cost += l; + res[a].addPath(b + 1, l); + int oldId = treeId[b], newId = treeId[a]; + for (int j = 0; j < _amount; ++j) { + if (treeId[j] == oldId) { + treeId[j] = newId; + } + } + } + } + _dots = res; + return Graphs.fromList(_name, res, _useLength, _oriented); + } \ No newline at end of file diff --git a/tex/dart/page/build.dart b/tex/dart/page/build.dart new file mode 100644 index 0000000..797a264 --- /dev/null +++ b/tex/dart/page/build.dart @@ -0,0 +1,58 @@ +@override + Widget build(BuildContext context) { + screenSize = MediaQuery.of(context).size.width; + _textGrNmController.text = graphData.getName(); + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Align( + alignment: Alignment.topLeft, + child: Text("Graph name:\n", + style: TextStyle( + fontSize: 18, + color: Colors.white, + ))), + toolbarHeight: 110, + flexibleSpace: Container( + color: Colors.green.shade900, + child: Column(children: [ + /* + Описание элементов на панеле приложения + */ + ]), + ), + actions: [ + IconButton( + onPressed: () { + setState(() { + clearDropDownVals(); + graphData.flushData(); + clearInputData(); + }); + }, + icon: const Icon(Icons.delete_sweep), + iconSize: 60, + ), + ]), + body: CustomPaint( + painter: CurvePainter( + graphData: graphData, + intListPath: intListPath, + dfsAccessTable: dfsAccessTable, + start: startDot, + end: endDot, + op: op), + child: Align( + alignment: Alignment.topRight, + child: ButtonBar( + mainAxisSize: MainAxisSize.min, + children: [ + /* + Описание кнопок действия + */ + ], + ), + ), + ), + )); + } \ No newline at end of file diff --git a/tex/dart/page/button.dart b/tex/dart/page/button.dart new file mode 100644 index 0000000..5b10633 --- /dev/null +++ b/tex/dart/page/button.dart @@ -0,0 +1,14 @@ +ElevatedButton createButton(String txt, void Function() onPressing) { + return ElevatedButton( + onPressed: onPressing, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (states) => Colors.green.shade700)), + child: Text(txt, + style: const TextStyle( + fontSize: 15, + color: Colors.white70, + height: 1, + )), + ); + } \ No newline at end of file diff --git a/tex/dart/painter/arrow.dart b/tex/dart/painter/arrow.dart new file mode 100644 index 0000000..baeb8ac --- /dev/null +++ b/tex/dart/painter/arrow.dart @@ -0,0 +1,22 @@ + void _drawHArrow(Canvas canvas, Size size, Offset from, Offset to, [bool doubleSided = false]) { + Path path; + + // The arrows usually looks better with rounded caps. + Paint paint = ... // задается стиль + + var length = sqrt((to.dx - from.dx) * (to.dx - from.dx) + + (to.dy - from.dy) * (to.dy - from.dy)); + + path = Path(); + path.moveTo(from.dx, from.dy); + path.relativeCubicTo( + 0, + 0, + -(from.dx + to.dx + length) / (length) - 40, + -(from.dy + to.dy + length) / (length) - 40, + to.dx - from.dx, + to.dy - from.dy); + path = + ArrowPath.make(path: path, isDoubleSided: doubleSided, tipLength: 16); + canvas.drawPath(path, paint); + } \ No newline at end of file diff --git a/tex/dart/painter/constr.dart b/tex/dart/painter/constr.dart new file mode 100644 index 0000000..7deba68 --- /dev/null +++ b/tex/dart/painter/constr.dart @@ -0,0 +1,9 @@ +CurvePainter({ + Key? key, + required this.graphData, + required this.intListPath, + required this.dfsAccessTable, + required this.start, + required this.end, + required this.op, + }); \ No newline at end of file diff --git a/tex/dart/painter/pos.dart b/tex/dart/painter/pos.dart new file mode 100644 index 0000000..576c89d --- /dev/null +++ b/tex/dart/painter/pos.dart @@ -0,0 +1,24 @@ +Map _getDotPos(int dotsAm, Size size) { + Map off = {}; + var width = size.width / 2; + var height = size.height / 2; + int add = 0; + int h = _getHighInputConnections(); + for (int i = 0; i < dotsAm; i++) { + if ((i + 1) != h) { + double x = + cos(2 * pi * (i - add) / (dotsAm - add)) * _circleRad + width; + double y = + sin(2 * pi * (i - add) / (dotsAm - add)) * _circleRad + height; + + off[i + 1] = Offset(x, y); + } else if ((i + 1) == h) { + off[i + 1] = Offset(width + 2, height - 2); + add = 1; + h = 0; + } else { + print("GetDotPos error"); + } + } + return off; + } \ No newline at end of file diff --git a/tex/dart/pseudoDijkstra.txt b/tex/dart/pseudoDijkstra.txt new file mode 100644 index 0000000..7362dbc --- /dev/null +++ b/tex/dart/pseudoDijkstra.txt @@ -0,0 +1,9 @@ +dijkstra (G, w, s): + source(G,s) + S <- null + Q <- V[G] + while Q!=null: + u <- Extract_min(Q) + s <- S + u + для каждой вершины v из Adj[u]: + relax(u, v, w) \ No newline at end of file diff --git a/tex/dart/pseudoKruscal.txt b/tex/dart/pseudoKruscal.txt new file mode 100644 index 0000000..3464bf5 --- /dev/null +++ b/tex/dart/pseudoKruscal.txt @@ -0,0 +1,10 @@ +kruscal(G, w): + A <- null + for v из V[G]: + create_set(v) + sort(E) + for (u, v) из E: + if find_set(u) != find_set(v): + A <- A + {(u,v)} + + return A \ No newline at end of file diff --git a/tex/example-work.tex b/tex/example-work.tex index 28e31c0..8c6d82e 100644 --- a/tex/example-work.tex +++ b/tex/example-work.tex @@ -245,35 +245,49 @@ $$ E = \{(a, b), (a, c), (a, e), (b, c), (b, d), (c, e), (d, e)\}$$ Поиск в ширину имеет такое название потому, что в процессе обхода мы идём вширь, то есть перед тем как приступить к поиску вершин на расстоянии $k + 1$, выполняется обход вершин на расстоянии $k$~\cite{Algo_2013}. -Приведенная ниже процедура поиска в ширину BFS предполагает, что входной граф $G = (V, E)$ представлен при помощи списков смежности. Псевдокод: +Приведенная ниже процедура поиска в ширину BFS предполагает, что входной граф $G = (V, E)$ представлен при помощи списков смежности. Псевдокод алгоритма: \inputminted[fontsize=\footnotesize, linenos]{python}{./dart/pseudoBFS.txt} Воспользуемся групповым анализом. Операции внесения в очередь и удаление из нее требует $O(1)$ времени. Следовательно на очередь потребуется $O(V)$ времени. Т.к. максимальная длина списков смежности $\theta(E)$, то время, необходимое для сканирования списков, равно $O(E)$. Расходы на инициализацию: $O(V)$. Таким образом, общее время работы алгоритма: $O(V + E)$. Время поиска в ширину линейно зависит от размера представления графа. \subsubsection{Обход в глубину} +Стратегия обхода в ширину состоит в том, чтобы идти ''вглубь'' графа. Сначала исследуются ребра, выходящие из вершины, открытой последней, и покидаем вершину, когда не остается неисследованных ребер --- при этом происходит возврат в вершину, из которой была открыта вершина $v$. Процесс продолжается, пока не будут открыты все вершины, достижимые из исходной. - - - +Псевдо код алгоритма: \inputminted[fontsize=\footnotesize, linenos]{python}{./dart/pseudoDFS.txt} +\subsubsection{Алгоритм Дейкстры} +Алгоритм был предложен голландским исследователем Эдсгером Дейкстрой в 1959 году. Используется для поиска кратчайшего пути от одной вершины до всех остальных. Используется только если вес ребер неотрицательный. +В алгоритме поддерживается множество вершин $U$, для которых уже вычислены длины кратчайших путей до них из $s$. На каждой итерации основного цикла выбирается вершина, которой на текущий момент соответствует минимальная оценка кратчайшего пути. Вершина $u$ добавляется в множество $U$ и производится релаксация всех исходящих из неё рёбер. + +Псевдокод алгоритма: +\inputminted[fontsize=\footnotesize, linenos]{python}{./dart/pseudoDijkstra.txt} + +\subsubsection{Алгоритм Краскала} +Алгоритм Краскала — алгоритм поиска минимального остовного дерева во взвешенном неориентированном связном графе. Был описан Джозефом Краскалом в 1956 году. + +Алгоритм Крускала изначально помещает каждую вершину в своё дерево, а затем постепенно объединяет эти деревья, объединяя на каждой итерации два некоторых дерева некоторым ребром. Перед началом выполнения алгоритма, все рёбра сортируются по весу (в порядке неубывания). Затем начинается процесс объединения: перебираются все рёбра от первого до последнего (в порядке сортировки), и если у текущего ребра его концы принадлежат разным поддеревьям, то эти поддеревья объединяются, а ребро добавляется к ответу. По окончании перебора всех рёбер, все вершины окажутся принадлежащими одному поддереву.\cite{krusc} + +\inputminted[fontsize=\footnotesize, linenos]{python}{./dart/pseudoKruscal.txt} + \section{Инструменты} Рассмотрим используемый язык и библиотеку для отрисовки. \subsection{Dart} В качестве основы используется язык \textbf{Dart}, разработанный компанией Google, и широко используемый для кросс-платформенной разработки~\cite{dart_web}. - Dart позиционируется в качестве замены/альтернативы JavaScript. Один из разработчиков языка Марк Миллер (Mark S. Miller) написал, что JavaScript «имеет фундаментальные изъяны»\cite{futureOfJavascript} («Javascript has fundamental flaws…»), которые невозможно исправить. +Dart позиционируется в качестве замены/альтернативы JavaScript. Один из разработчиков языка Марк Миллер (Mark S. Miller) писал, что JavaScript «имеет фундаментальные изъяны»\cite{futureOfJavascript}, которые невозможно исправить. Версия 1.0 вышла в 14 ноября 2013 года. Вторая версия была выпущена в августе 2018 года. В языке появилась надежная система типов, т.~е. во время выполнении программы все переменные будут гарантированно указанному типу~\cite{dart_sound}. +В Dart используется сборщик мусора. Синтаксис похож на языки: JavaScript, C\#, Java~\cite{dartInAction}. + Пример HelloWorld на Dart: \inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/helloWorld.dart} Dart поддерживает сокращенную запись. Код примера HelloWorld можно записать так: \inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/hW.dart} - Концепты языка~\cite{dart_tour}: \begin{itemize} \item Все, что можно поместить в переменную, является объектом, а каждый объект является частью класса; @@ -283,7 +297,6 @@ Dart \item для объявления локальных функций и переменных необходимо начать имя со знака ''\_'' \end{itemize} . - \subsection{Flutter} Для отрисовки информации используется фреймворк c открытым исходным кодом ''Flutter'', разработанный компанией Google. Flutter не использует нативные компоненты для отрисовки интерфейса. В его основе лежит графический движок ''Skia'', написанный преимущественно на С++. @@ -299,48 +312,139 @@ Skia --- \caption{\label{fig:flutter_example} Простейшее приложение на Flutter} \end{figure} +Интерфейс описывается с помощью виджетов. \section{Реализация} +Структура программы разбита на 4 файла: + +\begin{itemize} + \item main.dart --- точка входа в программу; + \item drawing\_page.dart --- страница с описанием работы кнопок; + \item curve\_painter.dart --- функционал для отрисовки графа; + \item graph.dart --- класс для хранения графа и манипуляции с ним. +\end{itemize} + \subsection{Графы} -\subsubsection{Класс для вершины} -\subsubsection{Класс для графа} +Информация хранится в виде списков смежности. +Полный код можно посмотреть в приложении~\ref{graph}. +\subsubsection{Класс для хранения вершины} +Класс \textbf{Dot} используется для хранения информации о ребрах, исходящих из данной вершины. + +Основная информация: +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/dot.dart} +\textit{String \_name} используется для хранения имени, \textit{int num} используется для хранения номера вершины. +Информация о ребрах задается в виде \textit{Map \_ln}, где каждой вершине можно присвоить вес ребра. + +Создать вершину можно тремя способами: +\begin{enumerate} + \item Пустая точка; + \item из двух списков, где в первом список вершин, а во втором - длины пути; + \item из \textit{Map}. +\end{enumerate} +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/dot_constr.dart} + +Для манипуляций с информацией доступно: +\begin{enumerate} + \item изменение имени; + \item добавление пути; + \item удаление пути. +\end{enumerate} +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/dot_manip.dart} + +\subsubsection{Класс для графов} +Класс \textbf{Graphs} используется для хранения точек и манипуляции с ними. + +Основная информация: +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/graph.dart} +\textit{String \_name} исользуется для имени графа, \textit{int \_amount} --- количество вершин, \textit{List \_dots} --- массив точек, \textit{Map \_nameTable} --- отслеживание имени каждой вершины, \textit{bool \_useLength} --- отслеживание взвешенности графа, \textit{bool \_oriented} --- отслеживание ориентированности графа. + +Для работы графом доступны функции: +\begin{enumerate} + \item \textbf{String? addDot(Dot a)} --- добавление вершины; + \item \textbf{String? addIsolated(String name)} --- добавление изолированной точки; + \item \textbf{String? addPath(int from, int to, [int len = 0])} --- добавление пути; + \item \textbf{String? delPath(int from, int to)} --- удаление пути; + \item \textbf{String? delDot(int inn)} --- удаление вершины; + \item \textbf{void flushData()} --- удаление информации из графа; + \item \textbf{bool checkDots([bool verbose = false])} --- проверка графа на ошибки; + \item \textbf{void \_fixAfterDel(int inn}) --- испралвение нумерации после удаления; + \item \textbf{void \_syncNameTable()} --- синхронизация таблицы имен вершин; + \item \textbf{void setName(String name)} --- изменение имени; + \item \textbf{String? replaceDataFromFile(String path)} --- заменяет информацию графа на информацию из файла; + \item \textbf{List getSortedPathList()} --- возвращает список всех путей, отсортированный в порядке неубывания; + \item \textbf{void printG()} --- выводит информацию о графе в консоль; + \item \textbf{void printToFile(String name)} --- выводит информацию о графе в файл; +\end{enumerate} + +Создать граф можно тремя способами: +\begin{enumerate} + \item пустой граф; + \item из списка смежности; + \item из файла. +\end{enumerate} + \subsubsection{Алгоритмы} -\subsubsection{Кнопки} +Алгоритмы описаны в классе графа.\newline + +Обход в ширину: +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/bfs.dart} +Обход в глубину: +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/dfs.dart} +Алгоритм Дейкстры: +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/dijkstra.dart} +Алгоритм Краскала: +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/kruscal.dart} +\subsubsection{Интерфейс} +Интерфейс описан в файле drawing\_page.dart. Полный код доступен в приложении~\ref{buttons}. + +Интерфейс программы: +\begin{figure}[!ht] + \centering + \includegraphics[width=9cm]{./pic/grafs.png} + \caption{\label{fig:programm} + Интерфейс программы} +\end{figure} +\newpage +В качестве основы выбран виджет, сохраняющий состояние. + +Построение интерфейса: +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/page/build.dart} + +Запуск работы алгоритмов происходит по нажатию соответствующих кнопок, затем страница отрисовки, используя полученные данные, отрисовывает на экране информацию. \subsubsection{Отрисовка графа} +Отрисовка графа описана в файле curve\_painter.dart. Код доступен в приложении~\ref{painter}. -\section{Таблица} -\subsection{Текст с таблицей} +Для отрисовки информации используется виджет CustomPaint~\cite{custompaint}. -\begin{table}[!ht] - \small - \caption{Результат сокращения словарей неисправностей при помощи масок} \label{table-1} - \begin{tabular}{|l|c|c|c|c|r|r|r|} - \hline 1 & 2& 3& 4& 5& 6& 7& 8\\ - \hline S298 & 177 & 1932 & 341964 & 61 & 10797 & 3,16\% & 0,61\\ - \hline S344 & 240 & 1397 & 335280 & 59 & 14160 & 4,22\% & 0,53\\ - \hline S349 & 243 & 1474 & 358182 & 62 & 15066 & 4,21\% & 0,60\\ - \hline S382 & 190 & 12444 & 2364360 & 55 & 10450 & 0,44\% & 3,78\\ - \hline S386 & 274 & 2002 & 548548 & 91 & 24934 & 4,55\% & 1,40\\ - \hline S400 & 194 & 13284 & 2577096 & 58 & 11252 & 0,44\% & 4,28\\ - \hline S444 & 191 & 13440 & 2567040 & 60 & 11460 & 0,45\% & 4,26\\ - \hline S510 & 446 & 700 & 312200 & 70 & 31220 & 10,00\% & 0,63\\ - \hline S526 & 138 & 13548 & 1869624 & 38 & 5244 & 0,28\% & 2,41\\ - \hline S641 & 345 & 5016 & 1730520 & 132 & 45540 & 2,63\% & 7,06\\ - \hline S713 & 343 & 3979 & 1364797 & 131 & 44933 & 3,29\% & 5,61\\ - \hline S820 & 712 & 21185 & 15083720 & 244 & 173728 & 1,15\% & 126,99\\ - \hline S832 & 719 & 21603 & 15532557 & 253 & 181907 & 1,17\% & 135,18\\ - \hline S953 & 326 & 322 & 104972 & 91 & 29666 & 28,26\% & 0,27\\ - \hline S1423 & 293 & 750 & 219750 & 93 & 27249 & 12,40\% & 0,57\\ - \hline S1488 & 1359 & 22230 & 30210570 & 384 & 521856 & 1,73\% & 541,69\\ - \hline - \end{tabular} -\end{table} +Для передачи информации графа используется конструктор CurvePainter. +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/painter/constr.dart} +Отрисовка соединений происходит с помощью пакета ''arrow\_path''. +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/painter/arrow.dart} +Для упрощения отрисовки, точки располагаются на окружности с центом, совпадающим с центром экрана. Сначала вычисляется их местоположение на экране, точка с максимальным количеством соединений располагается в центре: +\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/painter/pos.dart} +Процесс вывода на экран: +\begin{enumerate} + \item вывод вершин на экран; + \item определение текущей операции и вывод работы текущего алгоритма; + \item вывод ребер; + \item вывод длин ребер; + \item вывод имен вершин. +\end{enumerate} +\subsubsection{Итоговый вид приложения} +Итоговое приложение выглядит так: +\begin{figure}[!ht] + \centering + \includegraphics[width=15cm]{./pic/result.png} + \caption{\label{fig:resutl} + Интерфейс программы} +\end{figure} +\newpage % Раздел "Заключение" \conclusion Было разработано простое приложение для создания и работы с графами с использованием Dart и Flutter. -В приложении возможно: +В приложении возможно с помощью графического интерфейса: \begin{itemize} \item создать пустой граф; \item добавить вершину; @@ -349,20 +453,13 @@ Skia --- \item удалить путь; \item сохранить граф; \item загрузить граф; - \item построить минимальное остовное дерево с помощью алгоритма Крускала; + \item построить минимальное остовное дерево с помощью алгоритма Краскала; \item найти минимальный путь из выбранной вершины в другие с помощью алгоритма Дейкстры; \item проверить доступность вершин с помощью обхода в глубину; \item построить путь из одной вершины в другую с помощью обхода в ширину. \end{itemize} -%Библиографический список, составленный вручную, без использования BibTeX -% -%\begin{thebibliography}{99} -% \bibitem{Ione} Источник 1. -% \bibitem{Itwo} Источник 2 -%\end{thebibliography} - %Библиографический список, составленный с помощью BibTeX % \bibliographystyle{gost780uv} diff --git a/tex/example-work.toc b/tex/example-work.toc index 1a9e1c1..13abea6 100644 --- a/tex/example-work.toc +++ b/tex/example-work.toc @@ -8,22 +8,29 @@ \contentsline {subsubsection}{\numberline {1.3.2}\IeC {\CYRM }\IeC {\cyra }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrc }\IeC {\cyra } \IeC {\cyrs }\IeC {\cyrm }\IeC {\cyre }\IeC {\cyrzh }\IeC {\cyrn }\IeC {\cyro }\IeC {\cyrs }\IeC {\cyrt }\IeC {\cyri }}{6}{subsubsection.1.3.2}% \contentsline {subsubsection}{\numberline {1.3.3}\IeC {\CYRM }\IeC {\cyra }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrc }\IeC {\cyra } \IeC {\cyri }\IeC {\cyrn }\IeC {\cyrc }\IeC {\cyri }\IeC {\cyrd }\IeC {\cyre }\IeC {\cyrn }\IeC {\cyrt }\IeC {\cyrn }\IeC {\cyro }\IeC {\cyrs }\IeC {\cyrt }\IeC {\cyri }}{6}{subsubsection.1.3.3}% \contentsline {subsubsection}{\numberline {1.3.4}\IeC {\CYRS }\IeC {\cyrp }\IeC {\cyri }\IeC {\cyrs }\IeC {\cyrk }\IeC {\cyri } \IeC {\cyrs }\IeC {\cyrm }\IeC {\cyre }\IeC {\cyrzh }\IeC {\cyrn }\IeC {\cyro }\IeC {\cyrs }\IeC {\cyrt }\IeC {\cyri }}{7}{subsubsection.1.3.4}% -\contentsline {section}{\numberline {2}\IeC {\CYRI }\IeC {\cyrn }\IeC {\cyrs }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyru }\IeC {\cyrm }\IeC {\cyre }\IeC {\cyrn }\IeC {\cyrt }\IeC {\cyrery }}{8}{section.2}% -\contentsline {subsection}{\numberline {2.1}Dart}{8}{subsection.2.1}% -\contentsline {subsection}{\numberline {2.2}Flutter}{8}{subsection.2.2}% -\contentsline {section}{\numberline {3}\IeC {\CYRR }\IeC {\cyre }\IeC {\cyra }\IeC {\cyrl }\IeC {\cyri }\IeC {\cyrz }\IeC {\cyra }\IeC {\cyrc }\IeC {\cyri }\IeC {\cyrya }}{8}{section.3}% -\contentsline {subsection}{\numberline {3.1}\IeC {\CYRT }\IeC {\cyre }\IeC {\cyrk }\IeC {\cyrs }\IeC {\cyrt } \IeC {\cyrs } \IeC {\cyrf }\IeC {\cyro }\IeC {\cyrr }\IeC {\cyrm }\IeC {\cyru }\IeC {\cyrl }\IeC {\cyra }\IeC {\cyrm }\IeC {\cyri } \IeC {\cyri } \IeC {\cyrl }\IeC {\cyre }\IeC {\cyrm }\IeC {\cyrm }\IeC {\cyro }\IeC {\cyrishrt }}{8}{subsection.3.1}% -\contentsline {subsection}{\numberline {3.2}\IeC {\CYRN }\IeC {\cyra }\IeC {\cyrz }\IeC {\cyrv }\IeC {\cyra }\IeC {\cyrn }\IeC {\cyri }\IeC {\cyre } \IeC {\cyrd }\IeC {\cyrr }\IeC {\cyru }\IeC {\cyrg }\IeC {\cyro }\IeC {\cyrg }\IeC {\cyro } \IeC {\cyrp }\IeC {\cyro }\IeC {\cyrd }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrz }\IeC {\cyrd }\IeC {\cyre }\IeC {\cyrl }\IeC {\cyra }}{8}{subsection.3.2}% -\contentsline {subsubsection}{\numberline {3.2.1}\IeC {\CYRB }\IeC {\cyro }\IeC {\cyrl }\IeC {\cyre }\IeC {\cyre } \IeC {\cyrm }\IeC {\cyre }\IeC {\cyrl }\IeC {\cyrk }\IeC {\cyri }\IeC {\cyrishrt } \IeC {\cyrp }\IeC {\cyro }\IeC {\cyrd }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrz }\IeC {\cyrd }\IeC {\cyre }\IeC {\cyrl }}{8}{subsubsection.3.2.1}% -\contentsline {subsubsection}{\numberline {3.2.2}\IeC {\CYRT }\IeC {\cyre }\IeC {\cyrk }\IeC {\cyrs }\IeC {\cyrt } \IeC {\cyrs } \IeC {\cyrt }\IeC {\cyra }\IeC {\cyrb }\IeC {\cyrl }\IeC {\cyri }\IeC {\cyrc }\IeC {\cyre }\IeC {\cyrishrt }}{8}{subsubsection.3.2.2}% -\contentsline {subsubsection}{\numberline {3.2.3}\IeC {\CYRT }\IeC {\cyre }\IeC {\cyrk }\IeC {\cyrs }\IeC {\cyrt } \IeC {\cyrs } \IeC {\cyrk }\IeC {\cyro }\IeC {\cyrd }\IeC {\cyro }\IeC {\cyrm } \IeC {\cyrp }\IeC {\cyrr }\IeC {\cyro }\IeC {\cyrg }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrm }\IeC {\cyrm }\IeC {\cyrery }}{8}{subsubsection.3.2.3}% -\contentsline {section}{\cyrillictext \CYRZ \CYRA \CYRK \CYRL \CYRYU \CYRCH \CYRE \CYRN \CYRI \CYRE }{8}{section*.8}% -\contentsline {section}{\cyrillictext \CYRS \CYRP \CYRI \CYRS \CYRO \CYRK \ \CYRI \CYRS \CYRP \CYRO \CYRL \CYRSFTSN \CYRZ \CYRO \CYRV \CYRA \CYRN \CYRN \CYRERY \CYRH \ \CYRI \CYRS \CYRT \CYRO \CYRCH \CYRN \CYRI \CYRK \CYRO \CYRV }{10}{section*.9}% +\contentsline {subsection}{\numberline {1.4}\IeC {\CYRA }\IeC {\cyrl }\IeC {\cyrg }\IeC {\cyro }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrt }\IeC {\cyrm }\IeC {\cyrery }}{7}{subsection.1.4}% +\contentsline {subsubsection}{\numberline {1.4.1}\IeC {\CYRO }\IeC {\cyrb }\IeC {\cyrh }\IeC {\cyro }\IeC {\cyrd } \IeC {\cyrv } \IeC {\cyrsh }\IeC {\cyri }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrn }\IeC {\cyru }}{7}{subsubsection.1.4.1}% +\contentsline {subsubsection}{\numberline {1.4.2}\IeC {\CYRO }\IeC {\cyrb }\IeC {\cyrh }\IeC {\cyro }\IeC {\cyrd } \IeC {\cyrv } \IeC {\cyrg }\IeC {\cyrl }\IeC {\cyru }\IeC {\cyrb }\IeC {\cyri }\IeC {\cyrn }\IeC {\cyru }}{8}{subsubsection.1.4.2}% +\contentsline {subsubsection}{\numberline {1.4.3}\IeC {\CYRA }\IeC {\cyrl }\IeC {\cyrg }\IeC {\cyro }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrt }\IeC {\cyrm } \IeC {\CYRD }\IeC {\cyre }\IeC {\cyrishrt }\IeC {\cyrk }\IeC {\cyrs }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyrery }}{9}{subsubsection.1.4.3}% +\contentsline {subsubsection}{\numberline {1.4.4}\IeC {\CYRA }\IeC {\cyrl }\IeC {\cyrg }\IeC {\cyro }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrt }\IeC {\cyrm } \IeC {\CYRK }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrs }\IeC {\cyrk }\IeC {\cyra }\IeC {\cyrl }\IeC {\cyra }}{9}{subsubsection.1.4.4}% +\contentsline {section}{\numberline {2}\IeC {\CYRI }\IeC {\cyrn }\IeC {\cyrs }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyru }\IeC {\cyrm }\IeC {\cyre }\IeC {\cyrn }\IeC {\cyrt }\IeC {\cyrery }}{11}{section.2}% +\contentsline {subsection}{\numberline {2.1}Dart}{11}{subsection.2.1}% +\contentsline {subsection}{\numberline {2.2}Flutter}{12}{subsection.2.2}% +\contentsline {section}{\numberline {3}\IeC {\CYRR }\IeC {\cyre }\IeC {\cyra }\IeC {\cyrl }\IeC {\cyri }\IeC {\cyrz }\IeC {\cyra }\IeC {\cyrc }\IeC {\cyri }\IeC {\cyrya }}{14}{section.3}% +\contentsline {subsection}{\numberline {3.1}\IeC {\CYRG }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrf }\IeC {\cyrery }}{14}{subsection.3.1}% +\contentsline {subsubsection}{\numberline {3.1.1}\IeC {\CYRK }\IeC {\cyrl }\IeC {\cyra }\IeC {\cyrs }\IeC {\cyrs } \IeC {\cyrd }\IeC {\cyrl }\IeC {\cyrya } \IeC {\cyrh }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrn }\IeC {\cyre }\IeC {\cyrn }\IeC {\cyri }\IeC {\cyrya } \IeC {\cyrv }\IeC {\cyre }\IeC {\cyrr }\IeC {\cyrsh }\IeC {\cyri }\IeC {\cyrn }\IeC {\cyrery }}{14}{subsubsection.3.1.1}% +\contentsline {subsubsection}{\numberline {3.1.2}\IeC {\CYRK }\IeC {\cyrl }\IeC {\cyra }\IeC {\cyrs }\IeC {\cyrs } \IeC {\cyrd }\IeC {\cyrl }\IeC {\cyrya } \IeC {\cyrg }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrf }\IeC {\cyro }\IeC {\cyrv }}{15}{subsubsection.3.1.2}% +\contentsline {subsubsection}{\numberline {3.1.3}\IeC {\CYRA }\IeC {\cyrl }\IeC {\cyrg }\IeC {\cyro }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrt }\IeC {\cyrm }\IeC {\cyrery }}{16}{subsubsection.3.1.3}% +\contentsline {subsubsection}{\numberline {3.1.4}\IeC {\CYRI }\IeC {\cyrn }\IeC {\cyrt }\IeC {\cyre }\IeC {\cyrr }\IeC {\cyrf }\IeC {\cyre }\IeC {\cyrishrt }\IeC {\cyrs }}{19}{subsubsection.3.1.4}% +\contentsline {subsubsection}{\numberline {3.1.5}\IeC {\CYRO }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrs }\IeC {\cyro }\IeC {\cyrv }\IeC {\cyrk }\IeC {\cyra } \IeC {\cyrg }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrf }\IeC {\cyra }}{21}{subsubsection.3.1.5}% +\contentsline {subsubsection}{\numberline {3.1.6}\IeC {\CYRI }\IeC {\cyrt }\IeC {\cyro }\IeC {\cyrg }\IeC {\cyro }\IeC {\cyrv }\IeC {\cyrery }\IeC {\cyrishrt } \IeC {\cyrv }\IeC {\cyri }\IeC {\cyrd } \IeC {\cyrp }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrl }\IeC {\cyro }\IeC {\cyrzh }\IeC {\cyre }\IeC {\cyrn }\IeC {\cyri }\IeC {\cyrya }}{23}{subsubsection.3.1.6}% +\contentsline {section}{\cyrillictext \CYRZ \CYRA \CYRK \CYRL \CYRYU \CYRCH \CYRE \CYRN \CYRI \CYRE }{24}{section*.10}% +\contentsline {section}{\cyrillictext \CYRS \CYRP \CYRI \CYRS \CYRO \CYRK \ \CYRI \CYRS \CYRP \CYRO \CYRL \CYRSFTSN \CYRZ \CYRO \CYRV \CYRA \CYRN \CYRN \CYRERY \CYRH \ \CYRI \CYRS \CYRT \CYRO \CYRCH \CYRN \CYRI \CYRK \CYRO \CYRV }{25}{section*.11}% \redeflsection \ttl@change@i {\@ne }{section}{3ex}{\hspace {-3ex}}{\appendixname ~\thecontentslabel \hspace {2ex}}{\hspace {2.3em}}{\titlerule *[0.98ex]{.}\contentspage }\relax \ttl@change@v {section}{}{}{}\relax -\contentsline {section}{\numberline {\CYRA }\IeC {\CYRK }\IeC {\cyro }\IeC {\cyrd } main.dart}{11}{appendix.A}% -\contentsline {section}{\numberline {\CYRB }\IeC {\CYRK }\IeC {\cyro }\IeC {\cyrd } \IeC {\cyrs }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrn }\IeC {\cyri }\IeC {\cyrc }\IeC {\cyrery } \IeC {\cyro }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrs }\IeC {\cyro }\IeC {\cyrv }\IeC {\cyrk }\IeC {\cyri }}{12}{appendix.B}% -\contentsline {section}{\numberline {\CYRV }\IeC {\CYRK }\IeC {\cyro }\IeC {\cyrd } \IeC {\cyro }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrs }\IeC {\cyro }\IeC {\cyrv }\IeC {\cyrk }\IeC {\cyri } \IeC {\cyrg }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrf }\IeC {\cyra }}{23}{appendix.C}% -\contentsline {section}{\numberline {\CYRG }\IeC {\CYRK }\IeC {\cyro }\IeC {\cyrd } \IeC {\cyrk }\IeC {\cyrl }\IeC {\cyra }\IeC {\cyrs }\IeC {\cyrs }\IeC {\cyra } \IeC {\cyrd }\IeC {\cyrl }\IeC {\cyrya } \IeC {\cyrr }\IeC {\cyra }\IeC {\cyrb }\IeC {\cyro }\IeC {\cyrt }\IeC {\cyrery } \IeC {\cyrs } \IeC {\cyrg }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrf }\IeC {\cyro }\IeC {\cyrm }}{30}{appendix.D}% +\contentsline {section}{\numberline {\CYRA }\IeC {\CYRK }\IeC {\cyro }\IeC {\cyrd } main.dart}{26}{appendix.A}% +\contentsline {section}{\numberline {\CYRB }\IeC {\CYRK }\IeC {\cyro }\IeC {\cyrd } \IeC {\cyrs }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrn }\IeC {\cyri }\IeC {\cyrc }\IeC {\cyrery } \IeC {\cyro }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrs }\IeC {\cyro }\IeC {\cyrv }\IeC {\cyrk }\IeC {\cyri }}{27}{appendix.B}% +\contentsline {section}{\numberline {\CYRV }\IeC {\CYRK }\IeC {\cyro }\IeC {\cyrd } \IeC {\cyro }\IeC {\cyrt }\IeC {\cyrr }\IeC {\cyri }\IeC {\cyrs }\IeC {\cyro }\IeC {\cyrv }\IeC {\cyrk }\IeC {\cyri } \IeC {\cyrg }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrf }\IeC {\cyra }}{38}{appendix.C}% +\contentsline {section}{\numberline {\CYRG }\IeC {\CYRK }\IeC {\cyro }\IeC {\cyrd } \IeC {\cyrk }\IeC {\cyrl }\IeC {\cyra }\IeC {\cyrs }\IeC {\cyrs }\IeC {\cyra } \IeC {\cyrd }\IeC {\cyrl }\IeC {\cyrya } \IeC {\cyrr }\IeC {\cyra }\IeC {\cyrb }\IeC {\cyro }\IeC {\cyrt }\IeC {\cyrery } \IeC {\cyrs } \IeC {\cyrg }\IeC {\cyrr }\IeC {\cyra }\IeC {\cyrf }\IeC {\cyro }\IeC {\cyrm }}{45}{appendix.D}% \contentsfinish diff --git a/tex/lib/pages/drawing_page.dart b/tex/lib/pages/drawing_page.dart index aadccff..11f6d57 100644 --- a/tex/lib/pages/drawing_page.dart +++ b/tex/lib/pages/drawing_page.dart @@ -24,7 +24,6 @@ class _DrawingPageState extends State { Graphs graphData = getGraph(); List? intListPath; List? dfsAccessTable; - //List? dijkstraTable; String op = Operations.none; int? startDot; int? endDot; diff --git a/tex/lib/src/curve_painter.dart b/tex/lib/src/curve_painter.dart index c92ff75..ab5659d 100644 --- a/tex/lib/src/curve_painter.dart +++ b/tex/lib/src/curve_painter.dart @@ -128,7 +128,6 @@ class CurvePainter extends CustomPainter { print("GetDotPos error"); } } - return off; } @@ -245,7 +244,7 @@ class CurvePainter extends CustomPainter { _drawDot(canvas, _off[start]!, 9, Colors.green); _drawDot(canvas, _off[end]!, 7, Colors.red.shade200); for (int i = 0; i < intListPath!.length; i++) { - _drawDotNum(canvas, _off[intListPath![i]]!, "bfs: №${i + 1}"); + _drawDotNum(canvas, _off[intListPath![i]]!, "bfs: пїЅ${i + 1}"); } } } diff --git a/tex/lib/src/graph.dart b/tex/lib/src/graph.dart index c368d71..9ceafeb 100644 --- a/tex/lib/src/graph.dart +++ b/tex/lib/src/graph.dart @@ -449,7 +449,7 @@ class Graphs { result.sort((a, b) => a.l.compareTo(b.l)); return result; } - + //*****Getters******* //******Print****** @@ -541,7 +541,6 @@ class Graphs { //************Алгоритмы************ List? bfsPath(int startDot, int goalDot) { if (startDot == goalDot) return [startDot]; - //if (!bfsHasPath(startDot, goalDot)) return null; startDot--; goalDot--; List>? graph = getLenTable(); @@ -559,8 +558,7 @@ class Graphs { q.add(startDot); used[startDot] = true; dst[startDot] = 0; - pr[startDot] = - -1; //Пометка, означающая, что у вершины startDot нет предыдущей. + pr[startDot] = -1; //у вершины нет предыдущей. while (q.isNotEmpty) { int cur = q.removeAt(0); @@ -578,21 +576,16 @@ class Graphs { } } - //Восстановим кратчайший путь - //Для восстановления пути пройдём его в обратном порядке, и развернём. + //Восстановление кратчайшиего путьи List path = []; - - int cur = goalDot; //текущая вершина пути + int cur = goalDot; path.add(cur + 1); - while (pr[cur] != -1) { - //пока существует предыдущая вершина - cur = pr[cur]; //переходим в неё - path.add(cur + 1); //и дописываем к пути + cur = pr[cur]; + path.add(cur + 1); } path = path.reversed.toList(); - if (path[0] == (startDot + 1) && path[1] == (goalDot + 1) && !_dots[startDot].hasConnection(goalDot + 1)) return null; @@ -607,14 +600,12 @@ class Graphs { } List stack = []; stack.add(v); - //pos.add(v); while (stack.isNotEmpty) { v = stack.removeLast(); if (!label[v]) { label[v] = true; for (int i in _dots[v].getL().keys) { stack.add(i - 1); - //pos.add(i); } } } @@ -630,7 +621,6 @@ class Graphs { for (int i = 0; i < _amount; ++i) { int v = -1; for (int j = 0; j < _amount; ++j) { - // int t; if (!u[j] && (v == -1 || d[j]! < d[v]!)) { v = j; } @@ -647,7 +637,6 @@ class Graphs { } } for (int i = 0; i < d.length; i++) { - // подумать как убрать эту часть if (d[i] == intMax) d[i] = null; } return d; diff --git a/tex/pic/grafs.png b/tex/pic/grafs.png new file mode 100644 index 0000000..3fb4c74 Binary files /dev/null and b/tex/pic/grafs.png differ diff --git a/tex/pic/result.png b/tex/pic/result.png new file mode 100644 index 0000000..3723587 Binary files /dev/null and b/tex/pic/result.png differ diff --git a/tex/thesis.bib b/tex/thesis.bib index 8671968..d18d2ec 100644 --- a/tex/thesis.bib +++ b/tex/thesis.bib @@ -61,4 +61,14 @@ note={URL:~\url{https://skia.org/docs/}(Дата обращения 20.11.2021). Загл. с экр. Яз. англ.}, } +@Manual{krusc, + title={Минимальное остовное дерево. Алгоритм Крускала. [{Э}лектронный ресурс]}, + note={URL:~\url{https://e-maxx.ru/algo/mst_kruskal}(Дата обращения 20.11.2021). Загл. с экр. Яз. рус.}, +} + +@Manual{custompaint, + title={CustomPaint class. [{Э}лектронный ресурс]}, + note={URL:~\url{https://api.flutter.dev/flutter/widgets/CustomPaint-class.html}(Дата обращения 20.11.2021). Загл. с экр. Яз. англ.}, +} + @Comment{jabref-meta: databaseType:bibtex;}