Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/tree/coding.py: 21%

78 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-20 07:00 +0000

1"""Functions for encoding and decoding trees. 

2 

3Since a tree is a highly restricted form of graph, it can be represented 

4concisely in several ways. This module includes functions for encoding 

5and decoding trees in the form of nested tuples and Prüfer 

6sequences. The former requires a rooted tree, whereas the latter can be 

7applied to unrooted trees. Furthermore, there is a bijection from Prüfer 

8sequences to labeled trees. 

9 

10""" 

11from collections import Counter 

12from itertools import chain 

13 

14import networkx as nx 

15from networkx.utils import not_implemented_for 

16 

17__all__ = [ 

18 "from_nested_tuple", 

19 "from_prufer_sequence", 

20 "NotATree", 

21 "to_nested_tuple", 

22 "to_prufer_sequence", 

23] 

24 

25 

26class NotATree(nx.NetworkXException): 

27 """Raised when a function expects a tree (that is, a connected 

28 undirected graph with no cycles) but gets a non-tree graph as input 

29 instead. 

30 

31 """ 

32 

33 

34@not_implemented_for("directed") 

35@nx._dispatch(graphs="T") 

36def to_nested_tuple(T, root, canonical_form=False): 

37 """Returns a nested tuple representation of the given tree. 

38 

39 The nested tuple representation of a tree is defined 

40 recursively. The tree with one node and no edges is represented by 

41 the empty tuple, ``()``. A tree with ``k`` subtrees is represented 

42 by a tuple of length ``k`` in which each element is the nested tuple 

43 representation of a subtree. 

44 

45 Parameters 

46 ---------- 

47 T : NetworkX graph 

48 An undirected graph object representing a tree. 

49 

50 root : node 

51 The node in ``T`` to interpret as the root of the tree. 

52 

53 canonical_form : bool 

54 If ``True``, each tuple is sorted so that the function returns 

55 a canonical form for rooted trees. This means "lighter" subtrees 

56 will appear as nested tuples before "heavier" subtrees. In this 

57 way, each isomorphic rooted tree has the same nested tuple 

58 representation. 

59 

60 Returns 

61 ------- 

62 tuple 

63 A nested tuple representation of the tree. 

64 

65 Notes 

66 ----- 

67 This function is *not* the inverse of :func:`from_nested_tuple`; the 

68 only guarantee is that the rooted trees are isomorphic. 

69 

70 See also 

71 -------- 

72 from_nested_tuple 

73 to_prufer_sequence 

74 

75 Examples 

76 -------- 

77 The tree need not be a balanced binary tree:: 

78 

79 >>> T = nx.Graph() 

80 >>> T.add_edges_from([(0, 1), (0, 2), (0, 3)]) 

81 >>> T.add_edges_from([(1, 4), (1, 5)]) 

82 >>> T.add_edges_from([(3, 6), (3, 7)]) 

83 >>> root = 0 

84 >>> nx.to_nested_tuple(T, root) 

85 (((), ()), (), ((), ())) 

86 

87 Continuing the above example, if ``canonical_form`` is ``True``, the 

88 nested tuples will be sorted:: 

89 

90 >>> nx.to_nested_tuple(T, root, canonical_form=True) 

91 ((), ((), ()), ((), ())) 

92 

93 Even the path graph can be interpreted as a tree:: 

94 

95 >>> T = nx.path_graph(4) 

96 >>> root = 0 

97 >>> nx.to_nested_tuple(T, root) 

98 ((((),),),) 

99 

100 """ 

101 

102 def _make_tuple(T, root, _parent): 

103 """Recursively compute the nested tuple representation of the 

104 given rooted tree. 

105 

106 ``_parent`` is the parent node of ``root`` in the supertree in 

107 which ``T`` is a subtree, or ``None`` if ``root`` is the root of 

108 the supertree. This argument is used to determine which 

109 neighbors of ``root`` are children and which is the parent. 

110 

111 """ 

112 # Get the neighbors of `root` that are not the parent node. We 

113 # are guaranteed that `root` is always in `T` by construction. 

114 children = set(T[root]) - {_parent} 

115 if len(children) == 0: 

116 return () 

117 nested = (_make_tuple(T, v, root) for v in children) 

118 if canonical_form: 

119 nested = sorted(nested) 

120 return tuple(nested) 

121 

122 # Do some sanity checks on the input. 

123 if not nx.is_tree(T): 

124 raise nx.NotATree("provided graph is not a tree") 

125 if root not in T: 

126 raise nx.NodeNotFound(f"Graph {T} contains no node {root}") 

127 

128 return _make_tuple(T, root, None) 

129 

130 

131@nx._dispatch(graphs=None) 

132def from_nested_tuple(sequence, sensible_relabeling=False): 

