Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/traversal/breadth_first_search.py: 20%

121 statements  

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

1"""Basic algorithms for breadth-first searching the nodes of a graph.""" 

2import math 

3from collections import deque 

4 

5import networkx as nx 

6 

7__all__ = [ 

8 "bfs_edges", 

9 "bfs_tree", 

10 "bfs_predecessors", 

11 "bfs_successors", 

12 "descendants_at_distance", 

13 "bfs_layers", 

14 "bfs_labeled_edges", 

15 "generic_bfs_edges", 

16] 

17 

18 

19@nx._dispatch 

20def generic_bfs_edges(G, source, neighbors=None, depth_limit=None, sort_neighbors=None): 

21 """Iterate over edges in a breadth-first search. 

22 

23 The breadth-first search begins at `source` and enqueues the 

24 neighbors of newly visited nodes specified by the `neighbors` 

25 function. 

26 

27 Parameters 

28 ---------- 

29 G : NetworkX graph 

30 

31 source : node 

32 Starting node for the breadth-first search; this function 

33 iterates over only those edges in the component reachable from 

34 this node. 

35 

36 neighbors : function 

37 A function that takes a newly visited node of the graph as input 

38 and returns an *iterator* (not just a list) of nodes that are 

39 neighbors of that node with custom ordering. If not specified, this is 

40 just the``G.neighbors`` method, but in general it can be any function 

41 that returns an iterator over some or all of the neighbors of a 

42 given node, in any order. 

43 

44 depth_limit : int, optional(default=len(G)) 

45 Specify the maximum search depth. 

46 

47 sort_neighbors : Callable 

48 

49 .. deprecated:: 3.2 

50 

51 The sort_neighbors parameter is deprecated and will be removed in 

52 version 3.4. A custom (e.g. sorted) ordering of neighbors can be 

53 specified with the `neighbors` parameter. 

54 

55 A function that takes the list of neighbors of a given node as input, 

56 and returns an iterator over these neighbors but with a custom 

57 ordering. 

58 

59 Yields 

60 ------ 

61 edge 

62 Edges in the breadth-first search starting from `source`. 

63 

64 Examples 

65 -------- 

66 >>> G = nx.path_graph(3) 

67 >>> list(nx.bfs_edges(G, 0)) 

68 [(0, 1), (1, 2)] 

69 >>> list(nx.bfs_edges(G, source=0, depth_limit=1)) 

70 [(0, 1)] 

71 

72 Notes 

73 ----- 

74 This implementation is from `PADS`_, which was in the public domain 

75 when it was first accessed in July, 2004. The modifications 

76 to allow depth limits are based on the Wikipedia article 

77 "`Depth-limited-search`_". 

78 

79 .. _PADS: http://www.ics.uci.edu/~eppstein/PADS/BFS.py 

80 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search 

81 """ 

82 if neighbors is None: 

83 neighbors = G.neighbors 

84 if sort_neighbors is not None: 

85 import warnings 

86 

87 warnings.warn( 

88 ( 

89 "The sort_neighbors parameter is deprecated and will be removed\n" 

90 "in NetworkX 3.4, use the neighbors parameter instead." 

91 ), 

92 DeprecationWarning, 

93 stacklevel=2, 

94 ) 

95 _neighbors = neighbors 

96 neighbors = lambda node: iter(sort_neighbors(_neighbors(node))) 

97 if depth_limit is None: 

98 depth_limit = len(G) 

99 

100 seen = {source} 

101 n = len(G) 

102 depth = 0 

103 next_parents_children = [(source, neighbors(source))] 

104 while next_parents_children and depth < depth_limit: 

105 this_parents_children = next_parents_children 

106 next_parents_children = [] 

107 for parent, children in this_parents_children: 

108 for child in children: 

109 if child not in seen: 

110 seen.add(child) 

111 next_parents_children.append((child, neighbors(child))) 

112 yield parent, child 

113 if len(seen) == n: 

114 return 

115 depth += 1 

116 

117 

118@nx._dispatch 

119def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None): 

