Tuples are immutable

A tuple is a sequence of values. The values can be any type, and they are indexed by integers, so in that respect tuples are a lot like lists. The important difference is that tuples are immutable.

Syntactically, a tuple is a comma-separated list of values:

t = 'a', 'b', 'c', 'd', 'e'
# it is common to enclose tuples in parentheses
t = ('a', 'b', 'c', 'd', 'e')

To create a tuple with a single element, you have to include a final comma:

t1 = 'a',
print(type(t1))
<class 'tuple'>
t = tuple('Babson')
t
('B', 'a', 'b', 's', 'o', 'n')

Most list operators also work on tuples. The bracket operator indexes an element:

t = ('a', 'b', 'c', 'd', 'e')
print(t[0])
print(t[1:3])
a
('b', 'c')
t[0] = 'A'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-7e674cdf20e6> in <module>()
----> 1 t[0] = 'A'

TypeError: 'tuple' object does not support item assignment

Because tuples are immutable, you can’t modify the elements. But you can replace one tuple with another:

t = ('A',) + t[1:]
# makes a new tuple and then makes t refer to it
print(t)
('A', 'b', 'c', 'd', 'e')

Tuple assignment

It is often useful to swap the values of two variables. With conventional assignments, you have to use a temporary variable. For example, to swap a and b:

a = 10
b = 90
temp = a
a = b
b = temp
print(a, b)
90 10

This solution is cumbersome; tuple assignment is more elegant:

a, b = b, a

The right side can be any kind of sequence (string, list or tuple). For example, to split an email address into a user name and a domain, you could write:

email = 'zli@babson.edu'
id, domain = email.split('@')
print(id)
print(domain)
zli
babson.edu

Tuples as return values

Strictly speaking, a function can only return one value, but if the value is a tuple, the effect is the same as returning multiple values. For example, the built-in function divmod takes two arguments and returns a tuple of two values, the quotient and remainder.

t = divmod(7, 3)
print(t)
(2, 1)

Variable-length argument tuples

Functions can take a variable number of arguments. A parameter name that begins with * gathers arguments into a tuple. For example, printall takes any number of arguments and prints them:

def printall(*args):
    print(args)
    
printall(1, 2.0, '3')
printall(1, 2.0, '3', None, True)
(1, 2.0, '3')
(1, 2.0, '3', None, True)

The complement of gather is scatter. For example:

t = (7, 3)
divmod(t)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-12-b9b469f7c0ca> in <module>()
      1 t = (7, 3)
----> 2 divmod(t)

TypeError: divmod expected 2 arguments, got 1
divmod(*t)
(2, 1)

Exercise 01

Write a function called sumall that takes any number of arguments and returns their sum. Note: you are not allowed to use any built-in function.

Lists and tuples

zip is a built-in function that takes two or more sequences and returns a list of tuples where each tuple contains one element from each sequence. The name of the function refers to a zipper, which joins and interleaves two rows of teeth.

This example zips a string and a list:

s = 'abc'
t = [0, 1, 2]
zip(s, t)
<zip at 0x24b78134848>

The result is a zip object that knows how to iterate through the pairs. The most common use of zip is in a for loop:

for pair in zip(s, t):
    print(pair)
('a', 0)
('b', 1)
('c', 2)

A zip object is a kind of iterator, which is any object that iterates through a sequence. Iterators are similar to lists in some ways, but unlike lists, you can’t use an index to select an element from an iterator.

If you want to use list operators and methods, you can use a zip object to make a list:

list(zip(s, t))
[('a', 0), ('b', 1), ('c', 2)]
def has_match(t1, t2):
    for x, y in zip(t1, t2):
        if x == y:
            return True
    return False

If you need to traverse the elements of a sequence and their indices, you can use the built-in function enumerate:

for index, element in enumerate('abc'):
    print(index, element)
0 a
1 b
2 c

The result from enumerate is an enumerate object, which iterates a sequence of pairs; each pair contains an index (starting from 0) and an element from the given sequence.

Dictionaries and tuples

Dictionaries have a method called items that returns a sequence of tuples, where each tuple is a key-value pair.

d = {'a':0, 'b':1, 'c':2}
t = d.items()
t
dict_items([('a', 0), ('b', 1), ('c', 2)])
for key, value in d.items():
    print(key, value)
a 0
b 1
c 2

Sequences of sequences

In many contexts, the different kinds of sequences (strings, lists and tuples) can be used interchangeably. So how should you choose one over the others?

Strings are more limited than other sequences because the elements have to be characters. They are also immutable. If you need the ability to change the characters in a string (as opposed to creating a new string), you might want to use a list of characters instead.

Lists are more common than tuples, mostly because they are mutable. But there are a few cases where you might prefer tuples:

  1. In some contexts, like a return statement, it is syntactically simpler to create a tuple than a list.
  2. If you want to use a sequence as a dictionary key, you have to use an immutable type like a tuple or string.
  3. If you are passing a sequence as an argument to a function, using tuples reduces the potential for unexpected behavior due to aliasing.

Because tuples are immutable, they don’t provide methods like sort and reverse, which modify existing lists. But Python provides the built-in function sorted, which takes any sequence and returns a new list with the same elements in sorted order, and reversed, which takes a sequence and returns an iterator that traverses the list in reverse order.

Exercise 02

1. Write a function called most_frequent that takes a string and prints the letters in decreasing order of frequency. Find text samples from several different languages and see how letter frequency varies between languages. Compare your results with the tables at http://en.wikipedia.org/wiki/Letter_frequencies.

2. Write a program that reads a word list from a file and prints all the sets of words that are anagrams.

Here is an example of what the output might look like:

['deltas', 'desalt', 'lasted', 'salted', 'slated', 'staled']

['retainers', 'ternaries']

['generating', 'greatening']

['resmelts', 'smelters', 'termless']

Hint: you might want to build a dictionary that maps from a collection of letters to a list of words that can be spelled with those letters. The question is: how can you represent the collection of letters in a way that can be used as a key?

3. Modify the previous program so that it prints the longest list of anagrams first, followed by the second longest, and so on.

4. (Optional)Here’s another Car Talk Puzzler:

What is the longest English word, that remains a valid English word, as you remove its letters one at a time?

Now, letters can be removed from either end, or the middle, but you can’t rearrange any of the letters. Every time you drop a letter, you wind up with another English word. If you do that, you’re eventually going to wind up with one letter and that too is going to be an English word—one that’s found in the dictionary. I want to know what’s the longest word and how many letters does it have?

I’m going to give you a little modest example:Sprite. Ok? You start off with sprite, you take a letter off, one from the interior of the word, take the r away, and we’re left with the word spite, then we take the e off the end, we’re left with spit, we take the s off, we’re left with pit, it, and I.

Write a program to find all words that can be reduced in this way, and then find the longest one. You can use the template reducible.py.

This exercise is a little more challenging than most, so here are some suggestions:

  1. You might want to write a function that takes a word and computes a list of all the words that can be formed by removing one letter. These are the “children” of the word.
  2. Recursively, a word is reducible if any of its children are reducible. As a base case, you can consider the empty string reducible.
  3. The wordlist I provided, words.txt, doesn’t contain single letter words. So you might want to add “I”, “a”, and the empty string.
  4. To improve the performance of your program, you might want to memoize the words that are known to be reducible.