Python 3 Classes and Dictionary - python

I'm working on a project here and am pretty confused as of how to handle this next part. Included is the project description.
Student class: The student class will store the information about the student id, student’s first and last names, and a dictionary named grades that contains the course number and letter grades of the classes the student has taken. You will write accessor methods for student id, first name, and last name and mutator methods for student’s first name and last name. There will be two more methods in the Student class as shown below.
getCourseNumbers(): it returns a list of course numbers that the student has taken. It returns an empty list if the student has not taken any courses.
getGrade(course_no): it returns the grade the student has received in the course identified by the parameter course_no. If the given course number is not found, this function returns ‘Z’ to indicate that.
addCourse(course_no, grade): adds a course with the course number and grade
updateCourse(course_no, grade): updates an existing course’s grade
deleteCourse(course_no): deletes the course from the dictionary.
Here is my code thus far:
class Course:
def __init__ (self, __crn, __ch, __ins):
self.__crn = crn
self.__ch = ch
self.__ins = ins
def coursename(self):
return self.__crn
def credithour(self):
return self.__ch
def instructor(self):
return self.__ins
class Student:
def __init__(self, id, sfirst, slast):
self.sid = sid
self.sfirst = sfirst
self.slast = slast
def studentid(self):
return self.sid
def studentfirst(self):
return self.sfirst
def studentlast(self):
return self.slast
def main():
course = Course('CSC 1100', '4', 'Name')
print (course.coursename())
print (course.credithour())
print (course.instructor())
main()
So, my question really is. Now that I've made my initial two classes, I'd like to create a dictionary that contains the coursename and the students grade. What's the best way to do this? I'm kind of at a loss and have tried many different ways with no success.

Why do you have a Course class? Nothing in the question asks for one; in fact, it specifies that you are to store the information as a dictionary.
I don't agree with the request for accessor methods; that is a Java idiom, not Pythonic. Similarly, the given method names are javaCase, contrary to PEP8.
The code then reduces to
class Student(object):
def __init__(self, id, firstname, lastname, grades=None):
self.id = id
self.firstname = firstname
self.lastname = lastname
self.grades = {} if grades is None else dict(grades)
def get_course_numbers(self):
return self.grades.keys()
def get_grade(self, course):
return self.grades.get(course, 'Z')
def add_course(self, course, grade):
self.grades[course] = grade
... I'll leave the last couple of methods as an exercise ;)

Recall that a dictionary is just a key-value mapping - it associates keys with particular values. So what you're being asked for is just storing with each student a mapping from a unique identifier for a course to a letter grade (stored e.g. as a string). A dictionary is an excellent tool for this task.
Recall that you can use class instances as keys, even if instances of that class are mutable. The reason is that it's not really the instance itself being used as a key, but its unique identifier (memory address).
It would probably be more apt, though, to have a way to map course numbers to Course instances. Notice the keyword map there? You could also store all the courses in a list and do a search every time you wanted to find a course by number, but that would be slow (for len(courses) -> Infinity).

Related

Queston about define a function in a class

if len(self.students) < self.max_students
Not sure what the above codes are used for on the following statement, if anyone can help?
class course:
def __init__(self,name,max_students):
self.name = name
self.max_students = max_students
self.students = [] #empty list
def add_student(self,student):
if len(self.students) < self.max_students:
self.students.append(student)
return True
return False
This statement check before adding a student to the list that the number of the students in the course is smaller than the maximum students can join the course( max_students ). if the number of the students is smaller than the maximum the code add the variable student to the list.
self is used to access in the class atributes(variables, functions, etc...)
len is used to check the length of an object
The class defined in the code have three variables:
max_students - a number that save the maximum number of students that can join the course
students - list that saves the students that are in the course
name - a string that contain the course name
if len(self.students) < self.max_students
Here self.max_students defines the capacity of the student's list or it's just like a maximum number of students should be allowed inside a class.
So, here we are checking whether the number of students in a class should not exceed the capacity.
It was to control that the max_student value for the instance of this class is not exceeded. That permits to avoid to add indefinitely values

