How to sort a path of a graph - python

I've been trying sort the path of a graph.
For example, I have the following list in python.
graph = [
[4, 6], [6, 8], [8, 3], [3, 7], [7, 5], [5, 2], [1, 0], [0, 2], [4, 1]
]
The result needs to be,
graph = [
[0, 2], [2, 5], [5, 7], [7, 3], [3, 8], [8, 6], [6, 4], [4, 1], [1, 0]
]
0 -> 2 -> 5 -> 7 -> 3 -> 8 -> 6 -> 4 -> 1 -> 0
The premise is that the path begins with an edge whose initial value is zero (0) and ends with an edge whose last element is also zero.
Here is another example:
graph = [
[0, 4], [4, 6], [8, 3], [3, 7], [5, 2], [2, 1], [1, 0], [7, 6], [5, 8]
]
The result needs to be:
graph = [
[0, 1], [1, 2], [2, 5], [5, 8], [8, 3], [3, 7], [7, 6], [6, 4], [4, 0]
]
0 -> 1 -> 2 -> 5 -> 8 -> 3 -> 7 -> 6 -> 4 -> 0
The direction doesn't matter.
I started with this code.
def sort_graph(graph):
sorted_graph = []
for edge in graph:
if edge[0] == 0:
sorted_graph.append(edge)
for edge in graph:
if edge[0] != 0:
sorted_graph.append(edge)
return sorted_graph
but I'm not sure where to go from here.

For each node in the cycle, keep track of its two neighbors. You can then walk through these neighbors to produce an ordering of the nodes. Once you've reached a node where both neighbors have already been visited, you're done.
neighbors = {}
for fst, snd in graph:
neighbors.setdefault(fst, []).append(snd)
neighbors.setdefault(snd, []).append(fst)
seen_both_neighbors = False
current = 0
path = []
seen = set()
while not seen_both_neighbors:
path.append(current)
fst, snd = neighbors[current]
if fst not in seen:
seen.add(current)
current = fst
elif snd not in seen:
seen.add(current)
current = snd
else:
seen_both_neighbors = True
result = list(map(list, zip(path, path[1:] + [path[0]])))
print(result)
For both of your examples, this produces the correct answer down to ordering.

That was a fun problem to solve! Here's my idea for the approach:
Find all pairs (forward and backward)
Create a lookup table to easily navigate them
Start at 0, iterate through and remove nodes you've already visited
from itertools import chain
import random
graph = [
[4, 6], [6, 8], [8, 3], [3, 7], [7, 5], [5, 2], [1, 0], [0, 2], [4, 1]
]
# find all pairs independent of their direction
all_pairs = [*graph, *([t, f] for f, t in graph)]
# find all nodes
nodes = set(chain(*all_pairs))
# create a lookup dictionary for each point to show where you could go to
lookup = {node: {to_ for (from_, to_) in all_pairs if from_ == node} for node in nodes}
# simple solution - take a random path
from_ = 0
to_ = None
sorted_graph = []
while to_ != 0:
# select a random next point
to_ = random.choice(list(lookup[from_]))
# make sure to delete it so it doesn't get used again
lookup[from_].remove(to_)
lookup[to_].remove(from_)
# add to output
sorted_graph.append((from_, to_))
# tick one step forward
from_ = to_
print(sorted_graph)

You could also implement cycle,as below:
def cycle(vec):
result = [vec[0]]
s = vec[1:]
index = 0
while s:
if result[-1][0] == 0:
start = index
for i,v in enumerate(s):
if result[-1][1] in v:
del s[i]
result.append(v if v[0] == result[-1][1] else v[::-1])
break
index += 1
return result[start:] + result[:start]
cycle(graph)
[[0, 1], [1, 4], [4, 6], [6, 8], [8, 3], [3, 7], [7, 5], [5, 2], [2, 0]]

Related

Order the sub-lists in a nested list

I have a series of lists, and I want to combine them in a larger nested list. However, I want to order them in a certain way. I want the first sub-list to be the one whose first element is zero. Then i want the second sub-list to be the one whose first element is the same as the LAST element of the previous list.
For example, here's four sub-lists;
[0, 3], [7, 0], [3, 8], [8, 7]
I want to end up with this;
[[0, 3], [3, 8], [8, 7], [7,0]]
I can't for the life of me see the code logic in my head that would achieve this for me.
Can anyone help please?
UPDATE
Solved!
Many thanks to all who contributed!
I think of your list as being a collection of links which are to be arranged into a chain. Here is an approach which uses #quanrama 's idea of a dictionary keyed by the first element of that link:
links = [[0, 3], [7, 0], [3, 8], [8, 7]]
d = {link[0]:link for link in links}
chain = []
i = min(d)
while d:
link = d[i]
chain.append(link)
del d[i]
i = link[1]
print(chain) #[[0, 3], [3, 8], [8, 7], [7, 0]]
Another approach with a generator function:
links = [[0, 3], [7, 0], [3, 8], [8, 7]]
def get_path(links, *, start=0, end=0):
linkmap = dict(links)
key = start
while True:
link = linkmap[key]
yield [key,link]
key = link
if link == end:
break
print(list(get_path(links)))
print(list(get_path(links,start=3,end=3)))
# [[0, 3], [3, 8], [8, 7], [7, 0]]
# [[3, 8], [8, 7], [7, 0], [0, 3]]
You can try something like this:
source = [[0, 3], [7, 0], [3, 8], [8, 7]]
# Start at 0
last_val = 0
# this will be the output
l = []
while len(l)==0 or last_val!=0:
# Find the first value where the first element is last_val
l.append(next(i for i in source if i[0]==last_val))
# set last val to the second element of the list
last_val = l[-1][1]
print(l)

