Showing posts with label lists. Show all posts
Showing posts with label lists. Show all posts

Friday, 28 September 2018

A simple database program

So we know how to get Python to run the most basic of SQLite commands - enough to maintain a table. As I discussed earlier, one of the big benefits of Python is that it can make SQLite commands more user friendly. Even if you don't use a GUI, Python can make command line interactions easier.
Building on the database I've been using in the previous posts, I've cooked up a Python program that makes using the historical figures database easier.

import sqlite3
conn = sqlite3.connect("C:\\sqlite\\db\\history1.db")
curs = conn.cursor()


def viewtable():
    curs.execute("SELECT * FROM historicalfigures ORDER BY Name;")
    fulldump = curs.fetchall()
    print("Name                  | Born  | Died  | Role                  | Nation")
    print("======================|=======|=======|=======================|=========")
    for line in fulldump:
        namestr = line[0]
        if len(namestr) >= 23:
            namestr = namestr[0:22]
        rolestr = line[3]
        if len(rolestr) >= 23:
            rolestr = rolestr[0:22]
        nationstr = line[4]
        if len(nationstr) >= 17:
            nationstr = nationstr[0:16]
        gapname = " " * (22 - len(namestr)) + "| "
        gapborn = "  | "
        gapdied = "  | "
        gaprole = " " * (22 - len(rolestr)) + "| "
        print(namestr + gapname + str(line[1]) + gapborn + str(line[2]) + gapdied + rolestr + gaprole + nationstr)
   

def searchtable():
    print("Searching table - please select which column ")
    print("Name ----(N)")
    print("Born ----(B)")
    print("Died ----(D)")
    print("Role ----(R)")
    print("Nation --(T)")
    fieldchoice = input("? ")
    fieldchoice = fieldchoice.lower()
    if fieldchoice == "n":
        term = input("What name? ")
        searchterm = "Name = '" + term + "'"
    elif fieldchoice == "b":
        compare = input("Do you want results before ( < ), in exact year ( = ) or after ( > )? ")
        term = input("Which Year? ")
        searchterm = "YearBirth "+ compare + " " + term
    elif fieldchoice == "d":
        field = "YearDeath"
        compare = input("Do you want results before ( < ), in exact year ( = ) or after ( > )? ")
        term = input("Which Year? ")   
        searchterm = "YearDeath "+ compare + " " + term
    elif fieldchoice == "r":
        term = input("What Role? ")
        searchterm = "Role LIKE '%"+ term +"%'"
    elif fieldchoice == "t":
        term = input("What Nation? ")
        searchterm = "Nation LIKE '%" + term + "%'"
    else:
        print("Sorry, option not recognised. ")
        return()
    print (searchterm)
    curs.execute("SELECT * FROM historicalfigures WHERE " + searchterm +";")
    searchdump = curs.fetchall()
    print("Name                   | Born  | Died  | Role                  | Nation")
    print("=======================|=======|=======|=======================|=========")
    for line in searchdump:
        gapname = " " * (23 - len(line[0])) + "|"
        gapborn = " " * 3 + "|"
        gapdied = " " * 3 + "|"
        gaprole = " " * (23 - len(line[3])) + "|"
        print(line[0] + gapname + str(line[1]) + gapborn + str(line[2]) + gapdied + line[3] + gaprole + line[4])
   
def enternew():
    print("Entering new record")
    newname = input("Please enter new name: ")
    newbirth = input("Please enter new year of birth: ")
    newdeath = input("Please enter new year of death: ")
    newrole = input("Please enter new role: ")
    newnation = input("Please enter new nation: ")
    keeper = input("Do you want to keep this new record (Y to keep, N to discard): ")
    keeper = keeper.lower()
    if keeper == "y":
        curs.execute("INSERT INTO historicalfigures (Name, YearBirth, YearDeath, Role, Nation) VALUES ('" + newname + "', "+ newbirth +", "+ newdeath +", '" + newrole + "', '" +newnation +"');")
        curs.execute("COMMIT;")
   

