Python: Importing a graph - python

So I have this code here, which basically runs Dijkstra's Algortihm on a graph in Python, and then prints the distance and moves it takes to get from start to end. My problem is, I can't figure out how to import a graph as a .txt file using sys.argv and have it read it and run the algorithm on it. Here is my code, with a graph and start 'a' and end 'b' already filled into it. (It should work).
import sys
def shortestpath(graph,start,end,visited=[],distances={},predecessors={}):
"""Find the shortest path between start and end nodes in a graph"""
# we've found our end node, now find the path to it, and return
if start==end:
path=[]
while end != None:
path.append(end)
end=predecessors.get(end,None)
return distances[start], path[::-1]
# detect if it's the first time through, set current distance to zero
if not visited: distances[start]=0
# process neighbors as per algorithm, keep track of predecessors
for neighbor in graph[start]:
if neighbor not in visited:
neighbordist = distances.get(neighbor,sys.maxsize)
tentativedist = distances[start] + graph[start][neighbor]
if tentativedist < neighbordist:
distances[neighbor] = tentativedist
predecessors[neighbor]=start
# neighbors processed, now mark the current node as visited
visited.append(start)
# finds the closest unvisited node to the start
unvisiteds = dict((k, distances.get(k,sys.maxsize)) for k in graph if k not in visited)
closestnode = min(unvisiteds, key=unvisiteds.get)
# now we can take the closest node and recurse, making it current
return shortestpath(graph,closestnode,end,visited,distances,predecessors)
if __name__ == "__main__":
graph = {'a': {'w': 14, 'x': 7, 'y': 9},
'b': {'w': 9, 'z': 6},
'w': {'a': 14, 'b': 9, 'y': 2},
'x': {'a': 7, 'y': 10, 'z': 15},
'y': {'a': 9, 'w': 2, 'x': 10, 'z': 11},
'z': {'b': 6, 'x': 15, 'y': 11}}
print(shortestpath(graph,'a','b'))
"""
Result:
(20, ['a', 'y', 'w', 'b'])
"""
Now here is the graph that I am trying to import, it is called sample-map.txt:
{'a': {'b': 5, 'c': 8},
'b': {'a': 5, 'd': 6},
'c': {'a': 8, 'd': 2},
'd': {'b': 6, 'c': 2, 'e': 12, 'f': 2},
'e': {'d': 12, 'g': 3},
'f': {'d': 2, 'g': 7},
'g': {'e': 3, 'f':7}}
I just need to figure out how to import it using sys.argv and then have it take the place of the graph already in the .py. Also, being able to use sys.argv to define a starting point and end point would be nice too, something like in the format >python file.py start end sample-map.txt
Where
sys.argv[0] is file.py
sys.argv[1] is start
sys.argv[2] is end,
and sys.argv[3]
is the graph I want to import. Thank you!

If sys.argv[3] is the name of the file containing the graph you want to import, you can use ast.literal_eval:
with open(sys.argv[3], "r") as f:
graph = ast.literal_eval(f.read())
# If you don't trust your users, check that graph is indeed a dict
# and that it has the right structure

You have three options:
JSON encoder/decoder: Python dictionaries are quite similar to JSON in format and you can import files specified as dictionaries in json format and then convert them into python dictionary. That could work in your case. Have a close look at json.load(file) method and using something like json.load(sys.argv[3]) could be worth trying. Have a look at http://docs.python.org/2/library/json.html
You can alternatively read the entire file using readlines and manually convert lines into a dictionary. More cumbersome but doable
[EDIT] I just saw a comment talking about ast.literal_eval. Again, that does not give you a dictionary directly. You will have to write some code to convert it into a dictionary.

I think you want to transfer the content of the file "sample-map.txt" into a dict. If you can ensure that the content of that file obeys the syntax grammer of Python. You can use the code below to do this job:
# sys.argv[3] should be 'sample-map.txt'
graph=eval(open(sys.argv[3],'r').read())

