Python for the absolute beginner, Chapter 7 challenge 2 - python

I've been going through the exercises in this book and I've hit a bit of a road block. The challenge is to:
"Improve the Trivia Challenge game so that it maintains a high-scores list in a file. The program should record the player's name and score. Store the high scores using a pickled object."
I've managed to save scores to a list and then append this list to a dat file. However, when I try to view the scores/read the file it only seems to show the first score entered. I took a look at the bat file and it seems to be dumping the list correctly, so I'm wondering if I'm messing up the retrieval part?
Thanks for reading
Here's the code (BEFORE):
def high_score():
"""Records a player's score"""
high_scores = []
#add a score
name = input("What is your name? ")
player_score = int(input("What is your score? "))
entry = (name, player_score)
high_scores.append(entry)
high_scores.sort(reverse=True)
high_scores = high_scores[:5] # keep only top five
# Open a new file to store the pickled list
f = open("pickles1.dat", "ab")
pickle.dump(high_scores, f)
f.close()
choice = None
while choice !="0":
print(
"""
0 - Quit
1 - Show high scores
"""
)
choice = input("choice: ")
print()
# exit
if choice == "0":
print("Goodbye")
# show a score
if choice == "1":
f = open("pickles1.dat", "rb")
show_scores = pickle.load(f)
print(show_scores)
f.close()
input("\n\nPress enter to exit.")
Solution(AFTER):
def high_score():
"""Records a player's score"""
# no previous high score file
try:
with open("pickles1.dat", "rb") as f:
high_scores = pickle.load(f)
except EOFError:
high_scores = []
#add a score // Do current stuff for adding a new score...
name = input("What is your name? ")
player_score = int(input("What is your score? "))
entry = (name, player_score)
high_scores.append(entry)
high_scores.sort(reverse=True)
high_scores = high_scores[:5] # keep only top five
# dump scores
with open("pickles1.dat", "wb") as f:
pickle.dump(high_scores, f)

f = open("pickles1.dat", "ab")
# ^
You have opened the file in "append" mode. That's fine, but it's probably not doing what you want. each time you try to save data to such a file, it is tacked on the end. You'd have to read every single item you ever wrote to get to the most recently written. instead, open the file in write mode. This way, its contents will be replaced every time:
f = open("pickles1.dat", "wb")
alternatively, you can use appending, but you'll have to call pickle.load() repeatedly to see all of the written values. Here's pseudocode, first for the former, with "w"
scores = pickle.load(open('scores.dat', 'rb'))
scores.append(123)
pickle.dump(scores, open('scores.dat', 'wb'))
and the latter:
pickle.dump(123, open('scores.dat', 'ab'))
scores = []
with open('scores.dat', 'rb') as f:
while True:
try:
scores.append(pickle.load(f))
except EOFError:
break
I hope you can see why I think the former (overwriting) is nicer; it's much simpler.