def deleterecord():
    curs.execute("SELECT Name FROM historicalfigures ORDER BY Name;")
    namedump = curs.fetchall()
    print("Current list of names is:")
    linecount = 0
    for line in namedump:
        print(linecount + " - " + line[0])
        linecount += 1
    whichline = input("Enter number of row to be deleted: ")
    whichline = int(whichline)
    delname = namedump[whichline][0]
    confirm = input("Please confirm (Y) or cancel (N) deleting record for " + delname +": ")
    confirm = confirm.lower()
    if confirm == 'y':
        curs.execute("DELETE FROM historicalfigures WHERE Name ='" + delname + "';")
        curs.execute("COMMIT;")
   

def updaterecord():
    curs.execute("SELECT Name FROM historicalfigures ORDER BY Name;")
    namedump = curs.fetchall()
    print("Current list of names is:")
    linecount = 0
    for line in namedump:
        print(str(linecount) + " - " + line[0])
        linecount += 1
    whichline = input("Enter number of row to be changed: ")
    whichline = int(whichline)
    updatename = namedump[whichline][0]
    curs.execute("SELECT * FROM historicalfigures WHERE Name = '"+ updatename+"';") 
    changedump = curs.fetchall()
    coltuple = ('Name', 'YearBirth', 'YearDeath', 'Role', 'Nation')
    colcount = 0
    for col in changedump[0]:
        print (str(colcount), coltuple[colcount], changedump[0][colcount])
        colcount += 1
    colchoice = input("Please enter number of column to change: ")
    colchoice = int(colchoice)
    newvalue = input("Please enter new value for this column: ")
    if colchoice == 0 or colchoice == 3 or colchoice == 4:
        newvalue = "'"+ newvalue +"'"
    print("About to change "+ coltuple[colchoice] +" to " + newvalue + " for "+ updatename)
    confirmchange = input("Confirm(Y) or discard (N): ")
    confirmchange = confirmchange.lower()
    if confirmchange == 'y':
        curs.execute("UPDATE historicalfigures SET "+ coltuple[colchoice] +" = "+ newvalue +" WHERE Name = '" + updatename + "';")
        curs.execute("COMMIT;")

print ("Historical Figures Table")
print ("========================")
cont = True
while cont == True:
    print ("Main menu - Please select an option")
    print ("View whole table ---V")
    print ("Search table -------S")
    print ("Enter new record ---E")
    print ("Delete record ------D")
    print ("Update record ------U")
    print ("Quit Program -------Q")
    optchoice = input("? ")
    optchoice = optchoice.lower()
    if optchoice == 'v':
        viewtable()
    elif optchoice == 's':
        searchtable()
    elif optchoice == 'e':
        enternew()
    elif optchoice == 'd':
        deleterecord()
    elif optchoice == 'u':
        updaterecord()
    elif optchoice == 'q':
        conn.close()
        cont = False
    else:
        print("Sorry, not recognised")



So this is big - bigger than any other program I've created for this blog. And what are the results like?