This version will work:
import sys,json
def shortestpath(graph,start,end,visited=[],distances={},predecessors={}):
...
return shortestpath(graph,closestnode,end,visited,distances,predecessors)
if __name__ == "__main__":
start_node=sys.argv[1]
end_node=sys.argv[2]
filename=sys.argv[3]
#here we load file text content
with open(filename,"r") as f:
cont= f.read()
#here we convert text content to dict
graph=json.loads(cont)
print(shortestpath(graph,start_node,end_node))
You have to use " character instead of ' character to delimit keys & values in "sample-map.txt" file. (to respect JSON syntax)
Like this:
{ "a": {"b": 5, "c": 8},
"b": {"a": 5, "d": 6},
"c": {"a": 8, "d": 2},
"d": {"b": 6, "c": 2, "e": 12, "f": 2},
"e": {"d": 12, "g": 3},
"f": {"d": 2, "g": 7},
"g": {"e": 3, "f":7}
}
If you respect the JSON syntax in the graph text file, when you call your program from a terminal:
python shortestpath.py a b sample-map.txt
You will get the good answer !
>>>(5, ['a', 'b'])

Related

Splitting a nested dictionary into a list of single-key paths

So let's say I have a dictionary:
{"a": {"b": 1,
"c": 2,
"d": {"e": 3,
"f": 4,
}
}
"g": {"h": 5,
"i": 6
}
}
I've been trying to find a way to map this dictionary into a list of "paths" to each end-value, as follows:
[
{"a": {"b": 1}},
{"a": {"c": 2}},
{"a": {"d": {"e": 3}}},
{"a": {"d": {"f": 4}}},
{"g": {"h": 5}},
{"g": {"i": 6}}
]
Where each list entry is a single-key nested dictionary. I assume this can be done using some form of depth-first walk with recursion, but I'm not very familiar with programming recursive functions, and also don't know if that's even the best approach.
Any input would be appreciated.
I agree with you that a recursion is a good approach. Here is what I would do. Using a generator (i.e. yield-ing values) has the advantage that we don't have to have a variable gathering the individual result items.
The variable _revpath contains the "path" of dict keys leading to a value, but in reversed order, because at the end of the recursion we want to create a nested dict from inner to outer dict.
test = {"a": {"b": 1,
"c": 2,
"d": {"e": 3,
"f": 4,
}
},
"g": {"h": 5,
"i": 6
}
}
def walkdict(d, _revpath=[]):
if isinstance(d, dict):
for key, value in d.items():
yield from walkdict(value, [key] + _revpath)
else:
for key in _revpath:
d = {key: d}
yield d
print(list(walkdict(test)))
Use recursion to build the list of nodes you pass throught
def paths(values, parents=None):
results = []
parents = parents or []
for k, v in values.items():
if isinstance(v, int):
results.append(keys_to_nested_dict([*parents, k], v))
else:
results.extend(paths(v, [*parents, k]))
return results
Then when you reach an leaf, an int, transform that list of nodes, into a nested dict
def keys_to_nested_dict(keys, value):
result = {}
tmp = result
for k in keys[:-1]:
tmp[k] = {}
tmp = tmp[k]
tmp[keys[-1]] = value
return result
print(keys_to_nested_dict(['a', 'b', 'c'], 1)) # {'a': {'b': {'c': 1}}}
x = {"a": {"b": 1, "c": 2, "d": {"e": 3, "f": 4, }},
"g": {"h": 5, "i": 6}}
print(paths(x))
# [{'a': {'b': 1}}, {'a': {'c': 2}}, {'a': {'d': {'e': 3}}}, {'a': {'d': {'f': 4}}}, {'g': {'h': 5}}, {'g': {'i': 6}}]
Not exactly the same output, but a little tweak can you a lot of efforts & complexity
sample = {"a": {"b": 1,"c": 2,"d": {"e": 3,"f": 4}},"g": {"h": 5,"i": 6}}
from pandas.io.json._normalize import nested_to_record
flat = nested_to_record(sample, sep='_')
Output
{'a_b': 1, 'a_c': 2, 'a_d_e': 3, 'a_d_f': 4, 'g_h': 5, 'g_i': 6}
Now, whenever you wish to know the possible paths, just iterate over the keys of this dictionary & split('_') will give you the entire path & associated value

Pytest dict equality preserving order and nice diff output (python-3.7+)

