Detecting a Cycle in a Directed Graph Python - python

I am trying to extract a directed graph from a text file that looks like this (1: is node, second number is neighbor, followed by weight, neighbor, weight, etc:
1: 2 3 4 5 6 2
2: 3 -4
3: 8 4
4: 5 6
5: 4 -3 8 8
6: 7 3
7: 6 -6 8 7
8:
Below is my code:
fileName = ("graphin_Fig1.txt")
dictionary = {} # allocate dictionary space
with open(fileName,'r') as file: # open file
for line in file: # read each line of the file
tokens = line.strip().split(' ') # separate each number, remove white space
node = int (tokens[0].split(':')[0]) # remove :, separate node from neighbors and weights
pos = 1 # initialize counter
listtups = [] # allocate array to hold neghbors and weights
while pos < len(tokens): # while end of line has not been reached
listtups.append((int(tokens[pos]), int (tokens[pos++1]))) # add ints to array
pos += 2 # counter
if len(listtups) > 0: # if neigbors and edges are detected
dictionary[node] = listtups # add to nodes
print (dictionary)
This is the output:
`{1: [(2, 3), (4, 5), (6, 2)], 2: [(3, -4)], 3: [(8, 4)], 4: [(5, 6)], 5: [(4, -3), (8, 8)], 6: [(7, 3)], 7: [(6, -6), (8, 7)]}`
I am trying to use my dictionary for other algorithms, but they will only work if the dictionary looks like the example below:
dictionary = {
1: {2: 3, 4: 5, 6: 2},
2: {3: -4},
3: {8: 4},
4: {5: 6},
5: {4: -3, 8: 8},
6: {7: 3},
7: {6: -6, 8: 7},
8: {}
}
I am new to python and really struggling. How do I change my code to make my extracted dictionary look like the example directly above?

You are very close. The only thing left is to convert your list of tuples to dictionary, so instead of:
dictionary[node] = listtups # add to nodes
try
dictionary[node] = dict(listtups)
And remove if statement that checks for length of neigthbours, as your example shows that you want to add node even when there is no adjacent nodes.

Well, you don't have the same datastructure at all. What you want is a dictionary that contains dictionaries. E.g.:
outer_d = {"inner_d":{...}, ...}
What you're doing is a dictionary, whose values for each key isn't a dictionary but a list (of tuples), e.g.:
outer_d = {"key":[...], ... }
Do you understand the difference?
fileName = ("graphin_Fig1.txt")
dictionary = {}
with open(fileName,'r') as file:
for line in file:
tokens = line.strip().split(' ')
node = int (tokens[0].split(':')[0])
pos = 1 # initialize counter
listtups = {} # another dictionary, not a list!
while pos < len(tokens):
listtups[int(tokens[pos])] = int(tokens[pos++1])
pos += 2 # counter
if len(listtups) > 0:
dictionary[node] = listtups
print (dictionary)
Which yields:
{1: {2: 3, 4: 5, 6: 2}, 2: {3: -4}, 3: {8: 4}, 4: {5: 6}, 5: {4: -3, 8: 8}, 6: {7: 3}, 7: {6: -6, 8: 7}}

Related

how to subtract one list from another including duplicates

I have 2 lists
On is a big list with some elements having duplicates
super_set_list = [1,1,2,3,3,4,4,4,5,6,7,8,9]
The other is a subset of the big list, also with duplicates
sub_set_list = [1,2,3,3,4,4,6,7,9]
I want the difference, like this
diff = [1,4,5,8]
Not sure how I would go about this
You can use a Counter
super_set_list = [1,1,1,2,3,3,4,4,4,5,6,7,8,9]
sub_set_list = [1,2,3,3,4,4,6,7,9]
from collections import Counter
super_counter = Counter(super_set_list)
super_counter = Counter({1: 3, 4: 3, 3: 2, 2: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1})
For every element in sub_set_list, reduce the count in in super_counter
for item in sub_set_list:
super_counter[item]-=1
Now super_counter = Counter({1: 2, 4: 1, 5: 1, 8: 1, 2: 0, 3: 0, 6: 0, 7: 0, 9: 0})
Finally, just pick elements that have some count left (but add it that many number of times).
diff=[]
for k,v in super_counter.items():
for _ in range(v):
diff.append(k)
print(diff)
# [1, 1, 4, 5, 8]
You can loop through sub-set list and remove item in super-set list one by one as follows:
super_set_list = [1,1,2,3,3,4,4,4,5,6,7,8,9]
sub_set_list = [1,2,3,3,4,4,6,7,9]
for item in sub_set_list:
if item in super_set_list:
super_set_list.remove(item)
print(super_set_list)

python nested list retrieve and print specific elements

I have a nested list that looks like this. It is supposed to represent people and their possible connections. I need to output the number in quotes along with connections they might know (numbers in the sublist with 2 or 3 after the colon along with a list of people they probably (numbers in the sublist with 4 or more after the colon),so it looks like this:
6:Might(1,3,4,7,11)
1:Might(4,6,7) Probably(11)
4:Might(1,3,6,11,12)
here is my nested list
connect_cnt = [(6, {3: 3, 4: 3, 7: 2, 13: 1, 1: 3, 11: 2, 12: 1}),
(1, {7: 3, 5: 1, 9: 2, 11: 4, 10: 1 , 2: 1, 13: 1, 3: 2, 6: 3, 4: 3, 12: 2}),
(4, {3: 2, 7: 3, 6: 3, 13: 1, 1: 3, 11: 2,12: 3, 5: 1, 2: 1, 9: 1}),
I am not sure how to go about it. I tried this for starters just to see if I could get the count but it didn't work.
final_dict
for row in range(len(connect_cnt)):
for col in range(len(connect_cnt[row])):
key,value = connect_cnt[row].split(':')
if value > 1:
final_dict[key] = value
Even if I cold just get the ones I need to print in a nested list. I am a beginner and don't have much experience with nested lists.
I'm not sure of the exact solution to your answer, but I can provide a generalized solution which you can adapt to your needs.
l = [(1, {2: 3, 4: 5}), (6, {7: 8, 9: 10})]
# to change a value
l[0][1][2] = 100 # now l = [(1, {2: 100, 4: 5}), (6, {7: 8, 9: 10})]
If this does not work for your example, please provide more information, and I will try my best to help out.
Edit:
This code should extract all key-value pairs to another dictionary, given that a dictionary are always the second element of the sub-list.
final_dict = {}
for i in l:
for j in i[1].keys():
final_dict[j] = i[j]
Edit 2:
I think I understand now. This code should do it:
for i in l:
for j in i[1].keys():
if i[1][j] <= 1:
del i[1][j]
Edit 3:
Sorry. Let me know if this works:
l2 = []
for i in l:
d = {}
for j in i[1].keys():
if i[1][j] > 1:
d[j] = i[1][j]
l2.append((i[0], d))

Python format and print data in specific order from nested List

I have a list that looks like this:
[(6, {3: 3, 4: 3, 7: 2, 1: 3, 11: 2}),
(1, {7: 3, 9: 2, 11: 4, 3: 2, 6: 3, 4: 3, 12: 2}),
(4, {3: 2, 7: 3, 6: 3, 1: 3, 11: 2, 12: 3}), (2, {9: 4, 8: 2, 10: 2, 5: 2})
and I need to format it and print it so it looks like this:
6:Might(1,3,4,7,11)
1:Might(4,6,7) Probably(11)
4:Might(1,3,6,11,12)
13:
I have tried this:
for item in connect_out:
name = item[0]
pair_dict = item[1]
might = []
probably = []
for key, value in pair_dict.items():
if value > 3:
probably.append(str(value))
else:
might.append(str(value))
might = sorted(might)
probably = sorted(probably)
print("%s:Might(%s) Probably(%s)" % (name, ','.join(might),','(probably)) )
and I got this:
6:Might(2,2,3,3,3) Probably()
1:Might(2,2,2,3,3,3) Probably(4)
4:Might(2,2,3,3,3,3) Probably()
13:Might() Probably()
If there is nothing in the might or probably list neither title should print. I put some print statements in and it looks like it is putting the number after the colon in the might list instead of the one before it.
You'll want to do some preprocessing before sending this off to print()
You can start with something like this:
values = [(6, {3: 3, 4: 3, 7: 2, 1: 3, 11: 2}),
(1, {7: 3, 9: 2, 11: 4, 3: 2, 6: 3, 4: 3, 12: 2}),
(4, {3: 2, 7: 3, 6: 3, 1: 3, 11: 2, 12: 3}), (2, {9: 4, 8: 2, 10: 2, 5: 2})]
for item in values:
name = item[0]
pair_dict = item[1]
might = []
probably = []
for key, value in pair_dict.items():
if value > 3:
probably.append(value)
else:
might.append(value)
might = sorted(might)
probably = sorted(probably)
print("%s:Might(%s) Probably(%s)" % (name, ','.join(might), ','.join(probably))
EDIT: After tinkering a bit and better understanding your requirements, try this on for size:
values = [(6, {3: 3, 4: 3, 7: 2, 1: 3, 11: 2}),
(1, {7: 3, 9: 2, 11: 4, 3: 2, 6: 3, 4: 3, 12: 2}),
(4, {3: 2, 7: 3, 6: 3, 1: 3, 11: 2, 12: 3}), (2, {9: 4, 8: 2, 10: 2, 5: 2})]
for item in values:
name = item[0]
pair_dict = item[1]
might = []
probably = []
for key, value in pair_dict.items():
if value > 3:
probably.append(value)
else:
might.append(value)
might = [str(i) for i in sorted(might)]
probably = [str(i) for i in sorted(probably)]
mightstr = "" if len(might) == 0 else "Might(%s) " % ','.join(might)
probablystr = "" if len(probably) == 0 else "Probably(%s)" % ','.join(probably)
print("%s:%s%s" % (name, mightstr, probablystr))

how to use list comprehension to print a frequency dictionary?

Let's say our test case is: solution([6, 7, 7, 8, 8, 8, 9, 10, 10], 1)
where 1 is the maximum frequency to remain in the list. Logically, this translates to an expected output of [6, 9].
My attempt at a solution:
def solution(data, n):
occurrences = dict()
for _id in data:
try:
occurrences[_id] += 1
except KeyError:
occurrences[_id] = 1
print(occurrences)
return [_id for _ in range(occurrences[_id]) for _id in occurrences if occurrences[_id] <= n]
What I am trying to accomplish with the list comprehension at the end is
for key in occurrences:
if occurrences[key] <= 1:
for _ in range(occurrences[key]):
print(key)
Unfortunately, this is the output of this test case:
>>> solution([6, 7, 7, 8, 8, 8, 9, 10, 10], 1)
{6: 1}
{6: 1, 7: 1}
{6: 1, 7: 2}
{6: 1, 7: 2, 8: 1}
{6: 1, 7: 2, 8: 2}
{6: 1, 7: 2, 8: 3}
{6: 1, 7: 2, 8: 3, 9: 1}
{6: 1, 7: 2, 8: 3, 9: 1, 10: 1}
{6: 1, 7: 2, 8: 3, 9: 1, 10: 2}
[6, 9, 6, 9]
Something has to be broken in the list comprehension that is writing 6 and 9 2 times instead of range(1).
Any idea what that could be?
Your issue is caused by the for _ in range(occurrences[_id]. The reason it causes duplication of the result is that in this comprehension
return [_id for _ in range(occurrences[_id]) for _id in occurrences if occurrences[_id] <= n]
_id is not defined inside the comprehension when you execute range(occurrences[_id]) so it takes the value that _id last had in the function, which in this case is 10 and occurrences[10] is 2, so you get 2 copies of each _id. Had you used a different variable name inside the comprehension e.g.
return [xid for _ in range(occurrences[xid]) for xid in occurrences if occurrences[xid] <= n]
you would have seen the issue because you would have got a
NameError: name 'xid' is not defined
Note you can greatly simplify your code by using a Counter:
from collections import Counter
def solution(data, n):
counts = Counter(data)
return [_id for _id, _count in counts.items() if _count <= n for _ in range(_count)]
Oh snap, so I randomly decided to try plugging in the for loops the other way around and it worked...
return [_id for _id in occurrences if occurrences[_id] <= n for _ in range(occurrences[_id])]
sorry for the trouble!

Select only one unique element from multiple lists in Python

This is not a homework that I'm struggling to do but I am trying to solve a problem (here is the link if interested https://open.kattis.com/problems/azulejos).
Here you actually don't have to understand the problem but what I would like to accomplish now is that I want to select only one element from multiple lists and they do not overlap with each other.
For example, at the end of my code, I get an output:
{1: [1, 2, 3], 2: [1, 2, 3, 4], 3: [2, 4], 4: [1, 2, 3, 4]}
I would like to transform this into, for example,
{3: 4, 2: 2, 4:1, 1: 3} -- which is the sample answer that is in the website.
But from my understanding, it can also be simply
{1: 3, 2: 2, 3: 4, 4: 1}
I am struggling to select only one integer that does not overlap with the others. The dictionary I produce in my code contains lists with multiple integers. And I would like to pick only one from each and they are all unique
import sys
n_tiles_row = int(sys.stdin.readline().rstrip())
# print(n_tiles_row) ==> 4
# BACK ROW - JOAO
back_row_price = sys.stdin.readline().rstrip()
# print(back_row_price) ==> 3 2 1 2
back_row_height = sys.stdin.readline().rstrip()
# print(back_row_height) ==> 2 3 4 3
# FRONT ROW - MARIA
front_row_price = sys.stdin.readline().rstrip()
# print(front_row_price) ==> 2 1 2 1
front_row_height = sys.stdin.readline().rstrip()
# print(front_row_height) ==> 2 2 1 3
br_num1_price, br_num2_price, br_num3_price, br_num4_price = map(int, back_row_price.split())
# br_num1_price = 3; br_num2_price = 2; br_num3_price = 1; br_num4_price = 2;
br_num1_height, br_num2_height, br_num3_height, br_num4_height = map(int, back_row_height.split())
# 2 3 4 3
fr_num1_price, fr_num2_price, fr_num3_price, fr_num4_price = map(int, front_row_price.split())
# 2 1 2 1
fr_num1_height, fr_num2_height, fr_num3_height, fr_num4_height = map(int, front_row_height.split())
# 2 2 1 3
back_row = {1: [br_num1_price, br_num1_height],
2: [br_num2_price, br_num2_height],
3: [br_num3_price, br_num3_height],
4: [br_num4_price, br_num4_height]}
# {1: [3, 2], 2: [2, 3], 3: [1, 4], 4: [2, 3]}
front_row = {1: [fr_num1_price, fr_num1_height],
2: [fr_num2_price, fr_num2_height],
3: [fr_num3_price, fr_num3_height],
4: [fr_num4_price, fr_num4_height]}
# {1: [2, 2], 2: [1, 2], 3: [2, 1], 4: [1, 3]}
_dict = {1: [],
2: [],
3: [],
4: []
}
for i in range(n_tiles_row):
_list = []
for n in range(n_tiles_row):
if(list(back_row.values())[i][0] >= list(front_row.values())[n][0]
and list(back_row.values())[i][1] >= list(front_row.values())[n][1]):
_list.append(list(front_row.keys())[n])
_dict[list(back_row.keys())[i]] = _list
print(_dict)
# {1: [1, 2, 3], 2: [1, 2, 3, 4], 3: [2, 4], 4: [1, 2, 3, 4]}
Please let me know if there is another approach to this problem.
Here is a solution using the same syntax as the code you provided.
The trick here was to order the tiles first by price ascending (the question asked for non-descending) then by height descending such that the tallest tile of the next lowest price in the back row would be matched the tallest tile of the next lowest price in the front row.
To do this sorting I utilized Python's sorted() function. See a Stack Overflow example here.
I assumed if there was no such match then immediately break and print according to the problem you linked.
As a side note, you had originally claimed that a python dictionary
{3: 4, 2: 2, 4:1, 1: 3} was equivalent to {1: 3, 2: 2, 3: 4, 4: 1}. While you are correct, you must remember that in Python dictionary objects are unsorted by default so it is not easy to compare keys this way.
import sys
n_tiles_row = int(sys.stdin.readline().rstrip())
# print(n_tiles_row) ==> 4
# BACK ROW - JOAO
back_row_price = sys.stdin.readline().rstrip()
# print(back_row_price) ==> 3 2 1 2
back_row_height = sys.stdin.readline().rstrip()
# print(back_row_height) ==> 2 3 4 3
# FRONT ROW - MARIA
front_row_price = sys.stdin.readline().rstrip()
# print(front_row_price) ==> 2 1 2 1
front_row_height = sys.stdin.readline().rstrip()
# print(front_row_height) ==> 2 2 1 3
# preprocess data into lists of ints
back_row_price = [int(x) for x in back_row_price.strip().split(' ')]
back_row_height = [int(x) for x in back_row_height.strip().split(' ')]
front_row_price = [int(x) for x in front_row_price.strip().split(' ')]
front_row_height = [int(x) for x in front_row_height.strip().split(' ')]
# store each tile into lists of tuples
front = list()
back = list()
for i in range(n_tiles_row):
back.append((i, back_row_price[i], back_row_height[i])) # tuples of (tile_num, price, height)
front.append((i, front_row_price[i], front_row_height[i]))
# sort tiles by price first (as the price must be non-descending) then by height descending
back = sorted(back, key=lambda x: (x[1], -x[2]))
front = sorted(front, key=lambda x: (x[1], -x[2]))
# print(back) ==> [(2, 1, 4), (1, 2, 3), (3, 2, 3), (0, 3, 2)]
# print(front) ==> [(3, 1, 3), (1, 1, 2), (0, 2, 2), (2, 2, 1)]
possible_back_tile_order = list()
possible_front_tile_order = list()
for i in range(n_tiles_row):
if back[i][2] > front[i][2]: # if next lowest priced back tile is taller than next lowest priced front tile
possible_back_tile_order.append(back[i][0])
possible_front_tile_order.append(front[i][0])
else:
break
if len(possible_back_tile_order) < n_tiles_row: # check that all tiles had matching pairs in back and front
print("impossible")
else:
print(possible_back_tile_order)
print(possible_front_tile_order)
A, possibly inefficient, way of solving the issue, is to generate all possible "solutions" (with values potentially not present in the lists corresponding to a specific key) and settle for a "valid" one (for which all values are present in the corresponding lists).
One way of doing this with itertools.permutation (that is able to compute all possible solutions satisfying the uniqueness constraint) would be:
import itertools
def gen_valid(source):
keys = source.keys()
possible_values = set(x for k, v in source.items() for x in v)
for values in itertools.permutations(possible_values):
result = {k: v for k, v in zip(keys, values)}
# : check that `result` is valid
if all(v in source[k] for k, v in result.items()):
yield result
d = {1: [1, 2, 3], 2: [1, 2, 3, 4], 3: [2, 4], 4: [1, 2, 3, 4]}
next(gen_valid(d))
# {1: 1, 2: 2, 3: 4, 4: 3}
list(gen_valid(d))
# [{1: 1, 2: 2, 3: 4, 4: 3},
# {1: 1, 2: 3, 3: 2, 4: 4},
# {1: 1, 2: 3, 3: 4, 4: 2},
# {1: 1, 2: 4, 3: 2, 4: 3},
# {1: 2, 2: 1, 3: 4, 4: 3},
# {1: 2, 2: 3, 3: 4, 4: 1},
# {1: 3, 2: 1, 3: 2, 4: 4},
# {1: 3, 2: 1, 3: 4, 4: 2},
# {1: 3, 2: 2, 3: 4, 4: 1},
# {1: 3, 2: 4, 3: 2, 4: 1}]
This generates n! solutions.
The "brute force" approach using a Cartesian product over the lists, produces prod(n_k) = n_1 * n_1 * ... * n_k solutions (with n_k the length of each list). In the worst case scenario (maximum density) this is n ** n solutions, which is asymptotically much worse than the factorial.
In the best case scenario (minimum density) this is 1 solution only.
In general, this can be either slower or faster than the "permutation solution" proposed above, depending on the "sparsity" of the lists.
For an average n_k of approx. n / 2, n! is smaller/faster for n >= 6.
For an average n_k of approx. n * (3 / 4), n! is smaller/faster for n >= 4.
In this example there are 4! == 4 * 3 * 2 * 1 == 24 permutation solutions, and 3 * 4 * 2 * 4 == 96 Cartesian product solutions.

Categories