\chapter{Strings part 1}
\label{lec-strings}
\section{A compound data type}
So far we have seen a number of types, including: \pythoninline{int},
\pythoninline{float}, \pythoninline{bool}, \pythoninline{NoneType} and
\pythoninline{str}. Strings are qualitatively different from the first
four because they are made up of smaller pieces---characters.  Types
that comprise smaller pieces are called \myidx{compound~data~types}.
Depending on what we are doing, we may want to treat a compound data
type as a single thing, or we may want to access its parts. This
ambiguity is useful.

The bracket operator selects a single character from a string:

\begin{minipage}{\textwidth}
\begin{pythonInteractive}
>>> fruit = "banana"
>>> letter = fruit[1]
>>> print letter
\end{pythonInteractive}
\end{minipage}
The expression \pythoninline{fruit[1]} selects character number 1 from
\pythoninline{fruit}. The variable \pythoninline{letter} refers to the
result. When 
we display \pythoninline{letter}, we get a surprise: 
\begin{pythonOutput}
a
\end{pythonOutput}

The first letter of ``banana'' is not \pythoninline{a}, unless you are
a computer 
scientist. For perverse reasons, computer scientists always start
counting from zero. The 0\textsuperscript{th} letter of ``banana'' is
\pythoninline{b}. The first letter is \pythoninline{a}, and
the second letter is \pythoninline{n}. 
If you want the 0\textsuperscript{th} letter of a string, you just put 0, or any
expression with the value 0, in the brackets:

\begin{minipage}{\textwidth}
\begin{pythonInteractive}
>>> letter = fruit[0]
>>> print letter
b
\end{pythonInteractive}
\end{minipage}
The expression in brackets is called an \myidx{index}. An index
specifies a member of an ordered set, in this case the set of
characters in the string. The index \emph{indicates} which one you
want, hence the name. It can be any integer expression.

\section{Length}
The \pythoninline{len} function returns the number of characters in a string:

\begin{minipage}{\textwidth}
\begin{pythonInteractive}
>>> fruit = "banana"
>>> len(fruit)
6
\end{pythonInteractive}
\end{minipage}
To get the last letter of a string, you might be tempted to try
something like this:

\begin{minipage}{\textwidth}
\begin{python}
length = len(fruit)
last = fruit[length]       # ERROR!
\end{python}
\end{minipage}
That won't work. It causes the runtime error 
\pythoninline{IndexError:~string~index~out~of~range}. The reason is
that there is no 6th letter in 
``banana''. Since we started counting at zero, the six letters are
numbered 0 to 5. To get the last character, we have to subtract 1
from length:

\begin{minipage}{\textwidth}
\begin{python}
length = len(fruit)
last = fruit[length-1]
\end{python}
\end{minipage}
Alternatively, we can use negative indices, which count backward from
the end of the string. The expression fruit[-1] yields the last
letter, fruit[-2] yields the second to last, and so on.

\section{Traversal and the \pythoninline{for} loop}
A lot of computations involve processing a string one character at a
time. Often they start at the beginning, select each character in
turn, do something to it, and continue until the end. This pattern of
processing is called a \myidx{traversal}. One way to encode a
traversal is with a while statement:

\begin{minipage}{\textwidth}
\begin{python}
index = 0
while index < len(fruit):
    letter = fruit[index]
    print letter
    index += 1
\end{python}
\end{minipage}
This loop traverses the string and displays each letter on a line by
itself. The loop condition is \pythoninline{index~$<$~len(fruit)}, so
when \pythoninline{index} is equal to the length of the string, the
condition is false, and the body of the loop is not executed. The last
character accessed is the one with the index
\pythoninline{len(fruit)-1}, which is the last character in the
string.
Using an index to traverse a set of values is so common that Python
provides an alternative, simpler syntax---the \pythoninline{for} loop:

\begin{minipage}{\textwidth}
\begin{python}
for char in fruit:
    print char
