CPX19Strings, Files, Validation and Authentication
Edexcel 1CP2 · 6.3.3
Strings: length, indexing, slicing and joining
A string is a sequence of characters. Like a list, each character has an index starting at 0.
text[start:end] gives the characters from start up to but not includingend. The slice has end - start characters.
Slicing past the end is safe: "Li"[0:5] is just "Li". Indexing past the end, "Li"[5], is an IndexError.
for letter in word: goes through the characters one at a time.
You can’t change one character of a string in place (word[0] = "X" is a TypeError). Build a new string instead: word = "X" + word[1:len (word)].
Compare whole strings with one relational operator: if (code == "AB"):. You don’t need to compare them letter by letter.
Upper and lower case are different characters: "Leeds" == "leeds" is False. Convert both to one case first if case shouldn’t matter.
Code (bird = "PENGUIN")
Result
len (bird)
7
bird[0]
"P"
bird[len (bird) - 1]
"N", the last character
bird[1:4]
"ENG"
bird[0:3]
"PEN", the first three characters
bird + "S"
"PENGUINS": concatenation
"ab" * 3
"ababab": repetition
A slice includes the start index but stops before the end index
Worked examplepostcode = "WF10 4LP". Give the value of (a) len (postcode) (b) postcode[0:2] (c) postcode[5:8] (d) postcode[len (postcode) - 1].
(a) 8the space counts as a character
(b) "WF"indexes 0 and 1
(c) "4LP"indexes 5, 6 and 7
(d) "P"the last index is 8 − 1 = 7
(a) 8 (b) "WF" (c) "4LP" (d) "P"
Exam tipWhen you need the last character (a check digit, say), use text[len (text) - 1], and check the string isn’t empty first. Examiners saw programs crash here on blank input.
String
A sequence of characters. Python: str.
Slicing
Taking part of a string: text[start:end] gives indexes start to end − 1.
Concatenation
Joining strings end to end with +.
Edexcel 1CP2 · 6.3.3
String methods in the PLS
A method is called with a dot after the string: name.upper (). It returns a new value. The original string doesn’t change, so store the result: name = name.upper ().
The is… methods return True or False, so they fit straight into a condition: if (pin.isdigit ()):.
On an empty string, isalpha (), isdigit () and isalnum () all return False.
"-5".isdigit () and "2.5".isdigit () are False: - and . aren’t digits.
A space isn’t a letter or a digit, so "LS6 2QT".isalnum () is False.
Use find when the text might be missing (test for -1), and index only when you know it is there.
split always returns a list of strings. Convert number fields yourself.
Method
Example
Result
.upper () / .lower ()
"Leeds".upper ()
"LEEDS"
.isupper () / .islower ()
"LEEDS".isupper ()
True
.isalpha ()
"Ty2".isalpha ()
False: all letters?
.isdigit ()
"2026".isdigit ()
True: all digits 0–9?
.isalnum ()
"Ty2".isalnum ()
True: all letters or digits?
.find (sub)
"banana".find ("an")
1, or −1 if not there
.index (sub)
"banana".index ("n")
2, or a ValueError if not there
.replace (old, new)
"a-b-c".replace ("-", "")
"abc"
.split (sep)
"Ann,14,B".split (",")
["Ann", "14", "B"]
.strip (char)
"**hi**".strip ("*")
"hi": removed from both ends
Worked exampleA program reads a full name such as Ola Bakare. Write code that outputs the surname in capitals (everything after the first space).
space = fullName.find (" ") → 3the position of the first space
surname = fullName[space + 1:len (fullName)] → "Bakare"from the character after the space to the end
print (surname.upper ())upper () returns the capitalised copy
Exam tipname.upper () on a line by itself does nothing useful: the result is thrown away. Write name = name.upper ().
Method
A subprogram called on a value with a dot, e.g. word.upper ().
Edexcel 1CP2 · 6.3.3, 2.2.1
Character codes: ord () and chr ()
Every character has a number. The first 128 Unicode codes are the same as ASCII.
Letters run in order: if "A" is 65, "D" is 68. The next letter after c is chr (ord (c) + 1).
Each lower-case letter’s code is 32 more than its capital: ord ("a") - ord ("A") is 32.
A letter’s position in the alphabet is ord (letter) - 64 for capitals (A is 1).
Digits: ord ("7") is 55, not 7. int ("7") gives the number 7. Read the question to see which one it wants.
Comparisons between strings use these codes, which is why "Z" < "a" is True.
Function
Example
Result
ord (char)
ord ("A")
65
ord (char)
ord ("a")
97
ord (char)
ord ("0")
48
ord (char)
ord (" ")
32
chr (code)
chr (66)
"B"
Worked exampleWrite code that outputs the alphabet position of each letter in "CAB".
for letter in word:one character at a time
ord (letter) - 64C is 67, so 67 − 64 = 3
word = "CAB"
for letter in word:
print (letter, ord (letter) - 64)
Exam tipIf a task says ‘use the ASCII value’, use ord (). Using int () on a letter crashes, and on a digit it gives the wrong number for that task.
ord ()
Returns the integer code of a single character: ord ("A") is 65.
chr ()
Returns the character for an integer code: chr (65) is "A".
Edexcel 1CP2 · 6.4.2
Reading a comma-separated text file
In Paper 2, data files are comma-separated value (CSV) text files: one record per line, fields separated by commas, every line ending in a line feed (\n).
Open the file for reading: inFile = open ("Birds.txt", "r"). Use the file name constant if there is one.
Loop through the lines: for line in inFile: reads one line each pass.
Strip the line feed: line = line.strip ("\n"). Without this, the last field ends in \n and won’t match or convert properly.
Split into fields: fields = line.split (","). Every field is a string: convert numbers with int () or float ().
Use the fields straight away, or append them as a record to a two-dimensional list.
Close the file when you’ve finished: inFile.close ().
Also in the PLS: readline () returns the next line (an empty string at the end of the file), and readlines () returns a list of all the lines.
Opening a file that isn’t there for reading crashes with a FileNotFoundError. Keep data files in the same folder as the program.
Strip, split, convert: the three steps for every line
Worked exampleA garden bird survey is saved in Birds.txt. Write a program that displays each bird and its count, then the total number of birds seen.
Robin,14
Blue tit,22
Wren,6
Magpie,9
Open the file with "r", then for line in inFile:one record per pass
line.strip ("\n"), then .split (",")two string fields per line
int (fields[1]) added to the totalthe count is a string until converted
Close the file, then display the totalthe total is complete only after the loop
# -------------------------------------------------------------------
# Constants
# -------------------------------------------------------------------
IN_FILE = "Birds.txt"
# -------------------------------------------------------------------
# Global variables
# -------------------------------------------------------------------
inFile = ""
line = ""
fields = []
total = 0
# -------------------------------------------------------------------
# Main program
# -------------------------------------------------------------------
inFile = open (IN_FILE, "r")
for line in inFile:
line = line.strip ("\n")
fields = line.split (",")
print (fields[0], fields[1])
total = total + int (fields[1])
inFile.close ()
print ("Total birds:", total)
Exam tipThe most-missed file marks are the simplest: the right file name (with .txt), the right mode, and a close () on the same variable that was opened.
CSV file
A text file with one record per line and fields separated by commas.
Line feed
The control character \n that ends each line of a text file.
Edexcel 1CP2 · 6.4.2
Writing and appending to a text file
Build each line first: convert each field to a string, join them with commas, add "\n" at the end. Then write the whole line: outFile.write (outLine).
outLine = name + "," + str (score) + "\n". No spaces round the commas, and nothing after the last field except the line feed.
writelines (list) writes a list of strings, one after another. It doesn’t add line feeds, so each string must end with "\n" already.
Open a file for reading or writing, not both at once.
Always close () the file. Data may not be saved properly if you don’t.
Run file-writing programs twice and look at the file. With "w" it should be the same each time; with "a" it should grow.
Mode
Opens the file to…
If the file already exists
"r"
read
reads it (crashes if it doesn’t exist)
"w"
write
its old contents are destroyed
"a"
append
new lines are added to the end
Choose the mode carefully: "w" wipes the file first
Worked exampleA craft shop keeps its yarn stock as [yarn, grams per ball, balls in stock]. Write a program that saves this two-dimensional list to Yarn.txt, one record per line.
Exam tipWriting one character or one field at a time, or writing str (record) (which adds brackets and quotes), loses marks. Build the whole line as one string, then write it once.
Write mode ("w")
Opens a file for writing; any existing contents are destroyed.
Append mode ("a")
Opens a file so new data is added to the end of what is already there.
Edexcel 1CP2 · 6.4.3
Validation checks
Validation checks that input is sensible and in the right form before the program uses it. It stops a program crashing or storing nonsense.
Validation can’t make data correct. An age of 14 typed as 15 passes a range check. It only rejects data that can’t be right.
Check the pattern before converting: test text.isdigit () before int (text), so letters don’t cause a ValueError.
Use the constants given for limits: (len (code) >= MIN_LENGTH), not >= 6.
Several checks on one item: put the presence check first. On an empty string, a later check such as text[0] would crash.
Tell the user which check failed, so they know what to fix.
Only validate what the question asks for. Extra checks earn nothing.
Check
What it checks
Python condition (True when valid)
Presence
Something has been entered
len (name) > 0 or name != ""
Length
The number of characters is allowed
(len (code) >= 6) and (len (code) <= 8)
Range
A number is between limits
(age >= 11) and (age <= 18)
Pattern
The characters are the right kind, in the right places
pin.isdigit (), ref[0:2].isalpha ()
Worked exampleA bike-share scheme gives each dock a code that must be exactly 5 characters: two capital letters followed by three digits, such as KT204. Write the condition that is True for a valid code.
(len (code) == 5)length check first, so the slices below are safe to read
code[0:2].isalpha () and code[0:2].isupper ()pattern: the first two characters are capital letters
code[2:5].isdigit ()pattern: the last three are digits
if ((len (code) == 5) and (code[0:2].isalpha ()) and
(code[0:2].isupper ()) and (code[2:5].isdigit ())):
print ("Valid")
Exam tipWhen a question says ‘reject blank input’, that is a presence check, and it carries its own mark. Students who skipped it lost that mark.
Validation
Checking that input data is sensible, reasonable and in the right form before it is used.
Presence check
Checks that data has been entered.
Length check
Checks the number of characters.
Range check
Checks that a value lies between a lower and an upper limit.
Pattern check
Checks the type of characters and where they are, e.g. letters then digits.
Edexcel 1CP2 · 6.4.3
Repeating until the input is valid
Pearson’s advice: a while loop that only ends when the input is valid is an effective way to validate user input.
Pattern 1: input once, then while (input is invalid): show a message and input again.
Pattern 2: a Boolean flag. valid = False, then while (not valid): input, check, and set valid = True when every check passes.
The flag pattern suits several checks with a different message for each.
For a number, check the text with isdigit () first, then convert and range-check.
If a question says to use relational and logical operators in an if (reject and stop), don’t add a loop. Do what the question says.
The program can’t continue until the check passes
Worked exampleWrite code that asks for the number of raffle tickets (1 to 10) until a whole number in range is entered.
valid = False, then while (not valid):keep asking until a good value arrives
If the text isn’t all digits: messagestops int () crashing on letters
Otherwise convert, then range-check with and1 and 10 are both allowed
MAX_TICKETS = 10
valid = False
tickets = 0
while (not valid):
entry = input ("Number of raffle tickets (1 to 10): ")
if (not entry.isdigit ()):
print ("Please enter a whole number")
else:
tickets = int (entry)
if ((tickets >= 1) and (tickets <= MAX_TICKETS)):
valid = True
else:
print ("You can buy from 1 to", MAX_TICKETS, "tickets")
print ("Booking", tickets, "tickets")
Exam tipTest validation with a value just inside each limit, the limits themselves, and one just outside. Try a blank entry too.
Edexcel 1CP2 · 6.4.4
Authentication: checking an ID and password
Authentication confirms that a user is who they claim to be, usually by checking an ID and password against stored values, before letting them in.
Store the users as a two-dimensional list of records, such as [["ID", "password"], …].
Take the ID and password as input (presence-check them if asked).
Look up the ID with a linear search: a while loop with a found flag that stops at a match or at the end of the list.
Only compare the password with the password in the record you found, not with every password in the table.
Give the result: ID not found, wrong password, or access allowed. Exam questions often ask for separate messages. Real systems usually say only ‘details not recognised’, so attackers can’t learn which IDs exist.
Comparisons are case-sensitive. Convert the ID with upper () if case shouldn’t matter, but never the password.
A limit on attempts (for example 3) slows down anyone guessing passwords.
Worked exampleWrite a program that checks a judo club member’s ID and PIN against members and shows Welcome, Wrong PIN or ID not found.
Search for the ID with a while loop and a flagstops as soon as the ID is found
When found, index still points at that memberso members[index][1] is their PIN
Nested selection gives the three messagesnot found / found with wrong PIN / found with right PIN
members = [["Y104", "5521"], ["Y233", "0906"], ["Y317", "7340"]]
found = False
index = 0
memberID = input ("Member ID: ")
pin = input ("PIN: ")
while ((not found) and (index < len (members))):
if (members[index][0] == memberID):
found = True
else:
index = index + 1
if (not found):
print ("ID not found")
elif (members[index][1] == pin):
print ("Welcome")
else:
print ("Wrong PIN")
Exam tipKeep PINs and codes that start with 0 as strings. As integers, "0906" would become 906 and never match.
Authentication
Checking a user’s identity, e.g. by matching an ID and password with stored values.
Edexcel 1CP2 · 6.4.2, 6.4.3, 6.3.3
Putting it together: process a file and write a report
Paper 2’s longest questions often combine everything: read a file, check or process each record, and write the results to another file.
Plan the sections first: constants for file names and limits, global variables, then the main program.
Open the input file for reading and the output file(s) for writing before the loop. Close them all after it.
Inside the loop: strip, split, convert, check or calculate, then build and write the output line.
Go through the file once. Don’t read it all into a list first unless you need to use the data again later.
Keep a count or total during the loop and show a summary after it.
Run the program, open the output file to check it, then run it again to make sure the result is still right.
Worked exampleOrders.txt holds a food truck’s orders as orderNumber,item,price,quantity. Write a program that writes each order’s cost to Bills.txt as orderNumber,cost (two decimal places) and shows the day’s total.
Open Orders.txt with "r" and Bills.txt with "w"both before the loop
Strip, split, then float (fields[2]) * int (fields[3])price × quantity
fields[0] + "," + "{:.2f}".format (cost) + "\n"one complete line per order
Add to the total; close both files after the loopthen show the summary
IN_FILE = "Orders.txt"
OUT_FILE = "Bills.txt"
dayTotal = 0.0
inFile = open (IN_FILE, "r")
outFile = open (OUT_FILE, "w")
for line in inFile:
fields = line.strip ("\n").split (",")
cost = float (fields[2]) * int (fields[3])
dayTotal = dayTotal + cost
outFile.write (fields[0] + "," + "{:.2f}".format (cost) + "\n")
inFile.close ()
outFile.close ()
print ("Day total: £" + "{:.2f}".format (dayTotal))
Exam tipRecords stored as tuples, line feeds left on, and a file opened but never closed all appeared in examiners’ lists of lost marks. Check for each before you save.