tuples and lists - python

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.

Related

Strange behaviour when assigning list value to key in dictionary in Python

I'm having trouble to udnerstand what I did wrong in my simple code. I've found a "solution" but i would like to understand "why" :)
In general i'm doing a simple "group by" using the dictionary and lists.
When I assign to a key a value that is a 2 element list, the first element(value) in that dictionary, is splitted into two seperate values, all other value behave properly - are 2 elements list.
So I had 4 values when the dictionary was printed.
1301
425
['979', '340']
['1301', '977']
But I was able to fix this to have 3 pair values - the desired outcome:
['1301', '425']
['979', '340']
['1301', '977']
As I said i was able to fix this issue using the .setdefault() method to assign the first element insted of simple assigment but I don't understand why does it make a difference.
Here is my code, all other aspects of the class are working properly or will work properly in the "future" :):D
import os
import copy
from pathlib import Path
class ManageSettings:
def __init__(self, record_or_not = False,config_path = ".\\settings\\"):
self.to_record = record_or_not
self.ini_path = config_path + "\\position_settings.ini" # file path for settings
path_to_save = Path(config_path)
path_to_save.mkdir(exist_ok=True)
self.__position_list = {}
def save_or_load (self, sequence = 0):
if(self.to_record):
pass
else:
self.load_settings()
def save_settings(self, sequence = 0):
with open(self.ini_path, 'w') as file_writter:
for posXY in self.position_list:
file_writter.write("{};{};{}\n".format(sequence, posXY[0], posXY[1]))
def load_settings(self):
with open(self.ini_path, 'r') as file_reader:
sequence_dict = {}
for line_XY in file_reader:
sequence, posX, posY = line_XY.strip().split(";")
XYposList = list((posX, posY))
print(XYposList)
if not sequence in sequence_dict:
Here is the part that if changed will print a different result
# sequence_dict.setdefault(sequence,[]).append( XYposList)
sequence_dict[sequence] = XYposList
else:
sequence_dict[sequence].append( XYposList) # self.position_list = sequence_dict
print ('in class')
for values in sequence_dict.values():
print (values)
self.position_list = sequence_dict
#property
def position_list(self):
return self.__position_list
#position_list.setter
def position_list(self, position_list):
self.__position_list = position_list
This is how can you simply test the class, of course if you have the ini file :)
testClas = ManageSettings(False)
testClas.save_or_load()
print("outside the class")
for value in testKlas.position_list.values():
# print (len(value))
for test in value:
print (test)
Edit: Hi, here is the content of the ini file
0;1301;425
0;979;340
0;1301;977
This line:
sequence_dict[sequence] = XYposList
should be:
sequence_dict[sequence] = [XYposList]
The values in sequence_dict are supposed to be 2-dimensional lists. But when you're creating the first value, you're not putting it in a list, you're just using XYPostList as the list. Then subsequent append() calls append to that list. So you start with
['1301', '425']
and the next line appends ['979', '340'] to that, resulting in
['1301', '425', ['979', '340']]
But what you want is for the initial value to be:
[['1301', '425']]
and then it becomes
[['1301', '425'], ['979', '340']]

Python: Nested List Inside Dictionary?