There are a few problems with your code. #TokenMacGuy has identified one, which is that you're appending your results onto the end of the output file, rather than overwriting the previous values.
There is a more fundamental issue though. When you run the highscores function, you are always starting with an empty high score list. You then add a single score to it, and write it out. With this structure, you will never have more than one score being written at a time (and so even if you get it to read back what you've written properly, it will only get a 1-element list).
What you need to do is add some code to load the high score list from your file at the start of the function, before you add the new value. You need to put in a special case for when there is no previous high score file, but that's not too dificult with a try/except block. Here's some abbreviated code:
def high_score():
try:
with open("pickles1.dat", "rb") as f:
high_scores = pickle.load(f)
except FileNotFoundError:
high_scores = []
# do current stuff for adding a new score, sorting and slicing
with open("pickles1.dat", "wb") as f: # "wb" mode overwrites the previous file
pickle.dump(high_scores, f)
# do other stuff, like displaying high_scores
A benefit to this approach is that you don't need to reread the file later if the user asks for it, since the high_scores list will be up to date already.

Related

Create highscore file for simple textgame

I'm trying to create a highscore file in Python 3.1 that stores (already declared) username with scores. Then, I want the program to print the top 5 scores and the users that achieved them.
Currently, I'm using two files - highscores.txt and highuser.txt. What I want is for the code to find the highest integer value in highscores.txt, take its array and match it to the same position in highuser.txt.
Here's my current code:
def highscores():
global score
highscores = open("highscores.txt", "a")
highscores.write(str(score))
highscores.write("\n")
print("done")
highscoreuser = open("highuser.txt", "a")
global username
highscoreuser.write(username)
highscoreuser.write("\n")
print("done")
This code works, but I don't know how to sort the files.
this should do it
with open('highscores.txt','r') as infile:
maxval = 0
maxline = None
i = 0
for line in infile:
if maxval<int(line):
maxline = i
maxval = int(line)
i+=1
i = 0
with open('highuser.txt','r') as infile:
for line in infile:
if i == maxline:
highuser = line.strip()
break
i+=1
Try this:
https://www.geeksforgeeks.org/python-program-to-find-n-largest-elements-from-a-list/
Just use it for 5.
Also make sure to close your files. highscores.close()
And reopen them to read. highscores = highscores.open("highscores.txt", "r")
Make an array of all highscores by .split("\n")
Then similarly create a array of all users by .split("\n")
For each of the top 5 scores search the array for that score and get the indexes.
Then plug in those indexes into the user array and get the resulting users.
You could also make a dictionary with each user and their score and just find the 5 highest scores from all the users.

Code to print corresponding CSV/list values for car program is not printing anything at all

I have tried to get help on this question all day, but none of the already-asked questions have solved my problem, and any answers I have asked to help me solve this problem have resulted in answers that didn't answer the question.
As it stands now, my program takes the values of MPG ratings represented in a CSV and puts them in a list. These lists represent maximum and minimum MPGs. Once the max/min MPG rating is found is where I get into my problem. I need to be able to print all the cars that have the minimum/maximum MPG value. Ideally, the models should be printed indented under the print statements for the maximum/minimum mileage ratings.
The way the CSV file is done the MPG ratings are in column 9 (index[8]). The make and models are in columns 2 and 3, respectively (index[1] and index[2], respectively). As you'll see in the code, I tried looping through the list and using an if statement to pull the make/model, but when I do that it does not work. I don't know if the files play a part or not, but any help would be appreciated.
# prints welcome screen, asks user what year they want vehicle data for, and asks user for file name to save vehicle data to
print("Welcome to EPA Mileage Calculator")
yearChoice = int(input("What year would you like to view data for? (2008 or 2009): "))
while yearChoice != 2008 and yearChoice != 2009:
print("Invalid input, please try again")
yearChoice = int(input("What year would you like to view data for? (2008 or 2009): "))
saveResults = input("Enter the filename to save results to: ")
def carData():
if yearChoice == 2008:
fileName = "epaVehicleData2008.csv"
elif yearChoice == 2009:
fileName = "epaVehicleData2009.csv"
fileIn = open(fileName, "r")
fileIn.readline()
minMPG = []
maxMPG = []
for line in fileIn:
line = line.strip()
dataList = line.split(",")
if dataList[0] not in ['VANS - PASSENGER TYPE', 'VANS - CARGO TYPE', 'TRUCK', 'MINIVAN - 4WD', 'MINIVAN - 2WD']:
minMPG.append(int(dataList[8]))
maxMPG.append(int(dataList[8]))
maximumMPG = max(maxMPG)
minimumMPG = min(minMPG)
fileOut = open(saveResults, "w")
print("EPA City MPG Calculator", "(" + str(yearChoice) + ")", file=fileOut)
print("---------------------------------", file=fileOut)
print("Maximum Mileage (city):", maximumMPG, file=fileOut)
for line in fileIn:
line = line.strip()
dataList = line.split(",")
if dataList[8] == maximumMPG:
print("\t", dataList[1], dataList[2], file=fileOut)
print("Minimum Mileage (city):", minimumMPG, file=fileOut)
for line in fileIn:
line = line.strip()
dataList = line.split(",")
if dataList[8] == minimumMPG:
print("\t", dataList[1], dataList[2], file=fileOut)
fileIn.close()
fileOut.close()
def complete():
print()
print("Operation Success! Mileage data has been saved to", saveResults)
print("Thanks, and have a great day!")
def main():
carData()
complete()
main()
You open the input file only once via:
fileIn = open(fileName, "r")
But then you attempt to iterate over the lines in the file three times using that same file handle. You have three of these:
for line in fileIn:
Since you never close and reopen the file, and you don't do anything to seek back to the beginning of the file, the second and third such iterations will never do anything, because you've already reached the end of the file. Using this construct does not cause the file pointer to be rewound and the contents of the file to be iterated over another time.
Since your interesting print statements are in the second and third iterations over the file, which won't produce any new lines (the code in those iteration blocks will never be called), of course your print statements are never called and no output is generated.
To fix your problem, if this is the only thing wrong with your code, just put this line before each of the second and third iterations over the file:
f.seek(0)
to seek back to the beginning of the file and iterate over the contents a second and third time.
To be more efficient, you could read each line once and store it in a list. Then you could iterate over the list each time, and not have to read from the file three times. You could even do the strip and the split just once, making the code even more efficient.
Another small logic error that I see is that you skip one line in your first iteration over the file. I assume that this is to skip a header row at the start of the file. But you don't do this for the second and third iterations, so it seems that if you only add the seek() as I suggested, you'll be trying to process the header line as a data line. This would also be solved by reading the file once into a list. You'd skip the first line of the file while you do this. If you want to leave the code alone other than adding the seek(), you should also skip the first line before the second and third iterations.

Ordering a high score list by numeric value (descending order) in a .txt file using Python

I'm currently working on a challenge that requires me to save high scores to a .txt file. I'm happy with the process of writing the scores to the file, but I'm having some real difficulty working out how to order it in descending order.
I understand that I'm only able to write strings and not integers to a text file, but this leads to me not being able to order the scores as I would like, for example, .sort(reverse=True) would place a 9 higher than 15 on the list.
Is there any way around this issue at all or is this just the nature of writing to a .txt file? Thanks in advance and here is my code:
def high_score():
# open high scores
try:
text_file = open("high_score.txt", "r")
high_scores = text_file.readlines()
except FileNotFoundError:
high_scores = []
# get a new high score
name = input("What is your name? ")
player_score = input("What is your score? ")
entry = (player_score + " - " + name + "\n")
high_scores.append(entry)
high_scores.sort(reverse=True)
high_scores = high_scores[:5]
# write high scores to .txt file
text_file = open("high_score.txt", "w+")
for line in high_scores:
text_file.write(line)
What you need is Natural order sorting... See: https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
You can simplify the code from the blog to suit your need...
get_sort_keys = lambda k: int(k.split(' ')[0])
high_scores.sort(key=get_sort_keys, reverse=True)
The correct way is to first store a list of pairs (2-tuples) containing each a numerical score and a name, then sort the list by the numerical scores (descending order) and only then convert each pair record to a string and write that to the file.
You code could become:
def high_score():
# open high score file
try:
linenum = 0
with open("high_score.txt", "r") as text_file: # ensure proper close
high_scores = []
for line in text_file:
linenum += 1
score, name = line.strip().split('-', 1)
high_scores.append((int(score), name))
except FileNotFoundError:
high_scores = []
except ValueError:
print("Error line", linenum)
# get a new high score
name = input("What is your name? ")
player_score = input("What is your score? ")
high_scores.append((int(player_score), name))
high_scores.sort(reverse=True, key=lambda x: x[0])
high_scores = high_scores[:5]
# write high scores to .txt file
with open("high_score.txt", "w+") as high_scores
for line in high_scores:
text_file.write(str(line[0] + "-" + line[1])

How do you draw data from a csv file, and then store it is a variable?

I am trying to make a program that will get a user to input a GTIN-8 product code (done) and then for the program to search for it in a CSV file.
I then want to store the matched product in a variable in python so that I can add up the total cost of an order and display a receipt.
I am having problems getting the program to match the user inputted code with the code and product in the csv file.
Here is what I am trying;
def checkfile(code):
import csv
with open('products.csv', newline='') as f:
reader = csv.reader(f)
for row in reader:
file = open("products.csv", "r")
line = file.readline()
#file.close()
data = line.split(",")
#if code ==(data[0]):
if code in reader:
print("We found your product")
else:
print("We couldn't find your product")
start()
The CSV file looks like this;
65593691 500 Laminate Sheets 4.5
98593217 200 Sticky Notes 2.5
98693214 Blue Pencil Sharpener 2.1
98693399 500 Sheets of Value Paper 5
At the moment, the program just prints 'We couldn't find your product'
I need a way for it to find the product, print it and it's details out, and then store as a variable.
If anyone could help, I'd be very grateful.
Below is the code I have which is ta king the user input, as requested,
def start():
while True:
code = input("Please enter the product code of a product you wish to "
"add to your order:")
if len(code) == 8:
for i in code:
try:
int(i)
valid = checkvalidity(code)
except ValueError:
print("You have entered an invalid code type. Product codes "
"are 8 numbers long. Please try again.")
start()
else:
print("You have entered an invalid code type. Product codes are "
"8 numbers long. Please try again.")
start()
def checkvalidity(code):
number = code
total = (int(number[0]) * 3 + int(number[1]) +
int(number[2]) * 3 + int(number[3]) +
int(number[4]) * 3 + int(number[5]) +
int(number[6]) * 3 + int(number[7]))
if total % 10 == 0:
check = 0
print("Valid.")
checkfile(code)
else:
print("Invalid. Please try again.")
start()
Life is much easier with pandas.
dataframe = pandas.read_csv('file.csv')
Pandas documentation
Below is a function which returns the data found, if any, which would be better than storing it in a (global) variable in the function itself.
import csv
def checkfile(code):
found = None
with open('products.csv', newline='') as f:
reader = csv.reader(f, delimiter='\t')
for row in reader:
if row[0] == code:
print("We found your product")
found = row
break
else:
print("We couldn't find your product")
start() # ???
return found
result = checkfile('98693214') # -> We found your product
print('result: {}'.format(result)) # -> result: result: ['98693214', 'Blue Pencil Sharpener', '2.1']
result = checkfile('00000000') # -> We couldn't find your product
print('result: {}'.format(result)) # -> result: None
It looks like your csv file is delimited with tab characters, so I specified that.
def readFile(f):
o = open(f) # openFile
t = o.read()# get raw Text
l = t.split("\n") # Get lines
csv = [k.split(",") for k in t.split("\n")] # nested list of table contents
Now notice that there are options here I think the method you need is something more like this,
def readFile(f,d):
o = open(f)
r = [k.split(d) for k in o.read().split("\n")]
o.close()
return(r)
Where you would need to pass "\t" as d so that python knows to use tab as the delimiter for the lines of the csv file.
sometimes in my projects I will name files with the suffix defined by p:"|" , t:"\t", c:"," where the letter indicates the delimiter psv csv and tsv and sv stands for separated values. But more recently I have just been thinking json.
So, you may have figured this out by now but just in case anyone is still having trouble with it, I just figured it out: Instead of looking for row in reader, you should make a new variable (eg called read_lines) that reads through each individual line. Also, I used .read instead of .reader which may make a difference.
#use:
reader = csv.read()
read_lines = f.readlines()
#then later:
for row in read_lines
#and
if code in read_lines
This will read through each individual line to see if the code appears at all rather than skimming through to see if the code is on its own.
The way I did it was like this:
with open ('Stock File.csv','r') as stock:
reader=csv.reader(stock, delimiter=',')
barcode=input('Enter the GTIN-8 code of the product you wish to purchase: ')
quantity=int(input('Enter the quantity you wish to purchase: '))
for row in reader:
if barcode in row:
cost=float(row[2])
price=quantity*cost
total=total+price
receipt.append(barcode+' '+row[1]+' '+str(quantity)+' '+row[2]+' '+str(price))
numofitems=numofitems+1
for i in range(0, numofitems):
print(str(receipt[i]))
time.sleep(0.2)
The section of my code takes the input and searches for it in the stock file. It then saves the cost of the product as a variable.
I hope I managed to help.

Replacing data in files with new inputs

First of all my program must work with several files and 10 inputs in every file, this is just little piece, to be clear.
My code right now:
code = input(">> ")
print("\nPress <Enter> and parameter will be same!")
f = open("komad_namestaja.txt", "r")
allDATA = f.readlines()
f.close()
for line in allDATA:
lst = line.split("|")
if code == lst[0]:
print("\nName :", lst[1])
name = input("New Name >> ")
if name == "":
name = lst[1]
f = open("komad_namestaja.txt", "r")
allDATA = f.read()
f.close()
newdata = allDATA.replace(lst[1], name)
f = open("komad_namestaja.txt", "w")
f.write(newdata)
f.close()
print("\ndestination :", lst[2])
destination = input("New destination >> ")
if destination == "":
destination = lst[2]
#Writting function here
File before:
312|chessburger|Denmark
621|chesscake|USA
code input: 312
name input: Gyros
destination input: Poland
file after inputs:
312|Gyros|Poland
621|chesscake|USA
Problem is this replacing in file I cant write 7 lines code every time, because I have 10 x 5 inputs, and also I tried everything and cant make function of this.
I must write some function for reading/writing/replacing or replacing all inputs after last one.
You don't have to read the file in every time to modify one field, write it out, reopen it to change another field, and so on. That's inefficient, and in your case, causes code explosion.
Since your files are small, you could just read everything into memory at once and work on it in memory. Your code is easy to map via a dict.
Here's a function that takes a filename and converts your file into a dictionary.
def create_mapping(filename):
with open(filename, 'r') as infile:
data = infile.readlines()
mapping = {int(k): (i,d) for k,i,d in
(x.strip().split('|') for x in data)}
# Your mapping now looks like
# { 312: ('cheeseburger', 'Denmark'),
# 621: ('chesscake', 'USA') }
return mapping
Then you can update the mapping from user input since it's just a dictionary.
Once you want to write the file out, you can just serialize out your dictionary by iterating over the keys and rejoining all the elements using |.
If you want to use lists
If you want to stick with just using lists for everything, that is possible.
I would still recommend reading your file into a list, like so:
def load_file(filename):
with open(filename, 'r') as infile:
data = infile.readlines()
items = [(int(k), i, d) for k,i,d in
(x.strip().split('|') for x in data]
# Your list now looks like
# [(312, 'cheeseburger', 'Denmark'), (621, 'chesscake', 'USA')]
return items
Then when you get some user input, you have to traverse the list and find the tuple with what you want inside.
For example, say the user entered code 312, you could find the tuple that contained the 312 value from the list of tuples with this:
items = load_file(filename)
# Get input for 'code' from user
code = int(input(">> "))
# Get the position in the list where the item with this code is
try:
list_position = [item[0] for item in items].index(code)
# Do whatever you need to (ask for more input?)
# If you have to overwrite the element, just reassign its
# position in the list with
# items[list_position] = (code, blah, blah)
except IndexError:
# This means that the user's entered code wasn't entered
# Here you do what you need to (maybe add a new item to the list),
# but I'm just going to pass
pass

Categories

Resources