# Tree (Data Structure) [![maven](https://img.shields.io/maven-central/v/com.github.adriankuta/tree-structure?style=plastic)](https://mvnrepository.com/artifact/com.github.adriankuta/tree-structure) [![License: MIT](https://img.shields.io/github/license/AdrianKuta/Tree-Data-Structure?style=plastic)](https://github.com/AdrianKuta/Tree-Data-Structure/blob/master/LICENSE) [![Publish](https://github.com/AdrianKuta/Tree-Data-Structure/actions/workflows/publishRelease.yml/badge.svg)](https://github.com/AdrianKuta/Tree-Data-Structure/actions/workflows/publishRelease.yml) Lightweight Kotlin Multiplatform tree data structure for Kotlin and Java. Includes a small DSL, multiple traversal iterators, and pretty-print support. - Kotlin Multiplatform (JVM, JS, Wasm, iOS, and Native host) - Pre-order, Post-order, and Level-order iteration - Lazy `Sequence` traversal that composes with the Kotlin stdlib (`map`/`filter`/`firstOrNull`…) - Navigation helpers: `root()`, `ancestors()`, `siblings()`, `leaves()`, `descendants()`, `isLeaf`, `degree` - Functional helpers: `findNode`, `filterNodes`, `anyNode`, `allNodes`, `foldNodes`, `mapValues`, `deepCopy`, `structurallyEquals` - Stack-safe: traversal and `height()`/`nodeCount()`/`clear()` handle arbitrarily deep trees without `StackOverflowError` - Simple DSL: tree { child(...) } - Utilities: nodeCount(), height(), depth(), path(), prettyString(), clear(), removeChild() ## Installation Gradle (Kotlin DSL): ```kotlin // commonMain for KMP projects, or any sourceSet/module where you need it dependencies { implementation("com.github.adriankuta:tree-structure:3.1.5") // see badge above for the latest version } ``` Gradle (Groovy): ```groovy dependencies { implementation "com.github.adriankuta:tree-structure:3.1.5" // see badge above for the latest } ``` Maven: ```xml com.github.adriankuta tree-structure 3.1.5 ``` ## Usage **Kotlin** ```kotlin val root = TreeNode("World") val northA = TreeNode("North America") val europe = TreeNode("Europe") root.addChild(northA) root.addChild(europe) val usa = TreeNode("USA") northA.addChild(usa) val poland = TreeNode("Poland") val france = TreeNode("France") europe.addChild(poland) europe.addChild(france) println(root.prettyString()) ``` **Pretty Kotlin (DSL)** ```kotlin val root = tree("World") { child("North America") { child("USA") } child("Europe") { child("Poland") child("Germany") } } ``` **Java** ```java TreeNode root = new TreeNode<>("World"); TreeNode northA = new TreeNode<>("North America"); TreeNode europe = new TreeNode<>("Europe"); root.addChild(northA); root.addChild(europe); TreeNode usa = new TreeNode<>("USA"); northA.addChild(usa); TreeNode poland = new TreeNode<>("Poland"); TreeNode france = new TreeNode<>("France"); europe.addChild(poland); europe.addChild(france); System.out.println(root.prettyString()); ``` Output: ``` World ├── North America │ └── USA └── Europe ├── Poland └── France ``` ### Traversal and utilities ```kotlin val root = TreeNode("root") // ... build your tree // Choose iteration order (default is PreOrder) root.treeIterator = TreeNodeIterators.PostOrder for (node in root) println(node.value) // Utilities root.nodeCount() // number of descendants root.height() // longest path to a leaf (in edges) root.depth() // distance from current node to the root val path = root.path(root.children.first()) // nodes from descendant up to root // Mutations val child = root.children.first() root.removeChild(child) root.clear() // remove entire subtree ``` ### Lazy traversal with Sequence Traversal is exposed as a lazy `Sequence`, so it composes with the Kotlin standard library and short-circuits (it never materializes the whole tree just to find one node): ```kotlin val tree = tree("World") { child("North America") { child("USA") } child("Europe") { child("Poland") child("Germany") } } // Pick an order explicitly — no need to mutate the node's state. tree.preOrderSequence().map { it.value }.toList() // [World, North America, USA, Europe, Poland, Germany] tree.levelOrderSequence().first { it.value == "USA" } // stops as soon as it's found tree.asSequence(TreeNodeIterators.PostOrder).count() // 6 ``` ### Navigation ```kotlin val usa = tree.findNode { it == "USA" }!! usa.isLeaf // true usa.depth() // 2 usa.root().value // "World" usa.ancestors().map { it.value } // [North America, World] tree.leaves().map { it.value } // [USA, Poland, Germany] val europe = tree.findNode { it == "Europe" }!! europe.children.first().siblings().map { it.value } // [Germany] ``` ### Functional operations ```kotlin tree.anyNode { it == "Poland" } // true tree.filterNodes { it.length > 5 } // nodes whose value is longer than 5 chars tree.countNodes { it.startsWith("U") } // 1 // Transform values into a brand-new tree (the original is untouched); stack-safe. val lengths: TreeNode = tree.mapValues { it.length } // Deep copy + structural comparison. val copy = tree.deepCopy() tree.structurallyEquals(copy) // true (same values, same shape, different nodes) ``` ## Optional modules The core `tree-structure` artifact has no third-party dependencies. Ecosystem integrations ship as separate, opt-in artifacts that depend on the core. ### Serialization — `tree-structure-serialization` `kotlinx.serialization` support. A `TreeNode` holds a parent back-reference (a cycle), so it cannot be `@Serializable` directly — convert to/from the acyclic `TreeNodeDto` instead. ```kotlin implementation("com.github.adriankuta:tree-structure-serialization:3.4.0") ``` ```kotlin val json = Json.encodeToString(tree.toDto()) val restored = Json.decodeFromString>(json).toTreeNode() ``` ### Coroutines — `tree-structure-coroutines` Traverse a tree as a cold `Flow` (handy in coroutine/`ViewModel` pipelines). ```kotlin implementation("com.github.adriankuta:tree-structure-coroutines:3.4.0") ``` ```kotlin tree.preOrderFlow().collect { println(it.value) } tree.asFlow(TreeNodeIterators.LevelOrder).map { it.value } ``` ## Publishing to Maven Central (central.sonatype.com) This project is configured to publish artifacts to Maven Central via the Sonatype Central Portal. There are two supported ways to publish: 1) Via GitHub Actions (recommended) - Create a GitHub Release (tag) in this repository. When a release is published, the workflow .github/workflows/publishRelease.yml runs automatically. - The workflow uses the Gradle task publishToMavenCentral to upload artifacts through the Central Portal. - Make sure these repository secrets are configured in GitHub: - MAVEN_CENTRAL_USERNAME — Your Sonatype Central username (not email). - MAVEN_CENTRAL_PASSWORD — Your Sonatype Central password or a token from central.sonatype.com. - SIGNING_KEY — ASCII‑armored GPG private key (exported, single line; for in‑memory signing). - SIGNING_PASSWORD — Passphrase for the key above. - The workflow uses JDK 21 and publishes the version defined in build.gradle.kts. 2) Locally via Gradle - Ensure you have a Sonatype Central account and that the groupId com.github.adriankuta is verified in central.sonatype.com (Namespace Rules → Verify). - Export the same credentials/signing values as environment variables or pass them as Gradle properties: - ORG_GRADLE_PROJECT_mavenCentralUsername - ORG_GRADLE_PROJECT_mavenCentralPassword - ORG_GRADLE_PROJECT_signingInMemoryKey - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword - Then run: - ./gradlew publishToMavenCentral - For snapshot publishing, set -Psnapshot=true (the version is derived from PUBLISH_VERSION with -SNAPSHOT). Notes - Publishing is powered by the com.vanniktech.maven.publish Gradle plugin and Sonatype Central Portal (no legacy Nexus staging URLs needed). - The plugin is configured to sign all publications. Coordinates and POM metadata are defined in build.gradle.kts. - If using the combined task is preferred, you can also run publishAndReleaseToMavenCentral when automatic release is enabled; this repository currently uploads with publishToMavenCentral from CI. ## License MIT License Copyright (c) 2020 Adrian Kuta 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. ---