@@ -136,6 +136,16 @@ def add(self, parent, step):
136
136
def require (self , parent , step ):
137
137
return self .node if self .step == step else self .add (parent , step )
138
138
139
+ def merge (self , other , queue ):
140
+ """Moves children from other into this object."""
141
+ if type (other ) == _OneChild and other .step == self .step :
142
+ queue .append ((self .node , other .node ))
143
+ return self
144
+ else :
145
+ children = _Children ((self .step , self .node ))
146
+ children .merge (other , queue )
147
+ return children
148
+
139
149
def delete (self , parent , _step ):
140
150
parent .children = _EMPTY
141
151
@@ -172,6 +182,14 @@ def add(self, _parent, step):
172
182
def require (self , _parent , step ):
173
183
return self .setdefault (step , _Node ())
174
184
185
+ def merge (self , other , queue ):
186
+ """Moves children from other into this object."""
187
+ for step , other_node in other .iteritems ():
188
+ node = self .setdefault (step , other_node )
189
+ if node is not other_node :
190
+ queue .append ((node , other_node ))
191
+ return self
192
+
175
193
def delete (self , parent , step ):
176
194
del self [step ]
177
195
if len (self ) == 1 :
@@ -196,6 +214,24 @@ def __init__(self):
196
214
self .children = _EMPTY
197
215
self .value = _EMPTY
198
216
217
+ def merge (self , other , overwrite ):
218
+ """Move children from other node into this one.
219
+
220
+ Args:
221
+ other: Other node to move children and value from.
222
+ overwrite: Whether to overwrite existing node values.
223
+ """
224
+ queue = [(self , other )]
225
+ while queue :
226
+ lhs , rhs = queue .pop ()
227
+ if lhs .value is _EMPTY or (overwrite and rhs .value is not _EMPTY ):
228
+ lhs .value = rhs .value
229
+ if lhs .children is _EMPTY :
230
+ lhs .children = rhs .children
231
+ elif rhs .children is not _EMPTY :
232
+ lhs .children = lhs .children .merge (rhs .children , queue )
233
+ rhs .children = _EMPTY
234
+
199
235
def iterate (self , path , shallow , iteritems ):
200
236
"""Yields all the nodes with values associated to them in the trie.
201
237
@@ -484,6 +520,55 @@ def update(self, *args, **kwargs): # pylint: disable=arguments-differ
484
520
args = ()
485
521
super (Trie , self ).update (* args , ** kwargs )
486
522
523
+ def merge (self , other , overwrite = False ):
524
+ """Moves nodes from other trie into this one.
525
+
526
+ The merging happens at trie structure level and as such is different
527
+ than iterating over items of one trie and setting them in the other
528
+ trie.
529
+
530
+ The merging may happen between different types of tries resulting in
531
+ different (key, value) pairs in the destination trie compared to the
532
+ source. For example, merging two :class:`pygtrie.StringTrie` objects
533
+ each using different separators will work as if the other trie had
534
+ separator of this trie. Similarly, a :class:`pygtrie.CharTrie` may be
535
+ merged into a :class:`pygtrie.StringTrie` but when keys are read those
536
+ will be joined by the separator. For example:
537
+
538
+ >>> import pygtrie
539
+ >>> st = pygtrie.StringTrie(separator='.')
540
+ >>> st.merge(pygtrie.StringTrie({'foo/bar': 42}))
541
+ >>> list(st.items())
542
+ [('foo.bar', 42)]
543
+ >>> st.merge(pygtrie.CharTrie({'baz': 24}))
544
+ >>> sorted(st.items())
545
+ [('b.a.z', 24), ('foo.bar', 42)]
546
+
547
+ Not all tries can be merged into other tries. For example,
548
+ a :class:`pygtrie.StringTrie` may not be merged into
549
+ a :class:`pygtrie.CharTrie` because the latter imposes a requirement for
550
+ each component in the key to be exactly one character while in the
551
+ former components may be arbitrary length.
552
+
553
+ Note that the other trie is cleared and any references or iterators over
554
+ it are invalidated. To preserve other’s value it needs to be copied
555
+ first.
556
+
557
+ Args:
558
+ other: Other trie to move nodes from.
559
+ overwrite: Whether to overwrite existing values in this trie.
560
+ """
561
+ if isinstance (self , type (other )):
562
+ self ._merge_impl (self , other , overwrite = overwrite )
563
+ else :
564
+ other ._merge_impl (self , other , overwrite = overwrite ) # pylint: disable=protected-access
565
+ other .clear ()
566
+
567
+ @classmethod
568
+ def _merge_impl (cls , dst , src , overwrite ):
569
+ # pylint: disable=protected-access
570
+ dst ._root .merge (src ._root , overwrite = overwrite )
571
+
487
572
def copy (self , __make_copy = lambda x : x ):
488
573
"""Returns a shallow copy of the object."""
489
574
# pylint: disable=protected-access
@@ -1637,6 +1722,13 @@ def fromkeys(cls, keys, value=None, separator='/'): # pylint: disable=arguments
1637
1722
trie [key ] = value
1638
1723
return trie
1639
1724
1725
+ @classmethod
1726
+ def _merge_impl (cls , dst , src , overwrite ):
1727
+ if not isinstance (dst , StringTrie ):
1728
+ raise TypeError ('%s cannot be merged into a %s' % (
1729
+ type (src ).__name__ , type (dst ).__name__ ))
1730
+ super (StringTrie , cls )._merge_impl (dst , src , overwrite = overwrite )
1731
+
1640
1732
def __str__ (self ):
1641
1733
if not self :
1642
1734
return '%s(separator=%s)' % (type (self ).__name__ , self ._separator )
0 commit comments