Having a problem making a redundant dictionary into a function - python

I am having a trouble converting a really redundant dictionary into a function (def)
The original code that works just fine is:
Pen = (9,'always','monday')
Paper = (1,'always','tues')
PriceDic = {'Pen': Pen[0],
'Paper': Paper[0]}
while True:
name = input("name of the product?")
print(PriceDic.get(name),'dollar')
break
which prints as...
>>>name of the product?Pen
>>>9 dollar
but the problem is
I have not only Pen, and Paper but probably another 100-200 more tuples to write
and each tuple needs to contain multiple information... so the final goal of this program is to be able to fetch various info from the tuple indexes and print them.
so
I thought maybe I could function and wrote this code...
def FindPriceFunction(x):
Pen = (9,'always','monday')
Paper = (1,'always','tuesday')
FindPriceDic = { x : x[0]}
print(FindPriceDic.get(x),'dollar')
while True:
name = input("name of the product?")
FindPriceFunction(name)
break
which gave me...
>>>name of the product?Pen
>>>P dollar
PLEASE HELP ME

You are trying to use the string x to access the variable name which won't work the way you're expecting, as x holds the value 'Pen', for example. This is not necessarily a recommended approach, but you can use the locals function to dynamically get the value of the variable like this:
def FindPriceFunction(x):
Pen = (9,'always','monday')
Paper = (1,'always','tuesday')
print(locals()[x][0],'dollar')
while True:
name = input("name of the product?")
FindPriceFunction(name)
break
Here, locals returns a dictionary of locally-defined variables, and you can use the string x as a key to access the value of the variable. So locals()['Pen'] will give you the value (9,'always','monday')
However, it would be better (and safer) to just store your tuples directly into a dictionary somewhere, or maybe in a file that you read from if you don't want a long block in your code, and then access the data through there like you were originally trying, except you can store the whole tuple rather than just the price and then access the first element of the tuple for the price. locals returns a dictionary of the variable name as the key and the variable value as the value, so it's essentially accomplishing what you could just do in the first place with storing the value in a dict
If you wanted to store this all in a JSON file for example, since you will have hundreds of dicts, you could do this:
JSON file:
{
"Pen": [9, "always", "monday"],
"Paper": [1, "always", "tuesday"]
}
Code:
import json
with open('prices.json', 'r') as f:
prices = json.load(f)
def FindPriceFunction(x):
print(prices[x][0], 'dollar')

As you gave:
FindPriceDic = { x : x[0]}
print(FindPriceDic.get(x),'dollar')
and called the function with x as Pen, it prints x[0] = 'Pen'[0] = 'P'. This caused you the problem. So try:
def FindPriceFunction(x):
FindPriceDic = {}
FindPriceDic['Pen'] = (9,'always','monday')
FindPriceDic['Paper'] = (1,'always','tuesday')
print(FindPriceDic.get(x)[0],'dollar')
while True:
name = input("name of the product?")
FindPriceFunction(name)
break

You could write your data as a dictionary from the beginning (the values in a dictionary can be any type, a tuple is fine):
my_data = {
'Pen': (9, 'always', 'monday'),
'Paper': (1, 'always', 'tuesday')
}
def FindPriceFunction(x):
print(my_data.get(x)[0],'dollar')
while True:
name = input("name of the product?")
FindPriceFunction(name)
break

In case you would need to be more flexible with your data structure, you can also rely on semantic keys, rather than operate with indexes of tuples.
products.yaml
Pen:
price: 9
frequency: Always
day: Monday
Paper:
price: 1
frequency: Rarely
day: Tuesday
Scissors:
frequency: Often
main.py
import yaml # pyyaml package
with open("products.yaml", 'r') as stream:
try:
products = yaml.safe_load(stream)
except yaml.YAMLError as exc:
print(exc)
print(products)
print(products['Pen']['price'])
def find_price(product_name):
try:
return products[product_name]['price']
except KeyError:
return "N/A"
print(find_price('Pen'))
print(find_price('Scissors'))

Related

Python, how can I concatenate 2 variables and create a name pointing to 3rd variable

