\chapter{Tuples}

\section{Tuples and mutability}
So far, you have seen two compound types: strings, which are made up
of characters; and lists, which are made up of elements of any
type. One of the differences we noted is that the elements of a list
can be modified, but the characters in a string cannot. In other
words, strings are \myidx{immutable} and lists are \myidx{mutable}.

A \myidx{tuple} , like a list, is a sequence of items of any
type. Unlike lists, however, tuples are immutable. Syntactically, a
tuple is a comma-separated sequence of values:
\begin{pythonInteractive}
>>> tup = 2, 4, 6, 8, 10
\end{pythonInteractive}
Although it is not necessary, it is conventional to enclose tuples in
parentheses:
\begin{pythonInteractive}
>>> tup = (2, 4, 6, 8, 10)
\end{pythonInteractive}
To create a tuple with a single element, we have to include the final
comma:
\begin{pythonInteractive}
>>> tup = (5,)
>>> type(tup)
<type 'tuple'>
\end{pythonInteractive}
Without the comma, Python treats \pythoninline{(5)} as an integer in
parentheses: 
\begin{pythonInteractive}
>>> tup = (5)
>>> type(tup)
<type 'int'>
\end{pythonInteractive}
Syntax issues aside, tuples support the same sequence operations as
strings and lists. The index operator selects an element from a
tuple.
\begin{pythonInteractive}
>>> tup = ('a', 'b', 'c', 'd', 'e')
>>> tup[0]
'a'
\end{pythonInteractive}
And the slice operator selects a range of elements.
\begin{pythonInteractive}
>>> tup[1:3]
('b', 'c')
\end{pythonInteractive}
But if we try to use \pythoninline{item} assignment to modify one of
the elements of 
the tuple, we get an error:
\begin{python}
>>> tup[0] = 'X'
TypeError: 'tuple' object does not support item assignment
\end{python}
Of course, even if we can't modify the elements of a tuple, we can
replace it with a different tuple:
\begin{pythonInteractive}
>>> tup = ('X',) + tup[1:]
>>> tup
('X', 'b', 'c', 'd', 'e')
\end{pythonInteractive}
Alternatively, we could first convert it to a list, modify it, and
convert it back into a tuple:
\begin{pythonInteractive}
>>> tup = ('X', 'b', 'c', 'd', 'e')
>>> tup = list(tup)
>>> tup
['X', 'b', 'c', 'd', 'e']
>>> tup[0] = 'a'
>>> tup = tuple(tup)
>>> tup
('a', 'b', 'c', 'd', 'e')
\end{pythonInteractive}

\section{Tuple assignment}
Once in a while, it is useful to swap the values of two
variables. With conventional assignment statements, we have to use a
temporary variable. For example, to swap \pythoninline{a} and
\pythoninline{b}: 
\begin{python}
temp = a
a = b
b = temp
\end{python}
If we have to do this often, this approach becomes cumbersome. Python
provides a form of \myidx{tuple assignment} that solves this problem
neatly:
\begin{python}
a, b = b, a
\end{python}
The left side is a tuple of variables; the right side is a tuple of
values. Each value is assigned to its respective variable. All the
expressions on the right side are evaluated before any of the
assignments. This feature makes tuple assignment quite versatile.
Naturally, the number of variables on the left and the number of
values on the right have to be the same:
\begin{pythonInteractive}
>>> a, b, c, d = 1, 2, 3
ValueError: need more than 3 values to unpack 
\end{pythonInteractive}

\section{Tuples as return values}
Functions can return tuples as return values. For example, we could
write a function that swaps two parameters:
\begin{python}
def swap(x, y):
    return y, x
\end{python}
Then we can assign the return value to a tuple with two variables:
\begin{python}
a, b = swap(a, b)
\end{python}
In this case, there is no great advantage in making \pythoninline{swap} a
function. In fact, there is a danger in trying to encapsulate
\pythoninline{swap}, 
which is the following tempting mistake:
\begin{python}
def swap(x, y):      # incorrect version
     x, y = y, x
\end{python}
If we call \pythoninline{swap} like this:
\begin{python}
swap(a, b)
\end{python}
then \pythoninline{a} and \pythoninline{x} are aliases for the same
value. Changing \pythoninline{x} inside \pythoninline{swap} makes \pythoninline{x}
refer to a different value, but it has no effect on \pythoninline{a}
in \pythoninline{\_\_main\_\_}. Similarly, changing \pythoninline{y}
has no effect on \pythoninline{b}. 
This function runs without producing an error message, but it doesn't
do what we intended. This is an example of a semantic error.

\section{Why tuples?}
There is considerable debate (and some confusion) about why Python has
tuples at all. It hardly seems worth having a type with very similar
functionality to lists, without some of the advantages. So why have
tuples at all? The answer to that question is rather subtle and
requires some not insignificant programming experience to fully
understand. Here are some reasons why tuples are useful:
\begin{itemize}
\item Tuples are more efficient to use than lists. Admittedly, the
difference is likely to be small for most applications, but in some
cases can be significant.
\item Tuple assignment is just cool, since many
  programming languages won't allow you to do this (note the swap
  syntax above).
