Skip to content

Commit 369cb9f

Browse files
authored
Merge pull request nltk#3245 from ekaf/hotfix-closuredup
Avoid duplicate output in acyclic_breadth_first
2 parents bf05dc4 + 501c70e commit 369cb9f

File tree

1 file changed

+70
-23
lines changed

1 file changed

+70
-23
lines changed

nltk/util.py

Lines changed: 70 additions & 23 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
@@ -378,7 +388,15 @@ def unweighted_minimum_spanning_digraph(tree, children=iter, shapes=None, attr=N
378388

379389

380390
def acyclic_breadth_first(tree, children=iter, maxdepth=-1, verbose=False):
381-
"""Traverse the nodes of a tree in breadth-first order,
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,33 +407,42 @@ def acyclic_breadth_first(tree, children=iter, maxdepth=-1, verbose=False):
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:
400-
if verbose:
401-
warnings.warn(
402-
"Discarded redundant search for {} at depth {}".format(
403-
child, depth + 1
404-
),
405-
stacklevel=2,
406-
)
419+
elif verbose:
420+
warnings.warn(
421+
"Discarded redundant search for {} at depth {}".format(
422+
child, depth + 1
423+
),
424+
stacklevel=2,
425+
)
407426
except TypeError:
408427
pass
409428

410429

411430
def acyclic_depth_first(
412431
tree, children=iter, depth=-1, cut_mark=None, traversed=None, verbose=False
413432
):
414-
"""Traverse the nodes of a tree in depth-first order,
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,
415443
discarding eventual cycles within any branch,
416444
adding cut_mark (when specified) if cycles were truncated.
417-
418-
The first argument should be the tree root;
445+
The first argument should be the tree root;
419446
children should be a function taking as argument a tree node
420447
and returning an iterator of the node's children.
421448
@@ -476,7 +503,17 @@ def acyclic_depth_first(
476503
def acyclic_branches_depth_first(
477504
tree, children=iter, depth=-1, cut_mark=None, traversed=None, verbose=False
478505
):
479-
"""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,
480517
discarding eventual cycles within the same branch,
481518
but keep duplicate paths in different branches.
482519
Add cut_mark (when defined) if cycles were truncated.
@@ -548,15 +585,22 @@ def acyclic_branches_depth_first(
548585

549586

550587
def acyclic_dic2tree(node, dic):
551-
"""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
552593
values are lists of children, to output tree suitable for pprint(),
553594
starting at root 'node', with subtrees as nested lists."""
554595
return [node] + [acyclic_dic2tree(child, dic) for child in dic[node]]
555596

556597

557598
def unweighted_minimum_spanning_dict(tree, children=iter):
558599
"""
559-
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)
560604
of an unweighted graph, by traversing the nodes of a tree in
561605
breadth-first order, discarding eventual cycles.
562606
@@ -598,7 +642,10 @@ def unweighted_minimum_spanning_dict(tree, children=iter):
598642

599643
def unweighted_minimum_spanning_tree(tree, children=iter):
600644
"""
601-
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,
602649
by traversing the nodes of a tree in breadth-first order,
603650
discarding eventual cycles.
604651

0 commit comments

Comments
 (0)