I would like to reduce my code and eliminate 3 if statements by joining together, in an if statement, 2 variables that if printed would create the name pointing to a 3rd variable.
Currently I have 3 if statements that look like this:
if prd.lower() not in retentions_log:
I would like to use something like:
if prd.lower() not in retentions_+prd.lower:
So every time the function is called It would construct the 3rd variable
retentions+prd.lower = retentnions_log
This is one of the functions:
retentinon_approval_log = "SOME_VALUE"
def ret_handler_log(roletype, prd, pth, fnm, location):# [retention_proc]
"""Processors. 4Writing results to file."""
if prd in retentinon_approval_log:
if approval_check(roletype, prd, pth) == False:
try:
results_o.write("some_text")
except Exception as e:
print(e)
pass
else:
yaml_dumper(roletype, prd, location)
results_o.write("some_text")
else:
yaml_dumper(roletype, prd, location)
Update I'd like to be able to construct "retentinon_approval_+prd"
retentions approval being simple text and prd being "log". To
construct a variable "retentinon_approval_log" that points to "some
text" and dynamically generate this with different values that are
being passed to this function
You may use a dictionary to build variables. Example.
d={'var1':10, 'var2':20}
d['var1'+'var2'] = d['var1'] + d['var2'] #{'var1': 10, 'var2': 20, 'var1var2': 30}

Faster or better way than looping to find data?