\end{python}
\end{minipage}
Each time through the loop, the next character in the string is
assigned to the variable \pythoninline{char}. The loop continues until
no characters are left.

The following example shows how to use concatenation and a
\pythoninline{for} loop 
to generate an abecedarian series. Abecedarian refers to a series or
list in which the elements appear in alphabetical order. For example,
in Robert McCloskey's book \emph{Make Way for Ducklings}, the names
of the ducklings are Jack, Kack, Lack, Mack, Nack, Ouack, Pack, and
Quack. This loop outputs these names in order:

\begin{minipage}{\textwidth}
\begin{python}
prefixes = "JKLMNOPQ"
suffix = "ack"
for letter in prefixes:
    print letter + suffix
\end{python}
\end{minipage}
The output of this program is:

\begin{minipage}{\textwidth}
\begin{pythonOutput}
Jack
Kack
Lack
Mack
Nack
Oack
Pack
Qack
\end{pythonOutput}
\end{minipage}
Of course, that's not quite right because Ouack and Quack are
misspelled. You'll fix this as an exercise below.

\section{String slices}
A substring of a string is called a \myidx{slice}. Selecting a
slice is similar to selecting a character:

\begin{minipage}{\textwidth}
\begin{pythonInteractive}
>>> s = "Peter, Paul, and Mary"
>>> print s[0:5]
Peter
>>> print s[7:11]
Paul
>>> print s[17:21]
Mary
\end{pythonInteractive}
\end{minipage}
The operator \pythoninline{[n:m]} returns the part of the string from
the n\textsuperscript{th}
character to the m\textsuperscript{th} character, including the first but excluding
the last. This behaviour is counterintuitive; it makes more sense if
you imagine the indices pointing \emph{between} the characters, as in
the following diagram:

\includegraphics[scale=1]{figures/banana-fig}

If you omit the first index (before the colon), the slice starts at
the beginning of the string. If you omit the second index, the slice
goes to the end of the string. Thus:

\begin{minipage}{\textwidth}
\begin{pythonInteractive}
>>> fruit = "banana"
>>> fruit[:3]
'ban'
>>> fruit[3:]
'ana'
\end{pythonInteractive}
\end{minipage}
What do you think \pythoninline{s[:]} means?

\newpage
\section{String comparison}
The comparison operators work on strings. To see if two strings are
equal:

\begin{minipage}{\textwidth}
\begin{python}
if word == "banana":
    print  "Yes, we have no bananas!"
\end{python}
\end{minipage}
Other comparison operations are useful for putting words in
alphabetical order:

\begin{minipage}{\textwidth}
\begin{python}
if word < "banana":
    print "Your word," + word + ", comes before banana."
elif word > "banana":
    print "Your word," + word + ", comes after banana."
else:
    print "Yes, we have no bananas!"
\end{python}
\end{minipage}
You should be aware, though, that Python does not handle upper- and
lowercase letters the same way that people do. All the uppercase
letters come before all the lowercase letters. As a result:

\begin{minipage}{\textwidth}
\begin{pythonOutput}
Your word, Zebra, comes before banana.
\end{pythonOutput}
\end{minipage}
A common way to address this problem is to convert strings to a
standard format, such as all lowercase, before performing the
comparison. A more difficult problem is making the program realize
that zebras are not fruit.

\section{Strings are immutable}
It is tempting to use the [] operator on the left side of an
assignment, with the intention of changing a character in a
string. For example:

\begin{minipage}{\textwidth}
\begin{python}
greeting = "Hello, world!"
greeting[0] = 'J'            # ERROR!
print greeting
\end{python}
\end{minipage}
Instead of producing the output \pythoninline{Jello, world!}, this
code produces the 
runtime error \pythoninline{TypeError: 'str' object doesn't support item
assignment}.
Strings are \myidx{immutable}, which means you can't change an
existing string. The best you can do is create a new string that is a
variation on the original:

\begin{minipage}{\textwidth}
\begin{python}
greeting = "Hello, world!"
newGreeting = 'J' + greeting[1:]
print newGreeting
\end{python}
\end{minipage}
The solution here is to concatenate a new first letter onto a slice
of greeting. This operation has no effect on the original string.