\item In some situations, tuples can be used as dictionary keys (more
on dictionaries in lecture \ref{lec-dictionaries}).
\end{itemize}

\section{When should you use tuples?}

You should use tuples in preference to lists when:
\begin{itemize}
\item the tuple represents a value. For example, if you want to
represent a point in two-dimensions, then a tuple is probably what you
want. The tuple \pythoninline{(2,5)} represents the point $(2,5)$ and
that point will never change its value (it's a constant). This is
similar to the notion of integers --- you wouldn't want to change the
value of the integer $2$ to represent anything other than the number
$2$;
\item you want a sequence that won't change;
\item you want to return multiple values from a pure function.
\end{itemize}

\section{Sets}

Another useful builtin type is the \pythoninline{set}. A set is an
unordered collection of items with no duplication. Set objects also
support basic mathematical set operations such as union, intersection
and difference. Sets can be constructed from any sequence type
(string, list, tuple). Following is a quick demonstration of sets:

\begin{minipage}{\textwidth}
\begin{pythonInteractive}
>>> a = set("hello there")
>>> b = set("goodbye")
>>> a
set([' ', 'e', 'h', 'l', 'o', 'r', 't'])
>>> b
set(['b', 'e', 'd', 'g', 'o', 'y'])
>>> 'b' in a                   % set membership
False
>>> 'b' in b
True
>>> a - b                     % characters in a that aren't in b
set([' ', 't', 'r', 'l', 'h'])
>>> a | b                      % characters in either a or b 
set([' ', 'b', 'e', 'd', 'g', 'h', 'l', 'o', 'r', 't', 'y'])
>>> a & b                     % characters in both a and b
set(['e', 'o'])
>>> a ^ b                      % characters in a or b but not in both
set([' ', 'b', 'd', 'g', 'h', 'l', 'r', 't', 'y'])
\end{pythonInteractive}
\end{minipage}

\section{Glossary}
\begin{description}
\item[immutable] A value that cannot be changed.
\item[mutable] A value that can be changed.
\item[tuple] An immutable sequence data structure.
\item[tuple assignment] An extremely cool way of assigning multiple
  values to multiple variables:
\begin{python}
x, y = 1, 2
\end{python}
\end{description}

\newpage
\section{Laboratory Test}
This lab is the second mastery test lab.

\subsection*{Note: Please make sure you bring your Student ID Card
  with you.}

There are no new exercises corresponding to the lecture associated with
this lab.

\subsection*{Assessment}

There are four parts to complete for this lab.  Each part is worth
2.5\% giving a total of 10\% for the entire lab.  In the
\menu{File~\rarr~Recent~Files} of IDLE you will find a file for each
part of the test.  You must add code to make the doctests pass for the
first three files.  The last file contains a description of a GUI
program that you must write.  A demonstrator will mark off each part
as you complete it.  If you are unable to complete one of the parts
you may still continue on and attempt to complete the later parts.  If
you have any questions about this test and the way in which it will be
assessed please come and see Nick Meek.

\subsection*{Writing code unaided}

In the previous labs you could complete the work using whatever
resources you wished to, including help from demonstrators, lecture
notes, previous work, on-line resources. In this lab you must complete
the task during a 90-minute time slot, with minimal resources at your
disposal.  Using only IDLE and the four provided files, you must write
all of the code needed to pass the given tests without any other help.

At first you might think this sounds a bit daunting; however we are
not asking you to write all of the code for the first time during your
allocated lab.  We encourage you to write these functions initially
using whatever resources you need. Once you have done this, and are
sure that your code is working correctly, try to write it again
without using any resources (you might need to peek occasionally).
Keep doing this until you can write it all completely unaided. If you
prepare well for this lab then you will find it pretty
straightforward.  Solving problems like this, without any outside
assistance, will build your confidence to tackle more demanding
programming tasks.

\subsection*{Note: You are not permitted to access your home
  directory, or any other files or computers, nor may you use the
  Internet during this lab.}

Listings of the files which you will be provided with during the test
are given on pages \pageref{test2-start} to \pageref{test2-end} so
that you can be well prepared.  Note that some of the functions
require only one line of code and wouldn't normally be wrapped in a
function, but in this case it is convenient for testing purposes.

\newpage
\label{test2-start}
\begin{python}
# Mastery test 2 - part 1

def contains(alist, item):
    """
    Return True if alist contains item, otherwise False
    >>> contains([3,5,7,9], 7)
    True
    >>> contains([3,5,7,9], 8)
    False
    """

def make_sentence(list_of_words):
    """
    Return a string made up of each item in list_of_words concatenated with
    a space in between each item
    >>> make_sentence(['Here','are','some','words'])
    'Here are some words'
    """


def mirror(word):
    """
    Return a string made up of word concatenated to a reversed copy
    of itself
    >>> mirror("good")
    'gooddoog'
    >>> mirror('Python')
    'PythonnohtyP'
    >>> mirror("")
    ''
    >>> mirror("a")
    'aa'
    """

def remove_duplicates(strng):
    """
    Returns a string which is the same as the argument except only the
    first occurrence of each letter is present.  Upper and lower case
    letters are treated as different.  Only duplicate letters are removed,
    other characters such as spaces or numbers are not changed. 
    >>> remove_duplicates('apple')
    'aple'
    >>> remove_duplicates('Mississippi')
    'Misp'
    >>> remove_duplicates('The quick brown fox jumps over the lazy dog')
    'The quick brown fx jmps v t lazy dg'
    >>> remove_duplicates('121 balloons 2 u')
    '121 balons 2 u'
    """

if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)
\end{python}