133 """Returns the rooted tree corresponding to the given nested tuple. 

134 

135 The nested tuple representation of a tree is defined 

136 recursively. The tree with one node and no edges is represented by 

137 the empty tuple, ``()``. A tree with ``k`` subtrees is represented 

138 by a tuple of length ``k`` in which each element is the nested tuple 

139 representation of a subtree. 

140 

141 Parameters 

142 ---------- 

143 sequence : tuple 

144 A nested tuple representing a rooted tree. 

145 

146 sensible_relabeling : bool 

147 Whether to relabel the nodes of the tree so that nodes are 

148 labeled in increasing order according to their breadth-first 

149 search order from the root node. 

150 

151 Returns 

152 ------- 

153 NetworkX graph 

154 The tree corresponding to the given nested tuple, whose root 

155 node is node 0. If ``sensible_labeling`` is ``True``, nodes will 

156 be labeled in breadth-first search order starting from the root 

157 node. 

158 

159 Notes 

160 ----- 

161 This function is *not* the inverse of :func:`to_nested_tuple`; the 

162 only guarantee is that the rooted trees are isomorphic. 

163 

164 See also 

165 -------- 

166 to_nested_tuple 

167 from_prufer_sequence 

168 

169 Examples 

170 -------- 

171 Sensible relabeling ensures that the nodes are labeled from the root 

172 starting at 0:: 

173 

174 >>> balanced = (((), ()), ((), ())) 

175 >>> T = nx.from_nested_tuple(balanced, sensible_relabeling=True) 

176 >>> edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)] 

177 >>> all((u, v) in T.edges() or (v, u) in T.edges() for (u, v) in edges) 

178 True 

179 

180 """ 

181 

182 def _make_tree(sequence): 

183 """Recursively creates a tree from the given sequence of nested 

184 tuples. 

185 

186 This function employs the :func:`~networkx.tree.join` function 

187 to recursively join subtrees into a larger tree. 

188 

189 """ 

190 # The empty sequence represents the empty tree, which is the 

191 # (unique) graph with a single node. We mark the single node 

192 # with an attribute that indicates that it is the root of the 

193 # graph. 

194 if len(sequence) == 0: 

195 return nx.empty_graph(1) 

196 # For a nonempty sequence, get the subtrees for each child 

197 # sequence and join all the subtrees at their roots. After 

198 # joining the subtrees, the root is node 0. 

199 return nx.tree.join_trees([(_make_tree(child), 0) for child in sequence]) 

200 

201 # Make the tree and remove the `is_root` node attribute added by the 

202 # helper function. 

203 T = _make_tree(sequence) 

204 if sensible_relabeling: 

205 # Relabel the nodes according to their breadth-first search 

206 # order, starting from the root node (that is, the node 0). 

207 bfs_nodes = chain([0], (v for u, v in nx.bfs_edges(T, 0))) 

208 labels = {v: i for i, v in enumerate(bfs_nodes)} 

209 # We would like to use `copy=False`, but `relabel_nodes` doesn't 

210 # allow a relabel mapping that can't be topologically sorted. 

211 T = nx.relabel_nodes(T, labels) 

212 return T 

213 

214 

215@not_implemented_for("directed") 

216@nx._dispatch(graphs="T") 

217def to_prufer_sequence(T): 

218 r"""Returns the Prüfer sequence of the given tree. 

219 

220 A *Prüfer sequence* is a list of *n* - 2 numbers between 0 and 

221 *n* - 1, inclusive. The tree corresponding to a given Prüfer 

222 sequence can be recovered by repeatedly joining a node in the 

223 sequence with a node with the smallest potential degree according to 

224 the sequence. 

225 

226 Parameters 

227 ---------- 

228 T : NetworkX graph 

229 An undirected graph object representing a tree. 

230 

231 Returns 

232 ------- 

233 list 

234 The Prüfer sequence of the given tree. 

235 

236 Raises 

237 ------ 

238 NetworkXPointlessConcept 

239 If the number of nodes in `T` is less than two. 

240 

241 NotATree 

242 If `T` is not a tree. 

243 

244 KeyError 

245 If the set of nodes in `T` is not {0, …, *n* - 1}. 

246 

247 Notes 

248 ----- 

249 There is a bijection from labeled trees to Prüfer sequences. This 

250 function is the inverse of the :func:`from_prufer_sequence` 

251 function. 

252 

253 Sometimes Prüfer sequences use nodes labeled from 1 to *n* instead 

254 of from 0 to *n* - 1. This function requires nodes to be labeled in 

255 the latter form. You can use :func:`~networkx.relabel_nodes` to 

256 relabel the nodes of your tree to the appropriate format. 

257 

258 This implementation is from [1]_ and has a running time of 

259 $O(n)$. 

260 

261 See also 

262 -------- 

263 to_nested_tuple 

264 from_prufer_sequence 

265 

266 References 

267 ---------- 

268 .. [1] Wang, Xiaodong, Lei Wang, and Yingjie Wu. 

269 "An optimal algorithm for Prufer codes." 

270 *Journal of Software Engineering and Applications* 2.02 (2009): 111. 

271 <https://doi.org/10.4236/jsea.2009.22016> 

272 

273 Examples 

274 -------- 

275 There is a bijection between Prüfer sequences and labeled trees, so 

276 this function is the inverse of the :func:`from_prufer_sequence` 

277 function: 

278 

279 >>> edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)] 

280 >>> tree = nx.Graph(edges) 

281 >>> sequence = nx.to_prufer_sequence(tree) 

282 >>> sequence 

283 [3, 3, 3, 4] 

284 >>> tree2 = nx.from_prufer_sequence(sequence) 

285 >>> list(tree2.edges()) == edges 

286 True 

287 

288 """ 

