Tutorial 11

💾

Files

9618 A-Level


In this tutorial, we'll look at the 3 file modes require for AS (READ, WRITE & APPEND and 1 additional file mode required for A2: RANDOM

It is important to remember that when we open a file using OPENFILE, we must close it with a corresponding CLOSEFILE later in the program - this is so the file lock can be removed and it can be modified by another process (you don't want two processes writing to the same file at the same time, since that could cause data corruption/inconsistency)

Note: there are some example files at the bottom of the page you can use to test your programs

1

File Modes

For AS, there are three file modes - READ, WRITE and APPEND:

  • READ: opens a file that already exists to be read
  • WRITE: creates a new file/overwrites an existing file to write to
  • APPEND: creates a file if it doesn't exist, else adds lines to the end of an existing file

The following example shows us opening and closing a file in these modes - we aren't actually reading, appending or writing any data here

Note: as shown below, file names can be either in or out of quotes

OPENFILE Jokes.txt FOR READ OPENFILE "Palindromes.txt" FOR APPEND OPENFILE "MovieQuotes.txt" FOR WRITE CLOSEFILE "Jokes.txt" CLOSEFILE "Palindromes.txt" CLOSEFILE MovieQuotes.txt

We can also use a constant or variable to store filenames:

CONSTANT scores = "scores.txt" DECLARE students : STRING students ← "students.txt" OPENFILE scores FOR READ OPENFILE students FOR WRITE CLOSEFILE scores CLOSEFILE students

If in A2, the following file mode is also required:

  • RANDOM: creates a new file if it doesn't exist to allow records to be added to or removed from

Again, the below example simple shows us opening and closing a record - not actually putting or getting a record from the file

OPENFILE teams.dat FOR RANDOM CLOSEFILE teams.dat

2

Writing to a File

We can use WRITEFILE followed by the file and data to write

As stated, opening a file in WRITE mode will create a new file of the given name, regardless of it the file already exists - effectively, an existing file will be overwritten with the new content

OPENFILE palindromes.txt FOR WRITE WRITEFILE palindromes.txt, "Was it a car or a cat I saw" WRITEFILE palindromes.txt, "Mr. Owl ate my metal worm." WRITEFILE palindromes.txt, "Yo, Banana Boy!" CLOSEFILE palindromes.txt

3

Appending to a File

If we open the file in APPEND mode, new lines will be added to the end of an existing file (or a file will be created if it doesn't exist)

As with WRITE mode, we use the same WRITEFILE keyword followed by the file and data to write

OPENFILE palindromes.txt FOR APPEND WRITEFILE palindromes.txt, "A man, a plan, a canal, Panama!" CLOSEFILE palindromes.txt

4

Reading from a File

In AS, if we want to read the entire file, we can make use of the EOF function that will return whether we have reached the "end of file" - i.e. read all the lines

As an example, we can read all lines in the palindrome file:

READFILE is followed by the file we are reading from, then a variable where we will store the line we just read - here, I simply called it "line"

Note: if you know the number of lines in the file (in this case 3), you could loop from 1 to 3 and use READFILE inside the loop, but generally using a while loop is recommended, since this also works when we don't know the number of lines in the file

DECLARE line : STRING OPENFILE palindromes.txt FOR READ WHILE NOT EOF(palindromes.txt) DO READFILE palindromes.txt, line OUTPUT line ENDWHILE CLOSEFILE palindromes.txt

You would rarely do this, but this just demonstrates how EOF works - once all 3 lines have been read, EOF will now return true, symbolising there's no more lines to read

DECLARE lineNum : INTEGER DECLARE line : STRING lineNum ← 0 OPENFILE palindromes.txt FOR READ OUTPUT "EOF: ", EOF(palindromes.txt) READFILE palindromes.txt, line OUTPUT "Palindrome 1: ", line OUTPUT "EOF: ", EOF(palindromes.txt) READFILE palindromes.txt, line OUTPUT "Palindrome 2: ", line OUTPUT "EOF: ", EOF(palindromes.txt) READFILE palindromes.txt, line OUTPUT "Palindrome 3: ", line OUTPUT "EOF: ", EOF(palindromes.txt) CLOSEFILE palindromes.txt

5

Random Files

When deadling with random files, the following keywords are required

  • SEEK file, position: go to a specific record position in the file
  • PUTRECORD file, record: place the record in the file at the position we have seeked to
  • GETRECORD file, variable: read the record from the file at the position we have seeked to into the variable specified

As you can see, if using either PUTRECORD or GETRECORD, we first have to SEEK to the desired record position in the file

Assume we have 5 records - we would then seek to positions 1 to 5 in the file

The following demonstrates the all functionality associated with random files - note the following:

  • Defining a record (Team), creating an array of these records and initialising them with values
  • Seeking to file positions 1 & 2, then putting the relevant records in those positions - usually you would do this in a loop, here I just did it outside of a loop to make it obvious what the seek positions should be
  • Looping from 1 to 2 and reading the records from the file at this index into a new team array (a new array was created just to demonstrate that GETRECORD works)
TYPE Team DECLARE name : STRING DECLARE players : ARRAY[1:2] OF STRING DECLARE founded : DATE ENDTYPE DECLARE teams : ARRAY[1:2] OF Team teams[1].name ← "Man Utd" teams[1].players[1] ← "Bruno Fernandes" teams[1].players[2] ← "Alejandro Garnacho" teams[1].founded ← 03/05/1878 teams[2].name ← "Arsenal" teams[2].players[1] ← "Declan Rice" teams[2].players[2] ← "Martin Odegaard" teams[2].founded ← 11/12/1886 //putting records into random file OPENFILE teams.dat FOR RANDOM SEEK teams.dat, 1 PUTRECORD teams.dat, teams[1] SEEK teams.dat, 2 PUTRECORD teams.dat, teams[2] CLOSEFILE teams.dat //getting records from random file DECLARE teamsNew : ARRAY[1:2] OF Team DECLARE teamNum, playerNum : INTEGER OPENFILE teams.dat FOR RANDOM FOR teamNum ← 1 TO 2 SEEK teams.dat, teamNum GETRECORD teams.dat, teamsNew[teamNum] OUTPUT teamsNew[teamNum].name, " were founded on ", teamsNew[teamNum].founded, " and have the following players:" FOR playerNum ← 1 TO 2 OUTPUT "- ", teamsNew[teamNum].players[playerNum] NEXT playerNum OUTPUT "" NEXT teamNum CLOSEFILE teams.dat

6

Technology Quotes

Create a program to output a numbered list of all the technology quotes in the following numbered format, with each quote having an empty line between them

DECLARE lineNum : INTEGER DECLARE quote : STRING lineNum ← 0 OPENFILE TechnologyQuotes.txt FOR READ WHILE NOT EOF(TechnologyQuotes.txt) DO lineNum ← lineNum + 1 READFILE TechnologyQuotes.txt, quote OUTPUT lineNum, ") ", quote OUTPUT "" ENDWHILE CLOSEFILE TechnologyQuotes.txt

7

Student Scores

Use the student names and scores to create a program that outputs the data in the following format: "Albert scored 62"

The min/max score and student name who, scored that the average and the number who passed/failed (with e.g. 50% being the passing score) should also be output

Note: the student names/scores are in the same order in both files - i.e. line 1 refers to student 1's score, line 2 to student 2's score etc

DECLARE minScore, maxScore, total, studentCount, studentScore, numPassed : INTEGER DECLARE studentName, studentScoreStr, minScoreName, maxScoreName: STRING studentCount ← 0 OPENFILE students.txt FOR READ OPENFILE scores.txt FOR READ WHILE NOT EOF(students.txt) DO READFILE students.txt, studentName READFILE scores.txt, studentScoreStr studentScore ← STR_TO_NUM(studentScoreStr) OUTPUT studentName, " scored ", studentScore total ← total + studentScore IF studentCount = 0 OR studentScore < minScore THEN minScore ← studentScore minScoreName ← studentName ENDIF IF studentCount = 0 OR studentScore > maxScore THEN maxScore ← studentScore maxScoreName ← studentName ENDIF IF studentScore >= 50 THEN numPassed ← numPassed + 1 ENDIF studentCount ← studentCount + 1 ENDWHILE CLOSEFILE students.txt CLOSEFILE scores.txt OUTPUT "--- Summary ---" OUTPUT "Num Passed: ", numPassed OUTPUT "Num Failed: ", studentCount - numPassed OUTPUT "Top Score: ", maxScore, " by ", maxScoreName OUTPUT "Lowest Score: ", minScore, " by ", minScoreName OUTPUT "Average Score: ", total / studentCount

8

Character Ranges

Create a program that allows the user to specify two characters, then will write all characters including and between the two characters specified to an existing file

This should continue, until the two characters the user enters are the same - this character shouldn't be written to the file

DECLARE char1, char2 : CHAR DECLARE startASCII, endASCII, currentASCII : INTEGER OPENFILE chars.txt FOR APPEND REPEAT OUTPUT "Enter character 1:" INPUT char1 OUTPUT "Enter character 2:" INPUT char2 IF char1 > char2 THEN startASCII ← ASC(char2) endASCII ← ASC(char1) ELSE startASCII ← ASC(char1) endASCII ← ASC(char2) ENDIF IF startASCII <> endASCII THEN FOR currentASCII ← startASCII TO endASCII WRITEFILE chars.txt, CHR(currentASCII) NEXT currentASCII ENDIF UNTIL char1 = char2 CLOSEFILE chars.txt

9

Movie Quotes from Decade

Use the 20 movie quotes to create a program that allows people to input a decade, then outputs all the movie quotes from that decade along with the count

If no movie quotes exist for that decade, a message notifying the user of this should be displayed

The program should continue until the user enters nothing for the decade

CONSTANT MOVIE_FILE = "MovieQuotes.txt" DECLARE chosenDecade, quote : STRING DECLARE quoteCount : INTEGER REPEAT OUTPUT "Choose decade:" INPUT chosenDecade IF chosenDecade <> "" THEN OUTPUT "--- Quotes from ", chosenDecade, "s ---" chosenDecade ← MID(chosenDecade, 1, 3) //e.g. "1960" becomes "196" OPENFILE MOVIE_FILE FOR READ quoteCount ← 0 WHILE NOT EOF(MOVIE_FILE) DO READFILE MOVIE_FILE, quote IF MID(quote, LENGTH(quote) - 4, 3) = chosenDecade THEN OUTPUT quote quoteCount ← quoteCount + 1 ENDIF ENDWHILE CLOSEFILE MOVIE_FILE IF quoteCount > 0 THEN OUTPUT "--- Found ", quoteCount, " Quotes ---" ELSE OUTPUT "--- No Quotes Found ---" ENDIF ENDIF UNTIL chosenDecade = ""

10

Remove Blank Lines

Note how the technology facts is poorly formatted with many blank lines - create a program that will copy all of the facts and exclude the empty lines to a new text file

A count of the number of lines written and number of empty lines removed should also be displayed

CONSTANT OLD_FILE = "facts.txt" CONSTANT NEW_FILE = "facts-new.txt" DECLARE line : STRING DECLARE factCount, emptyLineCount : INTEGER factCount ← 0 emptyLineCount ← 0 OPENFILE OLD_FILE FOR READ OPENFILE NEW_FILE FOR WRITE WHILE NOT EOF(OLD_FILE) READFILE OLD_FILE, line IF line <> "" THEN WRITEFILE NEW_FILE, line factCount ← factCount + 1 ELSE emptyLineCount ← emptyLineCount + 1 ENDIF ENDWHILE CLOSEFILE OLD_FILE CLOSEFILE NEW_FILE OUTPUT factCount, " facts written and ", emptyLineCount, " empty lines removed"

11

Random Joke

Create a program that reads all 12 jokes into a 2D array, with the 1st column containing the question and the 2nd column containing the answer/joke (note: if the joke consists of just a single line, then the 2nd line in the file has been left blank)

The user should then be continually asked how many jokes they want to hear - entering 0 or less should result in the program terminating

DECLARE jokes : ARRAY[1:12, 1:2] OF STRING DECLARE jokeNum, jokeAmount, randIndex : INTEGER DECLARE line : STRING OPENFILE Jokes.txt FOR READ FOR jokeNum ← 1 TO 12 READFILE Jokes.txt, jokes[jokeNum, 1] READFILE Jokes.txt, jokes[jokeNum, 2] READFILE Jokes.txt, line NEXT jokeNum CLOSEFILE Jokes.txt REPEAT OUTPUT "How many jokes would you like to hear?" INPUT jokeAmount FOR jokeNum ← 1 TO jokeAmount randIndex ← 1 + ROUND(RANDOM() * 11, 0) OUTPUT jokes[randIndex, 1] IF jokes[randIndex, 2] <> "" THEN OUTPUT jokes[randIndex, 2] ENDIF OUTPUT "---" NEXT jokeNum UNTIL jokeAmount <= 0 OUTPUT "Goodbye..."

12

Text Editor

Create a text editor/notes app that asks you to enter a filename and whether you want to read, write or append from/to it. An exit option should be provided too

If they choose to read, all lines should be displayed

If they choose to write/append, they should be prompted with the line they want to write

DECLARE fileName, line : STRING DECLARE menuChoice : CHAR REPEAT OUTPUT "Please choose an option: r: read file w: write file a: append file q: quit" INPUT menuChoice menuChoice ← LCASE(menuChoice) CASE OF menuChoice 'r': OUTPUT "Enter filename to read from:" INPUT fileName OPENFILE fileName FOR READ WHILE NOT EOF(fileName) DO READFILE fileName, line OUTPUT line ENDWHILE CLOSEFILE fileName 'w': OUTPUT "Enter filename to write to:" INPUT fileName OPENFILE fileName FOR WRITE OUTPUT "Enter line to write" INPUT line WRITEFILE fileName, line CLOSEFILE fileName 'a': OUTPUT "Enter filename to append to:" INPUT fileName OPENFILE fileName FOR APPEND OUTPUT "Enter line to append" INPUT line WRITEFILE fileName, line CLOSEFILE fileName 'q': OUTPUT "Goodbye..." OTHERWISE: OUTPUT "Invalid menu option..." ENDCASE UNTIL menuChoice = 'q'

13

Capital City Quiz

Create a program that reads the csv file into a 2D array to store the 248 countries and capital cities

The program should choose 10 random countries and ask the user to name the capital city - an appropriate message should be displayed depending on if they were right or wrong

Their overall score should be output at the end

DECLARE info : ARRAY[1:248, 1:2] OF STRING DECLARE countryNum, charIndex, questionNum, randCountryIndex, score : INTEGER DECLARE line, country, capital, userAnswer : STRING OPENFILE CapitalCities.csv FOR READ countryNum ← 0 READFILE CapitalCities.csv, line WHILE NOT EOF(CapitalCities.csv) DO READFILE CapitalCities.csv, line countryNum ← countryNum + 1 charIndex ← 1 WHILE MID(line, charIndex, 1) <> ',' DO charIndex ← charIndex + 1 ENDWHILE info[countryNum, 1] ← MID(line, 2, charIndex - 3) info[countryNum, 2] ← MID(line, charIndex + 2, LENGTH(line) - charIndex - 2) ENDWHILE CLOSEFILE CapitalCities.csv score ← 0 FOR questionNum ← 1 TO 10 randCountryIndex ← 1 + INT(RAND(248)) OUTPUT "Question ", questionNum, ") What is the capital of ", info[randCountryIndex, 1] INPUT userAnswer IF TO_LOWER(userAnswer) = TO_LOWER(info[randCountryIndex, 2]) THEN OUTPUT "✅ Correct - the capital of ", info[randCountryIndex, 1], " is ", info[randCountryIndex, 2] score ← score + 1 ELSE OUTPUT "❌ Incorrect - the capital of ", info[randCountryIndex, 1], " is ", info[randCountryIndex, 2] ENDIF NEXT questionNum CASE OF score 10: OUTPUT "Incredible - you scored 10/10" 7 TO 9: OUTPUT "Well done - you scored ", score, "/10" 4 TO 6: OUTPUT "Not bad - you scored ", score, "/10" 1 TO 3: OUTPUT "Unlucky - you scored ", score, "/10" ENDCASE

14

Periodic Table (RANDOM)

Note: this question is for A2 - if you're in AS, you can skip it

Create a program that stores basic information (name, symbol, atomicNumber, mass, isMetal etc) about several chemical elements - these should be written to a file

Then create another program that allows the user to case-insensitively search chemical elements by name and displays the chemical's details - an empty input should terminate the program

TYPE Element DECLARE name, symbol : STRING DECLARE atomicNumber : INTEGER DECLARE mass : REAL DECLARE isMetal : BOOLEAN ENDTYPE DECLARE elements : ARRAY[1:2] OF Element elements[1].name ← "Hydrogen" elements[1].symbol ← "H" elements[1].atomicNumber ← 1 elements[1].mass ← 1.008 elements[1].isMetal ← FALSE elements[2].name ← "Uranium" elements[2].symbol ← "U" elements[2].atomicNumber ← 92 elements[2].mass ← 238.03 elements[2].isMetal ← TRUE DECLARE pos : INTEGER OPENFILE PeriodicTable.dat FOR RANDOM FOR pos ← 1 TO 2 SEEK PeriodicTable.dat, pos PUTRECORD PeriodicTable.dat, elements[pos] NEXT pos CLOSEFILE PeriodicTable.dat

Program to read file and let user search for elements:

TYPE Element DECLARE name, symbol : STRING DECLARE atomicNumber : INTEGER DECLARE mass : REAL DECLARE isMetal : BOOLEAN ENDTYPE DECLARE elements : ARRAY[1:2] OF Element DECLARE pos : INTEGER OPENFILE PeriodicTable.dat FOR RANDOM FOR pos ← 1 TO 2 SEEK PeriodicTable.dat, pos GETRECORD PeriodicTable.dat, elements[pos] NEXT pos CLOSEFILE PeriodicTable.dat DECLARE searchName : STRING REPEAT OUTPUT "Enter element to find:" INPUT searchName DECLARE found : BOOLEAN found ← FALSE searchName ← TO_LOWER(searchName) pos ← 1 WHILE found = FALSE AND pos <= 2 IF searchName = TO_LOWER(elements[pos].name) THEN OUTPUT "--- Element Details ---" OUTPUT "Name: ", elements[pos].name OUTPUT "Symbol: ", elements[pos].symbol OUTPUT "Atomic Number: ", elements[pos].atomicNumber OUTPUT "Mass: ", elements[pos].mass OUTPUT "Metal: ", elements[pos].isMetal found ← TRUE ENDIF pos ← pos + 1 ENDWHILE IF found = FALSE THEN OUTPUT "Element ", searchName, " not found" ENDIF UNTIL searchName = ""

The following are some example files you can use throughout these challenges - you may also want to use a list of English words to try some interesting tasks too