I'm currently stuck with this output of a nested list when trying to append the list as the definition of my dictionary.
I know this is probably more code than I should include, but I figured it's helpful to show more than less.
class Account:
accountInfo = {} #ex. ID : 5FE19C (hexadecimal ID's)
def __init__(self):
choice = raw_input("Would you like to login or signup?\n")
if choice.lower() == "login":
self.login()
elif choice.lower() == "signup":
print "Great! Fill in the following."
self.signup()
else:
self.__init__()
def signup(self):
import random
accountID = '%010x' % random.randrange(16**10) # 10 digit hexadecimal ID generator
personalInfo = []
self.accountInfo[accountID] = []
firstName = raw_input("First Name: ")
lastName = raw_input("Last Name: ")
email = raw_input("E-Mail: ")
password = raw_input("Password: ")
birthdate = raw_input("DOB (DD/MM/YYYY): ")
alias = raw_input("Username/Alias: ")
personalInfo.append(firstName)
personalInfo.append(lastName)
personalInfo.append(email)
personalInfo.append(password)
personalInfo.append(birthdate)
personalInfo.append(alias)
for i in range(1):
self.accountInfo[accountID].append(personalInfo)
#creates an unwanted nested list, but the output is correct
print self.accountInfo
I don't understand why I'm getting the output of a nested list in my dictionary. The contents of the dictionary are correct, but it's just that unwanted and unnecessary nested list.
output:
>>> {'6de7bcf201': [['firstName', 'lastName', 'email', 'password', 'birthdate', 'alias']]}
personalInfo = [] # PersonalInfo is a list
# skipped
self.accountInfo[accountID].append(personalInfo) # add list to the list
This is similar to
main = []
p_info = []
main.append(p_info) # result would be main = [[]]
If you want to have just a dict inside list, change personalInfo to {}
that would requare to change personalInfo.append to personalInfo[x] = y
I think this is pretty straight forward:
personalInfo is a list. You append items to it and get something like [..,..,..]. You initiate the value of self.accountInfo[accountID] also as a list. Then you append your first list to the second list, giving you a list of lists.
Instead of self.accountInfo[accountID].append(personalInfo) try self.accountInfo[accountID] = personalInfo
Welcome! A couple of things:
Imports should generally be at the top of a file. For your signup
function, the import random should be at the top.
Your __init__ shouldn't be called again on a class. __init__ is considered the "constructor" of the class - as such it should generally be called once. Consider putting your raw_input section in another function and call that twice.
Print is a function, not a statement (your last line)
To answer your question, you have a nested list because you are making two lists. You're first building up personalInfo and then appending that item to the empty list you made for accountInfo you can have a single-level list if you just set self.accountInfo[accountID] =personalInfo`.
personalInfo is a list and self.accountInfo[accountID] is another list.
With self.accountInfo[accountID].append(personalInfo) you are injecting one inside the other.
You can do self.accountInfo[accountID] = personalInfo
And what's the point of for i in range(1)? It's not looping at all!
I can't test right now but:.
Replace self.accountInfo[accountID] = [] with self.accountInfo[accountID] = personalInfo. And delete for i in range(1): self.accountInfo[accountID].append(personalInfo)

Writing to textfile in a specific way using a list in python

I am trying to write to a textfile in python where the the output in the file.
I have a Class called phonebook which has a list containing objects of the phonebook class.
My constructor looks like this:
def __init__(self,name,number):
self.name = name
self.number = number
When i add a new object to the list looks like this:
def add(self):
name = input()
number = input()
p = Phonebook(name,number)
list.append(p)
When I'm writing my list to the textfile the function looks like this:
def save():
f = open("textfile.txt","w")
for x in list:
f.write(x.number+";"+x.name+";")
f.close()
And its writes out:
12345;david;12345;dave;12345;davey;09876;cathryn;09876;cathy; and so on..
should look like this:
12345;david,dave,davey
09876;cathryn,cathy,
78887;peter,pete,petr,petemon
My question is then.. How do I implement this save function so it will only write out one unique number and all its names connected to that number?
Feels like its impossible to do with only a list containing names and numbers.. Maybe im wrong..
Dictionaries in Python give you fast access to items based on their key. So a good solution to your problem would be to index the Phonebook objects using the Phonebook.number as the key to store a list of Phonebooks as the values. Then at the end just handle the printing based on however you want each line to appear.
This example should work in your case:
phone_dict = dict() # Used to store Phonebook objects intead of list
def add(self):
name = input()
number = input()
p = Phonebook(name,number)
if p.number in phone_dict:
phone_dict[p.number].append(p) # Append p to list of Phonebooks for same number
else:
phone_dict[p.number] = [p] # Create list for new phone number key
def save():
f = open("textfile.txt","w")
# Loop through all keys in dict
for number in phone_dict:
f.write(x.number + ";") # Write out number
phone_books = phone_dict[number]
# Loop through all phone_books associated with number
for i, pb in enumerate(phone_books):
f.write(pb.name)
# Only append comma if not last value
if i < len(phone_books) - 1:
f.write(",")
f.write("\n") # Go to next line for next number
f.close()
so how would the load function look?
I have tried doing one, and it loads everything into the dictionary but the program doesnt function with my other functions like it did before i saved it and reload it to the program again..
def load(self,filename):
self.dictList = {}
f = open(filename,"r")
for readLine in f:
readLine = readLine.split(";")
number = readLine[0]
nameLength = len(readLine[1:])
name = readLine[1:nameLength]
p = phonebook(name)
self.dictList[number] = [p]
print(self.dictList)
f.close()

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).

