Building a collection across recursive iterations - python

I am attempting to implement an "all paths" algorithm described in this question's top answer using Python.
So far, I have defined the function as such:
def AllPaths(start, end, edges):
print(start)
if (start == end):
return
for i in range(0, len(edges)):
if edges[i][1] == start:
AllPaths(edges[i][0], end, edges)
I have included print(start) to try give myself an idea of the function's behaviour.
For example, if the initial function call is as such:
edges = [ (1, 2), (1, 3), (2, 3), (3, 4) ]
AllPaths(edges[len(edges) - 1][1], edges[0][0], edges)
With the starting node being 4 and the ending node being 1, the output of the function is:
4
3
1
2
1
Somewhere in that output, it is telling me that all paths between node 4 and node 1 consists of:
[ (4, 3), (3, 1) ]
[ (4, 3), (3, 2), (2, 1) ]
But how do I form these collections during execution and, if I wanted to keep track of how many distinct paths there are, how would this be counted during execution?

If you don't mind that I won't follow the backward traversal route, which I find rather odd, then here's a suggestion on how to modify your function to see what's going on:
def all_paths(start, end, edges):
if start == end:
return [[end]]
paths = []
for edge in edges:
if edge[0] == start:
paths += [[start] + path
for path in all_paths(edge[1], end, edges)]
return paths
Output for edges = [(1, 2), (1, 3), (2, 3), (3, 4)] and start = 1; end = 4:
[[1, 2, 3, 4],
[1, 3, 4]]
If you want a direct edges-result then I'd suggest:
def all_paths(start, end, edges):
paths = []
for edge in edges:
if edge[0] == start:
if edge[1] == end:
return [[edge]]
else:
paths += [[edge] + path
for path in all_paths(edge[1], end, edges)]
return paths
Output for the same input:
[[(1, 2), (2, 3), (3, 4)],
[(1, 3), (3, 4)]]
But a warning: If you have loops in the graph, e.g. edges = [(1, 2), (1, 3), (3, 1), (2, 3), (3, 4)], then these algorithms will crash (recursion won't stop). In these cases something like
def all_paths(start, end, edges):
edges = set(edges)
paths = []
for edge in edges:
if edge[0] == start:
if edge[1] == end:
paths += [[start, end]]
else:
new_edges = edges.difference({edge})
paths += [[start] + path
for path in all_paths(edge[1], end, new_edges)]
return paths
which removes visited edges is better. Result for start = 1; end = 4:
[[1, 2, 3, 1, 3, 4],
[1, 2, 3, 4],
[1, 3, 1, 2, 3, 4],
[1, 3, 4]]
I hope that this is at least a little bit what you were looking for.

Related

List comprehension from a set whose elements are tuples composed of two tuples

I have a huge list of tuples each containing another two tuples like e.g.
lst = [((0,2,1), (2,1,3)), ((3,2,1), (0,1,1)), ...]
Many of the elements of this list are not acceptable under certain criteria and I am trying to create a new list composed by those elements satisfying those conditions. Among others, I would like to remove those elements whose left (resp. right) tuple contains a zero but the right (resp. left) tuple has no zero in the same position. Also those elements whose left tuple contains two consecutive non zero numbers (in a given range) and the number located in the right tuple in the same position than the one where the repetition appears is not a 1. To do this, I have tried:
acceptable_lst = [elem for elem in lst for j in range(3) if not ((elem[0][j] == 0 and
elem[1][j] != 0) or (j < 2 and elem[0][j] != 0 and elem[0][j] == elem[0][j+1]
and elem[1][j+1] != 1)]
When I apply this code to e.g.
lst = [((3,2,2), (1,2,3)),
((0,1,3), (2,2,3)),
((1,1,2), (3,3,3)),
((0,2,2), (3,3,3)),
((2,2,1), (3,1,3))]
I would like to get:
acceptable_lst = [((2,2,1), (3,1,3))]
Why? The first element in lst has a rep of 2 in the left tuple, the second 2 in the third position, but the third element in the right tuple is not a 1. The second element has a zero in the first position of the left tuple and a non zero in the same position of the right tuple, so on ... Only the last element in lst satisfies the above conditions.
However what I get is
[((3, 2, 2), (1, 2, 3)),
((3, 2, 2), (1, 2, 3)),
((0, 1, 3), (2, 2, 3)),
((0, 1, 3), (2, 2, 3)),
((1, 1, 2), (3, 3, 3)),
((1, 1, 2), (3, 3, 3)),
((0, 2, 2), (3, 3, 3)),
((2, 2, 1), (3, 1, 3)),
((2, 2, 1), (3, 1, 3)),
((2, 2, 1), (3, 1, 3))]
which indicates that my code is completely wrong. How can I implement what I need?
Check the validity in a function
def valid(elemLeft, elemRight):
lastItem = None
for i in range(3):
if elemLeft[i] == 0 and elemRight[i] != 0:
return False
if lastItem != None:
if elemLeft[i] == lastItem and elemRight[i] != 1:
return False
lastItem = elemLeft[i]
return True
lst = [((3,2,2),(1,2,3)), ((0,1,3),(2,2,3)), ((1,1,2),(3,3,3)), ((0,2,2),(3,3,3)), ((2,2,1),(3,1,3))]
acceptable_lst = [elem for elem in lst if valid(elem[0],elem[1])]
print(acceptable_lst)
You are doing two for loops in your list comprehension.
[ elem for elem in lst for j in range(3) if condition ]
is equivalent to:
out_list = []
for elem in lst:
for j in range(3):
if condition:
out_list.append(elem)
If you have to use list comprehension for this task, you could modify it:
import numpy as np
acceptable_lst = [elem for elem in lst
if not (np.any([(elem[0][j] == 0 and elem[1][j] != 0) for j in range(3)]))
and not np.any([(j < 2 and elem[0][j] != 0 and elem[0][j] == elem[0][j+1] and elem[1][j+1] != 1) for j in range(3)])]

Creating list of values with according to condition in python

I need to create a list of jobs respecting precedence relationships stated by a dictionary.
dict_preced = {(1, 2): 0, (1, 3): 0, (2, 1): 1, (2, 3): 0, (3, 1): 1, (3, 2): 0}
Where (j1, j2) == 1 means that j1 requires j2, 0 otherwise.
Supposing I already have starting list: j_seq = [3, 2, 1], I need to create a new_list in which all values from j_seq will respect precedence relationship, meaning that there is no job being executed before a required job. (i.e., job 3 and job 2 cannot be executed before job 1).
Therefore, there are many candidate lists (i.e., new_list = [1, 2, 3] or new_list = [1, 3, 2]).
How to create samples of new_list that will always respect these precedence relationships?
I found many examples of list comprehension when each value need to respect a given condition with no dependences with other values. But I did not find any examples in which the condition stated concerns two values of the same list.
EDIT: I do not need to get all permutations respecting precedence constraints, just one is enough.
One solution would be to enumerate all the permutations of j_seq, and then do a lookup of each pair combination against your precedence dictionary to identify permutations that were invalid and could be thrown out.
For example:
import itertools
dict_preced = {(1, 2): 0, (1, 3): 0, (2, 1): 1, (2, 3): 0, (3, 1): 1, (3, 2): 0}
j_seq = [3, 2, 1]
valid = []
for perm in itertools.permutations(j_seq):
print('Permutation:', perm)
for perm_pair in itertools.combinations(perm, 2):
precedence = dict_preced.get(perm_pair, 0)
print('\tCombination:', perm_pair, '=>', precedence)
if precedence == 1:
print('\tDecision: exclude', perm)
break
else:
print('\tDecision: include', perm)
valid.append(perm)
print('Result:', valid)
The result is: [(1, 3, 2), (1, 2, 3)].
The complete output (including debug logs) is:
Permutation: (3, 2, 1)
Combination: (3, 2) => 0
Combination: (3, 1) => 1
Decision: exclude (3, 2, 1)
Permutation: (3, 1, 2)
Combination: (3, 1) => 1
Decision: exclude (3, 1, 2)
Permutation: (2, 3, 1)
Combination: (2, 3) => 0
Combination: (2, 1) => 1
Decision: exclude (2, 3, 1)
Permutation: (2, 1, 3)
Combination: (2, 1) => 1
Decision: exclude (2, 1, 3)
Permutation: (1, 3, 2)
Combination: (1, 3) => 0
Combination: (1, 2) => 0
Combination: (3, 2) => 0
Decision: include (1, 3, 2)
Permutation: (1, 2, 3)
Combination: (1, 2) => 0
Combination: (1, 3) => 0
Combination: (2, 3) => 0
Decision: include (1, 2, 3)
Result: [(1, 3, 2), (1, 2, 3)]
I am answering my own question because I found a solution to this problem.
When using short length lists, the method proposed by #jarmod can be handy. However, when long lists are considered, it becomes impracticable to use his method because the number of permutations arises to 10^18.
For those having access to the CP optimizer from IBM CPLEX (or other constraint programming solver), a possible alternative is using the constraint propagation method to obtain some lists of valid sequences (respecting the precedence constraints).
In my case, I called the CP optimizer through the docplex module.
You can check out the script here: https://github.com/campioni1/CPO_Docplex_precedence_constraints
Please, let me know if you encounter any difficulties,

Sorting a list of tuples made up of two numbers by consecutive numbers

The list of tuples is the output from a capacitated vehicle-routing optimization and represents the arcs from one stop to another, where 0 represents the depot of the vehicle (start and end point of vehicle). As the vehicle must drive several laps it may return to the depot before all stops were made. The solver will always return the starting-arcs first, which in the example below means that the first consecutive tuples starting with 0, namely (0, 3), (0, 7), (0, 8) will determine how many laps (= 3) there are.
How can I sort the arcs in consecutive order so that one vehicle could drive the arcs one after another?
Input:
li = [(0, 3), (0, 7), (0, 8), (3, 0), (4, 0), (7, 3), (8, 4), (3, 0)]
Output:
[(0, 3), (3, 0), (0, 7), (7, 3), (3, 0), (0, 8), (8, 4), (4, 0)]
What I tried so far:
laps = 0
for arc in li:
if arc[0] == 0:
laps = laps + 1
new_list = []
for i in range(laps):
value = li.pop(0)
new_list.append([value])
for i in range(laps):
while new_list[i][-1][1] != 0:
arc_end = new_list[i][-1][1]
for j in range(len(li)):
if li[j][0] == arc_end:
value = li.pop(j)
new_list[i].append(value)
break
flat_list = [item for sublist in new_list for item in sublist]
return flat_list
You are trying to solve the problem of finding cycles in a directed graph. The problem itself is not a difficult one to solve, and Python has a very good package for solving such problems - networkx. It would be a good idea to learn a bit about fundamental graph algorithms. There is also another stack overflow question about this algorithm, which you can consult, at Finding all cycles in a directed graph

create list of adjacent elements of another list in Python

I am looking to take as input a list and then create another list which contains tuples (or sub-lists) of adjacent elements from the original list, wrapping around for the beginning and ending elements. The input/output would look like this:
l_in = [0, 1, 2, 3]
l_out = [(3, 0, 1), (0, 1, 2), (1, 2, 3), (2, 3, 0)]
My question is closely related to another titled getting successive adjacent elements of a list, but this other question does not take into account wrapping around for the end elements and only handles pairs of elements rather than triplets.
I have a somewhat longer approach to do this involving rotating deques and zipping them together:
from collections import deque
l_in = [0, 1, 2, 3]
deq = deque(l_in)
deq.rotate(1)
deq_prev = deque(deq)
deq.rotate(-2)
deq_next = deque(deq)
deq.rotate(1)
l_out = list(zip(deq_prev, deq, deq_next))
# l_out is [(3, 0, 1), (0, 1, 2), (1, 2, 3), (2, 3, 0)]
However, I feel like there is probably a more elegant (and/or efficient) way to do this using other built-in Python functionality. If, for instance, the rotate() function of deque returned the rotated list instead of modifying it in place, this could be a one- or two-liner (though this approach of zipping together rotated lists is perhaps not the most efficient). How can I accomplish this more elegantly and/or efficiently?
One approach may be to use itertools combined with more_itertools.windowed:
import itertools as it
import more_itertools as mit
l_in = [0, 1, 2, 3]
n = len(l_in)
list(it.islice(mit.windowed(it.cycle(l_in), 3), n-1, 2*n-1))
# [(3, 0, 1), (0, 1, 2), (1, 2, 3), (2, 3, 0)]
Here we generated an infinite cycle of sliding windows and sliced the desired subset.
FWIW, here is an abstraction of the latter code for a general, flexible solution given any iterable input e.g. range(5), "abcde", iter([0, 1, 2, 3]), etc.:
def get_windows(iterable, size=3, offset=-1):
"""Return an iterable of windows including an optional offset."""
it1, it2 = it.tee(iterable)
n = mit.ilen(it1)
return it.islice(mit.windowed(it.cycle(it2), size), n+offset, 2*n+offset)
list(get_windows(l_in))
# [(3, 0, 1), (0, 1, 2), (1, 2, 3), (2, 3, 0)]
list(get_windows("abc", size=2))
# [('c', 'a'), ('a', 'b'), ('b', 'c')]
list(get_windows(range(5), size=2, offset=-2))
# [(3, 4), (4, 0), (0, 1), (1, 2), (2, 3)]
Note: more-itertools is a separate library, easily installed via:
> pip install more_itertools
This can be done with slices:
l_in = [0, 1, 2, 3]
l_in = [l_in[-1]] + l_in + [l_in[0]]
l_out = [l_in[i:i+3] for i in range(len(l_in)-2)]
Well, or such a perversion:
div = len(l_in)
n = 3
l_out = [l_in[i % div: i % div + 3]
if len(l_in[i % div: i % div + 3]) == 3
else l_in[i % div: i % div + 3] + l_in[:3 - len(l_in[i % div: i % div + 3])]
for i in range(3, len(l_in) + 3 * n + 2)]
You can specify the number of iterations.
Well I figured out a better solution as I was writing the question, but I already went through the work of writing it, so here goes. This solution is at least much more concise:
l_out = list(zip(l_in[-1:] + l_in[:-1], l_in, l_in[1:] + l_in[:1]))
See this post for different answers on how to rotate lists in Python.
The one-line solution above should be at least as efficient as the solution in the question (based on my understanding) since the slicing should not be more expensive than the rotating and copying of the deques (see https://wiki.python.org/moin/TimeComplexity).
Other answers with more efficient (or elegant) solutions are still welcome though.
as you found there is a list rotation slicing based idiom lst[i:] + lst[:i]
using it inside a comprehension taking a variable n for the number of adjacent elements wanted is more general [lst[i:] + lst[:i] for i in range(n)]
so everything can be parameterized, the number of adjacent elements n in the cyclic rotation and the 'phase' p, the starting point if not the 'natural' 0 base index, although the default p=-1 is set to -1 to fit the apparant desired output
tst = list(range(4))
def rot(lst, n, p=-1):
return list(zip(*([lst[i+p:] + lst[:i+p] for i in range(n)])))
rot(tst, 3)
Out[2]: [(3, 0, 1), (0, 1, 2), (1, 2, 3), (2, 3, 0)]
showing the shortend code as per the comment

Python: Obtain edge end points of the graph

I need the edge end points from a graph. I have installed networkx. I have some idea how to proceed.
networkx.Graph.edges_iter() returns all the edges in the graph
[e for e in G.edges_iter()]
[(0, 1), (1, 2), (2, 3)]
What I want is a list [0,1,1,2,2,3]
How do I get this from the above data?
>>> import itertools
>>> list(itertools.chain(*[(0, 1), (1, 2), (2, 3)]))
[0, 1, 1, 2, 2, 3]
You may not need list(...) because its already iterable.
And you may also try itertools.chain(G.edges_iter()) directly
edgeList = []
for (a,b) in G.edges_iter():
edgeList.append(a)
edgeList.append(b)

Categories

Resources