\section{The \pythoninline{in} operator}
The \pythoninline{in} operator tests if one string is a substring of another:

\begin{minipage}{\textwidth}
\begin{pythonInteractive}
>>> 'p' in 'apple'
True
>>> 'i' in 'apple'
False
>>> 'ap' in 'apple'
True
>>> 'pa' in 'apple'
False
\end{pythonInteractive}
\end{minipage}
Note that a string is a substring of itself:

\begin{minipage}{\textwidth}
\begin{pythonInteractive}
>>> 'a' in 'a'
True
>>> 'apple' in 'apple'
True
\end{pythonInteractive}
\end{minipage}
Combining the \pythoninline{in} operator with string concatenation
using \pythoninline{+}, we can write a function that removes all the
vowels from a string: 

\begin{minipage}{\textwidth}
\begin{python}
def remove_vowels(s):
    vowels = "aeiouAEIOU"
    s_without_vowels = ""
    for letter in s:
        if letter not in vowels:
            s_without_vowels += letter
    return s_without_vowels
\end{python}
\end{minipage}
Test this function to confirm that it does what we wanted it to do.

\section{A find function}
What does the following function do?

\begin{minipage}{\textwidth}
\begin{python}
def find(strng, ch):
    index = 0
    while index < len(strng):
        if strng[index] == ch:
            return index
        index += 1
    return -1
\end{python}
\end{minipage}
In a sense, \pythoninline{find} is the opposite of the
\pythoninline{[]} operator. Instead of 
taking an index and extracting the corresponding character, it takes
a character and finds the index where that character appears. If the
character is not found, the function returns -1.
This is the first example we have seen of a \pythoninline{return}
statement inside a 
loop. If \pythoninline{strng[index]~==~ch}, the function returns immediately,
breaking out of the loop prematurely.
If the character doesn't appear in the string, then the program exits
the loop normally and returns \pythoninline{-1}.
This pattern of computation is sometimes called a eureka traversal
because as soon as we find what we are looking for, we can cry
Eureka! and stop looking.

\section{Looping and counting}
The following program counts the number of times the letter
\pythoninline{a} appears 
in a string, and is another example of the counter pattern introduced
in Lecture 10: % \ref{lecture-iteration}:

\begin{minipage}{\textwidth}
\begin{python}
fruit = "banana"
count = 0
for char in fruit:
    if char == 'a':
        count += 1
print count
\end{python}
\end{minipage}
\section{Optional parameters}
\label{sec-optional-parameters}
To find the locations of the second or third occurrence of a character
in a string, we can modify the \pythoninline{find} function, adding a third
parameter for the starting position in the search string:

\begin{minipage}{\textwidth}
\begin{python}
def find2(strng, ch, start):
    index = start 
    while index < len(strng):
        if strng[index] == ch:
            return index
        index += 1
    return -1
\end{python}
\end{minipage}
The call \pythoninline{find2('banana', 'a', 2)} now returns
\pythoninline{3}, the 
index of the second 'a' in 'banana'.
Better still, we can combine \pythoninline{find} and
\pythoninline{find2} using an 
\myidx{optional~parameter}: 

\begin{minipage}{\textwidth}
\begin{python}
def find(strng, ch, start=0):
    index = start 
    while index < len(strng):
        if strng[index] == ch:
            return index
        index += 1
    return -1
\end{python}
\end{minipage}
The call \pythoninline{find('banana', 'a', 2)} to this version of find
behaves just like \pythoninline{find2}, while in the call
\pythoninline{find('banana',~'a')}, \pythoninline{start} will be set to the
\myidx{default~value} of \pythoninline{0}. 
Adding another optional parameter to find makes it search both
forward and backward:

\begin{minipage}{\textwidth}
\begin{python}
def find(strng, ch, start=0, step=1):
    index = start 
    while 0 <= index < len(strng):
        if strng[index] == ch:
            return index
        index += step 
    return -1