Removing list elements using recursion

This is my question for class:
• deepReverse(L) takes as input a list of elements where some of those elements may be lists
themselves. deepReverse returns the reversal of the list where, additionally, any element that is
a list is also deepReversed. Here are some examples:
deepReverse([1, 2, 3])
[3, 2, 1]
deepReverse([1, [2, 3], 4])
[4, [3, 2], 1]
deepReverse([1, [2, [3, 4], [5, [6, 7], 8]]])
[[[8, [7, 6], 5], [4, 3], 2], 1]
For this problem, you will need the ability to test whether or not an element in the list is a list itself. To this
end, you can use the following line of code to test whether or not x is a list:
if isinstance(x, list):
if True you will end up here
else:
if False you will end up here
this is the code i have so far:
def deepReverse(L):
if L == []:
return []
elif isinstance(L[0], list):
if True:
list2 = L[0]
return deepReverse(list2[1:]) + [list2[0]]
else:
deepReverse(L[1:]) + deepReverse(L[0])
else:
return deepReverse(L[1:]) + [L[0]]
for some reason it keeps returning [4,3,2,1] when i use the last test case. I do not understand why when I run the debugger once it gets to [3,4] going through each first element it just erases the second element. We are also not allowed to use any built in functions and must use recursion. Please help!
Assuming you cannot rely upon reversed -
def deep_rev(t):
if isinstance(t, list):
if not t:
return []
else:
return [ *deep_rev(t[1:]), deep_rev(t[0]) ]
else:
return t
print(deep_rev([1, [2, [3, 4], [5, [6, 7], 8]]]))
# [[[8, [7, 6], 5], [4, 3], 2], 1]
You can trade the *-unpacking for list concatenation, +, if you wish -
def deep_rev(t):
if isinstance(t, list):
if not t:
return []
else:
return deep_rev(t[1:]) + [ deep_rev(t[0]) ]
else:
return t
print(deep_rev([1, [2, [3, 4], [5, [6, 7], 8]]]))
# [[[8, [7, 6], 5], [4, 3], 2], 1]
The two programs above rely on list slicing, t[1:], to recur using a smaller list. Slicing the list for each iteration creates many intermediate values and we can avoid those using a different technique -
def deep_rev(t, k = 0):
try:
if isinstance(t, list):
v = t[k]
return deep_rev(t, k + 1) + [ deep_rev(v) ]
else:
return t
except IndexError:
return []
print(deep_rev([1, [2, [3, 4], [5, [6, 7], 8]]]))
# [[[8, [7, 6], 5], [4, 3], 2], 1]

How to change the index of a list?

I have two lists like below:
A = [[0, [1, 2]], [1, [3, 4]], [2, [5, 6]], [3, [7, 8]], [4, [9, 10]], [5, [11, 12]], [6, [13, 14]]]
and
B = [[0, [1, 2]], [1, [4, 5]], [4, [[7, 8], [9, 10]]]]
I want to replace some elements of A based on some conditions related to list B.
I have written a code that does what I'm looking for, as below:
x = 3
v = [0, 1, 4]
for i in range (x):
if i in v and B[i][0] == A[i][0]:
A[i][1][0] = B[i][1][1]
for elem in v:
if elem not in range(x):
A[elem][1][0] = B[2][1][1][0]
A[elem+1][1][0] = B[2][1][1][1]
else:
A = A
print (A)
My problem is with these lines:
for elem in v:
if elem not in range (x):
A[elem][1][0] = B[2][1][1][0]
A[elem+1][1][0] = B[2][1][1][1]
As you can see, after looking through the elements of list v, and check if those elements are not in range (x), in this case, that element is 4, I want to replace some elements of A with some elements of B in this case that element is [4, [[7, 8], [9, 10]]] , However, the index of this element in list B is 2. Is there any other way to use 4 in [4, [[7, 8], [9, 10]]] which is also an element of v inside the code instead of writing B[2]? I want to use [x[0] for x in B] as the indicators instead of using the indexes, as those are different.
Thanks
if you want to stick to the structure of your current code, you could use np.where for this
x= 3
v = [0, 1, 4]
import numpy as np
for i in range (x):
if i in v and B[i][0] == A[i][0]:
A [i][1][0] = B[i][1][1]
for elem in v:
if elem not in range (x):
# get index (in this case: 2)
ind = np.where(np.array([i[0] for i in B]) == elem)[0][0]
A [elem][1][0] = B[ind][1][1][0]
A [elem+1][1][0] = B[ind][1][1][1]
else:
A = A
print (A)

