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

1# -*- coding: utf-8 -*- 

2 

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################################################################### 

11 

12class Container(object): 

13 """ 

14 Container populated with data linked to a radic node 

15 """ 

16 

17 def __init__(self, data, tag=None): 

18 self._data = data 

19 self._tag = tag 

20 self._previous = None 

21 self._next = None 

22 

23 def __str__(self): 

24 return ("Container -> data: %s tag: %s" % (self._data, self._tag)) 

25 

26 

27class Node(object): 

28 """ 

29 A radix node 

30 """ 

31 

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 

37 

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)) 

46 

47class RadixTree(object): 

48 """ 

49 A radix tree 

50 """ 

51 

52 # max_key_len = 0 

53 

54 def __init__(self): 

55 self._tree = None 

56 

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) 

66 

67 if type(key) != str: 

68 key = bin(key).replace('0b', '') 

69 my_logger.debug("Key converted in string key: %s " % key) 

70 

71 if start_node == None: 

72 current = self._tree 

73 else: 

74 current = start_node 

75 

76 my_logger.info("Current node: %s " % current) 

77 

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 

86 

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 # ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ 

104 

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 

109 

110 current._key_size = len(key) 

111 current._next[current._key[tested]] = node1 

112 current._key = key 

113 current._data = cont 

114 

115 my_logger.debug(current) 

116 my_logger.debug(node1) 

117 

118 return cont 

119 

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) 

148 

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 

157 

158 my_logger.debug(current) 

159 my_logger.debug(node1) 

160 my_logger.debug(node2) 

161 

162 return cont 

163 tested += 1 

164 

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 # ┗━━━━━━━━━━━━┛ 

193 

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 

216 

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 """ 

224 

225 my_logger.debug(" RadixTree.get_node() ".center(60, '-')) 

226 

227 if type(key) != str: 

228 key = bin(key).replace('0b', '') 

229 my_logger.debug("Key converted in string key: %s " % key) 

230 

231 my_logger.debug("key: %s" % key) 

232 

233 if start_node == None: 

234 node = self._tree 

235 else: 

236 node = start_node 

237 

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 

258 

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 

270 

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 """ 

279 

280 my_logger.debug(" RadixTree.delete_node() ".center(60, '-')) 

281 

282 if type(key) != str: 

283 key = bin(key).replace('0b', '') 

284 my_logger.debug("Key converted in string key: %s " % key) 

285 

286 my_logger.debug(" Key : %s" % key) 

287 

288 if start_node == None: 

289 node = self._tree 

290 else: 

291 node = start_node 

292 

293 my_logger.info("Current node -> %s" % node) 

294 

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 # ┗━━━━━━━━━━━━┛ 

319 

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) 

324 

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) 

350 

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 

426 

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 

440 

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 """ 

448 

449 my_logger.debug(" RadixTree.dump() ".center(60, '-')) 

450 

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 + " │" 

488 

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]