\end{python}
\end{minipage}
Passing in a value of \pythoninline{-1} for step will make it search
toward the 
beginning of the string instead of the end. Note that we needed to
check for a lower bound for \pythoninline{index} in the while loop as
well as an upper bound to accommodate this change.

\section{Glossary}
\begin{description}
\item[compound data type:]A data type in which the values are made up
 of components, or elements, that are themselves values.
\item[index:]A variable or value used to select a member of an
  ordered set, such as a character from a string.
\item[traverse:]To iterate through the elements of a set, performing
   a similar operation on each.
\item[slice:]A part of a string (substring) specified by a range of
   indices. More generally, a subsequence of any sequence type in
   Python can be created using the slice operator (\emph{sequence}
   [\emph{start}:\emph{stop}]).
\item[immutable:]A compound data types whose elements can not be
   assigned new values.
\item[optional parameter:]A parameter written in a function header
   with an assignment to a default value which it will receive if no
   corresponding argument is given for it in the function call.
\item[default value:]The value given to an optional parameter if no
   argument for it is provided in the function call.

 \end{description}

\newpage
\section{Laboratory exercises}
\begin{enumerate}
\item Modify:

\begin{minipage}{\textwidth}
\begin{python}
prefixes = "JKLMNOPQ"
suffix = "ack"
for letter in prefixes:
    print letter + suffix
\end{python}
\end{minipage}
so that Ouack and Quack are spelled correctly.
\item Encapsulate

\begin{minipage}{\textwidth}
\begin{python}
fruit = "banana"
count = 0
for char in fruit:
    if char == 'a':
        count += 1
print count
\end{python}
\end{minipage}
in a function named \pythoninline{count\_letters}, and generalize it so that it
accepts the string and the letter as arguments.

\item Now rewrite the \pythoninline{count\_letters} function so that instead of
traversing the string, it repeatedly calls \pythoninline{find} (the
version from 
section \ref{sec-optional-parameters}), with the optional third
parameter to locate new occurrences of the letter being counted.

\item Create a file named \pythoninline{stringtools.py} and put the
following in it: 

\begin{minipage}{\textwidth}
\begin{python}
def reverse(s):
    """
      >>> reverse('happy')
      'yppah'
      >>> reverse('Python')
      'nohtyP'
      >>> reverse("")
      ''
      >>> reverse("P")
      'P'
    """

if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)
\end{python}
\end{minipage}
Add a function body to reverse to make the doctests pass.

\item Add mirror to \pythoninline{stringtools.py}.

\begin{minipage}{\textwidth}
\begin{python}
def mirror(s):
    """
      >>> mirror("good")
      'gooddoog'
      >>> mirror("yes")
      'yessey'
      >>> mirror('Python')
      'PythonnohtyP'
      >>> mirror("")
      ''
      >>> mirror("a")
      'aa'
    """
\end{python}
\end{minipage}
Write a function body for it that will make it work as indicated by
the doctests.

\item Include \pythoninline{remove\_letter} in
\pythoninline{stringtools.py}. 

\begin{minipage}{\textwidth}
\begin{python}
def remove_letter(letter, strng):
    """
      >>> remove_letter('a', 'apple')
      'pple'
      >>> remove_letter('a', 'banana')
      'bnn'
      >>> remove_letter('z', 'banana')
      'banana'
      >>> remove_letter('i', 'Mississippi')
      'Msssspp'
    """
\end{python}
\end{minipage}
Write a function body for it that will make it work as indicated by
the doctests.

\item \emph{Extension Exercise:} Write a function
  \pythoninline{remove\_duplicates} which takes a string argument and returns a
  string which is the same as the argument except only the first
  occurrence of each letter is present.  Make your function case
  sensitive, and ensure that it passes the doctests below.


\begin{minipage}{\textwidth}
\begin{python}
def remove_duplicates(strng):
    """
    >>> 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'
    """
\end{python}
\end{minipage}
\end{enumerate}


