Skip to content

Commit 633aaab

Browse files
committed
Avoid duplicate output in acyclic_breadth_first
1 parent f2a92bd commit 633aaab

File tree

1 file changed

+83
-31
lines changed

1 file changed

+83
-31
lines changed

nltk/util.py

Lines changed: 83 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,13 @@ def breadth_first(tree, children=iter, maxdepth=-1):
257257

258258

259259
def edge_closure(tree, children=iter, maxdepth=-1, verbose=False):
260-
"""Yield the edges of a graph in breadth-first order,
260+
"""
261+
:param tree: the tree root
262+
:param children: a function taking as argument a tree node
263+
:param maxdepth: to limit the search depth
264+
:param verbose: to print warnings when cycles are discarded
265+
266+
Yield the edges of a graph in breadth-first order,
261267
discarding eventual cycles.
262268
The first argument should be the start node;
263269
children should be a function taking as argument a graph node
@@ -295,13 +301,13 @@ def edge_closure(tree, children=iter, maxdepth=-1, verbose=False):
295301
def edges2dot(edges, shapes=None, attr=None):
296302
"""
297303
:param edges: the set (or list) of edges of a directed graph.
298-
299-
:return dot_string: a representation of 'edges' as a string in the DOT
300-
graph language, which can be converted to an image by the 'dot' program
301-
from the Graphviz package, or nltk.parse.dependencygraph.dot2img(dot_string).
302-
303304
:param shapes: dictionary of strings that trigger a specified shape.
304305
:param attr: dictionary with global graph attributes
306+
:return: a representation of 'edges' as a string in the DOT graph language.
307+
308+
Returns dot_string: a representation of 'edges' as a string in the DOT
309+
graph language, which can be converted to an image by the 'dot' program
310+
from the Graphviz package, or nltk.parse.dependencygraph.dot2img(dot_string).
305311
306312
>>> import nltk
307313
>>> from nltk.util import edges2dot
@@ -337,8 +343,12 @@ def edges2dot(edges, shapes=None, attr=None):
337343

338344
def unweighted_minimum_spanning_digraph(tree, children=iter, shapes=None, attr=None):
339345
"""
346+
:param tree: the tree root
347+
:param children: a function taking as argument a tree node
348+
:param shapes: dictionary of strings that trigger a specified shape.
349+
:param attr: dictionary with global graph attributes
340350
341-
Build a Minimum Spanning Tree (MST) of an unweighted graph,
351+
Build a Minimum Spanning Tree (MST) of an unweighted graph,
342352
by traversing the nodes of a tree in breadth-first order,
343353
discarding eventual cycles.
344354
@@ -377,8 +387,16 @@ def unweighted_minimum_spanning_digraph(tree, children=iter, shapes=None, attr=N
377387
##########################################################################
378388

379389

380-
def acyclic_breadth_first(tree, children=iter, maxdepth=-1):
381-
"""Traverse the nodes of a tree in breadth-first order,
390+
def acyclic_breadth_first(tree, children=iter, maxdepth=-1, verbose=False):
391+
"""
392+
:param tree: the tree root
393+
:param children: a function taking as argument a tree node
394+
:param maxdepth: to limit the search depth
395+
:param verbose: to print warnings when cycles are discarded
396+
:return: the tree in breadth-first order
397+
398+
Adapted from breadth_first() above, to discard cycles.
399+
Traverse the nodes of a tree in breadth-first order,
382400
discarding eventual cycles.
383401
384402
The first argument should be the tree root;
@@ -389,14 +407,16 @@ def acyclic_breadth_first(tree, children=iter, maxdepth=-1):
389407
queue = deque([(tree, 0)])
390408
while queue:
391409
node, depth = queue.popleft()
410+
if node in traversed:
411+
continue
392412
yield node
393413
traversed.add(node)
394414
if depth != maxdepth:
395415
try:
396416
for child in children(node):
397417
if child not in traversed:
398418
queue.append((child, depth + 1))
399-
else:
419+
elif verbose:
400420
warnings.warn(
401421
"Discarded redundant search for {} at depth {}".format(
402422
child, depth + 1
@@ -407,12 +427,22 @@ def acyclic_breadth_first(tree, children=iter, maxdepth=-1):
407427
pass
408428

409429

410-
def acyclic_depth_first(tree, children=iter, depth=-1, cut_mark=None, traversed=None):
411-
"""Traverse the nodes of a tree in depth-first order,
430+
def acyclic_depth_first(
431+
tree, children=iter, depth=-1, cut_mark=None, traversed=None, verbose=False
432+
):
433+
"""
434+
:param tree: the tree root
435+
:param children: a function taking as argument a tree node
436+
:param depth: the maximum depth of the search
437+
:param cut_mark: the mark to add when cycles are truncated
438+
:param traversed: the set of traversed nodes
439+
:param verbose: to print warnings when cycles are discarded
440+
:return: the tree in depth-first order
441+
442+
Traverse the nodes of a tree in depth-first order,
412443
discarding eventual cycles within any branch,
413444
adding cut_mark (when specified) if cycles were truncated.
414-
415-
The first argument should be the tree root;
445+
The first argument should be the tree root;
416446
children should be a function taking as argument a tree node
417447
and returning an iterator of the node's children.
418448
@@ -454,12 +484,13 @@ def acyclic_depth_first(tree, children=iter, depth=-1, cut_mark=None, traversed=
454484
)
455485
]
456486
else:
457-
warnings.warn(
458-
"Discarded redundant search for {} at depth {}".format(
459-
child, depth - 1
460-
),
461-
stacklevel=3,
462-
)
487+
if verbose:
488+
warnings.warn(
489+
"Discarded redundant search for {} at depth {}".format(
490+
child, depth - 1
491+
),
492+
stacklevel=3,
493+
)
463494
if cut_mark:
464495
out_tree += [f"Cycle({child},{depth - 1},{cut_mark})"]
465496
except TypeError:
@@ -470,9 +501,19 @@ def acyclic_depth_first(tree, children=iter, depth=-1, cut_mark=None, traversed=
470501

471502

472503
def acyclic_branches_depth_first(
473-
tree, children=iter, depth=-1, cut_mark=None, traversed=None
504+
tree, children=iter, depth=-1, cut_mark=None, traversed=None, verbose=False
474505
):
475-
"""Traverse the nodes of a tree in depth-first order,
506+
"""
507+
:param tree: the tree root
508+
:param children: a function taking as argument a tree node
509+
:param depth: the maximum depth of the search
510+
:param cut_mark: the mark to add when cycles are truncated
511+
:param traversed: the set of traversed nodes
512+
:param verbose: to print warnings when cycles are discarded
513+
:return: the tree in depth-first order
514+
515+
Adapted from acyclic_depth_first() above, to
516+
traverse the nodes of a tree in depth-first order,
476517
discarding eventual cycles within the same branch,
477518
but keep duplicate paths in different branches.
478519
Add cut_mark (when defined) if cycles were truncated.
@@ -527,12 +568,13 @@ def acyclic_branches_depth_first(
527568
)
528569
]
529570
else:
530-
warnings.warn(
531-
"Discarded redundant search for {} at depth {}".format(
532-
child, depth - 1
533-
),
534-
stacklevel=3,
535-
)
571+
if verbose:
572+
warnings.warn(
573+
"Discarded redundant search for {} at depth {}".format(
574+
child, depth - 1
575+
),
576+
stacklevel=3,
577+
)
536578
if cut_mark:
537579
out_tree += [f"Cycle({child},{depth - 1},{cut_mark})"]
538580
except TypeError:
@@ -543,15 +585,22 @@ def acyclic_branches_depth_first(
543585

544586

545587
def acyclic_dic2tree(node, dic):
546-
"""Convert acyclic dictionary 'dic', where the keys are nodes, and the
588+
"""
589+
:param node: the root node
590+
:param dic: the dictionary of children
591+
592+
Convert acyclic dictionary 'dic', where the keys are nodes, and the
547593
values are lists of children, to output tree suitable for pprint(),
548594
starting at root 'node', with subtrees as nested lists."""
549595
return [node] + [acyclic_dic2tree(child, dic) for child in dic[node]]
550596

551597

552598
def unweighted_minimum_spanning_dict(tree, children=iter):
553599
"""
554-
Output a dictionary representing a Minimum Spanning Tree (MST)
600+
:param tree: the tree root
601+
:param children: a function taking as argument a tree node
602+
603+
Output a dictionary representing a Minimum Spanning Tree (MST)
555604
of an unweighted graph, by traversing the nodes of a tree in
556605
breadth-first order, discarding eventual cycles.
557606
@@ -593,7 +642,10 @@ def unweighted_minimum_spanning_dict(tree, children=iter):
593642

594643
def unweighted_minimum_spanning_tree(tree, children=iter):
595644
"""
596-
Output a Minimum Spanning Tree (MST) of an unweighted graph,
645+
:param tree: the tree root
646+
:param children: a function taking as argument a tree node
647+
648+
Output a Minimum Spanning Tree (MST) of an unweighted graph,
597649
by traversing the nodes of a tree in breadth-first order,
598650
discarding eventual cycles.
599651

0 commit comments

Comments
 (0)