Objects/classes/lists Python

I am confused about classes in python. I don't want anyone to write down raw code but suggest methods of doing it. Right now I have the following code...
def main():
lst = []
filename = 'yob' + input('Enter year: ') + '.txt'
for line in open(filename):
line = line.strip()
lst.append(line.split(',')
What this code does is have a input for a file based on a year. The program is placed in a folder with a bunch of text files that have different years to them. Then, I made a class...
class Names():
__slots__ = ('Name', 'Gender', 'Occurences')
This class just defines what objects I should make. The goal of the project is to build objects and create lists based off these objects. My main function returns a list containing several elements that look like the following:
[[jon, M, 190203], ...]
These elements have a name in lst[0], a gender M or F in [1] and a occurence in [3]. I'm trying to find the top 20 Male and Female candidates and print them out.
Goal-
There should be a function which creates a name entry, i.e. mkEntry. It should be
passed the appropriate information, build a new object, populate the fields, and return
it.
If all you want is a handy container class to hold your data in, I suggest using the namedtuple type factory from the collections module, which is designed for exactly this. You should probably also use the csv module to handle reading your file. Python comes with "batteries included", so learn to use the standard library!
from collections import namedtuple
import csv
Person = namedtuple('Person', ('name', 'gender', 'occurences')) # create our type
def main():
filename = 'yob' + input('Enter year: ') + '.txt'
with open(filename, newlines="") as f: # parameters differ a bit in Python 2
reader = csv.reader(f) # the reader handles splitting the lines for you
lst = [Person(*row) for row in reader]
Note: If you're using Python 2, the csv module needs you to open the file in binary mode (with a second argument of 'rb') rather than using the newlines parameter.
If your file had just the single person you used in your example output, you' get a list with one Person object:
>>> print(lst)
[Person(name='jon', gender='M', occurences=190203)]
You can access the various values either by index (like a list or tuple) or by attribute name (like a custom object):
>>> jon = lst[0]
>>> print(jon[0])
jon
>>> print(jon.gender)
M
In your class, add an __init__ method, like this:
def __init__(self, name, gender, occurrences):
self.Name = name
# etc.
Now you don't need a separate "make" method; just call the class itself as a constructor:
myname = Names(lst[0], etc.)
And that's all there is to it.
If you really want an mkEntry function anyway, it'll just be a one-liner: return Names(etc.)
I know you said not to write out the code but it's just easier to explain it this way. You don't need to use slots - they're for a specialised optimisation purpose (and if you don't know what it is, you don't need it).
class Person(object):
def __init__(self, name, gender, occurrences):
self.name = name
self.gender = gender
self.occurrences = occurrences
def main():
# read in the csv to create a list of Person objects
people = []
filename = 'yob' + input('Enter year: ') + '.txt'
for line in open(filename):
line = line.strip()
fields = line.split(',')
p = Person(fields[0], fields[1], int(fields[2]))
people.append(p)
# split into genders
p_m = [p for p in people if p.gender == 'M']
p_f = [p for p in people if p.gender == 'F']
# sort each by occurrences descending
p_m = sorted(p_m, key=lambda x: -x.occurrences)
p_f = sorted(p_f, key=lambda x: -x.occurrences)
# print out the first 20 of each
for p in p_m[:20]:
print p.name, p.gender, p.occurrences
for p in p_f[:20]:
print p.name, p.gender, p.occurrences
if __name__ == '__main__':
main()
I've used a couple of features here that might look a little scary, but they're easy enough once you get used to them (and you'll see them all over python code). List comprehensions give us an easy way of filtering our list of people into genders. lambda gives you an anonymous function. The [:20] syntax says, give me the first 20 elements of this list - refer to list slicing.
Your case is quite simple and you probably don't even really need the class / objects but it should give you an idea of how you use them. There's also a csv reading library in python that will help you out if the csvs are more complex (quoted fields etc).

Categories

Resources