120 """Iterate over edges in a breadth-first-search starting at source. 

121 

122 Parameters 

123 ---------- 

124 G : NetworkX graph 

125 

126 source : node 

127 Specify starting node for breadth-first search; this function 

128 iterates over only those edges in the component reachable from 

129 this node. 

130 

131 reverse : bool, optional 

132 If True traverse a directed graph in the reverse direction 

133 

134 depth_limit : int, optional(default=len(G)) 

135 Specify the maximum search depth 

136 

137 sort_neighbors : function 

138 A function that takes the list of neighbors of given node as input, and 

139 returns an *iterator* over these neighbors but with custom ordering. 

140 

141 Yields 

142 ------ 

143 edge: 2-tuple of nodes 

144 Yields edges resulting from the breadth-first search. 

145 

146 Examples 

147 -------- 

148 To get the edges in a breadth-first search:: 

149 

150 >>> G = nx.path_graph(3) 

151 >>> list(nx.bfs_edges(G, 0)) 

152 [(0, 1), (1, 2)] 

153 >>> list(nx.bfs_edges(G, source=0, depth_limit=1)) 

154 [(0, 1)] 

155 

156 To get the nodes in a breadth-first search order:: 

157 

158 >>> G = nx.path_graph(3) 

159 >>> root = 2 

160 >>> edges = nx.bfs_edges(G, root) 

161 >>> nodes = [root] + [v for u, v in edges] 

162 >>> nodes 

163 [2, 1, 0] 

164 

165 Notes 

166 ----- 

167 The naming of this function is very similar to 

168 :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`. The difference 

169 is that ``edge_bfs`` yields edges even if they extend back to an already 

170 explored node while this generator yields the edges of the tree that results 

171 from a breadth-first-search (BFS) so no edges are reported if they extend 

172 to already explored nodes. That means ``edge_bfs`` reports all edges while 

173 ``bfs_edges`` only reports those traversed by a node-based BFS. Yet another 

174 description is that ``bfs_edges`` reports the edges traversed during BFS 

175 while ``edge_bfs`` reports all edges in the order they are explored. 

176 

177 Based on the breadth-first search implementation in PADS [1]_ 

178 by D. Eppstein, July 2004; with modifications to allow depth limits 

179 as described in [2]_. 

180 

181 References 

182 ---------- 

183 .. [1] http://www.ics.uci.edu/~eppstein/PADS/BFS.py. 

184 .. [2] https://en.wikipedia.org/wiki/Depth-limited_search 

185 

186 See Also 

187 -------- 

188 bfs_tree 

189 :func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges` 

190 :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs` 

191 

192 """ 

193 if reverse and G.is_directed(): 

194 successors = G.predecessors 

195 else: 

196 successors = G.neighbors 

197 

198 if callable(sort_neighbors): 

199 yield from generic_bfs_edges( 

200 G, source, lambda node: iter(sort_neighbors(successors(node))), depth_limit 

201 ) 

202 else: 

203 yield from generic_bfs_edges(G, source, successors, depth_limit) 

204 

205 

206@nx._dispatch 

207def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None): 

208 """Returns an oriented tree constructed from of a breadth-first-search 

209 starting at source. 

210 

211 Parameters 

212 ---------- 

213 G : NetworkX graph 

214 

215 source : node 

216 Specify starting node for breadth-first search 

217 

218 reverse : bool, optional 

219 If True traverse a directed graph in the reverse direction 

220 

221 depth_limit : int, optional(default=len(G)) 

222 Specify the maximum search depth 

223 

224 sort_neighbors : function 

225 A function that takes the list of neighbors of given node as input, and 

226 returns an *iterator* over these neighbors but with custom ordering. 

227 

228 Returns 

229 ------- 

230 T: NetworkX DiGraph 

231 An oriented tree 

232 

233 Examples 

234 -------- 

235 >>> G = nx.path_graph(3) 

236 >>> list(nx.bfs_tree(G, 1).edges()) 

237 [(1, 0), (1, 2)] 

238 >>> H = nx.Graph() 

239 >>> nx.add_path(H, [0, 1, 2, 3, 4, 5, 6]) 

240 >>> nx.add_path(H, [2, 7, 8, 9, 10]) 

241 >>> sorted(list(nx.bfs_tree(H, source=3, depth_limit=3).edges())) 

242 [(1, 0), (2, 1), (2, 7), (3, 2), (3, 4), (4, 5), (5, 6), (7, 8)] 

243 

244 

245 Notes 

246 ----- 

247 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py 

248 by D. Eppstein, July 2004. The modifications 

249 to allow depth limits based on the Wikipedia article 

250 "`Depth-limited-search`_". 

251 

252 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search 

253 

254 See Also 

255 -------- 

256 dfs_tree 

257 bfs_edges 

258 edge_bfs 

259 """ 

