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