mirror of
https://github.com/AdrianKuta/Tree-Data-Structure.git
synced 2026-06-19 19:00:14 +02:00
This commit is contained in:
@@ -11,6 +11,10 @@ All notable changes to this project are documented here. The format is based on
|
|||||||
`moveChild`, `addChildren`, and `sortChildren`.
|
`moveChild`, `addChildren`, and `sortChildren`.
|
||||||
- Tree query extensions: `lowestCommonAncestor`, `distance`, `pathBetween`, and `contains` for
|
- Tree query extensions: `lowestCommonAncestor`, `distance`, `pathBetween`, and `contains` for
|
||||||
finding common ancestors, edge distances, the path between two nodes, and value membership.
|
finding common ancestors, edge distances, the path between two nodes, and value membership.
|
||||||
|
- Customizable `prettyString(connectors, render)` extension: choose connector glyphs via
|
||||||
|
`TreeConnectors` (`Default` box-drawing or `Ascii`) and supply a per-node renderer that receives
|
||||||
|
the value, its depth and whether it is its parent's last child. The all-defaults call is
|
||||||
|
byte-identical to the existing no-arg `prettyString()`.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Rewrote the README for clarity: one consistent example tree, task-oriented sections
|
- Rewrote the README for clarity: one consistent example tree, task-oriented sections
|
||||||
|
|||||||
@@ -6,6 +6,29 @@ public final class com/github/adriankuta/datastructure/tree/ChildDeclarationInte
|
|||||||
public static synthetic fun child$default (Lcom/github/adriankuta/datastructure/tree/ChildDeclarationInterface;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/github/adriankuta/datastructure/tree/TreeNode;
|
public static synthetic fun child$default (Lcom/github/adriankuta/datastructure/tree/ChildDeclarationInterface;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/github/adriankuta/datastructure/tree/TreeNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class com/github/adriankuta/datastructure/tree/TreeConnectors {
|
||||||
|
public static final field Companion Lcom/github/adriankuta/datastructure/tree/TreeConnectors$Companion;
|
||||||
|
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
public final fun component1 ()Ljava/lang/String;
|
||||||
|
public final fun component2 ()Ljava/lang/String;
|
||||||
|
public final fun component3 ()Ljava/lang/String;
|
||||||
|
public final fun component4 ()Ljava/lang/String;
|
||||||
|
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/github/adriankuta/datastructure/tree/TreeConnectors;
|
||||||
|
public static synthetic fun copy$default (Lcom/github/adriankuta/datastructure/tree/TreeConnectors;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/github/adriankuta/datastructure/tree/TreeConnectors;
|
||||||
|
public fun equals (Ljava/lang/Object;)Z
|
||||||
|
public final fun getBranch ()Ljava/lang/String;
|
||||||
|
public final fun getEmpty ()Ljava/lang/String;
|
||||||
|
public final fun getLastBranch ()Ljava/lang/String;
|
||||||
|
public final fun getVertical ()Ljava/lang/String;
|
||||||
|
public fun hashCode ()I
|
||||||
|
public fun toString ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class com/github/adriankuta/datastructure/tree/TreeConnectors$Companion {
|
||||||
|
public final fun getAscii ()Lcom/github/adriankuta/datastructure/tree/TreeConnectors;
|
||||||
|
public final fun getDefault ()Lcom/github/adriankuta/datastructure/tree/TreeConnectors;
|
||||||
|
}
|
||||||
|
|
||||||
public class com/github/adriankuta/datastructure/tree/TreeNode : com/github/adriankuta/datastructure/tree/ChildDeclarationInterface, java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker {
|
public class com/github/adriankuta/datastructure/tree/TreeNode : com/github/adriankuta/datastructure/tree/ChildDeclarationInterface, java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker {
|
||||||
public fun <init> (Ljava/lang/Object;Lcom/github/adriankuta/datastructure/tree/iterators/TreeNodeIterators;)V
|
public fun <init> (Ljava/lang/Object;Lcom/github/adriankuta/datastructure/tree/iterators/TreeNodeIterators;)V
|
||||||
public synthetic fun <init> (Ljava/lang/Object;Lcom/github/adriankuta/datastructure/tree/iterators/TreeNodeIterators;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
public synthetic fun <init> (Ljava/lang/Object;Lcom/github/adriankuta/datastructure/tree/iterators/TreeNodeIterators;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
@@ -57,6 +80,11 @@ public final class com/github/adriankuta/datastructure/tree/TreeNodeNavigationEx
|
|||||||
public static final fun siblings (Lcom/github/adriankuta/datastructure/tree/TreeNode;)Ljava/util/List;
|
public static final fun siblings (Lcom/github/adriankuta/datastructure/tree/TreeNode;)Ljava/util/List;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class com/github/adriankuta/datastructure/tree/TreeNodePrettyPrintExtKt {
|
||||||
|
public static final fun prettyString (Lcom/github/adriankuta/datastructure/tree/TreeNode;Lcom/github/adriankuta/datastructure/tree/TreeConnectors;Lkotlin/jvm/functions/Function3;)Ljava/lang/String;
|
||||||
|
public static synthetic fun prettyString$default (Lcom/github/adriankuta/datastructure/tree/TreeNode;Lcom/github/adriankuta/datastructure/tree/TreeConnectors;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
public final class com/github/adriankuta/datastructure/tree/TreeNodeQueryExtKt {
|
public final class com/github/adriankuta/datastructure/tree/TreeNodeQueryExtKt {
|
||||||
public static final fun contains (Lcom/github/adriankuta/datastructure/tree/TreeNode;Ljava/lang/Object;)Z
|
public static final fun contains (Lcom/github/adriankuta/datastructure/tree/TreeNode;Ljava/lang/Object;)Z
|
||||||
public static final fun distance (Lcom/github/adriankuta/datastructure/tree/TreeNode;Lcom/github/adriankuta/datastructure/tree/TreeNode;)Ljava/lang/Integer;
|
public static final fun distance (Lcom/github/adriankuta/datastructure/tree/TreeNode;Lcom/github/adriankuta/datastructure/tree/TreeNode;)Ljava/lang/Integer;
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package com.github.adriankuta.datastructure.tree
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The four glyph strings used to draw the tree branches in [prettyString].
|
||||||
|
*
|
||||||
|
* Each value is the literal text emitted at the matching position:
|
||||||
|
* - [branch] precedes a child that is **not** its parent's last child.
|
||||||
|
* - [lastBranch] precedes a child that **is** its parent's last child.
|
||||||
|
* - [vertical] is accumulated into the prefix of the descendants of a non-last child (it keeps the
|
||||||
|
* vertical guide line going).
|
||||||
|
* - [empty] is accumulated into the prefix of the descendants of a last child (no guide line is
|
||||||
|
* needed past the last branch).
|
||||||
|
*
|
||||||
|
* Use [Default] for the box-drawing style or [Ascii] for a plain-ASCII style, or supply your own.
|
||||||
|
*
|
||||||
|
* @property branch drawn before a non-last child.
|
||||||
|
* @property vertical continuation prefix for descendants of a non-last child.
|
||||||
|
* @property lastBranch drawn before the last child.
|
||||||
|
* @property empty continuation prefix for descendants of a last child.
|
||||||
|
*/
|
||||||
|
public data class TreeConnectors(
|
||||||
|
public val branch: String,
|
||||||
|
public val vertical: String,
|
||||||
|
public val lastBranch: String,
|
||||||
|
public val empty: String,
|
||||||
|
) {
|
||||||
|
public companion object {
|
||||||
|
/** Box-drawing connectors, matching the output of the no-arg [TreeNode.prettyString]. */
|
||||||
|
public val Default: TreeConnectors = TreeConnectors(
|
||||||
|
branch = "├── ",
|
||||||
|
vertical = "│ ",
|
||||||
|
lastBranch = "└── ",
|
||||||
|
empty = " ",
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Plain-ASCII connectors for terminals or fonts that lack box-drawing glyphs. */
|
||||||
|
public val Ascii: TreeConnectors = TreeConnectors(
|
||||||
|
branch = "|-- ",
|
||||||
|
vertical = "| ",
|
||||||
|
lastBranch = "`-- ",
|
||||||
|
empty = " ",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders this subtree as a multi-line string, one node per line, with branch connectors.
|
||||||
|
*
|
||||||
|
* Calling this with all defaults produces output byte-identical to the no-arg member
|
||||||
|
* [TreeNode.prettyString]. Customise the drawing with [connectors] (e.g. [TreeConnectors.Ascii]) and
|
||||||
|
* the per-node text with [render].
|
||||||
|
*
|
||||||
|
* @param connectors the glyph set used to draw the branches. Defaults to [TreeConnectors.Default].
|
||||||
|
* @param render produces the text for each node from its `value`, its `depth` (distance from this
|
||||||
|
* receiver, which is `0`) and `isLast` (whether the node is its parent's last child; the root is
|
||||||
|
* considered `true`). Defaults to the value's string form (`"$value"`), which renders a `null`
|
||||||
|
* value as `"null"` to match the no-arg member.
|
||||||
|
* @return the rendered tree, each line terminated by `\n`.
|
||||||
|
*/
|
||||||
|
public fun <T> TreeNode<T>.prettyString(
|
||||||
|
connectors: TreeConnectors = TreeConnectors.Default,
|
||||||
|
render: (value: T, depth: Int, isLast: Boolean) -> String = { value, _, _ -> "$value" },
|
||||||
|
): String {
|
||||||
|
val stringBuilder = StringBuilder()
|
||||||
|
appendPretty(stringBuilder, "", "", 0, true, connectors, render)
|
||||||
|
return stringBuilder.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> TreeNode<T>.appendPretty(
|
||||||
|
stringBuilder: StringBuilder,
|
||||||
|
prefix: String,
|
||||||
|
childrenPrefix: String,
|
||||||
|
depth: Int,
|
||||||
|
isLast: Boolean,
|
||||||
|
connectors: TreeConnectors,
|
||||||
|
render: (value: T, depth: Int, isLast: Boolean) -> String,
|
||||||
|
) {
|
||||||
|
stringBuilder.append(prefix)
|
||||||
|
stringBuilder.append(render(value, depth, isLast))
|
||||||
|
stringBuilder.append('\n')
|
||||||
|
val childIterator = children.iterator()
|
||||||
|
while (childIterator.hasNext()) {
|
||||||
|
val node = childIterator.next()
|
||||||
|
if (childIterator.hasNext()) {
|
||||||
|
node.appendPretty(
|
||||||
|
stringBuilder,
|
||||||
|
childrenPrefix + connectors.branch,
|
||||||
|
childrenPrefix + connectors.vertical,
|
||||||
|
depth + 1,
|
||||||
|
false,
|
||||||
|
connectors,
|
||||||
|
render,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
node.appendPretty(
|
||||||
|
stringBuilder,
|
||||||
|
childrenPrefix + connectors.lastBranch,
|
||||||
|
childrenPrefix + connectors.empty,
|
||||||
|
depth + 1,
|
||||||
|
true,
|
||||||
|
connectors,
|
||||||
|
render,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.github.adriankuta.datastructure.tree
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class TreeNodePrettyPrintTest {
|
||||||
|
|
||||||
|
private fun sampleTree(): TreeNode<String> {
|
||||||
|
val root = TreeNode("Root")
|
||||||
|
val beverages = TreeNode("Beverages")
|
||||||
|
val curd = TreeNode("Curd")
|
||||||
|
root.addChild(beverages)
|
||||||
|
root.addChild(curd)
|
||||||
|
|
||||||
|
val tea = TreeNode("tea")
|
||||||
|
val coffee = TreeNode("coffee")
|
||||||
|
beverages.addChild(tea)
|
||||||
|
beverages.addChild(coffee)
|
||||||
|
|
||||||
|
tea.addChild(TreeNode("ginger tea"))
|
||||||
|
tea.addChild(TreeNode("normal tea"))
|
||||||
|
|
||||||
|
curd.addChild(TreeNode("yogurt"))
|
||||||
|
curd.addChild(TreeNode("lassi"))
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun defaultConnectorsMatchMemberPrettyString() {
|
||||||
|
val root = sampleTree()
|
||||||
|
assertEquals(root.prettyString(), root.prettyString(connectors = TreeConnectors.Default))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun defaultRenderMatchesMemberForNullValues() {
|
||||||
|
// The member prettyString() appends the value via StringBuilder, rendering null as "null".
|
||||||
|
// The all-defaults extension must stay byte-identical, including for null-valued nodes.
|
||||||
|
val root = TreeNode<String?>(null)
|
||||||
|
root.addChild(TreeNode("child"))
|
||||||
|
root.addChild(TreeNode<String?>(null))
|
||||||
|
assertEquals(root.prettyString(), root.prettyString(connectors = TreeConnectors.Default))
|
||||||
|
assertEquals(
|
||||||
|
"null\n" +
|
||||||
|
"├── child\n" +
|
||||||
|
"└── null\n",
|
||||||
|
root.prettyString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun asciiConnectorsRenderPlainAscii() {
|
||||||
|
val root = sampleTree()
|
||||||
|
assertEquals(
|
||||||
|
"Root\n" +
|
||||||
|
"|-- Beverages\n" +
|
||||||
|
"| |-- tea\n" +
|
||||||
|
"| | |-- ginger tea\n" +
|
||||||
|
"| | `-- normal tea\n" +
|
||||||
|
"| `-- coffee\n" +
|
||||||
|
"`-- Curd\n" +
|
||||||
|
" |-- yogurt\n" +
|
||||||
|
" `-- lassi\n",
|
||||||
|
root.prettyString(connectors = TreeConnectors.Ascii),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun customRenderIsApplied() {
|
||||||
|
val root = sampleTree()
|
||||||
|
assertEquals(
|
||||||
|
"ROOT\n" +
|
||||||
|
"├── BEVERAGES\n" +
|
||||||
|
"│ ├── TEA\n" +
|
||||||
|
"│ │ ├── GINGER TEA\n" +
|
||||||
|
"│ │ └── NORMAL TEA\n" +
|
||||||
|
"│ └── COFFEE\n" +
|
||||||
|
"└── CURD\n" +
|
||||||
|
" ├── YOGURT\n" +
|
||||||
|
" └── LASSI\n",
|
||||||
|
root.prettyString { value, _, _ -> value.uppercase() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun depthAndIsLastArePassedToRender() {
|
||||||
|
val root = sampleTree()
|
||||||
|
assertEquals(
|
||||||
|
"Root depth=0 last=true\n" +
|
||||||
|
"├── Beverages depth=1 last=false\n" +
|
||||||
|
"│ ├── tea depth=2 last=false\n" +
|
||||||
|
"│ │ ├── ginger tea depth=3 last=false\n" +
|
||||||
|
"│ │ └── normal tea depth=3 last=true\n" +
|
||||||
|
"│ └── coffee depth=2 last=true\n" +
|
||||||
|
"└── Curd depth=1 last=true\n" +
|
||||||
|
" ├── yogurt depth=2 last=false\n" +
|
||||||
|
" └── lassi depth=2 last=true\n",
|
||||||
|
root.prettyString { value, depth, isLast -> "$value depth=$depth last=$isLast" },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user