As of python 3.7, dict is guaranteed to be insertion ordered. I have a unit test using pytest where I would like to compare two dicts but with order of elements in mind.
My application will never run on python older then 3.7 so backward compatibility is not an issue for me.
Question
Is there any possibility to compare regular dicts, preserving order and nice diff output in pytest?
My unsuccesful tries
a = {'x': 1, 'y': {'u': 3, 'v': 4}}
b = {'y': {'v': 4, 'u': 3}, 'x': 1}
assert a == b
This assertion passes, but I don't want it to. These dicts are equal but with wrong order.
I can convert everything to OrderedDict but it is
ugly code
produces ugly pytest diff output
from collections import OrderedDict
def deep_ordered_dict(value):
if isinstance(value, dict):
value = OrderedDict(value)
for k, v in value.items():
value[k] = deep_ordered_dict(v)
if isinstance(value, list):
for k, v in enumerate(value):
value[k] = deep_ordered_dict(v)
return value
a = {'x': 1, 'y': {'u': 3, 'v': 4}}
b = {'y': {'v': 4, 'u': 3}, 'x': 1}
assert deep_ordered_dict(a) == deep_ordered_dict(b)
Comparing it as JSON is not better
produces ugly pytest diff output
import json
a = {'x': 1, 'y': {'u': 3, 'v': 4}}
b = {'y': {'v': 4, 'u': 3}, 'x': 1}
assert json.dumps(a, indent=2) == json.dumps(b, indent=2)
Even though dictionaries are required to preserve insertion order as of Python 3.7, for backward compatibility equality comparisons are order-independent. This is one of the documented differences between dict and OrderedDict, so if you want order-sensitive comparisons you should use the latter.

Remove multiple keys from dictionary from txt file using python

So I have a txt file with this reddit comments:
There's a lot of information on each line of the dict, and I only want 2 elements from there, which is author and body. I'm trying to iterate over each line of the file, to remove the unnecessary information and keep only those two. I searched a lot and I didn't found any thing that help me.
The output should be a new filename.txt with only author and body in the dict for each line.
I just realize that it is in json format. So I tried this:
The problem is, now when I remove the unnecessary elements, it also removes it's value.
listcomments = []
for line in open ('RC_2009-01.json', 'r'):
listcomments.append(json.loads(line))
#res = dict([(key, val) for key, val in comments.items() if key not in rem_list])
#print(res)
for line in listcomments:
rem_list = ['subreddit_id', 'name', 'author_flair_text', 'link_id', 'score_hidden', 'retrieved_on', 'controversiality',
'parent_id', 'subreddit', 'author_flair_css_class', 'created_utc', 'gilded', 'archived', 'distinguished',
'id', 'edited', 'score', 'downs', 'ups']
list1 = [ele for ele in line if ele not in rem_list]
out_file = open("teste2.json", "w")
json.dump(list1, out_file, indent = 4)
Example in the image file is in json format. You have to parse json and get needed tags from the parser.
forward the following link :
https://www.w3schools.com/python/python_json.asp
You do this.
Say you have a dictionary like this below.
a={chr(i):j for i,j in zip(range(65,91),range(1,27))}
'''a={'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'H': 8, 'I': 9,
'J': 10, 'K': 11, 'L': 12, 'M': 13, 'N': 14, 'O': 15, 'P': 16, 'Q': 17, 'R': 18,
'S': 19, 'T': 20, 'U': 21, 'V': 22, 'W': 23, 'X': 24, 'Y': 25, 'Z': 26}'''
And you want to extract only 'A' and 'C'.
wanted_key=['A','C']
res={key:a.get(key) for key in wanted_key}
print(res)
output
{'A': 1, 'C': 3}

count characters of a string using function

