Coverage for src/radix_tree/radix_tree.py: 100%
238 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-06 10:57 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-06 10:57 +0200
1# -*- coding: utf-8 -*-
3# run tests from Pycharms locally
4#from radix_config import my_logger
6# run tests from external Testpy as distributed package
7from radix_tree.radix_config import my_logger
9class Container(object):
10 """
11 Container populated with data linked to a radic node
12 """
14 def __init__(self, data, tag=None):
15 self._data = data
16 self._tag = tag
17 self._previous = None
18 self._next = None
20 def __str__(self):
21 return ("Container -> data: %s tag: %s" % (self._data, self._tag))
24class Node(object):
25 """
26 A radix node
27 """
29 def __init__(self, key, key_size, cont=None):
30 self._next = {}
31 self._key = key
32 self._key_size = key_size
33 self._data = cont
35 def __str__(self):
36 p = hex(id(self))
37 if self._data:
38 return ("Node %s -> key: %s (%s) key_size: %d _next: %s _data %s" % (
39 p, self._key[0:self._key_size], self._key[self._key_size + 1:], self._key_size, self._next, self._data))
40 else:
41 return ("Node %s -> key: %s (%s) key_size: %d _next: %s" % (
42 p, self._key[0:self._key_size], self._key[self._key_size + 1:], self._key_size, self._next))
44class RadixTree(object):
45 """
46 A radix tree
47 """
49 # max_key_len = 0
51 def __init__(self):
52 self._tree = None
54 def insert_node(self, key, val, start_node=None):
55 """
56 Insert a node in radix tree with a string key
57 :param key: string or int key
58 :param val: data linked to the node
59 :return: new node created
60 """
61 my_logger.debug(" RadixTree.insert_node() ".center(60, '-'))
62 my_logger.debug(" key: %s " % key)
64 if type(key) != str:
65 key = bin(key).replace('0b', '')
66 my_logger.debug("Key converted in string key: %s " % key)
68 if start_node == None:
69 current = self._tree
70 else:
71 current = start_node
73 my_logger.info("Current node: %s " % current)
75 if not current:
76 """ the radix tree is empty """
77 my_logger.debug("Radix tree empty")
78 cont = Container(data=val, tag=0)
79 node = Node(key, len(key), cont)
80 self._tree = node
81 my_logger.debug(node)
82 return cont
84 tested = 0
85 while tested < current._key_size:
86 if tested > len(key) - 1:
87 # Example
88 # key to insert AB
89 # tested = 2
90 # ┏━━━━━━━━━━━━┓B ┏━━━━━━━━━━━━┓
91 # ┃ current ┃->┃ next node ┃
92 # ┃ key=ABAB ┃ ┃ key=ABABB ┃
93 # ┃ len=4 ┃ ┃ len=5 ┃
94 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛
95 # Result :
96 # ┏━━━━━━━━━━━━┓A ┏━━━━━━━━━━━━┓B ┏━━━━━━━━━━━━┓
97 # ┃ current ┃->┃ node1 ┃->┃ next node ┃
98 # ┃ key=AB ┃ ┃ key=ABAB ┃ ┃ key=ABABB ┃
99 # ┃ len=2 ┃ ┃ len=4 ┃ ┃ len=5 ┃
100 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛
102 cont = Container(data=val, tag=0)
103 node1 = Node(current._key, current._key_size, None)
104 node1._next = current._next.copy()
105 node1._data = current._data
107 current._key_size = len(key)
108 current._next[current._key[tested]] = node1
109 current._key = key
110 current._data = cont
112 my_logger.debug(current)
113 my_logger.debug(node1)
115 return cont
117 elif current._key[tested] != key[tested]:
118 """
119 Creation of two new nodes and one container for data
120 """
121 # Example
122 # key to insert AC
123 # tested = 1
124 # ┏━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━┓
125 # ┃ current ┃->┃ next node ┃
126 # ┃ key=ABAB ┃ ┃ key=ABABB ┃
127 # ┃ len=4 ┃ ┃ len=5 ┃
128 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛
129 # Result :
130 # ┏━━━━━━━━━━━━┓B ┏━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━┓
131 # ┃ current ┃->┃ node1 ┃->┃ next node ┃
132 # ┃ key=A ┃ ┃ key=ABAB ┃ ┃ key=ABABB ┃
133 # ┃ len=1 ┃ ┃ len=4 ┃ ┃ len=5 ┃
134 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛
135 # │ C ┏━━━━━━━━━━━━┓
136 # +-------->┃ node2 ┃
137 # ┃ key=AC ┃
138 # ┃ len=2 ┃
139 # ┗━━━━━━━━━━━━┛
140 cont = Container(data=val, tag=0)
141 node1 = Node(current._key, current._key_size, None)
142 node1._next = current._next.copy()
143 node1._data = current._data
144 node2 = Node(key, len(key), cont)
146 current._key_size = tested
147 # for k in current._next:
148 # del current._next[k]
149 current._next = {}
150 current._next[current._key[tested]] = node1
151 current._next[key[tested]] = node2
152 current._key = key[0:tested]
153 current._data = None
155 my_logger.debug(current)
156 my_logger.debug(node1)
157 my_logger.debug(node2)
159 return cont
160 tested += 1
162 if tested == current._key_size:
163 if tested < len(key):
164 """Go to the next node"""
165 if key[tested] in current._next:
166 current = current._next[key[tested]]
167 my_logger.debug("Go to the next node: %s" % current)
168 self.insert_node(key, val, current)
169 else:
170 """Create the new node"""
171 # Example
172 # key to insert ABABA
173 # tested = 4
174 # ┏━━━━━━━━━━━━┓B ┏━━━━━━━━━━━━┓
175 # ┃ current ┃->┃ ┃
176 # ┃ key=ABAB ┃ ┃ key=ABABB ┃
177 # ┃ len=4 ┃ ┃ len=5 ┃
178 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛
179 # Result :
180 # ┏━━━━━━━━━━━━┓B ┏━━━━━━━━━━━━┓
181 # ┃ current ┃->┃ ┃
182 # ┃ key=ABAB ┃ ┃ key=ABABB ┃
183 # ┃ len=4 ┃ ┃ len=5 ┃
184 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛
185 # │ A ┏━━━━━━━━━━━━┓
186 # +-------->┃ node ┃
187 # ┃ key=ABABA ┃
188 # ┃ len=5 ┃
189 # ┗━━━━━━━━━━━━┛
191 cont = Container(data=val, tag=0)
192 node = Node(key, len(key), cont)
193 current._next[key[tested]] = node
194 my_logger.debug("Create the next node: %s" % node)
195 my_logger.debug("Modify the current node: %s" % current)
196 return cont
197 else:
198 """The leaf already exists, we have to update container"""
199 my_logger.debug("The leaf already exists, we have to update container node: %s" % current)
200 current._key = key
201 current._key_size = len(key)
202 cont = current._data
203 if cont:
204 """Update container"""
205 cont._data = val
206 my_logger.debug("Node already exist. Update container: %s" % current)
207 else:
208 """Create container"""
209 my_logger.debug("Node already exist. Create container: %s" % current)
210 cont = Container(data=val, tag=0)
211 current._data = cont
212 return cont
214 def get_node(self, key, start_node=None):
215 """
216 Get node in the radix tree beginning to <start_node> indexed by <key>
217 :param start_node: first node of the radix tree to explore
218 :param key: key to search
219 :return: data linked to the node, if any. None otherwise
220 """
222 my_logger.debug(" RadixTree.get_node() ".center(60, '-'))
224 if type(key) != str:
225 key = bin(key).replace('0b', '')
226 my_logger.debug("Key converted in string key: %s " % key)
228 my_logger.debug("key: %s" % key)
230 if start_node == None:
231 node = self._tree
232 else:
233 node = start_node
235 if node:
236 my_logger.info("Current node: %s" % node)
237 if node._key == key and node._key_size == len(node._key):
238 my_logger.info("Node found -> key: %s key_size: %d data: %s" % (node._key, node._key_size, node._data))
239 return node._data
240 else:
241 tested = 0
242 while tested < node._key_size:
243 if tested > len(key) - 1:
244 my_logger.warning("Node not found -> key: %s" % key)
245 return None
246 else:
247 my_logger.debug("Searching node... current node: %s " % node)
248 my_logger.debug(
249 "Searching node... index: %d tested: %s - %s" % (tested, node._key[tested], key[tested]))
250 if node._key[tested] != key[tested]:
251 my_logger.warning("Node not found -> key: %s" % key)
252 return None
253 else:
254 tested += 1
256 if tested == node._key_size:
257 if key[tested] in node._next:
258 node = node._next[key[tested]]
259 my_logger.info("2 Go to the next node -> next: %s node: %s" % (node._key[tested], node))
260 return self.get_node(key, node)
261 else:
262 my_logger.warning("Node not found -> key: %s" % key)
263 return None
264 else:
265 my_logger.info("Radix tree empty")
266 return None
268 def delete_node(self, key, start_node=None, prev_node=None):
269 """
270 Delete node in radix tree
271 :param key: key of the node to delete
272 :param start_node: first node of the radix tree or None to start from the beginning of radix tree
273 :param prev_node: previous node
274 :return: True if deleted. False otherwise.
275 """
277 my_logger.debug(" RadixTree.delete_node() ".center(60, '-'))
279 if type(key) != str:
280 key = bin(key).replace('0b', '')
281 my_logger.debug("Key converted in string key: %s " % key)
283 my_logger.debug(" Key : %s" % key)
285 if start_node == None:
286 node = self._tree
287 else:
288 node = start_node
290 my_logger.info("Current node -> %s" % node)
292 if node:
293 if node._key == key:
294 # Node to delete found
295 my_logger.debug("Node to delete found -> %s" % node)
296 if len(node._next) == 0:
297 if prev_node == None:
298 my_logger.debug("First node of tree deleted -> Radix tree empty")
299 del self._tree
300 self._tree = None
301 return True
302 else:
303 # Example
304 # Delete 'ABA'
305 # ┏━━━━━━━━━━━━┓A ┏━━━━━━━━━┓
306 # ┃ prev_node ┃->┃ node ┃
307 # ┃ key=AB ┃ ┃ key=ABA ┃
308 # ┃ len=2 ┃ ┃ len=3 ┃
309 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━┛
310 # Result :
311 # ┏━━━━━━━━━━━━┓
312 # ┃ prev_node ┃
313 # ┃ key=AB ┃
314 # ┃ len=2 ┃
315 # ┗━━━━━━━━━━━━┛
317 my_logger.debug("Node deleted %s " % node)
318 my_logger.debug("Previous link deleted %s " % prev_node)
319 del prev_node._next[node._key[prev_node._key_size]]
320 my_logger.debug("1. Previous node updated %s " % prev_node)
322 if len(prev_node._next) == 1 and prev_node._data == None:
323 # Example
324 # Delete 'ABB'
325 # ┏━━━━━━━━━━━━┓A ┏━━━━━━━━━┓
326 # ┃ prev_node ┃->┃ node ┃
327 # ┃ key=AB ┃ ┃ key=ABA ┃
328 # ┃ len=2 ┃ ┃ len=3 ┃
329 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━┛
330 # │ B ┏━━━━━━━━━━━━┓
331 # +-------->┃ other node ┃
332 # ┃ key=ABB ┃
333 # ┃ len=3 ┃
334 # ┗━━━━━━━━━━━━┛
335 # Result :
336 # ┏━━━━━━━━━━━━┓
337 # ┃ prev_node ┃
338 # ┃ key=ABA ┃
339 # ┃ len=3 ┃
340 # ┗━━━━━━━━━━━━┛
341 for k in prev_node._next:
342 prev_node._key = prev_node._next[k]._key
343 prev_node._key_size = prev_node._next[k]._key_size
344 prev_node._data = prev_node._next[k]._data
345 prev_node._next = prev_node._next[k]._next
346 my_logger.debug("2. Previous node updated %s " % prev_node)
348 del node._data
349 del node
350 return True
351 else:
352 if len(node._next) == 1:
353 # Example
354 # Delete 'ABA'
355 # ┏━━━━━━━━━━━━┓A ┏━━━━━━━━━━━━┓B ┏━━━━━━━━━━━━┓
356 # ┃ prev_node ┃->┃ node ┃->┃ next_node ┃
357 # ┃ key=AB ┃ ┃ key=ABA ┃ ┃ key=ABAB ┃
358 # ┃ len=2 ┃ ┃ len=3 ┃ ┃ len=4 ┃
359 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛
360 # Result :
361 # ┏━━━━━━━━━━━━┓A ┏━━━━━━━━━━━━┓
362 # ┃ prev_node ┃->┃ node ┃
363 # ┃ key=AB ┃ ┃ key=ABAB ┃
364 # ┃ len=2 ┃ ┃ len=4 ┃
365 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛
366 for ke in node._next:
367 next_node = node._next[ke]
368 my_logger.info("Node to update %s " % node)
369 node._key = next_node._key
370 node._key_size = next_node._key_size
371 node._data = next_node._data
372 node._next = next_node._next.copy()
373 my_logger.info("Node updated %s " % node)
374 my_logger.info("Node deleted %s " % next_node)
375 del next_node._data
376 del next_node
377 return True
378 else:
379 # Example
380 # Delete 'ABA'
381 # ┏━━━━━━━━━━━━┓A ┏━━━━━━━━━━━━┓B ┏━━━━━━━━━━━━┓
382 # ┃ prev_node ┃->┃ node ┃->┃ next_node1 ┃
383 # ┃ key=AB ┃ ┃ key=ABA ┃ ┃ key=ABAB ┃
384 # ┃ len=2 ┃ ┃ len=3 ┃ ┃ len=4 ┃
385 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛
386 # │ C ┏━━━━━━━━━━━━┓
387 # +-------->┃ next_nodei ┃
388 # ┃ key=ABAC ┃
389 # ┃ len=4 ┃
390 # ┗━━━━━━━━━━━━┛
391 # Result :
392 # ┏━━━━━━━━━━━━┓A ┏━━━━━━━━━━━━┓B ┏━━━━━━━━━━━━┓
393 # ┃ prev_node ┃->┃ node ┃->┃ next_node1 ┃
394 # ┃ key=AB ┃ ┃ key=ABA ┃ ┃ key=ABAB ┃
395 # ┃ len=2 ┃ ┃ len=3 ┃ ┃ len=4 ┃
396 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛
397 # │ C ┏━━━━━━━━━━━━┓
398 # +-------->┃ next_nodei ┃
399 # ┃ key=ABAC ┃
400 # ┃ len=4 ┃
401 # ┗━━━━━━━━━━━━┛
402 # In this case, just delete data linked to the node
403 del node._data
404 node._data = None
405 my_logger.info("Just delete data linked to the node %s " % node)
406 return True
407 else:
408 # Other node case
409 tested = 0
410 while tested < node._key_size:
411 if tested > len(key) - 1:
412 my_logger.warning("Node not found -> key: %s" % key)
413 return False
414 else:
415 my_logger.debug("Searching node... current node: %s " % node)
416 my_logger.debug(
417 "Searching node... index: %d tested: %s - %s" % (tested, node._key[tested], key[tested]))
418 if node._key[tested] != key[tested]:
419 my_logger.warning("Node not found -> key: %s" % key)
420 return False
421 else:
422 tested += 1
424 if tested == node._key_size:
425 if key[tested] in node._next:
426 prev_node = node
427 node = node._next[key[tested]]
428 my_logger.info("Go to the next node -> next: %s node: %s" % (node._key[tested], node))
429 ret = self.delete_node(key, node, prev_node)
430 return ret
431 else:
432 my_logger.warning("Node not found -> key: %s" % key)
433 return False
434 else:
435 my_logger.info("Radix tree empty")
436 return False
438 def dump(self, node=None, st_next_line=''):
439 """
440 Display a radix node
441 :param node: first node of the radix tree. If node = None dump the entire radix tree
442 :param st_next_line: start of next line to display
443 :return: None
444 """
446 my_logger.debug(" RadixTree.dump() ".center(60, '-'))
448 if not node:
449 """Dump the entire radix tree"""
450 node = self._tree
451 if not node:
452 print("Radix tree empty")
453 return
454 if node._data:
455 line = "■"
456 else:
457 line = "□"
458 line += " key: %s key_len: %d next: %d" % (node._key, node._key_size, len(node._next))
459 if node._data:
460 line += " data: %s" % node._data
461 print(line)
462 cpt = len(node._next) - 1
463 st_next_line = "│" * cpt
464 for item in node._next:
465 self.dump(node._next[item], st_next_line)
466 cpt -= 1
467 st_next_line = st_next_line[0:cpt]
468 else:
469 """Intermediate node"""
470 line = st_next_line
471 if node._data:
472 line += "└■"
473 else:
474 line += "└□"
475 line += " key: %s key_len: %d next: %d" % (node._key, node._key_size, len(node._next))
476 if node._data:
477 line += " data: %s" % node._data
478 print(line)
479 cpt = len(node._next) - 1
480 if cpt > 1:
481 st_next_line = st_next_line + " │" + "│" * (cpt - 1)
482 if cpt == 1:
483 my_logger.debug("node with only one son: %s" % node)
484 st_next_line = st_next_line + " │"
486 for item in node._next:
487 self.dump(node._next[item], st_next_line)
488 l = len(st_next_line) - 1
489 st_next_line = st_next_line[0:l]