<jupyter_start><jupyter_text>IntroductionThis is a course/reference for CAIE Computer Science Problem-solving and Programming, meant to help you understand the content alongside the textbooks. I try to cover most of the syllabus directly related to Python programming, trying to provide an alternate explanation where possible. I also talk about some parts (like expressions, and the `range()` function) which are sometimes not well explained by textbooks.This reference is interactive—you can try out the examples as soon as you read the explanation, without having to launch an IDE. The expetcation is that you use this and you textbooks to get an overview of the concepts, and then dig in by researching—I'll provide some links where appropriate.  0: Jupyter NotebooksWhat you're reading right now is called a [Jupyter](https://jupyter.org/index.html) Notebook. It allows me to write documentation (explanation), place graphics/diagrams, and write executable Python code in the same place. You can execute my code, edit it, or just create a new cell and experiment.\[Over\]Simply put, a Jupyter notebook is a file with various cells. A cell is a block of code, explanation (like this one), or a multimedia element. To execute a cell, click on it to select it, and press **Ctrl + Enter** (or **Cmd + Return**). Go ahead, and try it with the cell below—you should get a message telling that you succeeded. If you already know Python programming, you can edit that code too!<jupyter_code>print("You successfully executed a cell. Congratulations!")<jupyter_output><empty_output><jupyter_text>1: TagsEach cell has one or more tags. If it's visible, you should see the words `Meta` and `Jupyter` written in two small boxes on the top-left of this cell, and you don't have to do anything. If not, click **View > Cell Toolbar > Tags**.Tags tell you which syllabuses a given cell pertains to, or otherwise describes the content. For example, this cell has two tags:* `Meta` This cell is not about any syllabus-specific content, and just describes something about the course.* `Jupyter` This cell describes how to use Jupyter or its features.Here is a full list of tags you'll encounter.| Tag | Explanation ||:-|:-|| `IGXXXX` | This represents a [CAIE IGCSE syllabus](https://www.cambridgeinternational.org/programmes-and-qualifications/cambridge-upper-secondary/cambridge-igcse/subjects/https://www.cambridgeinternational.org/programmes-and-qualifications/cambridge-upper-secondary/cambridge-igcse/subjects/), with syllabus code XXXX. You must understand this content. || `ALYYYY` | This represents a [CAIE AS & A Level syllabus](https://www.cambridgeinternational.org/programmes-and-qualifications/cambridge-advanced/cambridge-international-as-and-a-levels/subjects/https://www.cambridgeinternational.org/programmes-and-qualifications/cambridge-advanced/cambridge-international-as-and-a-levels/subjects/), with syllabus code YYYY. You must understand this content. || `General` | This is something not explicitly mentioned by the syllabus, but is common knowledge amongst programmers. Knowing this may help you get a better grasp of the syllabus. || `Extended` | Content that might be interesting or useful, but not explicitly covered by the syllabus. You should avoid using this on exams. || `Used` | May appear with some `General` or `Extended` topics. You can use these in your exams. || `Meta` | These are not content about programming itself, instead it may explain something about my writing style, or help getting around Jupyter. || `Jupyter` | May appear with some `Meta` tags. These are about the Jupyter environment. |  2: Installation?If you don't want to edit my code and save those changes, then you don't have to install anything—you can keep coming back to this notebook in your browser using Binder.However, if you would like to experiment, create notebooks, or want more customizability, install Jupyter. I'll give you a simple method below, but visit the [official website](https://jupyter.org/install.html) for more detail about Anaconda and other methods.This method uses a command line. While you can just follow these instructions, it might be worthwhile understanding what's going on. If any of this is unfamiliar, take a look at my [advice for programming learners]().1. If you don't already have it, download and install Python from the its [website](https://www.python.org/downloads/). Make sure you get version 3.3 or newer.    * If you need to check whether you have Python, open a command line, type `python --version`, and press **Enter**.    * If you get a version number, you have Python; if not, you don't.2. Open up a command line.    * If you're on Windows, press **Windows + R** to bring up the run dialog box. Type `powershell` and hit **Enter**.    * If you're on MacOS, launch the application called **Terminal**.    * If you're on a Linux-based OS, I suppose you know what to do.3. Type in `pip3 install notebook` (try `pip install notebook` if that didn't work) and press **Enter**. If you're asked whether you want to continue, type `y` for 'yes' and press **Enter**. Wait for the installation to complete.To launch Jupyter, open a command line, and go to the folder you want to save your files in. Type in `jupyter notebook` and press enter.  3: Learn moreNow that you've had a basic introduction to Jupyter, we can jump into the lesson.If you're interested in learning more, I'll leave links to some sources below:* [Anaconda](https://docs.anaconda.com/anaconda/)* [Jupyter demo and more detailed overview](https://mybinder.org/v2/gh/ipython/ipython-in-depth/master?filepath=binder/Index.ipynb)* [Guide to using Jupyter](https://www.dataquest.io/blog/jupyter-notebook-tutorial/)* [Installing Jupyter Notebook and more](https://jupyter.org/install.html)* [Jupyter documentation](https://jupyter-notebook.readthedocs.io/en/stable/index.html)  4: My sourcesMost of the content here is about explaining skills, which I've acquired over time through practice and reverse-engineering others' examples. It would not make sense for me to cite these. However, for trasparency, I'll do my best to explain where I got information from.I sometimes refer sources online, for things like definitions, and I'll make sure to leave you a hyperlink to these (though it may take the form of suggested further reading).A lot of what I know also comes from the following:* My computer teacher at school, who taught me Python and a lot of rigour* My robotics mentor, who introduced me to programming through C++* Random YouTube vidoes \([my recommendations](https://eccentricorange.github.io/CAIE-Computer-Science/yt-channels.html)\)* [My A Level texboook](https://www.hoddereducation.co.uk/subjects/ict/products/general/cambridge-international-as-a-level-computer-sc-(3))* CAIE syllabus specifications: I look up individual topics as per these documents.  Common terminology and useful conceptsEveryone hates jargon—especially when it is presented in a clump like this. However, it can be helpful to be familiar with it, for two reasons.1. **Tutorials and guides are written using jargon:** as much as it can make things difficult for people starting out, discussing about concepts using their names actually makes things easy. Although many tutorials and guides use these terms to convey information—which is why we discuss jargon here—there are several videos and guides created with a beginner in mind, so don't be intimidated if you cannot remember these! Just follow along with the rest of this guide (practice and experiment a little bit too!), and concepts will gradually be clarified—you will find that there isn't a need to know every term and its definition verbatim, rather it's the understanding that's important.2. **Examinations use jargon:** questions on the A Level papers often use it to frame the question, and candidates are also expected to be familiar with it.  1: CommentsIt is useful to be able to write something in plain text alongside executable code. We tell the computer that something is a comment through symbols—these have been allocated specifically for comments in the design of translator (compiler/interpreter).There are two basic ways a comment can be defined: it can be either one line long, or it can span multiple lines. Usually, a single-line comment is only opened (preceded) using a symbol; a multi-line comment must be opened (preceded) and closed (succeded) with symbols. Some common ways are listed below. Python, R, Windows PowerShell, PHP```python This is a single-line commentprint ("Hello World!")   This is also a single-line comment Even this is this a single-line comment!```**Note:** Sometimes, people write multi-line comments in Python using triple double quotes, but this is in fact a string literal. You can use it in programming (althoug it's not good practice, and should be avoided on exams). See the cell below. C++, Java, JavaScript```cpp// This is a single-line commentprintf ("Hello World!");  // This is also a single-line comment// // Even this is this a single-line comment!/* This is a multi-line comment. It is writtenspecifically to waste your time. Your are readingthis because you want to learn about comments */``` SQL, MySQL```mySQL-- This is a commentSELECT * FROM Students; -- This is also a comment-- -- -- Even this is a comment!/* This is a multi-line comment. It is writtenspecifically to waste your time. Your are readingthis because you want to learn about comments */```In CAIE pseudocode, single-line comments must be preceded with `//` (like C++); multi-line comments are not allowed.<jupyter_code>### Multi-line comments

"""This is like a multi-line comment.
Python will ignore it.
It will not be executed."""

message = """This is string constant.
Python will not ignore it.
Do with it what you like."""

print(message)<jupyter_output><empty_output><jupyter_text>2: ConstructsPrograms are designed using common building blocks. These building blocks, known as programming constructs (or programming concepts), form the basis for all programs. You may notice two different uses of the term construct; these are given explicitly below:1. **Common concepts:** some concepts that help structure a program are called constructs. In some sense, this is the *correct* meaning of a construct, and it is what you will come across during you A Level course.2. **Creating "sections":** many statements are often placed inside another set of statements, i.e., some statement will **open** a construct, and some other statement will **close** it. Anything inside that section will behave according to the rules of the construct. Consider the example of a **selection** construct below. It would ask you to enter a value—type a name, and press Enter.<jupyter_code># take a value from the user
name = input("Enter a name: ")

# initialize the if construct
if name == "<NAME>":

    # the indentation of four spaces lets the interpreter know that any statement following it is to be executed inside the if construct
    print ("The entered name is of the inventor of Python.")
    print ("This is another print statement.")

# since no spaces precede this statement, it must execute independently of the if construct. It indicates that the if construct is now closed and cannot be opened again
print ("Hello world!")<jupyter_output><empty_output><jupyter_text>3: Expressions, and returningExpressions are parts of statement that evaluate to something and return it. This is easiest to understand by example, so consider the statement below:<jupyter_code>print ("My name is <NAME>")<jupyter_output><empty_output><jupyter_text>Here, the `print()` statement expects something to be placed in the parenthesis—this is what `print()` acts on. We placed a **literal** statement into the parenthesis, and that is what is printed. However, we could just as well have placed something a little more complex, as int the examples below.<jupyter_code>print ("My name is" + ' ' + "<NAME>")
print ("The product of 2 and 3 is", (2 * 3))<jupyter_output><empty_output><jupyter_text>Notice how these are no longer simple values—some calculation (or some other process if you will) must take place before the `print()` statement can actually act on these. The parts of the statements inside the parenthesis (`"My name is" + ' ' + "<NAME>"` and `"The product of 2 and 3 is", (2 * 3)`) are called **expressions**. When the line of code containing them gets executed, the expression is evaluated (calculated), and the final value is **returned** to the whatever contains the expression (in this case, the `print()` statement gets the final value). This will, hopefully, be clearer when you learn about **functions**, whose whole job it is to handle returning and passing values.**Note:** if you decide to research this topic online (which is always encouraged), you may find the term **regular expression**, often abbreviated to **regex**. This is not part of your syllabus and should not be confused with the general meaning of the term expression. In fact, [regular expressions](https://www.w3schools.com/python/python_regex.asp) are usually considered to be a rather advanced topic.  4: IdentifiersThe memory of a computer actually consists of several (example: 4 294 967 296, or $2 ^ {20}$ unique locations for a 32-bit computer with 4 GiB RAM) memory locations, each with a unique hexadecimal address. Programming languages allow "names" to be assigned to these memory locations in order to access them, greatly simplifying work for a programmer. In fact, high-level languages detect when a "name" has been used, and automatically allocate a memory location to it—they manage all the addressing under the hood, and the programmer works only with the "names" they declare (yes, you can christen 4 294 967 296 places on a computer).However, memory locations for holding values aren't the only thing that can be given "names". Blocks of code (sub-routines) which can be called with just one line of code, several memory blocks at once, and much more can be given a "name". The general term for this "name" is an **identifier**. The table gives some common uses of identifiers (don't worry if you don't understand these—they will be covered in the two notebooks about sections 2 and 4).| Structure | Use || :-- | :-- || variable | single memory location into which can hold a value || array/list | series of consecutive memory locations, accessed using the identifier and a unique number || class | a template for creating identifiers (A Level P4) || sub-routine | name given to a set of instructions that will be called with the identifier |  2.1: Algorithm design and problem-solving  2.1.1: AlgorithmsAn algorithm is a solution to a problem expressed as a sequence ofdefined steps.An algorithm may be documented using any of the following:* Structured English* Flowcharts* Pseudocode  1: Identifier tablesAn identifier table should be used in planning. It should list the identifier, its datatype and its purpose.| Identifier | Datatype | Purpose || :-- | :-- | :-- || `_pi` | `REAL` | constant $\pi = 3.1415$ || `radius` | `REAL` | variable for user input radius || `area` | `REAL` | variable for the area of the circle |  2: Basic constructsMany algorithms consist of the four basic constructs of programming.* **Assignment:** storing a value, either a *literal* value or an *expression* that returns a value, in a variable.* **Sequence:** program statements may execute consecutively, or in parallel alongside eachother. The sequence need not be known ahead of time and can be determined programmatically.* **Selection:** programs can determine which statements are to be execute based on conditions (using `IF` and `CASE` structures, for example).* **Repetition:** statements in a program can be repeated either a fixed number of times, or based on a condition.Simple algorithms follow the **input-process-output cycle** at various places, i.e., they take in an input, perform some processing on it, and then return it back. For example, consider an algorithm which takes in the radius `r` of a circle as its input. It then multiplies the value with $2 \pi$ (this is the processing). Finally it outputs the product `2 * _pi * r` (the expression $2 \pi r$) which is the length of the circumference of the circle.  3: Decomposition, stepwise refinement and pattern recognition**Decomposition** is the process of breaking down a complex process into smaller parts. The process can be broken down repeatedly until the sub-tasks can be performed by small and manageable **procedures** or **functions**; this process is called **stepwise refinement**. Throughout this process, **pattern recognition** is used to identify those parts which are similar and could use the same solution.  2.3: Programming  2.2.1: Data types 1: The typesSome examples of the **atomic** data types commonly used in program are given below. Atomic datatypes are the fundamental datatypes, and cannot be decomposed into other types.**Note:** the names of most datatypes (integers, float, strings et cetera) are used as common nouns, and begin with a lowercase. However, the Boolean datatype is named after the mathematician [George Bool](https://en.wikipedia.org/wiki/George_Boole), so it often begins with an uppercase 'B' when discussing in textbooks or other literature. However, when programming, you should follow the syntax of the language you're working with. In Python 3, we write `bool()` to force conversion.<jupyter_code># Common names
UnsignedWholeNumber = 32
SignedWholeNumber = -128
RealNumber = 3.1415
Boolean = True
Character = 'T'
String = "Hello World!"
# Python declarations
UnsignedWholeNumber = int(32)
SignedWholeNumber = int(-128)
RealNumber = float(3.1415)
Boolean = bool(True)
Character = str('T')
String = str("Hello World!")
### Checking the datatype of an expression
print(type(1))
print(type('1'))
print(type("1"))
print(type(True))
print(type('True'))
print(type(3.14))
print(type("3.14"))

# NOTE:  You'll see that the output has the term "class".
#        This is a topic from paper 4 (A2), and it's 
#        quite interesting—you can define your own datatypes!
#        It also makes it much easier to manage large projects.
#        Keyword for further research: Object Oriented Programming<jupyter_output><empty_output><jupyter_text>2: Converting between datatypesIt is often important to convert between datatypes. For example, if you have to print (output) a numerical value, it's good practice to explicitly convert it to a string, because that allows you to perform string manipulation on it.Here's the general syntax:```pythondatatype(expression) -> datatype```You'll find yourself using this a lot with `print()` and `input()` statements. It's common to convert to and from a string.<jupyter_code>### Example of conversion
### Here we convert various datatypes to an integer
print(type(int(1)))
print(type(int('1')))
print(type(int(1.1)))
### Example of conversion
### Here we convert various datatypes to an string
print(type(str(1)))
print(type(str('1')))
print(type(str(1.1)))<jupyter_output><empty_output><jupyter_text>2.3.1: Basics  1: Declaration of variables and constantsAll variables and constants used in a program must be declared explicitly. In Python, it is usual to write the data type using a **comment**, as declarations without the name of the type are legal.**Note:** Python 3 doesn't really support constants, although [hacky workarounds](https://stackoverflow.com/questions/2682745/how-do-i-create-a-constant-in-python) do exist. By convention, you can begin a variable name with an underscore `_` to signify to fellow programmers that a value is a constant. However, this doesn't stop them from assigning a value to it.<jupyter_code>#### Declare and initialize a variable
counter = 0     # INTEGER

### Declare and initialize a constant
_pi = 22 / 7    # REAL<jupyter_output><empty_output><jupyter_text>Re-assignmentAs their name suggests, you can change the value of an identifier once you declare it. However, in many languages, you cannot change the *datatype* of an identifier once it's assigned. Such languages are said to be **strongly typed**. For example, with C++, if you make an `int` variable, you cannot convert it to a `float` or a `char` (character) later.```c++// C++ example 1bool variable1 = true;  // variable is now of type Booleanvariable1 = not true;  // this is a valid statementvariable1 = false; // this is also a valid statement``````c++// C++ example 2bool variable2 = true;  // variable is now of type Booleanint variable2 = 6;  // this would raise an exception during compilationchar variable2 = 'x'; // this would also raise an error during compilation```Some (often interpreted) languages, like Python and JavaScript, are said to be **loosely typed** or **weakly typed**. If you assign a value to an identifier, you can assign a different type later.Further reading:* [Wikipedia](https://en.wikipedia.org/wiki/Strong_and_weak_typing)* [Medium article](https://medium.com/android-news/magic-lies-here-statically-typed-vs-dynamically-typed-languages-d151c7f95e2b)<jupyter_code>### Re-assigning values of the of the same type
variable1 = 1
print (type(variable), variable)

variable1 = 34
print (type(variable), variable)

variable1 = 7
print (type(variable), variable)

variable1 = 1550
print (type(variable), variable)
### Re-assigning values of the of different types
variable2 = 1
print (type(variable), variable)

variable2 = 3.14159
print (type(variable), variable)

variable2 = "Hello"
print (type(variable), variable)

variable2 = False
print (type(variable), variable)<jupyter_output><empty_output><jupyter_text>2: Perform input and output (interact with the user)It is often essential for programs to interact with the user. There are two basic ways this can be done: by asking for a value using the `input()` function, and giving an output using the `print()` function.General syntax:```pythoninput("prompt") -> strprint("message")```When taking an input from a user, it is usual to force **type conversion**, i.e., convert the `STRING` data **returned** by the `input()` function into whatever type is required. Suppose a user enters the number five hundred and thirty seven: the `input()` statement returns a `STRING` that has the characters `'5'`, `'3'` and `'7'`, not the mathematical value $(5 \times 100) + (3 \times 10) + (7 \times 1) = 537$; this string cannot be processed mathematically. In general, any value (and not just from `input()`) can be converted to another datatype. See the general format below:```python Input a string (no conversion)identifier = input("prompt message: ") Input a different value (convert)identifier = datatype(input("prompt message: "))```<jupyter_code>### Input a value from a user
name = input("Enter your name: ")       # python allows the prompt and the input statements to be handled in one line

### Output a value
print ("Your name is", name)
### Input a numeric value
base = int(input("\nEnter the number you want to square: "))  # the int() command has been used to force type conversion to INTEGER; STRING type would be considered otherwise
square = base ** 2.0

### Output a value
print("The square of", base, "is", square)<jupyter_output><empty_output><jupyter_text>The `end` value in the output statementThe print statement allows us to specify an optional `end` argument, which describes what is printed after a string—if nothing is specified, a newline `'\n'` is printed. This is useful in printing repeatedly (such as inside a loop).<jupyter_code>### Demo without the end character
print ("This is one line")
print ("This is another line")
### Demo with the end character as a space
print ("This", end = ' ')
print ("is", end = ' ')
print ("one", end = ' ')
print ("line")  # print a blank line after the message
### Demo with the end character as a comma followed by a space
print ("You need to get: ", end='')     # end can also be empty
print ("bananas", end = ", ")
print ("oranges", end = ", ")
print ("and apples", end = '.')<jupyter_output><empty_output><jupyter_text>2.3.3: SelectionSelection statements are almost solely responsible for allowing programmers to write any "smart" programs. They allow statements to be executed selectively, based on a condition, or a set thereof.  0: Logic statementsConditions are often specified using Boolean logic statements, which are expressions that return a `TRUE` or `FALSE`. Logic statements are essential because they let a program control execution (using loops, selection statements, and conditional loops).Logic statements often use comparison symbols—it is a good idea to be familiar with these:| Symbol | Purpose | Use || :-: | :-- | :-- || `==` | checks whether two values are equal | `a == b` returns `True` if `a` and `b` have the same value || `!=` | checks whether two values are unequal | `a != b` returns `True` if `a` and `b` do not have the same value || `>` | checks one value is greater than the other | `a > b` returns `True` if `a` is greater than `b` || `<` | checks one value is lesser than the other | `a < b` returns `True` if `a` is lesser than `b` || `>=` | checks one value is greater or equal than the other | `a >= b` returns `True` if `a` is greater than `b`, or if `a` and `b` have the same value || `<=` | checks one value is lesser or equal than the other | `a <= b` returns `True` if `a` is lesser than `b`, or if `a` and `b` have the same value || `and` | checks whether two conditions evaluate to `True` | `a and b` returns `True` if `a` and `b` both evaluate to `True` at the time of the comparison || `or` | checks whether at least one of two conditions evaluate to `True` | `a or b` returns `True` if at least one of `a` and `b` evaluate to `True` at the time of the comparison || `not` | negates a logic statement | `not a` returns `True` if `a` is `False` || `in` | checks for an element in a list | `a in b` returns `True` is element `a` is present in the list `b` |The general syntax for using logic statement is:```pythonexpression1 operator expression2 -> bool``` `expression1` and `expression2` are two expressions that return a Boolean value. They can themselves be made of logic statements. The statements `in` and `not`, however, are exceptions to this rule, and we'll discuss how to use them by example.<jupyter_code>### Demonstrating comparision
print(1 == 1, 2 == 1)
print(0 > 1, 2 > 1, 1 > 1)
print(0 >= 1, 2 >= 1, 1 >= 1)
### Demonstrating combining
print(True and True, False and True, False and False)
print(not True, not False)

print((1 == 1) and (2 == 1))
print((1 == 1) or (2 == 1))
print(not (1 == 2))
### Demonstrating 'in' (see Arrays below)
print(1 in [7, 6, 1, 8, 2, 3, 5])
print(9 in [7, 6, 1, 8, 2, 3, 5])
print(1 not in [7, 6, 1, 8, 2, 3, 5])

print('A' in "Apple")
print('B' in "Apple")
print('A' in 'A')
print('B' in 'A')<jupyter_output><empty_output><jupyter_text>1: If-else statementsIn its simplest form, an `IF` construct may be thought of as a "switch" that determines whether or not a block of code gets executed. Consider that a block of program code called `trueStatements` is placed in the `IF` section of the statement. An **expression** `condition()` that returns a `BOOLEAN` value is used to determine how the construct behaves. Then `trueStatements` are executed only if `condition()` evaluates to `TRUE`. For example, a child is punished only if they conduct mischief; their parents just ignore the concept of punishments otherwise.An extremely useful property is the optional `ELSE` construct. This contains a block of code (let's call it `falseStatements`) that would execute *instead of `trueStatements`* if `condition()` evaluated to `FALSE`. Continuing the analogy with particularly generous parents, the child would be rewarded every time they do *not* conduct mischief but punished otherwise.**NOTE:** It is possible to **nest** `IF ELSE` statements, i.e., execute one inside another. This is also true of most common constructs.<jupyter_code>### Input an integer from the user
integer = int(input("Enter an integer: "))

### Compare the integer with 1023
if integer == 1023:
    ## Block of statements to execute if the condition is true
    print ("You entered the largest 10-bit unsigned integer!!")

    ## Nothing happens if the integer was not 1023 (i.e., the condition was false)
### Input the user's favourite subject
favouriteSubject = input("\nEnter your favourite subject: ")

### Compare the favourite subject with Computer Science
if favouriteSubject == "Computer Science":
    ## Block of statements to execute if the condition is true
    print (":)")

else:
    ## Block of statements to execute if the condition is false
    print (":(")
### Input the user's favourite subject
favouriteSubject = input("\nEnter your favourite subject: ")

### Compare the favourite subject with Computer Science
if favouriteSubject == "Computer Science":
    ## Block of statements to execute if the condition is true
    print (":)")

else:
    ## Block of statements to execute if the condition is false
    ## This just happens to be another if-else statement

    ## The nested condition here uses the same variable as the parent
    ## if condition, but this is not at all necessary; both the if
    ## conditions are completely independent.
    if favouriteSubject == "Further Mathematics":
        ## Block of statements to execute if the nested condition is true
        print ("You may enjoy trying out Computer Science.")
    
    else:
        ## Block of statements to execute if the nested condition is false
        print (":(")<jupyter_output><empty_output><jupyter_text>2: Case statementsThese also let instructions to be executed selectively, based on the value of an expression (let's call it `caseExpression()`). However, the `caseExpression()` can return any **atomic data type** and not just Boolean. The programmer must allocate different values for each value of `caseExpression()` they can anticipate; each value and its corresponding statement is called a **case**. For example, a sub-routine returns the name of a weekday given its number (where Monday is 1). `CASE` structures allow an `OTHERWISE` case to be defined to handle unexpected values (day number 16, for example).Python 3 does not natively support `CASE` structures, but an extension of the if-else statements allows case structures to be implemented. Apart from the Boolean `if` and `else` statements, Python supports `elif` statements (this is equivalent to `else if () { }` in some languages like C++). Using this approach, each `elif` and a single `if` statement form the cases the programmer must explicitly program; the `else` statement forms the `OTHERWISE` case the program needn't care to program.<jupyter_code>### Input a number for the day of the week
dayNumber = int(input("Enter the number of the day: "))


### Use if-elif-else statements to output the name of the day.
  # The statements from dayNumber = 1 to dayNumber = 7 correspond
  # to one weekday each.
if dayNumber == 1:
    print("Monday")

elif dayNumber == 2:
    print("Tuesday")

elif dayNumber == 3:
    print("Wednesday")

elif dayNumber == 4:
    print("Thursday")

elif dayNumber == 5:
    print("Friday")

elif dayNumber == 6:
    print("Saturday")

elif dayNumber == 7:
    print("Sunday")

### This is used to handle an erroneous value
else:
    print("The value", dayNumber, "does not correspond to a weekday.")<jupyter_output><empty_output><jupyter_text>2.3.4: Iteration**Loops** allow blocks of code to be repeated, and are another crucial construct. There are two basic categories of loops:1. **Count controlled loops:** for loops2. **Condition based loops:** while loops  Count controlled loopsThe number of **iterations** that have to occur has to be specified in advance. A common example is a `FOR` loop. Usually, a variable called a **counter** is declared with some initial value (often 0), and with each **pass** of the loop, some mathematical operation is performed on it (often adding 1, aka **incrementing**); if a constant value is added/subtracted, the value is called the **step**. The value of the counter can be accessed like any other identifier. This readily available number, that goes through a pre-determined number of values, makes `FOR` loops a natural choice in many applications involving **arrays**. A `FOR` loop terminates when some condition, related to the counter, is met. For example, a loop to print out the marks students of a class with 25 students scored, would start with the counter set to 0, increment it after each pass (after printing a mark) and then terminate when the counter is equal to 24.<jupyter_code>### Output ten consecutive integers using a "normal" for loop
for counter in range(10):       # we can call the identifier counter as it behaves like a counter in this case
    print (counter, end = ' ')
### Use a counter to traverse an array
Fruits = ["Orange", "Papaya", "Apple", "Banana", "Plum"]    # declare a list

for counter in range(5):                # initialize the loop to step through each fruit
    print (Fruits[counter], end = ' ')  # now we must manually index the array<jupyter_output><empty_output><jupyter_text>The `range()` functionIn Python 3, the `for` loop does not obey a counter. Instead, `for` loops always step through the elements of a list, and it is possible to avoid manually indexing an array if all of its contents have to accessed in the order they are available: the identifier previously referred to as a counter would just return the array element corresponding to the current pass. Having written so, Python 3 does offer a function `range(n)` that returns a list with `n` consecutive integers starting at zero—a `for` loop can use this to control its iterations. `range()` allows the initial value, final value and step to be controlled, and the it is encouraged to explore this.You can try to print the values of `range()`, as below, to see how it behaves.<jupyter_code>### Demonstrate ranges
print(range(10))
print(range(4, 10))
print(range(4, 10, 2))
### Traverse an array using a for loop
Fruits = ["Orange", "Papaya", "Apple", "Banana", "Plum"]    # declare a list

for fruit in Fruits:            # initialize the loop to step through each fruit
    print (fruit, end = ' ')<jupyter_output><empty_output><jupyter_text>Condition based loopsThe number of times a loop executes need not be known in advance. Instead, the loop would terminate when some **condition** is met. The condition must be an expression that evaluates to a Boolean value. A **pre-condition loop** (a `WHILE` loop) runs while the expression evaluates to `TRUE` and terminates as soon as it evaluates to `FALSE`. A **post-condition loop** (a `REPEAT UNTIL` loop) until the expression evaluates to `TRUE`. As their names suggest, the condition is checked *before* the first iteration in a pre-condition loop, and it is possible that the loop *may not execute at all* if the condition is initially `FALSE`; the condition is checked *after* the first iteration in a post-condition loop, and it *must execute at least once*.Python 3 does not support post-condition loops; `WHILE` loops are the only condition loops that can be used.<jupyter_code>### Use a while loop to make sure a user enters a given value
### The loop would not terminate until you enter Computer Science.
### This is a technique called validation, and is often used
  # to check whether or not a value makes sense.
while favouriteSubject != "Computer Science":
    favouriteSubject = input("Enter your favourite subject: ")

print(":)\n")
### Make the validation more friendly
### We are leveraging the property that the loop may not
  # execute at all—if the value meets our criteria the first
  # time, we don't have to enter the loop
favouriteSubject = input("Enter your favourite subject: ")

while favouriteSubject != "Computer Science":
    print ("\nYou are lying about your favourite subject! Try again!")
    favouriteSubject = input("Enter your favourite subject again: ")

print(":)\n")<jupyter_output><empty_output><jupyter_text>2.3.6: Structured programmingProperly structured programs have benefits other than readability: they often execute more efficiently and **subroutines** are the backbone of **decomposition**. A subroutine can be thought of as a shortcut to a block of statements. If a block of statements defined inside a subroutine have to be executed, they don't have to written in their entirety; instead, they can be **called** using a single line of code. Subroutines allow code to be reused very efficiently (and allow lazy programmers  to exist) as a subroutine can be called in several places without having to rewrite in each instance. There are two basic concepts that determine how a subroutine is designed:* **Arguments**: It is possible to make a routine run on the basis of a value **passed** into it. For example, for a **function** that squares a number $x$ and returns $x^2$, $x$ is the argument that is passed into it. The function *cannot run* if this is not provided. In contrast, a subroutine that returns a random number, always between 0 and 1, needn't have any arguments.* **Return value(s)**: A **function** can **return** a value after execution. Continuing the example of $x^2$ from above, the function can return the value to the parent construct. This means that if it is placed inside another subroutine (such as the `print()` statement), the parent routine can access the value (the `print()` statement can print it); or if the function is assigned to an *identifier*, the value will be stored in that identifier. In contrast, a **procedure** does not return anything—it cannot be assigned to an identifier or placed inside a construct that expects a value. For example, the squaring subroutine might simply print the value out to the user—in this case it needn't return anything.There are two types of subroutines: a `PROCEDURE` and a `FUNCTION`. In both cases, the programmer must decide whether or not any arguments are required. The only difference between them is of returning values: a function *must return a value* and a procedure *cannot return a value*. While the distinction between a procedure and a function can be crucial when planning a project, they are defined almost identically in Python 3. If a subroutine has the `return` keyword to return a value, it is a function; else it is a procedure. Specifying arguments/**parameters** is similar too—if a parameter is specified in the **declaration**, the subroutine will accept arguments; else it will not.Further reading: [The difference between **parameter** and **argument**](https://stackoverflow.com/questions/1788923/parameter-vs-argument)<jupyter_code>### Declare a procedure that prints a greeting
def greet():    # the def keyword is used to declare any subroutine
                # the round brackets must be used with a subroutine
    print ("Hello World!")
    print ("Welcome to my first subroutine!")
    print ("It is boring, but the hope is that you enjoyed learning about it.")

### Run the procedure
greet()         # the name just has to be typed with the round brackets
### Declare a function
def returnPi():
    _pi = 22.0 / 7.0
    return _pi  # the return keyword is used to pass a value to the parent construct

### Run the function and pass its output to a print statement
print ("This is the value of pi (approx):", returnPi())  # the function behaves as a variable
### Declare a procedure with an argument
def greetName(name):    # the identifier in the round brackets will be the argument(s)
    print ("Hello from " + name + "!")
    print ("Welcome to your first subroutine!")
    print ("It is boring, but the hope is that you enjoyed learning about it.")

### Run the procedure
greetName("<NAME>")   # the value of the argument is inside the round brackets
### Declare a function with an argument
def areaCircle(radius):     # the identifier in the round brackets will be the argument(s)
    return (22.0 / 7.0) * (radius ** 2.0)   # the evaluation of an expression be returned directly, without using an identifier

### Run the function and pass its output to a print statement
r = float(input("Enter the radius for a circle: "))
print ("The area of a circle of", r, "units is", areaCircle(r), "square units.")
    # note that the output returned by a function may also be used directly without using an identifier
### Declare a function with arguments
def areaRectangle(length, breadth):     # the identifier in the round brackets will be the argument(s)
    return length * breadth   # the evaluation of an expression be returned directly, without using an identifier

### Run the function and pass its output to a print statement
l = float(input("Enter the length for a rectangle: "))
b = float(input("Enter the breadth for a rectangle: "))
print ("The area of a rectangle of", str(l) + 'x' + str(b), "units is", areaRectangle(l, b), "square units.")
    # note that the output returned by a function may also be used directly without using an identifier<jupyter_output><empty_output><jupyter_text>A **library** (called a **module** in Python) often allows you to use a number of pre-defined subroutines. Python ships with many modules built in (such as `array`, `time`, `random`, `pickle`—which you may have already used) but many, many more can be [installed using a command-line tool](https://packaging.python.org/tutorials/installing-packages/). Any time the `import` keyword is used, a module is being added to a program.<jupyter_code>### Import a library to generate a random number
import random

### Call a subroutine (function) from the library, and print its output
print(random.random())

### You can call all the subroutines present in that library
print(random.randint(0, 10))
print(random.randrange(10))<jupyter_output><empty_output><jupyter_text>2.3.5 Built-in functionsBesides basic constructs, datatypes, and data structures most programming languages also have multiple **functions** built into them. Using them can not only greatly accelerate development and execution speed, but also often avoid rather ugly code by not requiring the wheel to be reinvented.  1: Length of an objectIt is possible to extract the length of an object with a single function (it returns an integer). If a string is passed as the parameter, the number of characters is returned. If a list is passed, the number of elements is returned.<jupyter_code>### Get length of a string
pangram = "The quick brown fox jumped over a lazy dog."
print (len(pangram))
### Get the length of a 1D list
Fruits = ["Orange", "Papaya", "Apple", "Banana", "Plum"]
print (len(Fruits))
### Get the length of a 2D list
Marks = [[85, 79, 86], [81, 81, 95], [88, 96, 82], [83, 75, 96], [88, 84, 80]]
print (len(Marks))<jupyter_output><empty_output><jupyter_text>2: String manipulationThe `STRING` and `CHARACTER` datatypes have numerous applications, beyond handling interaction with the user. Many electronics communicate using strings (for example, a smartphone and a computer connected using USB), and many internal components also communicate with the CPU using strings (for example, the GPS modules in most smartphones output strings formatted using the [NMEA 1083](https://en.wikipedia.org/wiki/NMEA_0183) standard or one of it predecessors). Clearly, then being able to manipulate strings efficiently is an essential skill.**NOTE:** in Python 3, strings written inside double quotes `""` behave like lists—elements can be indexed, popped, deleted and used to iterate `for` loops. However, strings written in single quotes `''` usually behave like traditional strings, and string functions must be used to manipulate them.  ConcatenationOne of the most useful things that can be done with strings is joining multiple of them together, and this is what will be explored first. There are several interesting actions that can be done using string concatenation in Python 3, and some of these are demonstrated below (not all of these are possible in pseudocode). For concatenating using pseudocode, the syntax will usually be provided on the question paper.<jupyter_code>### Declare strings
string1 = "Hello"
string2 = "World"

### Concatenate inside a print statement
  # Notice in which case a space is added automatically, and in which case it isn't.
print (string1, string2)    # this only works inside a print statement
print (string1 + string2)   # this is general and can be used in any assignment, or passed as a value to anything else
### Concatenate with repetition
print ("I am sleepy!", end = ' ')
print ('Z' + ('z' * 5) + ('.' * 3))
### Concatenate other values when converting automatically
print ("2 times 3 is", (2 * 3))     # the INTEGER returned by (2 * 3) is converted to a string automatically when concatenating using commas
### Concatenate other values when converting explicitly
print ("2 times 3 is " + str(2 * 3))     # the concatenation using + signs only accepts strings, so we must ensure we are returning it a string<jupyter_output><empty_output><jupyter_text>It can be helpful to be familiar with some terms and characters that come up frequently in strings, and more can be explored from [W3 Schools](https://www.w3schools.com/python/gloss_python_escape_characters.asp).| Character(s) | Name | Purpose || :-- | :-- | :-- || `'\n'` | newline (ASCII linefeed) | has the effect of inserting a blank line || `'\t'` | tab | has the effect of a tab (four spaces in Python 3) || `'\b'` | ASCII backspace | deletes the character occurring before it || `'\n'` `'\t'` `' '` | whitespace | characters that do not appear but may have utility in aligning other characters || | case | capitalization of a string/character || | uppercase | capital (A, B, C...) || | lowercase | small/print (a, b, c...) || | delimiter | a given character used to separate strings; the comma is the delimiter  in a `.csv` file |Python 3 has a variety of functions, and some of the common ones are demonstrated in the cell below. Exploring [more](https://www.w3schools.com/python/python_ref_string.asp) is encouraged.<jupyter_code>### Initialize a string to start working with
testString = "The quick brown fox jumped over the lazy dog!"

### Print its characters one-by-one
for character in testString:
    print (character, end='')

print ("")

### This can also be used to index individual characters just like a string
print (testString[1] + testString[2] + testString[36] + testString[36] + testString[12] + testString[44])
### Initialize a string to start working with
greeting = "good Day, World!"

### Convert the first character 'g' to uppercase 'G'
print (greeting.capitalize())

### Invert the case of the string
print (greeting.swapcase())

### Convert all characters to uppercase, then lowercase
print (greeting.upper())
print (greeting.lower())
### Separate words using the new line
print ("\nEach\nword\nappears\non\na\nseparate\nline\n")

### Separate words using a tab
print ("Some\twords\tappear\tfurther\tapart\tthan\tothers\n")
### Split words based on a delimiter automatically
  # and extract them into a list
fruitsString = "Orange;Papaya;Apple;Banana;Plum"    # the delimiter here is the semicolon
Fruits = fruitsString.split(';')                    # extract
print (Fruits, '\n')                                # print to test
### Declare a string without whitespace
  # The length of this string will be observed
  # because it is easier to observe that here as
  # whitespace characters are not visible by definition
testString = "ABCD"
print(testString)
print (len(testString))

print ("")

  # Add whitespace to both ends
testString = "ABCD"
testString = "     " + testString + "   "
print(testString)
print(len(testString))

print ("")

  # Trim whitespace on the left side only
testString = "ABCD"
testString = "     " + testString + "   "
print(testString.lstrip())
print(len(testString.lstrip()))

print ("")

  # Trim all whitespace
testString = "ABCD"
testString = "     " + testString + "   "
print(testString.strip())
print(len(testString.strip()))<jupyter_output><empty_output><jupyter_text>3: FormattingIt is often necessary to **format** numbers (or anything else actually), usually either for presenting them to an end user or for some electronics (as described above). For example, a number may have to be formatted to a specific number of **significant digits**, or be aligned in some direction, or be **padded** (start) with zeros so that its length is fixed—far too many formatting options for all to listed here, but more details can be found at [pyformat.info](https://pyformat.info/number).In Python 3, numbers (and other data) are formatted using the `.format()` **method**. Consider an example: a message has to be printed to the user, and it must have the value returned by a function formatted into **scientific notation**. The programmer can place curly brackets with an identifier `"{identifier}"` within the message, and then follow it by `.format()`—the value(s) are passed to the method as arguments (given below in a generalised form).```Python"message which has a spot {identifier:formatCode} for values".format(value)"message which has {identifierTwo:formatCode} two spots {identifierOne:format} for values".format(identifierOne=valueOne, identifierTwo=valueTwo)```<jupyter_code>### Format one number and print the output
someValue = 256
print ("In binary, {number:b} is actually 17.".format(number = 17))
print ("Although 128 is only three digits long, {number:05} looks like it's 5 digits long.".format(number = 128))
print ("Here, 300 looks like its aligned to the right.\n{number:46d}".format(number = 300))
print ("You can refer to variables too. Like {number}, which we declared above.".format(number = someValue))
print ("A binary number (like {number:#010b}, which is {number}) should be written in groups of 8 bits.".format(number = 17))<jupyter_output><empty_output><jupyter_text>Formatting using f-stringsSince Python 3.6, you can use **f-strings** to insert variables and their values into a string. This makes the code more readable, and provides you lots of options in addition to the formatting we did above.General syntax:```pythonf"stringText{value:formatCode}moreText" -> strF"stringText{value:formatCode}moreText" -> strf'stringText{value:formatCode}moreText' -> strF'stringText{value:formatCode}moreText' -> str The F may be any of uppercase or lowercase. You may use either of single or double quotes.```In general, outside of A Levels and IGCSEs, f-strings are the preferred way of inserting values into strings like `print()` statements.<jupyter_code>### Format one number and print the output
someValue = 256
print (f"In binary, {17:b} is actually 17.")
print (f"Although {128} is only three digits long, {128:05} looks like it's 5 digits long.")
print (f"Here, {300} looks like its aligned to the right.\n{300:46d}")
print (f"You can refer to variables too. Like {someValue = }, which we declared above.")
print (f"A binary number (like {17:#010b}, which is {17}) should be written in groups of 8 bits.")
print (f"This kind of thing is useful for multiple variables:\n{someValue}")
#### Format more complex objects
from datetime import datetime

## The now() method from this library gives us the current timestamp
timeStamp = datetime.now()
print (timeStamp)

## We can format it however we like. See the link below for more details
## https://www.w3schools.com/python/python_datetime.asp
print(f"The current time is {timeStamp:%I:%M:%S %p}, and today is {timeStamp:%A, %d %B, %Y}.")

## Objects are a topic at the A Level (P4), but you can use this
#  syntax as it is given here if you want to use dates and times
#  in your projects.<jupyter_output><empty_output><jupyter_text>4: Random number generationMany algorithms, such as sorting, should be tested using a sequence of **random numbers**. Computers typically generate **pseudorandom** numbers, which are not strictly random but it is still quite difficult to predict their sequence and they are suitable for testing basic applications. Python 3 has [several functions](https://www.w3schools.com/python/module_random.asp) to generate pseudorandom numbers, but two critical ones from the `random` module will be considered here. A list containing ten random numbers will be created for each function.<jupyter_code>### Import the module to generate random numbers
import random

### Generate and print one random number
randomNumber = random.random()  # this generates one random between 0 and 1

print (randomNumber)

### Generate and print 10 random numbers
randomNumbers = [random.random() for counter in range(10)]
print (randomNumbers)
### Import the module to generate random numbers
import random

### Generate and print one random integer
randomNumber = random.randint(0, 9)     # this generates one random between 0 and 9 (which we specified)

print (randomNumber)

### Generate and print 10 random integers
randomNumbers = [random.randint(0, 9) for counter in range(10)]
print (randomNumbers)<jupyter_output><empty_output><jupyter_text>5: Character codesComputers use some form of a character **encoding** system, and every character corresponds to a unique number. **UNICODE**, and its **predecessor** ASCII, are most commonly used ones. Python 3 has functions that allow an `INTEGER` to be converted to its corresponding ASCII character, and vice versa.<jupyter_code>### Input a word and convert it to ASCII codes
word = input("Enter a word: ").lower()
ASCII_codes = [ord(character) for character in word]
print (ASCII_codes)
### Convert every letter to uppercase by subtracting 32
WORD = [chr(ASCII_code - 32) for ASCII_code in ASCII_codes]
print (WORD)<jupyter_output><empty_output><jupyter_text>2.2: Data representation**Data representation** refers to the form in which data is stored, processed, and transmitted.  2.2.2: ArraysArrays are data structures that allow multiple values of a given datatype to be stored without using multiple identifiers. Each value is stored in a memory location, and this can be accessed using a number, the **index** (written in square brackets `[]`). Arrays are usually **zero-indexed**: the index value of the first element is 0 (and not 1). Consider, for example, an array called `Fruits` which holds the names of fruits to be bought on a particular shopping trip; to access the element at index `i` we use the syntax `Fruits[i]`. The first element is `Fruits[0]`; the sixth element is `Fruits[5]`; and, in general, the nth element is `Fruits[n - 1]`. The **lower bound** of an array is the index of the first element (usually 0), and the **upper bound** is the index of the last element (usually one less than the total number of elements).In Python, it is usual to use a different type of structure called a **list**. A list is **dynamic** (its length can be changed after it has been initialized) and **mutable** (elements can have different datatypes). However, arrays are **static** (not dynamic) and **immutable**, and we will treat Python lists as arrays.In Python 3, lists in dimensions higher than one behave like nested lists. For example, a 2D list is a list of many 1D lists.The table below gives a summary of some common terms related to arrays.| Term | Meaning || :-- | :-- || array | a **data structure** where every element has a unique **index** value || index | the number (position) of an element in an array || lenght | the number of elements in the array || lower bound | the index of the first element (usually 0 but often 1 too) || upper bound | the index of the last element in the array (either length, one less than it) || traverse | visit each element in turn, often with a `for` loop |The cells below give an introduction to arrays, and some common algorithms (sorting, searching and generating a list of random numbers) used with arrays.<jupyter_code>### Declare the array
Fruits = []


### Add some elements
Fruits = ["Orange", "Papaya", "Apple", "Banana", "Plum"]


### Print out the array
print (Fruits)


### Print out the array in a more readable format

# We will use a FOR loop to implement this part of the program. Since
# FOR loops have a counter built right into them, they are a natural choice
# when working with arrays.

print ("You have to buy these fruits:", end=' ')

for Fruit in Fruits:
    print (Fruit, end=", ")

print ('')


### Add an element to the array at the end (append)
Fruits.append("Dragon Fruit")


### Print out the array again to test
print (Fruits)<jupyter_output><empty_output><jupyter_text>0: Initializing arraysUsually, an array is initialized as an empty data structure, i.e., with no elements and it is usual to set its contents to `None` (or just let its length be zero).However, Python 3 allows arrays to be initialized with initial values that may depend upon a function. For example, you can initialize all elements to `0`, or set them to be equal to their index, or be odd numbers, or be square numbers, or contain consecutive letters, or anything else required.<jupyter_code>### Initialize a blank array
myArray0 = []

### Initialize an array of length 20 and values set to None
myArray1 = [None for counter in range(20)]

### Initialize an array of length 20 and values set to 0
myArray2 = [0 for counter in range(20)]

### Initialize an array of length 20 and values set to the counter
myArray3 = [counter for counter in range(20)]

### Initialize an array of length 20 and values set to odd numbers
myArray4 = [((2 * counter) + 1) for counter in range(20)]

### Initialize an array of length 20 and values set to square numbers
myArray5 = [(counter ** 2) for counter in range(20)]

### Initialize an array of length 20 and values set to English letters
myArray6 = [chr(counter + 65) for counter in range(20)]


### Print all the arrays
print (f"{myArray0 = }")
print (f"{myArray1 = }")
print (f"{myArray2 = }")
print (f"{myArray3 = }")
print (f"{myArray4 = }")
print (f"{myArray5 = }")
print (f"{myArray6 = }")<jupyter_output><empty_output><jupyter_text>1: Dimensions in an arrayAn array can have more than one **dimension**; the number of dimensions is the number of indices required to locate an element in an array. A 1D (with a single dimension) array, such as `Fruits` from above, can be represented as a list of values—not unlike a number line. In fact, this analogy from graphs gives us a way to represent 2D arrays: as two axes make a grid, two dimensions of an array can be visualized as a table. Consider, for example, a 2D array called `Marks` which stores the marks of students in a class where everyone studies Computer Science, Further Mathematics and Physics. Each "row" then represents one student, and each "column" represents one subject. The syntax to access an element is `Marks[i][j]` where `i` is the index of the row and `j` is the index of the column.| Index Number | Subjects || :-- | :-- || `[0]` | Computer Science || `[1]` | Further Mathematics || `[2]` | Physics | Following this section, we are going to spend some time with searching and sorting algorithms. It may help to familiarise yourself with these before going ahead. The videos linked below are quite interesting, and all of the offer simple intuitive explanations.* [\[Link\]](https://www.youtube.com/watch?v=WaNLJf8xzC4) A fun and intuitive introduction to different sortingm ethods  from TED-ed.* [\[Link\]](https://www.youtube.com/watch?v=RGuJga2Gl_k) Sorting and Big O notion from Tom Scott.* [\[Link\]](https://www.youtube.com/watch?v=KXJSjte_OAI) Video from Tom Scott that explains binary search in the context of database indexing.* [\[Link\]](https://www.youtube.com/watch?v=tgVSkMA8joQ) Video from mCoding that has an unusual but quite clearly explained perspective on binary searching.<jupyter_code>### Declare the array
Marks = [[]]    # use two nested brackets to indicate two dimensions


### Write marks randomly to the array, assuming each subject is scored out of 100
from random import randint as randomInteger
Marks = [[randomInteger(75, 99) for i in range(3)] for j in range(5)]


### Print out the array
print (Marks)


### Print out the marks more neatly
for Student in Marks:
    for Subject in Student:
        print (Subject, end=' ')
    
    print ("")<jupyter_output><empty_output><jupyter_text>2: Generate random numbersWe would need to generate lists of random numbers to test our codes for searching and sorting. Here is the function to do that.<jupyter_code>from random import randint  # Import the function to generate random integers

## Subroutine to generate random list of unique integers
def generateRandom(n):

    arr = []

    for i in range(n):
        r = randint(0, 10 * n)

        while r in arr:
            r = randint(0, 10 * n)
        
        arr.append(r)
    
    return arr<jupyter_output><empty_output><jupyter_text>3: Linear searchThis is the simplest of the searching algorithms. It would find `element` in an array and return the index at which it is stored. The program traverses an array, going through every element until a match is found. An element `[i]` is checked in the iteration `i`. If element `[i]` matches the required `element`, the programs returns `i` and halts; else it goes to `[i + 1]` until all elements have been searched.<jupyter_code>def linearSearch(listIn, element):
    index = -1
    
    for i in range(len(listIn)):
        if listIn[i] == element:
            index = i
            break
    
    return index

# Test
arr = generateRandom(10)
x = arr[5]
print (f"In list {arr} element {x} occurs at position {linearSearch(arr, x)}")<jupyter_output><empty_output><jupyter_text>4: Bubble sortThis is the simplest sorting algorithm. After the procedure executes, the elements of the array are sorted in ascending order. The program traverses an array, while comparing the current `[i]` element to the next element `[i + 1]`. If the element `[i + 1]` was greater than the element `[i]`, they are swapped.<jupyter_code>def bubbleSort(listIn):
    key = None
    
    for i in range (len(listIn)):

        for j in range (len(listIn) - 1):

            if listIn[j] > listIn[j + 1]:
                key = listIn[j]
                listIn[j] = listIn[j + 1]
                listIn[j + 1] = key

# Test
arr = generateRandom(10)
print(f"Unsorted array:\t{arr}")
bubbleSort(arr)
print(f"Sorted array:\t{arr}")<jupyter_output><empty_output><jupyter_text>2.2.3: FilesA computer **file** is a computer resource for recording data discretely in a computer storage device. Just as words can be written to paper, so can information be written to a computer file. Files can be edited and transferred through the internet on that particular computer system. By using computer programs, a person can open, read, change, save, and close a computer file. Computer files may be reopened, modified, and copied an arbitrary number of times. Typically, files are organised in a file system, which keeps track of where the files are located on disk and enables user access.  1: Opening filesTwo types of files will be considered: `.txt` **text files**, and `.dat` **binary files**. A computer can open files in one of the several modes given below, using Python 3. The character `b` can be added after any of the modes below (`wb`, `rb+` etc) to consider a binary file, rather than a text file. The function `open()` creates a file with the given name (at the given path) if it does not exist.| Python Code | Mode | Functionality || :-- | :-- | :-- || `r` | `READ` | Allows software to read the contents of the file. It cannot alter (append, delete or modify) them. || `w` | `WRITE` | Allows the software open a file to write (typically delete or modify; it can append by controlling the pointer) contents only. It cannot read them. || `a` | `APPEND` | Allows the software to append a file only. It cannot read, modify or delete. || `w+` `r+` | `READ` and `WRITE` | Allows a software to read and write.| `a+` | `READ` and `APPEND` | Allows a software to read and append. It cannot modify or delete. |<jupyter_code>### Open the file and initialize a file object
fileObject = open("textfile.txt", "a+")

### Read the contents of the file
contents = fileObject.readlines()
print(contents)

### Append to the file
fileObject.write("Hi Earth!\n")     # the '\n' character is called a newline and used to separate lines in a file (or to tidy up a long series of print statements).

### Close the file
fileObject.close()<jupyter_output><empty_output><jupyter_text>2: Read a text file consisting of one lineOpen the file in `READ` mode `r`, and print out any contents it contains.<jupyter_code>### Open the file and initialize a file object
fileObject = open("textfile.txt", 'r')

### Read the contents of the file
contents = fileObject.read()

### Check whether file contained anything
if len(contents) > 0:
    print (contents)

else:
    print ("The selected file was empty.")

### Close the file
fileObject.close()<jupyter_output><empty_output><jupyter_text>3: Read a text file consisting of multiple linesOpen a file in `READ` mode `r`, and print out its contents.<jupyter_code>### Open the file and initialize a file object
fileObject = open("textfile.txt", 'r')

### Read the contents of the file
contents = fileObject.readlines()   # automatically place each line into an index in an array

### Print out each line one-by-one
for counter in range(len(contents)):
    print(f"line {str(counter + 1)}: {contents[counter]}")

### Close the file
fileObject.close()<jupyter_output><empty_output><jupyter_text>4: Write a single line to a text fileOpen a file in `WRITE` mode `w`, and write a single line to it. This would delete any contents it previously held.<jupyter_code>### Open the file and initialize a file object
fileObject = open("textfile.txt", 'w')

### Read the contents of the file
contents = fileObject.write("Hello World!\n")

### Close the file
fileObject.close()<jupyter_output><empty_output><jupyter_text>5: Append multiple lines to a text fileOpen a file in `APPEND` mode `a`, and append random numbers to it.<jupyter_code>### Open the file and initialize a file object
fileObject = open("textfile.txt", 'a')

### Write the random numbers
import random

numberOfEntries = random.randint(10, 20)
print (f"{numberOfEntries} new lines will be appended.\n")

for count in range(numberOfEntries):
    number = random.random()
    print(f"Writing {number} to the file...")
    fileObject.write(f"{number}\n")

### Close the file
fileObject.close()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09094464500601684, 'fraction_numerical': 0.009551744885679904, 'mean_word_length': 4.6339830508474575, 'pattern_counts': {'":': 13, '<': 158, '<?xml version=': 0, '>': 175, 'https://': 31, 'lorem ipsum': 0, 'www.': 16, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '37', 'dir': 'jupyter-structured-clean-dedup', 'id': '657152', 'license': 'MIT', 'n_tokens_mistral': 18180, 'n_tokens_neox': 17233, 'n_words': 9268, 'path': 'Section 2 Fundamental Problem-solving and Programming Skills.ipynb', 'repo_name': 'eccentricOrange/9608-Programming-Interactive-Notes', 'size': '66599'}