12.2 Mutable Lists
Let’s expand our study of updates yet again, this time looking at updating lists. We’ll start with lists.
Imagine that Shaunae wants to use a program to maintain her shopping list. She creates an initial list with two items:
shaunae_list = ["bread", "coffee"]
Shaunae wants to add eggs to her list. Write a line of code to accomplish this.
There are two ways you could have done this:
# approach 1 shaunae_list = shaunae_list + ["eggs"] #approach 2 shaunae_list.append("eggs")
The first version creates a new list containing
"eggs", then puts the elements of the two lists together in a new list.
The second version inserts
"eggs"into the existing list in the heap.
Let’s look at the directories for each version. Here’s the final directory for the first version:
shaunae_listis in address 1005, the list with
"eggs"is in 1008, and the combined list is in 1010.
In contrast, the final directory for the second version would look like:
Which approach do you think is better? Why?
At first glance, the second approach might seem better because it
doesn’t create additional unnecessary lists. Both approaches result in
the same contents in
shaunae_List, so there seems little
benefit to using the additional space.
Unless, of course, we want to still have access to the old version of
shaunae_list later on. The old list is still in the heap
(though our current program has no name through which to access that
old list). What if we instead had written the program this way?
shaunae_list = ["bread", "coffee"] prev_list = shaunae_list shaunae_list = ["paint", "brushes"] + shaunae_list
Now, if Shaunae realizes she goofed and put her art supply shopping on the grocery list on that last update, she could “undo” the update by resetting her list variable to the previous list:
shaunae_list = prev_list
Undoing a modification (just like the undo feature in document-editing tools) is just one example of where it can help to hang on to older versions of data for a little while. The point here is not to give a sophisticated treatment of undoing computations, but more to motivate that there are situations in which creating a new list is preferable to updating the old one.
When might we want to update, rather than preserve, the existing list?
Remember our discussion of aliasing? We wanted two people, Elena and Jorge to share access to a common bank account. Might we ever want a shared shopping list? Sure, Shaunae and her roommate Jonella do share a shopping list, so that they can both add items while letting either one go to the store.
Set up a shared shopping list that is accessible through two names,
jonella_list. Then, add an item to the list via one of these names and check that the item appears under the other name.
You might have written something like the following:
shaunae_list = ["bread", "coffee"] jonella_list = shaunae_list jonella_list.append("eggs")
In contrast, had we written the code as follows, only one of them would see the new item:
>>> jonella_list = ["apples"] + jonella_list >>> jonella_list ["apples", "bread", "coffee", "eggs"] >>> shaunae_list ["bread", "coffee", "eggs"]
Draw the memory diagram for the above program.
Exercise: Creating Lists of Accounts
In [REFSEC], we wrote a function to create new accounts for the bank. That function returned each new account as it was created. That meant that every newly-created account had to be associated with a name in the directory (otherwise we would not be able to access it from the heap).
Maintaining either a list or a dictionary of all the created accounts
makes much more sense. We’d need only a single name for the collection
of accounts, but could still access individual accounts as needed. For
example, we might want an
all_accts list that looks something
like the following:
all_accts = [Account(8623, 100), Account(8624, 300), Account(8625, 225), ... ]
Write a program that creates an empty
all_acctslist, then adds a new
Accountto it each time
create_acctis called. You will need to modify
create_acctin order to do this. Here is the existing code as a starting point.
next_id = 1 def create_acct(init_bal: float) -> Account: global next_id new_acct = Account(next_id, init_bal, ) next_id = next_id + 1 return new_acct
Did you include a line like
global all_acctsin your code? Why or why not?
If you used
append to update the
then you would not need to include
global all_accts. Recall
global is needed to tell Python to update a variable in
the top-level directory rather than the local directory. If you use
all_accts.append, however, you are modifying the heap instead
of the directory. There is no need for
global if your code is
only modifying heap contents.