Write a function named count_letters that takes as a parameter a string and returns a dictionary that tabulates how many of each letter is in that string. The string can contain characters other than letters, but only the letters should be counted. The string could even be the empty string. Lower-case and upper-case versions of a letter should be part of the same count. The keys of the dictionary should be the upper-case letters. If a letter does not appear in the string, then it would not get added to the dictionary. For example, if the string is
"AaBb"
then the dictionary that is returned should contain these key-value pairs:
{'A': 2, 'B': 2}
def count_letters(string):
"""counts all the letters in a given string"""
your_dict = dict()
for x in string:
x = x.upper() # makes lowercase upper
if x not in your_dict:
your_dict[x]= 1
else:
your_dict[x] += 1
return your_dict
I am getting the following error when I go to upload:
Test Failed: {'Q': 1, 'U': 3, 'I': 3, 'S': 6, ' ': 3, 'C[48 chars]': 1} != {'S': 6, 'U': 3, 'I': 3, 'T': 3, 'O': 3, 'C[32 chars]': 1}
+ {'C': 2, 'D': 2, 'E': 2, 'I': 3, 'O': 3, 'P': 1, 'Q': 1, 'S': 6, 'T': 3, 'U': 3}
- {' ': 3,
- '?': 1,
- 'C': 2,
- 'D': 2,
- 'E': 2,
- 'I': 3,
- 'O': 3,
- 'P': 1,
- 'Q': 1,
- 'S': 6,
- 'T': 3,
- 'U': 3}
Try something like this. Feel free to adjust it to your requirements:
import collections
def count_letters(string):
return collections.Counter(string.upper())
print(count_letters('Google'))
Output: Counter({'G': 2, 'O': 2, 'L': 1, 'E': 1})
For documentation of the Counter dict subclass in collections module, check this.
Update without using collections module:
def count_letters(string):
your_dict={}
for i in string.upper():
if i in your_dict:
your_dict[i] += 1
else:
your_dict[i] = 1
return your_dict
Output: {'G': 2, 'O': 2, 'L': 1, 'E': 1}
This solution does use collections, but unlike with Counter we aren’t getting the entire solution from a single library function. I hope it’s permitted, and if it isn’t, that it will at least be informative in some way.
import collections as colls
def count_letters(str_in):
str_folded = str_in.casefold()
counts = colls.defaultdict(int)
for curr_char in str_folded:
counts[curr_char] += 1
return counts
defaultdict is extremely practical. As the name indicates, when we try to index a dictionary with a key that doesn’t exist, it creates a default value for that key and carries out our original operation. In this case, since we declare that our defaultdict will use integers for its keys, the default value is 0.
str.casefold() is a method designed specifically for the complex problem that is case-insensitive comparison. While it is unlikely to make a difference here, it’s a good function to know.
Let me know if you have any questions :)
Without using collections, here is a solution:
def count_letters(string):
string = string.upper()
counts = {}
for a in set(string):
counts[a] = string.count(a)
return counts
This function iterates over set(string), which is equal to all the letters used in your word, without duplicates, and in uppercase. Then it counts how many times each letter appears in your string, and adds it to your counts dictionary.
I hope this answers your question. :)

a string to a dictionary of dictionary

Hi I want to create a dictionary of dictionary but I can't for a text and in an iterative way…. and I am obligated to create a lot of variable and I want that the creation of the dictionary to be automatical without the creation of variables. Can you help me to improve it ?
I tried to create two functions and create this dictionary with one word…
def create_last_dictionary(word, n):
dico={}
dico[word[len(word) - n]] = {}
return dico
def create_dictionary(dic_to_add, word, n):
dic = {}
dic[word[len(word) - n]]=dic_to_add
return dic
word = "mommy"
one = create_last_dictionary(word, 1)
two = create_dictionary(one, word, 2)
three = create_dictionary(two, word, 3)
four = create_dictionary(three, word, 4)
five = create_dictionary(four, word, 5)
six = create_dictionary(five, word, 6)
seven = create_dictionary(six, word, 7)
result :
{'m': {'o': {'r': {'n': {'i': {'n': {'g': {}}}}}}}}
I want it for a list of words like :
if the list is : ["good", "Morning", "mommy"]
I want the dictionary to be :
{{'g': {'o': {'o': {'d': {}}}}}, 'm': {'o': {'m': {'m': {'y': {}}}}, {'r': {'n': {'i': {'n': {'g': {}}}}}}}}
the representation of the dictionary :
{
{'g': {'o': {'o': {'d': {}}}}},
{'m': {'o': {{'m': {'m': {'y': {}}}},
{'r': {'n': {'i': {'n': {'g': {}}}}}}}}}
}
You need to create a function which inserts a new word in an existing (maybe empty) tree. To achieve this I propose a recursive function:
def insert(tree, path):
if path:
insert(tree.setdefault(path[0], {}), path[1:])
Now you can use it:
tree = {}
insert(tree, 'good')
insert(tree, 'morning')
insert(tree, 'mommy')
print(tree)
prints
{'m': {'o': {'m': {'m': {'y': {}}},
'r': {'n': {'i': {'n': {'g': {}}}}}}},
'g': {'o': {'o': {'d': {}}}}}
EDIT:
In case you don't like using .setdefault() because it seems a hard-to-read shortcut, consider this:
def insert(tree, path):
if path:
if path[0] not in tree:
tree[path[0]] = {}
insert(tree[path[0]], path[1:])
Instead of chaining function calls, you could create a recursive function; your are almost there as you created two cases already. I found a course about Python recursive functions here: https://www.python-course.eu/python3_recursive_functions.php

Categories