sttp/basic_python/list_tuples.rst
changeset 0 27e1f5bd2774
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sttp/basic_python/list_tuples.rst	Tue Mar 02 18:43:02 2010 +0530
@@ -0,0 +1,526 @@
+Lists and Tuples
+================
+
+Lists
+-----
+
+Python provides an intuitive way to represent a group items, called *Lists*. The
+items of a *List* are called its elements. Unlike C/C++, elements can be of any
+type. A *List* is represented as a list of comma-sepated elements with square
+brackets around them::
+
+  >>> a = [10, 'Python programming', 20.3523, 23, 3534534L]
+  >>> a
+  [10, 'Python programming', 20.3523, 23, 3534534L]
+
+
+Common List Operations
+~~~~~~~~~~~~~~~~~~~~~~
+
+The following are some of the most commonly used operations on *Lists*.
+
+
+~~~~~~~~
+Indexing
+~~~~~~~~
+
+Individual elements of a *List* can be accessed using an index to the element.
+The indices start at 0. One can also access the elements of the *List* in reverse
+using negative indices.::
+
+  >>> a[1]
+  'Python programming'
+  >>> a[-1]
+  3534534L
+
+It is important to note here that the last element of the *List* has an index of
+-1.
+
+~~~~~~~~~~~~~
+Concatenating
+~~~~~~~~~~~~~
+
+Two or more *Lists* can be concatenated using the + operator::
+
+  >>> a + ['foo', 12, 23.3432, 54]
+  [10, 'Python programming', 20.3523, 'foo', 12, 23.3432, 54]
+  >>> [54, 75, 23] + ['write', 67, 'read']
+  [54, 75, 23, 'write', 67, 'read']
+  
+
+~~~~~~~
+Slicing
+~~~~~~~
+
+A *List* can be sliced off to contain a subset of elements of the *List*. Slicing
+can be done by using two indices separated by a colon, where the first index is
+inclusive and the second index is exclusive. The resulting slice is also a *List*.::
+
+  >>> num = [1, 2, 3, 4, 5, 6, 7, 8, 9]
+  >>> num[3:6]
+  [4, 5, 6]
+  >>> num[0:1]
+  [1]
+  >>> num[7:10]
+  [7, 8, 9]
+
+The last example showed how to access last 3 elements of the *List*. There is a 
+small catch here. The second index 10 actually refers to the 11th element of the
+*List* which is still valid, even though it doesn't exist because the second 
+index is exclusive and tells the Python interpreter to get the last element of
+the *List*. But this can also be done in a much easier way using negative indices::
+
+  >>> num[-3:-1]
+  [7, 8, 9]
+
+Excluding the first index implies that the slice must start at the beginning of 
+the *List*, while excluding the second index includes all the elements till the
+end of the *List*. A third parameter to a slice, which is implicitly taken as 1
+is the step of the slice. It is specified as a value which follows a colon after
+the second index::
+
+  >>> num[:4]
+  [1, 2, 3, 4]
+  >>> num[7:]
+  [8, 9]
+  >>> num[-3:]
+  [7, 8, 9]
+  >>> num[:]
+  [1, 2, 3, 4, 5, 6, 7, 8, 9]
+  >>> num[4:9:3]
+  [5, 8]
+  >>> num[3::2]
+  [4, 6, 8]
+  >>> num[::4]
+  [1, 5, 9]
+
+
+~~~~~~~~~~~~~~
+Multiplication
+~~~~~~~~~~~~~~
+
+
+A *List* can be multiplied with an integer to repeat itself::
+
+  >>> [20] * 5
+  [20, 20, 20, 20, 20]
+  >>> [42, 'Python', 54] * 3
+  [42, 'Python', 54, 42, 'Python', 54, 42, 'Python', 54]
+
+
+~~~~~~~~~~
+Membership
+~~~~~~~~~~
+
+**in** operator is used to find whether an element is part of the *List*. It
+returns **True** if the element is present in the *List* or **False** if it is not 
+present. Since this operator returns a Boolean value it is called a Boolean
+operator::
+
+  >>> names = ['Guido', 'Alex', 'Tim']
+  >>> 'Tim' in names
+  True
+  >>> 'Adam' in names
+  False
+
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Length, Maximum and Minimum
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Length of a *List* can be found out using the len function. The max function
+returns the element with the largest value and the min function returns the 
+element with the smallest value::
+
+  >>> num = [4, 1, 32, 12, 67, 34, 65]
+  >>> len(num)
+  7
+  >>> max(num)
+  67
+  >>> min(num)
+  1
+
+
+~~~~~~~~~~~~~~~~~
+Changing Elements
+~~~~~~~~~~~~~~~~~
+
+Unlike Strings *Lists* are mutable, i.e. elements of a *List* can be manipulated::
+
+  >>> a = [1, 3, 5, 7]
+  >>> a[2] = 9
+  >>> a
+  [1, 3, 9, 7]
+
+
+~~~~~~~~~~~~~~~~~
+Deleting Elements
+~~~~~~~~~~~~~~~~~
+
+An element or a slice of a *List* can be deleted by using the **del** statement::
+
+  >>> a = [1, 3, 5, 7, 9, 11]
+  >>> del a[-2:]
+  >>> a
+  [1, 3, 5, 7]
+  >>> del a[1]
+  >>> a
+  [1, 5, 7]
+
+
+~~~~~~~~~~~~~~~~
+Assign to Slices
+~~~~~~~~~~~~~~~~
+
+In the same way, values can be assigned to individual elements of the *List*, 
+a *List* of elements can be assigned to a slice::
+
+  >>> a = [2, 3, 4, 5]
+  >>> a[:2] = [0, 1]
+  [0, 1, 4, 5]
+  >>> a[2:2] = [2, 3]
+  >>> a
+  [0, 1, 2, 3, 4, 5]
+  >>> a[2:4] = []
+  >>> a
+  [0, 1, 4, 5]
+
+The last two examples should be particularly noted carefully. The last but one
+example insert elements or a list of elements into a *List* and the last example
+deletes a list of elements from the *List*.
+
+
+None, Empty Lists, and Initialization
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+An *Empty List* is a *List* with no elements and is simply represented as
+[]. A *None List* is one with all elements in it being **None**. It serves
+the purpose having a container list of some fixed number of elements with
+no value::
+
+  >>> a = []
+  >>> a
+  []
+  >>> n = [None] * 10
+  >>> n
+  [None, None, None, None, None, None, None, None, None, None]
+
+
+Nested Lists
+~~~~~~~~~~~~
+
+As mentioned earlier, a List can contain elements of any data type. This also
+implies a *List* can have a *Lists* themselves as its elements. These are 
+called as *Nested Lists*. There is no limit on the depth of the *Nested Lists*::
+
+  >>> a = [1, [1, 2, 3], 3, [1, [1, 2, 3]], 7]
+
+
+List Methods
+~~~~~~~~~~~~
+
+A method is a function that is coupled to an object. More about objects
+and its methods are discussed in Advanced Python module. In general, a
+method is called like::
+
+  object.method(arguments)
+
+For now, it is enough to know that a list of elements is an object and
+so *List* methods can be called upon them. Also some of the methods change
+the *List* in-place, meaning it modifies the existing list instead of creating
+a new one, while other methods don't. It must be noted as we run through
+the *List* methods.
+
+Some of the most commonly used *List* methods are as follows:
+
+
+~~~~~~
+append
+~~~~~~
+
+The *append* method is used to append an object at the end of the list::
+
+  >>> prime = [2, 3, 5]
+  >>> prime.append(7)
+  >>> prime
+  [2, 3, 5, 7]
+
+It is important to note that append changes the *List* in-place.
+
+
+~~~~~
+count
+~~~~~
+
+The *count* method returns the number of occurences of a particular element
+in a list::
+
+  >>> [1, 4, 4, 9, 9, 9].count(9)
+  3
+  >>> tlst = ['Python', 'is', 'a', 'beautiful', 'language']
+  >>> tlst.count('Python')
+  1
+
+
+~~~~~~
+extend
+~~~~~~
+
+The *extend* method extends the list on which it is called by the list supplied
+as argument to it::
+
+  >>> a = [1, 2, 3]
+  >>> b = [4, 5, 6]
+  >>> a.extend(b)
+  [1, 2, 3, 4, 5, 6]
+
+This is an in-place method. This method is equivalent to using the + operator, but
+using the + operator returns a new list.
+
+
+~~~~~
+index
+~~~~~
+
+The *index* method returns the index position of the element in the list 
+specified as argument::
+
+  >>> a = [1, 2, 3, ,4, 5]
+  >>> a.index(4)
+  3
+
+
+~~~~~~
+insert
+~~~~~~
+
+The *insert* method is used to insert an element specified as the second 
+argument to the list at the position specified by the first argument::
+
+  >>> a = ['Python', 'is', 'cool']
+  >>> a.insert(2, 'so')
+  >>> a
+  ['Python', 'is', 'so', 'cool']
+
+The *insert* method changes the *List* in-place.
+
+
+~~~
+pop
+~~~
+
+The *pop* method removes an element from the list. The index position
+of the element to be removed can be specified as an argument to the
+*pop* method, if not it removes the last element by default::
+
+  >>> a = [1, 2, 3, 4, 5]
+  >>> a.pop()
+  >>> a
+  5
+  >>> a.pop(2)
+  >>> a
+  3
+
+The *pop* method changes the *List* in-place.
+
+
+~~~~~~
+remove
+~~~~~~
+
+The *remove* method removes the first occurence of an element supplied as a
+parameter::
+
+  >>> a = [1, 2, 3, 4, 2, 5, 2]
+  >>> a.remove(2)
+  >>> a
+  [1, 3, 4, 2, 5, 2]
+
+
+~~~~~~~
+reverse
+~~~~~~~
+
+The *reverse* method reverses elements in the list. It is important to note
+here that *reverse* method changes the list in-place and doesn't return any
+thing::
+
+  >>> a = ['guido', 'alex', 'tim']
+  >>> a.reverse()
+  >>> a
+  ['tim', 'alex', 'guido']
+
+
+~~~~
+sort
+~~~~
+
+The *sort* method is used to sort the elements of the list. The *sort* method
+also sorts in-place and does not return anything::
+
+  >>> a = [5, 1, 3, 7, 4]
+  >>> a.sort()
+  >>> a
+  [1, 3, 4, 5, 7]
+
+In addition to the sort method on a *List* object we can also use the built-in
+**sorted** function. This function takes the *List* as a parameter and returns
+a sorted copy of the list. However the original list is left intact::
+
+  >>> a = [5, 1, 3, 7, 4]
+  >>> b = sorted(a)
+  >>> b
+  [1, 3, 4, 5, 7]
+  >>> a
+  [5, 1, 3, 7, 4]
+
+
+Tuples
+------
+
+*Tuples* are sequences just like *Lists*, but they are immutable. In other
+words *Tuples* provides a way to represent a group of items, where the group
+of items cannot be changed in any way. The syntax of a *Tuple* is also very
+similar to *List*. A *Tuple* is represented with the list of items, called
+elements of the *Tuple* separated by comma, with the entire list being enclosed
+in parenthesis. It is not compulsory to use parenthesis around a *Tuple* but
+it may be necessary in some of the cases::
+
+  >>> a = 1, 2, 3
+  >>> a
+  (1, 2, 3)
+  >>> b = 1,
+  >>> b
+  (1,)
+
+It is interesting to note the second example. Just a value followed by a comma
+automatically makes that an element of a *Tuple* with only one element. It is
+also important to note that, irrespective of input having a parenthesis, the
+output always has a parenthesis.
+
+The first example is also known as *Tuple packing*, because values are being
+packed into a tuple. It is also possible to do *Tuple unpacking* which is more
+interesting. It is better to understand that by example. Say we have a 
+co-ordinate pair from which we need to separate x and y co-ordinates::
+
+  >>> a = (1, 2)
+  >>> x, y = a
+  >>> x
+  1
+  >>> y
+  2
+
+*Tuple unpacking* also has several other use-cases of which the most interesting
+one is to swap the values of two variables. Using programming languages like C
+would require anywhere around 10 lines of code and an extra temporary variable
+to do this (including all the #include stuff). Python does it in the most
+intuitive way in just one line. Say we want to swap the co-ordinates in the
+above example::
+
+  >>> x, y = y, x
+  >>> x
+  2
+  >>> y
+  1
+
+Common Tuple Operations
+~~~~~~~~~~~~~~~~~~~~~~~
+
+There is no need to introduce all the *Tuple* operations again, since *Tuples*
+support the following operations that *List* supports in exactly the same way:
+
+  * Indexing
+  * Concatenating
+  * Slicing
+  * Membership
+  * Multiplication
+  * Length, Maximum, Minimum
+
+The following examples illustrate the above operations::
+
+  >>> a = (1, 2, 3, 4, 5, 6)
+  >>> a[5]
+  6
+  >>> b = (7, 8, 9)
+  >>> a + b
+  (1, 2, 3, 4, 5, 6, 7, 8, 9)
+  >>> a[3:5]
+  (4, 5)
+  >>> 5 in a
+  True
+  >>> c = (1,)
+  >>> c * 5
+  (1, 1, 1, 1, 1)
+  >>> len(a)
+  6
+  >>> max(a)
+  6
+  >>> min(a)
+  1
+
+However the following *List* operations are not supported by *Tuples* because
+*Tuples* cannot be changed once they are created:
+
+  * Changing elements
+  * Deleting elements
+  * Assigning to slices
+
+Similarity to *Lists* leads to the questions like, why not *Lists* only? Why do
+we even want *Tuples*? Can we do the same with *Lists*? And the answer is **Yes**
+we can do it, but *Tuples* are helpful at times, like we can return Tuples from
+functions. They are also returned by some built-in functions and methods. And
+also there are some use cases like co-ordinate among other things. So *Tuples*
+are helpful.
+
+Additional Syntax
+-----------------
+
+The following additional syntax are introduced to make it easier to operate on
+*Lists*.
+
+range()
+~~~~~~~
+
+The *range* function takes at least one argument and 2 additional optional
+arguments. If two or more arguments are specified, the range function returns
+a list of natural numbers starting from the first argument passed to it to the
+second argument. The third argument, if specified is used as a step. Suppose
+only one argument is specified, then *range* function returns a list of natural
+numbers starting from 0 upto the argument specified::
+
+  >>> range(5, 10, 2)
+  [5, 7, 9]
+  >>> range(2, 15)
+  [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
+  >>> range(12)
+  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
+
+for
+~~~
+
+The **for** keyword is used as a part of the looping construct. Unlike for loops
+in other languages, Python's for is used to iterate through the elements of 
+sequences like *Lists*, *Tuples*, *Dictionaries*, etc. The syntax of the for loop
+consists of **for**, followed by a variable to hold the individual or the current
+element of the list during iteration and **in**, followed by the sequence and a
+semicolon(':') The next line which is part of the **for** loop, i.e the statements
+that are part of the loop should start with a new intend::
+
+  >>> names = ['Guido', 'Alex', 'Tim']
+  >>> for name in names:
+  ...   print "Name =", name
+  ... 
+  Name = Guido
+  Name = Alex
+  Name = Tim
+
+
+Conclusion
+----------
+
+This section on *Lists* and *Tuples* introduces almost all the necessary 
+machinary required to work on *Lists* and *Tuples*. Topics like how to
+use these data structures in bigger more useful programs will be introduced
+in the subsequent chapters.
+
+