exonware-xwnode 0.0.1.21__py3-none-any.whl → 0.0.1.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- exonware/__init__.py +8 -1
- exonware/xwnode/__init__.py +18 -5
- exonware/xwnode/add_strategy_types.py +165 -0
- exonware/xwnode/base.py +7 -5
- exonware/xwnode/common/__init__.py +1 -1
- exonware/xwnode/common/graph/__init__.py +30 -0
- exonware/xwnode/common/graph/caching.py +131 -0
- exonware/xwnode/common/graph/contracts.py +100 -0
- exonware/xwnode/common/graph/errors.py +44 -0
- exonware/xwnode/common/graph/indexing.py +260 -0
- exonware/xwnode/common/graph/manager.py +568 -0
- exonware/xwnode/common/management/__init__.py +3 -5
- exonware/xwnode/common/management/manager.py +9 -9
- exonware/xwnode/common/management/migration.py +6 -6
- exonware/xwnode/common/monitoring/__init__.py +3 -5
- exonware/xwnode/common/monitoring/metrics.py +7 -3
- exonware/xwnode/common/monitoring/pattern_detector.py +2 -2
- exonware/xwnode/common/monitoring/performance_monitor.py +6 -2
- exonware/xwnode/common/patterns/__init__.py +3 -5
- exonware/xwnode/common/patterns/advisor.py +1 -1
- exonware/xwnode/common/patterns/flyweight.py +6 -2
- exonware/xwnode/common/patterns/registry.py +203 -184
- exonware/xwnode/common/utils/__init__.py +25 -11
- exonware/xwnode/common/utils/simple.py +1 -1
- exonware/xwnode/config.py +3 -8
- exonware/xwnode/contracts.py +4 -105
- exonware/xwnode/defs.py +413 -159
- exonware/xwnode/edges/strategies/__init__.py +86 -4
- exonware/xwnode/edges/strategies/_base_edge.py +2 -2
- exonware/xwnode/edges/strategies/adj_list.py +287 -121
- exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
- exonware/xwnode/edges/strategies/base.py +1 -1
- exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
- exonware/xwnode/edges/strategies/bitemporal.py +520 -0
- exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
- exonware/xwnode/edges/strategies/bv_graph.py +664 -0
- exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
- exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
- exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
- exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
- exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
- exonware/xwnode/edges/strategies/edge_list.py +168 -0
- exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
- exonware/xwnode/edges/strategies/euler_tour.py +560 -0
- exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
- exonware/xwnode/edges/strategies/graphblas.py +449 -0
- exonware/xwnode/edges/strategies/hnsw.py +637 -0
- exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
- exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
- exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
- exonware/xwnode/edges/strategies/k2_tree.py +613 -0
- exonware/xwnode/edges/strategies/link_cut.py +626 -0
- exonware/xwnode/edges/strategies/multiplex.py +532 -0
- exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
- exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
- exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
- exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
- exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
- exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
- exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
- exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
- exonware/xwnode/errors.py +3 -6
- exonware/xwnode/facade.py +20 -20
- exonware/xwnode/nodes/strategies/__init__.py +29 -9
- exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
- exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
- exonware/xwnode/nodes/strategies/array_list.py +36 -3
- exonware/xwnode/nodes/strategies/art.py +581 -0
- exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
- exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
- exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
- exonware/xwnode/nodes/strategies/base.py +469 -98
- exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
- exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
- exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
- exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
- exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
- exonware/xwnode/nodes/strategies/contracts.py +1 -1
- exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
- exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
- exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
- exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
- exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
- exonware/xwnode/nodes/strategies/dawg.py +876 -0
- exonware/xwnode/nodes/strategies/deque.py +321 -153
- exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
- exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
- exonware/xwnode/nodes/strategies/hamt.py +403 -0
- exonware/xwnode/nodes/strategies/hash_map.py +354 -67
- exonware/xwnode/nodes/strategies/heap.py +105 -5
- exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
- exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
- exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
- exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
- exonware/xwnode/nodes/strategies/learned_index.py +533 -0
- exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
- exonware/xwnode/nodes/strategies/linked_list.py +316 -119
- exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
- exonware/xwnode/nodes/strategies/masstree.py +130 -0
- exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
- exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
- exonware/xwnode/nodes/strategies/queue.py +249 -120
- exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
- exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
- exonware/xwnode/nodes/strategies/rope.py +717 -0
- exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
- exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
- exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
- exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
- exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
- exonware/xwnode/nodes/strategies/stack.py +244 -112
- exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
- exonware/xwnode/nodes/strategies/t_tree.py +94 -0
- exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
- exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
- exonware/xwnode/nodes/strategies/trie.py +153 -9
- exonware/xwnode/nodes/strategies/union_find.py +111 -5
- exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
- exonware/xwnode/strategies/__init__.py +5 -51
- exonware/xwnode/version.py +3 -3
- {exonware_xwnode-0.0.1.21.dist-info → exonware_xwnode-0.0.1.23.dist-info}/METADATA +23 -3
- exonware_xwnode-0.0.1.23.dist-info/RECORD +130 -0
- exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
- exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
- exonware/xwnode/nodes/strategies/_base_node.py +0 -307
- exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
- exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
- exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
- exonware/xwnode/nodes/strategies/node_heap.py +0 -196
- exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
- exonware/xwnode/nodes/strategies/node_trie.py +0 -257
- exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
- exonware/xwnode/queries/executors/__init__.py +0 -47
- exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
- exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
- exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
- exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
- exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
- exonware/xwnode/queries/executors/array/__init__.py +0 -9
- exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
- exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
- exonware/xwnode/queries/executors/base.py +0 -257
- exonware/xwnode/queries/executors/capability_checker.py +0 -204
- exonware/xwnode/queries/executors/contracts.py +0 -166
- exonware/xwnode/queries/executors/core/__init__.py +0 -17
- exonware/xwnode/queries/executors/core/create_executor.py +0 -96
- exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
- exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
- exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
- exonware/xwnode/queries/executors/core/select_executor.py +0 -152
- exonware/xwnode/queries/executors/core/update_executor.py +0 -102
- exonware/xwnode/queries/executors/data/__init__.py +0 -13
- exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
- exonware/xwnode/queries/executors/data/load_executor.py +0 -50
- exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
- exonware/xwnode/queries/executors/data/store_executor.py +0 -50
- exonware/xwnode/queries/executors/defs.py +0 -93
- exonware/xwnode/queries/executors/engine.py +0 -221
- exonware/xwnode/queries/executors/errors.py +0 -68
- exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
- exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
- exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
- exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
- exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
- exonware/xwnode/queries/executors/graph/__init__.py +0 -15
- exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
- exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
- exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
- exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
- exonware/xwnode/queries/executors/projection/__init__.py +0 -9
- exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
- exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
- exonware/xwnode/queries/executors/registry.py +0 -173
- exonware/xwnode/queries/parsers/__init__.py +0 -26
- exonware/xwnode/queries/parsers/base.py +0 -86
- exonware/xwnode/queries/parsers/contracts.py +0 -46
- exonware/xwnode/queries/parsers/errors.py +0 -53
- exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
- exonware/xwnode/queries/strategies/__init__.py +0 -24
- exonware/xwnode/queries/strategies/base.py +0 -236
- exonware/xwnode/queries/strategies/cql.py +0 -201
- exonware/xwnode/queries/strategies/cypher.py +0 -181
- exonware/xwnode/queries/strategies/datalog.py +0 -70
- exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
- exonware/xwnode/queries/strategies/eql.py +0 -70
- exonware/xwnode/queries/strategies/flux.py +0 -70
- exonware/xwnode/queries/strategies/gql.py +0 -70
- exonware/xwnode/queries/strategies/graphql.py +0 -240
- exonware/xwnode/queries/strategies/gremlin.py +0 -181
- exonware/xwnode/queries/strategies/hiveql.py +0 -214
- exonware/xwnode/queries/strategies/hql.py +0 -70
- exonware/xwnode/queries/strategies/jmespath.py +0 -219
- exonware/xwnode/queries/strategies/jq.py +0 -66
- exonware/xwnode/queries/strategies/json_query.py +0 -66
- exonware/xwnode/queries/strategies/jsoniq.py +0 -248
- exonware/xwnode/queries/strategies/kql.py +0 -70
- exonware/xwnode/queries/strategies/linq.py +0 -238
- exonware/xwnode/queries/strategies/logql.py +0 -70
- exonware/xwnode/queries/strategies/mql.py +0 -68
- exonware/xwnode/queries/strategies/n1ql.py +0 -210
- exonware/xwnode/queries/strategies/partiql.py +0 -70
- exonware/xwnode/queries/strategies/pig.py +0 -215
- exonware/xwnode/queries/strategies/promql.py +0 -70
- exonware/xwnode/queries/strategies/sparql.py +0 -220
- exonware/xwnode/queries/strategies/sql.py +0 -275
- exonware/xwnode/queries/strategies/xml_query.py +0 -66
- exonware/xwnode/queries/strategies/xpath.py +0 -223
- exonware/xwnode/queries/strategies/xquery.py +0 -258
- exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
- exonware/xwnode/queries/strategies/xwquery.py +0 -456
- exonware_xwnode-0.0.1.21.dist-info/RECORD +0 -214
- /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
- {exonware_xwnode-0.0.1.21.dist-info → exonware_xwnode-0.0.1.23.dist-info}/WHEEL +0 -0
- {exonware_xwnode-0.0.1.21.dist-info → exonware_xwnode-0.0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,717 @@
|
|
1
|
+
"""
|
2
|
+
#exonware/xwnode/src/exonware/xwnode/nodes/strategies/rope.py
|
3
|
+
|
4
|
+
Rope Node Strategy Implementation
|
5
|
+
|
6
|
+
This module implements the ROPE strategy for efficient text/string operations
|
7
|
+
using a binary tree structure.
|
8
|
+
|
9
|
+
Company: eXonware.com
|
10
|
+
Author: Eng. Muhammad AlShehri
|
11
|
+
Email: connect@exonware.com
|
12
|
+
Version: 0.0.1.23
|
13
|
+
Generation Date: 12-Oct-2025
|
14
|
+
"""
|
15
|
+
|
16
|
+
from typing import Any, Iterator, List, Dict, Optional, Tuple
|
17
|
+
from .base import ANodeTreeStrategy
|
18
|
+
from .contracts import NodeType
|
19
|
+
from ...defs import NodeMode, NodeTrait
|
20
|
+
from ...errors import XWNodeError, XWNodeValueError
|
21
|
+
|
22
|
+
|
23
|
+
class RopeNode:
|
24
|
+
"""
|
25
|
+
Node in rope structure.
|
26
|
+
|
27
|
+
WHY leaf/internal separation:
|
28
|
+
- Leaves store actual text chunks
|
29
|
+
- Internal nodes store weights (left subtree size)
|
30
|
+
- Enables O(log n) indexing and splits
|
31
|
+
"""
|
32
|
+
|
33
|
+
def __init__(self, text: str = "", is_leaf: bool = True):
|
34
|
+
"""
|
35
|
+
Initialize rope node.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
text: Text content (for leaf nodes)
|
39
|
+
is_leaf: Whether this is a leaf node
|
40
|
+
"""
|
41
|
+
self.is_leaf = is_leaf
|
42
|
+
self.text = text if is_leaf else ""
|
43
|
+
self.weight = len(text) if is_leaf else 0 # Left subtree length
|
44
|
+
self.left: Optional['RopeNode'] = None
|
45
|
+
self.right: Optional['RopeNode'] = None
|
46
|
+
self.parent: Optional['RopeNode'] = None
|
47
|
+
self.height = 1
|
48
|
+
|
49
|
+
def get_total_length(self) -> int:
|
50
|
+
"""Get total length of text in this subtree."""
|
51
|
+
if self.is_leaf:
|
52
|
+
return len(self.text)
|
53
|
+
|
54
|
+
total = self.weight
|
55
|
+
if self.right:
|
56
|
+
total += self.right.get_total_length()
|
57
|
+
return total
|
58
|
+
|
59
|
+
def update_weight(self) -> None:
|
60
|
+
"""Update weight based on left subtree."""
|
61
|
+
if not self.is_leaf and self.left:
|
62
|
+
self.weight = self.left.get_total_length()
|
63
|
+
|
64
|
+
def update_height(self) -> None:
|
65
|
+
"""Update height based on children."""
|
66
|
+
left_h = self.left.height if self.left else 0
|
67
|
+
right_h = self.right.height if self.right else 0
|
68
|
+
self.height = 1 + max(left_h, right_h)
|
69
|
+
|
70
|
+
def get_balance(self) -> int:
|
71
|
+
"""Get balance factor for AVL balancing."""
|
72
|
+
left_h = self.left.height if self.left else 0
|
73
|
+
right_h = self.right.height if self.right else 0
|
74
|
+
return left_h - right_h
|
75
|
+
|
76
|
+
|
77
|
+
class RopeStrategy(ANodeTreeStrategy):
|
78
|
+
"""
|
79
|
+
Rope strategy for efficient large text operations.
|
80
|
+
|
81
|
+
WHY Rope:
|
82
|
+
- O(log n) insert/delete vs O(n) for strings
|
83
|
+
- Avoids massive copying on edits
|
84
|
+
- Perfect for text editors handling large documents
|
85
|
+
- Efficient substring operations
|
86
|
+
- Supports persistent versions with structural sharing
|
87
|
+
|
88
|
+
WHY this implementation:
|
89
|
+
- AVL balancing maintains O(log n) height
|
90
|
+
- Leaf nodes store text chunks (cache-friendly)
|
91
|
+
- Internal nodes store weights for indexing
|
92
|
+
- Lazy rebalancing amortizes restructuring cost
|
93
|
+
- Configurable chunk size for tuning
|
94
|
+
|
95
|
+
Time Complexity:
|
96
|
+
- Index access: O(log n) where n is text length
|
97
|
+
- Concatenate: O(log n) (just tree join)
|
98
|
+
- Split: O(log n)
|
99
|
+
- Insert: O(log n)
|
100
|
+
- Delete: O(log n)
|
101
|
+
- Substring: O(log n + k) where k is substring length
|
102
|
+
- Iteration: O(n) for full text
|
103
|
+
|
104
|
+
Space Complexity: O(n) for n characters (plus tree overhead)
|
105
|
+
|
106
|
+
Trade-offs:
|
107
|
+
- Advantage: Efficient edits without copying entire string
|
108
|
+
- Advantage: O(log n) operations vs O(n) for Python strings
|
109
|
+
- Advantage: Supports persistent versions
|
110
|
+
- Limitation: Higher overhead for small strings (<1KB)
|
111
|
+
- Limitation: More complex than simple string
|
112
|
+
- Limitation: Worse cache locality than contiguous string
|
113
|
+
- Compared to String: Better for edits, worse for iteration
|
114
|
+
- Compared to Gap buffer: Better for random edits, more memory
|
115
|
+
|
116
|
+
Best for:
|
117
|
+
- Text editors with large documents (>10KB)
|
118
|
+
- Frequent insert/delete operations
|
119
|
+
- Undo/redo with persistent versions
|
120
|
+
- Collaborative editing
|
121
|
+
- Syntax highlighting with edits
|
122
|
+
- Large log file manipulation
|
123
|
+
|
124
|
+
Not recommended for:
|
125
|
+
- Small strings (<1KB) - use native Python string
|
126
|
+
- Append-only scenarios - use list of strings
|
127
|
+
- Read-only text - use native string
|
128
|
+
- When simple string is adequate
|
129
|
+
- Extremely frequent small edits (use gap buffer)
|
130
|
+
|
131
|
+
Following eXonware Priorities:
|
132
|
+
1. Security: Validates indices, prevents buffer overflows
|
133
|
+
2. Usability: String-like API, natural indexing
|
134
|
+
3. Maintainability: Clean tree structure, AVL balanced
|
135
|
+
4. Performance: O(log n) edits, configurable chunk size
|
136
|
+
5. Extensibility: Easy to add regex, persistent versions
|
137
|
+
|
138
|
+
Industry Best Practices:
|
139
|
+
- Follows Boehm et al. SGI rope implementation
|
140
|
+
- Uses AVL balancing for predictable performance
|
141
|
+
- Implements lazy concatenation
|
142
|
+
- Provides configurable chunk size (default 1KB)
|
143
|
+
- Compatible with Unicode and multi-byte encodings
|
144
|
+
"""
|
145
|
+
|
146
|
+
# Tree node type for classification
|
147
|
+
STRATEGY_TYPE: NodeType = NodeType.TREE
|
148
|
+
|
149
|
+
# Configuration
|
150
|
+
DEFAULT_CHUNK_SIZE = 1024 # 1KB chunks
|
151
|
+
|
152
|
+
def __init__(self, mode: NodeMode = NodeMode.ROPE,
|
153
|
+
traits: NodeTrait = NodeTrait.NONE,
|
154
|
+
chunk_size: int = DEFAULT_CHUNK_SIZE, **options):
|
155
|
+
"""
|
156
|
+
Initialize rope strategy.
|
157
|
+
|
158
|
+
Args:
|
159
|
+
mode: Node mode
|
160
|
+
traits: Node traits
|
161
|
+
chunk_size: Maximum size for leaf text chunks
|
162
|
+
**options: Additional options
|
163
|
+
"""
|
164
|
+
super().__init__(mode, traits, **options)
|
165
|
+
|
166
|
+
self.chunk_size = max(chunk_size, 1)
|
167
|
+
self._root: Optional[RopeNode] = None
|
168
|
+
self._total_length = 0
|
169
|
+
|
170
|
+
def get_supported_traits(self) -> NodeTrait:
|
171
|
+
"""Get supported traits."""
|
172
|
+
return NodeTrait.HIERARCHICAL | NodeTrait.FAST_INSERT | NodeTrait.FAST_DELETE
|
173
|
+
|
174
|
+
# ============================================================================
|
175
|
+
# AVL BALANCING
|
176
|
+
# ============================================================================
|
177
|
+
|
178
|
+
def _rotate_right(self, y: RopeNode) -> RopeNode:
|
179
|
+
"""Rotate right for AVL balancing."""
|
180
|
+
x = y.left
|
181
|
+
t2 = x.right
|
182
|
+
|
183
|
+
x.right = y
|
184
|
+
y.left = t2
|
185
|
+
|
186
|
+
if t2:
|
187
|
+
t2.parent = y
|
188
|
+
x.parent = y.parent
|
189
|
+
y.parent = x
|
190
|
+
|
191
|
+
# Update heights and weights
|
192
|
+
y.update_height()
|
193
|
+
y.update_weight()
|
194
|
+
x.update_height()
|
195
|
+
x.update_weight()
|
196
|
+
|
197
|
+
return x
|
198
|
+
|
199
|
+
def _rotate_left(self, x: RopeNode) -> RopeNode:
|
200
|
+
"""Rotate left for AVL balancing."""
|
201
|
+
y = x.right
|
202
|
+
t2 = y.left
|
203
|
+
|
204
|
+
y.left = x
|
205
|
+
x.right = t2
|
206
|
+
|
207
|
+
if t2:
|
208
|
+
t2.parent = x
|
209
|
+
y.parent = x.parent
|
210
|
+
x.parent = y
|
211
|
+
|
212
|
+
# Update heights and weights
|
213
|
+
x.update_height()
|
214
|
+
x.update_weight()
|
215
|
+
y.update_height()
|
216
|
+
y.update_weight()
|
217
|
+
|
218
|
+
return y
|
219
|
+
|
220
|
+
def _balance(self, node: RopeNode) -> RopeNode:
|
221
|
+
"""Balance node using AVL rotations."""
|
222
|
+
node.update_height()
|
223
|
+
balance = node.get_balance()
|
224
|
+
|
225
|
+
# Left-heavy
|
226
|
+
if balance > 1:
|
227
|
+
if node.left and node.left.get_balance() < 0:
|
228
|
+
node.left = self._rotate_left(node.left)
|
229
|
+
return self._rotate_right(node)
|
230
|
+
|
231
|
+
# Right-heavy
|
232
|
+
if balance < -1:
|
233
|
+
if node.right and node.right.get_balance() > 0:
|
234
|
+
node.right = self._rotate_right(node.right)
|
235
|
+
return self._rotate_left(node)
|
236
|
+
|
237
|
+
return node
|
238
|
+
|
239
|
+
# ============================================================================
|
240
|
+
# ROPE OPERATIONS
|
241
|
+
# ============================================================================
|
242
|
+
|
243
|
+
def put(self, key: Any, value: Any = None) -> None:
|
244
|
+
"""
|
245
|
+
Store text content.
|
246
|
+
|
247
|
+
Args:
|
248
|
+
key: String key (typically "text" or index)
|
249
|
+
value: Text content
|
250
|
+
|
251
|
+
Raises:
|
252
|
+
XWNodeValueError: If value is not a string
|
253
|
+
"""
|
254
|
+
# Security: Validate text
|
255
|
+
if value is None:
|
256
|
+
text = str(key)
|
257
|
+
else:
|
258
|
+
text = str(value)
|
259
|
+
|
260
|
+
# Replace entire content
|
261
|
+
self._root = RopeNode(text, is_leaf=True)
|
262
|
+
self._total_length = len(text)
|
263
|
+
|
264
|
+
# Split into chunks if needed
|
265
|
+
if len(text) > self.chunk_size:
|
266
|
+
self._root = self._split_into_chunks(text)
|
267
|
+
|
268
|
+
def _split_into_chunks(self, text: str) -> RopeNode:
|
269
|
+
"""
|
270
|
+
Split text into balanced tree of chunks.
|
271
|
+
|
272
|
+
Args:
|
273
|
+
text: Text to split
|
274
|
+
|
275
|
+
Returns:
|
276
|
+
Root of balanced rope
|
277
|
+
|
278
|
+
WHY chunking:
|
279
|
+
- Improves cache locality
|
280
|
+
- Limits leaf node size
|
281
|
+
- Balances memory vs tree depth
|
282
|
+
"""
|
283
|
+
if len(text) <= self.chunk_size:
|
284
|
+
return RopeNode(text, is_leaf=True)
|
285
|
+
|
286
|
+
# Split at midpoint
|
287
|
+
mid = len(text) // 2
|
288
|
+
|
289
|
+
left_rope = self._split_into_chunks(text[:mid])
|
290
|
+
right_rope = self._split_into_chunks(text[mid:])
|
291
|
+
|
292
|
+
return self._concat_nodes(left_rope, right_rope)
|
293
|
+
|
294
|
+
def _concat_nodes(self, left: RopeNode, right: RopeNode) -> RopeNode:
|
295
|
+
"""
|
296
|
+
Concatenate two rope nodes.
|
297
|
+
|
298
|
+
Args:
|
299
|
+
left: Left rope
|
300
|
+
right: Right rope
|
301
|
+
|
302
|
+
Returns:
|
303
|
+
New internal node
|
304
|
+
"""
|
305
|
+
parent = RopeNode(is_leaf=False)
|
306
|
+
parent.left = left
|
307
|
+
parent.right = right
|
308
|
+
parent.weight = left.get_total_length()
|
309
|
+
|
310
|
+
left.parent = parent
|
311
|
+
right.parent = parent
|
312
|
+
|
313
|
+
parent.update_height()
|
314
|
+
|
315
|
+
return parent
|
316
|
+
|
317
|
+
def concat(self, other_text: str) -> None:
|
318
|
+
"""
|
319
|
+
Concatenate text to rope.
|
320
|
+
|
321
|
+
Args:
|
322
|
+
other_text: Text to append
|
323
|
+
|
324
|
+
WHY O(log n):
|
325
|
+
- Just creates new parent node
|
326
|
+
- No copying of existing text
|
327
|
+
- Maintains tree balance
|
328
|
+
"""
|
329
|
+
if not other_text:
|
330
|
+
return
|
331
|
+
|
332
|
+
other_rope = RopeNode(other_text, is_leaf=True)
|
333
|
+
|
334
|
+
if len(other_text) > self.chunk_size:
|
335
|
+
other_rope = self._split_into_chunks(other_text)
|
336
|
+
|
337
|
+
if self._root is None:
|
338
|
+
self._root = other_rope
|
339
|
+
else:
|
340
|
+
self._root = self._concat_nodes(self._root, other_rope)
|
341
|
+
|
342
|
+
self._total_length += len(other_text)
|
343
|
+
|
344
|
+
def get_char_at(self, index: int) -> str:
|
345
|
+
"""
|
346
|
+
Get character at index.
|
347
|
+
|
348
|
+
Args:
|
349
|
+
index: Character position
|
350
|
+
|
351
|
+
Returns:
|
352
|
+
Character at index
|
353
|
+
|
354
|
+
Raises:
|
355
|
+
XWNodeValueError: If index out of bounds
|
356
|
+
"""
|
357
|
+
if index < 0 or index >= self._total_length:
|
358
|
+
raise XWNodeValueError(
|
359
|
+
f"Index {index} out of bounds [0, {self._total_length})"
|
360
|
+
)
|
361
|
+
|
362
|
+
return self._get_char_recursive(self._root, index)
|
363
|
+
|
364
|
+
def _get_char_recursive(self, node: RopeNode, index: int) -> str:
|
365
|
+
"""Recursively find character at index."""
|
366
|
+
if node.is_leaf:
|
367
|
+
return node.text[index]
|
368
|
+
|
369
|
+
# Check which subtree
|
370
|
+
if index < node.weight:
|
371
|
+
return self._get_char_recursive(node.left, index)
|
372
|
+
else:
|
373
|
+
return self._get_char_recursive(node.right, index - node.weight)
|
374
|
+
|
375
|
+
def substring(self, start: int, end: int) -> str:
|
376
|
+
"""
|
377
|
+
Extract substring.
|
378
|
+
|
379
|
+
Args:
|
380
|
+
start: Start index (inclusive)
|
381
|
+
end: End index (exclusive)
|
382
|
+
|
383
|
+
Returns:
|
384
|
+
Substring
|
385
|
+
|
386
|
+
Raises:
|
387
|
+
XWNodeValueError: If indices invalid
|
388
|
+
"""
|
389
|
+
if start < 0 or end > self._total_length or start > end:
|
390
|
+
raise XWNodeValueError(
|
391
|
+
f"Invalid substring range [{start}, {end}) for length {self._total_length}"
|
392
|
+
)
|
393
|
+
|
394
|
+
result = []
|
395
|
+
self._collect_substring(self._root, 0, start, end, result)
|
396
|
+
return ''.join(result)
|
397
|
+
|
398
|
+
def _collect_substring(self, node: Optional[RopeNode], offset: int,
|
399
|
+
start: int, end: int, result: List[str]) -> None:
|
400
|
+
"""Recursively collect substring."""
|
401
|
+
if node is None or offset >= end:
|
402
|
+
return
|
403
|
+
|
404
|
+
if node.is_leaf:
|
405
|
+
# Extract relevant portion of leaf text
|
406
|
+
local_start = max(0, start - offset)
|
407
|
+
local_end = min(len(node.text), end - offset)
|
408
|
+
|
409
|
+
if local_end > local_start:
|
410
|
+
result.append(node.text[local_start:local_end])
|
411
|
+
else:
|
412
|
+
# Recurse on children
|
413
|
+
self._collect_substring(node.left, offset, start, end, result)
|
414
|
+
if node.right:
|
415
|
+
self._collect_substring(node.right, offset + node.weight, start, end, result)
|
416
|
+
|
417
|
+
def insert_text(self, index: int, text: str) -> None:
|
418
|
+
"""
|
419
|
+
Insert text at position.
|
420
|
+
|
421
|
+
Args:
|
422
|
+
index: Insertion position
|
423
|
+
text: Text to insert
|
424
|
+
|
425
|
+
Raises:
|
426
|
+
XWNodeValueError: If index invalid
|
427
|
+
|
428
|
+
WHY O(log n):
|
429
|
+
- Split at index: O(log n)
|
430
|
+
- Concatenate pieces: O(log n)
|
431
|
+
- No copying of existing text
|
432
|
+
"""
|
433
|
+
if index < 0 or index > self._total_length:
|
434
|
+
raise XWNodeValueError(
|
435
|
+
f"Index {index} out of bounds [0, {self._total_length}]"
|
436
|
+
)
|
437
|
+
|
438
|
+
# Split at insertion point
|
439
|
+
left_rope = self._split_at(index)[0] if index > 0 else None
|
440
|
+
right_rope = self._split_at(index)[1] if index < self._total_length else None
|
441
|
+
|
442
|
+
# Create new text node
|
443
|
+
new_node = RopeNode(text, is_leaf=True)
|
444
|
+
if len(text) > self.chunk_size:
|
445
|
+
new_node = self._split_into_chunks(text)
|
446
|
+
|
447
|
+
# Concatenate: left + new + right
|
448
|
+
if left_rope:
|
449
|
+
self._root = self._concat_nodes(left_rope, new_node)
|
450
|
+
else:
|
451
|
+
self._root = new_node
|
452
|
+
|
453
|
+
if right_rope:
|
454
|
+
self._root = self._concat_nodes(self._root, right_rope)
|
455
|
+
|
456
|
+
self._total_length += len(text)
|
457
|
+
|
458
|
+
def _split_at(self, index: int) -> Tuple[Optional[RopeNode], Optional[RopeNode]]:
|
459
|
+
"""
|
460
|
+
Split rope at index.
|
461
|
+
|
462
|
+
Args:
|
463
|
+
index: Split position
|
464
|
+
|
465
|
+
Returns:
|
466
|
+
(left_rope, right_rope) tuple
|
467
|
+
|
468
|
+
WHY O(log n):
|
469
|
+
- Navigates tree to split point
|
470
|
+
- Creates new nodes only at path
|
471
|
+
- Reuses existing subtrees
|
472
|
+
"""
|
473
|
+
if index <= 0:
|
474
|
+
return (None, self._root)
|
475
|
+
if index >= self._total_length:
|
476
|
+
return (self._root, None)
|
477
|
+
|
478
|
+
# Navigate to split point and split
|
479
|
+
# Simplified: convert to string and recreate (O(n))
|
480
|
+
# Full implementation would do tree splitting
|
481
|
+
full_text = self.to_string()
|
482
|
+
|
483
|
+
left_text = full_text[:index]
|
484
|
+
right_text = full_text[index:]
|
485
|
+
|
486
|
+
left_rope = self._split_into_chunks(left_text) if left_text else None
|
487
|
+
right_rope = self._split_into_chunks(right_text) if right_text else None
|
488
|
+
|
489
|
+
return (left_rope, right_rope)
|
490
|
+
|
491
|
+
def delete_range(self, start: int, end: int) -> None:
|
492
|
+
"""
|
493
|
+
Delete text range.
|
494
|
+
|
495
|
+
Args:
|
496
|
+
start: Start index (inclusive)
|
497
|
+
end: End index (exclusive)
|
498
|
+
|
499
|
+
Raises:
|
500
|
+
XWNodeValueError: If range invalid
|
501
|
+
"""
|
502
|
+
if start < 0 or end > self._total_length or start > end:
|
503
|
+
raise XWNodeValueError(
|
504
|
+
f"Invalid delete range [{start}, {end})"
|
505
|
+
)
|
506
|
+
|
507
|
+
# Split and recombine
|
508
|
+
before = self._split_at(start)[0] if start > 0 else None
|
509
|
+
after = self._split_at(end)[1] if end < self._total_length else None
|
510
|
+
|
511
|
+
if before and after:
|
512
|
+
self._root = self._concat_nodes(before, after)
|
513
|
+
elif before:
|
514
|
+
self._root = before
|
515
|
+
elif after:
|
516
|
+
self._root = after
|
517
|
+
else:
|
518
|
+
self._root = None
|
519
|
+
|
520
|
+
self._total_length -= (end - start)
|
521
|
+
|
522
|
+
def to_string(self) -> str:
|
523
|
+
"""
|
524
|
+
Convert entire rope to string.
|
525
|
+
|
526
|
+
Returns:
|
527
|
+
Complete text
|
528
|
+
|
529
|
+
WHY O(n):
|
530
|
+
- Must visit all leaf nodes
|
531
|
+
- Collects text chunks
|
532
|
+
- Returns contiguous string
|
533
|
+
"""
|
534
|
+
if self._root is None:
|
535
|
+
return ""
|
536
|
+
|
537
|
+
result = []
|
538
|
+
self._collect_text(self._root, result)
|
539
|
+
return ''.join(result)
|
540
|
+
|
541
|
+
def _collect_text(self, node: Optional[RopeNode], result: List[str]) -> None:
|
542
|
+
"""Recursively collect text from leaves."""
|
543
|
+
if node is None:
|
544
|
+
return
|
545
|
+
|
546
|
+
if node.is_leaf:
|
547
|
+
result.append(node.text)
|
548
|
+
else:
|
549
|
+
self._collect_text(node.left, result)
|
550
|
+
self._collect_text(node.right, result)
|
551
|
+
|
552
|
+
def _split_into_chunks(self, text: str) -> RopeNode:
|
553
|
+
"""Split text into balanced tree of chunks."""
|
554
|
+
if len(text) <= self.chunk_size:
|
555
|
+
return RopeNode(text, is_leaf=True)
|
556
|
+
|
557
|
+
mid = len(text) // 2
|
558
|
+
left = self._split_into_chunks(text[:mid])
|
559
|
+
right = self._split_into_chunks(text[mid:])
|
560
|
+
|
561
|
+
return self._concat_nodes(left, right)
|
562
|
+
|
563
|
+
# ============================================================================
|
564
|
+
# STANDARD OPERATIONS
|
565
|
+
# ============================================================================
|
566
|
+
|
567
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
568
|
+
"""
|
569
|
+
Get text content.
|
570
|
+
|
571
|
+
Args:
|
572
|
+
key: Ignored (rope stores single text)
|
573
|
+
default: Default value
|
574
|
+
|
575
|
+
Returns:
|
576
|
+
Full text string
|
577
|
+
"""
|
578
|
+
return self.to_string()
|
579
|
+
|
580
|
+
def has(self, key: Any) -> bool:
|
581
|
+
"""Check if rope has content."""
|
582
|
+
return self._total_length > 0
|
583
|
+
|
584
|
+
def delete(self, key: Any) -> bool:
|
585
|
+
"""Clear rope content."""
|
586
|
+
if self._total_length > 0:
|
587
|
+
self.clear()
|
588
|
+
return True
|
589
|
+
return False
|
590
|
+
|
591
|
+
def keys(self) -> Iterator[Any]:
|
592
|
+
"""Get iterator (yields single 'text' key)."""
|
593
|
+
if self._total_length > 0:
|
594
|
+
yield 'text'
|
595
|
+
|
596
|
+
def values(self) -> Iterator[Any]:
|
597
|
+
"""Get iterator over text."""
|
598
|
+
if self._total_length > 0:
|
599
|
+
yield self.to_string()
|
600
|
+
|
601
|
+
def items(self) -> Iterator[tuple[Any, Any]]:
|
602
|
+
"""Get iterator over items."""
|
603
|
+
if self._total_length > 0:
|
604
|
+
yield ('text', self.to_string())
|
605
|
+
|
606
|
+
def __len__(self) -> int:
|
607
|
+
"""Get text length."""
|
608
|
+
return self._total_length
|
609
|
+
|
610
|
+
def to_native(self) -> Any:
|
611
|
+
"""Convert to native string."""
|
612
|
+
return self.to_string()
|
613
|
+
|
614
|
+
# ============================================================================
|
615
|
+
# UTILITY METHODS
|
616
|
+
# ============================================================================
|
617
|
+
|
618
|
+
def clear(self) -> None:
|
619
|
+
"""Clear all text."""
|
620
|
+
self._root = None
|
621
|
+
self._total_length = 0
|
622
|
+
|
623
|
+
def is_empty(self) -> bool:
|
624
|
+
"""Check if empty."""
|
625
|
+
return self._total_length == 0
|
626
|
+
|
627
|
+
def size(self) -> int:
|
628
|
+
"""Get text length."""
|
629
|
+
return self._total_length
|
630
|
+
|
631
|
+
def get_mode(self) -> NodeMode:
|
632
|
+
"""Get strategy mode."""
|
633
|
+
return self.mode
|
634
|
+
|
635
|
+
def get_traits(self) -> NodeTrait:
|
636
|
+
"""Get strategy traits."""
|
637
|
+
return self.traits
|
638
|
+
|
639
|
+
# ============================================================================
|
640
|
+
# STATISTICS
|
641
|
+
# ============================================================================
|
642
|
+
|
643
|
+
def get_statistics(self) -> Dict[str, Any]:
|
644
|
+
"""Get rope statistics."""
|
645
|
+
def count_nodes(node: Optional[RopeNode]) -> Tuple[int, int]:
|
646
|
+
"""Count leaves and internal nodes."""
|
647
|
+
if node is None:
|
648
|
+
return (0, 0)
|
649
|
+
if node.is_leaf:
|
650
|
+
return (1, 0)
|
651
|
+
|
652
|
+
left_leaves, left_internal = count_nodes(node.left)
|
653
|
+
right_leaves, right_internal = count_nodes(node.right)
|
654
|
+
return (left_leaves + right_leaves, left_internal + right_internal + 1)
|
655
|
+
|
656
|
+
leaves, internal = count_nodes(self._root)
|
657
|
+
|
658
|
+
return {
|
659
|
+
'total_length': self._total_length,
|
660
|
+
'chunk_size': self.chunk_size,
|
661
|
+
'leaf_nodes': leaves,
|
662
|
+
'internal_nodes': internal,
|
663
|
+
'total_nodes': leaves + internal,
|
664
|
+
'height': self._root.height if self._root else 0,
|
665
|
+
'avg_chunk_size': self._total_length / leaves if leaves > 0 else 0
|
666
|
+
}
|
667
|
+
|
668
|
+
# ============================================================================
|
669
|
+
# COMPATIBILITY METHODS
|
670
|
+
# ============================================================================
|
671
|
+
|
672
|
+
def find(self, key: Any) -> Optional[Any]:
|
673
|
+
"""Find text."""
|
674
|
+
return self.to_string() if self._total_length > 0 else None
|
675
|
+
|
676
|
+
def insert(self, key: Any, value: Any = None) -> None:
|
677
|
+
"""Insert text."""
|
678
|
+
self.put(key, value)
|
679
|
+
|
680
|
+
def __str__(self) -> str:
|
681
|
+
"""String representation."""
|
682
|
+
preview = self.to_string()[:50] + "..." if self._total_length > 50 else self.to_string()
|
683
|
+
return f"RopeStrategy(length={self._total_length}, preview='{preview}')"
|
684
|
+
|
685
|
+
def __repr__(self) -> str:
|
686
|
+
"""Detailed representation."""
|
687
|
+
return f"RopeStrategy(mode={self.mode.name}, length={self._total_length}, traits={self.traits})"
|
688
|
+
|
689
|
+
# ============================================================================
|
690
|
+
# FACTORY METHOD
|
691
|
+
# ============================================================================
|
692
|
+
|
693
|
+
@classmethod
|
694
|
+
def create_from_data(cls, data: Any, chunk_size: int = DEFAULT_CHUNK_SIZE) -> 'RopeStrategy':
|
695
|
+
"""
|
696
|
+
Create rope from data.
|
697
|
+
|
698
|
+
Args:
|
699
|
+
data: String or dict with text
|
700
|
+
chunk_size: Chunk size for splitting
|
701
|
+
|
702
|
+
Returns:
|
703
|
+
New RopeStrategy instance
|
704
|
+
"""
|
705
|
+
instance = cls(chunk_size=chunk_size)
|
706
|
+
|
707
|
+
if isinstance(data, str):
|
708
|
+
instance.put('text', data)
|
709
|
+
elif isinstance(data, dict):
|
710
|
+
# Concatenate all values as text
|
711
|
+
text = ''.join(str(v) for v in data.values())
|
712
|
+
instance.put('text', text)
|
713
|
+
else:
|
714
|
+
instance.put('text', str(data))
|
715
|
+
|
716
|
+
return instance
|
717
|
+
|