========= RESTART: C:\Users\pc\Documents\Programming\historical_2.py =========
Historical Figures Table
========================
Main menu - Please select an option
View whole table ---V
Search table -------S
Enter new record ---E
Delete record ------D
Update record ------U
Quit Program -------Q
? v
Name                  | Born  | Died  | Role                  | Nation
======================|=======|=======|=======================|=========
Cardinal Thomas Wolsey| 1473  | 1530  | Churchman & Politician | England
Christopher Columbus  | 1451  | 1509  | Explorer               | Spain & Italy
Elizabeth I           | 1533  | 1603  | Monarch                | England
Ferdinand Magellan    | 1480  | 1521  | Explorer               | Portugal
Francis Drake         | 1540  | 1596  | Explorer & Naval Comma | England
Henry VIII            | 1491  | 1547  | Monarch                | England
Leonardo da Vinci     | 1452  | 1519  | Artist                 | Italy
Niccolo Machiavelli   | 1469  | 1527  | Politician & Author    | Italy (Florence)
William Caxton        | 1422  | 1491  | Publisher              | England
William Shakespeare   | 1564  | 1616  | Author & Playwright    | England
Main menu - Please select an option
View whole table ---V
Search table -------S
Enter new record ---E
Delete record ------D
Update record ------U
Quit Program -------Q
?
========= RESTART: C:\Users\pc\Documents\Programming\historical_2.py =========
Historical Figures Table
========================
Main menu - Please select an option
View whole table ---V
Search table -------S
Enter new record ---E
Delete record ------D
Update record ------U
Quit Program -------Q
? v
Name                  | Born  | Died  | Role                  | Nation
======================|=======|=======|=======================|=========
Cardinal Thomas Wolsey| 1473  | 1530  | Churchman & Politician| England
Christopher Columbus  | 1451  | 1509  | Explorer              | Spain & Italy
Elizabeth I           | 1533  | 1603  | Monarch               | England
Ferdinand Magellan    | 1480  | 1521  | Explorer              | Portugal
Francis Drake         | 1540  | 1596  | Explorer & Naval Comma| England
Henry VIII            | 1491  | 1547  | Monarch               | England
Leonardo da Vinci     | 1452  | 1519  | Artist                | Italy
Niccolo Machiavelli   | 1469  | 1527  | Politician & Author   | Italy (Florence)
William Caxton        | 1422  | 1491  | Publisher             | England
William Shakespeare   | 1564  | 1616  | Author & Playwright   | England
Main menu - Please select an option
View whole table ---V
Search table -------S
Enter new record ---E
Delete record ------D
Update record ------U
Quit Program -------Q
? e
Entering new record
Please enter new name: Dante Alighieri
Please enter new year of birth: 1265
Please enter new year of death: 1321
Please enter new role: Author & Poet
Please enter new nation: Italy (Florence)
Do you want to keep this new record (Y to keep, N to discard): y
Main menu - Please select an option
View whole table ---V
Search table -------S
Enter new record ---E
Delete record ------D
Update record ------U
Quit Program -------Q
? y
Sorry, not recognised
Main menu - Please select an option
View whole table ---V
Search table -------S
Enter new record ---E
Delete record ------D
Update record ------U
Quit Program -------Q
? e
Entering new record
Please enter new name: Michelangelo di Lodovico Buonarotti Simoni
Please enter new year of birth: 1475
Please enter new year of death: 1564
Please enter new role: Artist
Please enter new nation: Italy (Florence & Rome)
Do you want to keep this new record (Y to keep, N to discard): y
Main menu - Please select an option
View whole table ---V
Search table -------S
Enter new record ---E
Delete record ------D
Update record ------U
Quit Program -------Q
? q
>>>


There are plenty of points I would like to make about this program:
  • It relies on functions to split the program into manageable chunks. Each choice (except Q to quit) in the main menu calls on a user-defined function defined before the main program.
  • I didn't need to reopen the connection to the database or recreate the cursor object when in the user-defined functions - they can automatically use the ones from the main program. 
  • The function viewtable()is about taking the list of tuples returned from SQLite by curs.fetchall() and presenting it neatly on the IDLE command line. One unforeseen problem is this blog has a narrower column width than the IDLE interface, so I've shrunk the font on the output to get the table to look neat. 
  • In viewtable() I have used slicing strings to make oversized text strings fit into columns, and repeating strings with * to fill with the right number of spaces. I'm hoping that all dates just have 4 digits. 
  • In searchtable() I have used user choices to assemble an SQL command. For searching both role and nation I have used the LIKE SQL comparator. This looks for records in a less exacting way than =  , and as here will find records where the searched-for string is part of that record's data in that field. So when searching the Role column, LIKE allows me to find author in a record whose Role field is author & playwright.  
  • All functions that involve changing the database have a confirming part, where if the user confirms they want to make the change, the COMMIT SQLite command is used. 
  • Both  deleterecord() and updaterecord() present a list of names (from the first column in the database) and allows the user to select the name by its index (its numerical position in the list). This name is then used as the identifier to make sure the right record in the database is updated or deleted as appropriate.  



Wednesday, 19 September 2018

Connecting to SQLite and Cursor Objects

