Extracting a graph from a text file to a python graph - python

I have a text file with the following graph inside:
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've been trying to extract this graph into a python graph that is formatted like the example below:
graph = {
'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 cannot figure it out. I've tried using the code below, but it just loads the graph into an array. I am unsure of how to form it into the graph example above.
graph = []
with open(fileName,'r') as file:
for line in file:
for line in line[:-1].split():
graph.append(line)
output of above code:
['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:']

Try storing a variable and slicing every other value in the lines to create the key - value pairs to construct the dictionary, if there is nothing after the colon, it feeds and empty dictionary:
graph = []
with open(fileName,'r') as file:
for line in file:
graph.append({line.split(':')[0].replace(' ', ''): (dict(zip(line.split(': ')[1].split()[::2], map(int, line.split(': ')[1].split()[1::2]))) if ': ' in line else {})})

You can do this by splitting each line by spaces, then you can take the first element of the split and get rid of the ':' to get the start of the edge and process the rest of the entries two at a time, the first being the other node in the edge and the entry being the number. Then you can just put each intermediate result into a dictionary:
graph = {}
with open(fileName,'r') as file:
for line in file:
s = line.split(' ')
u = s[0].strip(':')
graph[u] = {}
for i in range(1, len(s), 2):
v = s[i]
c = int(s[i + 1])
graph[u][v] = c

Related

Random choice function adds invinsible "0"?

I wanted to define a function that gets me the set amount of keys in a dictionary and puts it in a list.
The function though adds a "0" to the list, which is not a key and also exceeds the set amount?
I tried the code in 2 different editors (VS Code and Thonny), both did this in like 40% of the time...
The Code:
import random
def random_card(times):
chosen_card = []
for t in range(times):
chosen_card += random.choice(list(cards))
return chosen_card
cards = {
'A': 1,
'2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10,
'J': 10, 'Q': 10, 'K': 10
}
player_cards = random_card(2)
pc_cards = random_card(2)
print(f"Your cards: {player_cards}\nDealer cards: {pc_cards}")
print(list(cards))
Outputs with the "0":
Your cards: ['8', '1', '0']
Dealer cards: ['9', '1', '0']
['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
Your cards: ['Q', '1', '0']
Dealer cards: ['7', '1', '0']
['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
Your cards: ['J', '1', '0']
Dealer cards: ['Q', '4']
['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
Problem lies here
chosen_card += random.choice(list(cards))
using += cause problems for any string of length greater than 1, in your case it is 10. Consider following simple examples
cards = []
cards += "7"
print(cards) # ['7']
cards += "10"
print(cards) # ['7', '1', '0']
It did add 1 then 0 rather than 10. If you have to use += AT ANY PRICE then encase added value in list, namely
cards = []
cards += ["10"]
print(cards) # ['10']
otherwise you might use .append which is method altering list inplace, following way
cards = []
cards.append("10")
print(cards) # ['10']
This can all be fixed if you use the choices function from the random module instead. That is, simply do the following
import random
cards = {
'A': 1,
'2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10,
'J': 10, 'Q': 10, 'K': 10
}
player_cards = random.choices(list(cards),2)
pc_cards = random.choices(list(cards),2)

I tried to created dictionary in dictionary but I struggled

I tried to created a get_dict function that takes a parameter as a filename and then creates and returns a dictionary which contains
key is the number of the product code and has
value is a dictionary that contains
key is a string of sizes (S, M, L, or XL), and
value is the number of the product.
enter image description here
I tried this.
def get_dict(file_name):
d={}
e={}
with open(file_name) as f:
for line in f:
line = line.strip()
alist = line.split()
e[alist[1]] = alist[2]
d[alist[0]] = e
print (d)
the output is look like this
{'4125': {'M': '4', 'L': '7', 'XL': '3'}, '5645': {'M': '4', 'L': '7', 'XL': '3'}, '7845': {'M': '4', 'L': '7', 'XL': '3'}}
but I expect that output will be like this
{4125: {'S': 1, 'M': 4}, 5645: {'L': 7}, 9874: {'S': 8}, 9875: {'M': 8}, 7845: {'S': 10, 'XL': 3}}
Text file example
7845 XL 3
4125 S 1
5645 L 7
9874 S 3
4125 M 4
def get_dict(file_name):
d={}
with open(file_name) as f:
for line in f:
line = line.strip()
alist = line.split()
if not alist[0] in d:
d[alist[0]] = {alist[1]: alist[2]}
else:
d[alist[0]].update({alist[1]: alist[2]})
print(d)
You have to update the dictionary instead of overwriting the same key value. The above solution should work.
Output -
{'7845': {'XL': '3'}, '4125': {'S': '1', 'M': '4'}, '5645': {'L': '7'}, '9874': {'S': '3'}}

How Connect Lines That Have Mutual Joints

I have a list of lines like this:
Lines = ['1', '2', '3', '4', '5', '6', '7', '8']
each line has two points I and J:
LinesDetail = {
'1': {
'I': '100',
'J': '101'},
'2': {
'I': '101',
'J': '102'},
'3': {
'I': '256',
'J': '257'},
'4': {
'I': '257',
'J': '258'},
'5': {
'I': '258',
'J': '259'},
'6': {
'I': '304',
'J': '305'},
'7': {
'I': '305',
'J': '306'},
'8': {
'I': '102',
'J': '103'}}
As you see in the picture, some of these lines have mutual points
so they are connected to each other and I need to know which lines are connected to each other.
I tried while loop but I don't have the basic idea of how to solve this kind of problems.
and the result would be:
result = [["1","2","8"],["3","4","5"],["6","7"]]
All Lines Are Vertical
This is a graph problem of finding connected components. A possible interpretation is that the outer keys are labels and the inner dictionaries are the edges (and inner dict values are the nodes). If dependency is not an issue, Python has a nice API networkx that deals with graphs. Specifically, one can use the UnionFind data structure to find the disjoint subsets.
from networkx.utils.union_find import UnionFind
# reverse the label-edge mapping to get a mapping from nodes to edge labels
edges = {}
for k, d in LinesDetail.items():
for v in d.values():
edges.setdefault(v, []).append(k)
# construct union-find data structure
c = UnionFind()
for lst in edges.values():
c.union(*lst)
# get the disjoint sets as sorted lists
result = list(map(sorted, c.to_sets()))
result
# [['1', '2', '8'], ['3', '4', '5'], ['6', '7']]
Not the most optimal solution, but putting it out there since I worked on it
LinesDetail = {
'1': {
'I': '100',
'J': '101'},
'2': {
'I': '101',
'J': '102'},
'3': {
'I': '256',
'J': '257'},
'4': {
'I': '257',
'J': '258'},
'5': {
'I': '258',
'J': '259'},
'6': {
'I': '304',
'J': '305'},
'7': {
'I': '305',
'J': '306'},
'8': {
'I': '102',
'J': '103'}}
Lines = ['1', '2', '3', '4', '5', '6', '7', '8']
results = []
for item in Lines:
match_this = LinesDetail[item]['J']
list_form = []
for key, value in LinesDetail.items():
if match_this == value['I']:
results.append([item, key])
needed_list = []
for i in range(0, len(results)-1):
if results[i][1] == results[i+1][0]:
yes_list = results[i][:1] + results[i+1]
needed_list.append(yes_list)
else:
try:
if results[i][1] == results[i+2][0]:
continue
except:
needed_list.append(results[i+1])
print(needed_list)
output:
[['1', '2', '8'], ['3', '4', '5'], ['6', '7']]

print an array vertically

I recently found out how to print an array vertically but I have yet to find out how to choose when to increment it
array = ['1', '2', '3', '5', '7', '11', '13',]
array = map(str,array)
array = list(array)
print("\n".join(array))
output example:
1
2
3
5
7
11
13
output goal:
1, 2, 3, 5, 7,
11, 13,
array = ['1', '2', '3', '5', '7', '11', '13',]
for i in range(len(array)):
print(array[i], end=', ')
if i % 5 == 4:
print()
To perform the desired print out of the provided array, this will work (with the addition of a newline in between):
array = ['1', '2', '3', '5', '7', '11', '13']
for n in array[:-2]:
print(n + ',', end=' ')
print('\n')
for n in array[-2:]:
print(n + ',', end=' ')
Returns:
1, 2, 3, 5, 7,
11, 13,

How to calculate sum of path's weight in N allowable moves defined in graph but the destination node is not fixed using Python

gr = {'0': {'4': 4, '6': 6},
'4': {'3': 7, '9': 13, '0':4},
'6': {'1': 7, '7':13,'0':6},
'3': {'4': 7, '8': 11},
'9': {'4': 13, '2': 11},
'1': {'6': 7, '8': 9},
'7': {'2': 9, '6': 13},
'8': {'1': 9, '3': 11},
'2': {'9': 11, '7': 9}}
This is the graph, I have made for the allowable moves and defined the weights in moves as I need to calculate sum of the weight of the path after certain number of moves(n) starting from '0'. The path can be random but from within those paths defined in graph, given the destination is not defined.
I have tried the functions like this where the parameters given are starting and ending point. This works to find the path and calculate the weight traveled. But I need to be able to have the parameters as starting point and the number of moves but not the destination and find the sum of path's weight. Also the nodes can be visited as many number of times. Please help me to edit this function or may be my approach should be different.
def paths(gr, frm, to, path_len = 0, visited = None):
if frm == to:
return [[to, path_len]]
visited = visited or []
result = []
for point, length in gr[frm].items():
if point in visited:
continue
visited.append(point)
for sub_path in paths(gr, point, to, path_len + length, visited[:]):
result.append([frm] + sub_path)
return result
print (paths(gr, '0', '9'))
My Desired Output is:
Path: 0->4->3->8>1->6->7->2->9->4, Sum: 44
Path: 0->6->7->2->9->4->3->8->1->6, Sum: 46
From the comments:
The problem statement is "It chooses amongst the allowable moves uniformly at random and keeps track of the running sum S of nodes on which it lands." So my problem is to find the sum S of all the nodes on which it lands in K moves.
Here's a diagram of the graph that I created using some Python code and Graphviz.
In the code you posted you have a visited list. The purpose of that list is to prevent a node from being visited more than once. However, you aren't adding the initial frm node to the visited list, so that node can get visited twice. Here's a repaired version:
gr = {
'0': {'4': 4, '6': 6},
'4': {'3': 7, '9': 13, '0': 4},
'6': {'1': 7, '7':13, '0': 6},
'3': {'4': 7, '8': 11},
'9': {'4': 13, '2': 11},
'1': {'6': 7, '8': 9},
'7': {'2': 9, '6': 13},
'8': {'1': 9, '3': 11},
'2': {'9': 11, '7': 9},
}
def paths(gr, frm, to, path_len=0, visited=None):
if frm == to:
return [[to, path_len]]
visited = visited or [frm]
result = []
for point, length in gr[frm].items():
if point in visited:
continue
for sub_path in paths(gr, point, to, path_len + length, visited + [point]):
result.append([frm] + sub_path)
return result
# Test
frm, to = '2', '8'
for p in paths(gr, frm, to):
print(p)
output
['2', '9', '4', '0', '6', '1', '8', 50]
['2', '9', '4', '3', '8', 42]
['2', '7', '6', '0', '4', '3', '8', 50]
['2', '7', '6', '1', '8', 38]
As Antti mentioned in the comments, it's better to do this using a generator which yields paths as it finds them, rather than saving all the results in a big list that gets returned at the end. And we can make the "visited" test more efficient by using a set instead of a list:
def paths(gr, frm, to, path_len=0, visited=None):
if frm == to:
yield [to, path_len]
return
visited = visited or {frm}
for point, length in gr[frm].items():
if point in visited:
continue
for sub_path in paths(gr, point, to, path_len + length, visited | {point}):
yield [frm] + sub_path
We can use a similar approach to generate all the paths of a fixed length from a given starting node.
gr = {
'0': {'4': 4, '6': 6},
'4': {'3': 7, '9': 13, '0': 4},
'6': {'1': 7, '7':13, '0': 6},
'3': {'4': 7, '8': 11},
'9': {'4': 13, '2': 11},
'1': {'6': 7, '8': 9},
'7': {'2': 9, '6': 13},
'8': {'1': 9, '3': 11},
'2': {'9': 11, '7': 9},
}
def paths_by_length(gr, frm, steps, path_len=0, path=None):
if steps == 0:
yield path, path_len
return
path = path or [frm]
steps -= 1
for point, weight in gr[frm].items():
new_path = path + [point]
new_len = path_len + weight
for sub_path, sub_length in paths_by_length(gr, point, steps, new_len, new_path):
yield sub_path, sub_length
frm = '0'
steps = 3
for path, path_len in paths_by_length(gr, frm, steps):
print(path, path_len)
output
['0', '4', '9', '2'] 28
['0', '4', '9', '4'] 30
['0', '4', '0', '4'] 12
['0', '4', '0', '6'] 14
['0', '4', '3', '4'] 18
['0', '4', '3', '8'] 22
['0', '6', '7', '2'] 28
['0', '6', '7', '6'] 32
['0', '6', '1', '8'] 22
['0', '6', '1', '6'] 20
['0', '6', '0', '4'] 16
['0', '6', '0', '6'] 18
Because your graph has such a simple structure, and the edge weights conform to weight = frm + to there's probably a more efficient way to do this. Also, you could simplify gr in various ways, eg you could use integers instead of strings for the node names, which would allow gr to be a list or tuple instead of a dict, and instead of each node having a dict of (node, weight) pairs, each node could just be a list or tuple of the nodes it connects to, since it's so easy to calculate the edge weights.
Update
Your actual problem is much simpler than what you originally asked for.
We don't need to use recursion for this, we can just use a simple for loop which calls the choice function from the random module to choose moves uniformly at random.
from random import choice, seed
gr = (
(4, 6), # 0
(6, 8), # 1
(7, 9), # 2
(4, 8), # 3
(0, 3, 9), # 4
(), # 5
(0, 1, 7), # 6
(2, 6), # 7
(1, 3), # 8
(2, 4), # 9
)
# Seed the randomizer
seed(42)
def random_path(gr, node, steps):
path = [node]
for i in range(steps):
node = choice(gr[node])
path.append(node)
return path
# Test
frm = 0
steps = 3
for i in range(10):
path = random_path(gr, frm, steps)
print(path, sum(path))
print()
# Find the mean score of a number of paths
steps = 1024
num = 10000
total = 0
for i in range(num):
path = random_path(gr, frm, steps)
total += sum(path)
print('Steps: {}, Trials: {}, Mean: {}'.format(steps, num, total / num))
output
[0, 4, 0, 6] 10
[0, 4, 0, 4] 8
[0, 4, 9, 2] 15
[0, 6, 0, 4] 10
[0, 4, 0, 4] 8
[0, 4, 9, 2] 15
[0, 6, 0, 6] 12
[0, 6, 0, 4] 10
[0, 6, 1, 8] 15
[0, 4, 0, 6] 10
Steps: 1024, Trials: 10000, Mean: 4607.2152
If you don't need the actual paths, just the node sums, this function can be simplified even further, but that will be left as an exercise for the reader.
Since you need only the sum, the solution is much easier.
Start with a dictionary with only {start_node: 0}; this is your starting position.
For a new position after a move:
make a new empty defaultdict(int).
then for for each (node, weight_sum) pair in your current position
find the each (new_node, new_weight) connected to node
add the sum of weight_sum + new_weight to the value of key new_node in the new dictionary.
Repeat these steps until you've done all K moves. The time complexity for this algorithm is O(NMK) where N is number of nodes, M is number of connections between pair of nodes, K is the number of moves.

Categories

Resources