\newpage

\begin{python}
# Mastery test 2 - part 2


def make_numberlist(first, last):
    """
    Return a list containing all of the numbers from first to last inclusive.

    >>> make_numberlist(0,3)
    [0, 1, 2, 3]
    >>> make_numberlist(2,10)
    [2, 3, 4, 5, 6, 7, 8, 9, 10]
    """


def common_elements(list1, list2):
    """
    Return a list containing the elements which are in both list1 and list2

    >>> common_elements([1,2,3,4,5,6], [3,5,7,9])
    [3, 5]
    >>> common_elements(['this','this','n','that'],['this','not','that','that'])
    ['this', 'that']
    """


def remove_section(alist, start, end):
    """
    Return a copy of alist removing the section from start to end inclusive

    >>> remove_section([8,7,6,5,4,3,2,1], 2, 5)
    [8, 7, 2, 1]
    >>> remove_section(['bob','sue','jim','mary','tony'], 0,1)
    ['jim', 'mary', 'tony']
    """


def add_lists(lista, listb):
    """
    Returns a new list containing the sum of each corresponding pair of items
 
    >>> add_lists([1, 1], [1, 1])
    [2, 2]
    >>> add_lists([1, 2], [1, 4])
    [2, 6]
    >>> add_lists([1, 2, 1], [1, 4, 3])
    [2, 6, 4]
    """


if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)
\end{python}

\newpage

\begin{python}
# Mastery test 2 - part 3

# write whole function definition for make_numberlist
    """
    Return a list of the numbers from first to last exclusive with an
    optional step.
    >>> make_numberlist(3,9,2)
    [3, 5, 7]
    >>> make_numberlist(-3,2)
    [-3, -2, -1, 0, 1]
    >>> make_numberlist(5,1,-1)
    [5, 4, 3, 2]
    """

def reverse_section(alist, start, end):
    """
    Reverse the order of the items in alist between start and end inclusive
    >>> reverse_section([1,2,3,4,5,6,7], 1, 3)
    [1, 4, 3, 2, 5, 6, 7]
    >>> reverse_section(['bob','sue','mary','jim','lucy'], 3,4)
    ['bob', 'sue', 'mary', 'lucy', 'jim']
    """

def print_function_table():
    """
    Prints a table of values for i to the power of 1,2,5 and 10 from 1 to 10
    left aligned in columns of width 4,5,8 and 13
    >>> print_function_table()
    i   i**2 i**5    i**10        
    1   1    1       1            
    2   4    32      1024         
    3   9    243     59049        
    4   16   1024    1048576      
    5   25   3125    9765625      
    6   36   7776    60466176     
    7   49   16807   282475249    
    8   64   32768   1073741824   
    9   81   59049   3486784401   
    10  100  100000  10000000000  
    """

def count_words_in_file(filename):
    """
    Return the number of whitespace separated words in the given file
    >>> count_words_in_file('mary.txt')
    39
    """

def sum_numbers_in_file(filename):
    """
    Return the sum of the numbers in the given file (which only contains
    integers separated by whitespace).
    >>> sum_numbers_in_file('numbers.txt')
    19138
    """

if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)

\end{python}

\vspace{0.5cm}

\begin{python}
# Mastery test 2 - part 4


#   Create a program like the one in section 4 of lecture 13.


# NOTE:  When you want to run this program follow these three steps
# 1. Save the program from within IDLE
# 2. Double click on the source file (test_2_4.py) in a finder window
# 3. Double click on the application file (test_2_4) that is created
#
#
# The finished program should look like this:
# 
#  ---------------------------------------------------------------
# |                   ---------------------    -------   ------   |
# | Enter your name: |                     |  | Hello | | QUIT |  |
# |                   ---------------------    -------   ------   |
#  ---------------------------------------------------------------
# 
# When someone types their name (e.g. Brendan) into the text field and
# presses the Hello button then another window should pop up saying
# 'Hi there Brendan' like this:
#
#  ---------------------------------------
# |                              ------   |
# |   Hi there Brendan          |  OK  |  |
# |                              ------   |
#  ---------------------------------------
#
# The program should exit if the QUIT button is pressed.
#

from Tkinter import *
from tkMessageBox import *

\end{python} \label{test2-end}
