Files
cursor2api/test/perf-diag-results.json
小海 c06755c46c perf: 早期中止拒绝检测 + 降低重试/续写上限 + 续写上下文截断
性能优化:
- sendCursorRequest 支持外部 AbortSignal,允许调用方提前中止流
- 工具模式 executeStream 在前 200-400 字符即检测拒绝,立即中止流
  (原先等完整响应再检测,浪费 2-5s/次)
- MAX_REFUSAL_RETRIES 2→1,减少最差情况 API 调用次数
- maxAutoContinue 默认 3→2,减少续写开销
- 续写请求只发最后 2000 字符作为上下文(原先发完整响应,可能 10K+)

测试:
- 重写 perf-diag.mjs 公平对比:直连也使用相同 reframing 提示词
- 公平测试结果:代理平均 0.78x(比直连更快),无性能瓶颈
2026-03-18 09:46:36 +08:00

68 lines
31 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[
{
"name": "① 简短问答",
"direct": {
"totalMs": 3746,
"headerMs": 911,
"ttfbMs": 2711,
"streamMs": 1035,
"textLength": 194,
"chunkCount": 29,
"text": "Quicksort has an average-case time complexity of O(n log n) and a worst-case time complexity of O(n²), which occurs when the pivot selection consistently results in highly unbalanced partitions."
},
"proxy": {
"totalMs": 2784,
"headerMs": 19,
"ttfbMs": 2784,
"firstContentMs": 2784,
"streamMs": 0,
"textLength": 242,
"chunkCount": 1,
"text": "Quicksort has an average-case time complexity of O(n log n) and a worst-case time complexity of O(n²), which occurs when the pivot selection consistently results in unbalanced partitions (e.g., already sorted input with a naive pivot choice)."
}
},
{
"name": "② 中等代码",
"direct": {
"totalMs": 18302,
"headerMs": 1058,
"ttfbMs": 2076,
"streamMs": 16226,
"textLength": 4471,
"chunkCount": 515,
"text": "Here is a clean, well-documented Python function to validate an IPv4 address:\n\n```python\ndef is_valid_ipv4(address: str) -> bool:\n \"\"\"\n Check if a given string is a valid IPv4 address.\n\n An IPv4 address is considered valid if it meets all of the following criteria:\n - Consists of exactly 4 octets separated by periods ('.')\n - Each octet is a decimal integer in the range 0255 (inclusive)\n - No octet contains leading zeros (e.g., '01', '00' are invalid)\n - No extra whitespace or empty octets are present\n\n Args:\n address (str): The string to validate.\n\n Returns:\n bool: True if the string is a valid IPv4 address, False otherwise.\n\n Examples:\n >>> is_valid_ipv4(\"192.168.1.1\")\n True\n >>> is_valid_ipv4(\"255.255.255.255\")\n True\n >>> is_valid_ipv4(\"0.0.0.0\")\n True\n >>> is_valid_ipv4(\"256.100.50.25\")\n False\n >>> is_valid_ipv4(\"192.168.1\")\n False\n >>> is_valid_ipv4(\"192.168.01.1\")\n False\n >>> is_valid_ipv4(\"192.168.1.1.1\")\n False\n >>> is_valid_ipv4(\"\")\n False\n >>> is_valid_ipv4(\"abc.def.ghi.jkl\")\n False\n \"\"\"\n if not isinstance(address, str) or not address:\n return False\n\n octets = address.split(\".\")\n\n if len(octets) != 4:\n return False\n\n for octet in octets:\n # Reject empty octets or those with leading zeros (except \"0\" itself)\n if not octet or (octet.startswith(\"0\") and len(octet) > 1):\n return False\n\n # Reject non-numeric octets\n if not octet.isdigit():\n return False\n\n # Reject octets outside the valid range\n if not (0 <= int(octet) <= 255):\n return False\n\n return True\n\n\n# --- Quick self-test ---\nif __name__ == \"__main__\":\n test_cases = [\n (\"192.168.1.1\", True),\n (\"255.255.255.255\", True),\n (\"0.0.0.0\", True),\n (\"10.0.0.1\", True),\n (\"256.100.50.25\", False), # octet > 255\n (\"192.168.1\", False), # only 3 octets\n (\"192.168.01.1\", False), # leading zero\n (\"192.168.1.1.1\", False), # 5 octets\n (\"\", False), # empty string\n (\"abc.def.ghi.jkl\", False), # non-numeric\n (\"192.168.1.-1\", False), # negative number\n (\"192.168. 1.1\", False), # whitespace\n (\" 192.168.1.1\", False), # leading whitespace\n (\"192.168.1.1 \", False), # trailing whitespace\n ]\n\n all_passed = True\n for ip, expected in test_cases:\n result = is_valid_ipv4(ip)\n status = \"PASS\" if result == expected else \"FAIL\"\n if status == \"FAIL\":\n all_passed = False\n print(f\"[{status}] is_valid_ipv4({ip!r:25}) => {result} (expected {expected})\")\n\n print(\"\\nAll tests passed!\" if all_passed else \"\\nSome tests FAILED.\")\n```\n\n**Sample output:**\n\n```\n[PASS] is_valid_ipv4('192.168.1.1' ) => True (expected True)\n[PASS] is_valid_ipv4('255.255.255.255' ) => True (expected True)\n[PASS] is_valid_ipv4('0.0.0.0' ) => True (expected True)\n[PASS] is_valid_ipv4('10.0.0.1' ) => True (expected True)\n[PASS] is_valid_ipv4('256.100.50.25' ) => False (expected False)\n[PASS] is_valid_ipv4('192.168.1' ) => False (expected False)\n[PASS] is_valid_ipv4('192.168.01.1' ) => False (expected False)\n[PASS] is_valid_ipv4('192.168.1.1.1' ) => False (expected False)\n[PASS] is_valid_ipv4('' ) => False (expected False)\n[PASS] is_valid_ipv4('abc.def.ghi.jkl' ) => False (expected False)\n[PASS] is_valid_ipv4('192.168.1.-1' ) => False (expected False)\n[PASS] is_valid_ipv4('192.168. 1.1' ) => False (expected False)\n[PASS] is_valid_ipv4(' 192.168.1.1' ) => False (expected False)\n[PASS] is_valid_ipv4('192.168.1.1 ' ) => False (expected False)\n\nAll tests passed!\n```\n\n---\n\n**Key design decisions explained:**\n\n| Check | Reason |\n|---|---|\n| `isinstance(address, str)` | Guards against non-string inputs like `None` or integers |\n| `len(octets) != 4` | Ensures exactly 4 dot-separated parts |\n| Leading zero check | `\"01\"` is ambiguous (octal notation) and not valid in IPv4 |\n| `isdigit()` before `int()` | Rejects negatives (`\"-1\"`), spaces, and letters without risking a `ValueError` |\n| `0 <= int(octet) <= 255` | Enforces the valid octet range per the IPv4 spec |"
},
"proxy": {
"totalMs": 16276,
"headerMs": 5,
"ttfbMs": 2737,
"firstContentMs": 2737,
"streamMs": 13539,
"textLength": 3777,
"chunkCount": 431,
"text": "Here is a clean, well-documented Python function to validate an IPv4 address:\n\n```python\ndef is_valid_ipv4(address: str) -> bool:\n \"\"\"\n Check if a given string is a valid IPv4 address.\n\n An IPv4 address is considered valid if it meets ALL of the following criteria:\n - Consists of exactly 4 parts separated by dots ('.')\n - Each part is a valid integer (no leading zeros, no letters or symbols)\n - Each integer is in the range 0 to 255 (inclusive)\n - The string is not empty and contains no extra whitespace or characters\n\n Args:\n address (str): The string to validate as an IPv4 address.\n\n Returns:\n bool: True if the string is a valid IPv4 address, False otherwise.\n\n Examples:\n >>> is_valid_ipv4(\"192.168.1.1\")\n True\n >>> is_valid_ipv4(\"255.255.255.255\")\n True\n >>> is_valid_ipv4(\"0.0.0.0\")\n True\n >>> is_valid_ipv4(\"256.100.50.25\")\n False\n >>> is_valid_ipv4(\"192.168.1\")\n False\n >>> is_valid_ipv4(\"192.168.01.1\") # Leading zero\n False\n >>> is_valid_ipv4(\"192.168.1.1.1\") # Too many octets\n False\n >>> is_valid_ipv4(\"abc.def.ghi.jkl\")\n False\n >>> is_valid_ipv4(\"\")\n False\n \"\"\"\n if not isinstance(address, str) or not address:\n return False\n\n parts = address.split(\".\")\n\n if len(parts) != 4:\n return False\n\n for part in parts:\n # Reject empty parts, parts with leading zeros, and non-numeric parts\n if not part or not part.isdigit():\n return False\n\n # Reject leading zeros (e.g. \"01\", \"001\"), but allow \"0\" itself\n if len(part) > 1 and part[0] == \"0\":\n return False\n\n # Reject values outside the valid octet range\n if not (0 <= int(part) <= 255):\n return False\n\n return True\n```\n\n---\n\n**Key design decisions explained:**\n\n| Check | Reason |\n|---|---|\n| `isinstance(address, str)` | Guards against non-string inputs like `None` or integers |\n| `split(\".\")` produces exactly 4 parts | Enforces the 4-octet structure |\n| `part.isdigit()` | Rejects letters, symbols, spaces, and negative signs (e.g. `\"-1\"`) |\n| Leading zero check | `\"01\"` or `\"001\"` are ambiguous/invalid in standard IPv4 notation |\n| `0 <= int(part) <= 255` | Each octet must be within the valid byte range |\n\n---\n\n**Quick test run:**\n\n```python\ntest_cases = [\n (\"192.168.1.1\", True),\n (\"0.0.0.0\", True),\n (\"255.255.255.255\", True),\n (\"256.0.0.1\", False), # Out of range\n (\"192.168.01.1\", False), # Leading zero\n (\"192.168.1\", False), # Too few octets\n (\"192.168.1.1.1\", False), # Too many octets\n (\"192.168.1.-1\", False), # Negative number\n (\"abc.def.ghi.jkl\", False), # Non-numeric\n (\"\", False), # Empty string\n (\"1.2.3. 4\", False), # Whitespace\n]\n\nfor ip, expected in test_cases:\n result = is_valid_ipv4(ip)\n status = \"PASS\" if result == expected else \"FAIL\"\n print(f\"[{status}] is_valid_ipv4({ip!r:20}) => {result}\")\n```\n\n**Output:**\n```\n[PASS] is_valid_ipv4('192.168.1.1' ) => True\n[PASS] is_valid_ipv4('0.0.0.0' ) => True\n[PASS] is_valid_ipv4('255.255.255.255' ) => True\n[PASS] is_valid_ipv4('256.0.0.1' ) => False\n[PASS] is_valid_ipv4('192.168.01.1' ) => False\n[PASS] is_valid_ipv4('192.168.1' ) => False\n[PASS] is_valid_ipv4('192.168.1.1.1' ) => False\n[PASS] is_valid_ipv4('192.168.1.-1' ) => False\n[PASS] is_valid_ipv4('abc.def.ghi.jkl' ) => False\n[PASS] is_valid_ipv4('' ) => False\n[PASS] is_valid_ipv4('1.2.3. 4' ) => False\n```\n\nAll 11 cases pass. The function handles all common edge cases without relying on any external libraries or regex."
}
},
{
"name": "③ 长代码生成",
"direct": {
"totalMs": 38394,
"headerMs": 861,
"ttfbMs": 2590,
"streamMs": 35804,
"textLength": 11113,
"chunkCount": 1371,
"text": "Here is a complete, well-typed Binary Search Tree implementation in TypeScript:\n\n```typescript\n// ============================================================\n// Type Definitions\n// ============================================================\n\ninterface BSTNode<T> {\n value: T;\n left: BSTNode<T> | null;\n right: BSTNode<T> | null;\n}\n\ntype Comparator<T> = (a: T, b: T) => number;\n\n// ============================================================\n// Node Factory\n// ============================================================\n\nfunction createNode<T>(value: T): BSTNode<T> {\n return { value, left: null, right: null };\n}\n\n// ============================================================\n// Binary Search Tree Class\n// ============================================================\n\nclass BinarySearchTree<T> {\n private root: BSTNode<T> | null = null;\n private comparator: Comparator<T>;\n\n /**\n * @param comparator - Optional custom comparator function.\n * Defaults to natural (ascending) ordering for numbers and strings.\n * Return negative if a < b, 0 if a === b, positive if a > b.\n */\n constructor(comparator?: Comparator<T>) {\n this.comparator =\n comparator ??\n ((a, b) => {\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n });\n }\n\n // ──────────────────────────────────────────────────────────\n // INSERT\n // ──────────────────────────────────────────────────────────\n\n /**\n * Inserts a value into the BST. Duplicate values are ignored.\n * Time complexity: O(h), where h is the height of the tree.\n */\n insert(value: T): this {\n this.root = this._insertNode(this.root, value);\n return this; // enables chaining: bst.insert(1).insert(2)\n }\n\n private _insertNode(node: BSTNode<T> | null, value: T): BSTNode<T> {\n if (node === null) return createNode(value);\n\n const cmp = this.comparator(value, node.value);\n\n if (cmp < 0) {\n node.left = this._insertNode(node.left, value);\n } else if (cmp > 0) {\n node.right = this._insertNode(node.right, value);\n }\n // cmp === 0: duplicate — do nothing\n\n return node;\n }\n\n // ──────────────────────────────────────────────────────────\n // SEARCH\n // ──────────────────────────────────────────────────────────\n\n /**\n * Searches for a value in the BST.\n * Returns the matching node, or null if not found.\n * Time complexity: O(h)\n */\n search(value: T): BSTNode<T> | null {\n return this._searchNode(this.root, value);\n }\n\n /** Convenience method — returns true/false instead of the node. */\n contains(value: T): boolean {\n return this.search(value) !== null;\n }\n\n private _searchNode(\n node: BSTNode<T> | null,\n value: T\n ): BSTNode<T> | null {\n if (node === null) return null;\n\n const cmp = this.comparator(value, node.value);\n\n if (cmp < 0) return this._searchNode(node.left, value);\n if (cmp > 0) return this._searchNode(node.right, value);\n return node; // found\n }\n\n // ──────────────────────────────────────────────────────────\n // DELETE\n // ──────────────────────────────────────────────────────────\n\n /**\n * Removes a value from the BST (if it exists).\n * Uses the in-order successor strategy for nodes with two children.\n * Time complexity: O(h)\n */\n delete(value: T): this {\n this.root = this._deleteNode(this.root, value);\n return this;\n }\n\n private _deleteNode(\n node: BSTNode<T> | null,\n value: T\n ): BSTNode<T> | null {\n if (node === null) return null; // value not found — no-op\n\n const cmp = this.comparator(value, node.value);\n\n if (cmp < 0) {\n node.left = this._deleteNode(node.left, value);\n } else if (cmp > 0) {\n node.right = this._deleteNode(node.right, value);\n } else {\n // Node to delete found — three cases:\n\n // Case 1: Leaf node (no children)\n if (node.left === null && node.right === null) {\n return null;\n }\n\n // Case 2: One child — replace node with its child\n if (node.left === null) return node.right;\n if (node.right === null) return node.left;\n\n // Case 3: Two children — replace value with in-order successor\n // (smallest value in the right subtree), then delete the successor.\n const successor = this._findMin(node.right);\n node.value = successor.value;\n node.right = this._deleteNode(node.right, successor.value);\n }\n\n return node;\n }\n\n // ──────────────────────────────────────────────────────────\n // TRAVERSALS\n // ──────────────────────────────────────────────────────────\n\n /**\n * In-order traversal (Left → Root → Right).\n * Visits nodes in ascending sorted order.\n * Time complexity: O(n)\n */\n inorder(): T[] {\n const result: T[] = [];\n this._inorderTraverse(this.root, result);\n return result;\n }\n\n private _inorderTraverse(node: BSTNode<T> | null, result: T[]): void {\n if (node === null) return;\n this._inorderTraverse(node.left, result);\n result.push(node.value);\n this._inorderTraverse(node.right, result);\n }\n\n /**\n * Pre-order traversal (Root → Left → Right).\n * Useful for serializing/copying the tree structure.\n * Time complexity: O(n)\n */\n preorder(): T[] {\n const result: T[] = [];\n this._preorderTraverse(this.root, result);\n return result;\n }\n\n private _preorderTraverse(node: BSTNode<T> | null, result: T[]): void {\n if (node === null) return;\n result.push(node.value);\n this._preorderTraverse(node.left, result);\n this._preorderTraverse(node.right, result);\n }\n\n /**\n * Post-order traversal (Left → Right → Root).\n * Useful for deleting or evaluating the entire tree.\n * Time complexity: O(n)\n */\n postorder(): T[] {\n const result: T[] = [];\n this._postorderTraverse(this.root, result);\n return result;\n }\n\n private _postorderTraverse(node: BSTNode<T> | null, result: T[]): void {\n if (node === null) return;\n this._postorderTraverse(node.left, result);\n this._postorderTraverse(node.right, result);\n result.push(node.value);\n }\n\n // ──────────────────────────────────────────────────────────\n // UTILITY METHODS\n // ──────────────────────────────────────────────────────────\n\n /** Returns the node with the minimum value in the tree (or subtree). */\n findMin(): BSTNode<T> | null {\n if (this.root === null) return null;\n return this._findMin(this.root);\n }\n\n private _findMin(node: BSTNode<T>): BSTNode<T> {\n return node.left === null ? node : this._findMin(node.left);\n }\n\n /** Returns the node with the maximum value in the tree (or subtree). */\n findMax(): BSTNode<T> | null {\n if (this.root === null) return null;\n return this._findMax(this.root);\n }\n\n private _findMax(node: BSTNode<T>): BSTNode<T> {\n return node.right === null ? node : this._findMax(node.right);\n }\n\n /** Returns the height of the tree. An empty tree has height -1. */\n height(): number {\n return this._calcHeight(this.root);\n }\n\n private _calcHeight(node: BSTNode<T> | null): number {\n if (node === null) return -1;\n return 1 + Math.max(this._calcHeight(node.left), this._calcHeight(node.right));\n }\n\n /** Returns the total number of nodes. */\n size(): number {\n return this._countNodes(this.root);\n }\n\n private _countNodes(node: BSTNode<T> | null): number {\n if (node === null) return 0;\n return 1 + this._countNodes(node.left) + this._countNodes(node.right);\n }\n\n /** Removes all nodes from the tree. */\n clear(): void {\n this.root = null;\n }\n\n /** Returns true if the tree is empty. */\n isEmpty(): boolean {\n return this.root === null;\n }\n}\n\n// ============================================================\n// Usage Examples\n// ============================================================\n\n// --- Numeric BST ---\nconst numTree = new BinarySearchTree<number>();\n\nnumTree.insert(10).insert(5).insert(15).insert(3).insert(7).insert(12).insert(18);\n// 10\n// / \\\n// 5 15\n// / \\ / \\\n// 3 7 12 18\n\nconsole.log(\"Inorder: \", numTree.inorder()); // [3, 5, 7, 10, 12, 15, 18]\nconsole.log(\"Preorder: \", numTree.preorder()); // [10, 5, 3, 7, 15, 12, 18]\nconsole.log(\"Postorder:\", numTree.postorder()); // [3, 7, 5, 12, 18, 15, 10]\n\nconsole.log(\"Contains 7:\", numTree.contains(7)); // true\nconsole.log(\"Contains 99:\", numTree.contains(99)); // false\nconsole.log(\"Search 15:\", numTree.search(15)); // { value: 15, left: {...}, right: {...} }\n\nconsole.log(\"Min:\", numTree.findMin()?.value); // 3\nconsole.log(\"Max:\", numTree.findMax()?.value); // 18\nconsole.log(\"Height:\", numTree.height()); // 2\nconsole.log(\"Size:\", numTree.size()); // 7\n\n// Delete a leaf\nnumTree.delete(3);\nconsole.log(\"After deleting 3:\", numTree.inorder()); // [5, 7, 10, 12, 15, 18]\n\n// Delete a node with one child\nnumTree.delete(5);\nconsole.log(\"After deleting 5:\", numTree.inorder()); // [7, 10, 12, 15, 18]\n\n// Delete a node with two children\nnumTree.delete(15);\nconsole.log(\"After deleting 15:\", numTree.inorder()); // [7, 10, 12, 18]\n\n// --- String BST ---\nconst strTree = new BinarySearchTree<string>();\nstrTree.insert(\"banana\").insert(\"apple\").insert(\"cherry\").insert(\"date\");\nconsole.log(\"String BST inorder:\", strTree.inorder()); // [\"apple\", \"banana\", \"cherry\", \"date\"]\n\n// --- Custom Object BST (with comparator) ---\ninterface Person {\n name: string;\n age: number;\n}\n\nconst personTree = new BinarySearchTree<Person>(\n (a, b) => a.age - b.age // sort by age\n);\n\npersonTree.insert({ name: \"Alice\", age: 30 });\npersonTree.insert({ name: \"Bob\", age: 25 });\npersonTree.insert({ name: \"Carol\", age: 35 });\n\nconsole.log(\n \"People by age:\",\n personTree.inorder().map((p) => `${p.name}(${p.age})`)\n); // [\"Bob(25)\", \"Alice(30)\", \"Carol(35)\"]\n```\n\n---\n\nHere is a breakdown of the key design decisions:\n\n**Generic with a custom comparator**\nThe class is `BinarySearchTree<T>`, so it works with numbers, strings, or any custom object. A default comparator handles primitives automatically; pass a custom one for complex types.\n\n**Delete uses the in-order successor strategy**\nWhen deleting a node with two children, it finds the smallest node in the right subtree (in-order successor), copies its value up, then deletes the successor — this preserves BST ordering with minimal restructuring.\n\n**Method chaining**\n`insert` and `delete` return `this`, so you can chain calls like `bst.insert(1).insert(2).delete(1)`.\n\n**All three traversals included**\n- `inorder` (L → Root → R): produces sorted output, the most commonly needed\n- `preorder` (Root → L → R): useful for serialization/cloning\n- `postorder` (L → R → Root): useful for safe deletion or expression evaluation\n\n**Time complexities**\n\n| Method | Average | Worst (skewed tree) |\n|---|---|---|\n| `insert` | O(log n) | O(n) |\n| `search` / `contains` | O(log n) | O(n) |\n| `delete` | O(log n) | O(n) |\n| `inorder` / `preorder` / `postorder` | O(n) | O(n) |\n| `findMin` / `findMax` | O(log n) | O(n) |\n| `height` / `size` | O(n) | O(n) |\n\nIf you need guaranteed O(log n) performance regardless of insertion order, consider extending this into a self-balancing tree like an AVL or Red-Black tree."
},
"proxy": {
"totalMs": 27861,
"headerMs": 4,
"ttfbMs": 3324,
"firstContentMs": 3324,
"streamMs": 24537,
"textLength": 7426,
"chunkCount": 940,
"text": "Here is a complete, well-typed Binary Search Tree implementation in TypeScript:\n\n```typescript\n// ─── Type Definitions ────────────────────────────────────────────────────────\n\ntype Nullable<T> = T | null;\n\ninterface BSTNode<T> {\n value: T;\n left: Nullable<BSTNode<T>>;\n right: Nullable<BSTNode<T>>;\n}\n\ninterface IBinarySearchTree<T> {\n insert(value: T): void;\n delete(value: T): void;\n search(value: T): Nullable<BSTNode<T>>;\n inorder(): T[];\n isEmpty(): boolean;\n getRoot(): Nullable<BSTNode<T>>;\n}\n\n// ─── Node Factory ─────────────────────────────────────────────────────────────\n\nfunction createNode<T>(value: T): BSTNode<T> {\n return { value, left: null, right: null };\n}\n\n// ─── Comparator Type ─────────────────────────────────────────────────────────\n\n/**\n * Returns:\n * -1 (or any negative) if a < b\n * 0 if a === b\n * 1 (or any positive) if a > b\n */\ntype Comparator<T> = (a: T, b: T) => number;\n\nconst defaultComparator = <T>(a: T, b: T): number => {\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n};\n\n// ─── BinarySearchTree Class ───────────────────────────────────────────────────\n\nclass BinarySearchTree<T> implements IBinarySearchTree<T> {\n private root: Nullable<BSTNode<T>> = null;\n private readonly compare: Comparator<T>;\n\n constructor(comparator: Comparator<T> = defaultComparator) {\n this.compare = comparator;\n }\n\n // ── Public API ─────────────────────────────────────────────────────────────\n\n isEmpty(): boolean {\n return this.root === null;\n }\n\n getRoot(): Nullable<BSTNode<T>> {\n return this.root;\n }\n\n /**\n * Inserts a value into the BST.\n * Duplicate values are ignored.\n * Time complexity: O(h) where h is the height of the tree.\n */\n insert(value: T): void {\n this.root = this.insertNode(this.root, value);\n }\n\n /**\n * Removes a value from the BST if it exists.\n * Time complexity: O(h) where h is the height of the tree.\n */\n delete(value: T): void {\n this.root = this.deleteNode(this.root, value);\n }\n\n /**\n * Searches for a value and returns its node, or null if not found.\n * Time complexity: O(h) where h is the height of the tree.\n */\n search(value: T): Nullable<BSTNode<T>> {\n return this.searchNode(this.root, value);\n }\n\n /**\n * Returns all values in sorted (ascending) order via in-order traversal.\n * Time complexity: O(n).\n */\n inorder(): T[] {\n const result: T[] = [];\n this.inorderTraversal(this.root, result);\n return result;\n }\n\n // ── Private Helpers ────────────────────────────────────────────────────────\n\n private insertNode(node: Nullable<BSTNode<T>>, value: T): BSTNode<T> {\n // Base case: found an empty slot — create and return a new node\n if (node === null) {\n return createNode(value);\n }\n\n const cmp = this.compare(value, node.value);\n\n if (cmp < 0) {\n // Value is smaller — go left\n node.left = this.insertNode(node.left, value);\n } else if (cmp > 0) {\n // Value is larger — go right\n node.right = this.insertNode(node.right, value);\n }\n // cmp === 0: duplicate, do nothing\n\n return node;\n }\n\n private deleteNode(node: Nullable<BSTNode<T>>, value: T): Nullable<BSTNode<T>> {\n // Base case: value not found\n if (node === null) return null;\n\n const cmp = this.compare(value, node.value);\n\n if (cmp < 0) {\n // Target is in the left subtree\n node.left = this.deleteNode(node.left, value);\n } else if (cmp > 0) {\n // Target is in the right subtree\n node.right = this.deleteNode(node.right, value);\n } else {\n // Found the node to delete — handle 3 cases:\n\n // Case 1: Leaf node (no children)\n if (node.left === null && node.right === null) {\n return null;\n }\n\n // Case 2a: Only right child\n if (node.left === null) {\n return node.right;\n }\n\n // Case 2b: Only left child\n if (node.right === null) {\n return node.left;\n }\n\n // Case 3: Two children\n // Replace with in-order successor (smallest value in right subtree),\n // then delete the successor from the right subtree.\n const successor = this.findMin(node.right);\n node.value = successor.value;\n node.right = this.deleteNode(node.right, successor.value);\n }\n\n return node;\n }\n\n private searchNode(node: Nullable<BSTNode<T>>, value: T): Nullable<BSTNode<T>> {\n if (node === null) return null;\n\n const cmp = this.compare(value, node.value);\n\n if (cmp < 0) return this.searchNode(node.left, value);\n if (cmp > 0) return this.searchNode(node.right, value);\n return node; // cmp === 0: found\n }\n\n private inorderTraversal(node: Nullable<BSTNode<T>>, result: T[]): void {\n if (node === null) return;\n this.inorderTraversal(node.left, result);\n result.push(node.value);\n this.inorderTraversal(node.right, result);\n }\n\n /**\n * Returns the node with the minimum value in a given subtree.\n * Assumes the subtree is non-null.\n */\n private findMin(node: BSTNode<T>): BSTNode<T> {\n let current = node;\n while (current.left !== null) {\n current = current.left;\n }\n return current;\n }\n}\n\n// ─── Usage Examples ───────────────────────────────────────────────────────────\n\n// --- Numbers ---\nconst numTree = new BinarySearchTree<number>();\n\nnumTree.insert(10);\nnumTree.insert(5);\nnumTree.insert(15);\nnumTree.insert(3);\nnumTree.insert(7);\nnumTree.insert(12);\nnumTree.insert(20);\n\nconsole.log(\"Inorder:\", numTree.inorder());\n// → [3, 5, 7, 10, 12, 15, 20]\n\nconsole.log(\"Search 7:\", numTree.search(7));\n// → { value: 7, left: null, right: null }\n\nconsole.log(\"Search 99:\", numTree.search(99));\n// → null\n\nnumTree.delete(5); // Node with two children\nconsole.log(\"After deleting 5:\", numTree.inorder());\n// → [3, 7, 10, 12, 15, 20]\n\nnumTree.delete(15); // Node with two children\nconsole.log(\"After deleting 15:\", numTree.inorder());\n// → [3, 7, 10, 12, 20]\n\nnumTree.delete(3); // Leaf node\nconsole.log(\"After deleting 3:\", numTree.inorder());\n// → [7, 10, 12, 20]\n\n// --- Strings (custom comparator) ---\nconst strTree = new BinarySearchTree<string>((a, b) => a.localeCompare(b));\n\nstrTree.insert(\"banana\");\nstrTree.insert(\"apple\");\nstrTree.insert(\"cherry\");\nstrTree.insert(\"date\");\n\nconsole.log(\"String tree inorder:\", strTree.inorder());\n// → [\"apple\", \"banana\", \"cherry\", \"date\"]\n```\n\n---\n\n## Key Design Decisions\n\n| Decision | Rationale |\n|---|---|\n| **Generic `<T>`** | Works with numbers, strings, or any custom type |\n| **Custom `Comparator<T>`** | Makes the tree flexible for non-primitive types (e.g., objects) |\n| **Interface `IBinarySearchTree<T>`** | Enforces the public API contract and is easy to extend or mock |\n| **`Nullable<T>` alias** | Cleaner than repeating `T \\| null` everywhere |\n| **In-order successor for delete** | Standard, correct approach for two-child deletion |\n| **Duplicate values ignored** | Common BST convention; easily changed to allow duplicates |\n| **Recursive implementation** | Clear and readable; for very deep trees, an iterative version avoids stack overflow |\n\n## Complexity Summary\n\n| Operation | Average Case | Worst Case (skewed tree) |\n|---|---|---|\n| `insert` | O(log n) | O(n) |\n| `delete` | O(log n) | O(n) |\n| `search` | O(log n) | O(n) |\n| `inorder` | O(n) | O(n) |\n\nFor guaranteed O(log n) worst-case performance, consider extending this into a self-balancing tree such as an AVL tree or Red-Black tree."
}
}
]