From 019831410ef0f22c27eea6e2031a6b1efc2b73b7 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed, 15 May 2024 11:40:22 +0000 Subject: [PATCH 001/131] Setting up GitHub Classroom Feedback From 0acceecb9368f5ef474d4b06a86b295fb5c9e151 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Fri, 17 May 2024 19:25:47 +0300 Subject: [PATCH 002/131] Initial commit --- .gitignore | 39 ++++ build.gradle.kts | 35 ++++ gradle.properties | 4 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 234 +++++++++++++++++++++++ gradlew.bat | 89 +++++++++ settings.gradle.kts | 16 ++ src/main/kotlin/Main.kt | 0 9 files changed, 422 insertions(+) create mode 100644 .gitignore create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts create mode 100644 src/main/kotlin/Main.kt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f35ca9e --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..9a0036f --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,35 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + kotlin("jvm") + id("org.jetbrains.compose") +} + +group = "org.example" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() +} + +dependencies { + // Note, if you develop a library, you should use compose.desktop.common. + // compose.desktop.currentOs should be used in launcher-sourceSet + // (in a separate module for demo project and in testMain). + // With compose.desktop.common you will also lose @Preview functionality + implementation(compose.desktop.currentOs) +} + +compose.desktop { + application { + mainClass = "MainKt" + + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "graph13" + packageVersion = "1.0.0" + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..1208625 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +kotlin.code.style=official +kotlin.version=1.9.22 +compose.version=1.6.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..bd23cd6 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() + gradlePluginPortal() + mavenCentral() + } + + plugins { + kotlin("jvm").version(extra["kotlin.version"] as String) + id("org.jetbrains.compose").version(extra["compose.version"] as String) + } +} + +rootProject.name = "Graphs" + diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt new file mode 100644 index 0000000..e69de29 From 321090e339b1e97f17687c045fb47024637ecaec Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva <144173684+b08lsoai@users.noreply.github.com> Date: Fri, 17 May 2024 19:44:57 +0300 Subject: [PATCH 003/131] feat: create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f8cd316 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Nabieva Liya, Ekaterina Tenyaeva, Anastasia Migunova + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 45213d13baf6277274dd75956181c76c5257db90 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva <144173684+b08lsoai@users.noreply.github.com> Date: Fri, 17 May 2024 20:15:59 +0300 Subject: [PATCH 004/131] refactor: corrected names in the LICENSE the sequence of the first and last names has been changed --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index f8cd316..f7610c7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Nabieva Liya, Ekaterina Tenyaeva, Anastasia Migunova +Copyright (c) 2024 Nabieva Liya, Tenyaeva Ekaterina, Migunova Anastasia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 08caa2793e2572d5dd539ab1c56890957068bbde Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Fri, 17 May 2024 21:58:37 +0300 Subject: [PATCH 005/131] feat: added vertex, edge and graph classes --- src/main/kotlin/model/graph/Edge.kt | 5 +++++ src/main/kotlin/model/graph/Graph.kt | 10 ++++++++++ src/main/kotlin/model/graph/Vertex.kt | 4 ++++ 3 files changed, 19 insertions(+) create mode 100644 src/main/kotlin/model/graph/Edge.kt create mode 100644 src/main/kotlin/model/graph/Graph.kt create mode 100644 src/main/kotlin/model/graph/Vertex.kt diff --git a/src/main/kotlin/model/graph/Edge.kt b/src/main/kotlin/model/graph/Edge.kt new file mode 100644 index 0000000..ec7db44 --- /dev/null +++ b/src/main/kotlin/model/graph/Edge.kt @@ -0,0 +1,5 @@ +package model.graph + +data class Edge(val vertices: Pair, var weight: Int?) { + fun incident(v: Int) = (v == vertices.first || v == vertices.second) +} diff --git a/src/main/kotlin/model/graph/Graph.kt b/src/main/kotlin/model/graph/Graph.kt new file mode 100644 index 0000000..b7a8993 --- /dev/null +++ b/src/main/kotlin/model/graph/Graph.kt @@ -0,0 +1,10 @@ +package model.graph + +class Graph { + var isDirected: Boolean = false + val vertices: MutableList> = mutableListOf() + val edges: MutableList = mutableListOf() + + fun addVertex(id: Int, data: D) = vertices.add(Vertex(id, data)) + fun addEdge(v: Pair, w: Int?) = edges.add(Edge(v, w)) +} diff --git a/src/main/kotlin/model/graph/Vertex.kt b/src/main/kotlin/model/graph/Vertex.kt new file mode 100644 index 0000000..aebc32a --- /dev/null +++ b/src/main/kotlin/model/graph/Vertex.kt @@ -0,0 +1,4 @@ +package model.graph + +data class Vertex(var id: Int, var data: D) { +} From 60de90584e8b67ecf59cd1ff52e240573a2c46bd Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Sun, 19 May 2024 01:34:55 +0300 Subject: [PATCH 006/131] refactor: removed vertex id --- src/main/kotlin/model/graph/Edge.kt | 4 ++-- src/main/kotlin/model/graph/Graph.kt | 9 ++++----- src/main/kotlin/model/graph/Vertex.kt | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/model/graph/Edge.kt b/src/main/kotlin/model/graph/Edge.kt index ec7db44..047e525 100644 --- a/src/main/kotlin/model/graph/Edge.kt +++ b/src/main/kotlin/model/graph/Edge.kt @@ -1,5 +1,5 @@ package model.graph -data class Edge(val vertices: Pair, var weight: Int?) { - fun incident(v: Int) = (v == vertices.first || v == vertices.second) +data class Edge(val vertices: Pair, Vertex>, var weight: Int?) { + fun incident(v: Vertex) = (v == vertices.first || v == vertices.second) } diff --git a/src/main/kotlin/model/graph/Graph.kt b/src/main/kotlin/model/graph/Graph.kt index b7a8993..6e35a5d 100644 --- a/src/main/kotlin/model/graph/Graph.kt +++ b/src/main/kotlin/model/graph/Graph.kt @@ -2,9 +2,8 @@ package model.graph class Graph { var isDirected: Boolean = false - val vertices: MutableList> = mutableListOf() - val edges: MutableList = mutableListOf() + val vertices = mutableListOf>() + val edges = mutableListOf>() - fun addVertex(id: Int, data: D) = vertices.add(Vertex(id, data)) - fun addEdge(v: Pair, w: Int?) = edges.add(Edge(v, w)) -} + fun addVertex(d: D) = vertices.add(Vertex(d)) + fun addEdge(v: Pair, w: Int?) = edges.add(Edge(Pair(Vertex(v.first), Vertex(v.second)), w)) diff --git a/src/main/kotlin/model/graph/Vertex.kt b/src/main/kotlin/model/graph/Vertex.kt index aebc32a..b6d8c8a 100644 --- a/src/main/kotlin/model/graph/Vertex.kt +++ b/src/main/kotlin/model/graph/Vertex.kt @@ -1,4 +1,4 @@ package model.graph -data class Vertex(var id: Int, var data: D) { +data class Vertex(var data: D) { } From 5c23eb948742b72eb25698eaa4ed41e0cbc56bd0 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Thu, 23 May 2024 21:43:49 +0300 Subject: [PATCH 007/131] refactor: transition to a new architecture --- LICENSE | 21 +++++++++++++++++ src/main/kotlin/model/graph/DirectedGraph.kt | 21 +++++++++++++++++ src/main/kotlin/model/graph/Edge.kt | 4 ++-- src/main/kotlin/model/graph/Graph.kt | 16 +++++++++---- .../kotlin/model/graph/UndirectedGraph.kt | 23 +++++++++++++++++++ src/main/kotlin/model/graph/Vertex.kt | 2 +- 6 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 LICENSE create mode 100644 src/main/kotlin/model/graph/DirectedGraph.kt create mode 100644 src/main/kotlin/model/graph/UndirectedGraph.kt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7d5d226 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Nabieva Liya, Tenyaeva Ekaterina, Migunova Anastasia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/main/kotlin/model/graph/DirectedGraph.kt b/src/main/kotlin/model/graph/DirectedGraph.kt new file mode 100644 index 0000000..04d9bba --- /dev/null +++ b/src/main/kotlin/model/graph/DirectedGraph.kt @@ -0,0 +1,21 @@ +package model.graph + +class DirectedGraph() : Graph() { + fun addEdge(v: Pair, w: Int?) { + if (!vertices.containsKey(v.first)) { + throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") + } + if (!vertices.containsKey(v.second)) { + throw IllegalArgumentException("Vertex with id: ${v.second} not exists in the graph.") + } + //Переделать этот кринж...!!! +// if (!(successors.containsKey(vertices[v.first]) && successors[vertices[v.first]]!!.containsKey(vertices[v.second]))) { //refactoring!!!!!!!!! +// throw IllegalArgumentException("The edge between vertices ${v.first} and ${v.second} already exists in the graph.") +// } +// if (!(successors.containsKey(vertices[v.second]) && successors[vertices[v.second]]!!.containsKey(vertices[v.first]) && w != successors[vertices[v.second]]!![vertices[v.first]])) { //refactoring!!!!!!!!! +// throw IllegalArgumentException("The weight between two vertices in both directions should be the same.") +// } + edges.add(Edge(Pair(v.first, v.second), w)) + adjacency.getOrPut(v.first) { hashMapOf() }[v.second] = w + } +} \ No newline at end of file diff --git a/src/main/kotlin/model/graph/Edge.kt b/src/main/kotlin/model/graph/Edge.kt index 047e525..bdf517b 100644 --- a/src/main/kotlin/model/graph/Edge.kt +++ b/src/main/kotlin/model/graph/Edge.kt @@ -1,5 +1,5 @@ package model.graph -data class Edge(val vertices: Pair, Vertex>, var weight: Int?) { - fun incident(v: Vertex) = (v == vertices.first || v == vertices.second) +data class Edge(val vertices: Pair, var weight: Int?) { + fun incident(v: Int) = (v == vertices.first || v == vertices.second) } diff --git a/src/main/kotlin/model/graph/Graph.kt b/src/main/kotlin/model/graph/Graph.kt index 6e35a5d..3ce36a1 100644 --- a/src/main/kotlin/model/graph/Graph.kt +++ b/src/main/kotlin/model/graph/Graph.kt @@ -1,9 +1,15 @@ package model.graph -class Graph { - var isDirected: Boolean = false - val vertices = mutableListOf>() +abstract class Graph { + val adjacency = hashMapOf>() // + val vertices = hashMapOf>() val edges = mutableListOf>() + fun getVertices(): Collection> = vertices.values + fun addVertex(id: Int, data: D) { + if (vertices.containsKey(id)) { + throw IllegalArgumentException("Vertex with id: $id already exists in the graph.") + } + vertices[id] = Vertex(id, data) + } +} - fun addVertex(d: D) = vertices.add(Vertex(d)) - fun addEdge(v: Pair, w: Int?) = edges.add(Edge(Pair(Vertex(v.first), Vertex(v.second)), w)) diff --git a/src/main/kotlin/model/graph/UndirectedGraph.kt b/src/main/kotlin/model/graph/UndirectedGraph.kt new file mode 100644 index 0000000..d16d3eb --- /dev/null +++ b/src/main/kotlin/model/graph/UndirectedGraph.kt @@ -0,0 +1,23 @@ +package model.graph + +class UndirectedGraph() : Graph() { + fun addEdge(v: Pair, w: Int?) { + if (!vertices.containsKey(v.first)) { + throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") + } + if (!vertices.containsKey(v.second)) { + throw IllegalArgumentException("Vertex with id: ${v.second} not exists in the graph.") + } + if (v.first == v.second) { + throw IllegalArgumentException("Can't add edge from vertex to itself.") + } + // Переделать этот кринж...!! +// if (!(adjacency.containsKey(vertices[v.first]) && adjacency[vertices[v.first]]!!.containsKey(vertices[v.second]))) { +// throw IllegalArgumentException("The edge between vertices ${v.first} and ${v.second} already exists in the graph.") +// } + edges.add(Edge(Pair(v.first, v.second), w)) + edges.add(Edge(Pair(v.second, v.first), w)) + adjacency.getOrPut(v.first) { hashMapOf() }[v.second] = w + adjacency.getOrPut(v.second) { hashMapOf() }[v.first] = w + } +} \ No newline at end of file diff --git a/src/main/kotlin/model/graph/Vertex.kt b/src/main/kotlin/model/graph/Vertex.kt index b6d8c8a..aebc32a 100644 --- a/src/main/kotlin/model/graph/Vertex.kt +++ b/src/main/kotlin/model/graph/Vertex.kt @@ -1,4 +1,4 @@ package model.graph -data class Vertex(var data: D) { +data class Vertex(var id: Int, var data: D) { } From 742f721f2b11a57aca479f798e4290eb45a280f3 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Thu, 23 May 2024 23:18:17 +0300 Subject: [PATCH 008/131] feat: added Dijkstra algorithm --- src/main/kotlin/model/algorithms/Dijkstra.kt | 46 ++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/kotlin/model/algorithms/Dijkstra.kt diff --git a/src/main/kotlin/model/algorithms/Dijkstra.kt b/src/main/kotlin/model/algorithms/Dijkstra.kt new file mode 100644 index 0000000..b062e44 --- /dev/null +++ b/src/main/kotlin/model/algorithms/Dijkstra.kt @@ -0,0 +1,46 @@ +package model.algorithms + +import model.graph.* +import java.util.PriorityQueue + +class Dijkstra(private val graph: Graph) { + fun findShortestPaths(start: Int, end: Int): List { + if (!graph.vertices.contains(start) || !graph.vertices.contains(end)) { + throw NoSuchElementException("The vertex doesn't exist in the graph.") + } + val distances = hashMapOf() + val previousVertices = hashMapOf() + val queue = PriorityQueue>(compareBy { it.second }) + distances[start] = 0 + queue.add(Pair(start, 0)) + + while (queue.isNotEmpty()) { + val (currentVertex, currentDistance) = queue.poll() + if (currentVertex == end) break + graph.adjacency[currentVertex]?.forEach { (neighbor, weight) -> + if (weight == null) { + throw IllegalArgumentException("Edge without weights in Dijkstra's algorithm.") + } else if (weight < 0) { + throw IllegalArgumentException("Edge with negative weights in Dijkstra's algorithm.") + } else { + val newDistance = currentDistance + weight + if (newDistance < (distances[neighbor] ?: Int.MAX_VALUE)) { + distances[neighbor] = newDistance + previousVertices[neighbor] = currentVertex + queue.add(Pair(neighbor, newDistance)) + } + } + } + } + val path = mutableListOf() + previousVertices[end] ?: return path + var current = end + while (current != start) { + path.add(current) + current = previousVertices[current] ?: break + } + path.add(start) + path.reverse() + return path + } +} \ No newline at end of file From 43fb4524d4314b676c95893452b347c31642f7a6 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Sat, 25 May 2024 02:18:18 +0300 Subject: [PATCH 009/131] refactor: extracted variables to class scope --- src/main/kotlin/model/algorithms/Dijkstra.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/model/algorithms/Dijkstra.kt b/src/main/kotlin/model/algorithms/Dijkstra.kt index b062e44..6fd9ec0 100644 --- a/src/main/kotlin/model/algorithms/Dijkstra.kt +++ b/src/main/kotlin/model/algorithms/Dijkstra.kt @@ -4,13 +4,13 @@ import model.graph.* import java.util.PriorityQueue class Dijkstra(private val graph: Graph) { + private val distances = hashMapOf() + private val previousVertices = hashMapOf() + private val queue = PriorityQueue>(compareBy { it.second }) fun findShortestPaths(start: Int, end: Int): List { if (!graph.vertices.contains(start) || !graph.vertices.contains(end)) { throw NoSuchElementException("The vertex doesn't exist in the graph.") } - val distances = hashMapOf() - val previousVertices = hashMapOf() - val queue = PriorityQueue>(compareBy { it.second }) distances[start] = 0 queue.add(Pair(start, 0)) @@ -43,4 +43,4 @@ class Dijkstra(private val graph: Graph) { path.reverse() return path } -} \ No newline at end of file +} From 279b0c674b67e52039c95371a64283318d94cacd Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 28 May 2024 15:23:54 +0300 Subject: [PATCH 010/131] docs: added documentation to Dijkstra's algorithm --- src/main/kotlin/model/algorithms/Dijkstra.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/kotlin/model/algorithms/Dijkstra.kt b/src/main/kotlin/model/algorithms/Dijkstra.kt index 6fd9ec0..fc37b22 100644 --- a/src/main/kotlin/model/algorithms/Dijkstra.kt +++ b/src/main/kotlin/model/algorithms/Dijkstra.kt @@ -3,10 +3,26 @@ package model.algorithms import model.graph.* import java.util.PriorityQueue +/** + * The Dijkstra class is used to find the shortest paths in a weighted graph using Dijkstra's algorithm. + * + * @property graph A directed graph in which shortest paths are searched. + * @property distances Stores the lengths of the shortest paths from the starting vertex to other vertices. + * @property previousVertices Stores previous vertices along the shortest path to each vertex. + * @property queue Queue for processing vertices in order of increasing distances. + */ class Dijkstra(private val graph: Graph) { private val distances = hashMapOf() private val previousVertices = hashMapOf() private val queue = PriorityQueue>(compareBy { it.second }) + + /** + * Finds the shortest path from one vertex (start) to another vertex (end) and returns a list of id vertices of the path. + * + * @param start The starting vertex from which to start searching for the shortest path. + * @param end The end vertex to which the shortest path is sought. + * @return A list of vertices that form the shortest path from vertex start to vertex end. + */ fun findShortestPaths(start: Int, end: Int): List { if (!graph.vertices.contains(start) || !graph.vertices.contains(end)) { throw NoSuchElementException("The vertex doesn't exist in the graph.") @@ -32,6 +48,7 @@ class Dijkstra(private val graph: Graph) { } } } + // construct final path using previous vertices val path = mutableListOf() previousVertices[end] ?: return path var current = end From 4ea491b8116d582c307f4abe9ee98ccd526ac4d2 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 28 May 2024 15:31:47 +0300 Subject: [PATCH 011/131] refactor: corrected import in Dijkstra's algorithm replaced the import of the entire folder with the import of one class --- src/main/kotlin/model/algorithms/Dijkstra.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/model/algorithms/Dijkstra.kt b/src/main/kotlin/model/algorithms/Dijkstra.kt index fc37b22..37a643f 100644 --- a/src/main/kotlin/model/algorithms/Dijkstra.kt +++ b/src/main/kotlin/model/algorithms/Dijkstra.kt @@ -1,6 +1,6 @@ package model.algorithms -import model.graph.* +import model.graph.Graph import java.util.PriorityQueue /** From e01009b80d71c4d5021c84ee90b6de8515f33742 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 28 May 2024 15:52:45 +0300 Subject: [PATCH 012/131] feat: added Kosaraju's algorithm --- src/main/kotlin/model/algorithms/Kosaraju.kt | 49 ++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/kotlin/model/algorithms/Kosaraju.kt diff --git a/src/main/kotlin/model/algorithms/Kosaraju.kt b/src/main/kotlin/model/algorithms/Kosaraju.kt new file mode 100644 index 0000000..1bd5b3a --- /dev/null +++ b/src/main/kotlin/model/algorithms/Kosaraju.kt @@ -0,0 +1,49 @@ +package model.algorithms + +import model.graph.DirectedGraph + +class Kosaraju(private val graph: DirectedGraph) { + private val visited = hashMapOf() + private val stack = mutableListOf() + private val stronglyConnectedComponents = mutableListOf>() + + fun findStronglyConnectedComponents(): List> { + for (vertexID in graph.vertices.keys) { + if (visited[vertexID] != true) { + dfs(vertexID, stack, graph) + } + } + val transposedGraph = transposeGraph() + visited.replaceAll { _, _ -> false } + while (stack.isNotEmpty()) { + val vertexID = stack.removeAt(stack.size - 1) + if (visited[vertexID] != true) { + val component = mutableListOf() + dfs(vertexID, component, transposedGraph) + stronglyConnectedComponents.add(component) + } + } + return stronglyConnectedComponents + } + + private fun dfs(vertexID: Int, stack: MutableList, graph: DirectedGraph) { + visited[vertexID] = true + for (nextVertexID in (graph.adjacency[vertexID]?.keys ?: emptyList())) + if (visited[nextVertexID] != true) { + dfs(nextVertexID, stack, graph) + } + stack.add(vertexID) + } + + private fun transposeGraph(): DirectedGraph { + val transposedGraph = DirectedGraph() + for ((id, vertex) in graph.vertices) { + transposedGraph.addVertex(id, vertex.data) + } + for (edge in graph.edges) { + val (firstVertexID, secondVertexID) = edge.vertices + transposedGraph.addEdge(Pair(secondVertexID, firstVertexID), edge.weight) + } + return transposedGraph + } +} From 862bce4736427567b61618fb2bf746004ce65560 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 28 May 2024 15:55:25 +0300 Subject: [PATCH 013/131] docs: added documentation to Kosaraju's algorithm --- src/main/kotlin/model/algorithms/Kosaraju.kt | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/kotlin/model/algorithms/Kosaraju.kt b/src/main/kotlin/model/algorithms/Kosaraju.kt index 1bd5b3a..e97475e 100644 --- a/src/main/kotlin/model/algorithms/Kosaraju.kt +++ b/src/main/kotlin/model/algorithms/Kosaraju.kt @@ -2,11 +2,23 @@ package model.algorithms import model.graph.DirectedGraph +/** + * The Kosaraju class implements the Kosaraju algorithm for finding strongly connected + * components (scc) in a directed graph. + * @property graph is a directed graph in which we are looking for scc + * @property visited map storing information about visited vertices + * @property stack a stack for storing vertices in the order in which they were traversed + * @property stronglyConnectedComponents list of strongly connected components in graph + */ class Kosaraju(private val graph: DirectedGraph) { private val visited = hashMapOf() private val stack = mutableListOf() private val stronglyConnectedComponents = mutableListOf>() + /** + * The findStronglyConnectedComponents method runs an algorithm to find a scc. + * @return list of graph components + */ fun findStronglyConnectedComponents(): List> { for (vertexID in graph.vertices.keys) { if (visited[vertexID] != true) { @@ -26,6 +38,12 @@ class Kosaraju(private val graph: DirectedGraph) { return stronglyConnectedComponents } + /** + * The dfs method performs a depth-first traversal of the graph, starting from a given vertex. + * @param vertexID identifier of the vertex from which the traversal begins + * @param stack for storing vertices in traversed order + * @param graph graph in which the traversal occurs + */ private fun dfs(vertexID: Int, stack: MutableList, graph: DirectedGraph) { visited[vertexID] = true for (nextVertexID in (graph.adjacency[vertexID]?.keys ?: emptyList())) @@ -35,6 +53,10 @@ class Kosaraju(private val graph: DirectedGraph) { stack.add(vertexID) } + /** + * The transposeGraph method transposes the graph. + * @return the transposed graph + */ private fun transposeGraph(): DirectedGraph { val transposedGraph = DirectedGraph() for ((id, vertex) in graph.vertices) { From e6d7f541f224ffb5c47496a933e602f4dd4a5225 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 29 May 2024 18:41:49 +0300 Subject: [PATCH 014/131] fix: fixed 'adjacency' logic now vertices without outgoing edges are also included in the adjacency list --- src/main/kotlin/model/graph/DirectedGraph.kt | 10 ++-------- src/main/kotlin/model/graph/Edge.kt | 1 - src/main/kotlin/model/graph/Graph.kt | 5 ++++- src/main/kotlin/model/graph/UndirectedGraph.kt | 7 ++----- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/model/graph/DirectedGraph.kt b/src/main/kotlin/model/graph/DirectedGraph.kt index 04d9bba..95cf1b6 100644 --- a/src/main/kotlin/model/graph/DirectedGraph.kt +++ b/src/main/kotlin/model/graph/DirectedGraph.kt @@ -1,20 +1,14 @@ package model.graph class DirectedGraph() : Graph() { - fun addEdge(v: Pair, w: Int?) { + override fun addEdge(v: Pair, w: Int?) { + // добавить проверку на повторку ребра if (!vertices.containsKey(v.first)) { throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") } if (!vertices.containsKey(v.second)) { throw IllegalArgumentException("Vertex with id: ${v.second} not exists in the graph.") } - //Переделать этот кринж...!!! -// if (!(successors.containsKey(vertices[v.first]) && successors[vertices[v.first]]!!.containsKey(vertices[v.second]))) { //refactoring!!!!!!!!! -// throw IllegalArgumentException("The edge between vertices ${v.first} and ${v.second} already exists in the graph.") -// } -// if (!(successors.containsKey(vertices[v.second]) && successors[vertices[v.second]]!!.containsKey(vertices[v.first]) && w != successors[vertices[v.second]]!![vertices[v.first]])) { //refactoring!!!!!!!!! -// throw IllegalArgumentException("The weight between two vertices in both directions should be the same.") -// } edges.add(Edge(Pair(v.first, v.second), w)) adjacency.getOrPut(v.first) { hashMapOf() }[v.second] = w } diff --git a/src/main/kotlin/model/graph/Edge.kt b/src/main/kotlin/model/graph/Edge.kt index bdf517b..3221b30 100644 --- a/src/main/kotlin/model/graph/Edge.kt +++ b/src/main/kotlin/model/graph/Edge.kt @@ -1,5 +1,4 @@ package model.graph data class Edge(val vertices: Pair, var weight: Int?) { - fun incident(v: Int) = (v == vertices.first || v == vertices.second) } diff --git a/src/main/kotlin/model/graph/Graph.kt b/src/main/kotlin/model/graph/Graph.kt index 3ce36a1..39db4e0 100644 --- a/src/main/kotlin/model/graph/Graph.kt +++ b/src/main/kotlin/model/graph/Graph.kt @@ -1,15 +1,18 @@ package model.graph +// TODO: (*) добавить удаление вершин abstract class Graph { - val adjacency = hashMapOf>() // + val adjacency = hashMapOf>() val vertices = hashMapOf>() val edges = mutableListOf>() fun getVertices(): Collection> = vertices.values + abstract fun addEdge(v: Pair, w: Int?) fun addVertex(id: Int, data: D) { if (vertices.containsKey(id)) { throw IllegalArgumentException("Vertex with id: $id already exists in the graph.") } vertices[id] = Vertex(id, data) + adjacency[id] = hashMapOf() } } diff --git a/src/main/kotlin/model/graph/UndirectedGraph.kt b/src/main/kotlin/model/graph/UndirectedGraph.kt index d16d3eb..221c7f7 100644 --- a/src/main/kotlin/model/graph/UndirectedGraph.kt +++ b/src/main/kotlin/model/graph/UndirectedGraph.kt @@ -1,7 +1,8 @@ package model.graph class UndirectedGraph() : Graph() { - fun addEdge(v: Pair, w: Int?) { + override fun addEdge(v: Pair, w: Int?) { + // добавить проверку на повторку ребра if (!vertices.containsKey(v.first)) { throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") } @@ -11,10 +12,6 @@ class UndirectedGraph() : Graph() { if (v.first == v.second) { throw IllegalArgumentException("Can't add edge from vertex to itself.") } - // Переделать этот кринж...!! -// if (!(adjacency.containsKey(vertices[v.first]) && adjacency[vertices[v.first]]!!.containsKey(vertices[v.second]))) { -// throw IllegalArgumentException("The edge between vertices ${v.first} and ${v.second} already exists in the graph.") -// } edges.add(Edge(Pair(v.first, v.second), w)) edges.add(Edge(Pair(v.second, v.first), w)) adjacency.getOrPut(v.first) { hashMapOf() }[v.second] = w From e3e0b778abb50bb20ab3e23575194cddfa6f80a2 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 29 May 2024 18:49:32 +0300 Subject: [PATCH 015/131] chore: add testing dependencies --- build.gradle.kts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9a0036f..595bcb1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,15 @@ dependencies { // (in a separate module for demo project and in testMain). // With compose.desktop.common you will also lose @Preview functionality implementation(compose.desktop.currentOs) + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("io.kotest:kotest-runner-junit5-jvm:4.6.0") +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(21) } compose.desktop { @@ -32,4 +41,4 @@ compose.desktop { packageVersion = "1.0.0" } } -} \ No newline at end of file +} From 6feda7abcc3e8f67013b96942d9e86ad11dbfe2d Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 29 May 2024 19:13:48 +0300 Subject: [PATCH 016/131] test: added tests for Dijkstra's algorithm --- src/test/kotlin/DijkstraTest.kt | 158 ++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/test/kotlin/DijkstraTest.kt diff --git a/src/test/kotlin/DijkstraTest.kt b/src/test/kotlin/DijkstraTest.kt new file mode 100644 index 0000000..a9d901e --- /dev/null +++ b/src/test/kotlin/DijkstraTest.kt @@ -0,0 +1,158 @@ +import model.algorithms.Dijkstra +import model.graph.UndirectedGraph +import model.graph.DirectedGraph +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.assertEquals +import kotlin.test.assertFailsWith + +class DijkstraTest { + private val dirGraph = DirectedGraph() + private val undirGraph = UndirectedGraph() + private val shortestPathDirected = Dijkstra(dirGraph) + private val shortestPathUndirected = Dijkstra(undirGraph) + + //region Exceptions + @Test + fun `having a negative edge weight in a path should throw an exception`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addEdge(Pair(1, 3), 5) + dirGraph.addEdge(Pair(3, 2), -2) + assertFailsWith { shortestPathDirected.findShortestPaths(1, 2) } + } + + @Test + fun `having a null edge weight in a path should throw an exception`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addEdge(Pair(1, 3), 5) + dirGraph.addEdge(Pair(3, 2), null) + assertFailsWith { shortestPathDirected.findShortestPaths(1, 2) } + } + + @Test + fun `searching for a path from vertices that are not in the graph should throw an exception`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addEdge(Pair(1, 3), 5) + dirGraph.addEdge(Pair(3, 2), 2) + assertFailsWith { shortestPathDirected.findShortestPaths(4, 5) } + } + + //endregion + //region DirectedGraph + @Test + fun `searching for a non-existent path in a directed graph should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + dirGraph.addEdge(Pair(1, 2), 50) + dirGraph.addEdge(Pair(2, 3), 5) + dirGraph.addEdge(Pair(3, 1), 100) + dirGraph.addEdge(Pair(4, 3), 15) + assertEquals(listOf(), shortestPathDirected.findShortestPaths(1, 4)) + } + + @Test + fun `searching for an existing path in a directed graph without bidirectional edges should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + dirGraph.addVertex(5, "E") + dirGraph.addVertex(6, "F") + dirGraph.addVertex(7, "G") + dirGraph.addVertex(8, "H") + dirGraph.addVertex(9, "I") + dirGraph.addEdge(Pair(1, 2), 5) + dirGraph.addEdge(Pair(1, 3), 10) + dirGraph.addEdge(Pair(1, 4), 15) + dirGraph.addEdge(Pair(2, 5), 20) + dirGraph.addEdge(Pair(3, 5), 50) + dirGraph.addEdge(Pair(4, 6), 200) + dirGraph.addEdge(Pair(6, 5), 120) + dirGraph.addEdge(Pair(6, 8), 1) + dirGraph.addEdge(Pair(5, 7), 7) + dirGraph.addEdge(Pair(7, 8), 54) + dirGraph.addEdge(Pair(8, 9), 21) + assertEquals(listOf(1, 2, 5, 7, 8, 9), shortestPathDirected.findShortestPaths(1, 9)) + } + + @Test + fun `searching for an existing path in a directed graph with bidirectional edges should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + dirGraph.addVertex(5, "E") + dirGraph.addVertex(6, "F") + dirGraph.addVertex(7, "G") + dirGraph.addVertex(8, "H") + dirGraph.addVertex(9, "I") + dirGraph.addEdge(Pair(1, 2), 5) + dirGraph.addEdge(Pair(1, 3), 10) + dirGraph.addEdge(Pair(1, 4), 15) + dirGraph.addEdge(Pair(2, 5), 20) + dirGraph.addEdge(Pair(3, 5), 50) + dirGraph.addEdge(Pair(4, 6), 200) + dirGraph.addEdge(Pair(6, 5), 120) + dirGraph.addEdge(Pair(6, 8), 1) + dirGraph.addEdge(Pair(5, 7), 7) + dirGraph.addEdge(Pair(7, 8), 54) + dirGraph.addEdge(Pair(8, 9), 21) + dirGraph.addEdge(Pair(4, 1), 15) + dirGraph.addEdge(Pair(5, 6), 120) + dirGraph.addEdge(Pair(7, 5), 7) + assertEquals(listOf(1, 2, 5, 7, 8, 9), shortestPathDirected.findShortestPaths(1, 9)) + } + + //endregion + //region UnDirectedGraph + @Test + fun `searching for a non-existent path in a undirected graph should be correct`() { + undirGraph.addVertex(1, "A") + undirGraph.addVertex(2, "B") + undirGraph.addVertex(3, "C") + undirGraph.addVertex(4, "D") + undirGraph.addVertex(5, "E") + undirGraph.addVertex(6, "F") + undirGraph.addEdge(Pair(1, 3), 5) + undirGraph.addEdge(Pair(3, 2), 10) + undirGraph.addEdge(Pair(1, 2), 15) + undirGraph.addEdge(Pair(6, 5), 20) + undirGraph.addEdge(Pair(5, 4), 50) + assertEquals(listOf(), shortestPathUndirected.findShortestPaths(2, 5)) + } + + @Test + fun `searching for an existing path in the undirected graph should be correct`() { + undirGraph.addVertex(1, "A") + undirGraph.addVertex(2, "B") + undirGraph.addVertex(3, "C") + undirGraph.addVertex(4, "D") + undirGraph.addVertex(5, "E") + undirGraph.addVertex(6, "F") + undirGraph.addVertex(7, "G") + undirGraph.addVertex(8, "H") + undirGraph.addVertex(9, "I") + undirGraph.addVertex(10, "J") + undirGraph.addVertex(11, "K") + undirGraph.addEdge(Pair(1, 2), 50) + undirGraph.addEdge(Pair(1, 3), 5) + undirGraph.addEdge(Pair(3, 4), 100) + undirGraph.addEdge(Pair(3, 5), 15) + undirGraph.addEdge(Pair(5, 6), 5) + undirGraph.addEdge(Pair(4, 6), 20) + undirGraph.addEdge(Pair(4, 7), 20) + undirGraph.addEdge(Pair(4, 8), 50) + undirGraph.addEdge(Pair(8, 9), 34) + undirGraph.addEdge(Pair(9, 10), 80) + undirGraph.addEdge(Pair(2, 11), 5) + assertEquals(listOf(1, 3, 5, 6, 4, 8, 9), shortestPathUndirected.findShortestPaths(1, 9)) + } + //endregion +} From 546be300528d1dac5bcd8948ef0660cfb3dbc28a Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 29 May 2024 19:42:56 +0300 Subject: [PATCH 017/131] test: added tests for Kosaraju's algorithm --- src/test/kotlin/KosarajuTest.kt | 108 ++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/test/kotlin/KosarajuTest.kt diff --git a/src/test/kotlin/KosarajuTest.kt b/src/test/kotlin/KosarajuTest.kt new file mode 100644 index 0000000..3d9d04a --- /dev/null +++ b/src/test/kotlin/KosarajuTest.kt @@ -0,0 +1,108 @@ +import model.algorithms.Kosaraju +import model.graph.DirectedGraph +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.assertEquals + +class KosarajuTest { + private val dirGraph = DirectedGraph() + private val scc = Kosaraju(dirGraph) + + @Test + fun `searching for scc in a graph without vertices should be correct`() { + assertEquals(listOf>(), scc.findStronglyConnectedComponents()) + } + + @Test + fun `searching for scc in a graph without edges should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + assertEquals(listOf(listOf(3), listOf(2), listOf(1)), scc.findStronglyConnectedComponents()) + } + + @Test + fun `searching for scc in a graph with bidirectional edges should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + dirGraph.addVertex(5, "E") + dirGraph.addVertex(6, "F") + dirGraph.addVertex(7, "G") + dirGraph.addVertex(8, "H") + dirGraph.addVertex(9, "I") + dirGraph.addEdge(Pair(1, 2), 1) + dirGraph.addEdge(Pair(2, 3), 45) + dirGraph.addEdge(Pair(3, 1), 3) + dirGraph.addEdge(Pair(4, 5), 12) + dirGraph.addEdge(Pair(5, 4), -23) + dirGraph.addEdge(Pair(4, 2), 4) + dirGraph.addEdge(Pair(5, 6), 34) + dirGraph.addEdge(Pair(6, 8), -12) + dirGraph.addEdge(Pair(8, 9), null) + dirGraph.addEdge(Pair(9, 7), 0) + dirGraph.addEdge(Pair(7, 6), null) + assertEquals(listOf(listOf(5, 4), listOf(8, 9, 7, 6), listOf(2, 3, 1)), scc.findStronglyConnectedComponents()) + } + + @Test + fun `searching for scc in a graph without bidirectional edges should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + dirGraph.addVertex(5, "E") + dirGraph.addVertex(6, "F") + dirGraph.addVertex(7, "G") + dirGraph.addEdge(Pair(1, 2), null) + dirGraph.addEdge(Pair(2, 3), 34) + dirGraph.addEdge(Pair(3, 4), 2) + dirGraph.addEdge(Pair(4, 1), 1) + dirGraph.addEdge(Pair(5, 6), 11) + dirGraph.addEdge(Pair(6, 7), -2) + dirGraph.addEdge(Pair(7, 2), 3) + assertEquals(listOf(listOf(5), listOf(6), listOf(7), listOf(2, 3, 4, 1)), scc.findStronglyConnectedComponents()) + } + + @Test + fun `searching for scc in a disconnected graph should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + dirGraph.addVertex(5, "E") + dirGraph.addEdge(Pair(1, 2), 1) + dirGraph.addEdge(Pair(2, 3), -1) + dirGraph.addEdge(Pair(3, 1), 20) + dirGraph.addEdge(Pair(4, 5), 34) + assertEquals(listOf(listOf(4), listOf(5), listOf(2, 3, 1)), scc.findStronglyConnectedComponents()) + } + + @Test + fun `searching for scc in a full graph should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addEdge(Pair(1, 2), 1) + dirGraph.addEdge(Pair(1, 3), -1) + dirGraph.addEdge(Pair(2, 1), 1) + dirGraph.addEdge(Pair(2, 3), 9) + dirGraph.addEdge(Pair(3, 1), -1) + dirGraph.addEdge(Pair(3, 2), 9) + assertEquals(listOf(listOf(3, 2, 1)), scc.findStronglyConnectedComponents()) + } + + @Test + fun `searching for scc in a graph without cycles should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + dirGraph.addVertex(5, "E") + dirGraph.addEdge(Pair(1, 2), 1) + dirGraph.addEdge(Pair(1, 3), -1) + dirGraph.addEdge(Pair(2, 4), 1) + dirGraph.addEdge(Pair(4, 5), 9) + assertEquals(listOf(listOf(1), listOf(3), listOf(2), listOf(4), listOf(5)), scc.findStronglyConnectedComponents()) + } +} From 37c95598da2a4e209cdc835239fffe0ee88b451d Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 29 May 2024 20:35:36 +0300 Subject: [PATCH 018/131] feat: added Louvain's algorithm --- src/main/kotlin/model/algorithms/Louvain.kt | 78 +++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/main/kotlin/model/algorithms/Louvain.kt diff --git a/src/main/kotlin/model/algorithms/Louvain.kt b/src/main/kotlin/model/algorithms/Louvain.kt new file mode 100644 index 0000000..f596d1d --- /dev/null +++ b/src/main/kotlin/model/algorithms/Louvain.kt @@ -0,0 +1,78 @@ +package model.algorithms + +import model.graph.* + +/** + * Louvain algorithm for detecting communities in a graph. + * @param graph The graph in which communities should be discovered. + */ +class Louvain(private val graph: Graph) { + + /** + * Method for detecting communities in a graph using the Louvain algorithm. + * @return A list of vertex sets representing the detected communities. + */ + fun detectCommunities(): List> { + // each vertex starts in its own classer + val communities = graph.vertices.keys.associateWith { it }.toMutableMap() + var bestCommunities = communities.toMap() + var bestModularity = calculateModularity(bestCommunities) + var changed: Boolean + while (true) { // until modularity stops increasing + changed = false + // all vertices of the network are enumerated, and each vertex tries to move to the classer + // with a maximum increase in modularity with such movement + for (vertex in graph.vertices.keys) { + for (community in communities.keys) { + if (vertex != community && communities[vertex] != community) { + val newCommunities = bestCommunities.toMutableMap() + newCommunities[vertex] = communities[community]!! + val newModularity = calculateModularity(newCommunities) + + if (newModularity > bestModularity) { + bestModularity = newModularity + bestCommunities = newCommunities + communities[vertex] = communities[community]!! + changed = true + break + } + } + } + if (changed) break + } + if (!changed) break + } + val resultCommunities = mutableListOf>() + for (resultCommunity in bestCommunities.values.toSet()) { + val verticesOfCommunity = mutableSetOf() + for ((vertex, community) in bestCommunities) { + if (community == resultCommunity) + verticesOfCommunity.add(vertex) + } + resultCommunities.add(verticesOfCommunity) + } + return resultCommunities + } + + /** + * Method for calculating community modularity. + * @param communities Communities for which modularity needs to be calculated. + * @return The meaning of modularity for the community. + */ + private fun calculateModularity(communities: Map): Double { + val edgesNum = graph.edges.size.toDouble() + var modularity = 0.0 + + for ((vertex, neighbors) in graph.adjacency) { + for (neighbor in neighbors.keys) { + val delta = if (communities[vertex] == communities[neighbor]) 1.0 else 0.0 + + // calculate modularity taking into account the direction of the edges + val outDegree = graph.adjacency[vertex]!!.size // outgoing vertex degree + val inDegree = neighbors.size // incoming vertex degree + modularity += delta - (outDegree * inDegree) / edgesNum + } + } + return modularity / edgesNum + } +} From 758502cc637afaa8d91a12b945990d81ec117bb3 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 29 May 2024 20:41:44 +0300 Subject: [PATCH 019/131] test: added tests for Louvain's algorithm --- src/test/kotlin/LouvainTest.kt | 163 +++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 src/test/kotlin/LouvainTest.kt diff --git a/src/test/kotlin/LouvainTest.kt b/src/test/kotlin/LouvainTest.kt new file mode 100644 index 0000000..a2eccce --- /dev/null +++ b/src/test/kotlin/LouvainTest.kt @@ -0,0 +1,163 @@ +import model.algorithms.Louvain +import model.graph.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.assertEquals + +class LouvainTest { + private val dirGraph = DirectedGraph() + private val communityDir = Louvain(dirGraph) + private val undirGraph = UndirectedGraph() + private val communityUndir = Louvain(undirGraph) + + //region Special cases + @Test + fun `detected communities in a graph without vertices should be correct`() { + assertEquals(listOf>(), communityUndir.detectCommunities()) + } + + @Test + fun `detected communities in a graph without edges should be correct`() { + undirGraph.addVertex(1, "A") + undirGraph.addVertex(2, "B") + undirGraph.addVertex(3, "C") + assertEquals(listOf(setOf(1), setOf(2), setOf(3)), communityUndir.detectCommunities()) + } + + //endregion + //region DirectedGraph + @Test + fun `detected communities in a directed graph of cycle should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + dirGraph.addVertex(5, "E") + dirGraph.addVertex(6, "F") + dirGraph.addEdge(Pair(1, 2), 4) + dirGraph.addEdge(Pair(2, 3), 13) + dirGraph.addEdge(Pair(3, 4), 1) + dirGraph.addEdge(Pair(4, 5), null) + dirGraph.addEdge(Pair(5, 6), null) + dirGraph.addEdge(Pair(6, 1), 0) + assertEquals(listOf(setOf(1, 2, 3, 4, 5, 6)), communityDir.detectCommunities()) + } + + @Test + fun `detected communities in a directed graph of cycle with bidirectional edges should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + dirGraph.addVertex(5, "E") + dirGraph.addVertex(6, "F") + dirGraph.addEdge(Pair(1, 2), 4) + dirGraph.addEdge(Pair(2, 3), 13) + dirGraph.addEdge(Pair(3, 4), 1) + dirGraph.addEdge(Pair(4, 5), null) + dirGraph.addEdge(Pair(5, 6), null) + dirGraph.addEdge(Pair(6, 5), null) + dirGraph.addEdge(Pair(6, 1), 0) + assertEquals(listOf(setOf(1, 2, 3, 4), setOf(5, 6)), communityDir.detectCommunities()) + } + + @Test + fun `detected communities in a directed graph of cycles connected by an edge should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + dirGraph.addVertex(5, "E") + dirGraph.addVertex(6, "F") + dirGraph.addEdge(Pair(1, 2), null) + dirGraph.addEdge(Pair(2, 3), 1) + dirGraph.addEdge(Pair(1, 3), 4) + dirGraph.addEdge(Pair(3, 4), 3) + dirGraph.addEdge(Pair(4, 5), 1) + dirGraph.addEdge(Pair(5, 6), 0) + dirGraph.addEdge(Pair(4, 6), 12) + assertEquals(listOf(setOf(1, 2, 3), setOf(4, 5, 6)), communityDir.detectCommunities()) + } + + @Test + fun `detected communities in a disconnected directed graph should be correct`() { + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + dirGraph.addVertex(5, "E") + dirGraph.addVertex(6, "F") + dirGraph.addVertex(7, "G") + dirGraph.addVertex(8, "H") + dirGraph.addVertex(9, "I") + dirGraph.addEdge(Pair(1, 2), 4) + dirGraph.addEdge(Pair(2, 1), 4) + dirGraph.addEdge(Pair(2, 3), 12) + dirGraph.addEdge(Pair(3, 4), 3) + dirGraph.addEdge(Pair(4, 3), 56) + dirGraph.addEdge(Pair(5, 6), null) + dirGraph.addEdge(Pair(7, 8), 0) + dirGraph.addEdge(Pair(8, 9), -19) + dirGraph.addEdge(Pair(9, 7), 2) + assertEquals(listOf(setOf(1, 2), setOf(3, 4), setOf(5, 6), setOf(7, 8, 9)), communityDir.detectCommunities()) + } + + //endregion + //region UndirectedGraph + @Test + fun `detected communities in a graph of cycles connected by an edge should be correct`() { + undirGraph.addVertex(1, "A") + undirGraph.addVertex(2, "B") + undirGraph.addVertex(3, "C") + undirGraph.addVertex(4, "D") + undirGraph.addVertex(5, "E") + undirGraph.addVertex(6, "F") + undirGraph.addEdge(Pair(1, 2), null) + undirGraph.addEdge(Pair(2, 3), 1) + undirGraph.addEdge(Pair(1, 3), 4) + undirGraph.addEdge(Pair(3, 4), 3) + undirGraph.addEdge(Pair(4, 5), 1) + undirGraph.addEdge(Pair(5, 6), 0) + undirGraph.addEdge(Pair(4, 6), 12) + assertEquals(listOf(setOf(1, 2, 3), setOf(4, 5, 6)), communityUndir.detectCommunities()) + } + + @Test + fun `detected communities in a graph of cycle should be correct`() { + undirGraph.addVertex(1, "A") + undirGraph.addVertex(2, "B") + undirGraph.addVertex(3, "C") + undirGraph.addVertex(4, "D") + undirGraph.addVertex(5, "E") + undirGraph.addVertex(6, "F") + undirGraph.addEdge(Pair(1, 2), 4) + undirGraph.addEdge(Pair(2, 3), 13) + undirGraph.addEdge(Pair(3, 4), 1) + undirGraph.addEdge(Pair(4, 5), null) + undirGraph.addEdge(Pair(5, 6), null) + undirGraph.addEdge(Pair(6, 1), 0) + assertEquals(listOf(setOf(1, 2, 3, 4, 5, 6)), communityUndir.detectCommunities()) + } + + @Test + fun `detected communities in a disconnected graph should be correct`() { + undirGraph.addVertex(1, "A") + undirGraph.addVertex(2, "B") + undirGraph.addVertex(3, "C") + undirGraph.addVertex(4, "D") + undirGraph.addVertex(5, "E") + undirGraph.addVertex(6, "F") + undirGraph.addVertex(7, "G") + undirGraph.addVertex(8, "H") + undirGraph.addVertex(9, "I") + undirGraph.addEdge(Pair(1, 2), 4) + undirGraph.addEdge(Pair(2, 3), 12) + undirGraph.addEdge(Pair(3, 4), 3) + undirGraph.addEdge(Pair(4, 3), 56) + undirGraph.addEdge(Pair(5, 6), null) + undirGraph.addEdge(Pair(7, 8), 0) + undirGraph.addEdge(Pair(8, 9), -19) + undirGraph.addEdge(Pair(9, 7), 2) + assertEquals(listOf(setOf(1, 2, 3, 4), setOf(5, 6), setOf(7, 8, 9)), communityUndir.detectCommunities()) + } + //endregion +} From d3c5af48b3ecbabe6ea737efd09e9850ce0adbd8 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 29 May 2024 20:43:27 +0300 Subject: [PATCH 020/131] refactor: replaced wildcard with specific imports --- src/main/kotlin/model/algorithms/Louvain.kt | 2 +- src/test/kotlin/LouvainTest.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/model/algorithms/Louvain.kt b/src/main/kotlin/model/algorithms/Louvain.kt index f596d1d..1b78111 100644 --- a/src/main/kotlin/model/algorithms/Louvain.kt +++ b/src/main/kotlin/model/algorithms/Louvain.kt @@ -1,6 +1,6 @@ package model.algorithms -import model.graph.* +import model.graph.Graph /** * Louvain algorithm for detecting communities in a graph. diff --git a/src/test/kotlin/LouvainTest.kt b/src/test/kotlin/LouvainTest.kt index a2eccce..2e54e4e 100644 --- a/src/test/kotlin/LouvainTest.kt +++ b/src/test/kotlin/LouvainTest.kt @@ -1,5 +1,6 @@ import model.algorithms.Louvain -import model.graph.* +import model.graph.UndirectedGraph +import model.graph.DirectedGraph import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals From f576f3131b50d1f2bb5e8da702e42005dc748d3b Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 29 May 2024 21:33:01 +0300 Subject: [PATCH 021/131] feat: added Prim algorithm --- src/main/kotlin/model/algorithms/Prim.kt | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/kotlin/model/algorithms/Prim.kt diff --git a/src/main/kotlin/model/algorithms/Prim.kt b/src/main/kotlin/model/algorithms/Prim.kt new file mode 100644 index 0000000..4804b7f --- /dev/null +++ b/src/main/kotlin/model/algorithms/Prim.kt @@ -0,0 +1,40 @@ +package model.algorithms + +import model.graph.* + +class MST(private val graph: UndirectedGraph) { / + /**get the graph, make from it the MST and return it*/ + fun getMST(): UndirectedGraph { + + val newGraph = UndirectedGraph() + var minWeight: Int + var addedVertex: Vertex? = null + var pairForAddedEdge: Pair? = null + for (vertexId in graph.vertices.keys) { // добавили рондомную вершинку + graph.vertices[vertexId]?.let { newGraph.addVertex(vertexId, it.data) } + break + } + while (newGraph.vertices.size != graph.vertices.size) { + minWeight = + Int.MAX_VALUE // в нашем приложении должно быть ограничение на значение веса ребра : < Int.MAX_VALUE + for (vertex in newGraph.vertices) { + for (edge in graph.adjacency[vertex.key]!!) { + if (!(graph.vertices[edge.key] in newGraph.vertices.values && vertex.value in newGraph.vertices.values)) { //избегаем появления циклов в новом графе, т.к. он должен быть деревом + if (edge.value == null) { + throw IllegalArgumentException ("Exists the edge without weight") + } else if (edge.value!! <= minWeight) { + minWeight = edge.value!! + pairForAddedEdge = vertex.key to edge.key + addedVertex = graph.vertices[edge.key] + } + } + } + } + newGraph.addVertex(addedVertex!!.id, addedVertex.data) + newGraph.addEdge(pairForAddedEdge!!, minWeight) + } + + return newGraph + } + +} From 4a6fb9c3f48a41fb068e6d9de9f2c5524fe6100a Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 29 May 2024 22:04:02 +0300 Subject: [PATCH 022/131] feat: added CycleSearch algorithm --- .../kotlin/model/algorithms/CycleSearch.kt | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/main/kotlin/model/algorithms/CycleSearch.kt diff --git a/src/main/kotlin/model/algorithms/CycleSearch.kt b/src/main/kotlin/model/algorithms/CycleSearch.kt new file mode 100644 index 0000000..86a3992 --- /dev/null +++ b/src/main/kotlin/model/algorithms/CycleSearch.kt @@ -0,0 +1,68 @@ +package model.algorithms + +import model.graph.Graph +import model.graph.Edge + +class CycleSearch(private val graph: Graph) { + + fun findCycle(cycleVertexId: Int): List>? { + + if (cycleVertexId !in graph.vertices.keys) { + throw IllegalArgumentException("Vertex with index = $cycleVertexId doesn't exist in the graph. The cycle can't be found") + } + + val visited = HashMap() + for (vertexId in graph.vertices.keys) { + visited[vertexId] = false + } + val cycleWayFrom = + HashMap() + val cycleIsFound = Array(1) { false } + val returnList = mutableListOf>() + + dfs(cycleVertexId, visited, cycleWayFrom, cycleVertexId, cycleIsFound, returnList) + if (cycleIsFound[0]) { + return returnList + } + return null + + } + + private fun dfs( + vertexId: Int, + visited: HashMap, + cycleWayFrom: HashMap, + cycleVertexId: Int, + cycleIsFound: Array, + returnList: MutableList> + ) { + + visited[vertexId] = true + + if (!cycleIsFound[0]) { + for (adjacencyVertexId in graph.adjacency[vertexId]!!.keys) { + + if (visited[adjacencyVertexId] == false) { + cycleWayFrom[adjacencyVertexId] = vertexId + dfs(adjacencyVertexId, visited, cycleWayFrom, cycleVertexId, cycleIsFound, returnList) + } else if (adjacencyVertexId == cycleVertexId && adjacencyVertexId != cycleWayFrom[vertexId]) { + cycleWayFrom[cycleVertexId] = vertexId + var id = cycleWayFrom.keys.last() + while (id != cycleVertexId) { + returnList.add(Edge(cycleWayFrom[id]!! to id, graph.adjacency[cycleWayFrom[id]]!![id])) + id = cycleWayFrom[id]!! + } + returnList.add(Edge(cycleWayFrom[id]!! to id, graph.adjacency[cycleWayFrom[id]]!![id])) + cycleIsFound[0] = true + } + + } + } + } + + +} + + + + From 0a4681542ba2f8c7fb0c395a98d94da28f5221a9 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 29 May 2024 22:27:29 +0300 Subject: [PATCH 023/131] test: added tests for Prim's algorithm --- src/test/kotlin/PrimTest.kt | 104 ++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/test/kotlin/PrimTest.kt diff --git a/src/test/kotlin/PrimTest.kt b/src/test/kotlin/PrimTest.kt new file mode 100644 index 0000000..720a68f --- /dev/null +++ b/src/test/kotlin/PrimTest.kt @@ -0,0 +1,104 @@ +import model.algorithms.Prim +import model.graph.UndirectedGraph +import model.graph.DirectedGraph +import model.graph.Edge +import model.graph.Vertex +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.assertEquals + +class PrimTest{ //4 TESTS + + @Test + fun `a graph consists of two vertices and one edge`() { + val initGraph = UndirectedGraph() + + initGraph.addVertex(1, 1) + initGraph.addVertex(2, 2) + + initGraph.addEdge(1 to 2, 9) + val mstGraph = MST(initGraph) + val MSTree : UndirectedGraph = mstGraph.getMST() + assertEquals( listOf(Edge(2 to 1, 9),Edge(1 to 2, 9)), MSTree.edges ) + assertEquals( setOf(Vertex(1,1),Vertex(2,2)), setOf( MSTree.vertices ) ) + + } + + @Test + fun `a triangle-shaped graph, checking for the content of cycles in the MSTree`() { + val initGraph = UndirectedGraph() + + initGraph.addVertex(1, 1) + initGraph.addVertex(2, 2) + initGraph.addVertex(3,3) + + initGraph.addEdge(1 to 2, 10) + initGraph.addEdge(1 to 3, 5) + initGraph.addEdge(2 to 3, 1) + + val graph = MST(initGraph) + val MSTree : UndirectedGraph = graph.getMST() + val weightsEdgesOfMST = mutableListOf() + (MSTree.edges).forEach{ weightsEdgesOfMST.add(it.weight!!) } + assertEquals(2,weightsEdgesOfMST.size) // amount of edges + assertEquals(setOf(1,5),setOf(weightsEdgesOfMST)) // edge values + assertEquals( setOf(Vertex(1,1),Vertex(2,2),Vertex(3,3)), setOf( MSTree.vertices ) ) + + } + + @Test + fun `a graph in the form of a square with diagonals and a vertex in the middle`() { + val initGraph = UndirectedGraph() + + initGraph.addVertex(1,1) + initGraph.addVertex(2,2) + initGraph.addVertex(3,3) + initGraph.addVertex(4,4) + initGraph.addVertex(5,5) + + initGraph.addEdge(1 to 2,9) + initGraph.addEdge(1 to 4,3) + initGraph.addEdge(1 to 5,0) + initGraph.addEdge(2 to 4,13) + initGraph.addEdge(2 to 3,47) + initGraph.addEdge(3 to 4,31) + initGraph.addEdge(3 to 5,2) + initGraph.addEdge(5 to 4,6) + + val graph = MST(initGraph) + val MSTree : UndirectedGraph = graph.getMST() + val weightsEdgesOfMST = mutableListOf() + (MSTree.edges).forEach{ weightsEdgesOfMST.add(it.weight!!) } + assertEquals(setOf(0,2,3,9),setOf(weightsEdgesOfMST)) + assertEquals(true,weightsEdgesOfMST.groupBy { it }.values.map { it.size }.all{ it == 2 }) + + } + + @Test + fun `a graph in the form of a square with diagonals and a vertex in the middle with the same weight`() { + val initGraph = UndirectedGraph() + + initGraph.addVertex(1,1) + initGraph.addVertex(2,2) + initGraph.addVertex(3,3) + initGraph.addVertex(4,4) + initGraph.addVertex(5,5) + + initGraph.addEdge(1 to 2,3) + initGraph.addEdge(1 to 4,3) + initGraph.addEdge(1 to 5,3) + initGraph.addEdge(2 to 4,3) + initGraph.addEdge(2 to 3,3) + initGraph.addEdge(3 to 4,3) + initGraph.addEdge(3 to 5,3) + initGraph.addEdge(5 to 4,3) + + val graph = MST(initGraph) + val MSTree : UndirectedGraph = graph.getMST() + val weightsEdgesOfMST = mutableListOf() + (MSTree.edges).forEach{ weightsEdgesOfMST.add(it.weight!!) } + assertEquals(3,setOf(weightsEdgesOfMST)) + assertEquals(4,weightsEdgesOfMST.size) + + } + +} \ No newline at end of file From 42e88cf7b6c2d3044870c0e1d50c10e80c396111 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 29 May 2024 22:43:23 +0300 Subject: [PATCH 024/131] feat: added gui base --- src/main/kotlin/Main.kt | 44 +++++++ src/main/kotlin/view/MainScreen.kt | 115 ++++++++++++++++++ src/main/kotlin/view/graph/EdgeView.kt | 42 +++++++ src/main/kotlin/view/graph/GraphView.kt | 29 +++++ src/main/kotlin/view/graph/VertexView.kt | 43 +++++++ .../kotlin/viewmodel/MainScreenViewModel.kt | 27 ++++ .../graph/CircularPlacementStrategy.kt | 57 +++++++++ .../kotlin/viewmodel/graph/EdgeViewModel.kt | 17 +++ .../kotlin/viewmodel/graph/GraphViewModel.kt | 40 ++++++ .../viewmodel/graph/RepresentationStrategy.kt | 8 ++ .../kotlin/viewmodel/graph/VertexViewModel.kt | 49 ++++++++ 11 files changed, 471 insertions(+) create mode 100644 src/main/kotlin/view/MainScreen.kt create mode 100644 src/main/kotlin/view/graph/EdgeView.kt create mode 100644 src/main/kotlin/view/graph/GraphView.kt create mode 100644 src/main/kotlin/view/graph/VertexView.kt create mode 100644 src/main/kotlin/viewmodel/MainScreenViewModel.kt create mode 100644 src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt create mode 100644 src/main/kotlin/viewmodel/graph/EdgeViewModel.kt create mode 100644 src/main/kotlin/viewmodel/graph/GraphViewModel.kt create mode 100644 src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt create mode 100644 src/main/kotlin/viewmodel/graph/VertexViewModel.kt diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index e69de29..6024838 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -0,0 +1,44 @@ +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import model.graph.Graph +import model.graph.UndirectedGraph +import view.MainScreen +import viewmodel.MainScreenViewModel +import viewmodel.graph.CircularPlacementStrategy + +val sampleGraph = UndirectedGraph().apply { + addVertex(1, "A") + addVertex(2, "B") + addVertex(3, "C") + addVertex(4, "D") + addVertex(5, "E") + + addEdge(Pair(1, 2), 1) + addEdge(Pair(2, 3), 2) + addEdge(Pair(3, 4), 3) + addEdge(Pair(2, 4), 4) + addEdge(Pair(1, 5), 5) +} + +@Composable +@Preview +fun App() { + MaterialTheme { + MainScreen(MainScreenViewModel(sampleGraph, CircularPlacementStrategy())) + } +} + +fun main() = application { + Window(onCloseRequest = ::exitApplication) { + App() + } +} \ No newline at end of file diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt new file mode 100644 index 0000000..b7f74d0 --- /dev/null +++ b/src/main/kotlin/view/MainScreen.kt @@ -0,0 +1,115 @@ +package view + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.sp +import model.graph.Graph +import org.jetbrains.skia.impl.Stats.enabled +import view.graph.GraphView +import viewmodel.MainScreenViewModel +import viewmodel.graph.CircularPlacementStrategy +import viewmodel.graph.GraphViewModel + +@Composable +fun MainScreen(viewModel: MainScreenViewModel) { + var expanded by remember { mutableStateOf(false) } + Row( + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) { + Column(modifier = Modifier.width(300.dp).fillMaxHeight().background(Color.Gray)) { + Row { + Checkbox(checked = viewModel.showVerticesLabels.value, onCheckedChange = { + viewModel.showVerticesLabels.value = it + }) + Text("Show vertices data", fontSize = 20.sp, modifier = Modifier.padding(4.dp)) + } + Row { + Checkbox(checked = viewModel.showEdgesLabels.value, onCheckedChange = { + viewModel.showEdgesLabels.value = it + }) + Text("Show edges weight", fontSize = 20.sp, modifier = Modifier.padding(4.dp)) + } + Button( + onClick = viewModel::resetGraphView, + enabled = true, + ) { + Text( + text = "Reset default settings", fontSize = 20.sp, modifier = Modifier.padding(4.dp) + ) + } + Box { + Row(verticalAlignment = Alignment.CenterVertically) { + Button(onClick = { expanded = true }) { + Text("Algorithm") + Icon(Icons.Default.ArrowDropDown, contentDescription = "Select algorithm") + } + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier.background(Color.Green) + ) { + DropdownMenuItem(onClick = { + expanded = false + }) { + Text("Dijkstra") + } + DropdownMenuItem(onClick = { + expanded = false + }) { + Text("Kosaraju") + } + DropdownMenuItem(onClick = { + expanded = false + }) { + Text("Louvain") + } + } + } + } + Box { + var expandedThemeMenu by remember { mutableStateOf(false) } + Button( + onClick = { expandedThemeMenu = true }, + modifier = Modifier.align(Alignment.TopEnd) + ) { + Text("Theme") + Icon(Icons.Default.ArrowDropDown, contentDescription = "Select theme") + } + DropdownMenu( + expanded = expandedThemeMenu, + onDismissRequest = { expandedThemeMenu = false }, + modifier = Modifier.background(Color.Red) + ) { + DropdownMenuItem(onClick = { + expandedThemeMenu = false + }) { + Text("Nastya's theme") + } + DropdownMenuItem(onClick = { + expandedThemeMenu = false + }) { + Text("Liya's theme") + } + DropdownMenuItem(onClick = { + expandedThemeMenu = false + }) { + Text("Katya's theme") + } + } + } + Surface( + modifier = Modifier.weight(1f), + ) { + GraphView(viewModel.graphViewModel) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/view/graph/EdgeView.kt b/src/main/kotlin/view/graph/EdgeView.kt new file mode 100644 index 0000000..704f7bc --- /dev/null +++ b/src/main/kotlin/view/graph/EdgeView.kt @@ -0,0 +1,42 @@ +package view.graph + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import viewmodel.graph.EdgeViewModel + +@Composable +fun EdgeView( + viewModel: EdgeViewModel, + modifier: Modifier = Modifier, +) { + Canvas(modifier = modifier.fillMaxSize()) { + drawLine( + start = Offset( + viewModel.u.x.toPx() + viewModel.u.radius.toPx(), + viewModel.u.y.toPx() + viewModel.u.radius.toPx(), + ), + end = Offset( + viewModel.v.x.toPx() + viewModel.v.radius.toPx(), + viewModel.v.y.toPx() + viewModel.v.radius.toPx(), + ), + color = Color.Black + ) + } + if (viewModel.labelVisible) { + Text( + modifier = Modifier + .offset( + viewModel.u.x + (viewModel.v.x - viewModel.u.x) / 2, + viewModel.u.y + (viewModel.v.y - viewModel.u.y) / 2 + ), + text = viewModel.label, + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/view/graph/GraphView.kt b/src/main/kotlin/view/graph/GraphView.kt new file mode 100644 index 0000000..99eabf8 --- /dev/null +++ b/src/main/kotlin/view/graph/GraphView.kt @@ -0,0 +1,29 @@ +package view.graph + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import viewmodel.graph.GraphViewModel +import viewmodel.graph.VertexViewModel + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun GraphView( + viewModel: GraphViewModel, +) { + Box(modifier = Modifier + .fillMaxSize() + + ) { + viewModel.verticesViewValues.forEach { v -> + VertexView(v, Modifier) + } + viewModel.edgesViewValues.forEach { e -> + EdgeView(e, Modifier) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/view/graph/VertexView.kt b/src/main/kotlin/view/graph/VertexView.kt new file mode 100644 index 0000000..3d96360 --- /dev/null +++ b/src/main/kotlin/view/graph/VertexView.kt @@ -0,0 +1,43 @@ +package view.graph + +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.dp +import viewmodel.graph.VertexViewModel + +@Composable +fun VertexView( + viewModel: VertexViewModel, + modifier: Modifier = Modifier, +) { + Box(modifier = modifier + .size(viewModel.radius * 2, viewModel.radius * 2) + .offset(viewModel.x, viewModel.y) + .background( + color = viewModel.color, + shape = CircleShape + ) + .pointerInput(viewModel) { + detectDragGestures { change, dragAmount -> + change.consume() + viewModel.onDrag(dragAmount) + } + } + ) { + if (viewModel.labelVisible) { + Text( + modifier = Modifier + .align(Alignment.Center) + .offset(0.dp, -viewModel.radius - 10.dp), + text = viewModel.label, + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt new file mode 100644 index 0000000..5de6ab2 --- /dev/null +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -0,0 +1,27 @@ +package viewmodel + +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.graphics.Color +import model.graph.Graph +import viewmodel.graph.GraphViewModel +import viewmodel.graph.RepresentationStrategy + +class MainScreenViewModel(graph: Graph, private val representationStrategy: RepresentationStrategy,) { + val showVerticesLabels = mutableStateOf(false) + val showEdgesLabels = mutableStateOf(false) + val graphViewModel = GraphViewModel(graph, showVerticesLabels, showEdgesLabels) + private var algorithmRunning = false // флаг для отслеживания состояния работы алгоритма + + init { + representationStrategy.place(800.0, 600.0, graphViewModel.verticesViewValues) + } + + fun resetGraphView() { + representationStrategy.place(800.0, 600.0, graphViewModel.verticesViewValues) + graphViewModel.verticesViewValues.forEach{ v -> v.color = Color.Gray} + } + + fun setVerticesColor() { + representationStrategy.highlight(graphViewModel.verticesViewValues) + } +} \ No newline at end of file diff --git a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt new file mode 100644 index 0000000..970a032 --- /dev/null +++ b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt @@ -0,0 +1,57 @@ +package viewmodel.graph + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import kotlin.math.cos +import kotlin.math.min +import kotlin.math.sin +import kotlin.random.Random + +class CircularPlacementStrategy : RepresentationStrategy { + override fun place(width: Double, height: Double, vertices: Collection>) { + if (vertices.isEmpty()) { + println("CircularPlacementStrategy.place: there is nothing to place 👐🏻") + return + } + + + val center = Pair(width / 2, height / 2) + val angle = 2 * Math.PI / vertices.size + + val sorted = vertices.sortedBy { it.label } + val first = sorted.first() + var point = Pair(center.first, center.second - min(width, height) / 2) + first.x = point.first.dp + first.y = point.second.dp + first.color = Color.Gray + + sorted + .drop(1) + .onEach { + point = point.rotate(center, angle) + it.x = point.first.dp + it.y = point.second.dp + } + } + + override fun highlight(vertices: Collection>) { + val customColor_1 = Color(red = 235, green = 82, blue = 132) //235,82,132 + val customColor_2 = Color(red = 251, green = 160, blue = 227) //251,160,227 + vertices + .onEach { + it.color = if (Random.nextBoolean()) customColor_1 else customColor_2 + } + } + + private fun Pair.rotate(pivot: Pair, angle: Double): Pair { + val sin = sin(angle) + val cos = cos(angle) + + val diff = first - pivot.first to second - pivot.second + val rotated = Pair( + diff.first * cos - diff.second * sin, + diff.first * sin + diff.second * cos, + ) + return rotated.first + pivot.first to rotated.second + pivot.second + } +} \ No newline at end of file diff --git a/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt b/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt new file mode 100644 index 0000000..6c55add --- /dev/null +++ b/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt @@ -0,0 +1,17 @@ +package viewmodel.graph + +import androidx.compose.runtime.State +import model.graph.Edge + +class EdgeViewModel( + val u: VertexViewModel, + val v: VertexViewModel, + private val e: Edge, + private val _labelVisible: State, +) { + val label + get() = e.weight.toString() + + val labelVisible + get() = _labelVisible.value +} \ No newline at end of file diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt new file mode 100644 index 0000000..01274e4 --- /dev/null +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -0,0 +1,40 @@ +package viewmodel.graph + +import androidx.compose.runtime.State +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import model.graph.Edge +import model.graph.Graph +import model.graph.Vertex + +class GraphViewModel( + private val graph: Graph, + showVerticesLabels: State, + showEdgesLabels: State, +) { + private val verticesView: HashMap, VertexViewModel> = hashMapOf() + + init { + graph.getVertices().forEach { vertex -> + verticesView[vertex] = VertexViewModel(0.dp, 0.dp, Color.Blue, vertex, showVerticesLabels) + } + } + + private val edgesView: HashMap, EdgeViewModel> = hashMapOf() + + init { + graph.edges.forEach { edge -> + val fst = verticesView[graph.vertices[edge.vertices.first]] + ?: throw IllegalStateException("VertexView for vertex with id: ${edge.vertices.first} not found") + val snd = verticesView[graph.vertices[edge.vertices.second]] + ?: throw IllegalStateException("VertexView for vertex with id: ${edge.vertices.second} not found") + edgesView[edge] = EdgeViewModel(fst, snd, edge, showEdgesLabels) + } + } + + val verticesViewValues: Collection> + get() = verticesView.values + + val edgesViewValues: Collection> + get() = edgesView.values +} diff --git a/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt b/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt new file mode 100644 index 0000000..bb2b0e4 --- /dev/null +++ b/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt @@ -0,0 +1,8 @@ +package viewmodel.graph + +import viewmodel.graph.VertexViewModel + +interface RepresentationStrategy { + fun place(width: Double, height: Double, vertices: Collection>) + fun highlight(vertices: Collection>) +} \ No newline at end of file diff --git a/src/main/kotlin/viewmodel/graph/VertexViewModel.kt b/src/main/kotlin/viewmodel/graph/VertexViewModel.kt new file mode 100644 index 0000000..1d7ca41 --- /dev/null +++ b/src/main/kotlin/viewmodel/graph/VertexViewModel.kt @@ -0,0 +1,49 @@ +package viewmodel.graph + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import model.graph.Vertex + +class VertexViewModel( + x: Dp = 0.dp, + y: Dp = 0.dp, + color: Color, + private val v: Vertex, + private val _labelVisible: State, + val radius: Dp = 25.dp +) { + private var _x = mutableStateOf(x) + var x: Dp + get() = _x.value + set(value) { + _x.value = value + } + private var _y = mutableStateOf(y) + var y: Dp + get() = _y.value + set(value) { + _y.value = value + } + private var _color = mutableStateOf(color) + var color: Color + get() = _color.value + set(value) { + _color.value = value + } + + val label + get() = v.data.toString() + + val labelVisible + get() = _labelVisible.value + + fun onDrag(offset: Offset) { + _x.value += offset.x.dp + _y.value += offset.y.dp + } +} \ No newline at end of file From 045977fa8285cb57055e21e1035f3a50c158059f Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 29 May 2024 22:44:17 +0300 Subject: [PATCH 025/131] test: added tests for CycleSearch's algorithm --- src/test/kotlin/CycleSearchTest.kt | 142 +++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/test/kotlin/CycleSearchTest.kt diff --git a/src/test/kotlin/CycleSearchTest.kt b/src/test/kotlin/CycleSearchTest.kt new file mode 100644 index 0000000..e96eab2 --- /dev/null +++ b/src/test/kotlin/CycleSearchTest.kt @@ -0,0 +1,142 @@ +import model.algorithms.CycleSearch +import model.graph.UndirectedGraph +import model.graph.DirectedGraph +import model.graph.Edge +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.assertEquals + +class CycleSearchTest { //6 TESTS + + //region UNDIRECTED GRAPH (3) + + @Test + fun ` a triangle-shaped undirected graph wiht cycle `() { + val initGraph = UndirectedGraph() + + initGraph.addVertex(1, 1) + initGraph.addVertex(2, 2) + initGraph.addVertex(3, 3) + + initGraph.addEdge(1 to 2, 10) + initGraph.addEdge(2 to 3, 5) + initGraph.addEdge(3 to 1, 1) + + val graph = CycleSearch(initGraph) + val cycleEdges: List>? = graph.findCycle(1) + assertEquals(true, cycleEdges != null) + for (edge in initGraph.edges) { + assertEquals(true, edge in cycleEdges!!) + } + for (edge in cycleEdges!!) { + assertEquals(true, edge in initGraph.edges) + } + } + + @Test + fun ` undirected graph consisted of 4 vertices without cycle`() { + val initGraph = UndirectedGraph() + + initGraph.addVertex(1, 1) + initGraph.addVertex(2, 2) + initGraph.addVertex(3, 3) + initGraph.addVertex(4, 4) + + initGraph.addEdge(1 to 2, 10) + initGraph.addEdge(1 to 3, 5) + initGraph.addEdge(1 to 4, 1) + + val graph = CycleSearch(initGraph) + val cycleEdges: List>? = graph.findCycle(1) + assertEquals(null, cycleEdges) + + } + + @Test + fun ` the cycle of the undirected graph exists, but not around the input vertex `() { + val initGraph = UndirectedGraph() + + initGraph.addVertex(1, 1) + initGraph.addVertex(2, 2) + initGraph.addVertex(3, 3) + initGraph.addVertex(4, 4) + + initGraph.addEdge(1 to 2, 10) + initGraph.addEdge(2 to 3, 5) + initGraph.addEdge(3 to 4, 3) + initGraph.addEdge(4 to 2, 1) + + val graph = CycleSearch(initGraph) + val cycleEdges: List>? = graph.findCycle(1) + assertEquals(null, cycleEdges) + + } + + //endregion + + //region DIRECTED GRAPH (3) + + @Test + fun ` a triangle-shaped directed graph whith cycle `() { + val initGraph = DirectedGraph() + + initGraph.addVertex(1, 1) + initGraph.addVertex(2, 2) + initGraph.addVertex(3, 3) + + initGraph.addEdge(1 to 2, 10) + initGraph.addEdge(2 to 3, 5) + initGraph.addEdge(3 to 1, 1) + + val graph = CycleSearch(initGraph) + val cycleEdges: List>? = graph.findCycle(1) + assertEquals(true, cycleEdges != null) + for (edge in initGraph.edges) { + assertEquals(true, edge in cycleEdges!!) + } + for (edge in cycleEdges!!) { + assertEquals(true, edge in initGraph.edges) + } + // проверили равенство наборов ребер у первоначального графа и у цикла + } + + @Test + fun ` a triangle-shaped directed graph without cycle `() { + val initGraph = DirectedGraph() + + initGraph.addVertex(1, 1) + initGraph.addVertex(2, 2) + initGraph.addVertex(3, 3) + + initGraph.addEdge(1 to 2, 10) + initGraph.addEdge(1 to 3, 5) + initGraph.addEdge(2 to 3, 1) + + val graph = CycleSearch(initGraph) + val cycleEdges: List>? = graph.findCycle(1) + assertEquals(null, cycleEdges) + + } + + @Test + fun ` the cycle of the directed graph exists, but not around the input vertex `() { + val initGraph = DirectedGraph() + + initGraph.addVertex(0, 0) + initGraph.addVertex(1, 1) + initGraph.addVertex(2, 2) + initGraph.addVertex(3, 3) + + initGraph.addEdge(0 to 1, 10) + initGraph.addEdge(1 to 2, 7) + initGraph.addEdge(2 to 3, 5) + initGraph.addEdge(3 to 1, 1) + + val graph = CycleSearch(initGraph) + val cycleEdges: List>? = graph.findCycle(0) + assertEquals(null, cycleEdges) + + } + + //endregion + +} From 7090c37d59e02096cc947f4c2ea95b4bb5a64531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B8=D1=8F=20=D0=9D=D0=B0=D0=B1=D0=B8=D0=B5=D0=B2?= =?UTF-8?q?=D0=B0?= Date: Wed, 29 May 2024 22:58:11 +0300 Subject: [PATCH 026/131] feat: added BellmanFord algorithm --- .../kotlin/model/algorithms/BellmanFord.kt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/kotlin/model/algorithms/BellmanFord.kt diff --git a/src/main/kotlin/model/algorithms/BellmanFord.kt b/src/main/kotlin/model/algorithms/BellmanFord.kt new file mode 100644 index 0000000..36d861b --- /dev/null +++ b/src/main/kotlin/model/algorithms/BellmanFord.kt @@ -0,0 +1,42 @@ +package algorithms + +import model.graph.Graph + +class BellmanFord { + fun findShortestPath(graph: Graph, src: Int, dest: Int): Pair>? { + val dist = mutableMapOf().withDefault { Int.MAX_VALUE } + val pred = mutableMapOf() + dist[src] = 0 + + for (i in 1 until graph.vertices.size) { + for (edge in graph.edges) { + val (u, v) = edge.vertices + val weight = edge.weight ?: continue + if (dist.getValue(u) != Int.MAX_VALUE && dist.getValue(u) + weight < dist.getValue(v)) { + dist[v] = dist.getValue(u) + weight + pred[v] = u + } + } + } + + for (edge in graph.edges) { + val (u, v) = edge.vertices + val weight = edge.weight ?: continue + if (dist.getValue(u) != Int.MAX_VALUE && dist.getValue(u) + weight < dist.getValue(v)) { + println("Graph contains negative weight cycle") + return null + } + } + + val path = mutableListOf() + var current: Int? = dest + while (current != null) { + path.add(current) + current = pred[current] + } + path.reverse() + + return if (dist[dest] != Int.MAX_VALUE) Pair(dist[dest]!!, path) else null + } +} + From 79adb41b3b2eb2918ebf491888325be0f8abb525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B8=D1=8F=20=D0=9D=D0=B0=D0=B1=D0=B8=D0=B5=D0=B2?= =?UTF-8?q?=D0=B0?= Date: Wed, 29 May 2024 22:59:50 +0300 Subject: [PATCH 027/131] feat: added tests for BellmanFord algorithm --- src/test/kotlin/BellmanFordTest.kt | 66 ++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/test/kotlin/BellmanFordTest.kt diff --git a/src/test/kotlin/BellmanFordTest.kt b/src/test/kotlin/BellmanFordTest.kt new file mode 100644 index 0000000..57aec49 --- /dev/null +++ b/src/test/kotlin/BellmanFordTest.kt @@ -0,0 +1,66 @@ +import kotlin.test.assertNull +import kotlin.test.assertEquals +import model.algorithms.BellmanFord +import model.graph.DirectedGraph +import model.graph.UndirectedGraph +import org.junit.jupiter.api.Test + +class BellmanFordTest { + private val dirGraph = DirectedGraph() + private val undirGraph = UndirectedGraph() + private val bellmanFordDirected = BellmanFord(dirGraph) + private val bellmanFordUndirected = BellmanFord(undirGraph) + + init { + // Добавление вершин для направленного графа + dirGraph.addVertex(1, "A") + dirGraph.addVertex(2, "B") + dirGraph.addVertex(3, "C") + dirGraph.addVertex(4, "D") + + // Добавление рёбер для направленного графа + dirGraph.addEdge(Pair(1, 2), 4) + dirGraph.addEdge(Pair(1, 3), 2) + dirGraph.addEdge(Pair(2, 3), 5) + dirGraph.addEdge(Pair(2, 4), 10) + dirGraph.addEdge(Pair(3, 4), 3) + + // Добавление вершин для ненаправленного графа + undirGraph.addVertex(1, "A") + undirGraph.addVertex(2, "B") + undirGraph.addVertex(3, "C") + undirGraph.addVertex(4, "D") + + // Добавление рёбер для ненаправленного графа + undirGraph.addEdge(Pair(1, 2), 1) + undirGraph.addEdge(Pair(1, 3), 4) + undirGraph.addEdge(Pair(2, 3), 2) + undirGraph.addEdge(Pair(2, 4), 7) + undirGraph.addEdge(Pair(3, 4), 3) + } + + @Test + fun testShortestPathDirectedGraph() { + val result = bellmanFordDirected.findShortestPath(1, 4) + assertEquals(Pair(5, listOf(1, 3, 4)), result) + } + + @Test + fun testShortestPathUndirectedGraph() { + val result = bellmanFordUndirected.findShortestPath(1, 4) + assertEquals(Pair(6, listOf(1, 2, 3, 4)), result) + } + @Test + fun testNegativeWeightCycle() { + dirGraph.addEdge(Pair(4, 1), -8) // Добавляем отрицательный цикл + val result = bellmanFordDirected.findShortestPath(1, 4) + assertNull(result) // Должно вернуть null из-за отрицательного цикла + } + + @Test + fun testShortestPathWithNegativeWeights() { + dirGraph.addEdge(Pair(2, 3), -3) // Изменяем вес ребра + val result = bellmanFordDirected.findShortestPath(1, 4) + assertEquals(Pair(4, listOf(1, 2, 3, 4)), result) + } +} From 99a39553c60fe74f505360349744ddbe6b367d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B8=D1=8F=20=D0=9D=D0=B0=D0=B1=D0=B8=D0=B5=D0=B2?= =?UTF-8?q?=D0=B0?= Date: Wed, 29 May 2024 23:13:37 +0300 Subject: [PATCH 028/131] feat: added BridgeFinder algorithm --- .../kotlin/model/algorithms/BridgeFinder.kt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/kotlin/model/algorithms/BridgeFinder.kt diff --git a/src/main/kotlin/model/algorithms/BridgeFinder.kt b/src/main/kotlin/model/algorithms/BridgeFinder.kt new file mode 100644 index 0000000..a144d63 --- /dev/null +++ b/src/main/kotlin/model/algorithms/BridgeFinder.kt @@ -0,0 +1,51 @@ +package algorithms + +import model.graph.Graph + +class BridgeFinder { + private var time = 0 + private val NIL = -1 + + fun findBridges(graph: Graph): List> { + val visited = BooleanArray(graph.vertices.size) + val disc = IntArray(graph.vertices.size) + val low = IntArray(graph.vertices.size) + val parent = IntArray(graph.vertices.size) + val bridges = mutableListOf>() + + for (i in 0 until graph.vertices.size) { + parent[i] = NIL + visited[i] = false + } + + for (i in 0 until graph.vertices.size) { + if (!visited[i]) { + bridgeUtil(i, visited, disc, low, parent, graph, bridges) + } + } + + return bridges + } + + private fun bridgeUtil(u: Int, visited: BooleanArray, disc: IntArray, low: IntArray, parent: IntArray, graph: Graph, bridges: MutableList>) { + visited[u] = true + disc[u] = ++time + low[u] = time + + val neighbors = graph.adjacency[u] ?: return + for (v in neighbors.keys) { + if (!visited[v]) { + parent[v] = u + bridgeUtil(v, visited, disc, low, parent, graph, bridges) + + low[u] = minOf(low[u], low[v]) + + if (low[v] > disc[u]) { + bridges.add(Pair(u, v)) + } + } else if (v != parent[u]) { + low[u] = minOf(low[u], disc[v]) + } + } + } +} From aac083a4c3753816352cafa4d565d0c29a374e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B8=D1=8F=20=D0=9D=D0=B0=D0=B1=D0=B8=D0=B5=D0=B2?= =?UTF-8?q?=D0=B0?= Date: Wed, 29 May 2024 23:14:05 +0300 Subject: [PATCH 029/131] feat: added tests for BridgeFinder algorithm --- src/test/kotlin/BridgeFinderTest.kt | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/test/kotlin/BridgeFinderTest.kt diff --git a/src/test/kotlin/BridgeFinderTest.kt b/src/test/kotlin/BridgeFinderTest.kt new file mode 100644 index 0000000..aeef51d --- /dev/null +++ b/src/test/kotlin/BridgeFinderTest.kt @@ -0,0 +1,65 @@ +import kotlin.test.assertEquals +import model.algorithms.BridgeFinder +import model.graph.UndirectedGraph +import org.junit.jupiter.api.Test + +class BridgeFinderTest { + private val undirGraph = UndirectedGraph() + private val bridgeFinder = BridgeFinder() + + init { + // Добавление вершин для ненаправленного графа + undirGraph.addVertex(0, "A") + undirGraph.addVertex(1, "B") + undirGraph.addVertex(2, "C") + undirGraph.addVertex(3, "D") + undirGraph.addVertex(4, "E") + } + + @Test + fun testNoBridges() { + undirGraph.addEdge(Pair(0, 1), 1) + undirGraph.addEdge(Pair(1, 2), 1) + undirGraph.addEdge(Pair(2, 0), 1) + undirGraph.addEdge(Pair(1, 3), 1) + undirGraph.addEdge(Pair(3, 4), 1) + undirGraph.addEdge(Pair(4, 1), 1) + + val bridges = bridgeFinder.findBridges(undirGraph) + assertEquals(emptyList>(), bridges) + } + + @Test + fun testOneBridge() { + undirGraph.addEdge(Pair(0, 1), 1) + undirGraph.addEdge(Pair(1, 2), 1) + undirGraph.addEdge(Pair(2, 3), 1) + undirGraph.addEdge(Pair(3, 4), 1) + + val bridges = bridgeFinder.findBridges(undirGraph) + assertEquals(listOf(Pair(0, 1), Pair(1, 2), Pair(2, 3), Pair(3, 4)), bridges.sortedWith(compareBy({ it.first }, { it.second }))) + } + + @Test + fun testMultipleBridges() { + undirGraph.addEdge(Pair(0, 1), 1) + undirGraph.addEdge(Pair(1, 2), 1) + undirGraph.addEdge(Pair(2, 3), 1) + undirGraph.addEdge(Pair(3, 4), 1) + undirGraph.addEdge(Pair(1, 3), 1) + + val bridges = bridgeFinder.findBridges(undirGraph) + assertEquals(listOf(Pair(0, 1), Pair(3, 4)), bridges.sortedWith(compareBy({ it.first }, { it.second }))) + } + + @Test + fun testDisconnectedGraph() { + undirGraph.addVertex(5, "F") + undirGraph.addEdge(Pair(0, 1), 1) + undirGraph.addEdge(Pair(2, 3), 1) + undirGraph.addEdge(Pair(3, 4), 1) + + val bridges = bridgeFinder.findBridges(undirGraph) + assertEquals(listOf(Pair(0, 1), Pair(2, 3), Pair(3, 4)), bridges.sortedWith(compareBy({ it.first }, { it.second }))) + } +} From a1d5a7d3ea17a267c6eefc442fe0360fc2e931dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B8=D1=8F=20=D0=9D=D0=B0=D0=B1=D0=B8=D0=B5=D0=B2?= =?UTF-8?q?=D0=B0?= Date: Wed, 29 May 2024 23:17:37 +0300 Subject: [PATCH 030/131] fix: deleted BridgeFinder algorithm and tests --- .../kotlin/model/algorithms/BridgeFinder.kt | 51 --------------- src/test/kotlin/BridgeFinderTest.kt | 65 ------------------- 2 files changed, 116 deletions(-) delete mode 100644 src/main/kotlin/model/algorithms/BridgeFinder.kt delete mode 100644 src/test/kotlin/BridgeFinderTest.kt diff --git a/src/main/kotlin/model/algorithms/BridgeFinder.kt b/src/main/kotlin/model/algorithms/BridgeFinder.kt deleted file mode 100644 index a144d63..0000000 --- a/src/main/kotlin/model/algorithms/BridgeFinder.kt +++ /dev/null @@ -1,51 +0,0 @@ -package algorithms - -import model.graph.Graph - -class BridgeFinder { - private var time = 0 - private val NIL = -1 - - fun findBridges(graph: Graph): List> { - val visited = BooleanArray(graph.vertices.size) - val disc = IntArray(graph.vertices.size) - val low = IntArray(graph.vertices.size) - val parent = IntArray(graph.vertices.size) - val bridges = mutableListOf>() - - for (i in 0 until graph.vertices.size) { - parent[i] = NIL - visited[i] = false - } - - for (i in 0 until graph.vertices.size) { - if (!visited[i]) { - bridgeUtil(i, visited, disc, low, parent, graph, bridges) - } - } - - return bridges - } - - private fun bridgeUtil(u: Int, visited: BooleanArray, disc: IntArray, low: IntArray, parent: IntArray, graph: Graph, bridges: MutableList>) { - visited[u] = true - disc[u] = ++time - low[u] = time - - val neighbors = graph.adjacency[u] ?: return - for (v in neighbors.keys) { - if (!visited[v]) { - parent[v] = u - bridgeUtil(v, visited, disc, low, parent, graph, bridges) - - low[u] = minOf(low[u], low[v]) - - if (low[v] > disc[u]) { - bridges.add(Pair(u, v)) - } - } else if (v != parent[u]) { - low[u] = minOf(low[u], disc[v]) - } - } - } -} diff --git a/src/test/kotlin/BridgeFinderTest.kt b/src/test/kotlin/BridgeFinderTest.kt deleted file mode 100644 index aeef51d..0000000 --- a/src/test/kotlin/BridgeFinderTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -import kotlin.test.assertEquals -import model.algorithms.BridgeFinder -import model.graph.UndirectedGraph -import org.junit.jupiter.api.Test - -class BridgeFinderTest { - private val undirGraph = UndirectedGraph() - private val bridgeFinder = BridgeFinder() - - init { - // Добавление вершин для ненаправленного графа - undirGraph.addVertex(0, "A") - undirGraph.addVertex(1, "B") - undirGraph.addVertex(2, "C") - undirGraph.addVertex(3, "D") - undirGraph.addVertex(4, "E") - } - - @Test - fun testNoBridges() { - undirGraph.addEdge(Pair(0, 1), 1) - undirGraph.addEdge(Pair(1, 2), 1) - undirGraph.addEdge(Pair(2, 0), 1) - undirGraph.addEdge(Pair(1, 3), 1) - undirGraph.addEdge(Pair(3, 4), 1) - undirGraph.addEdge(Pair(4, 1), 1) - - val bridges = bridgeFinder.findBridges(undirGraph) - assertEquals(emptyList>(), bridges) - } - - @Test - fun testOneBridge() { - undirGraph.addEdge(Pair(0, 1), 1) - undirGraph.addEdge(Pair(1, 2), 1) - undirGraph.addEdge(Pair(2, 3), 1) - undirGraph.addEdge(Pair(3, 4), 1) - - val bridges = bridgeFinder.findBridges(undirGraph) - assertEquals(listOf(Pair(0, 1), Pair(1, 2), Pair(2, 3), Pair(3, 4)), bridges.sortedWith(compareBy({ it.first }, { it.second }))) - } - - @Test - fun testMultipleBridges() { - undirGraph.addEdge(Pair(0, 1), 1) - undirGraph.addEdge(Pair(1, 2), 1) - undirGraph.addEdge(Pair(2, 3), 1) - undirGraph.addEdge(Pair(3, 4), 1) - undirGraph.addEdge(Pair(1, 3), 1) - - val bridges = bridgeFinder.findBridges(undirGraph) - assertEquals(listOf(Pair(0, 1), Pair(3, 4)), bridges.sortedWith(compareBy({ it.first }, { it.second }))) - } - - @Test - fun testDisconnectedGraph() { - undirGraph.addVertex(5, "F") - undirGraph.addEdge(Pair(0, 1), 1) - undirGraph.addEdge(Pair(2, 3), 1) - undirGraph.addEdge(Pair(3, 4), 1) - - val bridges = bridgeFinder.findBridges(undirGraph) - assertEquals(listOf(Pair(0, 1), Pair(2, 3), Pair(3, 4)), bridges.sortedWith(compareBy({ it.first }, { it.second }))) - } -} From a418912295cad7b9c0452513198e11c041bdc3e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B8=D1=8F=20=D0=9D=D0=B0=D0=B1=D0=B8=D0=B5=D0=B2?= =?UTF-8?q?=D0=B0?= Date: Wed, 29 May 2024 23:21:13 +0300 Subject: [PATCH 031/131] feat: added BridgeFinder algorithm --- .../kotlin/model/algorithms/BridgeFinder.kt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/kotlin/model/algorithms/BridgeFinder.kt diff --git a/src/main/kotlin/model/algorithms/BridgeFinder.kt b/src/main/kotlin/model/algorithms/BridgeFinder.kt new file mode 100644 index 0000000..0d7bc2c --- /dev/null +++ b/src/main/kotlin/model/algorithms/BridgeFinder.kt @@ -0,0 +1,51 @@ +package model.algorithms + +import model.graph.Graph + +class BridgeFinder { + private var time = 0 + private val NIL = -1 + + fun findBridges(graph: Graph): List> { + val visited = BooleanArray(graph.vertices.size) + val disc = IntArray(graph.vertices.size) + val low = IntArray(graph.vertices.size) + val parent = IntArray(graph.vertices.size) + val bridges = mutableListOf>() + + for (i in 0 until graph.vertices.size) { + parent[i] = NIL + visited[i] = false + } + + for (i in 0 until graph.vertices.size) { + if (!visited[i]) { + bridgeUtil(i, visited, disc, low, parent, graph, bridges) + } + } + + return bridges + } + + private fun bridgeUtil(u: Int, visited: BooleanArray, disc: IntArray, low: IntArray, parent: IntArray, graph: Graph, bridges: MutableList>) { + visited[u] = true + disc[u] = ++time + low[u] = time + + val neighbors = graph.adjacency[u] ?: return + for (v in neighbors.keys) { + if (!visited[v]) { + parent[v] = u + bridgeUtil(v, visited, disc, low, parent, graph, bridges) + + low[u] = minOf(low[u], low[v]) + + if (low[v] > disc[u]) { + bridges.add(Pair(u, v)) + } + } else if (v != parent[u]) { + low[u] = minOf(low[u], disc[v]) + } + } + } +} From 300aa604c17b09d4363cb3e90b5a4829cc3e87a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B8=D1=8F=20=D0=9D=D0=B0=D0=B1=D0=B8=D0=B5=D0=B2?= =?UTF-8?q?=D0=B0?= Date: Wed, 29 May 2024 23:21:36 +0300 Subject: [PATCH 032/131] feat: added tests for BridgeFinder algorithm --- src/test/kotlin/BridgeFinderTest.kt | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/test/kotlin/BridgeFinderTest.kt diff --git a/src/test/kotlin/BridgeFinderTest.kt b/src/test/kotlin/BridgeFinderTest.kt new file mode 100644 index 0000000..aeef51d --- /dev/null +++ b/src/test/kotlin/BridgeFinderTest.kt @@ -0,0 +1,65 @@ +import kotlin.test.assertEquals +import model.algorithms.BridgeFinder +import model.graph.UndirectedGraph +import org.junit.jupiter.api.Test + +class BridgeFinderTest { + private val undirGraph = UndirectedGraph() + private val bridgeFinder = BridgeFinder() + + init { + // Добавление вершин для ненаправленного графа + undirGraph.addVertex(0, "A") + undirGraph.addVertex(1, "B") + undirGraph.addVertex(2, "C") + undirGraph.addVertex(3, "D") + undirGraph.addVertex(4, "E") + } + + @Test + fun testNoBridges() { + undirGraph.addEdge(Pair(0, 1), 1) + undirGraph.addEdge(Pair(1, 2), 1) + undirGraph.addEdge(Pair(2, 0), 1) + undirGraph.addEdge(Pair(1, 3), 1) + undirGraph.addEdge(Pair(3, 4), 1) + undirGraph.addEdge(Pair(4, 1), 1) + + val bridges = bridgeFinder.findBridges(undirGraph) + assertEquals(emptyList>(), bridges) + } + + @Test + fun testOneBridge() { + undirGraph.addEdge(Pair(0, 1), 1) + undirGraph.addEdge(Pair(1, 2), 1) + undirGraph.addEdge(Pair(2, 3), 1) + undirGraph.addEdge(Pair(3, 4), 1) + + val bridges = bridgeFinder.findBridges(undirGraph) + assertEquals(listOf(Pair(0, 1), Pair(1, 2), Pair(2, 3), Pair(3, 4)), bridges.sortedWith(compareBy({ it.first }, { it.second }))) + } + + @Test + fun testMultipleBridges() { + undirGraph.addEdge(Pair(0, 1), 1) + undirGraph.addEdge(Pair(1, 2), 1) + undirGraph.addEdge(Pair(2, 3), 1) + undirGraph.addEdge(Pair(3, 4), 1) + undirGraph.addEdge(Pair(1, 3), 1) + + val bridges = bridgeFinder.findBridges(undirGraph) + assertEquals(listOf(Pair(0, 1), Pair(3, 4)), bridges.sortedWith(compareBy({ it.first }, { it.second }))) + } + + @Test + fun testDisconnectedGraph() { + undirGraph.addVertex(5, "F") + undirGraph.addEdge(Pair(0, 1), 1) + undirGraph.addEdge(Pair(2, 3), 1) + undirGraph.addEdge(Pair(3, 4), 1) + + val bridges = bridgeFinder.findBridges(undirGraph) + assertEquals(listOf(Pair(0, 1), Pair(2, 3), Pair(3, 4)), bridges.sortedWith(compareBy({ it.first }, { it.second }))) + } +} From 4c658b8d897e01f6d1fc58afcd2c0b85ed5e7b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B8=D1=8F=20=D0=9D=D0=B0=D0=B1=D0=B8=D0=B5=D0=B2?= =?UTF-8?q?=D0=B0?= Date: Wed, 29 May 2024 23:41:20 +0300 Subject: [PATCH 033/131] fix: changed package alghoritm BellmanFord --- src/main/kotlin/model/algorithms/BellmanFord.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/model/algorithms/BellmanFord.kt b/src/main/kotlin/model/algorithms/BellmanFord.kt index 36d861b..092bb0e 100644 --- a/src/main/kotlin/model/algorithms/BellmanFord.kt +++ b/src/main/kotlin/model/algorithms/BellmanFord.kt @@ -1,9 +1,9 @@ -package algorithms +package model.algorithms import model.graph.Graph -class BellmanFord { - fun findShortestPath(graph: Graph, src: Int, dest: Int): Pair>? { +class BellmanFord(private val graph: Graph) { + fun findShortestPath(src: Int, dest: Int): Pair>? { val dist = mutableMapOf().withDefault { Int.MAX_VALUE } val pred = mutableMapOf() dist[src] = 0 From a6a048445576818b8f4a8680f285d7d99fae8693 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Mon, 12 Aug 2024 18:48:01 +0300 Subject: [PATCH 034/131] feat: remade Prim's algorithm --- src/main/kotlin/model/algorithms/Prim.kt | 153 ++++++++++++++++++----- 1 file changed, 122 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/model/algorithms/Prim.kt b/src/main/kotlin/model/algorithms/Prim.kt index 4804b7f..cdbfc57 100644 --- a/src/main/kotlin/model/algorithms/Prim.kt +++ b/src/main/kotlin/model/algorithms/Prim.kt @@ -1,40 +1,131 @@ package model.algorithms -import model.graph.* - -class MST(private val graph: UndirectedGraph) { / - /**get the graph, make from it the MST and return it*/ - fun getMST(): UndirectedGraph { - - val newGraph = UndirectedGraph() - var minWeight: Int - var addedVertex: Vertex? = null - var pairForAddedEdge: Pair? = null - for (vertexId in graph.vertices.keys) { // добавили рондомную вершинку - graph.vertices[vertexId]?.let { newGraph.addVertex(vertexId, it.data) } - break - } - while (newGraph.vertices.size != graph.vertices.size) { - minWeight = - Int.MAX_VALUE // в нашем приложении должно быть ограничение на значение веса ребра : < Int.MAX_VALUE - for (vertex in newGraph.vertices) { - for (edge in graph.adjacency[vertex.key]!!) { - if (!(graph.vertices[edge.key] in newGraph.vertices.values && vertex.value in newGraph.vertices.values)) { //избегаем появления циклов в новом графе, т.к. он должен быть деревом - if (edge.value == null) { - throw IllegalArgumentException ("Exists the edge without weight") - } else if (edge.value!! <= minWeight) { - minWeight = edge.value!! - pairForAddedEdge = vertex.key to edge.key - addedVertex = graph.vertices[edge.key] - } - } +import model.graph.UndirectedGraph + +class Prim(private val graph: UndirectedGraph) { + + private fun getMST(graph: UndirectedGraph): UndirectedGraph { + + val graphMst = UndirectedGraph() + + for (vertex in graph.vertices.keys) { + graphMst.addVertex(vertex, graph.vertices[vertex]!!.data) + } + + val priorityQueue = hashMapOf>() + for (idVertex in graph.vertices.keys) { + priorityQueue[idVertex] = Int.MAX_VALUE to null + } + + //init Prim's algorithm + + val rootIdVertex: Int = graph.vertices.keys.first() + for (idAdjacency in graph.adjacency[rootIdVertex]!!.keys) { // неправильные соседи + priorityQueue[idAdjacency] = graph.adjacency[rootIdVertex]!![idAdjacency]!! to rootIdVertex + } + priorityQueue.remove(rootIdVertex) + + //Prim's algorithm + + var fromQueuePrioritet: Int + var fromQueueId: Int? = null + while (priorityQueue.isNotEmpty()) { + + fromQueuePrioritet = Int.MAX_VALUE + for (element in priorityQueue) { + if (element.value.first <= fromQueuePrioritet) { + fromQueuePrioritet = element.value.first + fromQueueId = element.key + } + } + + for (adjacency in graph.adjacency[fromQueueId]!!.filter { it.key in priorityQueue.keys }) { + if (graph.adjacency[fromQueueId]!![adjacency.key]!! < priorityQueue[adjacency.key]!!.first) { + priorityQueue.remove(adjacency.key) + priorityQueue[adjacency.key] = graph.adjacency[fromQueueId]!![adjacency.key]!! to fromQueueId + } + } + + graphMst.addEdge(fromQueueId!! to priorityQueue[fromQueueId]!!.second!!, fromQueuePrioritet) + priorityQueue.remove(fromQueueId) + + } + + return graphMst + + } + + private fun dfs( + idVertex: Int, + visited: HashMap, + component: UndirectedGraph + ): UndirectedGraph { + + visited[idVertex] = true + for (idAdjacency in graph.adjacency[idVertex]!!.keys) { + if (visited[idAdjacency] == false) { + visited[idAdjacency] = true + component.addVertex(idAdjacency, graph.vertices[idAdjacency]!!.data) + dfs(idAdjacency, visited, component) + } + } + for (edge in this.graph.edges) { + if (edge.vertices.first in component.vertices && edge.vertices.second in component.vertices && edge !in component.edges) { + component.addEdge(edge.vertices, edge.weight) + } + } + return component + } + + private fun privateTreePrim(): MutableList> { + + val returnListOfMST = mutableListOf>() + + val visited = hashMapOf() + for (vertexId in graph.vertices.keys) { + visited[vertexId] = false + } + + var initIndex: Int + var component: UndirectedGraph + while (visited.values.contains(false)) { + + initIndex = visited.filterValues { !it }.keys.first() + component = UndirectedGraph() + component.addVertex(initIndex, graph.vertices[initIndex]!!.data) + component = dfs(initIndex, visited, component) + returnListOfMST.add(getMST(component)) + + } + + return returnListOfMST + + } + + fun treePrim(): MutableList> { + + return privateTreePrim() + + } + + fun weightPrim(): Int { + + var treeWeight: Int = 0 + for (element in privateTreePrim()) { + for (edge in element.edges) { + + if (edge.weight == null) { + throw IllegalArgumentException("Each edge of a weighted graph must have a weight: the edge with weight = 'null' isn't correct") + } else { + treeWeight += edge.weight!! } + } - newGraph.addVertex(addedVertex!!.id, addedVertex.data) - newGraph.addEdge(pairForAddedEdge!!, minWeight) + } - return newGraph + return treeWeight / 2 + } } From 590c423b99c720edf101ea7f0141d42ce931bf0d Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Mon, 12 Aug 2024 18:59:32 +0300 Subject: [PATCH 035/131] test: rewrote all tests for Prim's algorithm and added file with auxiliary function for testing --- src/test/kotlin/PrimTest.kt | 143 +++++++++++++++++------------------- src/test/kotlin/forTests.kt | 12 +++ 2 files changed, 78 insertions(+), 77 deletions(-) create mode 100644 src/test/kotlin/forTests.kt diff --git a/src/test/kotlin/PrimTest.kt b/src/test/kotlin/PrimTest.kt index 720a68f..f26c074 100644 --- a/src/test/kotlin/PrimTest.kt +++ b/src/test/kotlin/PrimTest.kt @@ -1,104 +1,93 @@ import model.algorithms.Prim import model.graph.UndirectedGraph -import model.graph.DirectedGraph -import model.graph.Edge -import model.graph.Vertex +import model.graph.* import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals +import forTests.addVertices -class PrimTest{ //4 TESTS +class PrimTest { + + private val undirGraph = UndirectedGraph() + private var primGraph = Prim(undirGraph) + + @Test + fun `construction of MST from a graph consisting of a single vertex must be correct`() { + + addVertices(undirGraph, 1) + + assertEquals(0, primGraph.weightPrim()) + + } @Test - fun `a graph consists of two vertices and one edge`() { - val initGraph = UndirectedGraph() + fun `construction of MST from 2 components must be correct`() { - initGraph.addVertex(1, 1) - initGraph.addVertex(2, 2) + addVertices(undirGraph, 5) + undirGraph.run { + addEdge(1 to 2, 23) + addEdge(2 to 3, 3) + addEdge(1 to 3, 41) + addEdge(4 to 5, 12) + } - initGraph.addEdge(1 to 2, 9) - val mstGraph = MST(initGraph) - val MSTree : UndirectedGraph = mstGraph.getMST() - assertEquals( listOf(Edge(2 to 1, 9),Edge(1 to 2, 9)), MSTree.edges ) - assertEquals( setOf(Vertex(1,1),Vertex(2,2)), setOf( MSTree.vertices ) ) + assertEquals(38, primGraph.weightPrim()) } @Test - fun `a triangle-shaped graph, checking for the content of cycles in the MSTree`() { - val initGraph = UndirectedGraph() + fun `construction of MST from tree must be correct`() { //when building a MST from a tree, we need to get the original tree - initGraph.addVertex(1, 1) - initGraph.addVertex(2, 2) - initGraph.addVertex(3,3) + addVertices(undirGraph, 4) - initGraph.addEdge(1 to 2, 10) - initGraph.addEdge(1 to 3, 5) - initGraph.addEdge(2 to 3, 1) + undirGraph.run { + addEdge(3 to 1, 3) + addEdge(3 to 2, 8) + addEdge(3 to 4, 21) + } - val graph = MST(initGraph) - val MSTree : UndirectedGraph = graph.getMST() - val weightsEdgesOfMST = mutableListOf() - (MSTree.edges).forEach{ weightsEdgesOfMST.add(it.weight!!) } - assertEquals(2,weightsEdgesOfMST.size) // amount of edges - assertEquals(setOf(1,5),setOf(weightsEdgesOfMST)) // edge values - assertEquals( setOf(Vertex(1,1),Vertex(2,2),Vertex(3,3)), setOf( MSTree.vertices ) ) + assertEquals(32, primGraph.weightPrim()) } @Test - fun `a graph in the form of a square with diagonals and a vertex in the middle`() { - val initGraph = UndirectedGraph() - - initGraph.addVertex(1,1) - initGraph.addVertex(2,2) - initGraph.addVertex(3,3) - initGraph.addVertex(4,4) - initGraph.addVertex(5,5) - - initGraph.addEdge(1 to 2,9) - initGraph.addEdge(1 to 4,3) - initGraph.addEdge(1 to 5,0) - initGraph.addEdge(2 to 4,13) - initGraph.addEdge(2 to 3,47) - initGraph.addEdge(3 to 4,31) - initGraph.addEdge(3 to 5,2) - initGraph.addEdge(5 to 4,6) - - val graph = MST(initGraph) - val MSTree : UndirectedGraph = graph.getMST() - val weightsEdgesOfMST = mutableListOf() - (MSTree.edges).forEach{ weightsEdgesOfMST.add(it.weight!!) } - assertEquals(setOf(0,2,3,9),setOf(weightsEdgesOfMST)) - assertEquals(true,weightsEdgesOfMST.groupBy { it }.values.map { it.size }.all{ it == 2 }) + fun `construction of MST from graph consisting of edges with equal weight must be correct`() { + + addVertices(undirGraph, 4) + + undirGraph.run { + addEdge(1 to 2, 20) + addEdge(1 to 4, 20) + addEdge(3 to 2, 20) + addEdge(3 to 4, 20) + } + + assertEquals(60, primGraph.weightPrim()) } @Test - fun `a graph in the form of a square with diagonals and a vertex in the middle with the same weight`() { - val initGraph = UndirectedGraph() - - initGraph.addVertex(1,1) - initGraph.addVertex(2,2) - initGraph.addVertex(3,3) - initGraph.addVertex(4,4) - initGraph.addVertex(5,5) - - initGraph.addEdge(1 to 2,3) - initGraph.addEdge(1 to 4,3) - initGraph.addEdge(1 to 5,3) - initGraph.addEdge(2 to 4,3) - initGraph.addEdge(2 to 3,3) - initGraph.addEdge(3 to 4,3) - initGraph.addEdge(3 to 5,3) - initGraph.addEdge(5 to 4,3) - - val graph = MST(initGraph) - val MSTree : UndirectedGraph = graph.getMST() - val weightsEdgesOfMST = mutableListOf() - (MSTree.edges).forEach{ weightsEdgesOfMST.add(it.weight!!) } - assertEquals(3,setOf(weightsEdgesOfMST)) - assertEquals(4,weightsEdgesOfMST.size) + fun `construction of MST from non-trivial(more complex) graph must be correct`() { + + addVertices(undirGraph, 10) + + undirGraph.run { + addEdge(1 to 2, 5) + addEdge(2 to 3, 11) + addEdge(3 to 4, 25) + addEdge(4 to 8, 24) + addEdge(1 to 4, 1) + addEdge(2 to 5, 42) + addEdge(5 to 3, 11) + addEdge(5 to 4, 54) + addEdge(8 to 9, 73) + addEdge(6 to 7, 130) + addEdge(7 to 9, 4) + addEdge(8 to 7, 9) + addEdge(8 to 10, 42) + } + + assertEquals(237, primGraph.weightPrim()) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/forTests.kt b/src/test/kotlin/forTests.kt new file mode 100644 index 0000000..343ee64 --- /dev/null +++ b/src/test/kotlin/forTests.kt @@ -0,0 +1,12 @@ +package forTests + +import model.graph.Graph + +fun addVertices(graph: Graph, amount: Int) { + + var counter = amount + while (counter > 0) { + graph.addVertex(counter, counter) + counter-- + } +} From e2af46520d60fad017e629fb57948b4f79529443 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 14 Aug 2024 12:59:54 +0300 Subject: [PATCH 036/131] feat: added KDoc documentation for Prim's algorithm --- src/main/kotlin/model/algorithms/Prim.kt | 56 ++++++++++++++++++++---- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/model/algorithms/Prim.kt b/src/main/kotlin/model/algorithms/Prim.kt index cdbfc57..f663bc8 100644 --- a/src/main/kotlin/model/algorithms/Prim.kt +++ b/src/main/kotlin/model/algorithms/Prim.kt @@ -2,8 +2,29 @@ package model.algorithms import model.graph.UndirectedGraph +/** + * The class [Prim] implements the Prim's algorithm for construction Minimum spanning tree (MST) from + * undirected weighted graph. + * + * At the same time, if the graph consists of several connectivity components, + * the algorithm constructs a forest, each tree of which is MST. + * @param D input type + * @property [graph] a undirected weighted graph, whose MST we want to construct + * @constructor Creates a graph, based on [graph], to which the following functions can be applied: + * function [treePrim] : returns MST for [graph] + * function [weightPrim] : returns the weight of received MST + */ + class Prim(private val graph: UndirectedGraph) { + /** + * This function implements Prim's algorithm for + * constuction MST for transmitted connectivity component + * + * @param graph a connectivity component, + * @return MST of connectivity component + * @receiver fun [treePrim] + */ private fun getMST(graph: UndirectedGraph): UndirectedGraph { val graphMst = UndirectedGraph() @@ -55,6 +76,17 @@ class Prim(private val graph: UndirectedGraph) { } + /** + * The function finds the connectivity component + * and returns it to apply the Prim's algorithm to it + * + * @param idVertex init vertex for dfs + * @param visited stores information about which vertices of the graph + * have already been processed by the Prim's algorithm + * @param component found connectivity component of the graph + * @return found connectivity component + * @receiver fun [treePrim] + */ private fun dfs( idVertex: Int, visited: HashMap, @@ -77,7 +109,14 @@ class Prim(private val graph: UndirectedGraph) { return component } - private fun privateTreePrim(): MutableList> { + /** + * The function finds the connectivity components of the graph + * and then builds MST for each of them + * + * @return list of MST for each connectivity component of graph + * @receiver fun [weightPrim] + */ + fun treePrim(): MutableList> { val returnListOfMST = mutableListOf>() @@ -91,7 +130,7 @@ class Prim(private val graph: UndirectedGraph) { while (visited.values.contains(false)) { initIndex = visited.filterValues { !it }.keys.first() - component = UndirectedGraph() + component = UndirectedGraph() component.addVertex(initIndex, graph.vertices[initIndex]!!.data) component = dfs(initIndex, visited, component) returnListOfMST.add(getMST(component)) @@ -102,16 +141,15 @@ class Prim(private val graph: UndirectedGraph) { } - fun treePrim(): MutableList> { - - return privateTreePrim() - - } - + /** + * Counts the weight of received MST + * + * @return weight of MST + */ fun weightPrim(): Int { var treeWeight: Int = 0 - for (element in privateTreePrim()) { + for (element in treePrim()) { for (edge in element.edges) { if (edge.weight == null) { From 4487150304823d93588b21e21a24535dd57107e5 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 14 Aug 2024 13:12:37 +0300 Subject: [PATCH 037/131] feat Prim: deleted unnecessary import --- src/test/kotlin/PrimTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/kotlin/PrimTest.kt b/src/test/kotlin/PrimTest.kt index f26c074..a98dc1f 100644 --- a/src/test/kotlin/PrimTest.kt +++ b/src/test/kotlin/PrimTest.kt @@ -1,6 +1,5 @@ import model.algorithms.Prim import model.graph.UndirectedGraph -import model.graph.* import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals import forTests.addVertices From 86b396bfcca6c65f45f66121ebe6ed91dd8abf2e Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 14 Aug 2024 13:30:06 +0300 Subject: [PATCH 038/131] ci: added workflow for setting ci --- .github/workflows/CI.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/CI.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..0835d93 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,27 @@ +name: Test + +on: + pull_request: + paths: + - '**.kt' + - '**.yml' + push: + paths: + - '**.kt' + - '**.yml' + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: zulu + - name: Build + run: ./gradlew build -x test + - name: Test + run: ./gradlew test From 5ed31965add0c3e4869d9bb4055cf7dca6dea77a Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 14 Aug 2024 13:38:27 +0300 Subject: [PATCH 039/131] ci: changed permission of gradlew --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From b8f0f53b9b70746e7fbec7e36d486e9eb5c7495f Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 14 Aug 2024 13:47:58 +0300 Subject: [PATCH 040/131] ci: trigger ci --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0835d93..c262231 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,4 +1,4 @@ -name: Test +name: Test # autorun of the project build and tests on: pull_request: From 57d09707c17b4351908fde26c9669e23a0ed3f10 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 14 Aug 2024 14:01:22 +0300 Subject: [PATCH 041/131] ci: changed the trigger to activate the workflow --- .github/workflows/CI.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c262231..2484bc0 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -4,11 +4,10 @@ on: pull_request: paths: - '**.kt' - - '**.yml' push: paths: - '**.kt' - - '**.yml' + workflow_dispatch: jobs: test: From 45196ffab479d9afdac506907b47f6cfa8a7a2ff Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 14 Aug 2024 14:14:40 +0300 Subject: [PATCH 042/131] ci: configured workflow_dispatch trigger --- .github/workflows/CI.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2484bc0..1bd9031 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,14 +1,13 @@ name: Test # autorun of the project build and tests on: + workflow_dispatch: pull_request: paths: - '**.kt' push: paths: - '**.kt' - workflow_dispatch: - jobs: test: runs-on: ubuntu-latest From af644788c3268d1e8984bf55be563ff46f3a69d8 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Fri, 16 Aug 2024 17:12:40 +0300 Subject: [PATCH 043/131] remade CycleSearch algorithm --- .../kotlin/model/algorithms/CycleSearch.kt | 130 ++++++++++++------ 1 file changed, 86 insertions(+), 44 deletions(-) diff --git a/src/main/kotlin/model/algorithms/CycleSearch.kt b/src/main/kotlin/model/algorithms/CycleSearch.kt index 86a3992..ba1a717 100644 --- a/src/main/kotlin/model/algorithms/CycleSearch.kt +++ b/src/main/kotlin/model/algorithms/CycleSearch.kt @@ -1,68 +1,110 @@ package model.algorithms -import model.graph.Graph import model.graph.Edge +import model.graph.UndirectedGraph +import model.graph.Vertex -class CycleSearch(private val graph: Graph) { +class CycleSearch(private val graph: UndirectedGraph) { - fun findCycle(cycleVertexId: Int): List>? { + private fun getCyclePath(cyclePath: HashMap, cycleVertexId: Int): HashMap { - if (cycleVertexId !in graph.vertices.keys) { - throw IllegalArgumentException("Vertex with index = $cycleVertexId doesn't exist in the graph. The cycle can't be found") - } + var current: Int = cycleVertexId + var next: Int + val returnCyclePath = HashMap() + do { + next = cyclePath.filter { it.key == current }.values.first() + returnCyclePath[current] = next + current = next + } while (current != cycleVertexId) - val visited = HashMap() - for (vertexId in graph.vertices.keys) { - visited[vertexId] = false - } - val cycleWayFrom = - HashMap() - val cycleIsFound = Array(1) { false } - val returnList = mutableListOf>() - - dfs(cycleVertexId, visited, cycleWayFrom, cycleVertexId, cycleIsFound, returnList) - if (cycleIsFound[0]) { - return returnList - } - return null + return returnCyclePath } private fun dfs( - vertexId: Int, - visited: HashMap, - cycleWayFrom: HashMap, cycleVertexId: Int, - cycleIsFound: Array, - returnList: MutableList> + currentVertexId: Int, + visited: HashMap, + cyclePath: HashMap ) { - visited[vertexId] = true - - if (!cycleIsFound[0]) { - for (adjacencyVertexId in graph.adjacency[vertexId]!!.keys) { - - if (visited[adjacencyVertexId] == false) { - cycleWayFrom[adjacencyVertexId] = vertexId - dfs(adjacencyVertexId, visited, cycleWayFrom, cycleVertexId, cycleIsFound, returnList) - } else if (adjacencyVertexId == cycleVertexId && adjacencyVertexId != cycleWayFrom[vertexId]) { - cycleWayFrom[cycleVertexId] = vertexId - var id = cycleWayFrom.keys.last() - while (id != cycleVertexId) { - returnList.add(Edge(cycleWayFrom[id]!! to id, graph.adjacency[cycleWayFrom[id]]!![id])) - id = cycleWayFrom[id]!! - } - returnList.add(Edge(cycleWayFrom[id]!! to id, graph.adjacency[cycleWayFrom[id]]!![id])) - cycleIsFound[0] = true - } + for (idAdjacency in graph.adjacency[currentVertexId]!!.keys) { + if (visited[idAdjacency] == false) { + visited[idAdjacency] = true + cyclePath[currentVertexId] = idAdjacency + dfs(cycleVertexId, idAdjacency, visited, cyclePath) + } else if (idAdjacency == cycleVertexId && cyclePath[cycleVertexId] != currentVertexId) { + cyclePath[currentVertexId] = idAdjacency + return } } } + private fun findAnyCycle(vertexId: Int): HashMap? { -} + val devCyclePath = HashMap() + val visited = hashMapOf() + for (idVertex in graph.vertices.keys) { + visited[idVertex] = false + } + visited[vertexId] = true + dfs(vertexId, vertexId, visited, devCyclePath) + if (devCyclePath.filter { it.value == vertexId }.isEmpty()) { + return null + } + return getCyclePath(devCyclePath, vertexId) + } + + fun findCycle(vertex: Vertex): UndirectedGraph? { + var returnCyclePath = hashMapOf() + var currentCyclePath: HashMap? + var minCycleSize: Int = Int.MAX_VALUE + if (vertex.id !in graph.vertices.keys) { + throw IllegalArgumentException("Vertex with id = ${vertex.id} doesn't exist in the graph") + } + val returnGraph = UndirectedGraph() + if (graph.adjacency[vertex.id]!!.size < 2) { + return null + } else { // we consider all possible cases of a cycle by choosing a pair of neighbors to minimize the found cycle + val adjacencyOfVertex: MutableList = arrayListOf() + graph.adjacency[vertex.id]!!.keys.forEach { adjacencyOfVertex.add(it) } + val adjacencyWas: MutableList = arrayListOf() + val removedEdges: MutableList> = arrayListOf() + for (firstAdjacency in adjacencyOfVertex) { + adjacencyWas.add(firstAdjacency) + for (secondAdjacency in adjacencyOfVertex.filter { it !in adjacencyWas && it != vertex.id }) { + + for (adjacency in graph.adjacency[vertex.id]!!.filter { it.key != firstAdjacency && it.key != secondAdjacency }) { + removedEdges.add(Edge(vertex.id to adjacency.key, adjacency.value)) + graph.removeEdge(vertex.id to adjacency.key, adjacency.value) + } + + currentCyclePath = findAnyCycle(vertex.id) + if (currentCyclePath != null && currentCyclePath.size < minCycleSize) { + minCycleSize = currentCyclePath.size + returnCyclePath = currentCyclePath + } + + for (edge in removedEdges) { + graph.addEdge(edge.vertices, edge.weight) + } + removedEdges.clear() + } + } + } + + if (minCycleSize == Int.MAX_VALUE) { + return null + } + returnCyclePath.forEach { returnGraph.addVertex(it.key, graph.vertices[it.key]!!.data) } + returnCyclePath.forEach { returnGraph.addEdge(it.key to it.value, graph.adjacency[it.key]!![it.value]) } + return returnGraph + + } + +} From 503597762bdc277efa8a48019d9cabb4bf8a9967 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Fri, 16 Aug 2024 17:20:37 +0300 Subject: [PATCH 044/131] test: rewrote all tests for CycleSearch algorithm --- src/test/kotlin/CycleSearchTest.kt | 164 ++++++++++------------------- 1 file changed, 55 insertions(+), 109 deletions(-) diff --git a/src/test/kotlin/CycleSearchTest.kt b/src/test/kotlin/CycleSearchTest.kt index e96eab2..ba58900 100644 --- a/src/test/kotlin/CycleSearchTest.kt +++ b/src/test/kotlin/CycleSearchTest.kt @@ -1,142 +1,88 @@ import model.algorithms.CycleSearch import model.graph.UndirectedGraph -import model.graph.DirectedGraph -import model.graph.Edge +import model.graph.Vertex import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals +import forTests.addVertices +import kotlin.test.assertFailsWith -class CycleSearchTest { //6 TESTS +class CycleSearchTest { - //region UNDIRECTED GRAPH (3) + private var graph = UndirectedGraph() + private var cycleGraph = CycleSearch(graph) @Test - fun ` a triangle-shaped undirected graph wiht cycle `() { - val initGraph = UndirectedGraph() - - initGraph.addVertex(1, 1) - initGraph.addVertex(2, 2) - initGraph.addVertex(3, 3) - - initGraph.addEdge(1 to 2, 10) - initGraph.addEdge(2 to 3, 5) - initGraph.addEdge(3 to 1, 1) - - val graph = CycleSearch(initGraph) - val cycleEdges: List>? = graph.findCycle(1) - assertEquals(true, cycleEdges != null) - for (edge in initGraph.edges) { - assertEquals(true, edge in cycleEdges!!) - } - for (edge in cycleEdges!!) { - assertEquals(true, edge in initGraph.edges) - } - } - - @Test - fun ` undirected graph consisted of 4 vertices without cycle`() { - val initGraph = UndirectedGraph() - - initGraph.addVertex(1, 1) - initGraph.addVertex(2, 2) - initGraph.addVertex(3, 3) - initGraph.addVertex(4, 4) + fun `passing as function argument a non-existent vertex should throw an exception`() { - initGraph.addEdge(1 to 2, 10) - initGraph.addEdge(1 to 3, 5) - initGraph.addEdge(1 to 4, 1) - - val graph = CycleSearch(initGraph) - val cycleEdges: List>? = graph.findCycle(1) - assertEquals(null, cycleEdges) + addVertices(graph, 3) + assertFailsWith { cycleGraph.findCycle(Vertex(4, 4)) } } @Test - fun ` the cycle of the undirected graph exists, but not around the input vertex `() { - val initGraph = UndirectedGraph() - - initGraph.addVertex(1, 1) - initGraph.addVertex(2, 2) - initGraph.addVertex(3, 3) - initGraph.addVertex(4, 4) - - initGraph.addEdge(1 to 2, 10) - initGraph.addEdge(2 to 3, 5) - initGraph.addEdge(3 to 4, 3) - initGraph.addEdge(4 to 2, 1) + fun `search for a cycle at a single vertex must be correct`() { - val graph = CycleSearch(initGraph) - val cycleEdges: List>? = graph.findCycle(1) - assertEquals(null, cycleEdges) + addVertices(graph, 1) + assertEquals(null, cycleGraph.findCycle(graph.vertices[1]!!)) } - //endregion - - //region DIRECTED GRAPH (3) - @Test - fun ` a triangle-shaped directed graph whith cycle `() { - val initGraph = DirectedGraph() - - initGraph.addVertex(1, 1) - initGraph.addVertex(2, 2) - initGraph.addVertex(3, 3) - - initGraph.addEdge(1 to 2, 10) - initGraph.addEdge(2 to 3, 5) - initGraph.addEdge(3 to 1, 1) - - val graph = CycleSearch(initGraph) - val cycleEdges: List>? = graph.findCycle(1) - assertEquals(true, cycleEdges != null) - for (edge in initGraph.edges) { - assertEquals(true, edge in cycleEdges!!) - } - for (edge in cycleEdges!!) { - assertEquals(true, edge in initGraph.edges) + fun `search for a cycle at the graph that is a tree must be correct`() { + + addVertices(graph, 4) + graph.run { + addEdge(1 to 2, 23) + addEdge(2 to 3, 12) + addEdge(4 to 2, 4) } - // проверили равенство наборов ребер у первоначального графа и у цикла + assertEquals(null, cycleGraph.findCycle(graph.vertices[1]!!)) + } @Test - fun ` a triangle-shaped directed graph without cycle `() { - val initGraph = DirectedGraph() - - initGraph.addVertex(1, 1) - initGraph.addVertex(2, 2) - initGraph.addVertex(3, 3) + fun `search for a cycle at the graph that is a cycle must be correct`() { - initGraph.addEdge(1 to 2, 10) - initGraph.addEdge(1 to 3, 5) - initGraph.addEdge(2 to 3, 1) - - val graph = CycleSearch(initGraph) - val cycleEdges: List>? = graph.findCycle(1) - assertEquals(null, cycleEdges) + addVertices(graph, 3) + graph.run { + addEdge(1 to 2, 23) + addEdge(2 to 3, 12) + addEdge(1 to 3, 4) + } + cycleGraph.findCycle(graph.vertices[1]!!) } @Test - fun ` the cycle of the directed graph exists, but not around the input vertex `() { - val initGraph = DirectedGraph() - - initGraph.addVertex(0, 0) - initGraph.addVertex(1, 1) - initGraph.addVertex(2, 2) - initGraph.addVertex(3, 3) + fun `search for a cycle at the graph around vertex with many adjacency must be correct`() { + + addVertices(graph, 6) + graph.run { + addEdge(1 to 2, 1) + addEdge(2 to 3, 2) + addEdge(2 to 5, 5) + addEdge(2 to 6, 7) + addEdge(3 to 4, 3) + addEdge(4 to 5, 4) + addEdge(5 to 6, 6) + } + assertEquals(true, cycleGraph.findCycle(graph.vertices[2]!!) != null) - initGraph.addEdge(0 to 1, 10) - initGraph.addEdge(1 to 2, 7) - initGraph.addEdge(2 to 3, 5) - initGraph.addEdge(3 to 1, 1) + } - val graph = CycleSearch(initGraph) - val cycleEdges: List>? = graph.findCycle(0) - assertEquals(null, cycleEdges) + @Test + fun `search for a cycle at the graph containing cycle, but around vertex without cycle must be correct`() { + + addVertices(graph, 5) + graph.run { + addEdge(1 to 2, 13) + addEdge(2 to 3, 21) + addEdge(3 to 1, 67) + addEdge(3 to 4, 41) + addEdge(4 to 5, 10) + } + assertEquals(null, cycleGraph.findCycle(graph.vertices[4]!!)) } - //endregion - } From 57f7caa45aa7cd32244f411343d738fa5b6a1a05 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Fri, 16 Aug 2024 18:55:03 +0300 Subject: [PATCH 045/131] feat: added KDoc documentation for CycleSearch algorithm --- .../kotlin/model/algorithms/CycleSearch.kt | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/model/algorithms/CycleSearch.kt b/src/main/kotlin/model/algorithms/CycleSearch.kt index ba1a717..455b550 100644 --- a/src/main/kotlin/model/algorithms/CycleSearch.kt +++ b/src/main/kotlin/model/algorithms/CycleSearch.kt @@ -4,8 +4,31 @@ import model.graph.Edge import model.graph.UndirectedGraph import model.graph.Vertex +/** + * The class [CycleSearch] implements the algorithm for finding a cycle around + * selected vertex of the undirected graph. + * + * At the same time, the algorithm has some minimization of the desired cycle + * by iterating through possible pairs of neighbors of the selected vertex + * through which the desired cycle will pass. + * @param D input type + * @property [graph] a undirected graph for whose vertex we will search for a cycle + * @constructor Creates a graph, based on [graph],for which it will be possible to apply + * a cycle search algorithm around the vertex selected in it. + */ + class CycleSearch(private val graph: UndirectedGraph) { + /** + * This auxiliary function for the function [findAnyCycle] aims to get from + * some hashmap with extra elements the hashmap the elements of which + * accurately describe found cycle path. + * + * @param cyclePath hashmap with extra elements + * @param cycleVertexId the index of the vertex around which the cycle must be found + * @return cycle path + * @receiver private fun [findAnyCycle] + */ private fun getCyclePath(cyclePath: HashMap, cycleVertexId: Int): HashMap { var current: Int = cycleVertexId @@ -21,6 +44,17 @@ class CycleSearch(private val graph: UndirectedGraph) { } + /**Searches for a cycle by dfs algorithm and writes it into [cyclePath] + * + * + * @param cycleVertexId the index of the vertex around which the cycle must be found + * @param currentVertexId using for recording the [cyclePath] + * @param visited stores information about which vertices of the graph + * have already been processed by [dfs] + * @param cyclePath hashmap consisting of elements that can be used to restore the found cycle path + * by applying the auxiliary function [getCyclePath] to it in function [findAnyCycle]. + * @receiver private fun [findAnyCycle] + */ private fun dfs( cycleVertexId: Int, currentVertexId: Int, @@ -41,6 +75,12 @@ class CycleSearch(private val graph: UndirectedGraph) { } } + /** Finds any existing cycle in the graph around a given vertex + * + * @param vertexId the index of the vertex around which the cycle must be found + * @return found cycle path or null if the cycle doesn't exist around vertex with id = [vertexId] + * @receiver fun [findCycle] + */ private fun findAnyCycle(vertexId: Int): HashMap? { val devCyclePath = HashMap() @@ -57,7 +97,19 @@ class CycleSearch(private val graph: UndirectedGraph) { return getCyclePath(devCyclePath, vertexId) } - + + /** + * The function implements the algorithm for finding a cycle around + * vertex with id = [vertex] of the undirected graph. + * + * The function searches for a cycle and performs some minimization of it. + * During the cycle minimization we iterate through variants of cycle, + * based on the choice of a pair of neighbors of [vertex] through which the cycle will pass. + * Each of the found cycle variants is written to a variable 'currentCyclePath' + * and its size compared with 'minCycleSize'. + * @param vertex the vertex around which the cycle must be found + * @return cycle path or null if it doesn't exist + */ fun findCycle(vertex: Vertex): UndirectedGraph? { var returnCyclePath = hashMapOf() From e5687cd027170ebfe3674034e8c9e70c14605f40 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Fri, 16 Aug 2024 21:32:01 +0300 Subject: [PATCH 046/131] feat: added functions of removing edge and removing vertex for graph-model --- src/main/kotlin/model/graph/DirectedGraph.kt | 37 ++++++++++++++++++- src/main/kotlin/model/graph/Graph.kt | 4 +- .../kotlin/model/graph/UndirectedGraph.kt | 34 ++++++++++++++++- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/model/graph/DirectedGraph.kt b/src/main/kotlin/model/graph/DirectedGraph.kt index 95cf1b6..4419964 100644 --- a/src/main/kotlin/model/graph/DirectedGraph.kt +++ b/src/main/kotlin/model/graph/DirectedGraph.kt @@ -12,4 +12,39 @@ class DirectedGraph() : Graph() { edges.add(Edge(Pair(v.first, v.second), w)) adjacency.getOrPut(v.first) { hashMapOf() }[v.second] = w } -} \ No newline at end of file + + override fun removeEdge(v: Pair, w: Int?) { + + if (!vertices.containsKey(v.first)) { + throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") + } + if (!vertices.containsKey(v.second)) { + throw IllegalArgumentException("Vertex with id: ${v.second} not exists in the graph.") + } + if (v.second !in adjacency[v.first]!!.keys) { + throw IllegalArgumentException("Edge from ${v.first} to ${v.second} not exists in the graph.") + } + + edges.remove(Edge(Pair(v.first, v.second), w)) + adjacency[v.first]!!.remove(v.second) + + } + + override fun removeVertex(id: Int) { + if (!vertices.containsKey(id)) { + throw IllegalArgumentException("Vertex with id: $id doesn't exist in the graph.") + } + if(adjacency[id]!!.isNotEmpty()){ + for(idAdjacency in adjacency[id]!!.keys) { + removeEdge(id to idAdjacency, adjacency[id]!![idAdjacency]) + } + } + if(adjacency.filterValues{ it.keys.contains(id)}.isNotEmpty()){ + for(adj in adjacency.filterValues{ it.keys.contains(id)}) { + removeEdge(adj.key to id, adjacency[adj.key]!![id]) + } + } + vertices.remove(id) + } + +} diff --git a/src/main/kotlin/model/graph/Graph.kt b/src/main/kotlin/model/graph/Graph.kt index 39db4e0..cae98b3 100644 --- a/src/main/kotlin/model/graph/Graph.kt +++ b/src/main/kotlin/model/graph/Graph.kt @@ -7,6 +7,8 @@ abstract class Graph { val edges = mutableListOf>() fun getVertices(): Collection> = vertices.values abstract fun addEdge(v: Pair, w: Int?) + abstract fun removeEdge(v: Pair, w: Int?) + abstract fun removeVertex(id: Int) fun addVertex(id: Int, data: D) { if (vertices.containsKey(id)) { throw IllegalArgumentException("Vertex with id: $id already exists in the graph.") @@ -14,5 +16,5 @@ abstract class Graph { vertices[id] = Vertex(id, data) adjacency[id] = hashMapOf() } -} +} diff --git a/src/main/kotlin/model/graph/UndirectedGraph.kt b/src/main/kotlin/model/graph/UndirectedGraph.kt index 221c7f7..43ab680 100644 --- a/src/main/kotlin/model/graph/UndirectedGraph.kt +++ b/src/main/kotlin/model/graph/UndirectedGraph.kt @@ -17,4 +17,36 @@ class UndirectedGraph() : Graph() { adjacency.getOrPut(v.first) { hashMapOf() }[v.second] = w adjacency.getOrPut(v.second) { hashMapOf() }[v.first] = w } -} \ No newline at end of file + + override fun removeEdge(v: Pair, w: Int?) { + + if (!vertices.containsKey(v.first)) { + throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") + } + if (!vertices.containsKey(v.second)) { + throw IllegalArgumentException("Vertex with id: ${v.second} not exists in the graph.") + } + if (v.second !in adjacency[v.first]!!.keys) { + throw IllegalArgumentException("Edge from ${v.first} to ${v.second} not exists in the graph.") + } + + edges.remove(Edge(Pair(v.second, v.first), w)) + edges.remove(Edge(Pair(v.first, v.second), w)) + adjacency[v.first]!!.remove(v.second) + adjacency[v.second]!!.remove(v.first) + + } + + override fun removeVertex(id: Int) { + if (!vertices.containsKey(id)) { + throw IllegalArgumentException("Vertex with id: $id doesn't exist in the graph.") + } + if(adjacency[id]!!.isNotEmpty()){ + for(idAdjacency in adjacency[id]!!.keys) { + removeEdge(id to idAdjacency, adjacency[id]!![idAdjacency]) + } + } + vertices.remove(id) + } + +} From 27814033a71a73c19a691279ec1eef6c4230a0e4 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Tue, 3 Sep 2024 09:42:28 +0300 Subject: [PATCH 047/131] feat: added harmonicCentrality algorithm --- .../model/algorithms/HarmonicCentrality.kt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/kotlin/model/algorithms/HarmonicCentrality.kt diff --git a/src/main/kotlin/model/algorithms/HarmonicCentrality.kt b/src/main/kotlin/model/algorithms/HarmonicCentrality.kt new file mode 100644 index 0000000..172deea --- /dev/null +++ b/src/main/kotlin/model/algorithms/HarmonicCentrality.kt @@ -0,0 +1,49 @@ +package model.algorithms + +import model.graph.DirectedGraph +import model.graph.Graph +import model.graph.UndirectedGraph +import kotlin.math.roundToInt + +class HarmonicCentrality(private val graph: Graph) { + + private fun roundTo(number: Double): Double { + return (number * 10000.0).roundToInt() / 10000.0 + } + + private fun getIndexEdge(getGraph: Graph, vertexId: Int): Double { + + var index: Double = 0.00 + + getGraph.vertices.filter { it.key != vertexId } + .forEach { + if (Dijkstra(getGraph).findShortestPaths(vertexId, it.key).isNotEmpty()) { + index += 1.0 / ((Dijkstra(getGraph).findShortestPaths(vertexId, it.key)).size - 1) + } + } + + return roundTo(index / (getGraph.vertices.size - 1)) + + } + + fun harmonicCentrality(): HashMap { + + val centralityIndexes = HashMap() + + val graphForCentrality: Graph = if (graph is UndirectedGraph) { + UndirectedGraph() + } else { + DirectedGraph() + } + graph.vertices.forEach { graphForCentrality.addVertex(it.key, it.value.data) } + graph.edges.forEach { graphForCentrality.addEdge(it.vertices, 1) } + + for (vertexId in graphForCentrality.vertices.keys) { + centralityIndexes[vertexId] = getIndexEdge(graphForCentrality, vertexId) + } + + return centralityIndexes + + } + +} From 5927fec3a6f7fbfdfd8ed6948489521e60d4e8b8 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Tue, 3 Sep 2024 09:49:28 +0300 Subject: [PATCH 048/131] test: added tests for HarmonicCentrality algorithm --- src/test/kotlin/HarmonicCentralityTest.kt | 128 ++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/test/kotlin/HarmonicCentralityTest.kt diff --git a/src/test/kotlin/HarmonicCentralityTest.kt b/src/test/kotlin/HarmonicCentralityTest.kt new file mode 100644 index 0000000..df53468 --- /dev/null +++ b/src/test/kotlin/HarmonicCentralityTest.kt @@ -0,0 +1,128 @@ +import forTests.addVertices +import model.algorithms.HarmonicCentrality +import model.graph.DirectedGraph +import model.graph.UndirectedGraph +import org.junit.jupiter.api.Test +import kotlin.math.roundToInt +import kotlin.test.assertEquals + +class HarmonicCentralityTest { + + private var undirGraph = UndirectedGraph() + private var dirGraph = DirectedGraph() + private var undirCentrality = HarmonicCentrality(undirGraph) + private var dirCentrality = HarmonicCentrality(dirGraph) + private fun roundTo(number: Double): Double { + return (number * 10000.0).roundToInt() / 10000.0 + } + + @Test + fun `indexes of single graph's vertices must be equal to zero`() { + + addVertices(undirGraph, 3) + undirGraph.addEdge(1 to 2, 23) + + assertEquals(0.0, (undirCentrality.harmonicCentrality())[3]) + + } + + @Test + fun `the index of the vertex connected with all vertices of the graph must be equal to 1`() { + + addVertices(undirGraph, 3) + undirGraph.addEdge(1 to 2, 23) + undirGraph.addEdge(1 to 3, 23) + + assertEquals(1.0, (undirCentrality.harmonicCentrality())[1]) + + } + + @Test + fun `result of function for simple graph must be correct`() { + + addVertices(undirGraph, 4) + undirGraph.run { + addEdge(1 to 2, null) + addEdge(2 to 3, null) + addEdge(3 to 4, null) + } + + undirCentrality.harmonicCentrality().let { + val expected = listOf(0.0, 11.0 / 18, 5.0 / 6, 5.0 / 6, 11.0 / 18) + for (index in 1..4) { + assertEquals(roundTo(expected[index]), it[index]!!) + } + } + + } + + @Test + fun `result of function for non-trivial(more complex) undirected graph must be correct`() { + + addVertices(undirGraph, 9) + + undirGraph.run { + addEdge(1 to 2, null) + addEdge(3 to 2, null) + addEdge(6 to 2, null) + addEdge(6 to 4, null) + addEdge(6 to 5, null) + addEdge(6 to 3, null) + addEdge(4 to 5, null) + addEdge(5 to 3, null) + addEdge(5 to 8, null) + addEdge(1 to 8, null) + addEdge(1 to 6, null) + addEdge(7 to 9, null) + } + + undirCentrality.harmonicCentrality().let { + val expected = listOf(0.0, 4.5 / 8, 4.5 / 8, 4.5 / 8, 0.5, 5.0 / 8, 11.0 / 16, 1.0 / 8, 0.5, 0.125) + for (index in 1..9) { + assertEquals(roundTo(expected[index]), it[index]) + } + } + + } + + @Test + fun `result of function for non-trivial(more complex) directed graph must be correct`() { + + addVertices(dirGraph, 9) + + dirGraph.run { + addEdge(1 to 3, null) + addEdge(2 to 1, null) + addEdge(2 to 3, null) + addEdge(4 to 3, null) + addEdge(5 to 3, null) + addEdge(3 to 8, null) + addEdge(3 to 9, null) + addEdge(5 to 9, null) + addEdge(8 to 7, null) + addEdge(8 to 9, null) + addEdge(9 to 7, null) + addEdge(7 to 6, null) + } + + dirCentrality.harmonicCentrality().let { + val expected = listOf( + 0.0, + 31.0 / (12 * 8), + 43.0 / (12 * 8), + 17.0 / 48, + 31.0 / (12 * 8), + 10.0 / 24, + 0.0, + 1.0 / 8, + 2.5 / 8, + 3.0 / 16 + ) + for (index in 1..9) { + assertEquals(roundTo(expected[index]), it[index]) + } + } + + } + +} From 12c5a167591250e8fa84f626c6c6be22214aa3aa Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Tue, 3 Sep 2024 09:53:37 +0300 Subject: [PATCH 049/131] feat: added KDoc documentation for HarmonicCentrality algorithm --- .../model/algorithms/HarmonicCentrality.kt | 61 ++++++++++++++++--- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/model/algorithms/HarmonicCentrality.kt b/src/main/kotlin/model/algorithms/HarmonicCentrality.kt index 172deea..dc5515e 100644 --- a/src/main/kotlin/model/algorithms/HarmonicCentrality.kt +++ b/src/main/kotlin/model/algorithms/HarmonicCentrality.kt @@ -5,41 +5,82 @@ import model.graph.Graph import model.graph.UndirectedGraph import kotlin.math.roundToInt +/** The class [HarmonicCentrality] implements the normalized harmonic centrality algorithm for + * a graph of any kind. For implementation using the Dijkstra algorithm. + * + * Harmonic centrality is a kind of closeness centrality, but the difference is that + * this algorithm is relevant not only for connected graphs, but also for disconnected ones. + * The harmonic centrality index for each vertex of the graph is calculated using the formula: + * Index = sum(1/the length of the shortest path to i-th vertex), + * for i in 1..n-1, where 'n' is amount of graph vertices. + * So that the index value lies in the interval from 0 to 1, we use the normalization of the centrality index + * by dividing the result by n, where 'n' is amount of graph vertices: + * Index = sum(1/the length of the shortest path to i-th vertex) / n, + * for i in 1..n-1, where 'n' is amount of graph vertices. + * In this case the length of the path is the amount of edges of this path. + * + * @param D input type + * @property [graph] a graph (of any kind) for the vertices of which it is necessary to calculate the centrality index + * @constructor Creates a graph, based on [graph], for which the algorithm for calculating the + * normalized harmonic centrality can be applied. + */ + class HarmonicCentrality(private val graph: Graph) { + /** + * This auxiliary function for the function [getIndex], that rounds + * the value [number] to the 4th digit after the decimal point. + * + * @return result of rounding + * @receiver [getIndex] + */ private fun roundTo(number: Double): Double { return (number * 10000.0).roundToInt() / 10000.0 } - private fun getIndexEdge(getGraph: Graph, vertexId: Int): Double { + /** + * This function calculate the centrality index for vertex with id = [vertexId]. + * + * @param graph graph for the vertex of which we calculate the centrality index. + * But weight of each graph edge is 1, what is used for applying Dijkstra algorithm. + * @param vertexId the index of the vertex for which calculating the centrality index. + * @return centrality index of vertex with id = [vertexId] + * @receiver [harmonicCentrality] + */ + private fun getIndex(graph: Graph, vertexId: Int): Double { - var index: Double = 0.00 + var index = 0.00 - getGraph.vertices.filter { it.key != vertexId } + graph.vertices.filter { it.key != vertexId } .forEach { - if (Dijkstra(getGraph).findShortestPaths(vertexId, it.key).isNotEmpty()) { - index += 1.0 / ((Dijkstra(getGraph).findShortestPaths(vertexId, it.key)).size - 1) + if (Dijkstra(graph).findShortestPaths(vertexId, it.key).isNotEmpty()) { + index += 1.0 / ((Dijkstra(graph).findShortestPaths(vertexId, it.key)).size - 1) } } - return roundTo(index / (getGraph.vertices.size - 1)) + return roundTo(index / (graph.vertices.size - 1)) } + /** + * This function calculates the centrality index for each graph vertex. + * + * @return hashmap, where key = vertex id; value = centrality index. + */ fun harmonicCentrality(): HashMap { val centralityIndexes = HashMap() - val graphForCentrality: Graph = if (graph is UndirectedGraph) { - UndirectedGraph() + val graphForCentrality: Graph = if (graph is UndirectedGraph) { + UndirectedGraph() } else { - DirectedGraph() + DirectedGraph() } graph.vertices.forEach { graphForCentrality.addVertex(it.key, it.value.data) } graph.edges.forEach { graphForCentrality.addEdge(it.vertices, 1) } for (vertexId in graphForCentrality.vertices.keys) { - centralityIndexes[vertexId] = getIndexEdge(graphForCentrality, vertexId) + centralityIndexes[vertexId] = getIndex(graphForCentrality, vertexId) } return centralityIndexes From 7a56ca4d7dc67e3bb05458d11bd2f5152f01d02e Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Sun, 22 Sep 2024 15:56:38 +0300 Subject: [PATCH 050/131] style: formatted code --- src/main/kotlin/viewmodel/MainScreenViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 5de6ab2..7510263 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -6,7 +6,7 @@ import model.graph.Graph import viewmodel.graph.GraphViewModel import viewmodel.graph.RepresentationStrategy -class MainScreenViewModel(graph: Graph, private val representationStrategy: RepresentationStrategy,) { +class MainScreenViewModel(graph: Graph, private val representationStrategy: RepresentationStrategy) { val showVerticesLabels = mutableStateOf(false) val showEdgesLabels = mutableStateOf(false) val graphViewModel = GraphViewModel(graph, showVerticesLabels, showEdgesLabels) @@ -18,7 +18,7 @@ class MainScreenViewModel(graph: Graph, private val representationStrategy fun resetGraphView() { representationStrategy.place(800.0, 600.0, graphViewModel.verticesViewValues) - graphViewModel.verticesViewValues.forEach{ v -> v.color = Color.Gray} + graphViewModel.verticesViewValues.forEach { v -> v.color = Color.Gray } } fun setVerticesColor() { From f08021a4b2d5eac2397eee10518a67aa0b502406 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Sun, 22 Sep 2024 16:31:25 +0300 Subject: [PATCH 051/131] fix: corrected color of the first vertex --- src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt index 970a032..0d19959 100644 --- a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt @@ -23,7 +23,6 @@ class CircularPlacementStrategy : RepresentationStrategy { var point = Pair(center.first, center.second - min(width, height) / 2) first.x = point.first.dp first.y = point.second.dp - first.color = Color.Gray sorted .drop(1) From 9cf73f24e8d558ac9f34a2d8cd76bd290f28c593 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Sun, 22 Sep 2024 16:39:47 +0300 Subject: [PATCH 052/131] style: variable renamed --- .../kotlin/viewmodel/graph/CircularPlacementStrategy.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt index 0d19959..45657a8 100644 --- a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt @@ -34,11 +34,11 @@ class CircularPlacementStrategy : RepresentationStrategy { } override fun highlight(vertices: Collection>) { - val customColor_1 = Color(red = 235, green = 82, blue = 132) //235,82,132 - val customColor_2 = Color(red = 251, green = 160, blue = 227) //251,160,227 + val customColor1 = Color(red = 235, green = 82, blue = 132) //235,82,132 + val customColor2 = Color(red = 251, green = 160, blue = 227) //251,160,227 vertices .onEach { - it.color = if (Random.nextBoolean()) customColor_1 else customColor_2 + it.color = if (Random.nextBoolean()) customColor1 else customColor2 } } From e2e80b70497aa8727298f3ce9b3824014bd9addb Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Sun, 22 Sep 2024 16:49:41 +0300 Subject: [PATCH 053/131] =?UTF-8?q?fix:=20vertex=20values=20=E2=80=8B?= =?UTF-8?q?=E2=80=8Bmoved?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/view/graph/VertexView.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/view/graph/VertexView.kt b/src/main/kotlin/view/graph/VertexView.kt index 3d96360..d8a1bb3 100644 --- a/src/main/kotlin/view/graph/VertexView.kt +++ b/src/main/kotlin/view/graph/VertexView.kt @@ -34,8 +34,7 @@ fun VertexView( if (viewModel.labelVisible) { Text( modifier = Modifier - .align(Alignment.Center) - .offset(0.dp, -viewModel.radius - 10.dp), + .align(Alignment.Center), text = viewModel.label, ) } From fb3b5a981648ad89804bc48235f4eeb9a7e0dd02 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Sun, 22 Sep 2024 17:19:24 +0300 Subject: [PATCH 054/131] refactor: replaced vertices with their indices --- src/main/kotlin/viewmodel/graph/GraphViewModel.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index 01274e4..05050c8 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -12,21 +12,21 @@ class GraphViewModel( showVerticesLabels: State, showEdgesLabels: State, ) { - private val verticesView: HashMap, VertexViewModel> = hashMapOf() + internal val verticesView: HashMap> = hashMapOf() init { graph.getVertices().forEach { vertex -> - verticesView[vertex] = VertexViewModel(0.dp, 0.dp, Color.Blue, vertex, showVerticesLabels) + verticesView[vertex.id] = VertexViewModel(0.dp, 0.dp, Color.Blue, vertex, showVerticesLabels) } } - private val edgesView: HashMap, EdgeViewModel> = hashMapOf() + internal val edgesView: HashMap, EdgeViewModel> = hashMapOf() init { graph.edges.forEach { edge -> - val fst = verticesView[graph.vertices[edge.vertices.first]] + val fst = verticesView[edge.vertices.first] ?: throw IllegalStateException("VertexView for vertex with id: ${edge.vertices.first} not found") - val snd = verticesView[graph.vertices[edge.vertices.second]] + val snd = verticesView[edge.vertices.second] ?: throw IllegalStateException("VertexView for vertex with id: ${edge.vertices.second} not found") edgesView[edge] = EdgeViewModel(fst, snd, edge, showEdgesLabels) } From f4e8fd837cf692c88e962ed7b7b7f41c26ede361 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Sun, 22 Sep 2024 17:22:42 +0300 Subject: [PATCH 055/131] style: formatted code --- src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt index 45657a8..e3edad4 100644 --- a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt @@ -13,8 +13,6 @@ class CircularPlacementStrategy : RepresentationStrategy { println("CircularPlacementStrategy.place: there is nothing to place 👐🏻") return } - - val center = Pair(width / 2, height / 2) val angle = 2 * Math.PI / vertices.size From 2ee32cc7c2debcd96836b7d16b23ddfce6ef0c23 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Sun, 22 Sep 2024 21:47:26 +0300 Subject: [PATCH 056/131] fix: unused function removed --- src/main/kotlin/viewmodel/MainScreenViewModel.kt | 4 ---- .../kotlin/viewmodel/graph/CircularPlacementStrategy.kt | 9 --------- .../kotlin/viewmodel/graph/RepresentationStrategy.kt | 1 - 3 files changed, 14 deletions(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 7510263..876759a 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -20,8 +20,4 @@ class MainScreenViewModel(graph: Graph, private val representationStrategy representationStrategy.place(800.0, 600.0, graphViewModel.verticesViewValues) graphViewModel.verticesViewValues.forEach { v -> v.color = Color.Gray } } - - fun setVerticesColor() { - representationStrategy.highlight(graphViewModel.verticesViewValues) - } } \ No newline at end of file diff --git a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt index e3edad4..630394b 100644 --- a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt @@ -31,15 +31,6 @@ class CircularPlacementStrategy : RepresentationStrategy { } } - override fun highlight(vertices: Collection>) { - val customColor1 = Color(red = 235, green = 82, blue = 132) //235,82,132 - val customColor2 = Color(red = 251, green = 160, blue = 227) //251,160,227 - vertices - .onEach { - it.color = if (Random.nextBoolean()) customColor1 else customColor2 - } - } - private fun Pair.rotate(pivot: Pair, angle: Double): Pair { val sin = sin(angle) val cos = cos(angle) diff --git a/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt b/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt index bb2b0e4..317ea6e 100644 --- a/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt @@ -4,5 +4,4 @@ import viewmodel.graph.VertexViewModel interface RepresentationStrategy { fun place(width: Double, height: Double, vertices: Collection>) - fun highlight(vertices: Collection>) } \ No newline at end of file From 77d737a2df7aa776bbbfc6fcb88bc2798a0e5efe Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 00:10:56 +0300 Subject: [PATCH 057/131] feat: added display of Dijkstra's algorithm --- src/main/kotlin/viewmodel/MainScreenViewModel.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 876759a..ed052bc 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -5,8 +5,11 @@ import androidx.compose.ui.graphics.Color import model.graph.Graph import viewmodel.graph.GraphViewModel import viewmodel.graph.RepresentationStrategy +import model.algorithms.* +import model.graph.DirectedGraph +import model.graph.UndirectedGraph -class MainScreenViewModel(graph: Graph, private val representationStrategy: RepresentationStrategy) { +class MainScreenViewModel(private val graph: Graph, private val representationStrategy: RepresentationStrategy) { val showVerticesLabels = mutableStateOf(false) val showEdgesLabels = mutableStateOf(false) val graphViewModel = GraphViewModel(graph, showVerticesLabels, showEdgesLabels) @@ -20,4 +23,13 @@ class MainScreenViewModel(graph: Graph, private val representationStrategy representationStrategy.place(800.0, 600.0, graphViewModel.verticesViewValues) graphViewModel.verticesViewValues.forEach { v -> v.color = Color.Gray } } + + fun runDijkstraAlgorithm(start: Int, end: Int) { + resetGraphView() + val dijkstra = Dijkstra(graph) + val result = dijkstra.findShortestPaths(start, end) + for (vertexId in result) { + graphViewModel.verticesView[vertexId]?.color = Color(240, 128, 128) + } + } } \ No newline at end of file From 723d3a6a6dcabb53f064f524047ecec79bd7b636 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 00:12:46 +0300 Subject: [PATCH 058/131] feat: added display of Kosaraju's algorithm --- .../kotlin/viewmodel/MainScreenViewModel.kt | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index ed052bc..84bf817 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -32,4 +32,25 @@ class MainScreenViewModel(private val graph: Graph, private val representa graphViewModel.verticesView[vertexId]?.color = Color(240, 128, 128) } } -} \ No newline at end of file + + fun runKosarajuAlgorithm() { + if (graph is UndirectedGraph) { + throw IllegalArgumentException("Kosaraju's algorithm cannot be run on undirected graphs.") + } + val colors = listOf( + Color(240, 128, 128), + Color(106, 90, 205), + Color(102, 205, 170), + Color(188, 143, 143), + Color(218, 112, 214) + ) + resetGraphView() + val kosaraju = Kosaraju(graph as DirectedGraph) + val result = kosaraju.findStronglyConnectedComponents() + for ((i, ccs) in result.withIndex()) { + for (vertexId in ccs) { + graphViewModel.verticesView[vertexId]?.color = colors[i % 5] + } + } + } +} From c709cf19554cce94eaa2d6618c39915b0d472e3f Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 00:13:25 +0300 Subject: [PATCH 059/131] feat: added display of Louvain's algorithm --- .../kotlin/viewmodel/MainScreenViewModel.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 84bf817..94ff041 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -53,4 +53,22 @@ class MainScreenViewModel(private val graph: Graph, private val representa } } } + + fun runLouvainAlgorithm() { + val colors = listOf( + Color(240, 128, 128), + Color(106, 90, 205), + Color(102, 205, 170), + Color(188, 143, 143), + Color(218, 112, 214) + ) + resetGraphView() + val louvain = Louvain(graph) + val result = louvain.detectCommunities() + for ((i, community) in result.withIndex()) { + for (vertexId in community) { + graphViewModel.verticesView[vertexId]?.color = colors[i % 5] + } + } + } } From 9c42d27681c1a9a7ac188c2e981e6c5e4ed46d01 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 00:15:11 +0300 Subject: [PATCH 060/131] feat: added documentation to functions --- src/main/kotlin/viewmodel/MainScreenViewModel.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 94ff041..3bfdcb6 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -24,6 +24,8 @@ class MainScreenViewModel(private val graph: Graph, private val representa graphViewModel.verticesViewValues.forEach { v -> v.color = Color.Gray } } + /** Paint the vertices of the found path. + */ fun runDijkstraAlgorithm(start: Int, end: Int) { resetGraphView() val dijkstra = Dijkstra(graph) @@ -33,6 +35,9 @@ class MainScreenViewModel(private val graph: Graph, private val representa } } + /** Paint each ccs its own color. The number of colors is limited, + * so if there are more than 5 ccs, the colors will begin to repeat. + */ fun runKosarajuAlgorithm() { if (graph is UndirectedGraph) { throw IllegalArgumentException("Kosaraju's algorithm cannot be run on undirected graphs.") @@ -54,6 +59,9 @@ class MainScreenViewModel(private val graph: Graph, private val representa } } + /** Paint each community its own color. The number of colors is limited, + * so if there are more than 5 communities, the colors will begin to repeat. + */ fun runLouvainAlgorithm() { val colors = listOf( Color(240, 128, 128), From b6580802983442fb0af0e3764cb176d883f00b0b Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 00:42:19 +0300 Subject: [PATCH 061/131] fix: the color of the initial vertices has been changed --- src/main/kotlin/viewmodel/graph/GraphViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index 05050c8..a81fbf6 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -16,7 +16,7 @@ class GraphViewModel( init { graph.getVertices().forEach { vertex -> - verticesView[vertex.id] = VertexViewModel(0.dp, 0.dp, Color.Blue, vertex, showVerticesLabels) + verticesView[vertex.id] = VertexViewModel(0.dp, 0.dp, Color.Gray, vertex, showVerticesLabels) } } From 507aa693c9f92373f9934c548465bf6256c49748 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 00:46:29 +0300 Subject: [PATCH 062/131] feat: added working button for Dijkstra algorithm pressing the button runs Dijkstra's algorithm --- src/main/kotlin/view/MainScreen.kt | 66 ++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index b7f74d0..8f7b93b 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -2,6 +2,7 @@ package view import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown @@ -10,6 +11,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.sp import model.graph.Graph import org.jetbrains.skia.impl.Stats.enabled @@ -21,6 +23,7 @@ import viewmodel.graph.GraphViewModel @Composable fun MainScreen(viewModel: MainScreenViewModel) { var expanded by remember { mutableStateOf(false) } + var showDialog by remember { mutableStateOf(false) } Row( horizontalArrangement = Arrangement.spacedBy(20.dp) ) { @@ -59,6 +62,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { ) { DropdownMenuItem(onClick = { expanded = false + showDialog = true }) { Text("Dijkstra") } @@ -75,6 +79,15 @@ fun MainScreen(viewModel: MainScreenViewModel) { } } } + if (showDialog) { + DijkstraDialog( + onDismiss = { showDialog = false }, + onRunAlgorithm = { start, end -> + viewModel.runDijkstraAlgorithm(start, end) + showDialog = false + } + ) + } Box { var expandedThemeMenu by remember { mutableStateOf(false) } Button( @@ -112,4 +125,57 @@ fun MainScreen(viewModel: MainScreenViewModel) { GraphView(viewModel.graphViewModel) } } +} +@Composable +fun DijkstraDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) { + var start by remember { mutableStateOf("") } + var end by remember { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Select vertices to find shortest path") + }, + text = { + Column(modifier = Modifier.padding(16.dp)) { + TextField( + value = start, + onValueChange = { start = it }, + label = { Text("Enter the id of the starting vertex:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = end, + onValueChange = { end = it }, + label = { Text("Enter the id of the destination vertex:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { + val startInt = start.toIntOrNull() + val endInt = end.toIntOrNull() + + if (startInt != null && endInt != null) { + onRunAlgorithm(startInt, endInt) + } + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Find the way", color = Color(255, 250, 250)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Cancel", color = Color(255, 250, 250)) + } + } + ) } \ No newline at end of file From 87874e30ef5197f222d39bb7d523d9f7260e0a40 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 00:50:40 +0300 Subject: [PATCH 063/131] feat: added working button for Louvain & Kosaraju --- src/main/kotlin/view/MainScreen.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 8f7b93b..9a2b225 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -68,11 +68,13 @@ fun MainScreen(viewModel: MainScreenViewModel) { } DropdownMenuItem(onClick = { expanded = false + viewModel.runKosarajuAlgorithm() }) { Text("Kosaraju") } DropdownMenuItem(onClick = { expanded = false + viewModel.runLouvainAlgorithm() }) { Text("Louvain") } From fc543323d56e485a560cc762552d83ed27344634 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 01:08:43 +0300 Subject: [PATCH 064/131] fix: vertex colors changed --- .../kotlin/viewmodel/MainScreenViewModel.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 3bfdcb6..e4b235c 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -43,11 +43,11 @@ class MainScreenViewModel(private val graph: Graph, private val representa throw IllegalArgumentException("Kosaraju's algorithm cannot be run on undirected graphs.") } val colors = listOf( - Color(240, 128, 128), - Color(106, 90, 205), - Color(102, 205, 170), - Color(188, 143, 143), - Color(218, 112, 214) + Color(125, 21, 21), + Color(41, 37, 37), + Color(145, 86, 86), + Color(56, 4, 4), + Color(161, 161, 161) ) resetGraphView() val kosaraju = Kosaraju(graph as DirectedGraph) @@ -64,11 +64,11 @@ class MainScreenViewModel(private val graph: Graph, private val representa */ fun runLouvainAlgorithm() { val colors = listOf( - Color(240, 128, 128), - Color(106, 90, 205), - Color(102, 205, 170), - Color(188, 143, 143), - Color(218, 112, 214) + Color(125, 21, 21), + Color(41, 37, 37), + Color(145, 86, 86), + Color(56, 4, 4), + Color(161, 161, 161) ) resetGraphView() val louvain = Louvain(graph) From c22df9f9a79444704cc74e4009b73001209d77ff Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 01:21:19 +0300 Subject: [PATCH 065/131] fix: vertex colors changed --- src/main/kotlin/viewmodel/MainScreenViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index e4b235c..986e52e 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -31,7 +31,7 @@ class MainScreenViewModel(private val graph: Graph, private val representa val dijkstra = Dijkstra(graph) val result = dijkstra.findShortestPaths(start, end) for (vertexId in result) { - graphViewModel.verticesView[vertexId]?.color = Color(240, 128, 128) + graphViewModel.verticesView[vertexId]?.color = Color(125, 21, 21) } } From 55fe3b800a7eb29b906726e1c3658ac9392ad9d8 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 02:46:59 +0300 Subject: [PATCH 066/131] refactor: moved methods --- src/main/kotlin/viewmodel/graph/GraphViewModel.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index a81fbf6..6622f23 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -13,16 +13,12 @@ class GraphViewModel( showEdgesLabels: State, ) { internal val verticesView: HashMap> = hashMapOf() + internal val edgesView: HashMap, EdgeViewModel> = hashMapOf() init { graph.getVertices().forEach { vertex -> verticesView[vertex.id] = VertexViewModel(0.dp, 0.dp, Color.Gray, vertex, showVerticesLabels) } - } - - internal val edgesView: HashMap, EdgeViewModel> = hashMapOf() - - init { graph.edges.forEach { edge -> val fst = verticesView[edge.vertices.first] ?: throw IllegalStateException("VertexView for vertex with id: ${edge.vertices.first} not found") From 99c188d6ef9657341661ae723f4325c0623f26d9 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 03:32:58 +0300 Subject: [PATCH 067/131] refactor: updated Main.kt --- src/main/kotlin/Main.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 6024838..ef520f1 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -9,24 +9,31 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import model.graph.DirectedGraph import model.graph.Graph import model.graph.UndirectedGraph import view.MainScreen import viewmodel.MainScreenViewModel import viewmodel.graph.CircularPlacementStrategy -val sampleGraph = UndirectedGraph().apply { +val sampleGraph = DirectedGraph().apply { addVertex(1, "A") addVertex(2, "B") addVertex(3, "C") addVertex(4, "D") addVertex(5, "E") - - addEdge(Pair(1, 2), 1) - addEdge(Pair(2, 3), 2) + addVertex(6, "F") + addVertex(7, "G") + addVertex(8, "H") + addVertex(9, "I") + addEdge(Pair(1, 2), 4) + addEdge(Pair(2, 3), 12) addEdge(Pair(3, 4), 3) - addEdge(Pair(2, 4), 4) - addEdge(Pair(1, 5), 5) + addEdge(Pair(4, 3), 56) + addEdge(Pair(5, 6), null) + addEdge(Pair(7, 8), 0) + addEdge(Pair(8, 9), -19) + addEdge(Pair(9, 7), 2) } @Composable From c109a71c5633f12f95f7dba7ee7629488cc6452e Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 14:52:34 +0300 Subject: [PATCH 068/131] feat(VertexView): added func to determine color the color of the vertex values now contrasts with the vertex itself --- src/main/kotlin/view/graph/VertexView.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/view/graph/VertexView.kt b/src/main/kotlin/view/graph/VertexView.kt index d8a1bb3..a6f44c9 100644 --- a/src/main/kotlin/view/graph/VertexView.kt +++ b/src/main/kotlin/view/graph/VertexView.kt @@ -8,6 +8,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp import viewmodel.graph.VertexViewModel @@ -17,6 +18,15 @@ fun VertexView( viewModel: VertexViewModel, modifier: Modifier = Modifier, ) { + + fun isColorDark(color: Color): Boolean { + val red = color.red + val green = color.green + val blue = color.blue + val brightness = (red * 299 + green * 587 + blue * 114) / 1000 + return brightness < 0.5 + } + Box(modifier = modifier .size(viewModel.radius * 2, viewModel.radius * 2) .offset(viewModel.x, viewModel.y) @@ -36,7 +46,8 @@ fun VertexView( modifier = Modifier .align(Alignment.Center), text = viewModel.label, + color = if (isColorDark(viewModel.color)) Color.White else Color.Black ) } } -} \ No newline at end of file +} From 5c454e170d59f5c03249e8f6d3024fbe0007b7ca Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 14:58:46 +0300 Subject: [PATCH 069/131] feat: added viewmodel for adding vertex --- src/main/kotlin/viewmodel/graph/GraphViewModel.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index 6622f23..4afbdf5 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -5,12 +5,13 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import model.graph.Edge import model.graph.Graph +import model.graph.UndirectedGraph import model.graph.Vertex class GraphViewModel( private val graph: Graph, - showVerticesLabels: State, - showEdgesLabels: State, + private val showVerticesLabels: State, + private val showEdgesLabels: State, ) { internal val verticesView: HashMap> = hashMapOf() internal val edgesView: HashMap, EdgeViewModel> = hashMapOf() @@ -28,9 +29,8 @@ class GraphViewModel( } } - val verticesViewValues: Collection> - get() = verticesView.values - - val edgesViewValues: Collection> - get() = edgesView.values + fun addVertex(id: Int, data: D) { + graph.addVertex(id, data) + verticesView[id] = VertexViewModel(0.dp, 0.dp, Color.Gray, graph.vertices[id]!!, showVerticesLabels) + } } From aa4b4e4cba0943116c13e4bcff9dd9d853c13d21 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 14:59:37 +0300 Subject: [PATCH 070/131] feat: added viewmodel for removing vertex --- src/main/kotlin/viewmodel/graph/GraphViewModel.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index 4afbdf5..e211689 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -33,4 +33,13 @@ class GraphViewModel( graph.addVertex(id, data) verticesView[id] = VertexViewModel(0.dp, 0.dp, Color.Gray, graph.vertices[id]!!, showVerticesLabels) } + + fun removeVertex(id: Int) { + graph.removeVertex(id) + verticesView.remove(id) + val edgesToRemove = edgesView.keys.filter { edge -> edge.vertices.first == id || edge.vertices.second == id } + edgesToRemove.forEach { edge -> + edgesView.remove(edge) + } + } } From adbda3aa5cf3efeba305a8fdca47a64a30185afb Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 15:00:22 +0300 Subject: [PATCH 071/131] feat: added viewmodel for added edge --- src/main/kotlin/viewmodel/graph/GraphViewModel.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index e211689..c369118 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -42,4 +42,13 @@ class GraphViewModel( edgesView.remove(edge) } } + + fun addEdge(from: Int, to: Int, w: Int?) { + graph.addEdge(Pair(from, to), w) + val fst = verticesView[from] ?: throw IllegalStateException("VertexView for vertex with id: $from not found") + val snd = verticesView[to] ?: throw IllegalStateException("VertexView for vertex with id: $to not found") + graph.edges.find { it.vertices == Pair(from, to) }?.let { edge -> + edgesView[edge] = EdgeViewModel(fst, snd, edge, showEdgesLabels) + } + } } From 71d929101025a8fda14f321045a7f5d8a3ce20ea Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 15:01:14 +0300 Subject: [PATCH 072/131] feat: added viewmodel for removing edge --- src/main/kotlin/viewmodel/graph/GraphViewModel.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index c369118..241a4b8 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -51,4 +51,16 @@ class GraphViewModel( edgesView[edge] = EdgeViewModel(fst, snd, edge, showEdgesLabels) } } + + fun removeEdge(from: Int, to: Int, w: Int?) { + graph.removeEdge(Pair(from, to), w) + edgesView.keys.find { it.vertices == Pair(from, to) }?.let { edge -> + edgesView.remove(edge) + } + if (graph is UndirectedGraph) { + edgesView.keys.find { it.vertices == Pair(to, from) }?.let { edge -> + edgesView.remove(edge) + } + } + } } From 9a07cbd5e2e46df63b8071b2d1a42568bc5c7ff0 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 15:03:16 +0300 Subject: [PATCH 073/131] feat: added func for removing/adding vertex/edge --- .../kotlin/viewmodel/MainScreenViewModel.kt | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 986e52e..5398d96 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -16,12 +16,28 @@ class MainScreenViewModel(private val graph: Graph, private val representa private var algorithmRunning = false // флаг для отслеживания состояния работы алгоритма init { - representationStrategy.place(800.0, 600.0, graphViewModel.verticesViewValues) + representationStrategy.place(800.0, 600.0, graphViewModel.verticesView.values) } fun resetGraphView() { - representationStrategy.place(800.0, 600.0, graphViewModel.verticesViewValues) - graphViewModel.verticesViewValues.forEach { v -> v.color = Color.Gray } + representationStrategy.place(800.0, 600.0, graphViewModel.verticesView.values) + graphViewModel.verticesView.values.forEach { v -> v.color = Color.Gray } + } + + fun addVertex(id: Int, data: D) { + graphViewModel.addVertex(id, data) + } + + fun addEdge(from: Int, to: Int, w: Int?) { + graphViewModel.addEdge(from, to, w) + } + + fun removeVertex(id: Int) { + graphViewModel.removeVertex(id) + } + + fun removeEdge(from: Int, to: Int, w: Int?) { + graphViewModel.removeEdge(from, to, w) } /** Paint the vertices of the found path. From 888da2c9ac62f4f9734f8155e84421baa886a721 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 15:08:05 +0300 Subject: [PATCH 074/131] feat: added menu for removing/adding vertex/edge --- src/main/kotlin/view/MainScreen.kt | 137 +++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 9a2b225..9b8187e 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -22,8 +22,16 @@ import viewmodel.graph.GraphViewModel @Composable fun MainScreen(viewModel: MainScreenViewModel) { - var expanded by remember { mutableStateOf(false) } - var showDialog by remember { mutableStateOf(false) } + var expandedAlgorithmsMenu by remember { mutableStateOf(false) } + var expandedAddMenu by remember { mutableStateOf(false) } + var expandedRemoveMenu by remember { mutableStateOf(false) } + + var showDijkstraDialog by remember { mutableStateOf(false) } + var showAddEdgeDialog by remember { mutableStateOf(false) } + var showAddVertexDialog by remember { mutableStateOf(false) } + var showRemoveVertexDialog by remember { mutableStateOf(false) } + var showRemoveEdgeDialog by remember { mutableStateOf(false) } + Row( horizontalArrangement = Arrangement.spacedBy(20.dp) ) { @@ -50,45 +58,90 @@ fun MainScreen(viewModel: MainScreenViewModel) { } Box { Row(verticalAlignment = Alignment.CenterVertically) { - Button(onClick = { expanded = true }) { + Button(onClick = { expandedAlgorithmsMenu = true }) { Text("Algorithm") Icon(Icons.Default.ArrowDropDown, contentDescription = "Select algorithm") } } DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - modifier = Modifier.background(Color.Green) + expanded = expandedAlgorithmsMenu, + onDismissRequest = { expandedAlgorithmsMenu = false }, + modifier = Modifier.background(Color.Gray) ) { DropdownMenuItem(onClick = { - expanded = false - showDialog = true + expandedAlgorithmsMenu = false + showDijkstraDialog = true }) { Text("Dijkstra") } DropdownMenuItem(onClick = { - expanded = false + expandedAlgorithmsMenu = false viewModel.runKosarajuAlgorithm() }) { Text("Kosaraju") } DropdownMenuItem(onClick = { - expanded = false + expandedAlgorithmsMenu = false viewModel.runLouvainAlgorithm() }) { Text("Louvain") } } } - } - if (showDialog) { - DijkstraDialog( - onDismiss = { showDialog = false }, - onRunAlgorithm = { start, end -> - viewModel.runDijkstraAlgorithm(start, end) - showDialog = false + Row { + Box(modifier = Modifier.padding(2.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Button(onClick = { expandedAddMenu = true }) { + Text("Add") + Icon(Icons.Default.ArrowDropDown, contentDescription = "Add") + } + } + DropdownMenu( + expanded = expandedAddMenu, + onDismissRequest = { expandedAddMenu = false }, + modifier = Modifier.background(Color.Gray) + ) { + DropdownMenuItem(onClick = { + expandedAddMenu = false + showAddVertexDialog = true + }) { + Text("Add vertex") + } + DropdownMenuItem(onClick = { + expandedAddMenu = false + showAddEdgeDialog = true + }) { + Text("Add edge") + } + } } - ) + Box(modifier = Modifier.padding(2.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Button(onClick = { expandedRemoveMenu = true }) { + Text("Remove") + Icon(Icons.Default.ArrowDropDown, contentDescription = "Remove") + } + } + DropdownMenu( + expanded = expandedRemoveMenu, + onDismissRequest = { expandedRemoveMenu = false }, + modifier = Modifier.background(Color.Gray) + ) { + DropdownMenuItem(onClick = { + expandedRemoveMenu = false + showRemoveVertexDialog = true + }) { + Text("Remove vertex") + } + DropdownMenuItem(onClick = { + expandedRemoveMenu = false + showRemoveEdgeDialog = true + }) { + Text("Remove edge") + } + } + } + } } Box { var expandedThemeMenu by remember { mutableStateOf(false) } @@ -127,7 +180,53 @@ fun MainScreen(viewModel: MainScreenViewModel) { GraphView(viewModel.graphViewModel) } } + if (showDijkstraDialog) { + DijkstraDialog( + onDismiss = { showDijkstraDialog = false }, + onRunAlgorithm = { start, end -> + viewModel.runDijkstraAlgorithm(start, end) + showDijkstraDialog = false + } + ) + } + if (showAddVertexDialog) { + AddVertexDialog( + onDismiss = { showAddVertexDialog = false }, + onRunAlgorithm = { id, data -> + viewModel.addVertex(id, data as D) + showAddVertexDialog = false + } + ) + } + if (showRemoveVertexDialog) { + RemoveVertexDialog( + onDismiss = { showRemoveVertexDialog = false }, + onRunAlgorithm = { id -> + viewModel.removeVertex(id) + showRemoveVertexDialog = false + } + ) + } + if (showAddEdgeDialog) { + AddEdgeDialog( + onDismiss = { showAddEdgeDialog = false }, + onRunAlgorithm = { from, to, w -> + viewModel.addEdge(from, to, w) + showAddEdgeDialog = false + } + ) + } + if (showRemoveEdgeDialog) { + RemoveEdgeDialog( + onDismiss = { showRemoveEdgeDialog = false }, + onRunAlgorithm = { from, to, w -> + viewModel.removeEdge(from, to, w) + showRemoveEdgeDialog = false + } + ) + } } + @Composable fun DijkstraDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) { var start by remember { mutableStateOf("") } @@ -180,4 +279,4 @@ fun DijkstraDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) { } } ) -} \ No newline at end of file +} From 475d892d201fd91168b89e987e89d4ca45fedc16 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 15:09:14 +0300 Subject: [PATCH 075/131] feat: added 'add vertex' dialog --- src/main/kotlin/view/MainScreen.kt | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 9b8187e..a03097f 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -280,3 +280,55 @@ fun DijkstraDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) { } ) } + +@Composable +fun AddVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, String) -> Unit) { + var id by remember { mutableStateOf("") } + var data by remember { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Enter id and data vertex") + }, + text = { + Column(modifier = Modifier.padding(16.dp)) { + TextField( + value = id, + onValueChange = { id = it }, + label = { Text("Enter the id:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = data, + onValueChange = { data = it }, + label = { Text("Enter the data:") }, + modifier = Modifier.padding(bottom = 12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { + val idInt = id.toIntOrNull() + + if (idInt != null) { + onRunAlgorithm(idInt, data) + } + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Add the vertex", color = Color(255, 250, 250)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Cancel", color = Color(255, 250, 250)) + } + } + ) +} From a981f356ea9194e51b3158db894eb7cfcebbb224 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 15:09:53 +0300 Subject: [PATCH 076/131] feat: added 'remove vertex' dialog --- src/main/kotlin/view/MainScreen.kt | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index a03097f..038dc4e 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -332,3 +332,47 @@ fun AddVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, String) -> Unit } ) } + +@Composable +fun RemoveVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int) -> Unit) { + var id by remember { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Enter id vertex") + }, + text = { + Column(modifier = Modifier.padding(16.dp)) { + TextField( + value = id, + onValueChange = { id = it }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { + val idInt = id.toIntOrNull() + + if (idInt != null) { + onRunAlgorithm(idInt) + } + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Remove the vertex", color = Color(255, 250, 250)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Cancel", color = Color(255, 250, 250)) + } + } + ) +} From 83553e48ff18c16acc2242c9f5c7fd2939a3216d Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 15:11:32 +0300 Subject: [PATCH 077/131] feat: added 'add edge' dialog --- src/main/kotlin/view/MainScreen.kt | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 038dc4e..cd00678 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -376,3 +376,68 @@ fun RemoveVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int) -> Unit) { } ) } + +@Composable +fun AddEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Unit) { + var from by remember { mutableStateOf("") } + var to by remember { mutableStateOf("") } + var w by remember { mutableStateOf(null) } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Enter edge parameters") + }, + text = { + Column(modifier = Modifier.padding(16.dp)) { + TextField( + value = from, + onValueChange = { from = it }, + label = { Text("Enter the start id:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = to, + onValueChange = { to = it }, + label = { Text("Enter the end id:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = w ?: "", + onValueChange = { newValue -> + w = newValue.ifEmpty { null } + }, + label = { Text("Enter the weight (optional):") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { + val fromInt = from.toIntOrNull() + val toInt = to.toIntOrNull() + val wInt = w?.toIntOrNull() + + if (fromInt != null && toInt != null) { + onRunAlgorithm(fromInt, toInt, wInt) + } + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Add the edge", color = Color(255, 250, 250)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Cancel", color = Color(255, 250, 250)) + } + } + ) +} From 39bb9bfd0da188e968d022d875c2ad9ee260f94f Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 15:11:59 +0300 Subject: [PATCH 078/131] feat: added 'remove edge' dialog --- src/main/kotlin/view/MainScreen.kt | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index cd00678..f19ada3 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -441,3 +441,68 @@ fun AddEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Uni } ) } + +@Composable +fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Unit) { + var from by remember { mutableStateOf("") } + var to by remember { mutableStateOf("") } + var w by remember { mutableStateOf(null) } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Enter edge parameters") + }, + text = { + Column(modifier = Modifier.padding(16.dp)) { + TextField( + value = from, + onValueChange = { from = it }, + label = { Text("Enter the start id:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = to, + onValueChange = { to = it }, + label = { Text("Enter the end id:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = w ?: "", + onValueChange = { newValue -> + w = newValue.ifEmpty { null } + }, + label = { Text("Enter the weight (optional):") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { + val fromInt = from.toIntOrNull() + val toInt = to.toIntOrNull() + val wInt = w?.toIntOrNull() + + if (fromInt != null && toInt != null) { + onRunAlgorithm(fromInt, toInt, wInt) + } + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Remove the edge", color = Color(255, 250, 250)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Cancel", color = Color(255, 250, 250)) + } + } + ) +} From 02de1abd066dc5867beca092a6afba8b216711b7 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 15:12:58 +0300 Subject: [PATCH 079/131] refactor: updated Main.kt --- src/main/kotlin/Main.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index ef520f1..baea5ce 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -30,9 +30,8 @@ val sampleGraph = DirectedGraph().apply { addEdge(Pair(2, 3), 12) addEdge(Pair(3, 4), 3) addEdge(Pair(4, 3), 56) - addEdge(Pair(5, 6), null) addEdge(Pair(7, 8), 0) - addEdge(Pair(8, 9), -19) + addEdge(Pair(8, 9), 19) addEdge(Pair(9, 7), 2) } From ae3f50c0295f6f299cea6e5078c6af913c18cb36 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 15:48:23 +0300 Subject: [PATCH 080/131] chore: dialogues are placed in the new file --- src/main/kotlin/view/CommonDialogs.kt | 291 ++++++++++++++++++++++++++ src/main/kotlin/view/MainScreen.kt | 280 ------------------------- 2 files changed, 291 insertions(+), 280 deletions(-) create mode 100644 src/main/kotlin/view/CommonDialogs.kt diff --git a/src/main/kotlin/view/CommonDialogs.kt b/src/main/kotlin/view/CommonDialogs.kt new file mode 100644 index 0000000..73feb59 --- /dev/null +++ b/src/main/kotlin/view/CommonDialogs.kt @@ -0,0 +1,291 @@ +package view + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp + +@Composable +fun DijkstraDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) { + var start by remember { mutableStateOf("") } + var end by remember { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Select vertices to find shortest path") + }, + text = { + Column(modifier = Modifier.padding(16.dp)) { + TextField( + value = start, + onValueChange = { start = it }, + label = { Text("Enter the id of the starting vertex:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = end, + onValueChange = { end = it }, + label = { Text("Enter the id of the destination vertex:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { + val startInt = start.toIntOrNull() + val endInt = end.toIntOrNull() + + if (startInt != null && endInt != null) { + onRunAlgorithm(startInt, endInt) + } + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Find the way", color = Color(255, 250, 250)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Cancel", color = Color(255, 250, 250)) + } + } + ) +} + +@Composable +fun AddVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, String) -> Unit) { + var id by remember { mutableStateOf("") } + var data by remember { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Enter id and data vertex") + }, + text = { + Column(modifier = Modifier.padding(16.dp)) { + TextField( + value = id, + onValueChange = { id = it }, + label = { Text("Enter the id:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = data, + onValueChange = { data = it }, + label = { Text("Enter the data:") }, + modifier = Modifier.padding(bottom = 12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { + val idInt = id.toIntOrNull() + + if (idInt != null) { + onRunAlgorithm(idInt, data) + } + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Add the vertex", color = Color(255, 250, 250)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Cancel", color = Color(255, 250, 250)) + } + } + ) +} + +@Composable +fun RemoveVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int) -> Unit) { + var id by remember { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Enter id vertex") + }, + text = { + Column(modifier = Modifier.padding(16.dp)) { + TextField( + value = id, + onValueChange = { id = it }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { + val idInt = id.toIntOrNull() + + if (idInt != null) { + onRunAlgorithm(idInt) + } + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Remove the vertex", color = Color(255, 250, 250)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Cancel", color = Color(255, 250, 250)) + } + } + ) +} + +@Composable +fun AddEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Unit) { + var from by remember { mutableStateOf("") } + var to by remember { mutableStateOf("") } + var w by remember { mutableStateOf(null) } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Enter edge parameters") + }, + text = { + Column(modifier = Modifier.padding(16.dp)) { + TextField( + value = from, + onValueChange = { from = it }, + label = { Text("Enter the start id:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = to, + onValueChange = { to = it }, + label = { Text("Enter the end id:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = w ?: "", + onValueChange = { newValue -> + w = newValue.ifEmpty { null } + }, + label = { Text("Enter the weight (optional):") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { + val fromInt = from.toIntOrNull() + val toInt = to.toIntOrNull() + val wInt = w?.toIntOrNull() + + if (fromInt != null && toInt != null) { + onRunAlgorithm(fromInt, toInt, wInt) + } + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Add the edge", color = Color(255, 250, 250)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Cancel", color = Color(255, 250, 250)) + } + } + ) +} + +@Composable +fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Unit) { + var from by remember { mutableStateOf("") } + var to by remember { mutableStateOf("") } + var w by remember { mutableStateOf(null) } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Enter edge parameters") + }, + text = { + Column(modifier = Modifier.padding(16.dp)) { + TextField( + value = from, + onValueChange = { from = it }, + label = { Text("Enter the start id:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = to, + onValueChange = { to = it }, + label = { Text("Enter the end id:") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + TextField( + value = w ?: "", + onValueChange = { newValue -> + w = newValue.ifEmpty { null } + }, + label = { Text("Enter the weight (optional):") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { + val fromInt = from.toIntOrNull() + val toInt = to.toIntOrNull() + val wInt = w?.toIntOrNull() + + if (fromInt != null && toInt != null) { + onRunAlgorithm(fromInt, toInt, wInt) + } + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Remove the edge", color = Color(255, 250, 250)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Cancel", color = Color(255, 250, 250)) + } + } + ) +} diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index f19ada3..44b74d9 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -226,283 +226,3 @@ fun MainScreen(viewModel: MainScreenViewModel) { ) } } - -@Composable -fun DijkstraDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) { - var start by remember { mutableStateOf("") } - var end by remember { mutableStateOf("") } - - AlertDialog( - onDismissRequest = onDismiss, - title = { - Text("Select vertices to find shortest path") - }, - text = { - Column(modifier = Modifier.padding(16.dp)) { - TextField( - value = start, - onValueChange = { start = it }, - label = { Text("Enter the id of the starting vertex:") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) - ) - TextField( - value = end, - onValueChange = { end = it }, - label = { Text("Enter the id of the destination vertex:") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) - ) - } - }, - confirmButton = { - Button( - onClick = { - val startInt = start.toIntOrNull() - val endInt = end.toIntOrNull() - - if (startInt != null && endInt != null) { - onRunAlgorithm(startInt, endInt) - } - }, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) - ) { - Text("Find the way", color = Color(255, 250, 250)) - } - }, - dismissButton = { - Button( - onClick = onDismiss, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) - ) { - Text("Cancel", color = Color(255, 250, 250)) - } - } - ) -} - -@Composable -fun AddVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, String) -> Unit) { - var id by remember { mutableStateOf("") } - var data by remember { mutableStateOf("") } - - AlertDialog( - onDismissRequest = onDismiss, - title = { - Text("Enter id and data vertex") - }, - text = { - Column(modifier = Modifier.padding(16.dp)) { - TextField( - value = id, - onValueChange = { id = it }, - label = { Text("Enter the id:") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) - ) - TextField( - value = data, - onValueChange = { data = it }, - label = { Text("Enter the data:") }, - modifier = Modifier.padding(bottom = 12.dp) - ) - } - }, - confirmButton = { - Button( - onClick = { - val idInt = id.toIntOrNull() - - if (idInt != null) { - onRunAlgorithm(idInt, data) - } - }, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) - ) { - Text("Add the vertex", color = Color(255, 250, 250)) - } - }, - dismissButton = { - Button( - onClick = onDismiss, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) - ) { - Text("Cancel", color = Color(255, 250, 250)) - } - } - ) -} - -@Composable -fun RemoveVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int) -> Unit) { - var id by remember { mutableStateOf("") } - - AlertDialog( - onDismissRequest = onDismiss, - title = { - Text("Enter id vertex") - }, - text = { - Column(modifier = Modifier.padding(16.dp)) { - TextField( - value = id, - onValueChange = { id = it }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) - ) - } - }, - confirmButton = { - Button( - onClick = { - val idInt = id.toIntOrNull() - - if (idInt != null) { - onRunAlgorithm(idInt) - } - }, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) - ) { - Text("Remove the vertex", color = Color(255, 250, 250)) - } - }, - dismissButton = { - Button( - onClick = onDismiss, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) - ) { - Text("Cancel", color = Color(255, 250, 250)) - } - } - ) -} - -@Composable -fun AddEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Unit) { - var from by remember { mutableStateOf("") } - var to by remember { mutableStateOf("") } - var w by remember { mutableStateOf(null) } - - AlertDialog( - onDismissRequest = onDismiss, - title = { - Text("Enter edge parameters") - }, - text = { - Column(modifier = Modifier.padding(16.dp)) { - TextField( - value = from, - onValueChange = { from = it }, - label = { Text("Enter the start id:") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) - ) - TextField( - value = to, - onValueChange = { to = it }, - label = { Text("Enter the end id:") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) - ) - TextField( - value = w ?: "", - onValueChange = { newValue -> - w = newValue.ifEmpty { null } - }, - label = { Text("Enter the weight (optional):") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) - ) - } - }, - confirmButton = { - Button( - onClick = { - val fromInt = from.toIntOrNull() - val toInt = to.toIntOrNull() - val wInt = w?.toIntOrNull() - - if (fromInt != null && toInt != null) { - onRunAlgorithm(fromInt, toInt, wInt) - } - }, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) - ) { - Text("Add the edge", color = Color(255, 250, 250)) - } - }, - dismissButton = { - Button( - onClick = onDismiss, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) - ) { - Text("Cancel", color = Color(255, 250, 250)) - } - } - ) -} - -@Composable -fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Unit) { - var from by remember { mutableStateOf("") } - var to by remember { mutableStateOf("") } - var w by remember { mutableStateOf(null) } - - AlertDialog( - onDismissRequest = onDismiss, - title = { - Text("Enter edge parameters") - }, - text = { - Column(modifier = Modifier.padding(16.dp)) { - TextField( - value = from, - onValueChange = { from = it }, - label = { Text("Enter the start id:") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) - ) - TextField( - value = to, - onValueChange = { to = it }, - label = { Text("Enter the end id:") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) - ) - TextField( - value = w ?: "", - onValueChange = { newValue -> - w = newValue.ifEmpty { null } - }, - label = { Text("Enter the weight (optional):") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) - ) - } - }, - confirmButton = { - Button( - onClick = { - val fromInt = from.toIntOrNull() - val toInt = to.toIntOrNull() - val wInt = w?.toIntOrNull() - - if (fromInt != null && toInt != null) { - onRunAlgorithm(fromInt, toInt, wInt) - } - }, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) - ) { - Text("Remove the edge", color = Color(255, 250, 250)) - } - }, - dismissButton = { - Button( - onClick = onDismiss, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) - ) { - Text("Cancel", color = Color(255, 250, 250)) - } - } - ) -} From bdcb5ab86764edaecab1598e043bc9cf1cb7b226 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 23 Sep 2024 15:56:47 +0300 Subject: [PATCH 081/131] fix: fixed incorrect access to variables --- src/main/kotlin/view/graph/GraphView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/view/graph/GraphView.kt b/src/main/kotlin/view/graph/GraphView.kt index 99eabf8..3f0d605 100644 --- a/src/main/kotlin/view/graph/GraphView.kt +++ b/src/main/kotlin/view/graph/GraphView.kt @@ -19,10 +19,10 @@ fun GraphView( .fillMaxSize() ) { - viewModel.verticesViewValues.forEach { v -> + viewModel.verticesView.values.forEach { v -> VertexView(v, Modifier) } - viewModel.edgesViewValues.forEach { e -> + viewModel.edgesView.values.forEach { e -> EdgeView(e, Modifier) } } From 189048c1d26566eac063e96d15cf57bc7506dda3 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 24 Sep 2024 01:38:08 +0300 Subject: [PATCH 082/131] fix: fixed a bug when deleting an edge --- .../kotlin/model/algorithms/CycleSearch.kt | 2 +- src/main/kotlin/model/graph/DirectedGraph.kt | 19 ++++++++++--------- src/main/kotlin/model/graph/Graph.kt | 4 ++-- .../kotlin/model/graph/UndirectedGraph.kt | 17 ++++++++++------- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/model/algorithms/CycleSearch.kt b/src/main/kotlin/model/algorithms/CycleSearch.kt index 455b550..898cc73 100644 --- a/src/main/kotlin/model/algorithms/CycleSearch.kt +++ b/src/main/kotlin/model/algorithms/CycleSearch.kt @@ -133,7 +133,7 @@ class CycleSearch(private val graph: UndirectedGraph) { for (adjacency in graph.adjacency[vertex.id]!!.filter { it.key != firstAdjacency && it.key != secondAdjacency }) { removedEdges.add(Edge(vertex.id to adjacency.key, adjacency.value)) - graph.removeEdge(vertex.id to adjacency.key, adjacency.value) + graph.removeEdge(vertex.id to adjacency.key) } currentCyclePath = findAnyCycle(vertex.id) diff --git a/src/main/kotlin/model/graph/DirectedGraph.kt b/src/main/kotlin/model/graph/DirectedGraph.kt index 4419964..32e0494 100644 --- a/src/main/kotlin/model/graph/DirectedGraph.kt +++ b/src/main/kotlin/model/graph/DirectedGraph.kt @@ -13,7 +13,7 @@ class DirectedGraph() : Graph() { adjacency.getOrPut(v.first) { hashMapOf() }[v.second] = w } - override fun removeEdge(v: Pair, w: Int?) { + override fun removeEdge(v: Pair) { if (!vertices.containsKey(v.first)) { throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") @@ -24,8 +24,9 @@ class DirectedGraph() : Graph() { if (v.second !in adjacency[v.first]!!.keys) { throw IllegalArgumentException("Edge from ${v.first} to ${v.second} not exists in the graph.") } - - edges.remove(Edge(Pair(v.first, v.second), w)) + edges.find { it.vertices == Pair(v.first, v.second) }?.let { edge -> + edges.remove(edge) + } adjacency[v.first]!!.remove(v.second) } @@ -34,14 +35,14 @@ class DirectedGraph() : Graph() { if (!vertices.containsKey(id)) { throw IllegalArgumentException("Vertex with id: $id doesn't exist in the graph.") } - if(adjacency[id]!!.isNotEmpty()){ - for(idAdjacency in adjacency[id]!!.keys) { - removeEdge(id to idAdjacency, adjacency[id]!![idAdjacency]) + if (adjacency[id]!!.isNotEmpty()) { + for (idAdjacency in adjacency[id]!!.keys) { + removeEdge(id to idAdjacency) } } - if(adjacency.filterValues{ it.keys.contains(id)}.isNotEmpty()){ - for(adj in adjacency.filterValues{ it.keys.contains(id)}) { - removeEdge(adj.key to id, adjacency[adj.key]!![id]) + if (adjacency.filterValues { it.keys.contains(id) }.isNotEmpty()) { + for (adj in adjacency.filterValues { it.keys.contains(id) }) { + removeEdge(adj.key to id) } } vertices.remove(id) diff --git a/src/main/kotlin/model/graph/Graph.kt b/src/main/kotlin/model/graph/Graph.kt index cae98b3..be7714a 100644 --- a/src/main/kotlin/model/graph/Graph.kt +++ b/src/main/kotlin/model/graph/Graph.kt @@ -1,13 +1,13 @@ package model.graph -// TODO: (*) добавить удаление вершин abstract class Graph { val adjacency = hashMapOf>() val vertices = hashMapOf>() val edges = mutableListOf>() + fun getVertices(): Collection> = vertices.values abstract fun addEdge(v: Pair, w: Int?) - abstract fun removeEdge(v: Pair, w: Int?) + abstract fun removeEdge(v: Pair) abstract fun removeVertex(id: Int) fun addVertex(id: Int, data: D) { if (vertices.containsKey(id)) { diff --git a/src/main/kotlin/model/graph/UndirectedGraph.kt b/src/main/kotlin/model/graph/UndirectedGraph.kt index 43ab680..87fa1c2 100644 --- a/src/main/kotlin/model/graph/UndirectedGraph.kt +++ b/src/main/kotlin/model/graph/UndirectedGraph.kt @@ -18,7 +18,7 @@ class UndirectedGraph() : Graph() { adjacency.getOrPut(v.second) { hashMapOf() }[v.first] = w } - override fun removeEdge(v: Pair, w: Int?) { + override fun removeEdge(v: Pair) { if (!vertices.containsKey(v.first)) { throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") @@ -29,9 +29,12 @@ class UndirectedGraph() : Graph() { if (v.second !in adjacency[v.first]!!.keys) { throw IllegalArgumentException("Edge from ${v.first} to ${v.second} not exists in the graph.") } - - edges.remove(Edge(Pair(v.second, v.first), w)) - edges.remove(Edge(Pair(v.first, v.second), w)) + edges.find { it.vertices == Pair(v.first, v.second) }?.let { edge -> + edges.remove(edge) + } + edges.find { it.vertices == Pair(v.second, v.first) }?.let { edge -> + edges.remove(edge) + } adjacency[v.first]!!.remove(v.second) adjacency[v.second]!!.remove(v.first) @@ -41,9 +44,9 @@ class UndirectedGraph() : Graph() { if (!vertices.containsKey(id)) { throw IllegalArgumentException("Vertex with id: $id doesn't exist in the graph.") } - if(adjacency[id]!!.isNotEmpty()){ - for(idAdjacency in adjacency[id]!!.keys) { - removeEdge(id to idAdjacency, adjacency[id]!![idAdjacency]) + if (adjacency[id]!!.isNotEmpty()) { + for (idAdjacency in adjacency[id]!!.keys) { + removeEdge(id to idAdjacency) } } vertices.remove(id) From e4e7bb5ea2f4f5cb0e296567b704797ab3109f12 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 24 Sep 2024 01:39:55 +0300 Subject: [PATCH 083/131] fix: updated gui after bug fix --- src/main/kotlin/view/CommonDialogs.kt | 15 ++------------- src/main/kotlin/view/MainScreen.kt | 4 ++-- src/main/kotlin/viewmodel/MainScreenViewModel.kt | 4 ++-- src/main/kotlin/viewmodel/graph/GraphViewModel.kt | 4 ++-- 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/view/CommonDialogs.kt b/src/main/kotlin/view/CommonDialogs.kt index 73feb59..f159b1e 100644 --- a/src/main/kotlin/view/CommonDialogs.kt +++ b/src/main/kotlin/view/CommonDialogs.kt @@ -226,10 +226,9 @@ fun AddEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Uni } @Composable -fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Unit) { +fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) { var from by remember { mutableStateOf("") } var to by remember { mutableStateOf("") } - var w by remember { mutableStateOf(null) } AlertDialog( onDismissRequest = onDismiss, @@ -252,15 +251,6 @@ fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.padding(bottom = 12.dp) ) - TextField( - value = w ?: "", - onValueChange = { newValue -> - w = newValue.ifEmpty { null } - }, - label = { Text("Enter the weight (optional):") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) - ) } }, confirmButton = { @@ -268,10 +258,9 @@ fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> onClick = { val fromInt = from.toIntOrNull() val toInt = to.toIntOrNull() - val wInt = w?.toIntOrNull() if (fromInt != null && toInt != null) { - onRunAlgorithm(fromInt, toInt, wInt) + onRunAlgorithm(fromInt, toInt) } }, colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 44b74d9..f42e87c 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -219,8 +219,8 @@ fun MainScreen(viewModel: MainScreenViewModel) { if (showRemoveEdgeDialog) { RemoveEdgeDialog( onDismiss = { showRemoveEdgeDialog = false }, - onRunAlgorithm = { from, to, w -> - viewModel.removeEdge(from, to, w) + onRunAlgorithm = { from, to -> + viewModel.removeEdge(from, to) showRemoveEdgeDialog = false } ) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 5398d96..c2763ce 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -36,8 +36,8 @@ class MainScreenViewModel(private val graph: Graph, private val representa graphViewModel.removeVertex(id) } - fun removeEdge(from: Int, to: Int, w: Int?) { - graphViewModel.removeEdge(from, to, w) + fun removeEdge(from: Int, to: Int) { + graphViewModel.removeEdge(from, to) } /** Paint the vertices of the found path. diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index 241a4b8..04db209 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -52,8 +52,8 @@ class GraphViewModel( } } - fun removeEdge(from: Int, to: Int, w: Int?) { - graph.removeEdge(Pair(from, to), w) + fun removeEdge(from: Int, to: Int) { + graph.removeEdge(Pair(from, to)) edgesView.keys.find { it.vertices == Pair(from, to) }?.let { edge -> edgesView.remove(edge) } From 9f6c4ca518e720f8abc6e78e98ce5a6513864d0e Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 24 Sep 2024 01:52:13 +0300 Subject: [PATCH 084/131] feat: added more colors for algorithms --- .../kotlin/viewmodel/MainScreenViewModel.kt | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index c2763ce..0cf2ff8 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -52,7 +52,7 @@ class MainScreenViewModel(private val graph: Graph, private val representa } /** Paint each ccs its own color. The number of colors is limited, - * so if there are more than 5 ccs, the colors will begin to repeat. + * so if there are more than 10 ccs, the colors will begin to repeat. */ fun runKosarajuAlgorithm() { if (graph is UndirectedGraph) { @@ -63,20 +63,25 @@ class MainScreenViewModel(private val graph: Graph, private val representa Color(41, 37, 37), Color(145, 86, 86), Color(56, 4, 4), - Color(161, 161, 161) + Color(161, 161, 161), + Color(115, 72, 101), + Color(38, 11, 29), + Color(255, 133, 141), + Color(99, 48, 37), + Color(61, 67, 74) ) resetGraphView() val kosaraju = Kosaraju(graph as DirectedGraph) val result = kosaraju.findStronglyConnectedComponents() for ((i, ccs) in result.withIndex()) { for (vertexId in ccs) { - graphViewModel.verticesView[vertexId]?.color = colors[i % 5] + graphViewModel.verticesView[vertexId]?.color = colors[i % 10] } } } /** Paint each community its own color. The number of colors is limited, - * so if there are more than 5 communities, the colors will begin to repeat. + * so if there are more than 10 communities, the colors will begin to repeat. */ fun runLouvainAlgorithm() { val colors = listOf( @@ -84,14 +89,19 @@ class MainScreenViewModel(private val graph: Graph, private val representa Color(41, 37, 37), Color(145, 86, 86), Color(56, 4, 4), - Color(161, 161, 161) + Color(161, 161, 161), + Color(115, 72, 101), + Color(38, 11, 29), + Color(255, 133, 141), + Color(99, 48, 37), + Color(61, 67, 74) ) resetGraphView() val louvain = Louvain(graph) val result = louvain.detectCommunities() for ((i, community) in result.withIndex()) { for (vertexId in community) { - graphViewModel.verticesView[vertexId]?.color = colors[i % 5] + graphViewModel.verticesView[vertexId]?.color = colors[i % 10] } } } From 2213da7e5919d34ed98de5120cde4b5edaaab1a4 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 24 Sep 2024 03:06:28 +0300 Subject: [PATCH 085/131] style: checkbox made more beautiful --- src/main/kotlin/view/MainScreen.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index f42e87c..fb0c2b6 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -39,14 +39,18 @@ fun MainScreen(viewModel: MainScreenViewModel) { Row { Checkbox(checked = viewModel.showVerticesLabels.value, onCheckedChange = { viewModel.showVerticesLabels.value = it - }) - Text("Show vertices data", fontSize = 20.sp, modifier = Modifier.padding(4.dp)) + }, colors = CheckboxDefaults.colors( + checkedColor = Color(125, 21, 21) + )) + Text("Show vertices data", fontSize = 18.sp, modifier = Modifier.padding(10.dp)) } Row { Checkbox(checked = viewModel.showEdgesLabels.value, onCheckedChange = { viewModel.showEdgesLabels.value = it - }) - Text("Show edges weight", fontSize = 20.sp, modifier = Modifier.padding(4.dp)) + }, colors = CheckboxDefaults.colors( + checkedColor = Color(125, 21, 21) + )) + Text("Show edges weight", fontSize = 18.sp, modifier = Modifier.padding(10.dp)) } Button( onClick = viewModel::resetGraphView, From a2592fb8e6c4cc027819a860387e0d55da5593ff Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Tue, 24 Sep 2024 15:34:58 +0300 Subject: [PATCH 086/131] feat: added color and width variables for viewModelEdge and default setting colors --- .../kotlin/viewmodel/MainScreenViewModel.kt | 11 ++++-- .../kotlin/viewmodel/graph/EdgeViewModel.kt | 35 ++++++++++--------- .../kotlin/viewmodel/graph/GraphViewModel.kt | 11 +++--- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 0cf2ff8..afe71e3 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -9,11 +9,14 @@ import model.algorithms.* import model.graph.DirectedGraph import model.graph.UndirectedGraph +var defaultColorLine: Color = Color.Black +var defaultColorVertex: Color = Color.Gray +var defaultStrokeWidth: Float = 4f + class MainScreenViewModel(private val graph: Graph, private val representationStrategy: RepresentationStrategy) { val showVerticesLabels = mutableStateOf(false) val showEdgesLabels = mutableStateOf(false) val graphViewModel = GraphViewModel(graph, showVerticesLabels, showEdgesLabels) - private var algorithmRunning = false // флаг для отслеживания состояния работы алгоритма init { representationStrategy.place(800.0, 600.0, graphViewModel.verticesView.values) @@ -21,7 +24,11 @@ class MainScreenViewModel(private val graph: Graph, private val representa fun resetGraphView() { representationStrategy.place(800.0, 600.0, graphViewModel.verticesView.values) - graphViewModel.verticesView.values.forEach { v -> v.color = Color.Gray } + graphViewModel.verticesView.values.forEach { v -> v.color = defaultColorVertex } + graphViewModel.edgesView.values.forEach { e -> + e.color = defaultColorLine + e.strokeWidth = defaultStrokeWidth + } } fun addVertex(id: Int, data: D) { diff --git a/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt b/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt index 6c55add..cb1c86e 100644 --- a/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt @@ -1,17 +1,20 @@ -package viewmodel.graph - -import androidx.compose.runtime.State -import model.graph.Edge - -class EdgeViewModel( - val u: VertexViewModel, - val v: VertexViewModel, - private val e: Edge, - private val _labelVisible: State, -) { - val label - get() = e.weight.toString() - - val labelVisible - get() = _labelVisible.value +package viewmodel.graph + +import androidx.compose.runtime.State +import androidx.compose.ui.graphics.Color +import model.graph.Edge + +class EdgeViewModel( + val u: VertexViewModel, + val v: VertexViewModel, + var color: Color, + var strokeWidth: Float, + private val e: Edge, + private val _labelVisible: State, +) { + val label + get() = e.weight.toString() + + val labelVisible + get() = _labelVisible.value } \ No newline at end of file diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index 04db209..1a2f852 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -7,6 +7,9 @@ import model.graph.Edge import model.graph.Graph import model.graph.UndirectedGraph import model.graph.Vertex +import viewmodel.defaultColorLine +import viewmodel.defaultColorVertex +import viewmodel.defaultStrokeWidth class GraphViewModel( private val graph: Graph, @@ -18,20 +21,20 @@ class GraphViewModel( init { graph.getVertices().forEach { vertex -> - verticesView[vertex.id] = VertexViewModel(0.dp, 0.dp, Color.Gray, vertex, showVerticesLabels) + verticesView[vertex.id] = VertexViewModel(0.dp, 0.dp, defaultColorVertex, vertex, showVerticesLabels) } graph.edges.forEach { edge -> val fst = verticesView[edge.vertices.first] ?: throw IllegalStateException("VertexView for vertex with id: ${edge.vertices.first} not found") val snd = verticesView[edge.vertices.second] ?: throw IllegalStateException("VertexView for vertex with id: ${edge.vertices.second} not found") - edgesView[edge] = EdgeViewModel(fst, snd, edge, showEdgesLabels) + edgesView[edge] = EdgeViewModel(fst, snd, defaultColorLine, defaultStrokeWidth, edge, showEdgesLabels) } } fun addVertex(id: Int, data: D) { graph.addVertex(id, data) - verticesView[id] = VertexViewModel(0.dp, 0.dp, Color.Gray, graph.vertices[id]!!, showVerticesLabels) + verticesView[id] = VertexViewModel(0.dp, 0.dp, defaultColorVertex, graph.vertices[id]!!, showVerticesLabels) } fun removeVertex(id: Int) { @@ -48,7 +51,7 @@ class GraphViewModel( val fst = verticesView[from] ?: throw IllegalStateException("VertexView for vertex with id: $from not found") val snd = verticesView[to] ?: throw IllegalStateException("VertexView for vertex with id: $to not found") graph.edges.find { it.vertices == Pair(from, to) }?.let { edge -> - edgesView[edge] = EdgeViewModel(fst, snd, edge, showEdgesLabels) + edgesView[edge] = EdgeViewModel(fst, snd, defaultColorLine, defaultStrokeWidth, edge, showEdgesLabels) } } From 8f1f0dd0a19f55f2569a70344df363dd02d18932 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Tue, 24 Sep 2024 15:51:31 +0300 Subject: [PATCH 087/131] feat: added a function to calculate the coordinates of the edge ends --- src/main/kotlin/view/graph/EdgeView.kt | 48 ++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/view/graph/EdgeView.kt b/src/main/kotlin/view/graph/EdgeView.kt index 704f7bc..d57c4be 100644 --- a/src/main/kotlin/view/graph/EdgeView.kt +++ b/src/main/kotlin/view/graph/EdgeView.kt @@ -7,26 +7,62 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import viewmodel.graph.EdgeViewModel +import kotlin.math.pow +import kotlin.math.sqrt @Composable fun EdgeView( viewModel: EdgeViewModel, modifier: Modifier = Modifier, ) { + + fun calculateEdge(): Pair { + val startX = (viewModel.u.x + viewModel.u.radius).value + val startY = (viewModel.u.y + viewModel.u.radius).value + val endX = (viewModel.v.x + viewModel.v.radius).value + val endY = (viewModel.v.y + viewModel.v.radius).value + val returnStart: DpOffset + val returnEnd: DpOffset + + val difXStart: Float + val difYStart: Float + val difXEnd: Float + val difYEnd: Float + val vertexDistance: Float = + sqrt((startX - endX).pow(2) + (startY - endY).pow(2)) + + val coefficientStart: Float = viewModel.u.radius.value / vertexDistance + val coefficientEnd: Float = viewModel.v.radius.value / vertexDistance + difXStart = coefficientStart * (endX - startX) + difYStart = coefficientStart * (endY - startY) + difXEnd = coefficientEnd * (startX - endX) + difYEnd = coefficientEnd * (startY - endY) + + returnStart = DpOffset( + (startX + difXStart).dp, + (startY + difYStart).dp + ) + returnEnd = DpOffset( + (endX + difXEnd).dp, + (endY + difYEnd).dp + ) + return returnStart to returnEnd + } + Canvas(modifier = modifier.fillMaxSize()) { drawLine( start = Offset( - viewModel.u.x.toPx() + viewModel.u.radius.toPx(), - viewModel.u.y.toPx() + viewModel.u.radius.toPx(), + x = calculateEdge().first.x.toPx(), + y = calculateEdge().first.y.toPx() ), end = Offset( - viewModel.v.x.toPx() + viewModel.v.radius.toPx(), - viewModel.v.y.toPx() + viewModel.v.radius.toPx(), + x = calculateEdge().second.x.toPx(), + y = calculateEdge().second.y.toPx() ), - color = Color.Black + color = viewModel.color ) } if (viewModel.labelVisible) { From b5cd342cbc5e613c607c3dedd69e209bcc2a14de Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 24 Sep 2024 15:57:46 +0300 Subject: [PATCH 088/131] style(MainScreen): the code is formatted --- src/main/kotlin/view/MainScreen.kt | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index fb0c2b6..2530e88 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -37,19 +37,23 @@ fun MainScreen(viewModel: MainScreenViewModel) { ) { Column(modifier = Modifier.width(300.dp).fillMaxHeight().background(Color.Gray)) { Row { - Checkbox(checked = viewModel.showVerticesLabels.value, onCheckedChange = { - viewModel.showVerticesLabels.value = it - }, colors = CheckboxDefaults.colors( - checkedColor = Color(125, 21, 21) - )) + Checkbox( + checked = viewModel.showVerticesLabels.value, onCheckedChange = { + viewModel.showVerticesLabels.value = it + }, colors = CheckboxDefaults.colors( + checkedColor = Color(125, 21, 21) + ) + ) Text("Show vertices data", fontSize = 18.sp, modifier = Modifier.padding(10.dp)) } Row { - Checkbox(checked = viewModel.showEdgesLabels.value, onCheckedChange = { - viewModel.showEdgesLabels.value = it - }, colors = CheckboxDefaults.colors( - checkedColor = Color(125, 21, 21) - )) + Checkbox( + checked = viewModel.showEdgesLabels.value, onCheckedChange = { + viewModel.showEdgesLabels.value = it + }, colors = CheckboxDefaults.colors( + checkedColor = Color(125, 21, 21) + ) + ) Text("Show edges weight", fontSize = 18.sp, modifier = Modifier.padding(10.dp)) } Button( From f1ef7a293c872507841aea96ec3348947d8bf1ad Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Tue, 24 Sep 2024 16:36:34 +0300 Subject: [PATCH 089/131] feat: added botton for Prim's algorithm execution and connected stroke width with edge view --- src/main/kotlin/view/MainScreen.kt | 6 +++++ src/main/kotlin/view/graph/EdgeView.kt | 3 ++- .../kotlin/viewmodel/MainScreenViewModel.kt | 23 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index fb0c2b6..8e1aec9 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -90,6 +90,12 @@ fun MainScreen(viewModel: MainScreenViewModel) { }) { Text("Louvain") } + DropdownMenuItem(onClick = { + expandedAlgorithmsMenu = false + viewModel.runPrimAlgorithm() + }) { + Text("Prim") + } } } Row { diff --git a/src/main/kotlin/view/graph/EdgeView.kt b/src/main/kotlin/view/graph/EdgeView.kt index d57c4be..0fe9757 100644 --- a/src/main/kotlin/view/graph/EdgeView.kt +++ b/src/main/kotlin/view/graph/EdgeView.kt @@ -62,7 +62,8 @@ fun EdgeView( x = calculateEdge().second.x.toPx(), y = calculateEdge().second.y.toPx() ), - color = viewModel.color + color = viewModel.color, + strokeWidth = viewModel.strokeWidth, ) } if (viewModel.labelVisible) { diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index afe71e3..5e82a3a 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -112,4 +112,27 @@ class MainScreenViewModel(private val graph: Graph, private val representa } } } + /** Paints over the vertices and edges that belong to the found MST. + */ + fun runPrimAlgorithm(): Int { + if (graph is DirectedGraph) { + throw IllegalArgumentException("Prims's algorithm cannot be run on directed graphs.") + } + val prim = Prim(graph as UndirectedGraph) + val result = prim.treePrim() + val weight = prim.weightPrim() + resetGraphView() + for (graphComponent in result) { + for (vertexId in graphComponent.vertices.keys) { + graphViewModel.verticesView[vertexId]?.color = Color(10, 230, 208) + } + for (edgeView in graphViewModel.edgesView) { + if (edgeView.key in graphComponent.edges) { + edgeView.value.color = Color(10, 230, 248) + edgeView.value.strokeWidth = 10f + } + } + } + return weight + } } From 228f6160e1d56cc7bf1552d591fafdcfd9753637 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Tue, 24 Sep 2024 17:10:03 +0300 Subject: [PATCH 090/131] feat: added botton for CycleSearch algorithm execution --- src/main/kotlin/view/CommonDialogs.kt | 47 +++++++++++++++++++ src/main/kotlin/view/MainScreen.kt | 17 +++++++ .../kotlin/viewmodel/MainScreenViewModel.kt | 29 +++++++++++- 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/view/CommonDialogs.kt b/src/main/kotlin/view/CommonDialogs.kt index f159b1e..da63811 100644 --- a/src/main/kotlin/view/CommonDialogs.kt +++ b/src/main/kotlin/view/CommonDialogs.kt @@ -64,6 +64,53 @@ fun DijkstraDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) { ) } +@Composable +fun CycleSearchDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int) -> Unit) { + var vertexId by remember { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Input vertex id around that you want to find the cycle:") + }, + text = { + Column( + modifier = Modifier.padding(top = 16.dp) + ) { + TextField( + value = vertexId, + onValueChange = { vertexId = it }, + label = { Text("Your vertex id is") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.padding(bottom = 12.dp) + ) + } + }, + confirmButton = { + Button( + onClick = { + val vertexIdInt = vertexId.toIntOrNull() + + if (vertexIdInt != null) { + onRunAlgorithm(vertexIdInt) + } + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Find the cycle", color = Color(255, 250, 250)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + ) { + Text("Cancel", color = Color(255, 250, 250)) + } + } + ) +} + @Composable fun AddVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, String) -> Unit) { var id by remember { mutableStateOf("") } diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 8e1aec9..a86421a 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -27,6 +27,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { var expandedRemoveMenu by remember { mutableStateOf(false) } var showDijkstraDialog by remember { mutableStateOf(false) } + var showCycleSearchDialog by remember { mutableStateOf(false) } var showAddEdgeDialog by remember { mutableStateOf(false) } var showAddVertexDialog by remember { mutableStateOf(false) } var showRemoveVertexDialog by remember { mutableStateOf(false) } @@ -96,6 +97,13 @@ fun MainScreen(viewModel: MainScreenViewModel) { }) { Text("Prim") } + DropdownMenuItem(onClick = { + expandedAlgorithmsMenu = false + showCycleSearchDialog = true + }) { + Text("CycleSearch") + } + } } Row { @@ -208,6 +216,15 @@ fun MainScreen(viewModel: MainScreenViewModel) { } ) } + if (showCycleSearchDialog) { + CycleSearchDialog( + onDismiss = { showCycleSearchDialog = false }, + onRunAlgorithm = { vertexId -> + viewModel.runCycleSearchAlgorithm(vertexId) + showCycleSearchDialog = false + } + ) + } if (showRemoveVertexDialog) { RemoveVertexDialog( onDismiss = { showRemoveVertexDialog = false }, diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 5e82a3a..4305880 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -8,6 +8,7 @@ import viewmodel.graph.RepresentationStrategy import model.algorithms.* import model.graph.DirectedGraph import model.graph.UndirectedGraph +import model.graph.Vertex var defaultColorLine: Color = Color.Black var defaultColorVertex: Color = Color.Gray @@ -129,10 +130,36 @@ class MainScreenViewModel(private val graph: Graph, private val representa for (edgeView in graphViewModel.edgesView) { if (edgeView.key in graphComponent.edges) { edgeView.value.color = Color(10, 230, 248) - edgeView.value.strokeWidth = 10f + edgeView.value.strokeWidth = 9f } } } return weight } + + fun runCycleSearchAlgorithm(vertexId: Int): Boolean { + if (vertexId !in graph.vertices.keys) { + throw IllegalArgumentException("Vertex with id = $vertexId doesn't exists in the graph.") + } + if (graph is DirectedGraph) { + throw IllegalArgumentException("CycleSearch algorithm cannot be run on directed graphs.") + } + val cycleSearch = CycleSearch(graph as UndirectedGraph) + val result = cycleSearch.findCycle(Vertex(vertexId, graph.vertices[vertexId]!!.data)) + resetGraphView() + if (result != null) { + for (idVertex in result.vertices.keys) { + graphViewModel.verticesView[idVertex]?.color = Color(10, 230, 208) + } + for (edgeView in graphViewModel.edgesView) { + if (edgeView.key in result.edges) { + edgeView.value.color = Color(10, 230, 248) + edgeView.value.strokeWidth = 9f + } + } + return false + } + return true + } + } From 5693bd44914465901460ba6743f3738d4d1eccb1 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 24 Sep 2024 18:08:50 +0300 Subject: [PATCH 091/131] feat: added file with themes and colors --- src/main/kotlin/view/Color.kt | 36 +++++++++++++++++++++++++++++++++++ src/main/kotlin/view/Theme.kt | 21 ++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/main/kotlin/view/Color.kt create mode 100644 src/main/kotlin/view/Theme.kt diff --git a/src/main/kotlin/view/Color.kt b/src/main/kotlin/view/Color.kt new file mode 100644 index 0000000..adbbf87 --- /dev/null +++ b/src/main/kotlin/view/Color.kt @@ -0,0 +1,36 @@ +package view + +import androidx.compose.material3.lightColorScheme +import androidx.compose.ui.graphics.Color + +//TODO: make more suitable colors +val DarkRed = Color(125, 21, 21) +val LightGray = Color(199, 199, 199) +val DarkGray = Color(74, 74, 74) +val Gray = Color(150, 150, 150) +val White = Color(245, 245, 245) +val Black = Color(0, 0, 0) +val DarkRed2 = Color(41, 28, 28) +val LightRed = Color(204, 0, 0) + +val NastyaColorPalette = lightColorScheme( +) + +val KatyaColorPalette = lightColorScheme( + primary = DarkRed, + onPrimary = White, + primaryContainer = LightGray, + onPrimaryContainer = Black, + secondary = DarkGray, + onSecondary = White, + error = LightRed, + onError = Black, + background = LightGray, + onBackground = DarkGray, + surface = Gray, + onSurface = DarkRed2, + outline = DarkRed2 +) + +val LiyaColorPalette = lightColorScheme( +) \ No newline at end of file diff --git a/src/main/kotlin/view/Theme.kt b/src/main/kotlin/view/Theme.kt new file mode 100644 index 0000000..cfa2a53 --- /dev/null +++ b/src/main/kotlin/view/Theme.kt @@ -0,0 +1,21 @@ +package view + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable + +@Composable +fun Material3AppTheme(theme: Theme, content: @Composable () -> Unit) { + val colors = when (theme) { + Theme.NASTYA -> NastyaColorPalette + Theme.KATYA -> KatyaColorPalette + else -> LiyaColorPalette + } + MaterialTheme( + colorScheme = colors, + content = content + ) +} + +enum class Theme { + NASTYA, KATYA, LIYA +} \ No newline at end of file From 646c89b05e912b6c99739eb01b3796e15e229118 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 24 Sep 2024 18:10:19 +0300 Subject: [PATCH 092/131] feat: dependencies added --- build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 595bcb1..64dbfa8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,8 @@ dependencies { // (in a separate module for demo project and in testMain). // With compose.desktop.common you will also lose @Preview functionality implementation(compose.desktop.currentOs) + implementation(compose.material3) + implementation(compose.materialIconsExtended) testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("io.kotest:kotest-runner-junit5-jvm:4.6.0") } From 30353aa4c780b9190337ebd86bdd94db62b986b9 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 24 Sep 2024 18:11:09 +0300 Subject: [PATCH 093/131] feat: added color dependencies on theme --- src/main/kotlin/view/CommonDialogs.kt | 132 +++++++-- src/main/kotlin/view/MainScreen.kt | 381 ++++++++++++++------------ 2 files changed, 313 insertions(+), 200 deletions(-) diff --git a/src/main/kotlin/view/CommonDialogs.kt b/src/main/kotlin/view/CommonDialogs.kt index f159b1e..3d973ab 100644 --- a/src/main/kotlin/view/CommonDialogs.kt +++ b/src/main/kotlin/view/CommonDialogs.kt @@ -1,9 +1,11 @@ package view +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -27,14 +29,28 @@ fun DijkstraDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) { onValueChange = { start = it }, label = { Text("Enter the id of the starting vertex:") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) ) TextField( value = end, onValueChange = { end = it }, label = { Text("Enter the id of the destination vertex:") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) ) } }, @@ -48,17 +64,17 @@ fun DijkstraDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) { onRunAlgorithm(startInt, endInt) } }, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + colors = ButtonDefaults.buttonColors(MaterialTheme.colorScheme.primary) ) { - Text("Find the way", color = Color(255, 250, 250)) + Text("Find the way", color = MaterialTheme.colorScheme.onPrimary) } }, dismissButton = { Button( onClick = onDismiss, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colorScheme.primary) ) { - Text("Cancel", color = Color(255, 250, 250)) + Text("Cancel", color = MaterialTheme.colorScheme.onPrimary) } } ) @@ -81,13 +97,27 @@ fun AddVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, String) -> Unit onValueChange = { id = it }, label = { Text("Enter the id:") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) ) TextField( value = data, onValueChange = { data = it }, label = { Text("Enter the data:") }, - modifier = Modifier.padding(bottom = 12.dp) + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) ) } }, @@ -100,17 +130,17 @@ fun AddVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, String) -> Unit onRunAlgorithm(idInt, data) } }, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colorScheme.primary) ) { - Text("Add the vertex", color = Color(255, 250, 250)) + Text("Add the vertex", color = MaterialTheme.colorScheme.onPrimary) } }, dismissButton = { Button( onClick = onDismiss, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colorScheme.primary) ) { - Text("Cancel", color = Color(255, 250, 250)) + Text("Cancel", color = MaterialTheme.colorScheme.onPrimary) } } ) @@ -131,7 +161,14 @@ fun RemoveVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int) -> Unit) { value = id, onValueChange = { id = it }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) ) } }, @@ -144,17 +181,17 @@ fun RemoveVertexDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int) -> Unit) { onRunAlgorithm(idInt) } }, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colorScheme.primary) ) { - Text("Remove the vertex", color = Color(255, 250, 250)) + Text("Remove the vertex", color = MaterialTheme.colorScheme.onPrimary) } }, dismissButton = { Button( onClick = onDismiss, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colorScheme.primary) ) { - Text("Cancel", color = Color(255, 250, 250)) + Text("Cancel", color = MaterialTheme.colorScheme.onPrimary) } } ) @@ -178,14 +215,28 @@ fun AddEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Uni onValueChange = { from = it }, label = { Text("Enter the start id:") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) ) TextField( value = to, onValueChange = { to = it }, label = { Text("Enter the end id:") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) ) TextField( value = w ?: "", @@ -194,7 +245,14 @@ fun AddEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Uni }, label = { Text("Enter the weight (optional):") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) ) } }, @@ -209,17 +267,17 @@ fun AddEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int, Int?) -> Uni onRunAlgorithm(fromInt, toInt, wInt) } }, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colorScheme.primary) ) { - Text("Add the edge", color = Color(255, 250, 250)) + Text("Add the edge", color = MaterialTheme.colorScheme.onPrimary) } }, dismissButton = { Button( onClick = onDismiss, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colorScheme.primary) ) { - Text("Cancel", color = Color(255, 250, 250)) + Text("Cancel", color = MaterialTheme.colorScheme.onPrimary) } } ) @@ -242,14 +300,28 @@ fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) onValueChange = { from = it }, label = { Text("Enter the start id:") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) ) TextField( value = to, onValueChange = { to = it }, label = { Text("Enter the end id:") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.padding(bottom = 12.dp) + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) ) } }, @@ -263,17 +335,17 @@ fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) onRunAlgorithm(fromInt, toInt) } }, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colorScheme.primary) ) { - Text("Remove the edge", color = Color(255, 250, 250)) + Text("Remove the edge", color = MaterialTheme.colorScheme.onPrimary) } }, dismissButton = { Button( onClick = onDismiss, - colors = ButtonDefaults.buttonColors(backgroundColor = Color(139, 0, 0)) + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colorScheme.primary) ) { - Text("Cancel", color = Color(255, 250, 250)) + Text("Cancel", color = MaterialTheme.colorScheme.onPrimary) } } ) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 2530e88..641131d 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.sp import model.graph.Graph +import androidx.compose.material3.MaterialTheme import org.jetbrains.skia.impl.Stats.enabled import view.graph.GraphView import viewmodel.MainScreenViewModel @@ -22,6 +23,7 @@ import viewmodel.graph.GraphViewModel @Composable fun MainScreen(viewModel: MainScreenViewModel) { + var theme by remember { mutableStateOf(Theme.NASTYA) } var expandedAlgorithmsMenu by remember { mutableStateOf(false) } var expandedAddMenu by remember { mutableStateOf(false) } var expandedRemoveMenu by remember { mutableStateOf(false) } @@ -31,206 +33,245 @@ fun MainScreen(viewModel: MainScreenViewModel) { var showAddVertexDialog by remember { mutableStateOf(false) } var showRemoveVertexDialog by remember { mutableStateOf(false) } var showRemoveEdgeDialog by remember { mutableStateOf(false) } - - Row( - horizontalArrangement = Arrangement.spacedBy(20.dp) - ) { - Column(modifier = Modifier.width(300.dp).fillMaxHeight().background(Color.Gray)) { - Row { - Checkbox( - checked = viewModel.showVerticesLabels.value, onCheckedChange = { - viewModel.showVerticesLabels.value = it - }, colors = CheckboxDefaults.colors( - checkedColor = Color(125, 21, 21) + Material3AppTheme(theme) { + Row( + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) { + Column(modifier = Modifier.width(300.dp).fillMaxHeight().background(MaterialTheme.colorScheme.surface)) { + Row { + Checkbox( + checked = viewModel.showVerticesLabels.value, onCheckedChange = { + viewModel.showVerticesLabels.value = it + }, colors = CheckboxDefaults.colors( + checkedColor = MaterialTheme.colorScheme.primary + ) ) - ) - Text("Show vertices data", fontSize = 18.sp, modifier = Modifier.padding(10.dp)) - } - Row { - Checkbox( - checked = viewModel.showEdgesLabels.value, onCheckedChange = { - viewModel.showEdgesLabels.value = it - }, colors = CheckboxDefaults.colors( - checkedColor = Color(125, 21, 21) + Text( + text = "Show vertices data", + fontSize = 18.sp, + modifier = Modifier.padding(10.dp), + color = MaterialTheme.colorScheme.onSurface ) - ) - Text("Show edges weight", fontSize = 18.sp, modifier = Modifier.padding(10.dp)) - } - Button( - onClick = viewModel::resetGraphView, - enabled = true, - ) { - Text( - text = "Reset default settings", fontSize = 20.sp, modifier = Modifier.padding(4.dp) - ) - } - Box { - Row(verticalAlignment = Alignment.CenterVertically) { - Button(onClick = { expandedAlgorithmsMenu = true }) { - Text("Algorithm") - Icon(Icons.Default.ArrowDropDown, contentDescription = "Select algorithm") - } } - DropdownMenu( - expanded = expandedAlgorithmsMenu, - onDismissRequest = { expandedAlgorithmsMenu = false }, - modifier = Modifier.background(Color.Gray) + Row { + Checkbox( + checked = viewModel.showEdgesLabels.value, onCheckedChange = { + viewModel.showEdgesLabels.value = it + }, colors = CheckboxDefaults.colors( + checkedColor = MaterialTheme.colorScheme.primary + ) + ) + Text( + text = "Show edges weight", + fontSize = 18.sp, + modifier = Modifier.padding(10.dp), + color = MaterialTheme.colorScheme.onSurface + ) + } + Button( + onClick = viewModel::resetGraphView, + enabled = true, + colors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) ) { - DropdownMenuItem(onClick = { - expandedAlgorithmsMenu = false - showDijkstraDialog = true - }) { - Text("Dijkstra") - } - DropdownMenuItem(onClick = { - expandedAlgorithmsMenu = false - viewModel.runKosarajuAlgorithm() - }) { - Text("Kosaraju") - } - DropdownMenuItem(onClick = { - expandedAlgorithmsMenu = false - viewModel.runLouvainAlgorithm() - }) { - Text("Louvain") - } + Text( + text = "Reset default settings", + fontSize = 20.sp, + modifier = Modifier.padding(4.dp), + color = MaterialTheme.colorScheme.onPrimary + ) } - } - Row { - Box(modifier = Modifier.padding(2.dp)) { + Box { Row(verticalAlignment = Alignment.CenterVertically) { - Button(onClick = { expandedAddMenu = true }) { - Text("Add") - Icon(Icons.Default.ArrowDropDown, contentDescription = "Add") + Button( + onClick = { expandedAlgorithmsMenu = true }, colors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text(text = "Algorithm", color = MaterialTheme.colorScheme.onPrimary) + Icon(Icons.Default.ArrowDropDown, contentDescription = "Select algorithm") } } DropdownMenu( - expanded = expandedAddMenu, - onDismissRequest = { expandedAddMenu = false }, - modifier = Modifier.background(Color.Gray) + expanded = expandedAlgorithmsMenu, + onDismissRequest = { expandedAlgorithmsMenu = false }, + modifier = Modifier.background(MaterialTheme.colorScheme.secondary) ) { DropdownMenuItem(onClick = { - expandedAddMenu = false - showAddVertexDialog = true + expandedAlgorithmsMenu = false + showDijkstraDialog = true + }) { + Text(text = "Dijkstra", color = MaterialTheme.colorScheme.onSecondary) + } + DropdownMenuItem(onClick = { + expandedAlgorithmsMenu = false + viewModel.runKosarajuAlgorithm() }) { - Text("Add vertex") + Text(text = "Kosaraju", color = MaterialTheme.colorScheme.onSecondary) } DropdownMenuItem(onClick = { - expandedAddMenu = false - showAddEdgeDialog = true + expandedAlgorithmsMenu = false + viewModel.runLouvainAlgorithm() }) { - Text("Add edge") + Text(text = "Louvain", color = MaterialTheme.colorScheme.onSecondary) } } } - Box(modifier = Modifier.padding(2.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { - Button(onClick = { expandedRemoveMenu = true }) { - Text("Remove") - Icon(Icons.Default.ArrowDropDown, contentDescription = "Remove") + Row { + Box(modifier = Modifier.padding(2.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Button( + onClick = { expandedAddMenu = true }, colors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text(text = "Add", color = MaterialTheme.colorScheme.onPrimary) + Icon(Icons.Default.ArrowDropDown, contentDescription = "Add") + } + } + DropdownMenu( + expanded = expandedAddMenu, + onDismissRequest = { expandedAddMenu = false }, + modifier = Modifier.background(MaterialTheme.colorScheme.secondary) + ) { + DropdownMenuItem(onClick = { + expandedAddMenu = false + showAddVertexDialog = true + }) { + Text("Add vertex", color = MaterialTheme.colorScheme.onSecondary) + } + DropdownMenuItem(onClick = { + expandedAddMenu = false + showAddEdgeDialog = true + }) { + Text("Add edge", color = MaterialTheme.colorScheme.onSecondary) + } } } - DropdownMenu( - expanded = expandedRemoveMenu, - onDismissRequest = { expandedRemoveMenu = false }, - modifier = Modifier.background(Color.Gray) - ) { - DropdownMenuItem(onClick = { - expandedRemoveMenu = false - showRemoveVertexDialog = true - }) { - Text("Remove vertex") + Box(modifier = Modifier.padding(2.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Button( + onClick = { expandedRemoveMenu = true }, colors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text(text = "Remove", color = MaterialTheme.colorScheme.onPrimary) + Icon(Icons.Default.ArrowDropDown, contentDescription = "Remove") + } } - DropdownMenuItem(onClick = { - expandedRemoveMenu = false - showRemoveEdgeDialog = true - }) { - Text("Remove edge") + DropdownMenu( + expanded = expandedRemoveMenu, + onDismissRequest = { expandedRemoveMenu = false }, + modifier = Modifier.background(MaterialTheme.colorScheme.secondary) + ) { + DropdownMenuItem(onClick = { + expandedRemoveMenu = false + showRemoveVertexDialog = true + }) { + Text("Remove vertex", color = MaterialTheme.colorScheme.onSecondary) + } + DropdownMenuItem(onClick = { + expandedRemoveMenu = false + showRemoveEdgeDialog = true + }) { + Text("Remove edge", color = MaterialTheme.colorScheme.onSecondary) + } } } } } - } - Box { - var expandedThemeMenu by remember { mutableStateOf(false) } - Button( - onClick = { expandedThemeMenu = true }, - modifier = Modifier.align(Alignment.TopEnd) - ) { - Text("Theme") - Icon(Icons.Default.ArrowDropDown, contentDescription = "Select theme") + Box { + var expandedThemeMenu by remember { mutableStateOf(false) } + Button( + onClick = { expandedThemeMenu = true }, + modifier = Modifier.align(Alignment.TopEnd), colors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text(text = "Theme", color = MaterialTheme.colorScheme.onPrimary) + Icon(Icons.Default.ArrowDropDown, contentDescription = "Select theme") + } + DropdownMenu( + expanded = expandedThemeMenu, + onDismissRequest = { expandedThemeMenu = false }, + modifier = Modifier.background(MaterialTheme.colorScheme.secondary) + ) { + DropdownMenuItem(onClick = { + theme = Theme.NASTYA + expandedThemeMenu = false + }) { + Text("Nastya's theme", color = MaterialTheme.colorScheme.onSecondary) + } + DropdownMenuItem(onClick = { + theme = Theme.LIYA + expandedThemeMenu = false + }) { + Text("Liya's theme", color = MaterialTheme.colorScheme.onSecondary) + } + DropdownMenuItem(onClick = { + theme = Theme.KATYA + expandedThemeMenu = false + }) { + Text("Katya's theme", color = MaterialTheme.colorScheme.onSecondary) + } + } } - DropdownMenu( - expanded = expandedThemeMenu, - onDismissRequest = { expandedThemeMenu = false }, - modifier = Modifier.background(Color.Red) + Surface( + modifier = Modifier.weight(1f).background(MaterialTheme.colorScheme.background), ) { - DropdownMenuItem(onClick = { - expandedThemeMenu = false - }) { - Text("Nastya's theme") + GraphView(viewModel.graphViewModel) + } + } + if (showDijkstraDialog) { + DijkstraDialog( + onDismiss = { showDijkstraDialog = false }, + onRunAlgorithm = { start, end -> + viewModel.runDijkstraAlgorithm(start, end) + showDijkstraDialog = false } - DropdownMenuItem(onClick = { - expandedThemeMenu = false - }) { - Text("Liya's theme") + ) + } + if (showAddVertexDialog) { + AddVertexDialog( + onDismiss = { showAddVertexDialog = false }, + onRunAlgorithm = { id, data -> + viewModel.addVertex(id, data as D) + showAddVertexDialog = false } - DropdownMenuItem(onClick = { - expandedThemeMenu = false - }) { - Text("Katya's theme") + ) + } + if (showRemoveVertexDialog) { + RemoveVertexDialog( + onDismiss = { showRemoveVertexDialog = false }, + onRunAlgorithm = { id -> + viewModel.removeVertex(id) + showRemoveVertexDialog = false } - } + ) } - Surface( - modifier = Modifier.weight(1f), - ) { - GraphView(viewModel.graphViewModel) + if (showAddEdgeDialog) { + AddEdgeDialog( + onDismiss = { showAddEdgeDialog = false }, + onRunAlgorithm = { from, to, w -> + viewModel.addEdge(from, to, w) + showAddEdgeDialog = false + } + ) + } + if (showRemoveEdgeDialog) { + RemoveEdgeDialog( + onDismiss = { showRemoveEdgeDialog = false }, + onRunAlgorithm = { from, to -> + viewModel.removeEdge(from, to) + showRemoveEdgeDialog = false + } + ) } } - if (showDijkstraDialog) { - DijkstraDialog( - onDismiss = { showDijkstraDialog = false }, - onRunAlgorithm = { start, end -> - viewModel.runDijkstraAlgorithm(start, end) - showDijkstraDialog = false - } - ) - } - if (showAddVertexDialog) { - AddVertexDialog( - onDismiss = { showAddVertexDialog = false }, - onRunAlgorithm = { id, data -> - viewModel.addVertex(id, data as D) - showAddVertexDialog = false - } - ) - } - if (showRemoveVertexDialog) { - RemoveVertexDialog( - onDismiss = { showRemoveVertexDialog = false }, - onRunAlgorithm = { id -> - viewModel.removeVertex(id) - showRemoveVertexDialog = false - } - ) - } - if (showAddEdgeDialog) { - AddEdgeDialog( - onDismiss = { showAddEdgeDialog = false }, - onRunAlgorithm = { from, to, w -> - viewModel.addEdge(from, to, w) - showAddEdgeDialog = false - } - ) - } - if (showRemoveEdgeDialog) { - RemoveEdgeDialog( - onDismiss = { showRemoveEdgeDialog = false }, - onRunAlgorithm = { from, to -> - viewModel.removeEdge(from, to) - showRemoveEdgeDialog = false - } - ) - } -} +} \ No newline at end of file From 77d6aa2ffbf56a27e3ed429db9f45c1e0fe931de Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 24 Sep 2024 23:35:40 +0300 Subject: [PATCH 094/131] feat: added highlighting of edges in Dijkstra --- src/main/kotlin/viewmodel/MainScreenViewModel.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 08ef033..784e097 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -48,7 +48,7 @@ class MainScreenViewModel(private val graph: Graph, private val representa graphViewModel.removeEdge(from, to) } - /** Paint the vertices of the found path. + /** Paint the vertices and edges of the found path. */ fun runDijkstraAlgorithm(start: Int, end: Int) { resetGraphView() @@ -57,6 +57,12 @@ class MainScreenViewModel(private val graph: Graph, private val representa for (vertexId in result) { graphViewModel.verticesView[vertexId]?.color = Color(125, 21, 21) } + for (edgeView in graphViewModel.edgesView) { + if (edgeView.key.vertices.first in result && edgeView.key.vertices.second in result) { + edgeView.value.color = Color(10, 230, 248) + edgeView.value.strokeWidth = 9f + } + } } /** Paint each ccs its own color. The number of colors is limited, From 5d02acfffdda40425e04a1d167f3bf9e0e3cbdbd Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Thu, 26 Sep 2024 17:22:14 +0300 Subject: [PATCH 095/131] feat: added neo4j dependencies --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index 64dbfa8..6b874a5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(compose.desktop.currentOs) implementation(compose.material3) implementation(compose.materialIconsExtended) + implementation("org.neo4j.driver", "neo4j-java-driver", "5.6.0") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("io.kotest:kotest-runner-junit5-jvm:4.6.0") } From 8abe734f18a86ba4c3e4b2a8f51f9dc37b6bcedc Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Thu, 26 Sep 2024 17:23:04 +0300 Subject: [PATCH 096/131] feat: added neo4j repository --- src/main/kotlin/databases/Neo4jRepository.kt | 55 ++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/main/kotlin/databases/Neo4jRepository.kt diff --git a/src/main/kotlin/databases/Neo4jRepository.kt b/src/main/kotlin/databases/Neo4jRepository.kt new file mode 100644 index 0000000..b60865a --- /dev/null +++ b/src/main/kotlin/databases/Neo4jRepository.kt @@ -0,0 +1,55 @@ +package databases + +import model.graph.DirectedGraph +import model.graph.Graph +import model.graph.UndirectedGraph +import org.neo4j.driver.* +import java.io.Closeable + +class Neo4jRepository(uri: String, user: String, password: String) : Closeable { + private val driver: Driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) + private val session: Session = driver.session() + + private fun addVertex(vertexId: Int, vertexData: String, transaction: Transaction) { + transaction.run( + "CREATE (:Vertex {id: \$id, data: \$data})", + Values.parameters("id", vertexId, "data", vertexData) + ) + } + + private fun addEdge(verticesId: Pair, weight: Int?, transaction: Transaction) { + transaction.run( + "MATCH (v1:Vertex {id:\$id1}) MATCH (v2:Vertex {id:\$id2}) " + + "MERGE (v1)-[r:EDGE${if (weight != null) " {weight: \$weight}" else ""}]->(v2)", + Values.parameters("id1", verticesId.first, "id2", verticesId.second, "weight", weight) + ) + } + + private fun clearDatabase(transaction: Transaction) { + transaction.run("MATCH (n) DETACH DELETE n") + } + + fun addGraph(graph: Graph) { + val transaction = session.beginTransaction() + try { + clearDatabase(transaction) + for ((id, data) in graph.vertices) { + addVertex(id, data.toString(), transaction) + } + for (edge in graph.edges) { + addEdge(edge.vertices, edge.weight, transaction) + } + transaction.commit() + } catch (e: Exception) { + transaction.rollback() + throw e + } finally { + transaction.close() + } + } + + override fun close() { + session.close() + driver.close() + } +} \ No newline at end of file From 154ead8473b8766c429ed4f4821ea6530d61edc3 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Thu, 26 Sep 2024 17:50:22 +0300 Subject: [PATCH 097/131] feat: added Neo4j dialog --- src/main/kotlin/view/CommonDialogs.kt | 80 ++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/view/CommonDialogs.kt b/src/main/kotlin/view/CommonDialogs.kt index 8682e02..2e4d2ac 100644 --- a/src/main/kotlin/view/CommonDialogs.kt +++ b/src/main/kotlin/view/CommonDialogs.kt @@ -1,17 +1,93 @@ package view import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +@Composable +fun SaveToNeo4jDialog(onDismiss: () -> Unit, onRunAlgorithm: (String, String, String) -> Unit) { + var uri by remember { mutableStateOf("") } + var user by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Enter your Neo4j details") + }, + text = { + Column(modifier = Modifier.padding(16.dp)) { + TextField( + value = uri, + onValueChange = { uri = it }, + label = { Text("Uri") }, + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) + ) + TextField( + value = user, + onValueChange = { user = it }, + label = { Text("User") }, + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) + ) + TextField( + value = password, + onValueChange = { password = it }, + label = { Text("Password") }, + modifier = Modifier.padding(bottom = 12.dp), + colors = TextFieldDefaults.textFieldColors( + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + unfocusedIndicatorColor = MaterialTheme.colorScheme.secondary, + focusedLabelColor = MaterialTheme.colorScheme.primary, + unfocusedLabelColor = MaterialTheme.colorScheme.secondary + ) + ) + } + }, + confirmButton = { + Button( + onClick = { + onRunAlgorithm(uri, user, password) + }, + colors = ButtonDefaults.buttonColors(MaterialTheme.colorScheme.primary) + ) { + Text("Save graph", color = MaterialTheme.colorScheme.onPrimary) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colorScheme.primary) + ) { + Text("Cancel", color = MaterialTheme.colorScheme.onPrimary) + } + } + ) +} + @Composable fun DijkstraDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) { var start by remember { mutableStateOf("") } From e50d7ee0d3ac7558651f976392d1e8e4cd83367c Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Thu, 26 Sep 2024 17:51:39 +0300 Subject: [PATCH 098/131] feat: saving to neo4j added to gui --- src/main/kotlin/view/MainScreen.kt | 36 +++++++++++++++++++ .../kotlin/viewmodel/MainScreenViewModel.kt | 6 ++++ 2 files changed, 42 insertions(+) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 4fffc4c..fa0768a 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -27,6 +27,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { var expandedAlgorithmsMenu by remember { mutableStateOf(false) } var expandedAddMenu by remember { mutableStateOf(false) } var expandedRemoveMenu by remember { mutableStateOf(false) } + var expandedSaveMenu by remember { mutableStateOf(false) } var showDijkstraDialog by remember { mutableStateOf(false) } var showCycleSearchDialog by remember { mutableStateOf(false) } @@ -34,6 +35,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { var showAddVertexDialog by remember { mutableStateOf(false) } var showRemoveVertexDialog by remember { mutableStateOf(false) } var showRemoveEdgeDialog by remember { mutableStateOf(false) } + var showNeo4jDialog by remember { mutableStateOf(false) } Material3AppTheme(theme) { Row( horizontalArrangement = Arrangement.spacedBy(20.dp) @@ -197,6 +199,31 @@ fun MainScreen(viewModel: MainScreenViewModel) { } } } + Box(modifier = Modifier.padding(2.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Button( + onClick = { expandedSaveMenu = true }, colors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text(text = "Save graph", color = MaterialTheme.colorScheme.onPrimary) + Icon(Icons.Default.ArrowDropDown, contentDescription = "Save graph") + } + } + DropdownMenu( + expanded = expandedSaveMenu, + onDismissRequest = { expandedSaveMenu = false }, + modifier = Modifier.background(MaterialTheme.colorScheme.secondary) + ) { + DropdownMenuItem(onClick = { + expandedSaveMenu = false + showNeo4jDialog = true + }) { + Text("Save to Neo4j", color = MaterialTheme.colorScheme.onSecondary) + } + } + } } Box { var expandedThemeMenu by remember { mutableStateOf(false) } @@ -295,5 +322,14 @@ fun MainScreen(viewModel: MainScreenViewModel) { } ) } + if (showNeo4jDialog) { + SaveToNeo4jDialog( + onDismiss = { showNeo4jDialog = false }, + onRunAlgorithm = { uri, user, password -> + viewModel.saveToNeo4j(uri, user, password) + showNeo4jDialog = false + } + ) + } } } \ No newline at end of file diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 784e097..19b65d6 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -2,6 +2,7 @@ package viewmodel import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color +import databases.Neo4jRepository import model.graph.Graph import viewmodel.graph.GraphViewModel import viewmodel.graph.RepresentationStrategy @@ -36,6 +37,11 @@ class MainScreenViewModel(private val graph: Graph, private val representa graphViewModel.addVertex(id, data) } + fun saveToNeo4j(uri: String, user: String, password: String) { + val neo = Neo4jRepository(uri, user, password) + neo.addGraph(graph) + } + fun addEdge(from: Int, to: Int, w: Int?) { graphViewModel.addEdge(from, to, w) } From 91dd51e2c43c9217623f99a89ef5f4e983ebe553 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Thu, 26 Sep 2024 17:56:41 +0300 Subject: [PATCH 099/131] refactor: variable renamed --- src/main/kotlin/viewmodel/MainScreenViewModel.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 19b65d6..0bccc8d 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -37,11 +37,6 @@ class MainScreenViewModel(private val graph: Graph, private val representa graphViewModel.addVertex(id, data) } - fun saveToNeo4j(uri: String, user: String, password: String) { - val neo = Neo4jRepository(uri, user, password) - neo.addGraph(graph) - } - fun addEdge(from: Int, to: Int, w: Int?) { graphViewModel.addEdge(from, to, w) } @@ -54,6 +49,11 @@ class MainScreenViewModel(private val graph: Graph, private val representa graphViewModel.removeEdge(from, to) } + fun saveToNeo4j(uri: String, user: String, password: String) { + val neo4j = Neo4jRepository(uri, user, password) + neo4j.addGraph(graph) + } + /** Paint the vertices and edges of the found path. */ fun runDijkstraAlgorithm(start: Int, end: Int) { From 7566fb402109400035f4bd916f37674d22f15a26 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Thu, 26 Sep 2024 22:28:24 +0300 Subject: [PATCH 100/131] refactor: function replaced with string --- src/main/kotlin/databases/Neo4jRepository.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/kotlin/databases/Neo4jRepository.kt b/src/main/kotlin/databases/Neo4jRepository.kt index b60865a..b20c92b 100644 --- a/src/main/kotlin/databases/Neo4jRepository.kt +++ b/src/main/kotlin/databases/Neo4jRepository.kt @@ -25,14 +25,10 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { ) } - private fun clearDatabase(transaction: Transaction) { - transaction.run("MATCH (n) DETACH DELETE n") - } - fun addGraph(graph: Graph) { val transaction = session.beginTransaction() try { - clearDatabase(transaction) + transaction.run("MATCH (n) DETACH DELETE n") for ((id, data) in graph.vertices) { addVertex(id, data.toString(), transaction) } From ff6ec0286b65bdf6b97eee94e891bb6a964db910 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 7 Oct 2024 21:31:09 +0300 Subject: [PATCH 101/131] feat: added loading from Neo4j --- src/main/kotlin/databases/Neo4jRepository.kt | 34 +++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/databases/Neo4jRepository.kt b/src/main/kotlin/databases/Neo4jRepository.kt index b20c92b..6c6e9ad 100644 --- a/src/main/kotlin/databases/Neo4jRepository.kt +++ b/src/main/kotlin/databases/Neo4jRepository.kt @@ -25,7 +25,7 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { ) } - fun addGraph(graph: Graph) { + fun saveGraph(graph: Graph) { val transaction = session.beginTransaction() try { transaction.run("MATCH (n) DETACH DELETE n") @@ -44,6 +44,38 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { } } + fun loadGraph(): Graph { + val graph = DirectedGraph() + val transaction = session.beginTransaction() + try { + val verticesResult = transaction.run("MATCH (v:Vertex) RETURN v.id AS id, v.data AS data") + while (verticesResult.hasNext()) { + val record = verticesResult.next() + val id = record.get("id").asInt() + val data = record.get("data").asString() + graph.addVertex(id, data) + } + val edgesResult = transaction.run( + "MATCH (v1:Vertex)-[r:EDGE]->(v2:Vertex) " + + "RETURN v1.id AS id1, v2.id AS id2, r.weight AS weight" + ) + while (edgesResult.hasNext()) { + val record = edgesResult.next() + val id1 = record.get("id1").asInt() + val id2 = record.get("id2").asInt() + val weight = if (record.containsKey("weight")) record.get("weight").asInt() else null + graph.addEdge(Pair(id1, id2), weight) + } + transaction.commit() + } catch (e: Exception) { + transaction.rollback() + throw e + } finally { + transaction.close() + } + return graph + } + override fun close() { session.close() driver.close() From 10d8795b77cb93bfd520ed376a67e691ece90e8e Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 7 Oct 2024 21:36:36 +0300 Subject: [PATCH 102/131] feat: added Welcome screen --- src/main/kotlin/Main.kt | 58 +++++++++++++-------------- src/main/kotlin/view/WelcomeScreen.kt | 29 ++++++++++++++ 2 files changed, 56 insertions(+), 31 deletions(-) create mode 100644 src/main/kotlin/view/WelcomeScreen.kt diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index baea5ce..1bb938a 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,45 +1,41 @@ import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier import androidx.compose.ui.window.Window import androidx.compose.ui.window.application -import model.graph.DirectedGraph -import model.graph.Graph -import model.graph.UndirectedGraph +import databases.Neo4jRepository +import model.graph.* import view.MainScreen +import view.WelcomeScreen import viewmodel.MainScreenViewModel import viewmodel.graph.CircularPlacementStrategy -val sampleGraph = DirectedGraph().apply { - addVertex(1, "A") - addVertex(2, "B") - addVertex(3, "C") - addVertex(4, "D") - addVertex(5, "E") - addVertex(6, "F") - addVertex(7, "G") - addVertex(8, "H") - addVertex(9, "I") - addEdge(Pair(1, 2), 4) - addEdge(Pair(2, 3), 12) - addEdge(Pair(3, 4), 3) - addEdge(Pair(4, 3), 56) - addEdge(Pair(7, 8), 0) - addEdge(Pair(8, 9), 19) - addEdge(Pair(9, 7), 2) -} - @Composable @Preview fun App() { MaterialTheme { - MainScreen(MainScreenViewModel(sampleGraph, CircularPlacementStrategy())) + // пока что, для удобства разработки + val neo4j = Neo4jRepository("bolt://localhost:7687", "neo4j", "password") + var graphType by remember { mutableStateOf(null) } + var sampleGraph by remember { mutableStateOf?>(null) } + if (graphType == null) { + WelcomeScreen { selectedGraphType -> + graphType = selectedGraphType + sampleGraph = when (graphType) { + "Directed" -> DirectedGraph() + "Neo4j" -> neo4j.loadGraph() + else -> UndirectedGraph() + } + } + } else { + sampleGraph?.let { + MainScreen(MainScreenViewModel(it, CircularPlacementStrategy())) + } + } } } diff --git a/src/main/kotlin/view/WelcomeScreen.kt b/src/main/kotlin/view/WelcomeScreen.kt new file mode 100644 index 0000000..a8ef410 --- /dev/null +++ b/src/main/kotlin/view/WelcomeScreen.kt @@ -0,0 +1,29 @@ +package view + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier + +@Composable +fun WelcomeScreen(selectType: (String) -> Unit) { + Surface(modifier = Modifier.fillMaxSize()) { + Column { + Text("Welcome to the Graph Application") + Button(onClick = { selectType("Undirected") }) { + Text("Select Undirected graph") + } + Button(onClick = { selectType("Directed") }) { + Text("Select Directed graph") + } + Button(onClick = { selectType("Neo4j") }) { + Text("Load Neo4j graph") + } + } + } +} From fe8e96d3c834d89a2a60755b5c7f44787d84751e Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 7 Oct 2024 21:37:17 +0300 Subject: [PATCH 103/131] fix: renamed function --- src/main/kotlin/viewmodel/MainScreenViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 0bccc8d..8d0e2c4 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -51,7 +51,7 @@ class MainScreenViewModel(private val graph: Graph, private val representa fun saveToNeo4j(uri: String, user: String, password: String) { val neo4j = Neo4jRepository(uri, user, password) - neo4j.addGraph(graph) + neo4j.saveGraph(graph) } /** Paint the vertices and edges of the found path. From bb7cd7695a783d92a579b896d332cab9bd09be58 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Mon, 7 Oct 2024 22:09:35 +0300 Subject: [PATCH 104/131] fix: allowed graph data type changed --- src/main/kotlin/Main.kt | 6 +++--- src/main/kotlin/databases/Neo4jRepository.kt | 17 ++++++++--------- src/main/kotlin/model/algorithms/Dijkstra.kt | 2 +- src/main/kotlin/model/algorithms/Kosaraju.kt | 8 ++++---- src/main/kotlin/model/algorithms/Louvain.kt | 2 +- src/main/kotlin/model/graph/DirectedGraph.kt | 2 +- src/main/kotlin/model/graph/Edge.kt | 2 +- src/main/kotlin/model/graph/Graph.kt | 10 +++++----- src/main/kotlin/model/graph/UndirectedGraph.kt | 2 +- src/main/kotlin/model/graph/Vertex.kt | 2 +- .../kotlin/viewmodel/MainScreenViewModel.kt | 10 +++++----- .../graph/CircularPlacementStrategy.kt | 2 +- .../kotlin/viewmodel/graph/EdgeViewModel.kt | 8 ++++---- .../kotlin/viewmodel/graph/GraphViewModel.kt | 10 +++++----- .../viewmodel/graph/RepresentationStrategy.kt | 2 +- .../kotlin/viewmodel/graph/VertexViewModel.kt | 4 ++-- src/test/kotlin/DijkstraTest.kt | 4 ++-- src/test/kotlin/KosarajuTest.kt | 2 +- src/test/kotlin/LouvainTest.kt | 4 ++-- 19 files changed, 49 insertions(+), 50 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 1bb938a..5655a98 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -21,14 +21,14 @@ fun App() { // пока что, для удобства разработки val neo4j = Neo4jRepository("bolt://localhost:7687", "neo4j", "password") var graphType by remember { mutableStateOf(null) } - var sampleGraph by remember { mutableStateOf?>(null) } + var sampleGraph by remember { mutableStateOf(null) } if (graphType == null) { WelcomeScreen { selectedGraphType -> graphType = selectedGraphType sampleGraph = when (graphType) { - "Directed" -> DirectedGraph() + "Directed" -> DirectedGraph() "Neo4j" -> neo4j.loadGraph() - else -> UndirectedGraph() + else -> UndirectedGraph() } } } else { diff --git a/src/main/kotlin/databases/Neo4jRepository.kt b/src/main/kotlin/databases/Neo4jRepository.kt index 6c6e9ad..598bddb 100644 --- a/src/main/kotlin/databases/Neo4jRepository.kt +++ b/src/main/kotlin/databases/Neo4jRepository.kt @@ -7,6 +7,7 @@ import org.neo4j.driver.* import java.io.Closeable class Neo4jRepository(uri: String, user: String, password: String) : Closeable { + // bolt://localhost:7687 private val driver: Driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) private val session: Session = driver.session() @@ -25,12 +26,12 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { ) } - fun saveGraph(graph: Graph) { + fun saveGraph(graph: Graph) { val transaction = session.beginTransaction() try { transaction.run("MATCH (n) DETACH DELETE n") - for ((id, data) in graph.vertices) { - addVertex(id, data.toString(), transaction) + for ((id, vertex) in graph.vertices) { + addVertex(id, vertex.data, transaction) } for (edge in graph.edges) { addEdge(edge.vertices, edge.weight, transaction) @@ -44,8 +45,8 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { } } - fun loadGraph(): Graph { - val graph = DirectedGraph() + fun loadGraph() : Graph { + val graph = DirectedGraph() val transaction = session.beginTransaction() try { val verticesResult = transaction.run("MATCH (v:Vertex) RETURN v.id AS id, v.data AS data") @@ -55,10 +56,8 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { val data = record.get("data").asString() graph.addVertex(id, data) } - val edgesResult = transaction.run( - "MATCH (v1:Vertex)-[r:EDGE]->(v2:Vertex) " + - "RETURN v1.id AS id1, v2.id AS id2, r.weight AS weight" - ) + val edgesResult = transaction.run("MATCH (v1:Vertex)-[r:EDGE]->(v2:Vertex) " + + "RETURN v1.id AS id1, v2.id AS id2, r.weight AS weight") while (edgesResult.hasNext()) { val record = edgesResult.next() val id1 = record.get("id1").asInt() diff --git a/src/main/kotlin/model/algorithms/Dijkstra.kt b/src/main/kotlin/model/algorithms/Dijkstra.kt index 37a643f..bec7bdd 100644 --- a/src/main/kotlin/model/algorithms/Dijkstra.kt +++ b/src/main/kotlin/model/algorithms/Dijkstra.kt @@ -11,7 +11,7 @@ import java.util.PriorityQueue * @property previousVertices Stores previous vertices along the shortest path to each vertex. * @property queue Queue for processing vertices in order of increasing distances. */ -class Dijkstra(private val graph: Graph) { +class Dijkstra(private val graph: Graph) { private val distances = hashMapOf() private val previousVertices = hashMapOf() private val queue = PriorityQueue>(compareBy { it.second }) diff --git a/src/main/kotlin/model/algorithms/Kosaraju.kt b/src/main/kotlin/model/algorithms/Kosaraju.kt index e97475e..8703faa 100644 --- a/src/main/kotlin/model/algorithms/Kosaraju.kt +++ b/src/main/kotlin/model/algorithms/Kosaraju.kt @@ -10,7 +10,7 @@ import model.graph.DirectedGraph * @property stack a stack for storing vertices in the order in which they were traversed * @property stronglyConnectedComponents list of strongly connected components in graph */ -class Kosaraju(private val graph: DirectedGraph) { +class Kosaraju(private val graph: DirectedGraph) { private val visited = hashMapOf() private val stack = mutableListOf() private val stronglyConnectedComponents = mutableListOf>() @@ -44,7 +44,7 @@ class Kosaraju(private val graph: DirectedGraph) { * @param stack for storing vertices in traversed order * @param graph graph in which the traversal occurs */ - private fun dfs(vertexID: Int, stack: MutableList, graph: DirectedGraph) { + private fun dfs(vertexID: Int, stack: MutableList, graph: DirectedGraph) { visited[vertexID] = true for (nextVertexID in (graph.adjacency[vertexID]?.keys ?: emptyList())) if (visited[nextVertexID] != true) { @@ -57,8 +57,8 @@ class Kosaraju(private val graph: DirectedGraph) { * The transposeGraph method transposes the graph. * @return the transposed graph */ - private fun transposeGraph(): DirectedGraph { - val transposedGraph = DirectedGraph() + private fun transposeGraph(): DirectedGraph { + val transposedGraph = DirectedGraph() for ((id, vertex) in graph.vertices) { transposedGraph.addVertex(id, vertex.data) } diff --git a/src/main/kotlin/model/algorithms/Louvain.kt b/src/main/kotlin/model/algorithms/Louvain.kt index 1b78111..7cd806f 100644 --- a/src/main/kotlin/model/algorithms/Louvain.kt +++ b/src/main/kotlin/model/algorithms/Louvain.kt @@ -6,7 +6,7 @@ import model.graph.Graph * Louvain algorithm for detecting communities in a graph. * @param graph The graph in which communities should be discovered. */ -class Louvain(private val graph: Graph) { +class Louvain(private val graph: Graph) { /** * Method for detecting communities in a graph using the Louvain algorithm. diff --git a/src/main/kotlin/model/graph/DirectedGraph.kt b/src/main/kotlin/model/graph/DirectedGraph.kt index 32e0494..30f621b 100644 --- a/src/main/kotlin/model/graph/DirectedGraph.kt +++ b/src/main/kotlin/model/graph/DirectedGraph.kt @@ -1,6 +1,6 @@ package model.graph -class DirectedGraph() : Graph() { +class DirectedGraph() : Graph() { override fun addEdge(v: Pair, w: Int?) { // добавить проверку на повторку ребра if (!vertices.containsKey(v.first)) { diff --git a/src/main/kotlin/model/graph/Edge.kt b/src/main/kotlin/model/graph/Edge.kt index 3221b30..ddc9278 100644 --- a/src/main/kotlin/model/graph/Edge.kt +++ b/src/main/kotlin/model/graph/Edge.kt @@ -1,4 +1,4 @@ package model.graph -data class Edge(val vertices: Pair, var weight: Int?) { +data class Edge(val vertices: Pair, var weight: Int?) { } diff --git a/src/main/kotlin/model/graph/Graph.kt b/src/main/kotlin/model/graph/Graph.kt index be7714a..79794e7 100644 --- a/src/main/kotlin/model/graph/Graph.kt +++ b/src/main/kotlin/model/graph/Graph.kt @@ -1,15 +1,15 @@ package model.graph -abstract class Graph { +abstract class Graph { val adjacency = hashMapOf>() - val vertices = hashMapOf>() - val edges = mutableListOf>() + val vertices = hashMapOf() + val edges = mutableListOf() - fun getVertices(): Collection> = vertices.values + fun getVertices(): Collection = vertices.values abstract fun addEdge(v: Pair, w: Int?) abstract fun removeEdge(v: Pair) abstract fun removeVertex(id: Int) - fun addVertex(id: Int, data: D) { + fun addVertex(id: Int, data: String) { if (vertices.containsKey(id)) { throw IllegalArgumentException("Vertex with id: $id already exists in the graph.") } diff --git a/src/main/kotlin/model/graph/UndirectedGraph.kt b/src/main/kotlin/model/graph/UndirectedGraph.kt index 87fa1c2..d25a2b9 100644 --- a/src/main/kotlin/model/graph/UndirectedGraph.kt +++ b/src/main/kotlin/model/graph/UndirectedGraph.kt @@ -1,6 +1,6 @@ package model.graph -class UndirectedGraph() : Graph() { +class UndirectedGraph() : Graph() { override fun addEdge(v: Pair, w: Int?) { // добавить проверку на повторку ребра if (!vertices.containsKey(v.first)) { diff --git a/src/main/kotlin/model/graph/Vertex.kt b/src/main/kotlin/model/graph/Vertex.kt index aebc32a..a4cca61 100644 --- a/src/main/kotlin/model/graph/Vertex.kt +++ b/src/main/kotlin/model/graph/Vertex.kt @@ -1,4 +1,4 @@ package model.graph -data class Vertex(var id: Int, var data: D) { +data class Vertex(var id: Int, var data: String) { } diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 8d0e2c4..f83e6db 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -15,7 +15,7 @@ var defaultColorLine: Color = Color.Black var defaultColorVertex: Color = Color.Gray var defaultStrokeWidth: Float = 4f -class MainScreenViewModel(private val graph: Graph, private val representationStrategy: RepresentationStrategy) { +class MainScreenViewModel(private val graph: Graph, private val representationStrategy: RepresentationStrategy) { val showVerticesLabels = mutableStateOf(false) val showEdgesLabels = mutableStateOf(false) val graphViewModel = GraphViewModel(graph, showVerticesLabels, showEdgesLabels) @@ -33,7 +33,7 @@ class MainScreenViewModel(private val graph: Graph, private val representa } } - fun addVertex(id: Int, data: D) { + fun addVertex(id: Int, data: String) { graphViewModel.addVertex(id, data) } @@ -129,10 +129,10 @@ class MainScreenViewModel(private val graph: Graph, private val representa /** Paints over the vertices and edges that belong to the found MST. */ fun runPrimAlgorithm(): Int { - if (graph is DirectedGraph) { + if (graph is DirectedGraph) { throw IllegalArgumentException("Prims's algorithm cannot be run on directed graphs.") } - val prim = Prim(graph as UndirectedGraph) + val prim = Prim(graph as UndirectedGraph) val result = prim.treePrim() val weight = prim.weightPrim() resetGraphView() @@ -154,7 +154,7 @@ class MainScreenViewModel(private val graph: Graph, private val representa if (vertexId !in graph.vertices.keys) { throw IllegalArgumentException("Vertex with id = $vertexId doesn't exists in the graph.") } - if (graph is DirectedGraph) { + if (graph is DirectedGraph) { throw IllegalArgumentException("CycleSearch algorithm cannot be run on directed graphs.") } val cycleSearch = CycleSearch(graph as UndirectedGraph) diff --git a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt index 630394b..ca7cab8 100644 --- a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt @@ -8,7 +8,7 @@ import kotlin.math.sin import kotlin.random.Random class CircularPlacementStrategy : RepresentationStrategy { - override fun place(width: Double, height: Double, vertices: Collection>) { + override fun place(width: Double, height: Double, vertices: Collection) { if (vertices.isEmpty()) { println("CircularPlacementStrategy.place: there is nothing to place 👐🏻") return diff --git a/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt b/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt index cb1c86e..624885d 100644 --- a/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt @@ -4,12 +4,12 @@ import androidx.compose.runtime.State import androidx.compose.ui.graphics.Color import model.graph.Edge -class EdgeViewModel( - val u: VertexViewModel, - val v: VertexViewModel, +class EdgeViewModel( + val u: VertexViewModel, + val v: VertexViewModel, var color: Color, var strokeWidth: Float, - private val e: Edge, + private val e: Edge, private val _labelVisible: State, ) { val label diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index 1a2f852..de5dd83 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -11,13 +11,13 @@ import viewmodel.defaultColorLine import viewmodel.defaultColorVertex import viewmodel.defaultStrokeWidth -class GraphViewModel( - private val graph: Graph, +class GraphViewModel( + private val graph: Graph, private val showVerticesLabels: State, private val showEdgesLabels: State, ) { - internal val verticesView: HashMap> = hashMapOf() - internal val edgesView: HashMap, EdgeViewModel> = hashMapOf() + internal val verticesView: HashMap = hashMapOf() + internal val edgesView: HashMap = hashMapOf() init { graph.getVertices().forEach { vertex -> @@ -32,7 +32,7 @@ class GraphViewModel( } } - fun addVertex(id: Int, data: D) { + fun addVertex(id: Int, data: String) { graph.addVertex(id, data) verticesView[id] = VertexViewModel(0.dp, 0.dp, defaultColorVertex, graph.vertices[id]!!, showVerticesLabels) } diff --git a/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt b/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt index 317ea6e..055ea1a 100644 --- a/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt @@ -3,5 +3,5 @@ package viewmodel.graph import viewmodel.graph.VertexViewModel interface RepresentationStrategy { - fun place(width: Double, height: Double, vertices: Collection>) + fun place(width: Double, height: Double, vertices: Collection) } \ No newline at end of file diff --git a/src/main/kotlin/viewmodel/graph/VertexViewModel.kt b/src/main/kotlin/viewmodel/graph/VertexViewModel.kt index 1d7ca41..ff1db31 100644 --- a/src/main/kotlin/viewmodel/graph/VertexViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/VertexViewModel.kt @@ -9,11 +9,11 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import model.graph.Vertex -class VertexViewModel( +class VertexViewModel( x: Dp = 0.dp, y: Dp = 0.dp, color: Color, - private val v: Vertex, + private val v: Vertex, private val _labelVisible: State, val radius: Dp = 25.dp ) { diff --git a/src/test/kotlin/DijkstraTest.kt b/src/test/kotlin/DijkstraTest.kt index a9d901e..e543c29 100644 --- a/src/test/kotlin/DijkstraTest.kt +++ b/src/test/kotlin/DijkstraTest.kt @@ -6,8 +6,8 @@ import org.junit.jupiter.api.Assertions.assertEquals import kotlin.test.assertFailsWith class DijkstraTest { - private val dirGraph = DirectedGraph() - private val undirGraph = UndirectedGraph() + private val dirGraph = DirectedGraph() + private val undirGraph = UndirectedGraph() private val shortestPathDirected = Dijkstra(dirGraph) private val shortestPathUndirected = Dijkstra(undirGraph) diff --git a/src/test/kotlin/KosarajuTest.kt b/src/test/kotlin/KosarajuTest.kt index 3d9d04a..7a8144f 100644 --- a/src/test/kotlin/KosarajuTest.kt +++ b/src/test/kotlin/KosarajuTest.kt @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals class KosarajuTest { - private val dirGraph = DirectedGraph() + private val dirGraph = DirectedGraph() private val scc = Kosaraju(dirGraph) @Test diff --git a/src/test/kotlin/LouvainTest.kt b/src/test/kotlin/LouvainTest.kt index 2e54e4e..c70795c 100644 --- a/src/test/kotlin/LouvainTest.kt +++ b/src/test/kotlin/LouvainTest.kt @@ -5,9 +5,9 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals class LouvainTest { - private val dirGraph = DirectedGraph() + private val dirGraph = DirectedGraph() private val communityDir = Louvain(dirGraph) - private val undirGraph = UndirectedGraph() + private val undirGraph = UndirectedGraph() private val communityUndir = Louvain(undirGraph) //region Special cases From f0456a017fc6125bb76272187a268e3fb18be4d2 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 8 Oct 2024 18:33:38 +0300 Subject: [PATCH 105/131] refactor: renamed variable --- src/main/kotlin/Main.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 5655a98..a9997a2 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -21,18 +21,18 @@ fun App() { // пока что, для удобства разработки val neo4j = Neo4jRepository("bolt://localhost:7687", "neo4j", "password") var graphType by remember { mutableStateOf(null) } - var sampleGraph by remember { mutableStateOf(null) } + var graph by remember { mutableStateOf(null) } if (graphType == null) { WelcomeScreen { selectedGraphType -> graphType = selectedGraphType - sampleGraph = when (graphType) { + graph = when (graphType) { "Directed" -> DirectedGraph() - "Neo4j" -> neo4j.loadGraph() - else -> UndirectedGraph() + "Undirected" -> UndirectedGraph() + else -> neo4j.loadGraph() } } } else { - sampleGraph?.let { + graph?.let { MainScreen(MainScreenViewModel(it, CircularPlacementStrategy())) } } From ca312001f4300f9078d329b14cbe87137a29e4dc Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Tue, 8 Oct 2024 21:20:46 +0300 Subject: [PATCH 106/131] fix: allowed graph data type changed --- .../kotlin/model/algorithms/BellmanFord.kt | 2 +- .../kotlin/model/algorithms/BridgeFinder.kt | 6 +++--- .../kotlin/model/algorithms/CycleSearch.kt | 9 ++++----- .../model/algorithms/HarmonicCentrality.kt | 7 +++---- src/main/kotlin/model/algorithms/Prim.kt | 20 +++++++++---------- src/main/kotlin/view/MainScreen.kt | 4 ++-- src/main/kotlin/view/graph/EdgeView.kt | 4 ++-- src/main/kotlin/view/graph/GraphView.kt | 4 ++-- src/main/kotlin/view/graph/VertexView.kt | 4 ++-- src/test/kotlin/BellmanFordTest.kt | 4 ++-- src/test/kotlin/BridgeFinderTest.kt | 4 ++-- src/test/kotlin/CycleSearchTest.kt | 6 +++--- src/test/kotlin/HarmonicCentralityTest.kt | 4 ++-- src/test/kotlin/PrimTest.kt | 2 +- src/test/kotlin/forTests.kt | 4 ++-- 15 files changed, 41 insertions(+), 43 deletions(-) diff --git a/src/main/kotlin/model/algorithms/BellmanFord.kt b/src/main/kotlin/model/algorithms/BellmanFord.kt index 092bb0e..825d9e7 100644 --- a/src/main/kotlin/model/algorithms/BellmanFord.kt +++ b/src/main/kotlin/model/algorithms/BellmanFord.kt @@ -2,7 +2,7 @@ package model.algorithms import model.graph.Graph -class BellmanFord(private val graph: Graph) { +class BellmanFord(private val graph: Graph) { fun findShortestPath(src: Int, dest: Int): Pair>? { val dist = mutableMapOf().withDefault { Int.MAX_VALUE } val pred = mutableMapOf() diff --git a/src/main/kotlin/model/algorithms/BridgeFinder.kt b/src/main/kotlin/model/algorithms/BridgeFinder.kt index 0d7bc2c..7d44233 100644 --- a/src/main/kotlin/model/algorithms/BridgeFinder.kt +++ b/src/main/kotlin/model/algorithms/BridgeFinder.kt @@ -2,11 +2,11 @@ package model.algorithms import model.graph.Graph -class BridgeFinder { +class BridgeFinder { private var time = 0 private val NIL = -1 - fun findBridges(graph: Graph): List> { + fun findBridges(graph: Graph): List> { val visited = BooleanArray(graph.vertices.size) val disc = IntArray(graph.vertices.size) val low = IntArray(graph.vertices.size) @@ -27,7 +27,7 @@ class BridgeFinder { return bridges } - private fun bridgeUtil(u: Int, visited: BooleanArray, disc: IntArray, low: IntArray, parent: IntArray, graph: Graph, bridges: MutableList>) { + private fun bridgeUtil(u: Int, visited: BooleanArray, disc: IntArray, low: IntArray, parent: IntArray, graph: Graph, bridges: MutableList>) { visited[u] = true disc[u] = ++time low[u] = time diff --git a/src/main/kotlin/model/algorithms/CycleSearch.kt b/src/main/kotlin/model/algorithms/CycleSearch.kt index 898cc73..181990f 100644 --- a/src/main/kotlin/model/algorithms/CycleSearch.kt +++ b/src/main/kotlin/model/algorithms/CycleSearch.kt @@ -11,13 +11,12 @@ import model.graph.Vertex * At the same time, the algorithm has some minimization of the desired cycle * by iterating through possible pairs of neighbors of the selected vertex * through which the desired cycle will pass. - * @param D input type * @property [graph] a undirected graph for whose vertex we will search for a cycle * @constructor Creates a graph, based on [graph],for which it will be possible to apply * a cycle search algorithm around the vertex selected in it. */ -class CycleSearch(private val graph: UndirectedGraph) { +class CycleSearch(private val graph: UndirectedGraph) { /** * This auxiliary function for the function [findAnyCycle] aims to get from @@ -110,7 +109,7 @@ class CycleSearch(private val graph: UndirectedGraph) { * @param vertex the vertex around which the cycle must be found * @return cycle path or null if it doesn't exist */ - fun findCycle(vertex: Vertex): UndirectedGraph? { + fun findCycle(vertex: Vertex): UndirectedGraph? { var returnCyclePath = hashMapOf() var currentCyclePath: HashMap? @@ -119,14 +118,14 @@ class CycleSearch(private val graph: UndirectedGraph) { if (vertex.id !in graph.vertices.keys) { throw IllegalArgumentException("Vertex with id = ${vertex.id} doesn't exist in the graph") } - val returnGraph = UndirectedGraph() + val returnGraph = UndirectedGraph() if (graph.adjacency[vertex.id]!!.size < 2) { return null } else { // we consider all possible cases of a cycle by choosing a pair of neighbors to minimize the found cycle val adjacencyOfVertex: MutableList = arrayListOf() graph.adjacency[vertex.id]!!.keys.forEach { adjacencyOfVertex.add(it) } val adjacencyWas: MutableList = arrayListOf() - val removedEdges: MutableList> = arrayListOf() + val removedEdges: MutableList = arrayListOf() for (firstAdjacency in adjacencyOfVertex) { adjacencyWas.add(firstAdjacency) for (secondAdjacency in adjacencyOfVertex.filter { it !in adjacencyWas && it != vertex.id }) { diff --git a/src/main/kotlin/model/algorithms/HarmonicCentrality.kt b/src/main/kotlin/model/algorithms/HarmonicCentrality.kt index dc5515e..0908f9b 100644 --- a/src/main/kotlin/model/algorithms/HarmonicCentrality.kt +++ b/src/main/kotlin/model/algorithms/HarmonicCentrality.kt @@ -19,13 +19,12 @@ import kotlin.math.roundToInt * for i in 1..n-1, where 'n' is amount of graph vertices. * In this case the length of the path is the amount of edges of this path. * - * @param D input type * @property [graph] a graph (of any kind) for the vertices of which it is necessary to calculate the centrality index * @constructor Creates a graph, based on [graph], for which the algorithm for calculating the * normalized harmonic centrality can be applied. */ -class HarmonicCentrality(private val graph: Graph) { +class HarmonicCentrality(private val graph: Graph) { /** * This auxiliary function for the function [getIndex], that rounds @@ -47,7 +46,7 @@ class HarmonicCentrality(private val graph: Graph) { * @return centrality index of vertex with id = [vertexId] * @receiver [harmonicCentrality] */ - private fun getIndex(graph: Graph, vertexId: Int): Double { + private fun getIndex(graph: Graph, vertexId: Int): Double { var index = 0.00 @@ -71,7 +70,7 @@ class HarmonicCentrality(private val graph: Graph) { val centralityIndexes = HashMap() - val graphForCentrality: Graph = if (graph is UndirectedGraph) { + val graphForCentrality: Graph = if (graph is UndirectedGraph) { UndirectedGraph() } else { DirectedGraph() diff --git a/src/main/kotlin/model/algorithms/Prim.kt b/src/main/kotlin/model/algorithms/Prim.kt index f663bc8..d5f2fc7 100644 --- a/src/main/kotlin/model/algorithms/Prim.kt +++ b/src/main/kotlin/model/algorithms/Prim.kt @@ -8,14 +8,14 @@ import model.graph.UndirectedGraph * * At the same time, if the graph consists of several connectivity components, * the algorithm constructs a forest, each tree of which is MST. - * @param D input type + * * @property [graph] a undirected weighted graph, whose MST we want to construct * @constructor Creates a graph, based on [graph], to which the following functions can be applied: * function [treePrim] : returns MST for [graph] * function [weightPrim] : returns the weight of received MST */ -class Prim(private val graph: UndirectedGraph) { +class Prim(private val graph: UndirectedGraph) { /** * This function implements Prim's algorithm for @@ -25,9 +25,9 @@ class Prim(private val graph: UndirectedGraph) { * @return MST of connectivity component * @receiver fun [treePrim] */ - private fun getMST(graph: UndirectedGraph): UndirectedGraph { + private fun getMST(graph: UndirectedGraph): UndirectedGraph { - val graphMst = UndirectedGraph() + val graphMst = UndirectedGraph() for (vertex in graph.vertices.keys) { graphMst.addVertex(vertex, graph.vertices[vertex]!!.data) @@ -90,8 +90,8 @@ class Prim(private val graph: UndirectedGraph) { private fun dfs( idVertex: Int, visited: HashMap, - component: UndirectedGraph - ): UndirectedGraph { + component: UndirectedGraph + ): UndirectedGraph { visited[idVertex] = true for (idAdjacency in graph.adjacency[idVertex]!!.keys) { @@ -116,9 +116,9 @@ class Prim(private val graph: UndirectedGraph) { * @return list of MST for each connectivity component of graph * @receiver fun [weightPrim] */ - fun treePrim(): MutableList> { + fun treePrim(): MutableList { - val returnListOfMST = mutableListOf>() + val returnListOfMST = mutableListOf() val visited = hashMapOf() for (vertexId in graph.vertices.keys) { @@ -126,11 +126,11 @@ class Prim(private val graph: UndirectedGraph) { } var initIndex: Int - var component: UndirectedGraph + var component: UndirectedGraph while (visited.values.contains(false)) { initIndex = visited.filterValues { !it }.keys.first() - component = UndirectedGraph() + component = UndirectedGraph() component.addVertex(initIndex, graph.vertices[initIndex]!!.data) component = dfs(initIndex, visited, component) returnListOfMST.add(getMST(component)) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index fa0768a..747a327 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -22,7 +22,7 @@ import viewmodel.graph.CircularPlacementStrategy import viewmodel.graph.GraphViewModel @Composable -fun MainScreen(viewModel: MainScreenViewModel) { +fun MainScreen(viewModel: MainScreenViewModel) { var theme by remember { mutableStateOf(Theme.NASTYA) } var expandedAlgorithmsMenu by remember { mutableStateOf(false) } var expandedAddMenu by remember { mutableStateOf(false) } @@ -281,7 +281,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { AddVertexDialog( onDismiss = { showAddVertexDialog = false }, onRunAlgorithm = { id, data -> - viewModel.addVertex(id, data as D) + viewModel.addVertex(id, data) showAddVertexDialog = false } ) diff --git a/src/main/kotlin/view/graph/EdgeView.kt b/src/main/kotlin/view/graph/EdgeView.kt index 0fe9757..f586bdc 100644 --- a/src/main/kotlin/view/graph/EdgeView.kt +++ b/src/main/kotlin/view/graph/EdgeView.kt @@ -14,8 +14,8 @@ import kotlin.math.pow import kotlin.math.sqrt @Composable -fun EdgeView( - viewModel: EdgeViewModel, +fun EdgeView( + viewModel: EdgeViewModel, modifier: Modifier = Modifier, ) { diff --git a/src/main/kotlin/view/graph/GraphView.kt b/src/main/kotlin/view/graph/GraphView.kt index 3f0d605..81d21ce 100644 --- a/src/main/kotlin/view/graph/GraphView.kt +++ b/src/main/kotlin/view/graph/GraphView.kt @@ -12,8 +12,8 @@ import viewmodel.graph.VertexViewModel @OptIn(ExperimentalComposeUiApi::class) @Composable -fun GraphView( - viewModel: GraphViewModel, +fun GraphView( + viewModel: GraphViewModel, ) { Box(modifier = Modifier .fillMaxSize() diff --git a/src/main/kotlin/view/graph/VertexView.kt b/src/main/kotlin/view/graph/VertexView.kt index a6f44c9..b32b5cf 100644 --- a/src/main/kotlin/view/graph/VertexView.kt +++ b/src/main/kotlin/view/graph/VertexView.kt @@ -14,8 +14,8 @@ import androidx.compose.ui.unit.dp import viewmodel.graph.VertexViewModel @Composable -fun VertexView( - viewModel: VertexViewModel, +fun VertexView( + viewModel: VertexViewModel, modifier: Modifier = Modifier, ) { diff --git a/src/test/kotlin/BellmanFordTest.kt b/src/test/kotlin/BellmanFordTest.kt index 57aec49..2d443d6 100644 --- a/src/test/kotlin/BellmanFordTest.kt +++ b/src/test/kotlin/BellmanFordTest.kt @@ -6,8 +6,8 @@ import model.graph.UndirectedGraph import org.junit.jupiter.api.Test class BellmanFordTest { - private val dirGraph = DirectedGraph() - private val undirGraph = UndirectedGraph() + private val dirGraph = DirectedGraph() + private val undirGraph = UndirectedGraph() private val bellmanFordDirected = BellmanFord(dirGraph) private val bellmanFordUndirected = BellmanFord(undirGraph) diff --git a/src/test/kotlin/BridgeFinderTest.kt b/src/test/kotlin/BridgeFinderTest.kt index aeef51d..5527acc 100644 --- a/src/test/kotlin/BridgeFinderTest.kt +++ b/src/test/kotlin/BridgeFinderTest.kt @@ -4,8 +4,8 @@ import model.graph.UndirectedGraph import org.junit.jupiter.api.Test class BridgeFinderTest { - private val undirGraph = UndirectedGraph() - private val bridgeFinder = BridgeFinder() + private val undirGraph = UndirectedGraph() + private val bridgeFinder = BridgeFinder() init { // Добавление вершин для ненаправленного графа diff --git a/src/test/kotlin/CycleSearchTest.kt b/src/test/kotlin/CycleSearchTest.kt index ba58900..be9e119 100644 --- a/src/test/kotlin/CycleSearchTest.kt +++ b/src/test/kotlin/CycleSearchTest.kt @@ -8,14 +8,14 @@ import kotlin.test.assertFailsWith class CycleSearchTest { - private var graph = UndirectedGraph() + private var graph = UndirectedGraph() private var cycleGraph = CycleSearch(graph) @Test fun `passing as function argument a non-existent vertex should throw an exception`() { addVertices(graph, 3) - assertFailsWith { cycleGraph.findCycle(Vertex(4, 4)) } + assertFailsWith { cycleGraph.findCycle(Vertex(4, 4.toString())) } } @@ -49,7 +49,7 @@ class CycleSearchTest { addEdge(2 to 3, 12) addEdge(1 to 3, 4) } - cycleGraph.findCycle(graph.vertices[1]!!) + assertEquals(true,cycleGraph.findCycle(graph.vertices[1]!!) != null) } diff --git a/src/test/kotlin/HarmonicCentralityTest.kt b/src/test/kotlin/HarmonicCentralityTest.kt index df53468..20b1411 100644 --- a/src/test/kotlin/HarmonicCentralityTest.kt +++ b/src/test/kotlin/HarmonicCentralityTest.kt @@ -8,8 +8,8 @@ import kotlin.test.assertEquals class HarmonicCentralityTest { - private var undirGraph = UndirectedGraph() - private var dirGraph = DirectedGraph() + private var undirGraph = UndirectedGraph() + private var dirGraph = DirectedGraph() private var undirCentrality = HarmonicCentrality(undirGraph) private var dirCentrality = HarmonicCentrality(dirGraph) private fun roundTo(number: Double): Double { diff --git a/src/test/kotlin/PrimTest.kt b/src/test/kotlin/PrimTest.kt index a98dc1f..f4e8fea 100644 --- a/src/test/kotlin/PrimTest.kt +++ b/src/test/kotlin/PrimTest.kt @@ -6,7 +6,7 @@ import forTests.addVertices class PrimTest { - private val undirGraph = UndirectedGraph() + private val undirGraph = UndirectedGraph() private var primGraph = Prim(undirGraph) @Test diff --git a/src/test/kotlin/forTests.kt b/src/test/kotlin/forTests.kt index 343ee64..4a195e0 100644 --- a/src/test/kotlin/forTests.kt +++ b/src/test/kotlin/forTests.kt @@ -2,11 +2,11 @@ package forTests import model.graph.Graph -fun addVertices(graph: Graph, amount: Int) { +fun addVertices(graph: Graph, amount: Int) { var counter = amount while (counter > 0) { - graph.addVertex(counter, counter) + graph.addVertex(counter, counter.toString()) counter-- } } From 530358ce8e079c166ee05b14603bd56dca98fb56 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 9 Oct 2024 01:40:01 +0300 Subject: [PATCH 107/131] feat: added opening graph from file --- build.gradle.kts | 1 + src/main/kotlin/Main.kt | 30 +++++-- src/main/kotlin/databases/FileSystem.kt | 115 ++++++++++++++++++++++++ src/main/kotlin/view/CommonDialogs.kt | 15 ++++ src/main/kotlin/view/WelcomeScreen.kt | 3 + 5 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/databases/FileSystem.kt diff --git a/build.gradle.kts b/build.gradle.kts index 6b874a5..b8f9010 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { implementation(compose.material3) implementation(compose.materialIconsExtended) implementation("org.neo4j.driver", "neo4j-java-driver", "5.6.0") + implementation("com.google.code.gson:gson:2.10.1") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("io.kotest:kotest-runner-junit5-jvm:4.6.0") } diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index a9997a2..3cf4ce3 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -7,8 +7,10 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import databases.FileSystem import databases.Neo4jRepository import model.graph.* +import view.ErrorDialog import view.MainScreen import view.WelcomeScreen import viewmodel.MainScreenViewModel @@ -20,15 +22,27 @@ fun App() { MaterialTheme { // пока что, для удобства разработки val neo4j = Neo4jRepository("bolt://localhost:7687", "neo4j", "password") + val fileSystem = FileSystem() var graphType by remember { mutableStateOf(null) } var graph by remember { mutableStateOf(null) } - if (graphType == null) { + var showErrorDialog by remember { mutableStateOf(false) } + var errorMessage by remember { mutableStateOf(null) } + fun catchErrorOrGetGraph(result:Pair) { + if (result.second == null ) { + graph = result.first + } else { + errorMessage = result.second + showErrorDialog = true + } + } + if (graph == null) { WelcomeScreen { selectedGraphType -> graphType = selectedGraphType - graph = when (graphType) { - "Directed" -> DirectedGraph() - "Undirected" -> UndirectedGraph() - else -> neo4j.loadGraph() + when (graphType) { + "Directed" -> graph = DirectedGraph() + "Undirected" -> graph = UndirectedGraph() + "Neo4j" -> graph = neo4j.loadGraph() + else -> catchErrorOrGetGraph(fileSystem.openGraph()) } } } else { @@ -36,6 +50,12 @@ fun App() { MainScreen(MainScreenViewModel(it, CircularPlacementStrategy())) } } + if (showErrorDialog) { + ErrorDialog( + onDismiss = { showErrorDialog = false }, + errorMessage!! + ) + } } } diff --git a/src/main/kotlin/databases/FileSystem.kt b/src/main/kotlin/databases/FileSystem.kt new file mode 100644 index 0000000..3ac01f6 --- /dev/null +++ b/src/main/kotlin/databases/FileSystem.kt @@ -0,0 +1,115 @@ +package databases + +import com.google.gson.Gson +import model.graph.DirectedGraph +import model.graph.Graph +import model.graph.UndirectedGraph +import java.awt.FileDialog +import java.awt.Frame +import java.io.File +import java.nio.file.Files + +class FileSystem { + + /** From-to Json-conversation functions: + * with the help of the Google's Gson library, it is possible + * to get an object from a file and vice versa: + * to get a file from an object **/ + + private fun fromGsonConversation(jsonString: String, graph: Graph): Graph { + + val gson = Gson() + if (graph is UndirectedGraph) { + return gson.fromJson(jsonString, UndirectedGraph()::class.java) + } + + return gson.fromJson(jsonString, DirectedGraph()::class.java) + + } + + private fun toGsonConversation(graph: Graph): String { + + val gson = Gson() + return gson.toJson(graph) + + } + + /**If the graph is successfully saved the graph to a file, + * the function returns null, in case of a save error, + * it returns an error message**/ + + fun saveGraph(graph: Graph): String? { + + val frame = Frame() + val fileDialog= FileDialog(frame, "Save your Json file:", FileDialog.SAVE) + fileDialog.setFile("*.json") + fileDialog.isVisible = true + + if( fileDialog.file == null) { // case: click the "cross" or "cancel" buttons + frame.dispose() + return null + } + + if(graph is UndirectedGraph) { + fileDialog.file = "undir" + fileDialog.file + }else { + fileDialog.file = "dir" + fileDialog.file + } + val savedFile = File(fileDialog.directory, fileDialog.file) + try { + savedFile.writeText(toGsonConversation(graph)) + frame.dispose() + return null + } catch (e: Exception) { + frame.dispose() + return "To Gson conversation error:\n" + + (e.message?.substringAfter("Exception: ") ?: + "incorrect converted graph") + } + + } + + /**The function returns pair, the fist element of that is + * opened graph in successful case and null in failure case and second + * element is an error message in failure case + * and null in successful case.**/ + + fun openGraph(): Pair { + var graph: Graph + val frame = Frame() + val fileDialog= FileDialog(frame, "Open your Json file:", FileDialog.LOAD) + fileDialog.setFile("*.json") + fileDialog.isVisible = true + + val fileName = fileDialog.file + val directory = fileDialog.directory + if( fileName == null) { // case: click the "cross" or "cancel" buttons + frame.dispose() + return null to null + } + if(fileName.startsWith("dir")) { + graph = DirectedGraph() + }else if(fileName.startsWith("undir")) { + graph = UndirectedGraph() + }else { + return null to "Incorrect name of the selected file: it must begin with 'dir' or 'undir' ." + } + + val openedFile = File(directory, fileName) + + try { + + graph = fromGsonConversation( + Files.readString(openedFile.toPath()), graph + ) + frame.dispose() + return graph to null + + } catch (e: Exception) { + frame.dispose() + return null to "The contents of the file are incorrect:\n" + + (e.message?.substringAfter("Exception: ") ?: + "fix this file or try to open another one") + } + } +} diff --git a/src/main/kotlin/view/CommonDialogs.kt b/src/main/kotlin/view/CommonDialogs.kt index 2e4d2ac..07c826c 100644 --- a/src/main/kotlin/view/CommonDialogs.kt +++ b/src/main/kotlin/view/CommonDialogs.kt @@ -473,3 +473,18 @@ fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) } ) } + +@Composable +fun ErrorDialog(onDismiss: () -> Unit, errorMessage: String) { + AlertDialog( + onDismissRequest = onDismiss, + title = { Text("Error:") }, + text = { Text(errorMessage) }, + confirmButton = { + Button(onClick = onDismiss) { + Text("ОК") + } + } + ) + +} diff --git a/src/main/kotlin/view/WelcomeScreen.kt b/src/main/kotlin/view/WelcomeScreen.kt index a8ef410..315453b 100644 --- a/src/main/kotlin/view/WelcomeScreen.kt +++ b/src/main/kotlin/view/WelcomeScreen.kt @@ -24,6 +24,9 @@ fun WelcomeScreen(selectType: (String) -> Unit) { Button(onClick = { selectType("Neo4j") }) { Text("Load Neo4j graph") } + Button(onClick = { selectType("File") }) { + Text("Open graph from file") + } } } } From ba41e76f2f9dfc9060df3dcfebf3556444c16e98 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 9 Oct 2024 02:55:05 +0300 Subject: [PATCH 108/131] fix: added error handling for some algorithms --- .../kotlin/model/algorithms/CycleSearch.kt | 4 --- src/main/kotlin/model/algorithms/Prim.kt | 4 +-- src/main/kotlin/view/MainScreen.kt | 26 ++++++++++++++-- src/main/kotlin/view/WelcomeScreen.kt | 2 +- .../kotlin/viewmodel/MainScreenViewModel.kt | 31 +++++++++++++------ 5 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/model/algorithms/CycleSearch.kt b/src/main/kotlin/model/algorithms/CycleSearch.kt index 181990f..2c05924 100644 --- a/src/main/kotlin/model/algorithms/CycleSearch.kt +++ b/src/main/kotlin/model/algorithms/CycleSearch.kt @@ -114,10 +114,6 @@ class CycleSearch(private val graph: UndirectedGraph) { var returnCyclePath = hashMapOf() var currentCyclePath: HashMap? var minCycleSize: Int = Int.MAX_VALUE - - if (vertex.id !in graph.vertices.keys) { - throw IllegalArgumentException("Vertex with id = ${vertex.id} doesn't exist in the graph") - } val returnGraph = UndirectedGraph() if (graph.adjacency[vertex.id]!!.size < 2) { return null diff --git a/src/main/kotlin/model/algorithms/Prim.kt b/src/main/kotlin/model/algorithms/Prim.kt index d5f2fc7..015d5b0 100644 --- a/src/main/kotlin/model/algorithms/Prim.kt +++ b/src/main/kotlin/model/algorithms/Prim.kt @@ -152,9 +152,7 @@ class Prim(private val graph: UndirectedGraph) { for (element in treePrim()) { for (edge in element.edges) { - if (edge.weight == null) { - throw IllegalArgumentException("Each edge of a weighted graph must have a weight: the edge with weight = 'null' isn't correct") - } else { + if (edge.weight != null) { treeWeight += edge.weight!! } diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 747a327..2be8c61 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.sp import model.graph.Graph import androidx.compose.material3.MaterialTheme +import databases.FileSystem import org.jetbrains.skia.impl.Stats.enabled import view.graph.GraphView import viewmodel.MainScreenViewModel @@ -36,6 +37,15 @@ fun MainScreen(viewModel: MainScreenViewModel) { var showRemoveVertexDialog by remember { mutableStateOf(false) } var showRemoveEdgeDialog by remember { mutableStateOf(false) } var showNeo4jDialog by remember { mutableStateOf(false) } + var showErrorDialog by remember { mutableStateOf(false) } + var errorMessage: String? = null + fun catchError(messageOfError: String? ) { + if(messageOfError != null ) { + errorMessage = messageOfError + showErrorDialog = true + } + } + Material3AppTheme(theme) { Row( horizontalArrangement = Arrangement.spacedBy(20.dp) @@ -123,7 +133,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { } DropdownMenuItem(onClick = { expandedAlgorithmsMenu = false - viewModel.runPrimAlgorithm() + catchError(viewModel.runPrimAlgorithm()) }) { Text("Prim") } @@ -222,6 +232,12 @@ fun MainScreen(viewModel: MainScreenViewModel) { }) { Text("Save to Neo4j", color = MaterialTheme.colorScheme.onSecondary) } + DropdownMenuItem(onClick = { + expandedSaveMenu = false + catchError(viewModel.saveToFile()) + }) { + Text("Save to json-file", color = MaterialTheme.colorScheme.onSecondary) + } } } } @@ -290,7 +306,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { CycleSearchDialog( onDismiss = { showCycleSearchDialog = false }, onRunAlgorithm = { vertexId -> - viewModel.runCycleSearchAlgorithm(vertexId) + catchError(viewModel.runCycleSearchAlgorithm(vertexId)) showCycleSearchDialog = false } ) @@ -331,5 +347,11 @@ fun MainScreen(viewModel: MainScreenViewModel) { } ) } + if (showErrorDialog) { + ErrorDialog( + onDismiss = { showErrorDialog = false }, + errorMessage!! + ) + } } } \ No newline at end of file diff --git a/src/main/kotlin/view/WelcomeScreen.kt b/src/main/kotlin/view/WelcomeScreen.kt index 315453b..686674c 100644 --- a/src/main/kotlin/view/WelcomeScreen.kt +++ b/src/main/kotlin/view/WelcomeScreen.kt @@ -25,7 +25,7 @@ fun WelcomeScreen(selectType: (String) -> Unit) { Text("Load Neo4j graph") } Button(onClick = { selectType("File") }) { - Text("Open graph from file") + Text("Open graph from json-file") } } } diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index f83e6db..feab0c9 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -2,6 +2,7 @@ package viewmodel import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color +import databases.FileSystem import databases.Neo4jRepository import model.graph.Graph import viewmodel.graph.GraphViewModel @@ -53,6 +54,10 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt val neo4j = Neo4jRepository(uri, user, password) neo4j.saveGraph(graph) } + fun saveToFile(): String? { + val fileSystem = FileSystem() + return fileSystem.saveGraph(graph) + } /** Paint the vertices and edges of the found path. */ @@ -128,9 +133,14 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt /** Paints over the vertices and edges that belong to the found MST. */ - fun runPrimAlgorithm(): Int { + fun runPrimAlgorithm(): String? { if (graph is DirectedGraph) { - throw IllegalArgumentException("Prims's algorithm cannot be run on directed graphs.") + return "Prims's algorithm cannot be run on directed graphs." + } + for(edge in graph.edges) { + if(edge.weight == null) { + return "Each edge of graph for Prim's algorithm must have a weight:\nthe edge with weight = 'null' is incorrect." + } } val prim = Prim(graph as UndirectedGraph) val result = prim.treePrim() @@ -147,15 +157,16 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt } } } - return weight + return null } - fun runCycleSearchAlgorithm(vertexId: Int): Boolean { - if (vertexId !in graph.vertices.keys) { - throw IllegalArgumentException("Vertex with id = $vertexId doesn't exists in the graph.") - } + fun runCycleSearchAlgorithm(vertexId: Int): String? { + if (graph is DirectedGraph) { - throw IllegalArgumentException("CycleSearch algorithm cannot be run on directed graphs.") + return "CycleSearch algorithm can't be run on directed graphs." + } + if (vertexId !in graph.vertices.keys) { + return "Vertex with id = $vertexId doesn't exists in the graph." } val cycleSearch = CycleSearch(graph as UndirectedGraph) val result = cycleSearch.findCycle(Vertex(vertexId, graph.vertices[vertexId]!!.data)) @@ -170,9 +181,9 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt edgeView.value.strokeWidth = 9f } } - return false + return null } - return true + return "The vertex with id = $vertexId doesn't have a cycle around it. " } } From e8fec3f8fe502c7ee99f5f538de71e6f74131755 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Wed, 9 Oct 2024 16:23:03 +0300 Subject: [PATCH 109/131] fix: deleted an unnecessary test In the algorithm for searching for a cycle around a vertex, the check for the existence of the received vertex is checked in function runCycleSearchAlgorithm into MainScreenViewModel.kt file. Therefore, when testing at the model level, there is no need for this test. --- src/test/kotlin/CycleSearchTest.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/test/kotlin/CycleSearchTest.kt b/src/test/kotlin/CycleSearchTest.kt index be9e119..274b99e 100644 --- a/src/test/kotlin/CycleSearchTest.kt +++ b/src/test/kotlin/CycleSearchTest.kt @@ -11,14 +11,6 @@ class CycleSearchTest { private var graph = UndirectedGraph() private var cycleGraph = CycleSearch(graph) - @Test - fun `passing as function argument a non-existent vertex should throw an exception`() { - - addVertices(graph, 3) - assertFailsWith { cycleGraph.findCycle(Vertex(4, 4.toString())) } - - } - @Test fun `search for a cycle at a single vertex must be correct`() { From d670f0937cf52d66879ab48a087bfc19aa3ace5a Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 9 Oct 2024 18:44:36 +0300 Subject: [PATCH 110/131] feat: added graph type storage in Neo4j --- src/main/kotlin/databases/Neo4jRepository.kt | 24 ++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/databases/Neo4jRepository.kt b/src/main/kotlin/databases/Neo4jRepository.kt index 598bddb..dddad19 100644 --- a/src/main/kotlin/databases/Neo4jRepository.kt +++ b/src/main/kotlin/databases/Neo4jRepository.kt @@ -27,6 +27,7 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { } fun saveGraph(graph: Graph) { + val typeGraph = if (graph is DirectedGraph) "DIRECTED" else "UNDIRECTED" val transaction = session.beginTransaction() try { transaction.run("MATCH (n) DETACH DELETE n") @@ -36,6 +37,7 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { for (edge in graph.edges) { addEdge(edge.vertices, edge.weight, transaction) } + transaction.run("CREATE (g:Graph {type: '${typeGraph}'})") transaction.commit() } catch (e: Exception) { transaction.rollback() @@ -45,10 +47,22 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { } } - fun loadGraph() : Graph { - val graph = DirectedGraph() + fun loadGraph(): Graph { + val graph: Graph val transaction = session.beginTransaction() try { + val graphTypeResult = transaction.run("MATCH (g:Graph) RETURN g.type AS type LIMIT 1") + val graphType = if (graphTypeResult.hasNext()) { + val record = graphTypeResult.next() + record.get("type").asString() + } else { + throw Exception("Graph type not found in the database") + } + graph = when (graphType) { + "DIRECTED" -> DirectedGraph() + "UNDIRECTED" -> UndirectedGraph() + else -> throw Exception("Unknown graph type: $graphType") + } val verticesResult = transaction.run("MATCH (v:Vertex) RETURN v.id AS id, v.data AS data") while (verticesResult.hasNext()) { val record = verticesResult.next() @@ -56,8 +70,10 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { val data = record.get("data").asString() graph.addVertex(id, data) } - val edgesResult = transaction.run("MATCH (v1:Vertex)-[r:EDGE]->(v2:Vertex) " + - "RETURN v1.id AS id1, v2.id AS id2, r.weight AS weight") + val edgesResult = transaction.run( + "MATCH (v1:Vertex)-[r:EDGE]->(v2:Vertex) " + + "RETURN v1.id AS id1, v2.id AS id2, r.weight AS weight" + ) while (edgesResult.hasNext()) { val record = edgesResult.next() val id1 = record.get("id1").asInt() From 8417449073422af8b7175226afeef878eae2341b Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 9 Oct 2024 18:46:31 +0300 Subject: [PATCH 111/131] fix: changed button text --- src/main/kotlin/view/WelcomeScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/view/WelcomeScreen.kt b/src/main/kotlin/view/WelcomeScreen.kt index 686674c..2a49e0b 100644 --- a/src/main/kotlin/view/WelcomeScreen.kt +++ b/src/main/kotlin/view/WelcomeScreen.kt @@ -22,7 +22,7 @@ fun WelcomeScreen(selectType: (String) -> Unit) { Text("Select Directed graph") } Button(onClick = { selectType("Neo4j") }) { - Text("Load Neo4j graph") + Text("Open graph from Neo4j") } Button(onClick = { selectType("File") }) { Text("Open graph from json-file") From 6f981c0362528d279275c712c5403f21655e1246 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 9 Oct 2024 18:58:18 +0300 Subject: [PATCH 112/131] feat: added error catching in Neo4j --- src/main/kotlin/Main.kt | 9 +++++---- src/main/kotlin/databases/Neo4jRepository.kt | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 3cf4ce3..a3e668c 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -27,22 +27,23 @@ fun App() { var graph by remember { mutableStateOf(null) } var showErrorDialog by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf(null) } - fun catchErrorOrGetGraph(result:Pair) { - if (result.second == null ) { + fun catchErrorOrGetGraph(result: Pair) { + if (result.second == null) { graph = result.first } else { errorMessage = result.second showErrorDialog = true } } + if (graph == null) { WelcomeScreen { selectedGraphType -> graphType = selectedGraphType when (graphType) { "Directed" -> graph = DirectedGraph() "Undirected" -> graph = UndirectedGraph() - "Neo4j" -> graph = neo4j.loadGraph() - else -> catchErrorOrGetGraph(fileSystem.openGraph()) + "Neo4j" -> catchErrorOrGetGraph(neo4j.loadGraph()) + else -> catchErrorOrGetGraph(fileSystem.openGraph()) } } } else { diff --git a/src/main/kotlin/databases/Neo4jRepository.kt b/src/main/kotlin/databases/Neo4jRepository.kt index dddad19..0834abe 100644 --- a/src/main/kotlin/databases/Neo4jRepository.kt +++ b/src/main/kotlin/databases/Neo4jRepository.kt @@ -47,7 +47,7 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { } } - fun loadGraph(): Graph { + fun loadGraph(): Pair { val graph: Graph val transaction = session.beginTransaction() try { @@ -56,12 +56,12 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { val record = graphTypeResult.next() record.get("type").asString() } else { - throw Exception("Graph type not found in the database") + return (null to "Graph type not found in the database") } graph = when (graphType) { "DIRECTED" -> DirectedGraph() "UNDIRECTED" -> UndirectedGraph() - else -> throw Exception("Unknown graph type: $graphType") + else -> return (null to "Unknown graph type: $graphType") } val verticesResult = transaction.run("MATCH (v:Vertex) RETURN v.id AS id, v.data AS data") while (verticesResult.hasNext()) { @@ -88,7 +88,7 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { } finally { transaction.close() } - return graph + return (graph to null) } override fun close() { From de7838007269d54e7399e21592e46abd05448f69 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 9 Oct 2024 19:00:19 +0300 Subject: [PATCH 113/131] refactor: removed unnecessary comment --- src/main/kotlin/databases/Neo4jRepository.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/databases/Neo4jRepository.kt b/src/main/kotlin/databases/Neo4jRepository.kt index 0834abe..51819ce 100644 --- a/src/main/kotlin/databases/Neo4jRepository.kt +++ b/src/main/kotlin/databases/Neo4jRepository.kt @@ -7,7 +7,6 @@ import org.neo4j.driver.* import java.io.Closeable class Neo4jRepository(uri: String, user: String, password: String) : Closeable { - // bolt://localhost:7687 private val driver: Driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) private val session: Session = driver.session() @@ -61,7 +60,7 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { graph = when (graphType) { "DIRECTED" -> DirectedGraph() "UNDIRECTED" -> UndirectedGraph() - else -> return (null to "Unknown graph type: $graphType") + else -> return (null to "Unknown graph type: $graphType") } val verticesResult = transaction.run("MATCH (v:Vertex) RETURN v.id AS id, v.data AS data") while (verticesResult.hasNext()) { From 176788f5a643fd0ce40a26129048448afc46b3e5 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 9 Oct 2024 19:18:27 +0300 Subject: [PATCH 114/131] feat: added docs --- src/main/kotlin/databases/Neo4jRepository.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/databases/Neo4jRepository.kt b/src/main/kotlin/databases/Neo4jRepository.kt index 51819ce..b9e6d89 100644 --- a/src/main/kotlin/databases/Neo4jRepository.kt +++ b/src/main/kotlin/databases/Neo4jRepository.kt @@ -25,7 +25,9 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { ) } - fun saveGraph(graph: Graph) { + /** Saves the graph in Neo4j, in case of an error, returns a + * string with error information, otherwise null */ + fun saveGraph(graph: Graph): String? { val typeGraph = if (graph is DirectedGraph) "DIRECTED" else "UNDIRECTED" val transaction = session.beginTransaction() try { @@ -38,14 +40,19 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { } transaction.run("CREATE (g:Graph {type: '${typeGraph}'})") transaction.commit() + return null } catch (e: Exception) { transaction.rollback() - throw e + return "Failed to save graph (transaction error)" } finally { transaction.close() } } + /** The function loads the graph from neo4j, if unsuccessful, returns an error message. + * @return pair graph (Graph?) and error string (String?), if loading is successful, + * returns graph and null string, if error returns null graph and error string + */ fun loadGraph(): Pair { val graph: Graph val transaction = session.beginTransaction() @@ -81,13 +88,13 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { graph.addEdge(Pair(id1, id2), weight) } transaction.commit() + return (graph to null) } catch (e: Exception) { transaction.rollback() - throw e + return null to "Failed to load graph (transaction error)" } finally { transaction.close() } - return (graph to null) } override fun close() { From 94b4e7f6b007a0e4bad344e09388168a7b796628 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 9 Oct 2024 20:42:44 +0300 Subject: [PATCH 115/131] feat: added catching errors --- src/main/kotlin/model/algorithms/Dijkstra.kt | 15 ++++---- .../model/algorithms/HarmonicCentrality.kt | 4 +- src/main/kotlin/model/graph/DirectedGraph.kt | 26 +++++++------ src/main/kotlin/model/graph/Graph.kt | 11 +++--- .../kotlin/model/graph/UndirectedGraph.kt | 25 ++++++------ src/main/kotlin/view/Color.kt | 5 ++- src/main/kotlin/view/CommonDialogs.kt | 11 ++++-- src/main/kotlin/view/MainScreen.kt | 18 ++++----- .../kotlin/viewmodel/MainScreenViewModel.kt | 38 ++++++++++--------- .../kotlin/viewmodel/graph/GraphViewModel.kt | 28 +++++++++----- src/test/kotlin/DijkstraTest.kt | 16 ++++---- 11 files changed, 111 insertions(+), 86 deletions(-) diff --git a/src/main/kotlin/model/algorithms/Dijkstra.kt b/src/main/kotlin/model/algorithms/Dijkstra.kt index bec7bdd..3e959c9 100644 --- a/src/main/kotlin/model/algorithms/Dijkstra.kt +++ b/src/main/kotlin/model/algorithms/Dijkstra.kt @@ -21,11 +21,12 @@ class Dijkstra(private val graph: Graph) { * * @param start The starting vertex from which to start searching for the shortest path. * @param end The end vertex to which the shortest path is sought. - * @return A list of vertices that form the shortest path from vertex start to vertex end. + * @return On success List of vertices that form the shortest path from the beginning of the vertex + * to the end of the vertex. In case of error, the string with the error. */ - fun findShortestPaths(start: Int, end: Int): List { + fun findShortestPaths(start: Int, end: Int): Pair?, String?> { if (!graph.vertices.contains(start) || !graph.vertices.contains(end)) { - throw NoSuchElementException("The vertex doesn't exist in the graph.") + return (null to "The vertex doesn't exist in the graph.") } distances[start] = 0 queue.add(Pair(start, 0)) @@ -35,9 +36,9 @@ class Dijkstra(private val graph: Graph) { if (currentVertex == end) break graph.adjacency[currentVertex]?.forEach { (neighbor, weight) -> if (weight == null) { - throw IllegalArgumentException("Edge without weights in Dijkstra's algorithm.") + return (null to "Edge without weights in Dijkstra's algorithm.") } else if (weight < 0) { - throw IllegalArgumentException("Edge with negative weights in Dijkstra's algorithm.") + return (null to "Edge with negative weights in Dijkstra's algorithm.") } else { val newDistance = currentDistance + weight if (newDistance < (distances[neighbor] ?: Int.MAX_VALUE)) { @@ -50,7 +51,7 @@ class Dijkstra(private val graph: Graph) { } // construct final path using previous vertices val path = mutableListOf() - previousVertices[end] ?: return path + previousVertices[end] ?: return (path to null) var current = end while (current != start) { path.add(current) @@ -58,6 +59,6 @@ class Dijkstra(private val graph: Graph) { } path.add(start) path.reverse() - return path + return (path to null) } } diff --git a/src/main/kotlin/model/algorithms/HarmonicCentrality.kt b/src/main/kotlin/model/algorithms/HarmonicCentrality.kt index 0908f9b..83c3356 100644 --- a/src/main/kotlin/model/algorithms/HarmonicCentrality.kt +++ b/src/main/kotlin/model/algorithms/HarmonicCentrality.kt @@ -52,8 +52,8 @@ class HarmonicCentrality(private val graph: Graph) { graph.vertices.filter { it.key != vertexId } .forEach { - if (Dijkstra(graph).findShortestPaths(vertexId, it.key).isNotEmpty()) { - index += 1.0 / ((Dijkstra(graph).findShortestPaths(vertexId, it.key)).size - 1) + if (Dijkstra(graph).findShortestPaths(vertexId, it.key).first!!.isNotEmpty()) { + index += 1.0 / ((Dijkstra(graph).findShortestPaths(vertexId, it.key)).first!!.size - 1) } } diff --git a/src/main/kotlin/model/graph/DirectedGraph.kt b/src/main/kotlin/model/graph/DirectedGraph.kt index 30f621b..86afc56 100644 --- a/src/main/kotlin/model/graph/DirectedGraph.kt +++ b/src/main/kotlin/model/graph/DirectedGraph.kt @@ -1,39 +1,42 @@ package model.graph class DirectedGraph() : Graph() { - override fun addEdge(v: Pair, w: Int?) { - // добавить проверку на повторку ребра + override fun addEdge(v: Pair, w: Int?): String? { if (!vertices.containsKey(v.first)) { - throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") + return "Vertex with id: ${v.first} not exists in the graph." } if (!vertices.containsKey(v.second)) { - throw IllegalArgumentException("Vertex with id: ${v.second} not exists in the graph.") + return "Vertex with id: ${v.second} not exists in the graph." + } + if (v.first == v.second) { + return "Can't add edge from vertex to itself." } edges.add(Edge(Pair(v.first, v.second), w)) adjacency.getOrPut(v.first) { hashMapOf() }[v.second] = w + return null } - override fun removeEdge(v: Pair) { + override fun removeEdge(v: Pair): String? { if (!vertices.containsKey(v.first)) { - throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") + return "Vertex with id: ${v.first} not exists in the graph." } if (!vertices.containsKey(v.second)) { - throw IllegalArgumentException("Vertex with id: ${v.second} not exists in the graph.") + return "Vertex with id: ${v.second} not exists in the graph." } if (v.second !in adjacency[v.first]!!.keys) { - throw IllegalArgumentException("Edge from ${v.first} to ${v.second} not exists in the graph.") + return "Edge from ${v.first} to ${v.second} not exists in the graph." } edges.find { it.vertices == Pair(v.first, v.second) }?.let { edge -> edges.remove(edge) } adjacency[v.first]!!.remove(v.second) - + return null } - override fun removeVertex(id: Int) { + override fun removeVertex(id: Int): String? { if (!vertices.containsKey(id)) { - throw IllegalArgumentException("Vertex with id: $id doesn't exist in the graph.") + return "Vertex with id: $id doesn't exist in the graph." } if (adjacency[id]!!.isNotEmpty()) { for (idAdjacency in adjacency[id]!!.keys) { @@ -46,6 +49,7 @@ class DirectedGraph() : Graph() { } } vertices.remove(id) + return null } } diff --git a/src/main/kotlin/model/graph/Graph.kt b/src/main/kotlin/model/graph/Graph.kt index 79794e7..9ada71e 100644 --- a/src/main/kotlin/model/graph/Graph.kt +++ b/src/main/kotlin/model/graph/Graph.kt @@ -6,15 +6,16 @@ abstract class Graph { val edges = mutableListOf() fun getVertices(): Collection = vertices.values - abstract fun addEdge(v: Pair, w: Int?) - abstract fun removeEdge(v: Pair) - abstract fun removeVertex(id: Int) - fun addVertex(id: Int, data: String) { + abstract fun addEdge(v: Pair, w: Int?): String? + abstract fun removeEdge(v: Pair): String? + abstract fun removeVertex(id: Int): String? + fun addVertex(id: Int, data: String): String? { if (vertices.containsKey(id)) { - throw IllegalArgumentException("Vertex with id: $id already exists in the graph.") + return "Vertex with id: $id already exists in the graph." } vertices[id] = Vertex(id, data) adjacency[id] = hashMapOf() + return null } } diff --git a/src/main/kotlin/model/graph/UndirectedGraph.kt b/src/main/kotlin/model/graph/UndirectedGraph.kt index d25a2b9..f8e58ac 100644 --- a/src/main/kotlin/model/graph/UndirectedGraph.kt +++ b/src/main/kotlin/model/graph/UndirectedGraph.kt @@ -1,33 +1,33 @@ package model.graph class UndirectedGraph() : Graph() { - override fun addEdge(v: Pair, w: Int?) { - // добавить проверку на повторку ребра + override fun addEdge(v: Pair, w: Int?): String? { if (!vertices.containsKey(v.first)) { - throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") + return "Vertex with id: ${v.first} not exists in the graph." } if (!vertices.containsKey(v.second)) { - throw IllegalArgumentException("Vertex with id: ${v.second} not exists in the graph.") + return "Vertex with id: ${v.second} not exists in the graph." } if (v.first == v.second) { - throw IllegalArgumentException("Can't add edge from vertex to itself.") + return "Can't add edge from vertex to itself." } edges.add(Edge(Pair(v.first, v.second), w)) edges.add(Edge(Pair(v.second, v.first), w)) adjacency.getOrPut(v.first) { hashMapOf() }[v.second] = w adjacency.getOrPut(v.second) { hashMapOf() }[v.first] = w + return null } - override fun removeEdge(v: Pair) { + override fun removeEdge(v: Pair): String? { if (!vertices.containsKey(v.first)) { - throw IllegalArgumentException("Vertex with id: ${v.first} not exists in the graph.") + return "Vertex with id: ${v.first} not exists in the graph." } if (!vertices.containsKey(v.second)) { - throw IllegalArgumentException("Vertex with id: ${v.second} not exists in the graph.") + return "Vertex with id: ${v.second} not exists in the graph." } if (v.second !in adjacency[v.first]!!.keys) { - throw IllegalArgumentException("Edge from ${v.first} to ${v.second} not exists in the graph.") + return "Edge from ${v.first} to ${v.second} not exists in the graph." } edges.find { it.vertices == Pair(v.first, v.second) }?.let { edge -> edges.remove(edge) @@ -37,12 +37,12 @@ class UndirectedGraph() : Graph() { } adjacency[v.first]!!.remove(v.second) adjacency[v.second]!!.remove(v.first) - + return null } - override fun removeVertex(id: Int) { + override fun removeVertex(id: Int): String? { if (!vertices.containsKey(id)) { - throw IllegalArgumentException("Vertex with id: $id doesn't exist in the graph.") + return "Vertex with id: $id doesn't exist in the graph." } if (adjacency[id]!!.isNotEmpty()) { for (idAdjacency in adjacency[id]!!.keys) { @@ -50,6 +50,7 @@ class UndirectedGraph() : Graph() { } } vertices.remove(id) + return null } } diff --git a/src/main/kotlin/view/Color.kt b/src/main/kotlin/view/Color.kt index adbbf87..69569da 100644 --- a/src/main/kotlin/view/Color.kt +++ b/src/main/kotlin/view/Color.kt @@ -12,6 +12,7 @@ val White = Color(245, 245, 245) val Black = Color(0, 0, 0) val DarkRed2 = Color(41, 28, 28) val LightRed = Color(204, 0, 0) +val DarkRed3 = Color(189, 0, 0) val NastyaColorPalette = lightColorScheme( ) @@ -29,7 +30,9 @@ val KatyaColorPalette = lightColorScheme( onBackground = DarkGray, surface = Gray, onSurface = DarkRed2, - outline = DarkRed2 + outline = DarkRed2, + errorContainer = DarkRed3, + onErrorContainer = Black, ) val LiyaColorPalette = lightColorScheme( diff --git a/src/main/kotlin/view/CommonDialogs.kt b/src/main/kotlin/view/CommonDialogs.kt index 07c826c..76ff780 100644 --- a/src/main/kotlin/view/CommonDialogs.kt +++ b/src/main/kotlin/view/CommonDialogs.kt @@ -478,11 +478,14 @@ fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) fun ErrorDialog(onDismiss: () -> Unit, errorMessage: String) { AlertDialog( onDismissRequest = onDismiss, - title = { Text("Error:") }, - text = { Text(errorMessage) }, + title = { Text(text = "Error:", color = MaterialTheme.colorScheme.onErrorContainer) }, + text = { Text(text = errorMessage, color = MaterialTheme.colorScheme.onErrorContainer) }, confirmButton = { - Button(onClick = onDismiss) { - Text("ОК") + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colorScheme.errorContainer) + ) { + Text(text = "ОК", color = MaterialTheme.colorScheme.onErrorContainer) } } ) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 2be8c61..1756242 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -121,7 +121,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { } DropdownMenuItem(onClick = { expandedAlgorithmsMenu = false - viewModel.runKosarajuAlgorithm() + catchError(viewModel.runKosarajuAlgorithm()) }) { Text(text = "Kosaraju", color = MaterialTheme.colorScheme.onSecondary) } @@ -135,13 +135,13 @@ fun MainScreen(viewModel: MainScreenViewModel) { expandedAlgorithmsMenu = false catchError(viewModel.runPrimAlgorithm()) }) { - Text("Prim") + Text(text = "Prim", color = MaterialTheme.colorScheme.onSecondary) } DropdownMenuItem(onClick = { expandedAlgorithmsMenu = false showCycleSearchDialog = true }) { - Text("CycleSearch") + Text(text = "CycleSearch", color = MaterialTheme.colorScheme.onSecondary) } } } @@ -288,7 +288,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { DijkstraDialog( onDismiss = { showDijkstraDialog = false }, onRunAlgorithm = { start, end -> - viewModel.runDijkstraAlgorithm(start, end) + catchError(viewModel.runDijkstraAlgorithm(start, end)) showDijkstraDialog = false } ) @@ -297,7 +297,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { AddVertexDialog( onDismiss = { showAddVertexDialog = false }, onRunAlgorithm = { id, data -> - viewModel.addVertex(id, data) + catchError(viewModel.addVertex(id, data)) showAddVertexDialog = false } ) @@ -315,7 +315,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { RemoveVertexDialog( onDismiss = { showRemoveVertexDialog = false }, onRunAlgorithm = { id -> - viewModel.removeVertex(id) + catchError(viewModel.removeVertex(id)) showRemoveVertexDialog = false } ) @@ -324,7 +324,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { AddEdgeDialog( onDismiss = { showAddEdgeDialog = false }, onRunAlgorithm = { from, to, w -> - viewModel.addEdge(from, to, w) + catchError(viewModel.addEdge(from, to, w)) showAddEdgeDialog = false } ) @@ -333,7 +333,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { RemoveEdgeDialog( onDismiss = { showRemoveEdgeDialog = false }, onRunAlgorithm = { from, to -> - viewModel.removeEdge(from, to) + catchError(viewModel.removeEdge(from, to)) showRemoveEdgeDialog = false } ) @@ -342,7 +342,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { SaveToNeo4jDialog( onDismiss = { showNeo4jDialog = false }, onRunAlgorithm = { uri, user, password -> - viewModel.saveToNeo4j(uri, user, password) + catchError(viewModel.saveToNeo4j(uri, user, password)) showNeo4jDialog = false } ) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index feab0c9..442c122 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -34,26 +34,27 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt } } - fun addVertex(id: Int, data: String) { - graphViewModel.addVertex(id, data) + fun addVertex(id: Int, data: String): String? { + return graphViewModel.addVertex(id, data) } - fun addEdge(from: Int, to: Int, w: Int?) { - graphViewModel.addEdge(from, to, w) + fun addEdge(from: Int, to: Int, w: Int?): String? { + return graphViewModel.addEdge(from, to, w) } - fun removeVertex(id: Int) { - graphViewModel.removeVertex(id) + fun removeVertex(id: Int): String? { + return graphViewModel.removeVertex(id) } - fun removeEdge(from: Int, to: Int) { - graphViewModel.removeEdge(from, to) + fun removeEdge(from: Int, to: Int): String? { + return graphViewModel.removeEdge(from, to) } - fun saveToNeo4j(uri: String, user: String, password: String) { + fun saveToNeo4j(uri: String, user: String, password: String): String? { val neo4j = Neo4jRepository(uri, user, password) - neo4j.saveGraph(graph) + return neo4j.saveGraph(graph) } + fun saveToFile(): String? { val fileSystem = FileSystem() return fileSystem.saveGraph(graph) @@ -61,27 +62,29 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt /** Paint the vertices and edges of the found path. */ - fun runDijkstraAlgorithm(start: Int, end: Int) { + fun runDijkstraAlgorithm(start: Int, end: Int):String? { resetGraphView() val dijkstra = Dijkstra(graph) val result = dijkstra.findShortestPaths(start, end) - for (vertexId in result) { + if (result.first == null) return result.second!! + for (vertexId in result.first!!) { graphViewModel.verticesView[vertexId]?.color = Color(125, 21, 21) } for (edgeView in graphViewModel.edgesView) { - if (edgeView.key.vertices.first in result && edgeView.key.vertices.second in result) { + if (edgeView.key.vertices.first in result.first!! && edgeView.key.vertices.second in result.first!!) { edgeView.value.color = Color(10, 230, 248) edgeView.value.strokeWidth = 9f } } + return null } /** Paint each ccs its own color. The number of colors is limited, * so if there are more than 10 ccs, the colors will begin to repeat. */ - fun runKosarajuAlgorithm() { + fun runKosarajuAlgorithm(): String? { if (graph is UndirectedGraph) { - throw IllegalArgumentException("Kosaraju's algorithm cannot be run on undirected graphs.") + return "Kosaraju's algorithm cannot be run on undirected graphs." } val colors = listOf( Color(125, 21, 21), @@ -103,6 +106,7 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt graphViewModel.verticesView[vertexId]?.color = colors[i % 10] } } + return null } /** Paint each community its own color. The number of colors is limited, @@ -137,8 +141,8 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt if (graph is DirectedGraph) { return "Prims's algorithm cannot be run on directed graphs." } - for(edge in graph.edges) { - if(edge.weight == null) { + for (edge in graph.edges) { + if (edge.weight == null) { return "Each edge of graph for Prim's algorithm must have a weight:\nthe edge with weight = 'null' is incorrect." } } diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index de5dd83..5aef07d 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -32,31 +32,38 @@ class GraphViewModel( } } - fun addVertex(id: Int, data: String) { - graph.addVertex(id, data) + fun addVertex(id: Int, data: String): String? { + val addedResult = graph.addVertex(id, data) + if (addedResult != null) return addedResult verticesView[id] = VertexViewModel(0.dp, 0.dp, defaultColorVertex, graph.vertices[id]!!, showVerticesLabels) + return null } - fun removeVertex(id: Int) { - graph.removeVertex(id) + fun removeVertex(id: Int): String? { + val removedResult = graph.removeVertex(id) + if (removedResult != null) return removedResult verticesView.remove(id) val edgesToRemove = edgesView.keys.filter { edge -> edge.vertices.first == id || edge.vertices.second == id } edgesToRemove.forEach { edge -> edgesView.remove(edge) } + return null } - fun addEdge(from: Int, to: Int, w: Int?) { - graph.addEdge(Pair(from, to), w) - val fst = verticesView[from] ?: throw IllegalStateException("VertexView for vertex with id: $from not found") - val snd = verticesView[to] ?: throw IllegalStateException("VertexView for vertex with id: $to not found") + fun addEdge(from: Int, to: Int, w: Int?): String? { + val addedResult = graph.addEdge(Pair(from, to), w) + if (addedResult != null) return addedResult + val fst = verticesView[from] ?: return "VertexView for vertex with id: $from not found" + val snd = verticesView[to] ?: return "VertexView for vertex with id: $to not found" graph.edges.find { it.vertices == Pair(from, to) }?.let { edge -> edgesView[edge] = EdgeViewModel(fst, snd, defaultColorLine, defaultStrokeWidth, edge, showEdgesLabels) } + return null } - fun removeEdge(from: Int, to: Int) { - graph.removeEdge(Pair(from, to)) + fun removeEdge(from: Int, to: Int): String? { + val removedResult = graph.removeEdge(Pair(from, to)) + if (removedResult != null) return removedResult edgesView.keys.find { it.vertices == Pair(from, to) }?.let { edge -> edgesView.remove(edge) } @@ -65,5 +72,6 @@ class GraphViewModel( edgesView.remove(edge) } } + return null } } diff --git a/src/test/kotlin/DijkstraTest.kt b/src/test/kotlin/DijkstraTest.kt index e543c29..f96b997 100644 --- a/src/test/kotlin/DijkstraTest.kt +++ b/src/test/kotlin/DijkstraTest.kt @@ -19,7 +19,7 @@ class DijkstraTest { dirGraph.addVertex(3, "C") dirGraph.addEdge(Pair(1, 3), 5) dirGraph.addEdge(Pair(3, 2), -2) - assertFailsWith { shortestPathDirected.findShortestPaths(1, 2) } + assertEquals(null to "Edge with negative weights in Dijkstra's algorithm.", shortestPathDirected.findShortestPaths(1, 2)) } @Test @@ -29,7 +29,7 @@ class DijkstraTest { dirGraph.addVertex(3, "C") dirGraph.addEdge(Pair(1, 3), 5) dirGraph.addEdge(Pair(3, 2), null) - assertFailsWith { shortestPathDirected.findShortestPaths(1, 2) } + assertEquals(null to "Edge without weights in Dijkstra's algorithm.", shortestPathDirected.findShortestPaths(1, 2)) } @Test @@ -39,7 +39,7 @@ class DijkstraTest { dirGraph.addVertex(3, "C") dirGraph.addEdge(Pair(1, 3), 5) dirGraph.addEdge(Pair(3, 2), 2) - assertFailsWith { shortestPathDirected.findShortestPaths(4, 5) } + assertEquals(null to "The vertex doesn't exist in the graph.", shortestPathDirected.findShortestPaths(4, 5)) } //endregion @@ -54,7 +54,7 @@ class DijkstraTest { dirGraph.addEdge(Pair(2, 3), 5) dirGraph.addEdge(Pair(3, 1), 100) dirGraph.addEdge(Pair(4, 3), 15) - assertEquals(listOf(), shortestPathDirected.findShortestPaths(1, 4)) + assertEquals(listOf() to null, shortestPathDirected.findShortestPaths(1, 4)) } @Test @@ -79,7 +79,7 @@ class DijkstraTest { dirGraph.addEdge(Pair(5, 7), 7) dirGraph.addEdge(Pair(7, 8), 54) dirGraph.addEdge(Pair(8, 9), 21) - assertEquals(listOf(1, 2, 5, 7, 8, 9), shortestPathDirected.findShortestPaths(1, 9)) + assertEquals(listOf(1, 2, 5, 7, 8, 9) to null, shortestPathDirected.findShortestPaths(1, 9)) } @Test @@ -107,7 +107,7 @@ class DijkstraTest { dirGraph.addEdge(Pair(4, 1), 15) dirGraph.addEdge(Pair(5, 6), 120) dirGraph.addEdge(Pair(7, 5), 7) - assertEquals(listOf(1, 2, 5, 7, 8, 9), shortestPathDirected.findShortestPaths(1, 9)) + assertEquals(listOf(1, 2, 5, 7, 8, 9) to null, shortestPathDirected.findShortestPaths(1, 9)) } //endregion @@ -125,7 +125,7 @@ class DijkstraTest { undirGraph.addEdge(Pair(1, 2), 15) undirGraph.addEdge(Pair(6, 5), 20) undirGraph.addEdge(Pair(5, 4), 50) - assertEquals(listOf(), shortestPathUndirected.findShortestPaths(2, 5)) + assertEquals(listOf() to null, shortestPathUndirected.findShortestPaths(2, 5)) } @Test @@ -152,7 +152,7 @@ class DijkstraTest { undirGraph.addEdge(Pair(8, 9), 34) undirGraph.addEdge(Pair(9, 10), 80) undirGraph.addEdge(Pair(2, 11), 5) - assertEquals(listOf(1, 3, 5, 6, 4, 8, 9), shortestPathUndirected.findShortestPaths(1, 9)) + assertEquals(listOf(1, 3, 5, 6, 4, 8, 9) to null, shortestPathUndirected.findShortestPaths(1, 9)) } //endregion } From ee92e2f6ec9053528815c8ec98d0e3a4cdac0cb0 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 9 Oct 2024 21:02:05 +0300 Subject: [PATCH 116/131] feat: added Neo4j dialog when loading --- src/main/kotlin/Main.kt | 16 +++++++++++++--- src/main/kotlin/view/CommonDialogs.kt | 4 ++-- src/main/kotlin/view/MainScreen.kt | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index a3e668c..8498efd 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -12,6 +12,7 @@ import databases.Neo4jRepository import model.graph.* import view.ErrorDialog import view.MainScreen +import view.Neo4jDialog import view.WelcomeScreen import viewmodel.MainScreenViewModel import viewmodel.graph.CircularPlacementStrategy @@ -20,12 +21,11 @@ import viewmodel.graph.CircularPlacementStrategy @Preview fun App() { MaterialTheme { - // пока что, для удобства разработки - val neo4j = Neo4jRepository("bolt://localhost:7687", "neo4j", "password") val fileSystem = FileSystem() var graphType by remember { mutableStateOf(null) } var graph by remember { mutableStateOf(null) } var showErrorDialog by remember { mutableStateOf(false) } + var showNeo4jDialog by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf(null) } fun catchErrorOrGetGraph(result: Pair) { if (result.second == null) { @@ -42,7 +42,7 @@ fun App() { when (graphType) { "Directed" -> graph = DirectedGraph() "Undirected" -> graph = UndirectedGraph() - "Neo4j" -> catchErrorOrGetGraph(neo4j.loadGraph()) + "Neo4j" -> showNeo4jDialog = true else -> catchErrorOrGetGraph(fileSystem.openGraph()) } } @@ -57,6 +57,16 @@ fun App() { errorMessage!! ) } + if (showNeo4jDialog) { + Neo4jDialog( + onDismiss = { showNeo4jDialog = false }, + onRunAlgorithm = { uri, user, password -> + val neo4j = Neo4jRepository(uri, user, password) + catchErrorOrGetGraph(neo4j.loadGraph()) + showNeo4jDialog = false + } + ) + } } } diff --git a/src/main/kotlin/view/CommonDialogs.kt b/src/main/kotlin/view/CommonDialogs.kt index 76ff780..530bc27 100644 --- a/src/main/kotlin/view/CommonDialogs.kt +++ b/src/main/kotlin/view/CommonDialogs.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp @Composable -fun SaveToNeo4jDialog(onDismiss: () -> Unit, onRunAlgorithm: (String, String, String) -> Unit) { +fun Neo4jDialog(onDismiss: () -> Unit, onRunAlgorithm: (String, String, String) -> Unit) { var uri by remember { mutableStateOf("") } var user by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } @@ -74,7 +74,7 @@ fun SaveToNeo4jDialog(onDismiss: () -> Unit, onRunAlgorithm: (String, String, St }, colors = ButtonDefaults.buttonColors(MaterialTheme.colorScheme.primary) ) { - Text("Save graph", color = MaterialTheme.colorScheme.onPrimary) + Text("Ok", color = MaterialTheme.colorScheme.onPrimary) } }, dismissButton = { diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 1756242..1992d21 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -339,7 +339,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { ) } if (showNeo4jDialog) { - SaveToNeo4jDialog( + Neo4jDialog( onDismiss = { showNeo4jDialog = false }, onRunAlgorithm = { uri, user, password -> catchError(viewModel.saveToNeo4j(uri, user, password)) From fc6dde136dbe075d976a2b751665a868236867cc Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 9 Oct 2024 21:34:34 +0300 Subject: [PATCH 117/131] fix: weight display made correct --- src/main/kotlin/viewmodel/graph/EdgeViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt b/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt index 624885d..a86a7d3 100644 --- a/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/EdgeViewModel.kt @@ -13,7 +13,7 @@ class EdgeViewModel( private val _labelVisible: State, ) { val label - get() = e.weight.toString() + get() = e.weight?.toString() ?: "" val labelVisible get() = _labelVisible.value From 5269c025db15d44888c60d1bf51d180bd43731de Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 9 Oct 2024 21:52:16 +0300 Subject: [PATCH 118/131] feat: added display of indexes --- src/main/kotlin/view/MainScreen.kt | 15 +++++++++++++++ src/main/kotlin/view/graph/VertexView.kt | 8 ++++++++ src/main/kotlin/viewmodel/MainScreenViewModel.kt | 3 ++- src/main/kotlin/viewmodel/graph/GraphViewModel.kt | 5 +++-- .../kotlin/viewmodel/graph/VertexViewModel.kt | 6 ++++++ 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 1992d21..92701c8 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -66,6 +66,21 @@ fun MainScreen(viewModel: MainScreenViewModel) { color = MaterialTheme.colorScheme.onSurface ) } + Row { + Checkbox( + checked = viewModel.showVerticesId.value, onCheckedChange = { + viewModel.showVerticesId.value = it + }, colors = CheckboxDefaults.colors( + checkedColor = MaterialTheme.colorScheme.primary + ) + ) + Text( + text = "Show vertices id", + fontSize = 18.sp, + modifier = Modifier.padding(10.dp), + color = MaterialTheme.colorScheme.onSurface + ) + } Row { Checkbox( checked = viewModel.showEdgesLabels.value, onCheckedChange = { diff --git a/src/main/kotlin/view/graph/VertexView.kt b/src/main/kotlin/view/graph/VertexView.kt index b32b5cf..8ca8a9f 100644 --- a/src/main/kotlin/view/graph/VertexView.kt +++ b/src/main/kotlin/view/graph/VertexView.kt @@ -49,5 +49,13 @@ fun VertexView( color = if (isColorDark(viewModel.color)) Color.White else Color.Black ) } + if (viewModel.idVisible) { + Text( + modifier = Modifier + .align(Alignment.Center).offset(0.dp, -viewModel.radius - 10.dp), + text = viewModel.id, + color = if (isColorDark(viewModel.color)) Color.White else Color.Black + ) + } } } diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 442c122..ef4a8dc 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -18,8 +18,9 @@ var defaultStrokeWidth: Float = 4f class MainScreenViewModel(private val graph: Graph, private val representationStrategy: RepresentationStrategy) { val showVerticesLabels = mutableStateOf(false) + val showVerticesId = mutableStateOf(false) val showEdgesLabels = mutableStateOf(false) - val graphViewModel = GraphViewModel(graph, showVerticesLabels, showEdgesLabels) + val graphViewModel = GraphViewModel(graph, showVerticesLabels, showVerticesId, showEdgesLabels) init { representationStrategy.place(800.0, 600.0, graphViewModel.verticesView.values) diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index 5aef07d..55512f6 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -14,6 +14,7 @@ import viewmodel.defaultStrokeWidth class GraphViewModel( private val graph: Graph, private val showVerticesLabels: State, + private val showVerticesId: State, private val showEdgesLabels: State, ) { internal val verticesView: HashMap = hashMapOf() @@ -21,7 +22,7 @@ class GraphViewModel( init { graph.getVertices().forEach { vertex -> - verticesView[vertex.id] = VertexViewModel(0.dp, 0.dp, defaultColorVertex, vertex, showVerticesLabels) + verticesView[vertex.id] = VertexViewModel(0.dp, 0.dp, defaultColorVertex, vertex, showVerticesLabels, showVerticesId) } graph.edges.forEach { edge -> val fst = verticesView[edge.vertices.first] @@ -35,7 +36,7 @@ class GraphViewModel( fun addVertex(id: Int, data: String): String? { val addedResult = graph.addVertex(id, data) if (addedResult != null) return addedResult - verticesView[id] = VertexViewModel(0.dp, 0.dp, defaultColorVertex, graph.vertices[id]!!, showVerticesLabels) + verticesView[id] = VertexViewModel(0.dp, 0.dp, defaultColorVertex, graph.vertices[id]!!, showVerticesLabels, showVerticesId) return null } diff --git a/src/main/kotlin/viewmodel/graph/VertexViewModel.kt b/src/main/kotlin/viewmodel/graph/VertexViewModel.kt index ff1db31..5c08bcd 100644 --- a/src/main/kotlin/viewmodel/graph/VertexViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/VertexViewModel.kt @@ -15,6 +15,7 @@ class VertexViewModel( color: Color, private val v: Vertex, private val _labelVisible: State, + private val _idVisible: State, val radius: Dp = 25.dp ) { private var _x = mutableStateOf(x) @@ -41,6 +42,11 @@ class VertexViewModel( val labelVisible get() = _labelVisible.value + val id + get() = v.id.toString() + + val idVisible + get() = _idVisible.value fun onDrag(offset: Offset) { _x.value += offset.x.dp From 95b5a58408603e000aad2e5596e50e2ab6592a58 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 9 Oct 2024 21:54:52 +0300 Subject: [PATCH 119/131] feat: changed color id vertex --- src/main/kotlin/view/graph/VertexView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/view/graph/VertexView.kt b/src/main/kotlin/view/graph/VertexView.kt index 8ca8a9f..760ceb0 100644 --- a/src/main/kotlin/view/graph/VertexView.kt +++ b/src/main/kotlin/view/graph/VertexView.kt @@ -54,7 +54,7 @@ fun VertexView( modifier = Modifier .align(Alignment.Center).offset(0.dp, -viewModel.radius - 10.dp), text = viewModel.id, - color = if (isColorDark(viewModel.color)) Color.White else Color.Black + color = Color.Black ) } } From c0506fb563e1f64d63177842e2a04230db3b9000 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 9 Oct 2024 22:37:20 +0300 Subject: [PATCH 120/131] fix: fixed a bug in highlighting edges in Dijkstra --- src/main/kotlin/viewmodel/MainScreenViewModel.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index ef4a8dc..3a844c6 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -71,11 +71,9 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt for (vertexId in result.first!!) { graphViewModel.verticesView[vertexId]?.color = Color(125, 21, 21) } - for (edgeView in graphViewModel.edgesView) { - if (edgeView.key.vertices.first in result.first!! && edgeView.key.vertices.second in result.first!!) { - edgeView.value.color = Color(10, 230, 248) - edgeView.value.strokeWidth = 9f - } + for (i in 1 until result.first!!.size) { + val a = graphViewModel.edgesView.keys.find {it.vertices == Pair(result.first!![i-1], result.first!![i]) } + graphViewModel.edgesView[a]!!.color = Color(86, 29, 39) } return null } From 0ac00444e2b63c87e3a40caa217e60c53e296c2d Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Fri, 11 Oct 2024 02:06:09 +0300 Subject: [PATCH 121/131] fix: changed error window --- src/main/kotlin/view/CommonDialogs.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/view/CommonDialogs.kt b/src/main/kotlin/view/CommonDialogs.kt index 530bc27..ad42a45 100644 --- a/src/main/kotlin/view/CommonDialogs.kt +++ b/src/main/kotlin/view/CommonDialogs.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp @Composable fun Neo4jDialog(onDismiss: () -> Unit, onRunAlgorithm: (String, String, String) -> Unit) { @@ -478,7 +479,12 @@ fun RemoveEdgeDialog(onDismiss: () -> Unit, onRunAlgorithm: (Int, Int) -> Unit) fun ErrorDialog(onDismiss: () -> Unit, errorMessage: String) { AlertDialog( onDismissRequest = onDismiss, - title = { Text(text = "Error:", color = MaterialTheme.colorScheme.onErrorContainer) }, + title = { + Text( + text = "Error:", color = MaterialTheme.colorScheme.errorContainer, + fontSize = 20.sp + ) + }, text = { Text(text = errorMessage, color = MaterialTheme.colorScheme.onErrorContainer) }, confirmButton = { Button( From 2d48498339a1c80fe8eadaee67c0b07386c8f0bb Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Fri, 11 Oct 2024 02:11:37 +0300 Subject: [PATCH 122/131] chore: imports optimized --- src/main/kotlin/Main.kt | 8 +++----- src/main/kotlin/view/CommonDialogs.kt | 6 ++---- src/main/kotlin/view/MainScreen.kt | 14 +++----------- src/main/kotlin/view/WelcomeScreen.kt | 4 +--- .../viewmodel/graph/CircularPlacementStrategy.kt | 2 -- src/main/kotlin/viewmodel/graph/GraphViewModel.kt | 2 -- .../viewmodel/graph/RepresentationStrategy.kt | 2 -- src/main/kotlin/viewmodel/graph/VertexViewModel.kt | 3 +-- 8 files changed, 10 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 8498efd..830c30d 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,15 +1,13 @@ import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material.* import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.* -import androidx.compose.ui.Modifier import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import databases.FileSystem import databases.Neo4jRepository -import model.graph.* +import model.graph.DirectedGraph +import model.graph.Graph +import model.graph.UndirectedGraph import view.ErrorDialog import view.MainScreen import view.Neo4jDialog diff --git a/src/main/kotlin/view/CommonDialogs.kt b/src/main/kotlin/view/CommonDialogs.kt index ad42a45..2da988b 100644 --- a/src/main/kotlin/view/CommonDialogs.kt +++ b/src/main/kotlin/view/CommonDialogs.kt @@ -1,13 +1,11 @@ package view -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.* -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.KeyboardType diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index 92701c8..d971689 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -2,25 +2,17 @@ package view import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.* import androidx.compose.ui.Alignment -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import model.graph.Graph -import androidx.compose.material3.MaterialTheme -import databases.FileSystem -import org.jetbrains.skia.impl.Stats.enabled import view.graph.GraphView import viewmodel.MainScreenViewModel -import viewmodel.graph.CircularPlacementStrategy -import viewmodel.graph.GraphViewModel @Composable fun MainScreen(viewModel: MainScreenViewModel) { diff --git a/src/main/kotlin/view/WelcomeScreen.kt b/src/main/kotlin/view/WelcomeScreen.kt index 2a49e0b..42a8f69 100644 --- a/src/main/kotlin/view/WelcomeScreen.kt +++ b/src/main/kotlin/view/WelcomeScreen.kt @@ -3,11 +3,9 @@ package view import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.Surface import androidx.compose.material.Text -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @Composable diff --git a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt index ca7cab8..a2e1b7e 100644 --- a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt @@ -1,11 +1,9 @@ package viewmodel.graph -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import kotlin.math.cos import kotlin.math.min import kotlin.math.sin -import kotlin.random.Random class CircularPlacementStrategy : RepresentationStrategy { override fun place(width: Double, height: Double, vertices: Collection) { diff --git a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt index 55512f6..5b220cc 100644 --- a/src/main/kotlin/viewmodel/graph/GraphViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/GraphViewModel.kt @@ -1,12 +1,10 @@ package viewmodel.graph import androidx.compose.runtime.State -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import model.graph.Edge import model.graph.Graph import model.graph.UndirectedGraph -import model.graph.Vertex import viewmodel.defaultColorLine import viewmodel.defaultColorVertex import viewmodel.defaultStrokeWidth diff --git a/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt b/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt index 055ea1a..0b56ffc 100644 --- a/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/RepresentationStrategy.kt @@ -1,7 +1,5 @@ package viewmodel.graph -import viewmodel.graph.VertexViewModel - interface RepresentationStrategy { fun place(width: Double, height: Double, vertices: Collection) } \ No newline at end of file diff --git a/src/main/kotlin/viewmodel/graph/VertexViewModel.kt b/src/main/kotlin/viewmodel/graph/VertexViewModel.kt index 5c08bcd..5faa9ff 100644 --- a/src/main/kotlin/viewmodel/graph/VertexViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/VertexViewModel.kt @@ -2,7 +2,6 @@ package viewmodel.graph import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp @@ -38,7 +37,7 @@ class VertexViewModel( } val label - get() = v.data.toString() + get() = v.data val labelVisible get() = _labelVisible.value From 87ac35b0b7e801013cb53df3c625ff54e0f4fc9d Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Fri, 11 Oct 2024 03:30:39 +0300 Subject: [PATCH 123/131] fix: the buttons are aligned --- src/main/kotlin/view/MainScreen.kt | 56 ++++++++++++++++++------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index d971689..fc79024 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -31,8 +31,8 @@ fun MainScreen(viewModel: MainScreenViewModel) { var showNeo4jDialog by remember { mutableStateOf(false) } var showErrorDialog by remember { mutableStateOf(false) } var errorMessage: String? = null - fun catchError(messageOfError: String? ) { - if(messageOfError != null ) { + fun catchError(messageOfError: String?) { + if (messageOfError != null) { errorMessage = messageOfError showErrorDialog = true } @@ -42,7 +42,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { Row( horizontalArrangement = Arrangement.spacedBy(20.dp) ) { - Column(modifier = Modifier.width(300.dp).fillMaxHeight().background(MaterialTheme.colorScheme.surface)) { + Column(modifier = Modifier.width(270.dp).fillMaxHeight().background(MaterialTheme.colorScheme.surface)) { Row { Checkbox( checked = viewModel.showVerticesLabels.value, onCheckedChange = { @@ -91,6 +91,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { Button( onClick = viewModel::resetGraphView, enabled = true, + modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp), colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary @@ -98,20 +99,26 @@ fun MainScreen(viewModel: MainScreenViewModel) { ) { Text( text = "Reset default settings", - fontSize = 20.sp, - modifier = Modifier.padding(4.dp), - color = MaterialTheme.colorScheme.onPrimary + fontSize = 18.sp, + color = MaterialTheme.colorScheme.onPrimary, + modifier = Modifier.align(Alignment.CenterVertically) ) } - Box { - Row(verticalAlignment = Alignment.CenterVertically) { + Box (modifier = Modifier.padding(horizontal = 10.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Button( onClick = { expandedAlgorithmsMenu = true }, colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary - ) + ), + modifier = Modifier.fillMaxWidth() ) { - Text(text = "Algorithm", color = MaterialTheme.colorScheme.onPrimary) + Text( + text = "Algorithm", + fontSize = 18.sp, + color = MaterialTheme.colorScheme.onPrimary, + modifier = Modifier.align(Alignment.CenterVertically) + ) Icon(Icons.Default.ArrowDropDown, contentDescription = "Select algorithm") } } @@ -152,16 +159,17 @@ fun MainScreen(viewModel: MainScreenViewModel) { } } } - Row { - Box(modifier = Modifier.padding(2.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { + Row (modifier = Modifier.padding(horizontal = 10.dp), horizontalArrangement = Arrangement.SpaceBetween){ + Box(modifier = Modifier.weight(1f).padding(end = 2.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Button( onClick = { expandedAddMenu = true }, colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary - ) + ), + modifier = Modifier.fillMaxWidth() ) { - Text(text = "Add", color = MaterialTheme.colorScheme.onPrimary) + Text(text = "Add", fontSize = 18.sp, color = MaterialTheme.colorScheme.onPrimary) Icon(Icons.Default.ArrowDropDown, contentDescription = "Add") } } @@ -184,15 +192,16 @@ fun MainScreen(viewModel: MainScreenViewModel) { } } } - Box(modifier = Modifier.padding(2.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { + Box(modifier = Modifier.weight(1f).padding(end = 2.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Button( onClick = { expandedRemoveMenu = true }, colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary - ) + ), + modifier = Modifier.fillMaxWidth() ) { - Text(text = "Remove", color = MaterialTheme.colorScheme.onPrimary) + Text(text = "Remove", fontSize = 18.sp, color = MaterialTheme.colorScheme.onPrimary) Icon(Icons.Default.ArrowDropDown, contentDescription = "Remove") } } @@ -216,15 +225,16 @@ fun MainScreen(viewModel: MainScreenViewModel) { } } } - Box(modifier = Modifier.padding(2.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { + Box(modifier = Modifier.padding(horizontal = 10.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Button( onClick = { expandedSaveMenu = true }, colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary - ) + ), + modifier = Modifier.fillMaxWidth() ) { - Text(text = "Save graph", color = MaterialTheme.colorScheme.onPrimary) + Text(text = "Save graph", fontSize = 18.sp, color = MaterialTheme.colorScheme.onPrimary) Icon(Icons.Default.ArrowDropDown, contentDescription = "Save graph") } } From 5670ddac2e0800763ad405aaae4d88263e053e5b Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Tue, 15 Oct 2024 03:32:13 +0300 Subject: [PATCH 124/131] fix: catching errors is done better --- src/main/kotlin/Main.kt | 11 +++++++++-- src/main/kotlin/databases/Neo4jRepository.kt | 6 ++++-- src/main/kotlin/viewmodel/MainScreenViewModel.kt | 15 ++++++++++----- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 830c30d..a493e10 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -59,8 +59,15 @@ fun App() { Neo4jDialog( onDismiss = { showNeo4jDialog = false }, onRunAlgorithm = { uri, user, password -> - val neo4j = Neo4jRepository(uri, user, password) - catchErrorOrGetGraph(neo4j.loadGraph()) + try { + val neo4j = Neo4jRepository(uri, user, password) + catchErrorOrGetGraph(neo4j.loadGraph()) + } catch (e: Exception) { + catchErrorOrGetGraph( + null to "Error loading:\n" + + (e.message?.substringAfter("Exception: ") ?: "unable to load graph") + ) + } showNeo4jDialog = false } ) diff --git a/src/main/kotlin/databases/Neo4jRepository.kt b/src/main/kotlin/databases/Neo4jRepository.kt index b9e6d89..995c4f0 100644 --- a/src/main/kotlin/databases/Neo4jRepository.kt +++ b/src/main/kotlin/databases/Neo4jRepository.kt @@ -43,7 +43,8 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { return null } catch (e: Exception) { transaction.rollback() - return "Failed to save graph (transaction error)" + return "Failed to save graph:\n" + + (e.message?.substringAfter("Exception: ") ?: "incorrect graph saving") } finally { transaction.close() } @@ -91,7 +92,8 @@ class Neo4jRepository(uri: String, user: String, password: String) : Closeable { return (graph to null) } catch (e: Exception) { transaction.rollback() - return null to "Failed to load graph (transaction error)" + return null to "Failed to load graph:\n" + + (e.message?.substringAfter("Exception: ") ?: "incorrect graph loading") } finally { transaction.close() } diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index 3a844c6..cfcd48e 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -52,8 +52,13 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt } fun saveToNeo4j(uri: String, user: String, password: String): String? { - val neo4j = Neo4jRepository(uri, user, password) - return neo4j.saveGraph(graph) + try { + val neo4j = Neo4jRepository(uri, user, password) + return neo4j.saveGraph(graph) + } catch (e: Exception) { + return "Error saving:\n" + + (e.message?.substringAfter("Exception: ") ?: "unable to save graph") + } } fun saveToFile(): String? { @@ -63,7 +68,7 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt /** Paint the vertices and edges of the found path. */ - fun runDijkstraAlgorithm(start: Int, end: Int):String? { + fun runDijkstraAlgorithm(start: Int, end: Int): String? { resetGraphView() val dijkstra = Dijkstra(graph) val result = dijkstra.findShortestPaths(start, end) @@ -72,8 +77,8 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt graphViewModel.verticesView[vertexId]?.color = Color(125, 21, 21) } for (i in 1 until result.first!!.size) { - val a = graphViewModel.edgesView.keys.find {it.vertices == Pair(result.first!![i-1], result.first!![i]) } - graphViewModel.edgesView[a]!!.color = Color(86, 29, 39) + val a = graphViewModel.edgesView.keys.find { it.vertices == Pair(result.first!![i - 1], result.first!![i]) } + graphViewModel.edgesView[a]!!.color = Color(86, 29, 39) } return null } From 2eadcc0294aae21cd88d905ba32247d65473e83f Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 30 Oct 2024 21:45:20 +0300 Subject: [PATCH 125/131] refactor: renamed and moved variable --- src/main/kotlin/view/Color.kt | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/view/Color.kt b/src/main/kotlin/view/Color.kt index 69569da..7bdb13b 100644 --- a/src/main/kotlin/view/Color.kt +++ b/src/main/kotlin/view/Color.kt @@ -3,22 +3,21 @@ package view import androidx.compose.material3.lightColorScheme import androidx.compose.ui.graphics.Color -//TODO: make more suitable colors -val DarkRed = Color(125, 21, 21) +val DarkRed00 = Color(125, 21, 21) +val DarkRed01 = Color(41, 28, 28) +val DarkRed02 = Color(189, 0, 0) +val LightRed = Color(204, 0, 0) val LightGray = Color(199, 199, 199) val DarkGray = Color(74, 74, 74) val Gray = Color(150, 150, 150) val White = Color(245, 245, 245) val Black = Color(0, 0, 0) -val DarkRed2 = Color(41, 28, 28) -val LightRed = Color(204, 0, 0) -val DarkRed3 = Color(189, 0, 0) val NastyaColorPalette = lightColorScheme( ) val KatyaColorPalette = lightColorScheme( - primary = DarkRed, + primary = DarkRed00, onPrimary = White, primaryContainer = LightGray, onPrimaryContainer = Black, @@ -29,9 +28,9 @@ val KatyaColorPalette = lightColorScheme( background = LightGray, onBackground = DarkGray, surface = Gray, - onSurface = DarkRed2, - outline = DarkRed2, - errorContainer = DarkRed3, + onSurface = DarkRed01, + outline = DarkRed01, + errorContainer = DarkRed02, onErrorContainer = Black, ) From 257923f7e790963e08ef5ecaa40f6e3782e13391 Mon Sep 17 00:00:00 2001 From: Ekaterina Tenyaeva Date: Wed, 30 Oct 2024 23:11:31 +0300 Subject: [PATCH 126/131] fix: the theme button is positioned correctly moved to the upper right corner and does not take up extra space --- src/main/kotlin/view/MainScreen.kt | 95 ++++++++++++++++-------------- 1 file changed, 52 insertions(+), 43 deletions(-) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index fc79024..d1eca62 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -39,9 +39,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { } Material3AppTheme(theme) { - Row( - horizontalArrangement = Arrangement.spacedBy(20.dp) - ) { + Row { Column(modifier = Modifier.width(270.dp).fillMaxHeight().background(MaterialTheme.colorScheme.surface)) { Row { Checkbox( @@ -104,7 +102,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { modifier = Modifier.align(Alignment.CenterVertically) ) } - Box (modifier = Modifier.padding(horizontal = 10.dp)) { + Box(modifier = Modifier.padding(horizontal = 10.dp)) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Button( onClick = { expandedAlgorithmsMenu = true }, colors = ButtonDefaults.buttonColors( @@ -159,7 +157,7 @@ fun MainScreen(viewModel: MainScreenViewModel) { } } } - Row (modifier = Modifier.padding(horizontal = 10.dp), horizontalArrangement = Arrangement.SpaceBetween){ + Row(modifier = Modifier.padding(horizontal = 10.dp), horizontalArrangement = Arrangement.SpaceBetween) { Box(modifier = Modifier.weight(1f).padding(end = 2.dp)) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Button( @@ -258,47 +256,58 @@ fun MainScreen(viewModel: MainScreenViewModel) { } } } - Box { - var expandedThemeMenu by remember { mutableStateOf(false) } - Button( - onClick = { expandedThemeMenu = true }, - modifier = Modifier.align(Alignment.TopEnd), colors = ButtonDefaults.buttonColors( - backgroundColor = MaterialTheme.colorScheme.primary, - contentColor = MaterialTheme.colorScheme.onPrimary - ) - ) { - Text(text = "Theme", color = MaterialTheme.colorScheme.onPrimary) - Icon(Icons.Default.ArrowDropDown, contentDescription = "Select theme") - } - DropdownMenu( - expanded = expandedThemeMenu, - onDismissRequest = { expandedThemeMenu = false }, - modifier = Modifier.background(MaterialTheme.colorScheme.secondary) - ) { - DropdownMenuItem(onClick = { - theme = Theme.NASTYA - expandedThemeMenu = false - }) { - Text("Nastya's theme", color = MaterialTheme.colorScheme.onSecondary) - } - DropdownMenuItem(onClick = { - theme = Theme.LIYA - expandedThemeMenu = false - }) { - Text("Liya's theme", color = MaterialTheme.colorScheme.onSecondary) - } - DropdownMenuItem(onClick = { - theme = Theme.KATYA - expandedThemeMenu = false - }) { - Text("Katya's theme", color = MaterialTheme.colorScheme.onSecondary) - } - } - } Surface( modifier = Modifier.weight(1f).background(MaterialTheme.colorScheme.background), ) { - GraphView(viewModel.graphViewModel) + Box(modifier = Modifier.fillMaxSize()) { + GraphView(viewModel.graphViewModel) // График занимает всё пространство + + var expandedThemeMenu by remember { mutableStateOf(false) } + + // Кнопка темы + Box( + modifier = Modifier + .align(Alignment.TopEnd) // Выровнено по правому верхнему углу + .padding(16.dp) + ) { + // Кнопка + Button( + onClick = { expandedThemeMenu = true }, + colors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text(text = "Theme", fontSize = 18.sp, color = MaterialTheme.colorScheme.onPrimary) + Icon(Icons.Default.ArrowDropDown, contentDescription = "Select theme") + } + // Выпадающее меню + DropdownMenu( + expanded = expandedThemeMenu, + onDismissRequest = { expandedThemeMenu = false }, + modifier = Modifier.background(MaterialTheme.colorScheme.secondary) + ) { + DropdownMenuItem(onClick = { + theme = Theme.NASTYA + expandedThemeMenu = false + }) { + Text("Nastya's theme", color = MaterialTheme.colorScheme.onSecondary) + } + DropdownMenuItem(onClick = { + theme = Theme.LIYA + expandedThemeMenu = false + }) { + Text("Liya's theme", color = MaterialTheme.colorScheme.onSecondary) + } + DropdownMenuItem(onClick = { + theme = Theme.KATYA + expandedThemeMenu = false + }) { + Text("Katya's theme", color = MaterialTheme.colorScheme.onSecondary) + } + } + } + } } } if (showDijkstraDialog) { From 6d14a92dd04c3b91228b4b9799cfade879bb3a3d Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Thu, 31 Oct 2024 14:14:46 +0300 Subject: [PATCH 127/131] fix: fixed the bug in the cycle search algorithm on model-level --- .../kotlin/model/algorithms/CycleSearch.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/model/algorithms/CycleSearch.kt b/src/main/kotlin/model/algorithms/CycleSearch.kt index 455b550..6ade7ad 100644 --- a/src/main/kotlin/model/algorithms/CycleSearch.kt +++ b/src/main/kotlin/model/algorithms/CycleSearch.kt @@ -11,14 +11,14 @@ import model.graph.Vertex * At the same time, the algorithm has some minimization of the desired cycle * by iterating through possible pairs of neighbors of the selected vertex * through which the desired cycle will pass. - * @param D input type * @property [graph] a undirected graph for whose vertex we will search for a cycle * @constructor Creates a graph, based on [graph],for which it will be possible to apply * a cycle search algorithm around the vertex selected in it. */ -class CycleSearch(private val graph: UndirectedGraph) { +class CycleSearch(private val graph: UndirectedGraph) { + private var cycleIsFound: Boolean = false /** * This auxiliary function for the function [findAnyCycle] aims to get from * some hashmap with extra elements the hashmap the elements of which @@ -64,12 +64,16 @@ class CycleSearch(private val graph: UndirectedGraph) { for (idAdjacency in graph.adjacency[currentVertexId]!!.keys) { + if (cycleIsFound){ + return + } if (visited[idAdjacency] == false) { visited[idAdjacency] = true cyclePath[currentVertexId] = idAdjacency dfs(cycleVertexId, idAdjacency, visited, cyclePath) } else if (idAdjacency == cycleVertexId && cyclePath[cycleVertexId] != currentVertexId) { cyclePath[currentVertexId] = idAdjacency + cycleIsFound = true return } } @@ -90,6 +94,7 @@ class CycleSearch(private val graph: UndirectedGraph) { } visited[vertexId] = true dfs(vertexId, vertexId, visited, devCyclePath) + cycleIsFound = false if (devCyclePath.filter { it.value == vertexId }.isEmpty()) { return null @@ -110,30 +115,26 @@ class CycleSearch(private val graph: UndirectedGraph) { * @param vertex the vertex around which the cycle must be found * @return cycle path or null if it doesn't exist */ - fun findCycle(vertex: Vertex): UndirectedGraph? { + fun findCycle(vertex: Vertex): UndirectedGraph? { var returnCyclePath = hashMapOf() var currentCyclePath: HashMap? var minCycleSize: Int = Int.MAX_VALUE - - if (vertex.id !in graph.vertices.keys) { - throw IllegalArgumentException("Vertex with id = ${vertex.id} doesn't exist in the graph") - } - val returnGraph = UndirectedGraph() + val returnGraph = UndirectedGraph() if (graph.adjacency[vertex.id]!!.size < 2) { return null } else { // we consider all possible cases of a cycle by choosing a pair of neighbors to minimize the found cycle val adjacencyOfVertex: MutableList = arrayListOf() graph.adjacency[vertex.id]!!.keys.forEach { adjacencyOfVertex.add(it) } val adjacencyWas: MutableList = arrayListOf() - val removedEdges: MutableList> = arrayListOf() + val removedEdges: MutableList = arrayListOf() for (firstAdjacency in adjacencyOfVertex) { adjacencyWas.add(firstAdjacency) for (secondAdjacency in adjacencyOfVertex.filter { it !in adjacencyWas && it != vertex.id }) { for (adjacency in graph.adjacency[vertex.id]!!.filter { it.key != firstAdjacency && it.key != secondAdjacency }) { removedEdges.add(Edge(vertex.id to adjacency.key, adjacency.value)) - graph.removeEdge(vertex.id to adjacency.key, adjacency.value) + graph.removeEdge(vertex.id to adjacency.key) } currentCyclePath = findAnyCycle(vertex.id) From 25b1a8853daadea5e132b64a0065df64b5f907ea Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Thu, 31 Oct 2024 14:34:59 +0300 Subject: [PATCH 128/131] fix: changed the coordinates of the circle center, which provides the default location of the vertices --- src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt index a2e1b7e..df6b80b 100644 --- a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt @@ -11,7 +11,7 @@ class CircularPlacementStrategy : RepresentationStrategy { println("CircularPlacementStrategy.place: there is nothing to place 👐🏻") return } - val center = Pair(width / 2, height / 2) + val center = Pair(width / 2, height * (3.0/5) ) val angle = 2 * Math.PI / vertices.size val sorted = vertices.sortedBy { it.label } From 5a14cf6ddab29c003bf5eaa007102a980670d6d9 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Thu, 31 Oct 2024 14:38:42 +0300 Subject: [PATCH 129/131] fix: deleted the unnecessary output in stdout --- src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt index df6b80b..d7726de 100644 --- a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt @@ -8,7 +8,6 @@ import kotlin.math.sin class CircularPlacementStrategy : RepresentationStrategy { override fun place(width: Double, height: Double, vertices: Collection) { if (vertices.isEmpty()) { - println("CircularPlacementStrategy.place: there is nothing to place 👐🏻") return } val center = Pair(width / 2, height * (3.0/5) ) From a0e46903b4449e8c6310780e5d4529167b9d1e5e Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Thu, 31 Oct 2024 14:50:30 +0300 Subject: [PATCH 130/131] rep: added examples of graphs that can be read from the json-files --- examples/dirLotsOfEdges.json | 1 + examples/undirAverageNumberOfEdges.json | 1 + examples/undirSimpleGraph.json | 1 + 3 files changed, 3 insertions(+) create mode 100644 examples/dirLotsOfEdges.json create mode 100644 examples/undirAverageNumberOfEdges.json create mode 100644 examples/undirSimpleGraph.json diff --git a/examples/dirLotsOfEdges.json b/examples/dirLotsOfEdges.json new file mode 100644 index 0000000..9402105 --- /dev/null +++ b/examples/dirLotsOfEdges.json @@ -0,0 +1 @@ +{"adjacency":{"1":{},"2":{},"3":{},"4":{},"5":{},"6":{},"7":{"8":0},"8":{"9":19},"9":{"7":2},"11":{},"12":{},"15":{},"19":{}},"vertices":{"1":{"id":1,"data":"1"},"2":{"id":2,"data":"2"},"3":{"id":3,"data":"3"},"4":{"id":4,"data":"4"},"5":{"id":5,"data":"5"},"6":{"id":6,"data":"6"},"7":{"id":7,"data":"7"},"8":{"id":8,"data":"8"},"9":{"id":9,"data":"9"},"10":{"id":10,"data":"10"},"11":{"id":11,"data":"11"},"12":{"id":12,"data":"12"},"13":{"id":13,"data":"13"},"14":{"id":14,"data":"14"},"15":{"id":15,"data":"15"},"16":{"id":16,"data":"16"},"17":{"id":17,"data":"17"},"18":{"id":18,"data":"18"},"19":{"id":19,"data":"19"},"20":{"id":20,"data":"20"}},"edges":[{"vertices":{"first":7,"second":8},"weight":0},{"vertices":{"first":8,"second":9},"weight":19},{"vertices":{"first":9,"second":7},"weight":2},{"vertices":{"first":1,"second":2},"weight":0},{"vertices":{"first":1,"second":3},"weight":0},{"vertices":{"first":1,"second":4},"weight":0},{"vertices":{"first":5,"second":6},"weight":0},{"vertices":{"first":18,"second":9},"weight":0},{"vertices":{"first":20,"second":14},"weight":0},{"vertices":{"first":12,"second":20},"weight":0},{"vertices":{"first":17,"second":13},"weight":0},{"vertices":{"first":1,"second":20},"weight":0},{"vertices":{"first":12,"second":6}},{"vertices":{"first":19,"second":12}},{"vertices":{"first":6,"second":18}},{"vertices":{"first":1,"second":14}},{"vertices":{"first":1,"second":15}},{"vertices":{"first":6,"second":20}},{"vertices":{"first":12,"second":1}},{"vertices":{"first":9,"second":19}},{"vertices":{"first":12,"second":9}},{"vertices":{"first":15,"second":6}},{"vertices":{"first":15,"second":5}},{"vertices":{"first":4,"second":11}},{"vertices":{"first":11,"second":20}},{"vertices":{"first":1,"second":9}},{"vertices":{"first":1,"second":18}},{"vertices":{"first":19,"second":1}},{"vertices":{"first":15,"second":19}},{"vertices":{"first":6,"second":19}}]} \ No newline at end of file diff --git a/examples/undirAverageNumberOfEdges.json b/examples/undirAverageNumberOfEdges.json new file mode 100644 index 0000000..958096b --- /dev/null +++ b/examples/undirAverageNumberOfEdges.json @@ -0,0 +1 @@ +{"adjacency":{"1":{"10":5},"2":{"3":52,"4":10,"6":17,"9":65,"10":8},"3":{"2":52,"6":32,"8":14},"4":{"2":10,"5":18},"5":{"4":18,"8":20},"6":{"2":17,"3":32,"10":71},"7":{},"8":{"3":14,"5":20,"9":3,"10":21},"9":{"2":65,"8":3},"10":{"1":5,"2":8,"6":71,"8":21},"11":{"12":4},"12":{"11":4}},"vertices":{"1":{"id":1,"data":"1"},"2":{"id":2,"data":"2"},"3":{"id":3,"data":"3"},"4":{"id":4,"data":"4"},"5":{"id":5,"data":"5"},"6":{"id":6,"data":"6"},"7":{"id":7,"data":"7"},"8":{"id":8,"data":"8"},"9":{"id":9,"data":"9"},"10":{"id":10,"data":"10"},"11":{"id":11,"data":"11"},"12":{"id":12,"data":"12"}},"edges":[{"vertices":{"first":8,"second":5},"weight":20},{"vertices":{"first":5,"second":8},"weight":20},{"vertices":{"first":5,"second":4},"weight":18},{"vertices":{"first":4,"second":5},"weight":18},{"vertices":{"first":8,"second":9},"weight":3},{"vertices":{"first":9,"second":8},"weight":3},{"vertices":{"first":8,"second":3},"weight":14},{"vertices":{"first":3,"second":8},"weight":14},{"vertices":{"first":3,"second":6},"weight":32},{"vertices":{"first":6,"second":3},"weight":32},{"vertices":{"first":2,"second":9},"weight":65},{"vertices":{"first":9,"second":2},"weight":65},{"vertices":{"first":2,"second":3},"weight":52},{"vertices":{"first":3,"second":2},"weight":52},{"vertices":{"first":2,"second":4},"weight":10},{"vertices":{"first":4,"second":2},"weight":10},{"vertices":{"first":2,"second":6},"weight":17},{"vertices":{"first":6,"second":2},"weight":17},{"vertices":{"first":11,"second":12},"weight":4},{"vertices":{"first":12,"second":11},"weight":4},{"vertices":{"first":10,"second":8},"weight":21},{"vertices":{"first":8,"second":10},"weight":21},{"vertices":{"first":10,"second":6},"weight":71},{"vertices":{"first":6,"second":10},"weight":71},{"vertices":{"first":10,"second":1},"weight":5},{"vertices":{"first":1,"second":10},"weight":5},{"vertices":{"first":10,"second":2},"weight":8},{"vertices":{"first":2,"second":10},"weight":8}]} \ No newline at end of file diff --git a/examples/undirSimpleGraph.json b/examples/undirSimpleGraph.json new file mode 100644 index 0000000..504ae06 --- /dev/null +++ b/examples/undirSimpleGraph.json @@ -0,0 +1 @@ +{"adjacency":{"1":{"2":3,"3":45,"5":21},"2":{"1":3,"4":20,"5":17},"3":{"1":45,"4":17,"5":8},"4":{"2":20,"3":17,"5":8},"5":{"1":21,"2":17,"3":8,"4":8}},"vertices":{"1":{"id":1,"data":"1"},"2":{"id":2,"data":"2"},"3":{"id":3,"data":"3"},"4":{"id":4,"data":"4"},"5":{"id":5,"data":"5"}},"edges":[{"vertices":{"first":1,"second":2},"weight":3},{"vertices":{"first":2,"second":1},"weight":3},{"vertices":{"first":2,"second":4},"weight":20},{"vertices":{"first":4,"second":2},"weight":20},{"vertices":{"first":1,"second":5},"weight":21},{"vertices":{"first":5,"second":1},"weight":21},{"vertices":{"first":4,"second":3},"weight":17},{"vertices":{"first":3,"second":4},"weight":17},{"vertices":{"first":5,"second":2},"weight":17},{"vertices":{"first":2,"second":5},"weight":17},{"vertices":{"first":3,"second":5},"weight":8},{"vertices":{"first":5,"second":3},"weight":8},{"vertices":{"first":3,"second":1},"weight":45},{"vertices":{"first":1,"second":3},"weight":45},{"vertices":{"first":4,"second":5},"weight":8},{"vertices":{"first":5,"second":4},"weight":8}]} \ No newline at end of file From 418f07132689eee50ef041710164ca50b933b429 Mon Sep 17 00:00:00 2001 From: Migunova Anastasia Date: Thu, 31 Oct 2024 16:08:43 +0300 Subject: [PATCH 131/131] feat: added botton for HarmonicCentrality algorithm execution and made an incomplete implementation of this algorithm --- src/main/kotlin/view/MainScreen.kt | 6 +++++ .../kotlin/viewmodel/MainScreenViewModel.kt | 22 ++++++++++++++++++- .../graph/CircularPlacementStrategy.kt | 2 +- .../kotlin/viewmodel/graph/VertexViewModel.kt | 3 ++- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/view/MainScreen.kt b/src/main/kotlin/view/MainScreen.kt index d1eca62..b495803 100644 --- a/src/main/kotlin/view/MainScreen.kt +++ b/src/main/kotlin/view/MainScreen.kt @@ -155,6 +155,12 @@ fun MainScreen(viewModel: MainScreenViewModel) { }) { Text(text = "CycleSearch", color = MaterialTheme.colorScheme.onSecondary) } + DropdownMenuItem(onClick = { + expandedAlgorithmsMenu = false + viewModel.runHarmonicCentralityAlgorithm() + }) { + Text(text = "HarmonicCentrality", color = MaterialTheme.colorScheme.onSecondary) + } } } Row(modifier = Modifier.padding(horizontal = 10.dp), horizontalArrangement = Arrangement.SpaceBetween) { diff --git a/src/main/kotlin/viewmodel/MainScreenViewModel.kt b/src/main/kotlin/viewmodel/MainScreenViewModel.kt index cfcd48e..d8bb219 100644 --- a/src/main/kotlin/viewmodel/MainScreenViewModel.kt +++ b/src/main/kotlin/viewmodel/MainScreenViewModel.kt @@ -2,6 +2,7 @@ package viewmodel import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp import databases.FileSystem import databases.Neo4jRepository import model.graph.Graph @@ -15,6 +16,7 @@ import model.graph.Vertex var defaultColorLine: Color = Color.Black var defaultColorVertex: Color = Color.Gray var defaultStrokeWidth: Float = 4f +var defaultRadius = 25.0.dp class MainScreenViewModel(private val graph: Graph, private val representationStrategy: RepresentationStrategy) { val showVerticesLabels = mutableStateOf(false) @@ -28,7 +30,10 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt fun resetGraphView() { representationStrategy.place(800.0, 600.0, graphViewModel.verticesView.values) - graphViewModel.verticesView.values.forEach { v -> v.color = defaultColorVertex } + graphViewModel.verticesView.values.forEach { v -> + v.color = defaultColorVertex + v.radius = defaultRadius + } graphViewModel.edgesView.values.forEach { e -> e.color = defaultColorLine e.strokeWidth = defaultStrokeWidth @@ -194,4 +199,19 @@ class MainScreenViewModel(private val graph: Graph, private val representationSt return "The vertex with id = $vertexId doesn't have a cycle around it. " } + fun runHarmonicCentralityAlgorithm(): HashMap { + + val graphForAlgorithm = HarmonicCentrality(graph) + val vertexIdAndIndex: HashMap = graphForAlgorithm.harmonicCentrality() + + resetGraphView() + for(vertex in vertexIdAndIndex) { + graphViewModel.verticesView[vertex.key]?.radius = ((17.0 + vertex.value*30).toInt()).dp + graphViewModel.verticesView[vertex.key]?.color = + Color( red = 255 - (vertex.value*190).toInt(), green = 0, blue = 154 - (vertex.value*110).toInt() ) + } + return vertexIdAndIndex + + } + } diff --git a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt index d7726de..032ff79 100644 --- a/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt +++ b/src/main/kotlin/viewmodel/graph/CircularPlacementStrategy.kt @@ -10,7 +10,7 @@ class CircularPlacementStrategy : RepresentationStrategy { if (vertices.isEmpty()) { return } - val center = Pair(width / 2, height * (3.0/5) ) + val center = Pair(width * (4.0/5), height * (3.0/5) ) val angle = 2 * Math.PI / vertices.size val sorted = vertices.sortedBy { it.label } diff --git a/src/main/kotlin/viewmodel/graph/VertexViewModel.kt b/src/main/kotlin/viewmodel/graph/VertexViewModel.kt index 5faa9ff..0a50a87 100644 --- a/src/main/kotlin/viewmodel/graph/VertexViewModel.kt +++ b/src/main/kotlin/viewmodel/graph/VertexViewModel.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import model.graph.Vertex +import viewmodel.defaultRadius class VertexViewModel( x: Dp = 0.dp, @@ -15,7 +16,7 @@ class VertexViewModel( private val v: Vertex, private val _labelVisible: State, private val _idVisible: State, - val radius: Dp = 25.dp + var radius: Dp = defaultRadius ) { private var _x = mutableStateOf(x) var x: Dp