How to compare user input to class object data?

total beginner here.
I'm trying to understand how to compare user input data to preset class data. Let's say class = Person, with the age, weight, and name.
And I've got two people, johnsmith = Person(50, 200, "John Smith")
tomjones = Person(40, 220, "Tom Jones")
I want to prompt the user to input the name Tom, and have check all "name" attributes for the Person class. So something like,
person = input(print("Insert the person's name: ")) entering "Tom" and then returning a list of all Toms for the user to select, or if there's only one, confirm that the user in fact did mean Tom Jones.
Should I create an array like people = [tomjones, johnsmith, (etc)], and somehow search inside that?
Please point me in the right direction on this, thanks.
Should I create an array-like people = [tomjones, johnsmith, (etc)], and somehow search inside that?
Yes.
Although ihough if you expect a very large number of "people" and frequent queries always on the same field (e.g. you're always searching for a specific person by its name) you may also want to create an index to speed up searches e.g. create a dict (possibly a WeakValueDictionary) which maps whatever your search key is to the proper person.
This is because iterating a list is cheap and efficient but going through the entire list is linear time (O(n)) so if your list is very long and you go through it a lot, it adds up. Building an index is expensive (and requires going through the entire list at least once) and there is more setup to the lookup but setup aside the lookup is constant time (O(1)).
Storing the instances of your Person class in a list would be a way of doing this. You can then loop through the list to match the names.
e.g.
class Person():
def __init__(self, age, weight, name):
self.age = age
self.weight = weight
self.name = name
all_persons = []
all_persons.append(Person(50, 200, "John Smith")) # add your Person instance to a list
all_persons.append(Person(40, 220, "Tom Jones"))
name_input = input("Insert the person's name: ")
for person in all_persons: # loop through list of all Person instances
if name_input.lower() in person.name.lower(): # lower() to handle case
# do something with person

Why can't I get list to access a class, properly?

I'm new to Python. I'm making a basic list that has access to a class. But when I send it to the output, the terminal lists it as a basic object, instead of each class item seperately.
Here is the code:
# Make a class with attributes
class Persons(object):
def __init__(self, firstName=None, lastName=None):
self.firstName = firstName
self.lastName = lastName
# Make list with said attributes
def newList():
nameList = []
nameList.append(Persons("Mathew", "Dodson"))
nameList.append(Persons("Dr", "Kin"))
# Print out said list
print(str(nameList.firstName))
print(str(nameList.lastName))
newList()
I'm pretty sure I have the syntax entirely wrong some way. If anyone could help, that would be appreciated.
Make the list
To make list you don't need to do successive appends, just declare it in one go like so:
name_list = [Persons('Mathew', 'Dodson'), Persons('Dr', 'Kin')]
Iterate the list
The list itself has no first_name attribute. But the elements do.
for person in name_list:
print(person.first_name)
print(person.last_name)
I'm sorry to change your naming convention, but everyone uses snake case in python. You may explore further in PEP8.
nameList is a list - it contains Person objects. nameList.firstname will not work. You will need to select one of those Persons inside nameList, and then access the attributes. For example:
# Print out said list
print(str(nameList[0].firstName)) # Mathew
print(str(nameList[1].lastName)) # Kin
If you want to print out the whole list:
for person in nameList:
print(person.firstName)
print(person.lastName)
nameList is a list, lists don't have a firstName or lastName attribute (there is no magic by which lists automatically access attributes of their members).
Similarly, you say "the terminal lists it as a basic object, instead of each class item separately." That's because just like lists, object's have a repr that's fairly uninformative, but unlike lists, you can customize it.
If the goal is to print out each person's first name and last name, you could do something like:
for person in nameList:
print(person.firstName, person.lastName) # Separates with a space
You could also achieve this by defining a __str__ or __repr__ for your class such that a more useful string form is produced automatically, e.g.:
class Persons(object):
def __init__(self, firstName=None, lastName=None):
self.firstName = firstName
self.lastName = lastName
def __repr__(self):
return 'Persons({!r}, {!r})'.format(self.firstName, self.lastName)
def __str__(self):
return '{} {}'.format(self.firstName, self.lastName)
which would allow you to get the same results as the first loop I described with just:
for person in nameList:
print(person) # __str__ is called automatically, and you made it return something useful

The best suggestion for a structure to store multiple keys and values - dictionaries?

The intention is to create the beginnings of a facebook type program (For teaching purposes) that stores a list of names of individuals along with their bio details.
I have two questions, one follows on from the next:
Question 1: How to get at the list values that are part of a value in a key value pair in a dictionary. For instance, to find out what friends John and Mary have in common, in this case friend1 and friend3
Question 2: The best method to create a structure that stores names,gender,hobbies,and friends? Would this be a dictionary and if so, how could this be defined? If not, what do people suggest?
#create a dictionary that stores names, and a list of friends
facebook_profile={"John":["friend1","friend2","friend3","friend4"],"Mary":["friend1","friend7","friend3","friend9"]}
print(facebook_profile)
Required to store and subsequently print the following sample data:
Name:John
Gender: Male
Hobbies: Chess
Friends: friend1,friend2,friend3,friend4
Name: Mary
Gender: Female
Hobbies: Chequers
Friends: friend1,friend2,friend3,friend4
I am aware the best solution would be a database and to use some sort of file handling to achieve it however, for teaching purposes we are trying to use only lists or dictionaries. These dictionaries/lists could then be written to a file, but the solution(s)/answers I am looking for must ideally be utilising only the list and dictionary structures.
Another way is to store in Database with table columns and firends having many-to-many relationship with that table.
For Question 1, a set is a good choice for quickly and easily calculating intersections.
For Question 2, a dictionary works well.
For example:
facebook_profile={
"John":{"friends":{"friend1","friend2","friend3","friend4"},"Gender": "Male"},
"Mary":{"friends":{"friend1","friend7","friend3","friend9"},"Gender": "Female"}
}
mutual_friends = facebook_profile["John"]["friends"].intersection(facebook_profile["Mary"]["friends"])
print (mutual_friends)
Gives output:
{'friend1', 'friend3'}
Create a class:
class Person:
def __init__(self, name, gender, hobbies, friends):
self.name = name
self.gender = gender
self.hobbies = hobbies
self.friends = friends
def getMutualFriends(self, personB):
return list(set(personB.friends).intersection(self.friends))
person1 = Person('John', 'male', ['Chess'], ['friend1', 'friend2'])
person2 = Person('Anna', 'female', ['Soccer'], ['friend1', 'friend3'])
print(person1.getMutualFriends(person2))
EDIT You edited your answer to state you want lists and dicts only, So maybe this is not for you, however, classes are the best way to achieve complicated storage of information about similar repetitive objects with many features, so i'll just leave this here
As this gets more and more complicated, nested dictionaries can be a real headache, I would suggest you define a class, something like this:
class Person()
def __init__(self, name, gender):
self.name = name
self.gender = gender
For that class you can define all sorts of methods, like finding friends in common:
class Person()
def __init__(self, name, gender):
self.name = name
self.gender = gender
self.friends = []
self.hobbies = []
def add_friends(self, list_of_friends):
self.friends += list_of_friends
def add_hobbies(self, list_of_hobbies):
self.hobbies += list_of_hobbies
def mutual_friends(self, another_person):
return list(set(another_person.friends).intersection(self.friends))
Then all you have to do is initialize each friend and start running all sorts of methods
john = Person('John', 'Male')
john.add_friends(['friend1', 'friend2', ...]
mary = Person('Mary', 'Female')
mary.add_friends(['friend3', 'friend7', ...]
common_friends = john.mutual_friends(mary)
print(common_friends) # Will print a list of mutual friends
And like others said, for long lists of friends a more efficient way is to use set with intersection

(Python) Creating a list with a unique automatically generated name [duplicate]

This question already has answers here:
How can you dynamically create variables? [duplicate]
(8 answers)
Closed 8 years ago.
I am trying to automate populating a town by randomly generating households. I generate the name of the town, generate the number of households, the last name of each household and number of occupants in each. That much is fine. I am now, however, trying to create each individual, to generate a first name, a sex, an age and an occupation, and I'd like to store this data in a list as well, one list containing the attributes of each person. The problem I'm running into is that I want to use a for loop, something like:
#houseArray[currentFam][1] is the number of members in the current house.
for currentFam in range(houseArray[currentFam][1]):
uniquelyNamedArray[0] = genSex()
uniquelyNamedArray[1] = genFirstName()
uniquelyNamedArray[2] = genAge()
So... look at the data of the first household, use a for loop to iterate through each member assigning stats, then go to the next household and do the same, progressing through each household. My problem lies in not knowing how to assign a unique name to each array created by the for loop. It doesn't really matter what the name is, it could be anything as long as each person has their own uniquely named array storing their attributes.
Use a dictionary with the person's name as the key. Like:
people = {}
people["Billy Bloggs"] = ['23','Male','263 Evergreen Tce'] # store to dict
print ( people["Billy Bloggs"] ) # get stuff out of dict
Better still, give the attributes names by storing those as a dict as well:
people["Billy Bloggs"] = { 'Age':23, 'Gender':'M', 'Address':'263 Evergreen Tce' }
print ( people["Billy Bloggs"]['Age'] ) # Get billy's age
You can loop through the elements of a dictionary using the following syntax:
>>> mydict = {'a':'Apple', 'b':'Banana', 'c':'Cumquat'}
>>> for key, value in mydict.iteritems():
... print ('Key is :' + key + ' Value is:' + value)
...
Key is :a Value is:Apple
Key is :c Value is:Cumquat
Key is :b Value is:Banana
Note that there is no guarantee on the order of the data. You may insert data in the order A, B, C and get A, C, B back.
Note: The keys of a dict, in this case the person's name, are constrained to be unique. So if you store data to the same name twice, then the first key:value pair will be overwritten.
mydict["a"] = 5
mydict["a"] = 10
print (mydict["a"]) # prints 10
Sidenote: some of your gen*() functions could almost certainly be replaced by random.choice():
import random
first_names = ['Alice','Bob','Charlie','Dick','Eliza']
random_first_name = random.choice(first_names)
Keep data out of your variable names and just store them in a dict.
First, while you haven't shown us the surrounding code, you are probably relying too much on global variables. Rather than trying to create uniquely named arrays for each family member simply do something like this:
Don't really do this (I'll tell you why in a minute)
#houseArray[currentFam][1] is the number of members in the current house.
for currentFam in range(houseArray[currentFam][1]):
family_member_info = []
family_member_info[0] = genSex()
family_member_info[1] = genFirstName()
family_member_info[2] = genAge()
# Pretend 2 is where we are storing the family member information list
houseArray[currentFam][2].append(family_member_info)
A better way
Don't use an array for this sort of thing - it gets very difficult very quickly to tell what is actually stored in which index. Even in your example you have to note that houseArray[currentFam][1] is storing the number of members in the current house.
I would use either a dictionary or a named tuple and store your information in there. That way you can do something like this:
from collections import namedtuple
# Create a class called "household"
# with three fields, "owner", "size" and "members"
household = namedtuple("household", "owner size members")
househould_array = []
# Create some households and put them in the array
household_array.append(household("Family #1", 3, []))
household_array.append(household("Family #2", 1, []))
household_array.append(household("Family #3", 7, []))
# Loop over every household in the household_array
for family in household_array:
# Each `household` namedtulpe's values can be accessed by
# attribute as well as by index number
# family[1] == family.size == 3
# (for Family #1)
for member_number in range(family.size):
# family[2] == family.members == []
# (before we put anything in it)
family.members.append(generate_family_member())
You are mixing program data with variable names. It is okay to call a variable something generic; you do this all the time: e.g. in your for-loop, you use currentFam rather than the name of the family. Asking to uniquely name the array makes (no offense) as much sense as either asking what to name currentFam (it doesn't matter what you name it), or alternatively trying to do:
Andersons[0] = genSex()
Andersons[1] = genFirstName()
Andersons[2] = genAge()
Longs[0] = genSex()
Longs[1] = genFirstName()
Longs[2] = genAge()
Smiths[0] = genSex()
Smiths[1] = genFirstName()
Smiths[2] = genAge()
...
Variables are separate from program data.
You should just name your array person, and store it with other arrays. Even better would be to define a class Person(object): ..., so you could do things like x.name and x.age, but you don't need to do that. For example:
class Person(object):
def __init__(self, **kw):
self.data = kw
self.__dict__.update(kw)
def __repr__(self):
return str('Person(**{})'.format(self.data))
__str__ = __repr__
M = Person.M = 'm'
F = Person.F = 'f'
ALL_PEOPLE = set()
for ...:
person = Person(name=..., age=..., sex=...)
people.add(person)
Then to find people:
def findPeople(name=None, age=None, custom=set()):
matchers = custom
if name!=None:
matchers.add(lambda x:name.lower() in x.name.lower())
if age!=None:
matchers.add(lambda x:age==x.age)
return set(p for p in ALL_PEOPLE if all(m(p) for m in matchers))
Demo:
ALL_PEOPLE = set([
Person(name='Alex', age=5, sex=M),
Person(name='Alexander', age=33, sex=M),
Person(name='Alexa', age=21, sex=F)
])
>>> pprint.pprint( findPeople(name='alex', custom={lambda p: p.age>10}) )
{Person(**{'age': 33, 'name': 'Alexander', 'sex': 'm'}),
Person(**{'age': 21, 'name': 'Alexa', 'sex': 'f'})}
Wow, I really enjoyed reading all of the other answers.
So many great suggestions including, but not limited to:
#Sean Vieira suggests named-tuples -- an excellent, light-weight choice;
#ninjagecko uses a neat trick to dynamically assign instance attributes;
#Li-aung Yip mentions using the built-in sqlite3 module.
Much if not all of what's here has already been suggested.
If nothing else I hope this answer is an introduction to what classes may provide beyond what is provided by other data-structures.
Caveat: If performance is a huge concern, modeling each entity as a class might be overkill.
from __future__ import division, print_function
class Town(object):
def __init__(self, name=None, country=None, area_km2=0, population=0):
self.name = name
self.area_km2 = area_km2
self.area_mi2 = self.area_km2 * 0.38610217499077215
self.population = population
self.households = []
#property
def total_households(self):
return len(self.households)
#property
def population_density_per_km2(self):
try:
return self.population / self.area_km2
except ZeroDivisionError:
return 0
#property
def population_density_per_mi2(self):
try:
return self.population / self.area_mi2
except ZeroDivisionError:
return 0
class Household(object):
def __init__(self, primary_lang='Esperanto'):
self.primary_lang = primary_lang
self.members = []
#property
def total_members(self):
return len(self.members)
class Person(object):
def __init__(self, age=0, gender=None, first_name=None):
self.age = age
self.gender = gender
self.first_name = first_name
if __name__ == '__main__':
londontown = Town(name='London',
country='UK',
area_km2=1572,
population=7753600)
print(londontown.population_density_per_km2)
print(londontown.population_density_per_mi2)
a_household = Household()
a_household.members.append(
Person(age=10, gender='m', first_name='john'),
)
a_household.members.append(
Person(age=10, gender='f', first_name='jane')
)
londontown.households.append(a_household)
print(londontown.total_households)
print(a_household.total_members)

Categories