289 # Perform some sanity checks on the input. 

290 n = len(T) 

291 if n < 2: 

292 msg = "Prüfer sequence undefined for trees with fewer than two nodes" 

293 raise nx.NetworkXPointlessConcept(msg) 

294 if not nx.is_tree(T): 

295 raise nx.NotATree("provided graph is not a tree") 

296 if set(T) != set(range(n)): 

297 raise KeyError("tree must have node labels {0, ..., n - 1}") 

298 

299 degree = dict(T.degree()) 

300 

301 def parents(u): 

302 return next(v for v in T[u] if degree[v] > 1) 

303 

304 index = u = next(k for k in range(n) if degree[k] == 1) 

305 result = [] 

306 for i in range(n - 2): 

307 v = parents(u) 

308 result.append(v) 

309 degree[v] -= 1 

310 if v < index and degree[v] == 1: 

311 u = v 

312 else: 

313 index = u = next(k for k in range(index + 1, n) if degree[k] == 1) 

314 return result 

315 

316 

317@nx._dispatch(graphs=None) 

318def from_prufer_sequence(sequence): 

319 r"""Returns the tree corresponding to the given Prüfer sequence. 

320 

321 A *Prüfer sequence* is a list of *n* - 2 numbers between 0 and 

322 *n* - 1, inclusive. The tree corresponding to a given Prüfer 

323 sequence can be recovered by repeatedly joining a node in the 

324 sequence with a node with the smallest potential degree according to 

325 the sequence. 

326 

327 Parameters 

328 ---------- 

329 sequence : list 

330 A Prüfer sequence, which is a list of *n* - 2 integers between 

331 zero and *n* - 1, inclusive. 

332 

333 Returns 

334 ------- 

335 NetworkX graph 

336 The tree corresponding to the given Prüfer sequence. 

337 

338 Raises 

339 ------ 

340 NetworkXError 

341 If the Prüfer sequence is not valid. 

342 

343 Notes 

344 ----- 

345 There is a bijection from labeled trees to Prüfer sequences. This 

346 function is the inverse of the :func:`from_prufer_sequence` function. 

347 

348 Sometimes Prüfer sequences use nodes labeled from 1 to *n* instead 

349 of from 0 to *n* - 1. This function requires nodes to be labeled in 

350 the latter form. You can use :func:`networkx.relabel_nodes` to 

351 relabel the nodes of your tree to the appropriate format. 

352 

353 This implementation is from [1]_ and has a running time of 

354 $O(n)$. 

355 

356 References 

357 ---------- 

358 .. [1] Wang, Xiaodong, Lei Wang, and Yingjie Wu. 

359 "An optimal algorithm for Prufer codes." 

360 *Journal of Software Engineering and Applications* 2.02 (2009): 111. 

361 <https://doi.org/10.4236/jsea.2009.22016> 

362 

363 See also 

364 -------- 

365 from_nested_tuple 

366 to_prufer_sequence 

367 

368 Examples 

369 -------- 

370 There is a bijection between Prüfer sequences and labeled trees, so 

371 this function is the inverse of the :func:`to_prufer_sequence` 

372 function: 

373 

374 >>> edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)] 

375 >>> tree = nx.Graph(edges) 

376 >>> sequence = nx.to_prufer_sequence(tree) 

377 >>> sequence 

378 [3, 3, 3, 4] 

379 >>> tree2 = nx.from_prufer_sequence(sequence) 

380 >>> list(tree2.edges()) == edges 

381 True 

382 

383 """ 

384 n = len(sequence) + 2 

385 # `degree` stores the remaining degree (plus one) for each node. The 

386 # degree of a node in the decoded tree is one more than the number 

387 # of times it appears in the code. 

388 degree = Counter(chain(sequence, range(n))) 

389 T = nx.empty_graph(n) 

390 # `not_orphaned` is the set of nodes that have a parent in the 

391 # tree. After the loop, there should be exactly two nodes that are 

392 # not in this set. 

393 not_orphaned = set() 

394 index = u = next(k for k in range(n) if degree[k] == 1) 

395 for v in sequence: 

396 # check the validity of the prufer sequence 

397 if v < 0 or v > n - 1: 

398 raise nx.NetworkXError( 

399 f"Invalid Prufer sequence: Values must be between 0 and {n-1}, got {v}" 

400 ) 

401 T.add_edge(u, v) 

402 not_orphaned.add(u) 

403 degree[v] -= 1 

404 if v < index and degree[v] == 1: 

405 u = v 

406 else: 

407 index = u = next(k for k in range(index + 1, n) if degree[k] == 1) 

408 # At this point, there must be exactly two orphaned nodes; join them. 

409 orphans = set(T) - not_orphaned 

410 u, v = orphans 

411 T.add_edge(u, v) 

412 return T