diff --git a/.github/.keep b/.github/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/.github/badges/branches.svg b/.github/badges/branches.svg
new file mode 100644
index 0000000..9d05455
--- /dev/null
+++ b/.github/badges/branches.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg
new file mode 100644
index 0000000..c126868
--- /dev/null
+++ b/.github/badges/jacoco.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.github/workflows/detekt.yml b/.github/workflows/detekt.yml
new file mode 100644
index 0000000..daa371c
--- /dev/null
+++ b/.github/workflows/detekt.yml
@@ -0,0 +1,29 @@
+name: Run detekt
+on:
+ pull_request:
+ branches:
+ - main
+ workflow_dispatch:
+jobs:
+ build:
+ permissions:
+ security-events: write
+ runs-on: [ ubuntu-latest ]
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v4
+ - name: Setup JDK 21
+ uses: actions/setup-java@v4
+ with:
+ java-version: "21"
+ distribution: temurin
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v3
+ - name: Build with Gradle
+ run: ./gradlew detekt
+ - name: Upload SARIF to GitHub using the upload-sarif action
+ uses: github/codeql-action/upload-sarif@v3
+ if: success() || failure()
+ with:
+ sarif_file: build/reports/detekt/detekt.sarif
+ category: static-analysis
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..68ee09b
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,38 @@
+name: Run tests
+on:
+ push:
+permissions: write-all
+jobs:
+ build:
+ runs-on: [ubuntu-latest]
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v4
+ - name: Setup JDK 21
+ uses: actions/setup-java@v4
+ with:
+ java-version: '21'
+ distribution: temurin
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v3
+ - name: Build with Gradle
+ run: ./gradlew build -x detekt
+ - name: Generate JaCoCo Badge
+ uses: cicirello/jacoco-badge-generator@v2
+ with:
+ generate-branches-badge: true
+ badges-directory: .github/badges
+ jacoco-csv-file: build/reports/jacoco/test/jacocoTestReport.csv
+ - name: Log coverage percentage
+ run: |
+ echo "coverage = ${{ steps.jacoco.outputs.coverage }}"
+ echo "branch coverage = ${{ steps.jacoco.outputs.branches }}"
+ - name: Commit the badge (if it changed)
+ run: |
+ if [[ `git status --porcelain *.svg` ]]; then
+ git config --global user.name 'github-actions[bot]'
+ git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com'
+ git add *.svg
+ git commit -m "chore: update autogenerated JaCoCo coverage badge" *.svg
+ git push
+ fi
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4d2c4b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,218 @@
+# Created by https://www.toptal.com/developers/gitignore/api/linux,intellij+iml,windows,macos,gradle,kotlin
+# Edit at https://www.toptal.com/developers/gitignore?templates=linux,intellij+iml,windows,macos,gradle,kotlin
+
+### Intellij+iml ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea//workspace.xml
+.idea//tasks.xml
+.idea//usage.statistics.xml
+.idea//dictionaries
+.idea//shelf
+
+# AWS User-specific
+.idea//aws.xml
+
+# Generated files
+.idea//contentModel.xml
+
+# Sensitive or high-churn files
+.idea//dataSources/
+.idea//dataSources.ids
+.idea//dataSources.local.xml
+.idea//sqlDataSources.xml
+.idea//dynamic.xml
+.idea//uiDesigner.xml
+.idea//dbnavigator.xml
+
+# Gradle
+.idea//gradle.xml
+.idea//libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea//mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij+iml Patch ###
+# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
+
+*.iml
+modules.xml
+.idea/misc.xml
+*.ipr
+
+### Kotlin ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### macOS Patch ###
+# iCloud generated files
+*.icloud
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+### Gradle ###
+.gradle
+/build/
+!src/**/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Avoid ignore Gradle wrappper properties
+!gradle-wrapper.properties
+
+# Cache of project
+.gradletasknamecache
+
+# Eclipse Gradle plugin generated files
+# Eclipse Core
+.project
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+### Gradle Patch ###
+# Java heap dump
+*.hprof
+
+# End of https://www.toptal.com/developers/gitignore/api/linux,intellij+iml,windows,macos,gradle,kotlin
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f4d96c0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Islam Magomedov, Damir Yunusov, Sofya Grishkova
+
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bc716c4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+  [](https://opensource.org/licenses/MIT)
+
+
+## GraphApp
+
+Представляем наше приложение для работы с графами.
+В нём представлены следующие возможности:
+1. Визуализация графа, возможность масштабирования и навигации (в разработке)
+2. Работа с 4 типами графов, в том числе направленными/ненаправленными и взвешенными/невзвешенными
+3. Сохранение и чтение файлов в формате JSON
+4. ForceAtlas2 - силовая модель раскладки графа (в разработке)
+5. Поиск сообществ
+6. Поиск мостов
+7. Поиск минимального остова
+8. Поиск компонент сильной связности
+
+
+## Лицензия
+
+Приложение распространяется под MIT License. Смотрите `LICENSE.txt` для большей информации.
+
+
+## Источники
+
+* [На чём основан наш графический интерфейс](https://github.com/spbu-coding-2023/gui-workshop?tab=readme-ov-file)
+* [Статья про ForceAtlas2](https://journals.plos.org/plosone/article%3Fid=10.1371/journal.pone.0098679)
+* [Статья на хабре про алгоритм Прима(и не только)](https://habr.com/ru/articles/569444/)
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..bb5d12c
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,92 @@
+import org.jetbrains.compose.desktop.application.dsl.TargetFormat
+import io.gitlab.arturbosch.detekt.Detekt
+
+plugins {
+ kotlin("plugin.serialization") version "1.9.23"
+ kotlin("jvm") version "1.9.23"
+ id("io.gitlab.arturbosch.detekt").version("1.23.6")
+ id("org.jetbrains.compose") version "1.6.1"
+ jacoco
+}
+
+group = "org.example"
+version = "1.0-SNAPSHOT"
+
+repositories {
+ mavenCentral()
+ maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
+ google()
+}
+
+dependencies {
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
+ implementation(compose.desktop.currentOs)
+ testImplementation(kotlin("test"))
+}
+
+kotlin {
+ jvmToolchain(21)
+}
+
+detekt {
+ toolVersion = "1.23.6"
+
+ source.setFrom("$projectDir/src/main/kotlin", "$projectDir/src/test/kotlin")
+
+ config.setFrom("$projectDir/config/detekt/detekt.yml")
+ buildUponDefaultConfig = true
+ allRules = false
+
+ ignoreFailures = false
+
+ basePath = rootProject.projectDir.absolutePath
+}
+
+jacoco {
+ toolVersion = "0.8.12"
+ reportsDirectory = layout.buildDirectory.dir("reports/jacoco")
+}
+
+tasks.test {
+ useJUnitPlatform()
+ finalizedBy(tasks.jacocoTestReport)
+}
+
+tasks.withType().configureEach {
+ reports {
+ html.required.set(true)
+ sarif.required.set(true) // SARIF to support integrations with GitHub Code Scanning
+ }
+}
+
+tasks.jacocoTestReport {
+ dependsOn(tasks.test)
+
+ classDirectories.setFrom(
+ files(classDirectories.files.map {
+ fileTree(it) {
+ exclude("**/view/**", "**/viewmodel/**", "**/app/**")
+ }
+ })
+ )
+
+ reports {
+ xml.required = false
+ csv.required = true
+ html.required = true
+ }
+
+
+}
+
+compose.desktop {
+ application {
+ mainClass = "MainKt"
+
+ nativeDistributions {
+ targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
+ packageName = "graphs-4"
+ packageVersion = "1.0.0"
+ }
+ }
+}
diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml
new file mode 100644
index 0000000..e6fbcfd
--- /dev/null
+++ b/config/detekt/detekt.yml
@@ -0,0 +1,774 @@
+build:
+ maxIssues: 0
+ excludeCorrectable: false
+ weights:
+ # complexity: 2
+ # LongParameterList: 1
+ # style: 1
+ # comments: 1
+
+config:
+ validation: true
+ warningsAsErrors: false
+ checkExhaustiveness: false
+ # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
+ excludes: ''
+
+processors:
+ active: true
+ exclude:
+ - 'DetektProgressListener'
+ # - 'KtFileCountProcessor'
+ # - 'PackageCountProcessor'
+ # - 'ClassCountProcessor'
+ # - 'FunctionCountProcessor'
+ # - 'PropertyCountProcessor'
+ # - 'ProjectComplexityProcessor'
+ # - 'ProjectCognitiveComplexityProcessor'
+ # - 'ProjectLLOCProcessor'
+ # - 'ProjectCLOCProcessor'
+ # - 'ProjectLOCProcessor'
+ # - 'ProjectSLOCProcessor'
+ # - 'LicenseHeaderLoaderExtension'
+
+console-reports:
+ active: true
+ exclude:
+ - 'ProjectStatisticsReport'
+ - 'ComplexityReport'
+ - 'NotificationReport'
+ - 'FindingsReport'
+ - 'FileBasedFindingsReport'
+ # - 'LiteFindingsReport'
+
+output-reports:
+ active: true
+
+comments:
+ active: true
+ AbsentOrWrongFileLicense:
+ active: false
+ licenseTemplateFile: 'license.template'
+ licenseTemplateIsRegex: false
+ CommentOverPrivateFunction:
+ active: false
+ CommentOverPrivateProperty:
+ active: false
+ DeprecatedBlockTag:
+ active: false
+ EndOfSentenceFormat:
+ active: false
+ endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
+ KDocReferencesNonPublicProperty:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ OutdatedDocumentation:
+ active: false
+ matchTypeParameters: true
+ matchDeclarationsOrder: true
+ allowParamOnConstructorProperties: false
+ UndocumentedPublicClass:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ searchInNestedClass: true
+ searchInInnerClass: true
+ searchInInnerObject: true
+ searchInInnerInterface: true
+ searchInProtectedClass: false
+ UndocumentedPublicFunction:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ searchProtectedFunction: false
+ UndocumentedPublicProperty:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ searchProtectedProperty: false
+
+complexity:
+ active: true
+ CognitiveComplexMethod:
+ active: false
+ threshold: 15
+ ComplexCondition:
+ active: true
+ threshold: 4
+ ComplexInterface:
+ active: false
+ threshold: 10
+ includeStaticDeclarations: false
+ includePrivateDeclarations: false
+ ignoreOverloaded: false
+ CyclomaticComplexMethod:
+ active: true
+ threshold: 15
+ ignoreSingleWhenExpression: false
+ ignoreSimpleWhenEntries: false
+ ignoreNestingFunctions: false
+ nestingFunctions:
+ - 'also'
+ - 'apply'
+ - 'forEach'
+ - 'isNotNull'
+ - 'ifNull'
+ - 'let'
+ - 'run'
+ - 'use'
+ - 'with'
+ LabeledExpression:
+ active: false
+ ignoredLabels: []
+ LargeClass:
+ active: true
+ threshold: 600
+ LongMethod:
+ active: true
+ threshold: 60
+ LongParameterList:
+ active: true
+ functionThreshold: 6
+ constructorThreshold: 7
+ ignoreDefaultParameters: false
+ ignoreDataClasses: true
+ ignoreAnnotatedParameter: []
+ MethodOverloading:
+ active: false
+ threshold: 6
+ NamedArguments:
+ active: false
+ threshold: 3
+ ignoreArgumentsMatchingNames: false
+ NestedBlockDepth:
+ active: true
+ threshold: 4
+ NestedScopeFunctions:
+ active: false
+ threshold: 1
+ functions:
+ - 'kotlin.apply'
+ - 'kotlin.run'
+ - 'kotlin.with'
+ - 'kotlin.let'
+ - 'kotlin.also'
+ ReplaceSafeCallChainWithRun:
+ active: false
+ StringLiteralDuplication:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ threshold: 3
+ ignoreAnnotation: true
+ excludeStringsWithLessThan5Characters: true
+ ignoreStringsRegex: '$^'
+ TooManyFunctions:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ thresholdInFiles: 11
+ thresholdInClasses: 15
+ thresholdInInterfaces: 15
+ thresholdInObjects: 11
+ thresholdInEnums: 11
+ ignoreDeprecated: false
+ ignorePrivate: false
+ ignoreOverridden: false
+ ignoreAnnotatedFunctions: []
+
+coroutines:
+ active: true
+ GlobalCoroutineUsage:
+ active: false
+ InjectDispatcher:
+ active: true
+ dispatcherNames:
+ - 'IO'
+ - 'Default'
+ - 'Unconfined'
+ RedundantSuspendModifier:
+ active: true
+ SleepInsteadOfDelay:
+ active: true
+ SuspendFunSwallowedCancellation:
+ active: false
+ SuspendFunWithCoroutineScopeReceiver:
+ active: false
+ SuspendFunWithFlowReturnType:
+ active: true
+
+empty-blocks:
+ active: true
+ EmptyCatchBlock:
+ active: true
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ EmptyClassBlock:
+ active: true
+ EmptyDefaultConstructor:
+ active: true
+ EmptyDoWhileBlock:
+ active: true
+ EmptyElseBlock:
+ active: true
+ EmptyFinallyBlock:
+ active: true
+ EmptyForBlock:
+ active: true
+ EmptyFunctionBlock:
+ active: true
+ ignoreOverridden: false
+ EmptyIfBlock:
+ active: true
+ EmptyInitBlock:
+ active: true
+ EmptyKtFile:
+ active: true
+ EmptySecondaryConstructor:
+ active: true
+ EmptyTryBlock:
+ active: true
+ EmptyWhenBlock:
+ active: true
+ EmptyWhileBlock:
+ active: true
+
+exceptions:
+ active: true
+ ExceptionRaisedInUnexpectedLocation:
+ active: true
+ methodNames:
+ - 'equals'
+ - 'finalize'
+ - 'hashCode'
+ - 'toString'
+ InstanceOfCheckForException:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ NotImplementedDeclaration:
+ active: false
+ ObjectExtendsThrowable:
+ active: false
+ PrintStackTrace:
+ active: true
+ RethrowCaughtException:
+ active: true
+ ReturnFromFinally:
+ active: true
+ ignoreLabeled: false
+ SwallowedException:
+ active: true
+ ignoredExceptionTypes:
+ - 'InterruptedException'
+ - 'MalformedURLException'
+ - 'NumberFormatException'
+ - 'ParseException'
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ ThrowingExceptionFromFinally:
+ active: true
+ ThrowingExceptionInMain:
+ active: false
+ ThrowingExceptionsWithoutMessageOrCause:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ exceptions:
+ - 'ArrayIndexOutOfBoundsException'
+ - 'Exception'
+ - 'IllegalArgumentException'
+ - 'IllegalMonitorStateException'
+ - 'IllegalStateException'
+ - 'IndexOutOfBoundsException'
+ - 'NullPointerException'
+ - 'RuntimeException'
+ - 'Throwable'
+ ThrowingNewInstanceOfSameException:
+ active: true
+ TooGenericExceptionCaught:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ exceptionNames:
+ - 'ArrayIndexOutOfBoundsException'
+ - 'Error'
+ - 'Exception'
+ - 'IllegalMonitorStateException'
+ - 'IndexOutOfBoundsException'
+ - 'NullPointerException'
+ - 'RuntimeException'
+ - 'Throwable'
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ TooGenericExceptionThrown:
+ active: true
+ exceptionNames:
+ - 'Error'
+ - 'Exception'
+ - 'RuntimeException'
+ - 'Throwable'
+
+naming:
+ active: true
+ BooleanPropertyNaming:
+ active: false
+ allowedPattern: '^(is|has|are)'
+ ClassNaming:
+ active: true
+ classPattern: '[A-Z][a-zA-Z0-9]*'
+ ConstructorParameterNaming:
+ active: true
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ privateParameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ EnumNaming:
+ active: true
+ enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
+ ForbiddenClassName:
+ active: false
+ forbiddenName: []
+ FunctionMaxLength:
+ active: false
+ maximumFunctionNameLength: 30
+ FunctionMinLength:
+ active: false
+ minimumFunctionNameLength: 3
+ FunctionParameterNaming:
+ active: true
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ InvalidPackageDeclaration:
+ active: true
+ rootPackage: ''
+ requireRootInDeclaration: false
+ LambdaParameterNaming:
+ active: false
+ parameterPattern: '[a-z][A-Za-z0-9]*|_'
+ MatchingDeclarationName:
+ active: true
+ mustBeFirst: true
+ MemberNameEqualsClassName:
+ active: true
+ ignoreOverridden: true
+ NoNameShadowing:
+ active: true
+ NonBooleanPropertyPrefixedWithIs:
+ active: false
+ ObjectPropertyNaming:
+ active: true
+ constantPattern: '[A-Za-z][_A-Za-z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
+ PackageNaming:
+ active: false
+ packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
+ TopLevelPropertyNaming:
+ active: true
+ constantPattern: '[A-Z][_A-Z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
+ VariableMaxLength:
+ active: false
+ maximumVariableNameLength: 64
+ VariableMinLength:
+ active: false
+ minimumVariableNameLength: 1
+ VariableNaming:
+ active: true
+ variablePattern: '[a-z][A-Za-z0-9]*'
+ privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+
+performance:
+ active: true
+ ArrayPrimitive:
+ active: true
+ CouldBeSequence:
+ active: false
+ threshold: 3
+ ForEachOnRange:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ SpreadOperator:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ UnnecessaryPartOfBinaryExpression:
+ active: false
+ UnnecessaryTemporaryInstantiation:
+ active: true
+
+potential-bugs:
+ active: true
+ AvoidReferentialEquality:
+ active: true
+ forbiddenTypePatterns:
+ - 'kotlin.String'
+ CastNullableToNonNullableType:
+ active: false
+ CastToNullableType:
+ active: false
+ Deprecation:
+ active: false
+ DontDowncastCollectionTypes:
+ active: false
+ DoubleMutabilityForCollection:
+ active: true
+ mutableTypes:
+ - 'kotlin.collections.MutableList'
+ - 'kotlin.collections.MutableMap'
+ - 'kotlin.collections.MutableSet'
+ - 'java.util.ArrayList'
+ - 'java.util.LinkedHashSet'
+ - 'java.util.HashSet'
+ - 'java.util.LinkedHashMap'
+ - 'java.util.HashMap'
+ ElseCaseInsteadOfExhaustiveWhen:
+ active: false
+ ignoredSubjectTypes: []
+ EqualsAlwaysReturnsTrueOrFalse:
+ active: true
+ EqualsWithHashCodeExist:
+ active: true
+ ExitOutsideMain:
+ active: false
+ ExplicitGarbageCollectionCall:
+ active: true
+ HasPlatformType:
+ active: true
+ IgnoredReturnValue:
+ active: true
+ restrictToConfig: true
+ returnValueAnnotations:
+ - 'CheckResult'
+ - '*.CheckResult'
+ - 'CheckReturnValue'
+ - '*.CheckReturnValue'
+ ignoreReturnValueAnnotations:
+ - 'CanIgnoreReturnValue'
+ - '*.CanIgnoreReturnValue'
+ returnValueTypes:
+ - 'kotlin.sequences.Sequence'
+ - 'kotlinx.coroutines.flow.*Flow'
+ - 'java.util.stream.*Stream'
+ ignoreFunctionCall: []
+ ImplicitDefaultLocale:
+ active: true
+ ImplicitUnitReturnType:
+ active: false
+ allowExplicitReturnType: true
+ InvalidRange:
+ active: true
+ IteratorHasNextCallsNextMethod:
+ active: true
+ IteratorNotThrowingNoSuchElementException:
+ active: true
+ LateinitUsage:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ ignoreOnClassesPattern: ''
+ MapGetWithNotNullAssertionOperator:
+ active: true
+ MissingPackageDeclaration:
+ active: false
+ excludes: ['**/*.kts']
+ NullCheckOnMutableProperty:
+ active: false
+ NullableToStringCall:
+ active: false
+ PropertyUsedBeforeDeclaration:
+ active: false
+ UnconditionalJumpStatementInLoop:
+ active: false
+ UnnecessaryNotNullCheck:
+ active: false
+ UnnecessaryNotNullOperator:
+ active: true
+ UnnecessarySafeCall:
+ active: true
+ UnreachableCatchBlock:
+ active: true
+ UnreachableCode:
+ active: true
+ UnsafeCallOnNullableType:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ UnsafeCast:
+ active: true
+ UnusedUnaryOperator:
+ active: true
+ UselessPostfixExpression:
+ active: true
+ WrongEqualsTypeParameter:
+ active: true
+
+style:
+ active: true
+ AlsoCouldBeApply:
+ active: false
+ BracesOnIfStatements:
+ active: false
+ singleLine: 'never'
+ multiLine: 'always'
+ BracesOnWhenStatements:
+ active: false
+ singleLine: 'necessary'
+ multiLine: 'consistent'
+ CanBeNonNullable:
+ active: false
+ CascadingCallWrapping:
+ active: false
+ includeElvis: true
+ ClassOrdering:
+ active: false
+ CollapsibleIfStatements:
+ active: false
+ DataClassContainsFunctions:
+ active: false
+ conversionFunctionPrefix:
+ - 'to'
+ allowOperators: false
+ DataClassShouldBeImmutable:
+ active: false
+ DestructuringDeclarationWithTooManyEntries:
+ active: true
+ maxDestructuringEntries: 3
+ DoubleNegativeLambda:
+ active: false
+ negativeFunctions:
+ - reason: 'Use `takeIf` instead.'
+ value: 'takeUnless'
+ - reason: 'Use `all` instead.'
+ value: 'none'
+ negativeFunctionNameParts:
+ - 'not'
+ - 'non'
+ EqualsNullCall:
+ active: true
+ EqualsOnSignatureLine:
+ active: false
+ ExplicitCollectionElementAccessMethod:
+ active: false
+ ExplicitItLambdaParameter:
+ active: true
+ ExpressionBodySyntax:
+ active: false
+ includeLineWrapping: false
+ ForbiddenAnnotation:
+ active: false
+ annotations:
+ - reason: 'it is a java annotation. Use `Suppress` instead.'
+ value: 'java.lang.SuppressWarnings'
+ - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.'
+ value: 'java.lang.Deprecated'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.'
+ value: 'java.lang.annotation.Documented'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.'
+ value: 'java.lang.annotation.Target'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.'
+ value: 'java.lang.annotation.Retention'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.'
+ value: 'java.lang.annotation.Repeatable'
+ - reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265'
+ value: 'java.lang.annotation.Inherited'
+ ForbiddenComment:
+ active: true
+ comments:
+ - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.'
+ value: 'FIXME:'
+ - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.'
+ value: 'STOPSHIP:'
+ - reason: 'Forbidden TODO todo marker in comment, please do the changes.'
+ value: 'TODO:'
+ allowedPatterns: ''
+ ForbiddenImport:
+ active: false
+ imports: []
+ forbiddenPatterns: ''
+ ForbiddenMethodCall:
+ active: false
+ methods:
+ - reason: 'print does not allow you to configure the output stream. Use a logger instead.'
+ value: 'kotlin.io.print'
+ - reason: 'println does not allow you to configure the output stream. Use a logger instead.'
+ value: 'kotlin.io.println'
+ ForbiddenSuppress:
+ active: false
+ rules: []
+ ForbiddenVoid:
+ active: true
+ ignoreOverridden: false
+ ignoreUsageInGenerics: false
+ FunctionOnlyReturningConstant:
+ active: true
+ ignoreOverridableFunction: true
+ ignoreActualFunction: true
+ excludedFunctions: []
+ LoopWithTooManyJumpStatements:
+ active: true
+ maxJumpCount: 1
+ MagicNumber:
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts']
+ ignoreNumbers:
+ - '-1'
+ - '0'
+ - '1'
+ - '2'
+ ignoreHashCodeFunction: true
+ ignorePropertyDeclaration: true
+ ignoreLocalVariableDeclaration: true
+ ignoreConstantDeclaration: true
+ ignoreCompanionObjectPropertyDeclaration: true
+ ignoreAnnotation: false
+ ignoreNamedArgument: true
+ ignoreEnums: false
+ ignoreRanges: false
+ ignoreExtensionFunctions: true
+ MandatoryBracesLoops:
+ active: false
+ MaxChainedCallsOnSameLine:
+ active: false
+ maxChainedCalls: 5
+ MaxLineLength:
+ active: true
+ maxLineLength: 120
+ excludePackageStatements: true
+ excludeImportStatements: true
+ excludeCommentStatements: false
+ excludeRawStrings: true
+ MayBeConst:
+ active: true
+ ModifierOrder:
+ active: true
+ MultilineLambdaItParameter:
+ active: false
+ MultilineRawStringIndentation:
+ active: false
+ indentSize: 4
+ trimmingMethods:
+ - 'trimIndent'
+ - 'trimMargin'
+ NestedClassesVisibility:
+ active: true
+ NewLineAtEndOfFile:
+ active: true
+ NoTabs:
+ active: false
+ NullableBooleanCheck:
+ active: false
+ ObjectLiteralToLambda:
+ active: true
+ OptionalAbstractKeyword:
+ active: true
+ OptionalUnit:
+ active: false
+ PreferToOverPairSyntax:
+ active: false
+ ProtectedMemberInFinalClass:
+ active: true
+ RedundantExplicitType:
+ active: false
+ RedundantHigherOrderMapUsage:
+ active: true
+ RedundantVisibilityModifierRule:
+ active: false
+ ReturnCount:
+ active: true
+ max: 2
+ excludedFunctions:
+ - 'equals'
+ excludeLabeled: false
+ excludeReturnFromLambda: true
+ excludeGuardClauses: false
+ SafeCast:
+ active: true
+ SerialVersionUIDInSerializableClass:
+ active: true
+ SpacingBetweenPackageAndImports:
+ active: false
+ StringShouldBeRawString:
+ active: false
+ maxEscapedCharacterCount: 2
+ ignoredCharacters: []
+ ThrowsCount:
+ active: true
+ max: 2
+ excludeGuardClauses: false
+ TrailingWhitespace:
+ active: false
+ TrimMultilineRawString:
+ active: false
+ trimmingMethods:
+ - 'trimIndent'
+ - 'trimMargin'
+ UnderscoresInNumericLiterals:
+ active: false
+ acceptableLength: 4
+ allowNonStandardGrouping: false
+ UnnecessaryAbstractClass:
+ active: true
+ UnnecessaryAnnotationUseSiteTarget:
+ active: false
+ UnnecessaryApply:
+ active: true
+ UnnecessaryBackticks:
+ active: false
+ UnnecessaryBracesAroundTrailingLambda:
+ active: false
+ UnnecessaryFilter:
+ active: true
+ UnnecessaryInheritance:
+ active: true
+ UnnecessaryInnerClass:
+ active: false
+ UnnecessaryLet:
+ active: false
+ UnnecessaryParentheses:
+ active: false
+ allowForUnclearPrecedence: false
+ UntilInsteadOfRangeTo:
+ active: false
+ UnusedImports:
+ active: false
+ UnusedParameter:
+ active: true
+ allowedNames: 'ignored|expected'
+ UnusedPrivateClass:
+ active: true
+ UnusedPrivateMember:
+ active: true
+ allowedNames: ''
+ UnusedPrivateProperty:
+ active: true
+ allowedNames: '_|ignored|expected|serialVersionUID'
+ UseAnyOrNoneInsteadOfFind:
+ active: true
+ UseArrayLiteralsInAnnotations:
+ active: true
+ UseCheckNotNull:
+ active: true
+ UseCheckOrError:
+ active: true
+ UseDataClass:
+ active: false
+ allowVars: false
+ UseEmptyCounterpart:
+ active: false
+ UseIfEmptyOrIfBlank:
+ active: false
+ UseIfInsteadOfWhen:
+ active: false
+ ignoreWhenContainingVariableDeclaration: false
+ UseIsNullOrEmpty:
+ active: true
+ UseLet:
+ active: false
+ UseOrEmpty:
+ active: true
+ UseRequire:
+ active: true
+ UseRequireNotNull:
+ active: true
+ UseSumOfInsteadOfFlatMapSize:
+ active: false
+ UselessCallOnNotNull:
+ active: true
+ UtilityClassWithPublicConstructor:
+ active: true
+ VarCouldBeVal:
+ active: true
+ ignoreLateinitVar: false
+ WildcardImport:
+ active: true
+ excludeImports:
+ - 'java.util.*'
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..d64cd49
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1af9e09
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..1aa94a4
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# 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
+ if ! command -v java >/dev/null 2>&1
+ then
+ 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
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ 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
+
+
+# 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"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# 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..6689b85
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@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=.
+@rem This is normally unused
+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% equ 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% equ 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!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/images/graphApp.png b/images/graphApp.png
new file mode 100644
index 0000000..eb2b8c9
Binary files /dev/null and b/images/graphApp.png differ
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..0c1a7dd
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,10 @@
+pluginManagement {
+ repositories {
+ maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
+ google()
+ gradlePluginPortal()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "graphs-graphs-4"
\ No newline at end of file
diff --git a/src/main/kotlin/app/Main.kt b/src/main/kotlin/app/Main.kt
new file mode 100644
index 0000000..c5b518a
--- /dev/null
+++ b/src/main/kotlin/app/Main.kt
@@ -0,0 +1,45 @@
+package app
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.application
+import model.graphs.Graph
+import view.AppTheme
+import view.screens.StartingScreen
+import view.screens.mainScreen
+import viewmodel.graphs.CircularPlacementStrategy
+import viewmodel.screens.MainScreenViewModel
+import viewmodel.screens.StartingScreenViewModel
+import java.awt.Dimension
+
+fun main() = application {
+ Window(
+ onCloseRequest = ::exitApplication,
+ title = "GraphApp"
+ ) {
+ window.minimumSize = Dimension(800, 600)
+
+ app()
+ }
+}
+
+@Composable
+fun app() {
+ val darkTheme = remember { mutableStateOf(false) }
+ val currentGraph = remember { mutableStateOf?>(null) }
+ val mainScreenViewModel = remember(currentGraph.value) {
+ currentGraph.value?.let { MainScreenViewModel(it, CircularPlacementStrategy(), currentGraph, darkTheme) }
+ }
+
+ AppTheme(darkTheme.value) {
+ if (currentGraph.value == null) {
+ StartingScreen(StartingScreenViewModel(currentGraph))
+ } else {
+ mainScreenViewModel?.let {
+ mainScreen(viewModel = it)
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/model/functionality/BridgeFinder.kt b/src/main/kotlin/model/functionality/BridgeFinder.kt
new file mode 100644
index 0000000..c85c91f
--- /dev/null
+++ b/src/main/kotlin/model/functionality/BridgeFinder.kt
@@ -0,0 +1,61 @@
+package model.functionality
+
+import model.graphs.Edge
+import model.graphs.GraphUndirected
+import model.graphs.Vertex
+import kotlin.math.min
+
+class BridgeFinder> {
+ private var discoveryTime = hashMapOf, Int>()
+ private var bridges: Set = emptySet()
+ private var parent = hashMapOf, Vertex?>()
+ private var low = hashMapOf, Int>()
+ private var timer: Int = 0
+
+ fun findBridges(graph: GraphUndirected): Set {
+ for (element in graph.vertices()) {
+ discoveryTime[element] = -1
+ low[element] = -1
+ parent[element] = null
+ }
+
+ graph.vertices().forEach {
+ if (discoveryTime[it] == -1) {
+ timer = 0
+ dfsRecursive(graph, it)
+ }
+ }
+
+ return bridges
+ }
+
+ private fun dfsRecursive(graph: GraphUndirected, vertex: Vertex) {
+ discoveryTime[vertex] = timer
+ low[vertex] = timer
+ timer += 1
+
+ graph.getNeighbors(vertex).forEach {
+ if (discoveryTime[it.to] == -1) {
+ parent[it.to] = vertex
+ dfsRecursive(graph, it.to)
+
+ val lowVertex: Int = low[vertex] ?: -1
+ val lowIt: Int = low[it.to] ?: -1
+ val discVertex: Int = discoveryTime[vertex] ?: -1
+
+ low[vertex] = min(lowVertex, lowIt)
+
+ if (lowIt > discVertex) {
+ bridges = bridges.plus(it)
+ }
+ } else {
+ if (parent[vertex] != it.to) {
+ val lowVertex: Int = low[vertex] ?: -1
+ val discTimeIt: Int = discoveryTime[it.to] ?: -1
+
+ low[vertex] = min(lowVertex, discTimeIt)
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/model/functionality/CommunityDetector.kt b/src/main/kotlin/model/functionality/CommunityDetector.kt
new file mode 100644
index 0000000..ea11be7
--- /dev/null
+++ b/src/main/kotlin/model/functionality/CommunityDetector.kt
@@ -0,0 +1,337 @@
+package model.functionality
+
+import model.graphs.Edge
+import model.graphs.GraphUndirected
+import model.graphs.UndirectedGraph
+import model.graphs.UnweightedEdge
+import model.graphs.Vertex
+import java.lang.Math.random
+import kotlin.math.exp
+import kotlin.math.pow
+
+class CommunityDetector>(
+ var graph: GraphUndirected,
+ private var resolution: Double,
+ private var randomness: Double
+) {
+ internal fun flatten(
+ partition: HashSet>>
+ ): HashSet>> {
+ val output = HashSet>>()
+
+ for (community in partition) {
+ output.add(flatCommunity(community))
+ }
+
+ return output
+ }
+
+ internal fun maintainPartition(
+ partition: List>>,
+ currGraph: GraphUndirected
+ ): HashSet>> {
+ // newPartition = {{v | v ⊆ C, v ∈ currGraph.vertices() } | C ∈ partition}
+ val newPartition: MutableList>> = MutableList(partition.size) { hashSetOf() }
+
+ for (vertex in currGraph.vertices()) {
+ val index = partition.indexOf(partition.find { it.containsAll(vertex.key as HashSet<*>) })
+ newPartition[index].add(vertex)
+ }
+
+ return newPartition.toHashSet()
+ }
+
+
+ fun leiden(): HashSet>> {
+ var currentGraph = graph
+ var partition: HashSet>> = initPartition(graph)
+ var notDone = true
+
+ while (notDone) {
+ moveNodesFast(currentGraph, partition)
+
+ notDone = (partition.size) != (currentGraph.vertices().size)
+
+ if (notDone) {
+ val refinedPartition = refinePartition(currentGraph, partition)
+ currentGraph = aggregateGraph(currentGraph, refinedPartition)
+ partition = maintainPartition(partition.toList(), currentGraph)
+ }
+ }
+
+ return flatten(partition)
+ }
+
+ private fun moveNodesFast(
+ graph: GraphUndirected,
+ partition: HashSet>>
+ ) {
+ val vertexQueue = graph.vertices().toMutableList()
+ vertexQueue.shuffle()
+
+ while (vertexQueue.isNotEmpty()) {
+ val currentVertex = vertexQueue.first()
+ vertexQueue.remove(currentVertex)
+
+ val startingQuality = quality(graph, partition)
+ var max = 0.0
+ var bestCommunity = partition.find { it.contains(currentVertex) }
+ val originalCommunity = bestCommunity
+
+ require(bestCommunity != null) { "Community that contains currentVertex must exist." }
+ bestCommunity.remove(currentVertex)
+
+ partition.add(hashSetOf())
+
+ // Determine the best community for currentVertex
+
+ for (community in partition) {
+ community.add(currentVertex)
+
+ val currentQuality = quality(graph, partition)
+ community.remove(currentVertex)
+
+ if (currentQuality - startingQuality >= max) {
+ max = currentQuality - startingQuality
+ bestCommunity = community
+ }
+ }
+
+ bestCommunity?.add(currentVertex)
+
+ if (bestCommunity != originalCommunity) {
+ for (edge in graph.getNeighbors(currentVertex)) {
+ if (bestCommunity?.contains(edge.to) == false) {
+ vertexQueue.add(edge.to)
+ }
+ }
+ }
+ }
+
+ partition.removeIf { it.size == 0 }
+ }
+
+ private fun quality(
+ graph: GraphUndirected,
+ partition: HashSet>>
+ ): Double {
+ var sum = 0.0
+
+ for (community in partition) {
+ val cS = flatCommunity(community).size
+ sum += countEdges(graph, community, community) - ((resolution * cS * (cS - 1)) / 2)
+ }
+
+ return sum
+ }
+
+ internal fun countEdges(
+ currGraph: GraphUndirected,
+ set1: HashSet>, set2: Set>
+ ): Int {
+ var count = 0
+
+ for (u in set1) {
+ for (v in currGraph.getNeighbors(u)) {
+ if (v.to in set2) {
+ count += v.copies
+ }
+ }
+ }
+
+ if (set1 == set2) {
+ count /= 2
+ }
+
+ return count
+ }
+
+ internal fun aggregateGraph(
+ graph: GraphUndirected,
+ partition: HashSet>>
+ ): GraphUndirected {
+ val newGraph = UndirectedGraph>>()
+
+ for (community in partition) {
+ if (community.size != 0) {
+ newGraph.addVertex(community)
+ }
+ }
+
+ val communities = newGraph.vertices()
+
+ for (edge in graph.edges()) {
+ val v1 = edge.from
+ val v2 = edge.to
+
+ val c1 = communities.find { it.key.contains(v1) }
+ val c2 = communities.find { it.key.contains(v2) }
+
+ if (c1 != null && c2 != null) {
+ newGraph.addSingleEdge(UnweightedEdge(c1, c2))
+ }
+ }
+
+ // ANY UndirectedGraph is GraphUndirected
+ @Suppress("UNCHECKED_CAST")
+ return newGraph as GraphUndirected
+ }
+
+ private fun refinePartition(
+ graph: GraphUndirected,
+ partition: HashSet>>
+ ): HashSet>> {
+ var refinedPartition = initPartition(graph)
+
+ for (community in partition) {
+ refinedPartition = mergeNodesSubset(graph, refinedPartition, community)
+ }
+
+ refinedPartition.removeAll { it.size == 0 }
+
+ return refinedPartition
+ }
+
+ internal fun flatVertex(vertex: Vertex): HashSet> {
+ if (vertex.key is Collection<*>) {
+ @Suppress("UNCHECKED_CAST")
+ return unpack(hashSetOf(), vertex as Vertex>)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return hashSetOf(vertex) as HashSet>
+ }
+
+ private fun unpack(
+ vertices: HashSet>,
+ vertex: Vertex>
+ ): HashSet> {
+ for (element in vertex.key) {
+ element as Vertex<*>
+ if (element.key is Collection<*>) {
+ @Suppress("UNCHECKED_CAST")
+ unpack(vertices, element as Vertex>)
+ } else {
+ @Suppress("UNCHECKED_CAST")
+ vertices.add(element as Vertex)
+ }
+ }
+
+ return vertices
+ }
+
+ internal fun flatCommunity(
+ community: HashSet>
+ ): HashSet> {
+ val output: HashSet> = hashSetOf()
+
+ for (vertex in community) {
+ output.addAll(flatVertex(vertex))
+ }
+
+ return output
+ }
+
+ private fun mergeNodesSubset(
+ graph: GraphUndirected,
+ partition: HashSet>>,
+ subset: HashSet>
+ ): HashSet>> {
+ // Consider only nodes that are well-connected within subset
+ val r: MutableList> = mutableListOf()
+
+ for (vertex in subset) {
+ val vertexSize: Double = flatVertex(vertex).size.toDouble()
+
+ var edges = 0
+ graph.getNeighbors(vertex).forEach {
+ if (subset.contains(it.to)) {
+ edges += 1
+ }
+ }
+
+ if (edges >= (resolution * vertexSize * (flatCommunity(subset).size - vertexSize))) {
+ r.add(vertex)
+ }
+ }
+
+ for (vertex in r.shuffled()) {
+ // Consider only nodes that have not yet been merged
+ val originalCommunity = partition.find { it.contains(vertex) }
+
+ if (originalCommunity?.size == 1) {
+ // Consider only well-connected communities
+ val wellConnectedCommunities: HashSet>> = hashSetOf()
+
+ for (community in partition) {
+ if (subset.containsAll(community)) {
+ val communitySize = flatCommunity(community).size
+ val edges = countEdges(graph, community, subset.minus(community))
+
+ val communityRank = resolution * communitySize *
+ (flatCommunity(subset).size) - communitySize
+ if (edges >= communityRank) {
+ wellConnectedCommunities.add(community)
+ }
+ }
+ }
+
+ originalCommunity.remove(vertex)
+
+ val qualityProbability: HashMap>, Double> = hashMapOf()
+ val startingQuality = quality(graph, partition)
+ val temp: HashSet>> = HashSet()
+
+ for (community in wellConnectedCommunities) {
+ if (community.size != 0) {
+ community.add(vertex)
+
+ val currentQuality = quality(graph, partition)
+
+ if (currentQuality - startingQuality < 0) {
+ temp.add(community.minus(vertex).toHashSet())
+ } else {
+ qualityProbability[community.minus(vertex).toHashSet()] =
+ exp((currentQuality - startingQuality) * (randomness.pow(-1.0)))
+ }
+
+ community.remove(vertex)
+ }
+ }
+
+ wellConnectedCommunities.removeAll(temp)
+ wellConnectedCommunities.removeIf { it.size == 0 }
+
+ if (wellConnectedCommunities.size != 0) {
+ // Choose random community for more broad exploration of possible partitions
+ var totalWeight = 0.0
+
+ for (community in wellConnectedCommunities) {
+ val x = qualityProbability[community]
+
+ require(x != null) { "qualityProbability != null" }
+ totalWeight += x
+ }
+
+ val randomNumber = random() * totalWeight
+ val keyList = qualityProbability.values.filter { it < randomNumber }
+ val key = keyList.maxOrNull() ?: qualityProbability.values.min()
+ val newCommunity = qualityProbability.entries.find { it.value == key }?.key
+
+ require(newCommunity != null) { "Failed to assign newCommunity." }
+ partition.find { it == newCommunity }?.add(vertex)
+ } else {
+ originalCommunity.add(vertex)
+ }
+ }
+ }
+
+ return partition
+ }
+
+ internal fun initPartition(
+ graph: GraphUndirected
+ ): HashSet>> {
+ return graph.vertices().map { hashSetOf(it) }.toHashSet()
+ }
+}
diff --git a/src/main/kotlin/model/functionality/DistanceRank.kt b/src/main/kotlin/model/functionality/DistanceRank.kt
new file mode 100644
index 0000000..abed5f3
--- /dev/null
+++ b/src/main/kotlin/model/functionality/DistanceRank.kt
@@ -0,0 +1,78 @@
+package model.functionality
+
+import model.graphs.DirectedGraph
+import model.graphs.Edge
+import model.graphs.Vertex
+import java.util.*
+import kotlin.math.exp
+import kotlin.math.log10
+
+@Suppress("MagicNumber")
+class DistanceRank>(val graph: DirectedGraph) {
+ private val vertexQueue = PriorityQueue, Double>>(compareBy { it.second })
+ private val dist = mutableMapOf, Double>().withDefault { 1e6 }
+ private var size = 0.0
+ private var t: Double = 0.0
+ private val beta = 0.1
+ private val gamma = 0.65
+ private var distance = 0.0
+ private val visitedStartingVertices = mutableMapOf, Boolean>().withDefault { false }
+
+ private fun enqueue(vertex: Vertex, distance: Double) {
+ vertexQueue.add(Pair(vertex, distance))
+ }
+
+ private fun dequeue(): Pair, Double> {
+ return vertexQueue.poll()
+ }
+
+ private fun getOutDegree(vertex: Vertex): Int {
+ return graph.adjList[vertex]?.size ?: 0
+ }
+
+ @Suppress("NestedBlockDepth")
+ fun rank(): Map, Double> {
+ for (i in graph.adjList.keys) dist[i] = 1e10
+
+ //val allSCCs = TarjanSCC().findSCCs(graph)
+ val allSCCs = graph.findSCC()
+ val startingVertices = mutableSetOf>()
+
+
+ allSCCs.forEach { scc ->
+ val vertex = scc.random()
+ val outDegree = getOutDegree(vertex).toDouble()
+ val initialDist = 1 + log10(outDegree + 1)
+ enqueue(vertex, initialDist)
+ dist[vertex] = initialDist
+ startingVertices.add(vertex)
+ visitedStartingVertices[vertex] = false
+ }
+
+ while (!vertexQueue.isEmpty()) {
+ val (vertex, currentDistance) = dequeue()
+ val newDistance = log10(getOutDegree(vertex).toDouble() + 1) + gamma * currentDistance
+ visitedStartingVertices[vertex] = true
+
+
+ size++
+ t = (size / graph.adjList.keys.size)
+ val alpha = exp(-t * beta)
+
+ graph.adjList[vertex]?.forEach { child ->
+ distance = (1 - alpha) * dist[vertex]!! + alpha * newDistance
+ if (startingVertices.contains(child.to) && !visitedStartingVertices[child.to]!!) {
+ dist[child.to] = distance
+ enqueue(child.to, distance)
+ } else if (distance < dist[child.to]!!) {
+ if (dist.getValue(child.to) == 1e10) {
+ enqueue(child.to, distance)
+ }
+ dist[child.to] = distance
+ }
+ }
+
+ }
+ return dist
+ }
+}
diff --git a/src/main/kotlin/model/functionality/FindingCycles.kt b/src/main/kotlin/model/functionality/FindingCycles.kt
new file mode 100644
index 0000000..563ac5f
--- /dev/null
+++ b/src/main/kotlin/model/functionality/FindingCycles.kt
@@ -0,0 +1,147 @@
+package model.functionality
+
+import model.graphs.*
+import java.util.*
+import kotlin.math.min
+
+class JohnsonAlg>(val graph: GraphDirected) {
+ private val stack = Stack>()
+ private val blocked = mutableMapOf, Boolean>()
+ private val blockedMap = mutableMapOf, MutableSet>>()
+ private val allCycles = HashSet>>()
+
+
+ fun findCycles(startVertex: Vertex): HashSet>> {
+ val relevantSCC = TarjanSCC().findSCC(startVertex, graph)
+ startFindCycles(startVertex, relevantSCC)
+ return allCycles
+ }
+
+ private fun startFindCycles(startVertex: Vertex, subgraph: HashSet>) {
+ val subGraphNodes = subgraph.associateWith { vertex ->
+ graph.getNeighbors(vertex).filter { subgraph.contains(it.to) } ?: listOf()
+ }
+ subgraph.forEach { node ->
+ blocked[node] = false
+ blockedMap[node] = mutableSetOf()
+ }
+ dfsCycleFind(startVertex, startVertex, subGraphNodes)
+ }
+
+ private fun dfsCycleFind(start: Vertex, current: Vertex, subGraph: Map, List>>): Boolean {
+ stack.add(current)
+ blocked[current] = true
+ var foundCycle = false
+
+ for (neighbor in subGraph[current] ?: emptyList()) {
+ if (neighbor.to == start && stack.size > 1) {
+ allCycles.add(ArrayList(stack))
+ foundCycle = true
+ } else if (blocked[neighbor.to] == false) {
+ val gotCycle = dfsCycleFind(start, neighbor.to, subGraph)
+ foundCycle = foundCycle || gotCycle
+ }
+ }
+
+ if (foundCycle) unblock(current)
+ else {
+ for (neighbor in subGraph[current] ?: emptyList()) {
+ if (!blockedMap[neighbor.to]!!.contains(current)) {
+ blockedMap[neighbor.to]!!.add(current)
+ }
+ }
+ }
+ stack.pop()
+ return foundCycle
+ }
+
+
+ /*private fun processUnblocking(current: Vertex) {
+ val queue = ArrayDeque>()
+ stack.pop()
+ queue.add(current)
+
+ while (queue.isNotEmpty()) {
+ val vertex = queue.removeFirst()
+ blocked[vertex] = false
+
+ blockedMap[vertex]?.forEach { dependent ->
+ if (blockedMap[dependent]?.all { blocked[it] == false } == true) {
+ queue.add(dependent)
+ }
+ }
+ blockedMap[vertex]?.clear()
+ }*/
+
+ private fun unblock(vertex: Vertex) {
+ blocked[vertex] = false
+ if (blockedMap[vertex]?.size != 0) {
+ blockedMap[vertex]?.forEach {
+ if (blocked[it] == true) unblock(it)
+ }
+ }
+ blockedMap[vertex]?.clear()
+ }
+
+
+}
+
+class TarjanSCC> {
+ val stack = Stack>()
+ val num = mutableMapOf, Int>()
+ val lowest = mutableMapOf, Int>()
+ val visited = hashSetOf>()
+ val processed = hashSetOf>()
+ var curIndex = 1
+
+ fun findSCC(vertex: Vertex, graph: GraphDirected): HashSet> {
+ return dfsTarjan(vertex, graph)
+ }
+
+ fun containsInAnySCC(allSCCs: HashSet>>, v: Vertex): Boolean {
+ for (scc in allSCCs) {
+ if (scc.contains(v)) return false
+ }
+ return true
+ }
+
+ fun findSCCs(graph: GraphDirected): HashSet>> {
+ val allSCCs: HashSet>> = HashSet>>()
+ for (v in graph.vertices()) {
+ if (containsInAnySCC(allSCCs, v)) allSCCs.add(dfsTarjan(v, graph))
+ }
+ return allSCCs
+ }
+
+
+ fun dfsTarjan(vertex: Vertex, graph: GraphDirected): HashSet> {
+ num[vertex] = curIndex
+ lowest[vertex] = curIndex
+ curIndex++
+ stack.add(vertex)
+ visited.add(vertex)
+
+ graph.getNeighbors(vertex).forEach {
+ if (!stack.contains(it.to)) {
+ dfsTarjan(it.to, graph)
+ lowest[vertex] = min(lowest[vertex]!!, lowest[it.to]!!)
+ //As they say it's not recommended
+ } else if (stack.contains(it.to)) {
+ lowest[vertex] = min(lowest[vertex]!!, num[it.to]!!)
+ //The same situation hier
+ }
+ }
+ processed.add(vertex)
+
+ val scc: HashSet> = HashSet>()
+ if (lowest[vertex] == num[vertex]) {
+ var sccVertex: Vertex
+ do {
+ sccVertex = stack.pop()
+ scc.add(sccVertex)
+ } while (sccVertex != vertex)
+ }
+
+ return scc
+ }
+}
diff --git a/src/main/kotlin/model/functionality/GraphAlgorithms.kt b/src/main/kotlin/model/functionality/GraphAlgorithms.kt
new file mode 100644
index 0000000..498deb8
--- /dev/null
+++ b/src/main/kotlin/model/functionality/GraphAlgorithms.kt
@@ -0,0 +1,13 @@
+package model.functionality
+
+enum class GraphAlgorithms(val string: String) {
+ STRONG_CONNECTION_COMPONENTS("Find Strong Connection Components"),
+ LAYOUT("ForceAtlas2"),
+ COMMUNITIES("Find Communities"),
+ BRIDGES("Find Bridges"),
+ MINIMAL_SPANNING_TREE("Find Minimal Spanning Tree"),
+ SHORTEST_DISTANCE("Find Shortest Distance"),
+ DIJKSTRA("Find Shortest Positive Distance"),
+ DISTANCE_RANK("Importance of vertices"),
+ JOHN_ALGORITHM("Find Cycles for vertex")
+}
diff --git a/src/main/kotlin/model/functionality/MinSpanTreeFinder.kt b/src/main/kotlin/model/functionality/MinSpanTreeFinder.kt
new file mode 100644
index 0000000..70901a4
--- /dev/null
+++ b/src/main/kotlin/model/functionality/MinSpanTreeFinder.kt
@@ -0,0 +1,51 @@
+package model.functionality
+
+import model.graphs.Edge
+import model.graphs.GraphUndirected
+import model.graphs.Vertex
+
+class MinSpanTreeFinder>(private val graph: GraphUndirected) {
+ fun mstSearch(): Set {
+ val spanningTreeEdges = mutableSetOf()
+ val spanningTreeVertices = mutableSetOf>()
+
+ //step 1: add first vertex in spanning tree
+ val firstVertex = graph.firstOrNull() ?: return spanningTreeEdges
+ spanningTreeVertices.add(firstVertex)
+
+ while (spanningTreeVertices.size != graph.size) {
+ //step 2: search edge that connects different connection components
+ val minEdge = findMinEdge(graph.edges(), spanningTreeVertices)
+
+ //step 3: add this edge and adjacent vertex in spanning tree
+ if (minEdge != null) {
+ spanningTreeVertices.add(minEdge.from)
+ spanningTreeVertices.add(minEdge.to)
+ spanningTreeEdges.add(minEdge)
+ } else { //graph isn't connected
+ return emptySet()
+ }
+ }
+
+ return spanningTreeEdges
+ }
+
+ private fun findMinEdge(edges: Set, spanningTreeVertices: MutableSet>): E? {
+ var minEdge: E? = null
+
+ for (vertex in spanningTreeVertices) {
+ for (edge in edges) {
+ val u = edge.from
+ val v = edge.to
+
+ if (//we want edge that connects vertex presented in spanning tree and vertex that isn't in tree
+ ((vertex == u && !spanningTreeVertices.contains(v))
+ || (vertex == v && !spanningTreeVertices.contains(u)))
+ && (minEdge == null || minEdge > edge)
+ ) minEdge = edge
+ }
+ }
+
+ return minEdge
+ }
+}
diff --git a/src/main/kotlin/model/functionality/ShortestPathFinder.kt b/src/main/kotlin/model/functionality/ShortestPathFinder.kt
new file mode 100644
index 0000000..e508bab
--- /dev/null
+++ b/src/main/kotlin/model/functionality/ShortestPathFinder.kt
@@ -0,0 +1,81 @@
+package model.functionality
+
+import model.graphs.GraphWeighted
+import model.graphs.Vertex
+import model.graphs.WeightedEdge
+import java.util.*
+import kotlin.Double.Companion.NEGATIVE_INFINITY
+import kotlin.Double.Companion.POSITIVE_INFINITY
+
+class ShortestPathFinder(private val graph: GraphWeighted) {
+ internal fun bellmanFord(start: Vertex): Map, Double> {
+ val dist: MutableMap, Double> = mutableMapOf()
+ graph.vertices().forEach {
+ dist[it] = POSITIVE_INFINITY
+ }
+
+ dist[start] = 0.0
+
+ for (i in 1..graph.size + 1) {
+ for (vertex in graph.vertices()) {
+ for (edge in graph.getNeighbors(vertex)) {
+ edge as WeightedEdge
+
+ val distVertex = dist[vertex]
+ val distNeighbor = dist[edge.to] ?: POSITIVE_INFINITY
+
+ if ((distVertex != null) && (i <= graph.size)) {
+ if (distVertex + edge.weight < distNeighbor) {
+ dist[edge.to] = (distVertex + edge.weight)
+ }
+ } else if (i == graph.size + 1) {
+ if (distVertex != null) {
+ if (distVertex + edge.weight < distNeighbor) {
+ dist[edge.to] = NEGATIVE_INFINITY
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return dist
+ }
+
+ fun dijkstra(start: Vertex): Map, Double> {
+ val dist: MutableMap, Double> = mutableMapOf()
+ graph.vertices().forEach {
+ dist[it] = POSITIVE_INFINITY
+ }
+ val priorityQueue = PriorityQueue, Double>>(compareBy { it.second })
+
+ dist[start] = 0.0
+ priorityQueue.add(Pair(start, 0.0))
+
+ while (priorityQueue.isNotEmpty()) {
+ val (current, currentDist) = priorityQueue.poll()
+
+ var weight: Number
+ var neighbor: Vertex
+
+ for (child in graph.getNeighbors(current)) {
+ child as WeightedEdge
+ neighbor = child.to
+ weight = child.weight
+ require(weight >= 0)
+
+ val next = neighbor
+ val nextDist: Double = currentDist.plus(weight).toDouble()
+
+ dist[next]?.let {
+ if (nextDist < it) {
+ dist[next] = nextDist
+ priorityQueue.add(Pair(next, nextDist))
+ }
+ }
+ }
+ }
+
+ return dist
+ }
+}
diff --git a/src/main/kotlin/model/functionality/StrConCompFinder.kt b/src/main/kotlin/model/functionality/StrConCompFinder.kt
new file mode 100644
index 0000000..270ded1
--- /dev/null
+++ b/src/main/kotlin/model/functionality/StrConCompFinder.kt
@@ -0,0 +1,72 @@
+package model.functionality
+
+import model.graphs.Edge
+import model.graphs.GraphDirected
+import model.graphs.Vertex
+import java.util.Stack
+import kotlin.math.min
+
+class StrConCompFinder>(private val graph: GraphDirected) {
+ private val strConCompSet = mutableSetOf>>()
+
+ fun sccSearch(): Set>> {
+ var index = 1
+ val stack = Stack>()
+ val sccSearchHelper = HashMap, TarjanAlgoVertexStats>()
+ for (vertex in graph) {
+ sccSearchHelper[vertex] = TarjanAlgoVertexStats()
+ }
+
+ fun strongConnect(vertex: Vertex): Set> {
+ val vertexStats = sccSearchHelper[vertex]
+ ?: throw IllegalArgumentException("$vertex vertex does not presented in graph.")
+ vertexStats.sccIndex = index
+ vertexStats.lowLink = index
+ vertexStats.onStack = true
+ stack.push(vertex)
+ index++
+
+ val adjEdges = graph.getNeighbors(vertex)
+ for (edge in adjEdges) {
+ val neighbor = edge.to
+ val neighborStats = sccSearchHelper[neighbor]
+ ?: throw IllegalArgumentException("$edge vertex does not presented in graph.")
+
+ if (sccSearchHelper[neighbor]?.sccIndex == 0) {
+ strongConnect(neighbor)
+ vertexStats.lowLink = min(vertexStats.lowLink, neighborStats.lowLink)
+ } else if (neighborStats.onStack) {
+ vertexStats.lowLink = min(vertexStats.lowLink, neighborStats.sccIndex)
+ }
+ }
+
+ val scc = mutableSetOf>()
+ if (vertexStats.lowLink == vertexStats.sccIndex) {
+ do {
+ val visitedVertex = stack.pop()
+ val visitedVertexStats = sccSearchHelper[visitedVertex]
+ ?: throw IllegalArgumentException("$visitedVertex vertex does not presented in graph.")
+ visitedVertexStats.onStack = false
+ scc.add(visitedVertex)
+ } while (visitedVertex != vertex)
+ }
+
+ return scc
+ }
+
+ for (vertex in graph) {
+ val vertexStats = sccSearchHelper[vertex]
+ ?: throw IllegalArgumentException("$vertex vertex does not presented in graph.")
+
+ if (vertexStats.sccIndex == 0) {
+ val scc = strongConnect(vertex)
+
+ if (scc.isNotEmpty()) {
+ strConCompSet.add(scc)
+ }
+ }
+ }
+
+ return strConCompSet
+ }
+}
diff --git a/src/main/kotlin/model/functionality/TarjanAlgoVertexStats.kt b/src/main/kotlin/model/functionality/TarjanAlgoVertexStats.kt
new file mode 100644
index 0000000..01d566a
--- /dev/null
+++ b/src/main/kotlin/model/functionality/TarjanAlgoVertexStats.kt
@@ -0,0 +1,7 @@
+package model.functionality
+
+class TarjanAlgoVertexStats(
+ var sccIndex: Int = 0,
+ var lowLink: Int = 0,
+ var onStack: Boolean = false,
+)
diff --git a/src/main/kotlin/model/functionality/iograph/GraphType.kt b/src/main/kotlin/model/functionality/iograph/GraphType.kt
new file mode 100644
index 0000000..7986f1e
--- /dev/null
+++ b/src/main/kotlin/model/functionality/iograph/GraphType.kt
@@ -0,0 +1,8 @@
+package model.functionality.iograph
+
+enum class GraphType(val string: String) {
+ UNDIRECTED_GRAPH("Undirected Graph"),
+ DIRECTED_GRAPH("Directed Graph"),
+ UNDIRECTED_WEIGHTED_GRAPH("Undirected Weighted Graph"),
+ DIRECTED_WEIGHTED_GRAPH("Directed Weighted Graph"),
+}
diff --git a/src/main/kotlin/model/functionality/iograph/ReadWriteIntGraph.kt b/src/main/kotlin/model/functionality/iograph/ReadWriteIntGraph.kt
new file mode 100644
index 0000000..0f3bf2c
--- /dev/null
+++ b/src/main/kotlin/model/functionality/iograph/ReadWriteIntGraph.kt
@@ -0,0 +1,123 @@
+package model.functionality.iograph
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeFromStream
+import kotlinx.serialization.json.encodeToStream
+import model.graphs.AbstractGraph
+import model.graphs.DirectedGraph
+import model.graphs.DirectedWeightedGraph
+import model.graphs.Edge
+import model.graphs.Graph
+import model.graphs.UndirectedGraph
+import model.graphs.UndirectedWeightedGraph
+import java.awt.FileDialog
+import java.awt.Frame
+import java.io.File
+
+class ReadWriteIntGraph {
+ private val format = Json {
+ isLenient = true
+ prettyPrint = true
+ ignoreUnknownKeys = true
+ allowStructuredMapKeys = true
+ }
+
+ @OptIn(ExperimentalSerializationApi::class)
+ internal fun writeUGraph(file: File, graph: UndirectedGraph) {
+ val output = file.outputStream()
+ format.encodeToStream(graph, output)
+ output.close()
+ }
+
+ @OptIn(ExperimentalSerializationApi::class)
+ internal fun writeDGraph(file: File, graph: DirectedGraph) {
+ val output = file.outputStream()
+ format.encodeToStream(graph, output)
+ output.close()
+ }
+
+ @OptIn(ExperimentalSerializationApi::class)
+ internal fun writeUWGraph(file: File, graph: UndirectedWeightedGraph) {
+ val output = file.outputStream()
+ format.encodeToStream(graph, output)
+ output.close()
+ }
+
+ @OptIn(ExperimentalSerializationApi::class)
+ internal fun writeDWGraph(file: File, graph: DirectedWeightedGraph) {
+ val output = file.outputStream()
+ format.encodeToStream(graph, output)
+ output.close()
+ }
+
+ fun > saveGraph(graph: Graph) {
+ val dialog = FileDialog(Frame(), "Select Graph File", FileDialog.SAVE)
+ dialog.isVisible = true
+
+ dialog.file ?: return
+
+ val file = File(dialog.directory, "${dialog.file}.json")
+
+ when (graph) {
+ is DirectedGraph -> writeDGraph(file, graph as DirectedGraph)
+ is UndirectedGraph -> writeUGraph(file, graph as UndirectedGraph)
+ is UndirectedWeightedGraph -> writeUWGraph(file, graph as UndirectedWeightedGraph)
+ is DirectedWeightedGraph -> writeDWGraph(file, graph as DirectedWeightedGraph)
+ }
+ }
+
+ @OptIn(ExperimentalSerializationApi::class)
+ internal fun readUGraph(file: File): UndirectedGraph {
+ val input = file.inputStream()
+ val graph = format.decodeFromStream>(input)
+ input.close()
+
+ return graph
+ }
+
+ @OptIn(ExperimentalSerializationApi::class)
+ internal fun readDGraph(file: File): DirectedGraph {
+ val input = file.inputStream()
+ val graph = format.decodeFromStream>(input)
+ input.close()
+
+ return graph
+ }
+
+ @OptIn(ExperimentalSerializationApi::class)
+ internal fun readUWGraph(file: File): UndirectedWeightedGraph {
+ val input = file.inputStream()
+ val graph = format.decodeFromStream>(input)
+ input.close()
+
+ return graph
+ }
+
+ @OptIn(ExperimentalSerializationApi::class)
+ internal fun readDWGraph(file: File): DirectedWeightedGraph {
+ val input = file.inputStream()
+ val graph = format.decodeFromStream>(input)
+ input.close()
+
+ return graph
+ }
+
+ fun openGraph(type: GraphType): AbstractGraph>? {
+ val dialog = FileDialog(Frame(), "Select Graph File", FileDialog.LOAD)
+ dialog.isVisible = true
+
+ dialog.file ?: return null
+
+ val file = File(dialog.directory, dialog.file)
+
+ val graph = when (type) {
+ GraphType.UNDIRECTED_WEIGHTED_GRAPH -> readUWGraph(file)
+ GraphType.UNDIRECTED_GRAPH -> readUGraph(file)
+ GraphType.DIRECTED_WEIGHTED_GRAPH -> readDWGraph(file)
+ GraphType.DIRECTED_GRAPH -> readDGraph(file)
+ }
+
+ return graph
+ }
+}
diff --git a/src/main/kotlin/model/functionality/iograph/VertexSerializer.kt b/src/main/kotlin/model/functionality/iograph/VertexSerializer.kt
new file mode 100644
index 0000000..3d8184e
--- /dev/null
+++ b/src/main/kotlin/model/functionality/iograph/VertexSerializer.kt
@@ -0,0 +1,22 @@
+package model.functionality.iograph
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import model.graphs.Vertex
+
+class VertexSerializer : KSerializer> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Vertex", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: Vertex) {
+ encoder.encodeString(value.key.toString())
+ }
+
+ override fun deserialize(decoder: Decoder): Vertex {
+ val key = decoder.decodeString()
+ return Vertex(key.toInt() as T)
+ }
+}
diff --git a/src/main/kotlin/model/graphs/AbstractGraph.kt b/src/main/kotlin/model/graphs/AbstractGraph.kt
new file mode 100644
index 0000000..69be39b
--- /dev/null
+++ b/src/main/kotlin/model/graphs/AbstractGraph.kt
@@ -0,0 +1,93 @@
+package model.graphs
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+abstract class AbstractGraph> : Graph {
+ @SerialName("graph")
+ var adjList: HashMap, HashSet> = HashMap()
+ internal set
+
+ @SerialName("size")
+ protected var _size: Int = 0
+ override val size: Int
+ get() = _size
+
+ override fun areConnected(u: Vertex, v: Vertex): Boolean {
+ return (adjList[u]?.any { it.contains(v) } ?: false)
+ || (adjList[v]?.any { it.contains(u) } ?: false)
+ }
+
+ override fun addVertex(key: T): Vertex {
+ for (v in adjList.keys) {
+ if (v.key == key) {
+ return v
+ }
+ }
+
+ val vertex = Vertex(key)
+ adjList[vertex] = HashSet()
+
+ _size += 1
+
+ return vertex
+ }
+
+ override fun addVertex(vertex: Vertex): Vertex {
+ if (adjList.containsKey(vertex)) {
+ return vertex
+ }
+
+ adjList[vertex] = HashSet()
+
+ _size += 1
+
+ return vertex
+ }
+
+ override fun addVertices(vararg keys: T) {
+ for (key in keys) {
+ addVertex(key)
+ }
+ }
+
+ override fun addVertices(vararg vertices: Vertex) {
+ for (vertex in vertices) {
+ addVertex(vertex)
+ }
+ }
+
+ override fun addEdges(vararg edges: E) {
+ for (edge in edges) {
+ this.addEdge(edge)
+ }
+ }
+
+ override fun addEdge(edge: E) {
+ for (vertex in adjList.keys) {
+ if (edge.from == vertex) {
+ adjList[vertex]?.add(edge)
+ }
+ }
+ }
+
+ override fun vertices(): Set> {
+ return adjList.keys
+ }
+
+ override fun getNeighbors(vertex: Vertex): HashSet {
+ return adjList[vertex] ?: HashSet()
+ }
+
+ override fun edges(): Set {
+ val edges = HashSet()
+ for (vertex in adjList.keys) {
+ for (edge in adjList[vertex] ?: HashSet()) {
+ edges.add(edge)
+ }
+ }
+
+ return edges
+ }
+}
diff --git a/src/main/kotlin/model/graphs/DirectedGraph.kt b/src/main/kotlin/model/graphs/DirectedGraph.kt
new file mode 100644
index 0000000..20f9ef8
--- /dev/null
+++ b/src/main/kotlin/model/graphs/DirectedGraph.kt
@@ -0,0 +1,37 @@
+package model.graphs
+
+import kotlinx.serialization.Serializable
+import model.functionality.DistanceRank
+import model.functionality.JohnsonAlg
+import model.functionality.StrConCompFinder
+import model.functionality.TarjanSCC
+
+@Serializable
+class DirectedGraph :
+ AbstractGraph>(),
+ GraphDirected> {
+ fun addEdge(vertex1: Vertex, vertex2: Vertex) {
+ require(adjList.containsKey(vertex1))
+ require(adjList.containsKey(vertex2))
+
+ adjList.getOrPut(vertex1) { HashSet() }.add(UnweightedEdge(vertex1, vertex2))
+ }
+
+
+ override fun findCycles(startNode: Vertex): HashSet>> {
+ return JohnsonAlg(this).findCycles(startNode)
+ }
+
+
+ override fun addEdge(edge: UnweightedEdge) {
+ addEdge(edge.from, edge.to)
+ }
+
+ override fun findSCC(): Set>> {
+ return StrConCompFinder(this).sccSearch()
+ }
+
+ fun distanceRank(): Map, Double> {
+ return DistanceRank(this).rank()
+ }
+}
diff --git a/src/main/kotlin/model/graphs/DirectedWeightedGraph.kt b/src/main/kotlin/model/graphs/DirectedWeightedGraph.kt
new file mode 100644
index 0000000..35e4316
--- /dev/null
+++ b/src/main/kotlin/model/graphs/DirectedWeightedGraph.kt
@@ -0,0 +1,31 @@
+package model.graphs
+
+import kotlinx.serialization.Serializable
+import model.functionality.JohnsonAlg
+import model.functionality.StrConCompFinder
+
+@Serializable
+class DirectedWeightedGraph :
+ AbstractGraph>(),
+ GraphDirected>,
+ GraphWeighted {
+ fun addEdge(vertex1: Vertex, vertex2: Vertex, weight: Double) {
+ require(adjList.containsKey(vertex1))
+ require(adjList.containsKey(vertex2))
+
+ adjList.getOrPut(vertex1) { HashSet() }.add(WeightedEdge(vertex1, vertex2, weight))
+ }
+
+ override fun addEdge(edge: WeightedEdge) {
+ addEdge(edge.from, edge.to, edge.weight)
+ }
+
+ override fun findCycles(startNode: Vertex): HashSet>> {
+ return JohnsonAlg(this).findCycles(startNode)
+ }
+
+
+ override fun findSCC(): Set>> {
+ return StrConCompFinder(this).sccSearch()
+ }
+}
diff --git a/src/main/kotlin/model/graphs/Edge.kt b/src/main/kotlin/model/graphs/Edge.kt
new file mode 100644
index 0000000..d297ae7
--- /dev/null
+++ b/src/main/kotlin/model/graphs/Edge.kt
@@ -0,0 +1,13 @@
+package model.graphs
+
+interface Edge : Comparable> {
+ val from: Vertex
+ val to: Vertex
+ var copies: Int
+
+ fun reverse(): Edge
+
+ fun contains(v: Vertex): Boolean {
+ return from == v || to == v
+ }
+}
diff --git a/src/main/kotlin/model/graphs/Graph.kt b/src/main/kotlin/model/graphs/Graph.kt
new file mode 100644
index 0000000..19fb169
--- /dev/null
+++ b/src/main/kotlin/model/graphs/Graph.kt
@@ -0,0 +1,34 @@
+package model.graphs
+
+interface Graph> : Iterable> {
+ val size: Int
+
+ fun addVertex(key: T): Vertex
+
+ fun addVertex(vertex: Vertex): Vertex
+
+ fun addVertices(vararg keys: T)
+
+ fun addVertices(vararg vertices: Vertex)
+
+ fun addEdge(edge: E)
+
+ fun addEdges(vararg edges: E)
+
+ //Нужно было свойствами, а не методами делать :(
+ fun vertices(): Set>
+
+ fun edges(): Set
+
+ fun areConnected(u: Vertex, v: Vertex): Boolean
+
+ override fun iterator(): Iterator> {
+ return this.vertices().iterator()
+ }
+
+ fun getNeighbors(vertex: Vertex): HashSet
+
+// fun cyclesForVertex(vertex: Vertex): HashSet>> {
+// return JohnsonAlg(this).findCycles(vertex)
+// }
+}
diff --git a/src/main/kotlin/model/graphs/GraphDirected.kt b/src/main/kotlin/model/graphs/GraphDirected.kt
new file mode 100644
index 0000000..7c2a860
--- /dev/null
+++ b/src/main/kotlin/model/graphs/GraphDirected.kt
@@ -0,0 +1,12 @@
+package model.graphs
+
+import model.functionality.TarjanSCC
+
+
+interface GraphDirected> : Graph {
+ fun findSCC(): Set>>
+ fun findCycles(startNode: Vertex): HashSet>>
+ fun Tarjan(startNode: Vertex): HashSet> {
+ return TarjanSCC().findSCC(startNode, this)
+ }
+}
diff --git a/src/main/kotlin/model/graphs/GraphUndirected.kt b/src/main/kotlin/model/graphs/GraphUndirected.kt
new file mode 100644
index 0000000..a98c19c
--- /dev/null
+++ b/src/main/kotlin/model/graphs/GraphUndirected.kt
@@ -0,0 +1,17 @@
+package model.graphs
+
+import model.functionality.BridgeFinder
+
+interface GraphUndirected> : Graph {
+ fun findBridges(): Set {
+ return BridgeFinder().findBridges(this)
+ }
+
+ fun findMinSpanTree(): Set>?
+
+
+ // Resolution parameter x > 0 for community detection
+ // Higher resolution -> more communities
+ // Higher randomness -> more random node movements
+ fun runLeidenMethod(randomness: Double, resolution: Double): HashSet>>
+}
diff --git a/src/main/kotlin/model/graphs/GraphWeighted.kt b/src/main/kotlin/model/graphs/GraphWeighted.kt
new file mode 100644
index 0000000..2a46099
--- /dev/null
+++ b/src/main/kotlin/model/graphs/GraphWeighted.kt
@@ -0,0 +1,15 @@
+package model.graphs
+
+import model.functionality.ShortestPathFinder
+
+interface GraphWeighted : Graph> {
+ fun findDistancesBellman(start: Vertex): Map, Double> {
+ val output = ShortestPathFinder(this).bellmanFord(start)
+ return output
+ }
+
+
+ fun findDistancesDijkstra(start: Vertex): Map, Double> {
+ return ShortestPathFinder(this).dijkstra(start)
+ }
+}
diff --git a/src/main/kotlin/model/graphs/UndirectedGraph.kt b/src/main/kotlin/model/graphs/UndirectedGraph.kt
new file mode 100644
index 0000000..807c8cc
--- /dev/null
+++ b/src/main/kotlin/model/graphs/UndirectedGraph.kt
@@ -0,0 +1,48 @@
+package model.graphs
+
+import kotlinx.serialization.Serializable
+import model.functionality.CommunityDetector
+import model.functionality.MinSpanTreeFinder
+
+@Serializable
+open class UndirectedGraph :
+ AbstractGraph>(),
+ GraphUndirected> {
+ fun addEdge(vertex1: Vertex, vertex2: Vertex) {
+ require(adjList.containsKey(vertex1))
+ require(adjList.containsKey(vertex2))
+
+ val edge = adjList[vertex1]?.find { it.to == vertex2 }
+
+ if (edge != null) {
+ edge.copies += 1
+ adjList[vertex2]!!.find { it.to == vertex1 }!!.copies += 1
+ } else {
+ adjList.getOrPut(vertex1) { HashSet() }.add(UnweightedEdge(vertex1, vertex2))
+ adjList.getOrPut(vertex2) { HashSet() }.add(UnweightedEdge(vertex2, vertex1))
+ }
+ }
+
+ override fun addEdge(edge: UnweightedEdge) {
+ addEdge(edge.from, edge.to)
+ }
+
+ // добавляет одно конкретное ребро, пока надо только алг поиска
+ // сообществ
+ fun addSingleEdge(edge: UnweightedEdge) {
+ require(adjList.containsKey(edge.from))
+ require(adjList.containsKey(edge.to))
+
+ edge.copies += 1
+ adjList.getOrPut(edge.from) { HashSet() }.add(edge)
+ }
+
+ override fun findMinSpanTree(): Set>? {
+ return MinSpanTreeFinder(this).mstSearch()
+ }
+
+ override fun runLeidenMethod(randomness: Double, resolution: Double): HashSet>> {
+ return CommunityDetector(this, resolution, randomness).leiden()
+ }
+
+}
diff --git a/src/main/kotlin/model/graphs/UndirectedWeightedGraph.kt b/src/main/kotlin/model/graphs/UndirectedWeightedGraph.kt
new file mode 100644
index 0000000..432acc4
--- /dev/null
+++ b/src/main/kotlin/model/graphs/UndirectedWeightedGraph.kt
@@ -0,0 +1,41 @@
+package model.graphs
+
+import kotlinx.serialization.Serializable
+import model.functionality.CommunityDetector
+import model.functionality.MinSpanTreeFinder
+
+@Serializable
+open class UndirectedWeightedGraph :
+ AbstractGraph>(),
+ GraphUndirected>,
+ GraphWeighted {
+ fun addEdge(vertex1: Vertex, vertex2: Vertex, weight: Double) {
+ require(adjList.containsKey(vertex1))
+ require(adjList.containsKey(vertex2))
+
+ // для орграфов тоже надо будет реализовать дубликаты
+ // избавиться от !! (?)
+
+ val edge = adjList[vertex1]?.find { it.to == vertex2 }
+
+ if (edge != null) {
+ edge.copies += 1
+ adjList[vertex2]!!.find { it.to == vertex1 }!!.copies += 1
+ } else {
+ adjList.getOrPut(vertex1) { HashSet() }.add(WeightedEdge(vertex1, vertex2, weight))
+ adjList.getOrPut(vertex2) { HashSet() }.add(WeightedEdge(vertex2, vertex1, weight))
+ }
+ }
+
+ override fun addEdge(edge: WeightedEdge) {
+ addEdge(edge.from, edge.to, edge.weight)
+ }
+
+ override fun findMinSpanTree(): Set>? {
+ return MinSpanTreeFinder(this).mstSearch()
+ }
+
+ override fun runLeidenMethod(randomness: Double, resolution: Double): HashSet>> {
+ return CommunityDetector(this, resolution, randomness).leiden()
+ }
+}
diff --git a/src/main/kotlin/model/graphs/UnweightedEdge.kt b/src/main/kotlin/model/graphs/UnweightedEdge.kt
new file mode 100644
index 0000000..3ac7803
--- /dev/null
+++ b/src/main/kotlin/model/graphs/UnweightedEdge.kt
@@ -0,0 +1,33 @@
+package model.graphs
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class UnweightedEdge