260 T = nx.DiGraph() 

261 T.add_node(source) 

262 edges_gen = bfs_edges( 

263 G, 

264 source, 

265 reverse=reverse, 

266 depth_limit=depth_limit, 

267 sort_neighbors=sort_neighbors, 

268 ) 

269 T.add_edges_from(edges_gen) 

270 return T 

271 

272 

273@nx._dispatch 

274def bfs_predecessors(G, source, depth_limit=None, sort_neighbors=None): 

275 """Returns an iterator of predecessors in breadth-first-search from source. 

276 

277 Parameters 

278 ---------- 

279 G : NetworkX graph 

280 

281 source : node 

282 Specify starting node for breadth-first search 

283 

284 depth_limit : int, optional(default=len(G)) 

285 Specify the maximum search depth 

286 

287 sort_neighbors : function 

288 A function that takes the list of neighbors of given node as input, and 

289 returns an *iterator* over these neighbors but with custom ordering. 

290 

291 Returns 

292 ------- 

293 pred: iterator 

294 (node, predecessor) iterator where `predecessor` is the predecessor of 

295 `node` in a breadth first search starting from `source`. 

296 

297 Examples 

298 -------- 

299 >>> G = nx.path_graph(3) 

300 >>> dict(nx.bfs_predecessors(G, 0)) 

301 {1: 0, 2: 1} 

302 >>> H = nx.Graph() 

303 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) 

304 >>> dict(nx.bfs_predecessors(H, 0)) 

305 {1: 0, 2: 0, 3: 1, 4: 1, 5: 2, 6: 2} 

306 >>> M = nx.Graph() 

307 >>> nx.add_path(M, [0, 1, 2, 3, 4, 5, 6]) 

308 >>> nx.add_path(M, [2, 7, 8, 9, 10]) 

309 >>> sorted(nx.bfs_predecessors(M, source=1, depth_limit=3)) 

310 [(0, 1), (2, 1), (3, 2), (4, 3), (7, 2), (8, 7)] 

311 >>> N = nx.DiGraph() 

312 >>> nx.add_path(N, [0, 1, 2, 3, 4, 7]) 

313 >>> nx.add_path(N, [3, 5, 6, 7]) 

314 >>> sorted(nx.bfs_predecessors(N, source=2)) 

315 [(3, 2), (4, 3), (5, 3), (6, 5), (7, 4)] 

316 

317 Notes 

318 ----- 

319 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py 

320 by D. Eppstein, July 2004. The modifications 

321 to allow depth limits based on the Wikipedia article 

322 "`Depth-limited-search`_". 

323 

324 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search 

325 

326 See Also 

327 -------- 

328 bfs_tree 

329 bfs_edges 

330 edge_bfs 

331 """ 

332 for s, t in bfs_edges( 

333 G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors 

334 ): 

335 yield (t, s) 

336 

337 

338@nx._dispatch 

339def bfs_successors(G, source, depth_limit=None, sort_neighbors=None): 