Rotating a matrix counter-clockwise changes the original as well

So I currently have a function to rotate a matrix counter-clockwise, the list is always a square grid:
def rotate(m: List[List[int]]) -> None:
temp = m.copy()
if len(m) > 1:
for x in range(len(m)):
for y in range(len(m)):
temp[len(m)-1-y][x] = m[x][y]
m = temp.copy()
I've traced this function repeatedly and to my knowledge it should be working. The problem is that for some reason every change to temp affects the original list. For example,
ORIGINAL =
[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
WHAT SHOULD OUTPUT =
[[3, 6, 9],
[2, 5, 8],
[1, 4, 7]]
WHAT ACTUALLY OUTPUTS =
[[3, 6, 1],
[2, 5, 2],
[1, 2, 1]
I am on python ver 3.7.0 and have tried slipicing instead of copying the string too but the same thing happens. Anyone know why?
Since every item in the m list is a reference to a sublist, when you make a copy of the m list by calling m.copy(), it is only copying the references of the sublists without creating new copies of the sublists, which is why every change to the temp list is reflected on the original m list.
You should use copy.deepcopy() instead to make copies of the sublists as well:
from copy import deepcopy
def rotate(m):
temp = deepcopy(m)
if len(m) > 1:
for x in range(len(m)):
for y in range(len(m)):
temp[len(m)-1-y][x] = m[x][y]
return temp
so that:
rotate([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
returns:
[[3, 6, 9],
[2, 5, 8],
[1, 4, 7]]
Alternatively, you can implement the same matrix rotation by zipping the list of lists and reversing it:
def rotate(m):
return list(zip(*m))[::-1]
import copy
def rotate(m: [[int]]):
temp = copy.deepcopy(m)
if len(m) > 1:
for x in range(len(m)):
for y in range(len(m)):
temp[len(m)-1-y][x] = m[x][y]
return temp
So:
rotate([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
Output:
[[3, 6, 9],
[2, 5, 8],
[1, 4, 7]]
Example: https://onlinegdb.com/ryGU1jD6X

Combining same elements into a list

I've been trying to convert my list
alist = [[1,[1,2]],[2,[3,4,5]],[3,[1,2]],[4,[3,4,5]],[5,[5,6,7]],[6,[1,2]]]
into this. Since the second item of those two sublists are same.
[[[1,3,6],[1,2]],[[2,4],[3,4,5]]]
This is my code
alist = [[1,[1,2]],[2,[3,4,5]],[3,[1,2]],[4,[3,4,5]],[5,[5,6,7]],[6,[1,2]]]
lst=[]
for i in range(len(alist)):
inner = []
inner1=[]
for j in range(i+1,len(alist)):
if i+1 < len(alist):
if alist[i][1] == alist[j][1]:
inner1.append(alist[i][0])
inner1.append(alist[j][0])
inner.append(inner1)
inner.append(alist[i][1])
lst.append(inner)
print(lst)
but it gives this instead
[[[1, 3, 1, 6], [1, 2], [1, 3, 1, 6], [1, 2]], [[1, 3, 1, 6], [1, 2], [1, 3, 1, 6], [1, 2]], [[2, 4], [3, 4, 5]], [[3, 6], [1, 2]]]
It works when there's only 2 elements that are the same but when there's 3 it doesn't work.
Example
[2,4],[3,4,5] #2 of the same elements from alist works
[1,3,1,6],[1,2] #3 of the same elements from alist doesn't work
Can anyone please offer a solution?
You can use a dict (an Ordered one since you have to maintain the order) to group "heads" by "tails":
alist = [[1,[1,2]],[2,[3,4,5]],[3,[1,2]],[4,[3,4,5]],[5,[5,6,7]],[6,[1,2]]]
from collections import OrderedDict
c = OrderedDict()
for head, tail in alist:
c.setdefault(tuple(tail), []).append(head)
res = [[heads, list(tail)] for tail, heads in c.items()]
print res
prints
[[[1, 3, 6], [1, 2]], [[2, 4], [3, 4, 5]], [[5], [5, 6, 7]]]
If you want to omit 5 (a group with a single "head"), add a condition to the res= line:
res = [[heads, list(tail)] for tail, heads in c.items() if len(heads) > 1]

Categories

Resources