I have an array of object of class Person like the below, with thisRate first set to None:
class Person(object):
def __init__(self, id, name):
self.id = id
self.name = name
self.thisRate= None
I loaded around 21K Person objects into an array, name not sorted.
Then I loaded another array from data in a file which has data for thisRate, about 13K of them, name is not sorted as well:
person_data = []
# read from file
row['name'] = 'Peter'
row['thisRate'] = '0.12334'
person_data.append(row)
Now with these 2 sets of arrays, when the name is matched between them, I will assign thisRate from person_data into Person.thisRate.
What I am doing is a loop is like this:
for person in persons:
data = None
try:
data = next(personData for personData in person_data
if personData['name'] == person.name)
except StopIteration:
print("No rate for this person: {}".format(person.name))
if data:
person.thisRate = float( data['thisRate'] )
This loop
data = next(personData for personData in person_data
if personData['name'] == person.name)
is running fine and uses 21 seconds on my machine with Python 2.7.13.
My question is, is there a faster or better way to achieve the same thing with the 2 arrays I have?
Yes. Make an dictionary from name to thisRate:
nd = {}
with open(<whatever>) as f:
reader = csv.DictReader(<whatever>):
for row in reader:
nd[row['name']] = row['thisRate']
Now, use this dictionary to do a single pass over your Person list:
for person in persons:
thisRate = nd.get(person.name, None)
person.thisRate = thisRate
if thisRate is None:
print("No rate for this person: {}".format(person.name))
Dictionaries have a .get method which allows you to provide a default value in case the key is not in the dict. I used None (which is actually what is the default default value) but you can use whatever you want.
This is a linear-time solution. Your solution was quadratic time, because you are essentially doing:
for person in persons:
for data in person_data:
if data['name'] == person.name:
person.thisRate = data['thisRate']
break
else:
print("No rate for this person: {}".format(person.name))
Just in a fashion that obscures this fundamentally nested for-loop inside of a generator expression (not really a good use-case for a generator expression, you should have just used a for-loop to begin with, then you don't have to deal with try-catch a StopIteration

Searching for an equal string?

I am not sure how to word the question title.
a = "Alpha";
b = "Bravo";
c = "Charlie";
fn = input("What is your first name: ")
for fletter in fn.split():
fl = fletter[0]
The code above gets the first letter entered. The goal is to then get the first letter to possible check in a while loop to see if the value of fl = one of the starting strings. Is that possible to do? Tips on where to begin?
Solution 1 [Using a dictionary]
Also makes things much simpler.
In this case, instead of defining separate variables for each string, you store them in a dictionary. So for example, instead of this:
a = "Alpha"
b = "Bravo"
c = "Charlie"
You would have this:
letterwords = {"a":"Alpha", "b":"Bravo", "c":"Charlie"}
This works very similarly to a list, however instead of indexing the dictionary you would reference to separate objects inside a dictionary according to its key. So if the dictionary letterwords is defined as above, you would reference to the string Alpha by calling letterwords["a"]. Therefore, in this case, the code would look something like this:
letterwords = {"a":"Alpha", "b":"Bravo", "c":"Charlie"}
fn = input("Please enter your first name: ")
try:
letterwords[fn[0]]
except KeyError:
print("There is no matching variable with that letter in the database.")
Solution 2 [Using the eval() function]
Not recommended.
This is perfectly possible, with the eval function. However, you should be aware that this is a quite dangerous function to run, as malicious users can use this to control the console. (Especially if you imported os.) However, it should get you over the hump for now. Here's the code:
a = "Alpha"
b = "Bravo"
c = "Charlie"
fl = input("Please enter your first name: ")
try:
compared = eval(fl[0])
except NameError:
print("Your first name's first letter does not match any strings in the database.")
More information on the eval() function here: https://docs.python.org/3/library/functions.html#eval
Hope this helped!

Sorting on list values read into a list from a file

I am trying to write a routine to read values from a text file, (names and scores) and then be able to sort the values az by name, highest to lowest etc. I am able to sort the data but only by the position in the string, which is no good where names are different lengths. This is the code I have written so far:
ClassChoice = input("Please choose a class to analyse Class 1 = 1, Class 2 = 2")
if ClassChoice == "1":
Classfile = open("Class1.txt",'r')
else:
Classfile = open("Class2.txt",'r')
ClassList = [line.strip() for line in Classfile]
ClassList.sort(key=lambda s: s[x])
print(ClassList)
This is an example of one of the data files (Each piece of data is on a separate line):
Bob,8,7,5
Fred,10,9,9
Jane,7,8,9
Anne,6,4,8
Maddy,8,5,5
Jim, 4,6,5
Mike,3,6,5
Jess,8,8,6
Dave,4,3,8
Ed,3,3,4
I can sort on the name, but not on score 1, 2 or 3. Something obvious probably but I have not been able to find an example that works in the same way.
Thanks
How about something like this?
indexToSortOn = 0 # will sort on the first integer value of each line
classChoice = ""
while not classChoice.isdigit():
classChoice = raw_input("Please choose a class to analyse (Class 1 = 1, Class 2 = 2) ")
classFile = "Class%s.txt" % classChoice
with open(classFile, 'r') as fileObj:
classList = [line.strip() for line in fileObj]
classList.sort(key=lambda s: int(s.split(",")[indexToSortOn+1]))
print(classList)
The key is to specify in the key function that you pass in what part of each string (the line) you want to be sorting on:
classList.sort(key=lambda s: int(s.split(",")[indexToSortOn+1]))
The cast to an integer is important as it ensures the sort is numeric instead of alphanumeric (e.g. 100 > 2, but "100" < "2")
I think I understand what you are asking. I am not a sort expert, but here goes:
Assuming you would like the ability to sort the lines by either the name, the first int, second int or third int, you have to realize that when you are creating the list, you aren't creating a two dimensional list, but a list of strings. Due to this, you may wish to consider changing your lambda to something more like this:
ClassList.sort(key=lambda s: str(s).split(',')[x])
This assumes that the x is defined as one of the fields in the line with possible values 0-3.
The one issue I see with this is that list.sort() may sort Fred's score of 10 as being less than 2 but greater than 0 (I seem to remember this being how sort worked on ints, but I might be mistaken).

tuples and lists

I have a list consisting of tuples in Python. I need to reference each index of each tuple to create a list of NBA player statics. The first index [0] in the tuple is the efficiency rating and the next two positions are the player name and lastly the team name. Right now the tuples are in a long list. When I run the program, all I get is an output of the first four tuples and not a list of the various tuples. I have tried to use append, but that did not help either.
Here is my code:
def get_data_list (file_name):
data_file = open(file_name, "r")
data = []
player_list=[]
for line_str in data_file:
# strip end-of-line, split on commas, and append items to list
data_list =line_str.strip().split(',')
data_list.append (data)
gp=int(data_list[6])
mins=int(data_list[7])
pts=int(data_list[8])
oreb=int(data_list[9])
dreb=int(data_list[10])
reb=int(data_list[11])
asts=int(data_list[12])
stl=int(data_list[13])
blk=int(data_list[14])
to=int(data_list[15])
pf=int(data_list[16])
fga=int(data_list[17])
fgm=int(data_list[18])
fta=int(data_list[19])
ftm=int(data_list[20])
tpa=int(data_list[21])
tpm=int(data_list[22])
efficiency = ((pts+reb+asts+stl+blk)-(fgm-ftm-to))/gp
data_list.append (efficiency)
data.append(data_list)
score=data_list[24]
first_name=data_list[2]
last_name=data_list[3]
team_name=data_list[4]
player_tuple = score, last_name, first_name, team_name
player_list.append(player_tuple)
a=sorted(player_list)
a.reverse()
return a
def print_results (lst):
"""Print the result in a nice format"""
print("The top 50 players based on efficiency are: ")
print('*'*75)
print('{:<20s}{:<20s}, {:<15s}{:<5s}'.format(lst[(0)],lst[(1)],lst[(2)],lst[(3)]))
file_name1 = input("File name: ")
result_list = get_data_list (file_name1)
top_50_list=[]
top_50_list=result_list[:50]
print_results(top_50_list)
I think my problem is in the print_results function.
Please remember that I am taking an intro class so many of the advanced options are not an option for me. Please keep the solutions simple.
Boliver
It looks like your problem is with the append function...
a=[some,list]
a.append(42) #a = [some,list,42]
a.append([1,2]) #now a = [some,list,42,[1,2]]
For you index 0 is itself an empty list
So in your code, for the first iteration of the loop:
data_list =line_str.strip().split(',') # data_list = [some,list]
data_list.append (data) # data_list = [some,list,[]]
...
data_list.append (efficiency) # data_list = [some,list,[],efficiency]
data.append(data_list) # data = [[some,list,[],efficiency]]
...
a = something special
The loop then continues on until the last line of the input file
Then you return a. a is only returned once and is screwy because of the before mentioned shuffling.
Play with for loops and append a bit and you should work it out no problem.
And if you are feeling brave then look up yield. A generator would be perfect here
in print_results
def print_results (lst):
"""Print the result in a nice format"""
print("The top 50 players based on efficiency are: ")
print('*'*75)
print('{:<20s}{:<20s}, {:<15s}{:<5s}'.format(lst[(0)],lst[(1)],lst[(2)],lst[(3)]))
could be somethign like
def print_results(lst):
"""Print the result in a nice format"""
print("The top 50 players based on efficiency are: ")
print('*'*75)
for player_tuple in lst:
print('{:<20s}{:<20s}, {:<15s}{:<5s}'.format(*player_tuple[:4]))
Since you have a list of tuples, each tuple representing a player, loop through each player and print out their information
I don't understand what your code is trying to do with data vs. data_list. I rewrote it to get rid of data. Also, after you compute efficiency you append it to the list, but then you seem to be pulling it off the list again as score. I simply got rid of that.
For working with files, best practice is to use a with statement so I rewrote to do that.
Also, you are converting string items to integer one at a time, when you could do them all in one go with a list comprehension. I hope list comprehensions are not a problem for you to use, because they make the code cleaner. The first list comprehension calls the .strip() method function on each of the three strings for names. The second one converts all the integers in one convenient way.
Instead of making a temp list named a and then reversing that list, I just specified the reverse=True option in sorted(). Now the list is built in reverse order, just what you want.
As others have noted, your print function needs a loop to print things in the list. Since the message in the print function says it prints the top 50, I changed the list slicing to happen inside the print function. Now the print function takes an optional argument specifying how many items to print; it has a default value of 50, so if you don't specify another value, it prints the top 50 items.
While you don't need to do it, there is a common Python feature of putting if __name__ == __main__: before your code. You can see an explanation here: What does if __name__ == "__main__": do?
def get_data_list (file_name):
player_list=[]
with open(file_name, "r") as f:
for line in f:
# split line on commas, and convert items to integer values
# make a list of the integer values.
items = line.split(',')
first_name = items[2].strip()
last_name = items[3].strip()
team_name = items[4].strip()
data_list = [int(x) for x in items[6:]
gp = data_list[0]
mins = data_list[1]
pts = data_list[2]
oreb = data_list[3]
dreb = data_list[4]
reb = data_list[5]
asts = data_list[6]
stl = data_list[7]
blk = data_list[8]
to = data_list[9]
pf = data_list[10]
fga = data_list[11]
fgm = data_list[12]
fta = data_list[13]
ftm = data_list[14]
tpa = data_list[15]
tpm = data_list[16]
efficiency = ((pts+reb+asts+stl+blk)-(fgm-ftm-to))/gp
player_tuple = efficiency, last_name, first_name, team_name
player_list.append(player_tuple)
return sorted(player_list, reverse=True)
def print_results(lst, how_many=50):
"""Print the result in a nice format"""
template = '{:<20}{:<20s}, {:<15s}{:<5s}'
print("The top {} players based on efficiency are: ".format(how_many))
print('*'*75)
for tup in lst[:how_many]:
print(template.format(tup[0], tup[1], tup[2], tup[3]))
if __name__ == "__main__":
file_name1 = input("File name: ")
result_list = get_data_list(file_name1)
print_results(result_list)
Now I'm going to smooth it out even further. This is using more advanced features in Python, but they are features that make things more convenient, not things that are just tricky.
First, instead of building a list with a list comprehension, and then picking items by index number, we will use a generator expression and directly unpack the items into variable names. A generator expression is just like a list comprehension, except that instead of building a list, it provides an "iterator" that can be looped over, or can be unpacked into variable names as I show here.
Second, in the print function, we just want to print all the values in the tuple, in order. Python provides a shortcut: putting a * in front of the tuple inside the call to .format() means "unpack this and use the unpacked values as the arguments to this function call".
def get_data_list (file_name):
player_list=[]
with open(file_name, "r") as f:
for line in f:
# Split line on commas and convert each item to integer. Unpack
# values directly into variable names. We are using a
# generator expression to convert all the items to integer,
# and Python's ability to unpack an iterator into a tuple.
items = line.strip().split(',')
# use list slicing to select just the three string values
first_name, last_name, team_name = (s.strip() for s in items[2:5])
# Use a generator expression to convert all values to int.
# Unpack directly to variable names using tuple unpacking.
# Put parentheses so Python won't worry about multiple lines
# of variable names.
(
gp, mins, pts, oreb, dreb, reb, asts,
stl, blk, to, pf, fga, fgm, fta, ftm,
tpa, tpm
) = (int(x) for x in items[6:])
efficiency = ((pts+reb+asts+stl+blk)-(fgm-ftm-to))/gp
player_tuple = efficiency, last_name, first_name, team_name
player_list.append(player_tuple)
return sorted(player_list, reverse=True)
def print_results(lst, how_many=50):
"""Print the result in a nice format"""
template = "{:<20}{:<20s}, {:<15s}{:<5s}"
print("The top {} players based on efficiency are: ".format(how_many))
print('*'*75)
for player_tuple in lst[:how_many]:
print(template.format(*player_tuple))
if __name__ == "__main__":
file_name1 = input("File name: ")
result_list = get_data_list(file_name1)
print_results(result_list)
EDIT: And here is another edited version. This one splits out the logic for parsing a line into a player_tuple into its own function. This makes get_data_list() very short.
def player_tuple(line):
# Split line on commas and convert each item to integer. Unpack
# values directly into variable names. We are using a
# generator expression to convert all the items to integer,
# and Python's ability to unpack an iterator into a tuple.
items = line.strip().split(',')
# use list slicing to select just the three string values
first_name, last_name, team_name = (s.strip() for s in items[2:5])
# use a generator expression to convert all values to int
# unpack directly to variable names using tuple unpacking
(
gp, mins, pts, oreb, dreb, reb, asts,
stl, blk, to, pf, fga, fgm, fta, ftm,
tpa, tpm
) = (int(x) for x in items[6:])
efficiency = ((pts+reb+asts+stl+blk)-(fgm-ftm-to))/gp
return efficiency, last_name, first_name, team_name
def get_data_list(file_name):
with open(file_name, "r") as f:
player_list = [player_tuple(line) for line in f]
return sorted(player_list, reverse=True)
def print_results(lst, how_many=50):
"""Print the result in a nice format"""
template = "{:<20}{:<20s}, {:<15s}{:<5s}"
print("The top {} players based on efficiency are: ".format(how_many))
print('*'*75)
for player_tuple in lst[:how_many]:
print(template.format(*player_tuple))
if __name__ == "__main__":
file_name1 = input("File name: ")
result_list = get_data_list(file_name1)
print_results(result_list)
Now that we have player_tuple() as a function, we could simplify get_data_list() even further. I won't repeat the whole program, just the simplified get_data_list(). This is probably the code I would write if I had to solve this problem.
def get_data_list(file_name):
with open(file_name, "r") as f:
return sorted((player_tuple(line) for line in f), reverse=True)
Here we don't even explicitly build the list. We just make a generator expression that provides all the player_tuple values, and directly pass that to sorted(). There is no need for this list to be given a name inside get_data_list(); it can just be built and returned in one line.

Categories

Resources