340 """Returns an iterator of successors in breadth-first-search from source. 

341 

342 Parameters 

343 ---------- 

344 G : NetworkX graph 

345 

346 source : node 

347 Specify starting node for breadth-first search 

348 

349 depth_limit : int, optional(default=len(G)) 

350 Specify the maximum search depth 

351 

352 sort_neighbors : function 

353 A function that takes the list of neighbors of given node as input, and 

354 returns an *iterator* over these neighbors but with custom ordering. 

355 

356 Returns 

357 ------- 

358 succ: iterator 

359 (node, successors) iterator where `successors` is the non-empty list of 

360 successors of `node` in a breadth first search from `source`. 

361 To appear in the iterator, `node` must have successors. 

362 

363 Examples 

364 -------- 

365 >>> G = nx.path_graph(3) 

366 >>> dict(nx.bfs_successors(G, 0)) 

367 {0: [1], 1: [2]} 

368 >>> H = nx.Graph() 

369 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) 

370 >>> dict(nx.bfs_successors(H, 0)) 

371 {0: [1, 2], 1: [3, 4], 2: [5, 6]} 

372 >>> G = nx.Graph() 

373 >>> nx.add_path(G, [0, 1, 2, 3, 4, 5, 6]) 

374 >>> nx.add_path(G, [2, 7, 8, 9, 10]) 

375 >>> dict(nx.bfs_successors(G, source=1, depth_limit=3)) 

376 {1: [0, 2], 2: [3, 7], 3: [4], 7: [8]} 

377 >>> G = nx.DiGraph() 

378 >>> nx.add_path(G, [0, 1, 2, 3, 4, 5]) 

379 >>> dict(nx.bfs_successors(G, source=3)) 

380 {3: [4], 4: [5]} 

381 

382 Notes 

383 ----- 

384 Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py 

385 by D. Eppstein, July 2004.The modifications 

386 to allow depth limits based on the Wikipedia article 

387 "`Depth-limited-search`_". 

388 

389 .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search 

390 

391 See Also 

392 -------- 

393 bfs_tree 

394 bfs_edges 

395 edge_bfs 

396 """ 

397 parent = source 

398 children = [] 

399 for p, c in bfs_edges( 

400 G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors 

401 ): 

402 if p == parent: 

403 children.append(c) 

404 continue 

405 yield (parent, children) 

406 children = [c] 

407 parent = p 

408 yield (parent, children) 

409 

410 

411@nx._dispatch 

412def bfs_layers(G, sources): 

413 """Returns an iterator of all the layers in breadth-first search traversal. 

414 

415 Parameters 

416 ---------- 

417 G : NetworkX graph 

418 A graph over which to find the layers using breadth-first search. 

419 

420 sources : node in `G` or list of nodes in `G` 

421 Specify starting nodes for single source or multiple sources breadth-first search 

422 

423 Yields 

424 ------ 

425 layer: list of nodes 

426 Yields list of nodes at the same distance from sources 

427 

428 Examples 

429 -------- 

430 >>> G = nx.path_graph(5) 

431 >>> dict(enumerate(nx.bfs_layers(G, [0, 4]))) 

432 {0: [0, 4], 1: [1, 3], 2: [2]} 

433 >>> H = nx.Graph() 

434 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) 

435 >>> dict(enumerate(nx.bfs_layers(H, [1]))) 

436 {0: [1], 1: [0, 3, 4], 2: [2], 3: [5, 6]} 

437 >>> dict(enumerate(nx.bfs_layers(H, [1, 6]))) 

438 {0: [1, 6], 1: [0, 3, 4, 2], 2: [5]} 

439 """ 

440 if sources in G: 

441 sources = [sources] 

442 

443 current_layer = list(sources) 

444 visited = set(sources) 

445 

446 for source in current_layer: 

447 if source not in G: 

448 raise nx.NetworkXError(f"The node {source} is not in the graph.") 

449 

450 # this is basically BFS, except that the current layer only stores the nodes at 

451 # same distance from sources at each iteration 

452 while current_layer: 

453 yield current_layer 

454 next_layer = [] 

455 for node in current_layer: 

456 for child in G[node]: 

457 if child not in visited: 

458 visited.add(child) 

459 next_layer.append(child) 

460 current_layer = next_layer 

461 

462 

463REVERSE_EDGE = "reverse" 

464TREE_EDGE = "tree" 

465FORWARD_EDGE = "forward" 

466LEVEL_EDGE = "level" 

467 

468 

469@nx._dispatch 

470def bfs_labeled_edges(G, sources): 

