Reputation: 33
I'm currently studying Computer Science at GCSE level, and am nearing my first controlled assessment. Last week we moved onto learning about lists, but for the purpose of this weeks assignment, we need to create an improved version of our address book task using a 2D array. The purpose of the task is to allow the user to enter as many people as they require into an address book, and ask them for four pieces of information. This information is to be then printed onto the screen underneath.
Be that as it may, I'm encountering an error when entering more than one person into the address book, with the error list index out of range
. I've read some posts on here already about the aforementioned error, but still don't seem to quite understand it fully. Any aid would be highly appreciated.
Here is my code thus far:
addressbook = ([])
number = int(input("How many people would you like in your address book:"))
for loop in range (0,number,1):
addressbook.append([(str(input("\nPlease enter your full name:")))])
addressbook.append([(str(input("Please enter your home address:")))])
addressbook.append([(str(input("Please enter your hometown:")))])
addressbook.append([(str(input("Please enter your mobile number:")))])
print("\nName:",addressbook[0][loop],"\nHomeaddress:",addressbook[1][loop],"\nHometown:",addressbook[2][loop],"\nMobile number:",addressbook[3][loop])
With the following error appearing:
Traceback (most recent call last):
File "C:\Users\Owner\Documents\Computer Science\Python\Address%20book.py", line 23, in <module>
print("\nName:",addressbook[0][loop],"\nHomeaddress:",addressbook[1][loop],"\nHometown:",addressbook[2][loop],"\nMobile number:",addressbook[3][loop])
IndexError: list index out of range
Upvotes: 2
Views: 578
Reputation: 54223
Rob's answer, as Apero stated, is perfect. However as Apero addressed your format with functional programming, I'll address it in OOP (mostly because I love abstracting functional code! :D)
from collections import OrderedDict
# this is only necessary if you need your fields to always be in the
# same order when they're printed. Otherwise ignore and have Person
# inherit from dict
FIELDS = ["full name", "address", "hometown", "mobile number"]
class Person(OrderedDict): # see note above
def __init__(self, keys=None):
super().__init__() # this is magic that makes the OrderedDict work
if isinstance(keys, dict):
self.update(keys)
# this allows you to pass in a normal dict like you can for any
# other dict or ordered dict, and only prompt otherwise
else:
for key in keys:
self[key] = input("Please enter your %s: " % key)
def __str__(self):
"""The __str__ function describes how str(self) looks. I'm defining
it here to mimic the way Apero used his person_print_template string"""
output_strings = []
for key,value in self.items():
output_strings.append("{}: {}".format(key, value))
return "\n".join(output_strings)
# this is equivalently:
## return "\n".join(["{}: {}".format(key,value) for key,value in self.items()])
class AddressBook(list):
def __init__(self, size=None):
if size is None:
size = int(input("How many entries are in this address book? "))
for entry in range(size):
global FIELDS
self.append(Person(FIELDS))
def __str__(self):
return "\n\n".join([entry for entry in self])
# double spaced for readability
if __name__ == "__main__":
book = AddressBook()
print()
print(book)
# OUTPUT:
How many entries are in this address book? 2
Please enter your full name: Adam Smith
Please enter your address: 123 Some St.
Please enter your hometown: Eugene, OR
Please enter your mobile number: 555-867-5309
Please enter your full name: John Smith
Please enter your address: 987 Hello World, Ave.
Please enter your hometown: Camelot (tis a silly place)
Please enter your mobile number: 555-789-1234
full name: Adam Smith
address: 123 Some St.
hometown: Eugene, OR
mobile number: 555-867-5309
full name: John Smith
address: 987 Hello World, Ave.
hometown: Camelot (tis a silly place)
mobile number: 555-789-1234
You can certainly make changes in format to suit. I'd point you at the str.format method so you can do things like justify your strings beautifully
# # inside Person.__str__
for key,value in self.items():
global SCREENWIDTH # perhaps 50?
output_strings.append("{0}: {1:>{2}}".format(key,value, SCREENWIDTH - len(str(key)) - 1))
full name: Adam
address: blah
hometown: doohickey
mobile number: 1234
Possibly a flag on AddressBook.init that works as an alternate constructor given an existing list of Person
objects.
class AddressBook(list):
def __init__(self, argument=None, _prompted=True):
if _prompted:
size = argument # just for clarity
self.from_prompt(size)
else:
lst = argument # just for clarity
self.from_list(lst)
def from_prompt(self, size):
# exactly as __init__ is above
def from_list(self, lst):
self.extend(lst)
Lots of fun stuff to be done with OOP. Personally I like them just because of Python's duck typing only becomes stronger when I can control both what kind of quack to listen to and what kind of quack to MAKE! :)
Upvotes: 2
Reputation: 8437
First, Rob's explanation above is perfect for your assignment.
Now, I wanted to show you a few techniques, you can use, to make your life easier, without entering in OOP concepts and keeping the code as simple as possible, without error catching or whatsoever.
from collections import namedtuple
Person = namedtuple(
'Person',
('full_name', 'home_address', 'home_town', 'mobile_number')
)
person_print_template = '''
Full Name: {full_name}
Home Address: {home_address}
Home Town: {home_town}
Mobile Number: {mobile_number}'''
persons = []
number = int(input('How many people would you like in your address book: '))
for iteration in range(number):
full_name = input('\nFull name: ')
home_address = input('Home address: ')
home_town = input('Home town: ')
mobile_number = input('Mobile number: ')
person = Person(full_name, home_address, home_town, mobile_number)
persons.append(person)
for person in persons:
print(person_print_template.format(**person._asdict()))
collections.namedtuple is a way of defining really simple reusable objects and offers an _asdict() method which return a dictionary.
In python, putting ** in front of a dictionary allows you to unpack each of its dictionary keys and values which can be then passed, for example here, to a template, and str.format() will replace the "{key_name}" by its value.
Again this is a powerful way of formatting string output, especially when multi-line.=, either for the web or for command line output, like here.
Upvotes: 2
Reputation: 168716
Rather than think of addressbook
as a two-dimensional array of information about people, think of each dimension separately. An addressbook
is a one-dimensional array of persons. Each person is, in turn, a one-dimensional array of information.
Bringing that thinking to our code can make the purpose much clearer:
# An address boook is an arary of persons
addressbook = []
number = int(input("How many people would you like in your address book:"))
# Create several persons, adding each to addressbook as we go:
for _ in range(number):
# Create one person:
person = []
person.append(str(input("\nPlease enter your full name:")))
person.append(str(input("Please enter your home address:")))
person.append(str(input("Please enter your hometown:")))
person.append(str(input("Please enter your mobile number:")))
# Add one person to addressbook
addressbook.append(person)
# Now addressbook has several persons
# Display addressbook, thinking of it as two-dim array
for loop in range(number):
print("\nName:",addressbook[loop][0],"\nHomeaddress:",addressbook[loop][1],"\nHometown:",addressbook[loop][2],"\nMobile number:",addressbook[loop][3])
# Display addressbook, thinking of it as array of persons:
for person in addressbook:
print("\nName:",person[0],"\nHomeaddress:",person[1],"\nHometown:",person[2],"\nMobile number:",person[3])
Other notes:
The statement addressbook = ([])
is identical to addressbook = []
. Parentheses in that context do absolutely nothing.
Multi-dimensional arrays list the row number first, the column second. That is to say, the first number gets you to a row, and the 2nd number gets you to an element of that row. More simply, say addressbook[loop][0]
, not addressbook[0][loop]
.
The expression range(0, number,1)
is identical to range(number)
, and the latter is easier to read.
In Python, if you need to assign a value but don't care about it further, use _
, like so: for _ in range(number):
.
The expression str(input(...))
is identical to the expression input(...)
, since input
returns an object of type str
. I left those alone because I suppose that verbose style is required by your instructor.
When the time comes, learn about breaking long expressions into multiple lines. Many people find that a line longer than about 80 characters is difficult to read.
You prompt the user "Please enter your full name" (emphasis added) each time. Presumably their name hasn't changed since the previous iteration of the loop. You are actually asking for some third party's full name. Try "Please enter their full name" or "Please enter this person's full name" or some such.
Finally, realize that computer programs will be read more often than they are written. This is true for professionally-maintained programs with hundreds of contributing programmers and also of one-off programs written by you for your own benefit. Use any means necessary to communicate effectively with the reader(s) of your program--comments, clear variable names, whitespace, whatever it takes.
Upvotes: 5
Reputation: 945
Use this instead
for loop in range (0,number,1):
addressbook.append([])
addressbook[-1].append([(str(input("\nPlease enter your full name:")))])
addressbook[-1].append([(str(input("Please enter your home address:")))])
addressbook[-1].append([(str(input("Please enter your hometown:")))])
addressbook[-1].append([(str(input("Please enter your mobile number:")))])
print("\nName:",addressbook[-1][0],"\nHomeaddress:",addressbook[-1][1],"\nHometown:",addressbook[-1][2],"\nMobile number:",addressbook[-1][3])
The problem with your code was you were using single dimension list. For every loop you are adding the items into addressbook so after 2 iterations of the loop it will look smthing like this
["name1","addr1","hmtown1","mob1","name2","addr2","hmtown2","mob2"]
So instead solution would be add an empty list for every iteration in loop and to that empty list we add the details so the structure would look like this
[["name1","addr1","hmtown1","mob1"],["name2","addr2","hmtown2","mob2"]]
In every iteration we access the last list by addressbook[-1] and then append the items to that last list, similarly accessing the items by using addressbook[-1][0].
Hope this helped you understand the problem.
Upvotes: 0