2424import sys
2525import itertools as it
2626import functools as ft
27- import random
2827from collections import defaultdict
2928
3029from tqdm import tqdm
@@ -54,9 +53,11 @@ def paths_from_dag(dag, node_mapping=None, max_subpath_length=None, separator=',
5453
5554 """
5655 # Try to topologically sort the graph if not already sorted
57-
58- test_key = list (node_mapping .keys ())[0 ]
59- ONE_TO_MANY = isinstance (node_mapping [test_key ], set )
56+ if node_mapping :
57+ test_key = list (node_mapping .keys ())[0 ]
58+ ONE_TO_MANY = isinstance (node_mapping [test_key ], set )
59+ else :
60+ ONE_TO_MANY = False
6061
6162 if dag .is_acyclic is None :
6263 dag .topsort ()
@@ -84,8 +85,7 @@ def paths_from_dag(dag, node_mapping=None, max_subpath_length=None, separator=',
8485 path_counter = defaultdict (lambda : 0 )
8586 for root in tqdm (dag .roots , unit = 'roots' , desc = 'count unique paths' ):
8687 for set_path in dag .routes_from_node (root , node_mapping ):
87- blown_up_paths = blowup_set_paths (set_path )
88- for blown_up_path in blown_up_paths :
88+ for blown_up_path in expand_set_paths (set_path ):
8989 path_counter [blown_up_path ] += 1
9090
9191 for path , count in tqdm (path_counter .items (), unit = 'path' , desc = 'add paths' ):
@@ -97,7 +97,7 @@ def paths_from_dag(dag, node_mapping=None, max_subpath_length=None, separator=',
9797 return p
9898
9999
100- def blowup_set_paths (set_path ):
100+ def expand_set_paths (set_path ):
101101 """returns all possible paths which are consistent with the sequence of sets
102102
103103 Parameters
@@ -107,29 +107,36 @@ def blowup_set_paths(set_path):
107107
108108 Examples
109109 -------
110- >>> node_path = [{'320', '324'}, {'324'}, {'324', '429'}]
111- >>> blowup_set_paths(node_path)
112- [('324', '324', '324'),
113- ('320', '324', '429'),
114- ('324', '324', '324'),
115- ('320', '324', '429')]
116-
117-
118-
119- Returns
120- -------
121- list
122- a list of paths
110+ >>> node_path = [{1, 2}, {2, 5}, {1, 2}]
111+ >>> list(expand_set_paths(node_path))
112+ [(1, 2, 1), (2, 2, 1), (1, 5, 1), (2, 5, 1), (1, 2, 2), (2, 2, 2), (1, 5, 2), (2, 5, 2)]
113+ >>> node_path = [{1, 2}, {5}, {2, 5}]
114+ >>> list(expand_set_paths(node_path))
115+ [(1, 5, 2), (2, 5, 2), (1, 5, 5), (2, 5, 5)]
116+
117+
118+ Yields
119+ ------
120+ tuple
121+ a possible path
123122 """
124123 # how many possible combinations are there
125- node_sizes = ( len (n ) for n in set_path )
124+ node_sizes = [ len (n ) for n in set_path ]
126125 num_possibilities = ft .reduce (lambda x , y : x * y , node_sizes , 1 )
127126
128- all_paths = []
129- iterator = [it .cycle (node_set ) for node_set in set_path ]
127+ # create a list of lists such that each iterator is repeated the number of times
128+ # his predecessors have completed their cycle
129+ all_periodics = []
130+ current_length = 1
131+ for node_set in set_path :
132+ periodic_num = []
133+ for num in node_set :
134+ periodic_num .extend ([num ] * current_length )
135+ current_length *= len (node_set )
136+ all_periodics .append (periodic_num )
137+
138+ iterator = [it .cycle (periodic ) for periodic in all_periodics ]
130139 for i , elements in enumerate (zip (* iterator )):
131140 if i >= num_possibilities :
132141 break
133- all_paths .append (elements )
134- return all_paths
135-
142+ yield elements
0 commit comments