471 """Iterate over edges in a breadth-first search (BFS) labeled by type. 

472 

473 We generate triple of the form (*u*, *v*, *d*), where (*u*, *v*) is the 

474 edge being explored in the breadth-first search and *d* is one of the 

475 strings 'tree', 'forward', 'level', or 'reverse'. A 'tree' edge is one in 

476 which *v* is first discovered and placed into the layer below *u*. A 

477 'forward' edge is one in which *u* is on the layer above *v* and *v* has 

478 already been discovered. A 'level' edge is one in which both *u* and *v* 

479 occur on the same layer. A 'reverse' edge is one in which *u* is on a layer 

480 below *v*. 

481 

482 We emit each edge exactly once. In an undirected graph, 'reverse' edges do 

483 not occur, because each is discovered either as a 'tree' or 'forward' edge. 

484 

485 Parameters 

486 ---------- 

487 G : NetworkX graph 

488 A graph over which to find the layers using breadth-first search. 

489 

490 sources : node in `G` or list of nodes in `G` 

491 Starting nodes for single source or multiple sources breadth-first search 

492 

493 Yields 

494 ------ 

495 edges: generator 

496 A generator of triples (*u*, *v*, *d*) where (*u*, *v*) is the edge being 

497 explored and *d* is described above. 

498 

499 Examples 

500 -------- 

501 >>> G = nx.cycle_graph(4, create_using = nx.DiGraph) 

502 >>> list(nx.bfs_labeled_edges(G, 0)) 

503 [(0, 1, 'tree'), (1, 2, 'tree'), (2, 3, 'tree'), (3, 0, 'reverse')] 

504 >>> G = nx.complete_graph(3) 

505 >>> list(nx.bfs_labeled_edges(G, 0)) 

506 [(0, 1, 'tree'), (0, 2, 'tree'), (1, 2, 'level')] 

507 >>> list(nx.bfs_labeled_edges(G, [0, 1])) 

508 [(0, 1, 'level'), (0, 2, 'tree'), (1, 2, 'forward')] 

509 """ 

510 if sources in G: 

511 sources = [sources] 

512 

513 neighbors = G._adj 

514 directed = G.is_directed() 

515 visited = set() 

516 visit = visited.discard if directed else visited.add 

517 # We use visited in a negative sense, so the visited set stays empty for the 

518 # directed case and level edges are reported on their first occurrence in 

519 # the undirected case. Note our use of visited.discard -- this is built-in 

520 # thus somewhat faster than a python-defined def nop(x): pass 

521 depth = {s: 0 for s in sources} 

522 queue = deque(depth.items()) 

523 push = queue.append 

524 pop = queue.popleft 

525 while queue: 

526 u, du = pop() 

527 for v in neighbors[u]: 

528 if v not in depth: 

529 depth[v] = dv = du + 1 

530 push((v, dv)) 

531 yield u, v, TREE_EDGE 

532 else: 

533 dv = depth[v] 

534 if du == dv: 

535 if v not in visited: 

536 yield u, v, LEVEL_EDGE 

537 elif du < dv: 

538 yield u, v, FORWARD_EDGE 

539 elif directed: 

540 yield u, v, REVERSE_EDGE 

541 visit(u) 

542 

543 

544@nx._dispatch 

545def descendants_at_distance(G, source, distance): 

546 """Returns all nodes at a fixed `distance` from `source` in `G`. 

547 

548 Parameters 

549 ---------- 

550 G : NetworkX graph 

551 A graph 

552 source : node in `G` 

553 distance : the distance of the wanted nodes from `source` 

554 

555 Returns 

556 ------- 

557 set() 

558 The descendants of `source` in `G` at the given `distance` from `source` 

559 

560 Examples 

561 -------- 

562 >>> G = nx.path_graph(5) 

563 >>> nx.descendants_at_distance(G, 2, 2) 

564 {0, 4} 

565 >>> H = nx.DiGraph() 

566 >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) 

567 >>> nx.descendants_at_distance(H, 0, 2) 

568 {3, 4, 5, 6} 

569 >>> nx.descendants_at_distance(H, 5, 0) 

570 {5} 

571 >>> nx.descendants_at_distance(H, 5, 1) 

572 set() 

573 """ 

574 if source not in G: 

575 raise nx.NetworkXError(f"The node {source} is not in the graph.") 

576 

577 bfs_generator = nx.bfs_layers(G, source) 

578 for i, layer in enumerate(bfs_generator): 

579 if i == distance: 

580 return set(layer) 

581 return set()