# Python Notes - Lists (arrays)

In many programming languages, this topic uses arrays. However, Python provides sequences, which include strings and lists. They are similar to arrays, but more powerful. We can create a list by:
```
a = ["Mike", "Maggie", "Matt", "Ellie"]
b = []
c = [22, 33, 44]
city = "Sheffield"
```
a is a list of 4 elements, each a string, with the 'index' numbers: 0, 1, 2, 3
b is a list with 0 elements at the moment.
c is a list of numbers.

city is a string, NOT a list. The functions below (such as insert, append, pop...) do not work with strings, but len and in work. We can also access individual characters of strings, but cannot change them:
```n= city         # n is "h"
city = "s"      # not allowed !!

```

Back to lists. Here are some operations:

```
print a            #print all elements in a
print a         #print an element  ("Matt"  here )
c= 11           # assign a new value to an item of a list (it was 33)
if 33 in b:        #the 'in' operator (True   - 33 is in the list b)
print "there!"

n = len(a)         # returns length  (4 here)
b.append(55)       # add an element to the end of a list
a.insert(1, "Sue") # inserts an item at specified position - shifts the rest to make room
n = max(c)         # returns biggest item in list  ( 44 here )
n = min(c)         # returns smallest value
del a           # delete an item (the others are moved to fill the gap)
c.count(44)        # counts how many times 44 occurs in list c
c.index(33)        # finds the (first) position of 33 in c
# Warning: index gives a run-time error if the item is not there, so
# if this might happen, use 'in' before calling index

c.sort()           # sort items into ascending order
n=c.pop(1)         # returns and deletes the specified item at position 1
n = c.pop()        # returns and deletes last item
c.remove(33)       # searches for and removes the specified item
c.insert(1, 99)    # inserts an item (e.g. 99) at position (e.g. 1).  Items are shifted up
#       to make room

```

## A note on 'for' loops

Here is a while loop which prints 1 to 10 inclusive:

```count = 1
while count <= 10:
print count
count = count + 1
#end while

```
We can get the same effect in a simpler way with for:
```
for count in range(1, 11):   # NB 1 beyond the final value
print count
#end for

```
There is a special for to access lists:
```
for person in a:                        # a is a list
print "Name is ", person

```
Each time round the loop, person takes the value of each element in turn. We do not have to state how many times the loop is to be repeated.

## Problem: input validation

Here, we test if each character in a string is a digit. This can be used to reject non-numeric items. We look at each element in turn, and see it it is 'in' the string "0123456789":
```
inString = raw_input("Type a number: ")  #nb - use raw_input
for ch in inString:
if not (ch in "0123456789"):
error=True

```
Because this task is common, we can package it up as a function. here is a program which inputs two valid integers:
```
#nonNumeric demo
def nonNumeric(inString):
error=False
if inString=="":
return True   # error if no chars
else:
for ch in inString:
if not (ch in "0123456789"):
error=True
return error
#end def

def inputValidInteger(prompt):
print "Invalid number: re-enter!!"
#end while
return int(reply)   #convert string to int
#end def

n1 = inputValidInteger("Type first: ")
n2 = inputValidInteger("Type second: ")
print "You entered:  ", n1, n2

```

## Exam Marks

Problem: process a series of names and associated marks, as in:
```
Smith   47
Wilson  55
Davis   91

etc...
```
We wish to find the average mark, and which students got the average or higher. Note that we must do 2 passes over the data - first to find the average, then again to compare each mark to the average.

We use a while to accept the data - the user enters a blank name as the final one. Then we calculate the average with a for...in loop.
Finally, we scan through the marks again, this time with a for..range loop. This is needed because we need to use the index number of a mark to access the name list.
Note that the functions use lists as parameters. You may recall that when we pass single items as parameters, as in:

```
def func(a):
a = 3

```
the change to a only happens locally: the change is not reflected in the parameter. (If we wanted to send back a changed value, we could use a global, or (better) use return.

However, with lists:

```
def func(someList):
someList = 42
```
Here, the change IS reflected in the parameter. We use this in the marks example, passing 2 lists to a function which gets a mark and a name from the user, and appends them to the lists

Here is the code:

```#exam marks
#inputs a series of marks, names
# calc average, display those marks/names above average

nameList = []
markList = []

def main():
getNamesAndMarks(nameList, markList)
average = getAverage(markList)
print "Above average of ", average, ":"
for studentNum in range(0, len(markList)):
if markList[studentNum] >= average:
print markList[studentNum], nameList[studentNum]
#end if
#end for

def getNamesAndMarks (names, marks):
name = raw_input("Type name ( Hit enter to end): ")
while name != "":
names.append(name)
mark = input("Type mark for " + name + ": ")
marks.append(mark)
name = raw_input("Type name ( Hit enter to end): ")
#end while
#end def

def getAverage(marks):
sum = 0
for mark in marks:
sum = sum + mark
return sum / float(len(marks))
#end def

main()

```
Though there probably are neater ways to do this in Python, the approach of using a for...range loop to index through an array is very common in other languages.
Problem: add input validation to the exam marks program (use the inputValidInteger function).
Note: many modern programming languages( Java, VB, C# etc) have a class such as 'arrayList', which provides similar facilities to a Python list. But they are an add-on, not part of the language.

## Lists and references

Lists can be massive - shifting them around in memory can be relatively slow. In most languages, such structures are manipulated via a 'reference' - a position in memory rather than the data itself. here is an example:
```
a= [6, 12, 18]   # set up a list
aCopy = a        # assign it to aCopy
a = 30        # modify contents of a
print a          # [30, 12, 18]
print aCopy      # [30, 12, 18]    NB**** NOT [6, 12, 18]  !!
if a == aCopy:   # always true (they both refer to 6000)
```
The 3 items in the list 'a' are held at a position in memory (RAM). We don't need to know the position, but for the sake of argument, assume it is at 6000.

When we refer to a, python goes to position 6000, where it finds the 3 items.

When we assign a to aCopy, the contents of the list (3 of them) are not copied! Instead, the reference to the list (6000 here) is copied. This is fast for the computer to do.

So, there is only ONE copy of 6, 12, 18 in memory, and both a and aCopy refer to it. When an item is modified (we set a to 30 ) this affects both a and aCopy.

Note that strings, tuples and single items such as numbers do not work by reference - they work 'by value', as in

```
a = 3   # not a list
aCopy = a
a = 4
print a, aCopy    # 4 ,  3     ( aCopy stays at 3 )

# and a list example...
stuff = [20, 30]
stuffCopy = [stuff, stuff ]  # copy the contained values, not the reference
stuff = 40
print stuffCopy                    # [20, 30]
```