SQLite, as mentioned in previous posts, is a software package for managing databases. It does so mainly through SQL, Standard Query Language. There are other database software packages that use SQL, namely MySQL (I didn't like it but you may have better luck), PostgreSQL and Oracle. SQLite has the nice feature that it integrates with programming languages such as Python, C++, Perl, Java and JavaScript.

So how do you use it? First you need to install it (my experience described here). Then you write a Python program that imports the sqlite3 module, connects to the database, and creates a virtual cursor. For example, if my SQLite is installed at C:\\sqlite and the database is at C:\\sqlite\db\base1.db then I create a connection by

import sqlite3 # only needed once at the start
conn = sqlite3.connect("C:\\SQLite\db\base1.db")

Remember when I looked at files, and I talked about creating a file object? This is basically what the conn variable represents. I think of it as a specialised file object, and the sqlite3 module allows us to interact in more varied ways with this file than just the usual read, write or append. 

Once you have made the connection with your chosen database you create what is called a cursor object. This is a virtual cursor - you don't see it flashing on your screen, but it enables you to pass SQL commands from Python to the SQLite program, and retrieve answers. 

So if we call our virtual cursor object curs (just a variable, but one I can recognise), we create it by
curs = conn.cursor()

Now we can tell Python to pass SQL commands to the SQLite program which will interact with the database. 
The bad news is SQL is a language in itself (that's what the L stands for). So the SQLite module lets you use a language within another language. 
The good news is SQL is not particularly difficult. 

One quick way of finding out the tables in a database is by querying the SQLite_master table. So the python command to run the SQL command is

curs.execute("SELECT * FROM sqlite_master WHERE type = 'table';")

Everything within the double quotes is being passed by the cursor object (curs) to the SQLite3 program. Also note the semi-colon at the end of each SQL command. This command on its own does not produce much on screen. If you are doing this on the IDLE command line rather than in a script, you might get a reply like
<sqlite3.Cursor object at 0x038DD360>
Not very informative. It's actually saying that a memory location has something from the cursor object. If execute() sends instructions from Python to SQL, then fetchall() and fetchone() bring back the reply.
I find it best to assign these replies to variables within Python. So
dump = curs.fetchall()
will retrieve whatever reply the curs object has received from SQL and pass it to Python, holding it in the variable dump. So what does dump hold?
>>> print(dump)
[('table', 'Location', 'Location', 2, 'CREATE TABLE Location(Scientific_name TEXT, Nation TEXT, Biome TEXT)')]
>>>

This might look like a mess, but a lot of queries will return in this format, and Python understands what this is. It is a list (denoted by the square brackets) that contains a tuple (denoted by the outer set of round brackets). The list is all the lines returned, with each line returned as a tuple - here it is just one line, so just one tuple within the list. Within the tuple, each element is the contents of the column (or field) for that row. In spreadsheet terms each element in the tuple would be a cell.

I admit this example is helped by the fact that I have already done some work on base1.db, so there is something to show for the queries.  It's like one of those "how to..." programmes on the TV where rather than going through the whole process, the presenter moves to a different workbench and announces "And here's one I did earlier" much to the frustration of the audience trying to follow.
So what tables are in this database? Just one, called Location (hence just one tuple in the response), and the final element in the tuple is actually the  SQL command used to create the table (here 'CREATE TABLE Location(Scientific_name TEXT, Nation TEXT, Biome TEXT)'. I won't go into a detailed explanation here.
So what if I then run the same two commands (execute() and fetchall()) for this table Location?
>>> curs.execute("SELECT * FROM Location;")
<sqlite3.Cursor object at 0x038DD360>
>>> dump2 = curs.fetchall()
>>> print(str(dump2))
[('Homo sapiens', 'United Kingdom', 'Urban'), ('Homo sapiens', 'United Kingdom', 'Rural'), ('Rattus norvegicus', 'United Kingdom', 'Urban'), ('Rattus norvegicus', 'United Kingdom', 'Rural'), ('Carcharodon carcharias', 'South Africa', 'Ocean'), ('Homo sapiens', 'South Africa', 'Urban'), ('Homo sapiens', 'South Africa', 'Ocean'), ('Carcharodon carcharias', 'Australia', 'Ocean'), ('Homo sapiens', 'Australia', 'Ocean')]


That's a mess (hence my choice of variable name).
But if I then tell Python to loop through the list (leaving the tuples as tuples) we get:
>>> for line in dump2:
 print(line)


('Homo sapiens', 'United Kingdom', 'Urban')
('Homo sapiens', 'United Kingdom', 'Rural')
('Rattus norvegicus', 'United Kingdom', 'Urban')
('Rattus norvegicus', 'United Kingdom', 'Rural')
('Carcharodon carcharias', 'South Africa', 'Ocean')
('Homo sapiens', 'South Africa', 'Urban')
('Homo sapiens', 'South Africa', 'Ocean')
('Carcharodon carcharias', 'Australia', 'Ocean')
('Homo sapiens', 'Australia', 'Ocean')
>>>

which is more clearly a table of results, with each tuple containing three elements (here Scientific Name, Nation and Biome respectively - biome is a biological term for what sort of habitat). This table actually was part of an early draft of the project I have been working on.

Finally, when finishing with SQLite, it is sensible to close any connections (again similar to closing a file object). This also closes the cursor object. So:
>>> conn.close()
>>> 

More later...

Monday, 2 October 2017

Ranges

Back in the early posts, I introduced the range() function as part of a for loop. The general idea is that it produces integers from 0 up to 1 less than the number stated.
At first I thought range() generated a list. But the Python command line shows this is not quite what happens.
>>> print (range(10))
range(0, 10)
>>> print (list(range(10)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
print(type(range(10)))
<class 'range'>>>>
It actually generates a range object that can say how a sequence of numbers is generated, but doesn't generate them until execution. You can then use the list() function to convert a range object into an actual list of numbers, or even tuple() to convert it to a tuple.
One of the old problems with ranges in Python is that they start at 0 and end just before you expect them to. This can be worked around by specifying the starting point of a range and increasing the end point by 1. So to get a range that really is from 1 to 10,
>>> print (list(range(1, 11)))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>>
 Being able to specify a starting point other than 0 can be very useful.
As far as I am aware, ranges only deal with integers, not floating point numbers, and by default they step forward by +1 each time. The distance they step can be modified.
The range() function takes 1, 2 or 3 arguments (values in the brackets).
1 argument - The stop point
2 arguments - The start point and stop point
>>> print (list(range(10, 21)))
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
3 arguments - The start point, the stop point and the increment per step.
>>> print (list(range(10, 31, 2)))
[10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30] 
>>> print (list(range(10, 20, -1)))
[]
>>> print (list(range(20, 10, -1)))
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11]
>>>
I included the mistake of getting the starting point and end point muddled up when stepping backwards as it is the type of error I would absent-mindedly do.



Sunday, 1 October 2017

Slicing Lists, Strings and Tuples

Sequence data types include lists, strings and tuples - all of them keep their elements in order and so each element in the sequence can be picked out by its index (position).
>>> examplestring = 'Hello World'
>>> examplestring[0]
'H'
>>> examplelist = ['This', 'is', 'a', 'list', 'of', 'strings']
>>> examplelist[-2]
'of'
>>> exampletuple = (20, 40, 60, 80, 100, 120)
>>> exampletuple[2]
60
>>>
So far this has already been covered. But the index in the square brackets need not be a single number for a single element.
Slicing is selecting a number of adjacent elements in the sequence, giving the indexes of the range you want, with a colon : to separate start point from end point. For example:
>>> print(examplestring[2:7])
llo W
>>> print (examplelist[1:3])
['is', 'a']
>>> print (exampletuple[3:5])
(80, 100)
>>>
As you can tell, it starts at the starting point but the last element given is one short of the stated finishing point. In the first example with the print(examplestring[2:7]), it gives the characters at the indices 2, 3, 4, 5 & 6, not 7
 Also the slice returned is the same type as the original sequence, so a slice of a string returns a string, a slice of a list returns a list etc.
If no index is given before the colon, the slice starts at the beginning
>>> print (examplestring[:8])Hello Wo
and if there is no index given after the colon, the slice goes through to the end
>>> print (exampletuple[3:])
(80, 100, 120)
>>>
If you really wanted to, you could simply have a colon without indices, and it would return the whole sequence, though I am not sure if this is of practical use.
>>> print (examplelist[:])
['This', 'is', 'a', 'list', 'of', 'strings']
>>>
Slices can accept -1 as the end element and negative indices step backwards from the end of a sequence. So if you don't know how long a sequence is but you want the last three elements you can do
>>> print (exampletuple[-3:])
(80, 100, 120)
>>>

All of these slices can be assigned to new variables, or even replace the existing variable with an assignment statement.


Saturday, 23 September 2017

Comparing with >, < and !=

In a previous post I showed how an if structure could make a program do one of several possible things, depending on whether an input was equal to some value. I used == throughout that post to test equality.
However, that is only one possible way of comparing two values. There are other comparisons:
  • > (greater than)
  • >= (greater than or equal to)
  • < (less than)
  • <= (less than or equal to)
  • != (not equal to)
  • in (whether a value is also an element in a list or other collection)
In each case, Python checks whether or not the comparison is True or False. Why have I capitalised these? Because they are keywords in Python, and are the two possibilities of the Boolean data type. As a data type (along with strings, lists, floats & integers), Boolean data can be assigned to a variable (and even included in lists), but more often than not it is used in decision-making in the structure of the program - typically if...elif... structures, and while loops (which I haven't covered yet but will get round to).  

A word of warning about data types: Python can and usually will throw a type error if you try to compare two different data types - this is actually the same fundamental error as when Python cannot sort lists containing different types. Python cannot compare strings with numbers, or numbers with lists etc.  Integers and floats can be compared with each other because they are both numbers.
Interestingly Boolean values can be compared to numbers, with True being treated as 1 and False being treated as 0. For example on the Python command line:
>>> print(4.777 > 4)
True
>>> print(4.777 < 4)
False
>>> print(0.5 > True)
False
>>> print(0.5 > False)
True
>>> print(0 == False)
True
>>> print(True == 1)
True
>>> print(0.5 == 'String')
False
>>> print(0.5 < 'String')
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    print(0.5 < 'String')
TypeError: '<' not supported between instances of 'float' and 'str'

>>>

Strings can be compared to each other - in this case they are compared alphabetically/ASCIIbetically rather than numerically, the same way as they are sorted if in a list. As you can see here, Python is case sensitive - upper case letters are considered different characters from their lower-case counterparts. This is also true for variable names, and can be a source of errors.
>>> print('Hello World' == 'hello world')
False
>>> print('Hello World' < 'hello world')
True
>>> print('Hello World' == 'Hello World')
True
>>>
Lists are equal to each other if they are the same length and the elements in each index are the same as in the other list - i.e. the contents of the list are identical. Whether a list is greater or less than another list is based on first the length of the list (i.e. number of elements) and then the contents of the elements.

The final comparator in the bullet-point list is in. Strictly speaking it is not a comparator but an operator, and is also found in for loops, though here the context is different. It is useful for finding if something being searched for matches one or more elements in a list. For example on the command line:
>>> x = [10, 12, 15, 17]
>>> print(10 in x)
True
>>> print(11 in x)
False
>>> y = ['This', 'is', 'a', 'list', 'of', 'strings']
>>> print ('is' in y)
True
>>> print ('Hello' in y)
False
>>>


Tuesday, 19 September 2017

Manipulating Lists

So previously we've looked at what lists are. Now what can we do with them?
Lists are a mutable data type. This means we can change them without assigning them to a new variable (or recreating the existing variable). However, the order of elements stays put unless we specifically change them. This means you can look at an element at a specific position (also known as the element's index) in a list by the list variable name followed by the position in square brackets. So for example on the command line:
>>> example = [23, 10, 18, 31]
>>> print (example[1])
10
>>>
You might say the computer has got it wrong. That's not the first number in the list, it should be 23.
Remember when using range(10) we actually got a range from 0 to 9? Lists are numbered the same way - from 0 to length-1. So if you wanted the first element, you need [0]
>>> print (example[0])
23
>>>
And just to show you what happens if you try to go beyond the end of a list:
 >>> print(example[5])
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    print(example[5])
IndexError: list index out of range

>>>
This is known as an "out of range error" and may crop up if you aren't careful with lists in programs. As you can see, the position of the element is referred to as its index.
To find the last item in a list you can use [-1], so you don't need to know the actual length:
>>> print (example[-1])
31
>>>
You can change an element's value by using its index in an assignment statement
>>> print (example)
[23, 10, 18, 31]
>>> example[1] = 15
>>> print (example)
[23, 15, 18, 31]
>>>
Lists also have a bunch of useful built-in functions, including index(), sort(), append() and clear().

index()

This function looks for the position (index) of an element within a list with the format listname.index(element). If one or more elements match what it's looking for, it returns the index of the first one. Again remember that the index starts at 0 and ends at number of elements -1.
>>> print (example)
[23, 15, 18, 31]
>>> example.index(23)
0
>>> example.index(31)
3
>>> example.index(0)
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    example.index(0)
ValueError: 0 is not in list

>>>
As you can see, Python throws an error when asked to give the index of something not in the list so this is not good for generally searching for a term in a list - for that use in. 

sort()

This allows us to sort a list into some order. Going back to our example on the Python command line
>>> example.sort()
>>> print (example)
[15, 18, 23, 31]
>>> print (example.sort())
None
>>>
That print(example.sort()) is a tricky one that has caught me out several times. The sort() function will sort the list, but returns None. There have been times in a program when I've done something like
>>> newlist = example.sort()
>>> print (newlist)
None
>>>

and then been puzzled and annoyed when the newlist is empty. What I should have done is
>>> newlist = example
>>> newlist.sort()
>>> print (newlist)
[15, 18, 23, 31]
>>>
 One final caveat - when sorting, don't mix types - more specifically, don't try to sort a mix of numbers and strings. sort() can sort numbers (including a mix of floats and integers, such as secondexample below) numerically, and it can sort strings ASCIIbetically (like alphabetically but with a wider range of characters, such as thirdexample below) , but it doesn't sort a mix of numerical and ASCIIbetical (like badexample) and comes back with a type error.
>>> secondexample = [102, 52.3, 75, 10.666, 0.01, 10]
>>> secondexample.sort()
>>> print (secondexample)
[0.01, 10, 10.666, 52.3, 75, 102]
>>> print (type(secondexample[0]))
<class 'float'>
>>> print (type(secondexample[1]))
<class 'int'>
>>> thirdexample = ['this', 'is', 'a', 'list', 'of', 'strings']
>>> thirdexample.sort()
>>> print (thirdexample)
['a', 'is', 'list', 'of', 'strings', 'this']
>>> badexample = [0.2, 'Hello', 'world', 50]
>>> badexample.sort()
Traceback (most recent call last):
  File "<pyshell#42>", line 1, in <module>
    badexample.sort()
TypeError: '<' not supported between instances of 'str' and 'float'

>>>

append()and Concatenation

Adding new elements to the end of a list can be done two ways. One is concatenation (a fancy way of saying sticking two things together, one after the other), the other uses the append() function. So going back to our example on the Python command line:
>>> example = example + [17]
>>> print (example)
[15, 18, 23, 31, 17]
>>> example.append(25)
>>> print (example)
[15, 18, 23, 31, 17, 25]
>>>
At this stage there doesn't seem to be a major difference between the two. However, there are ramifications later on because append() modifies the existing list, while concatenation creates a new list, or at least here recreates the existing list with the addition.
Concatenation has two advantages: firstly you can add new elements onto the front, by doing the concatenation the other way round:
>>> print (example)
[15, 18, 23, 31, 17, 25]
>>> example = [41] + example
>>> print (example)
[41, 15, 18, 23, 31, 17, 25]
>>>

and secondly you can concatenate more than one element at a time, or even join two lists together.
>>> concatexample = secondexample + example
>>> print (concatexample)
[0.01, 10, 10.666, 52.3, 75, 102, 41, 15, 18, 23, 31, 17, 25]
>>>
If you append() one list onto the end of another list, what you get is a list within a list.
>>> print (thirdexample)
['a', 'is', 'list', 'of', 'strings', 'this']
>>> thirdexample.append(badexample)
>>> print (thirdexample)
['a', 'is', 'list', 'of', 'strings', 'this', [0.2, 'Hello', 'world', 50]]
>>>
This might be what you actually want. There are good reasons for having lists within lists. But I need to be careful about this.

del and clear()

Finally you can remove elements from lists. If appending the list into the other list was a mistake, I can use del command and the element's index in the list:
>>> print (thirdexample)
['a', 'is', 'list', 'of', 'strings', 'this', [0.2, 'Hello', 'world', 50]]
>>>
>>> del thirdexample[6]
>>> print (thirdexample)
['a', 'is', 'list', 'of', 'strings', 'this']
>>>
 To totally clear out a list, you can use the clear() function. This doesn't delete the list itself, merely all the elements it contains.
>>> print(badexample)
[0.2, 'Hello', 'world', 50]
>>> badexample.clear()
>>> print (badexample)
[]
>>>


Monday, 18 September 2017

A First Look at Lists

Previously I've mentioned three different data types: strings (enclosed in quotes, and treated as text, such as words and sentences), integers (whole numbers) and floating point numbers (numbers with a decimal point).

Lists are another data type. Whereas strings have quote marks at each end ("" or ''), lists use square brackets ([]) at each end and commas to separate the elements of the list. Element is the term for each item in the list. For example this script:
#!/usr/bin/python3
foo = ['Hello', "World", 4, 6.333, 5, "This is a list"]
for elem in foo:
    print (elem, type(elem))
produces the output:
  RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test04b_listdemo.py
Hello <class 'str'>
World <class 'str'>
4 <class 'int'>
6.333 <class 'float'>
5 <class 'int'>
This is a list <class 'str'>

>>>
 There are a number of points here:
  • Lists, like other data types, are often assigned to variables, in this case foo. 
  • for loops can go through each element (item) in a list instead of a range of numbers
  • A list can contain different types of data, including strings, floats & integers (here using the type() function we encountered in a previous post). 
I can modify the script to show that lists can even include other lists, or variables containing lists.
#!/usr/bin/python3
zim = ['list', 'within', 'a', 'list']
foo = ['Hello', "World", 4, 6.333, 5, "This is a string", zim]
for elem in foo:
    print (elem, type(elem))
with the output:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test04b_listdemo.py
Hello <class 'str'>
World <class 'str'>
4 <class 'int'>
6.333 <class 'float'>
5 <class 'int'>
This is a string <class 'str'>
['list', 'within', 'a', 'list'] <class 'list'>

>>>
 The len() function returns the length of whatever is in the brackets. So to demonstrate I can add a few lines to the script:
#!/usr/bin/python3
zim = ['list', 'within', 'a', 'list']
foo = ['Hello', "World", 4, 6.333, 5, "This is a string", zim]
for elem in foo:
    print (elem, type(elem))
print ('elements in foo = ', len(foo))
print ('elements in zim = ', len(zim))
with the resulting output:
  RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test04b_listdemo.py
Hello <class 'str'>
World <class 'str'>
4 <class 'int'>
6.333 <class 'float'>
5 <class 'int'>
This is a string <class 'str'>
['list', 'within', 'a', 'list'] <class 'list'>
elements in foo =  7
elements in zim =  4

>>>
Although you may have noticed, I'll just point out that in calculating the length of foo, len(foo) does not  include the elements within zim - zim is treated as a single element.

Just as an aside, len() also works on strings, counting then returning the number of characters in the string. For example, on the Python command line:
>>> len("Hello World")
11
>>> example = "This is an example string"
>>> len(example)
25
>>>
 Back to lists. If a list just contains numbers, then you can use a for loop to do maths on them. Here's another script.
#!/usr/bin/python3
# initialising the list variable
x = [45, 32, 46, 21, 40, 39, 28, 55]
total = 0
for num in x:
    total = total + num
print ('List is ', x)
print ('Total for list is: ', total)
avg = total / len(x)
print ('Average for list is ', avg)
which gives the output:
  RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test04c_numberlist.py
List is  [45, 32, 46, 21, 40, 39, 28, 55]
Total for list is:  306
Average for list is  38.25

>>>
 As you can see, len() is useful for tasks such as finding averages as you don't need to work out for yourself how many elements you need to divide the total by.