Practical session
Duration3h45Presentation & objectives
In this session, you will solve some exercises to familiarize with defensive exceptions and assertions, and their use for defensive programming. You will also write your first unit tests, and focus on documentation.
The aim of this session is to help you master important notions in computer science. An intelligent programming assistant such as GitHub Copilot, that you may have installed already, will be able to provide you with a solution to these exercises based only on a wisely chosen file name.
For the sake of training, we advise you to disable such tools first.
At the end of the practical activity, we suggest you to work on the exercise again with these tools activated. Following these two steps will improve your skills both fundamentally and practically.
Also, we provide you the solutions to the exercises. Make sure to check them only after you have a solution to the exercises, for comparison purpose! Even if you are sure your solution is correct, please have a look at them, as they sometimes provide additional elements you may have missed.
Activity contents
1 — Extract a list of floats
We have a list l
containing elements of different types.
for example: l = [1, '3.14', 2.8, [], (2, 5), 'hello', {'d': 3, 'e': 2.145}]
.
We also have the following function which extracts all the floats from a given list to feed and returns a new list floats_in_list
:
# Needed imports
from typing import List, Any
def list_of_floats (composite_list: List[Any]) -> List[float]:
"""
This function checks the contents of the list to extract the floats inside.
In:
* composite_list: A list with various types.
Out:
* A list with the floats found in composite_list.
"""
# Extract floats from the list
floats_in_list = []
for value in composite_list:
if type(value) in (int, float):
floats_in_list.append(float(value))
return floats_in_list
Test this function on the example list l
above.
What do you notice?
Rewrite the function to take account numbers in the form of strings.
To do this, you can use the string.digits
property.
Use the exceptions mechanism to achieve the same result.
Test on lists l
and l2 = [(2, 5), 'hello', {'d': 3, 'e': 2.145}]
.
2 — Argmax
The argmax
function returns the index of the (first occurrence of the) maximum value occuring in a list of values, if the list is empty or null (None
or null
), it returns -1
.
Let’s write unit tests for it.
Here is an example of test for the function (copy-paste it in a file tests_argmax.py
), assuming argmax
is in a file argmax.py
:
# Needed imports
import unittest
from argmax import argmax
# Define a class for unit tests
class TestArgmax (unittest.TestCase):
# Here, we define a method with a simple test
# You will have to write new tests, possibly in new methods
def test_argmax_standardcase (self):
self.assertEqual(1, argmax([1, 5, 3, 4, 2, 0]))
# Run the tests
if __name__ == '__main__':
_ = unittest.main(argv=[""], verbosity=2, exit=False)
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class TestArgmax {
@Test
public void testArgmax1() {
Assertions.assertEquals(1, argmax(new int[]{1,5,3,4,2,0}));
}
}
You are going to code the function argmax(lst)
and write some other tests to make sure it’s working properly.
Before coding, you need to list a few unique examples that you can also use for your tests.
You must also document the function and, if necessary, comment on the code.
The statement unittest.main()
runs the tests defined in the file.
The best practice is to separate the functions and the tests.
So do not forget to import the file (and functions) in the module containing the tests.
3 — Leap year
In this exercise, you will have to write a function is_leap(year)
that takes a year as a positive integer parameter, and returns a boolean value that is true if the year is a leap year and false otherwise.
Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these centurial years are leap years if they are exactly divisible by 400 (Wikipedia).
We add an additional rule which specifies that if the integer passed as a parameter is strictly negative, then the value returned is False
.
3.1 — Write the tests
Let’s first write a placeholder for the function, which is just meant for the function to be defined:
def is_leap (year: int) -> bool:
"""
The function returns True if the input is a leap year and False otherwise.
This function only works for positive integers.
In:
* year: The year to be checked.
Out:
* True if the year is a leap year, False otherwise.
"""
return False
/**
* This function takes a year as input and returns True if the year is a leap year, False otherwise.
* An additional constraint is that the year must be greater than 0, otherwise the function will return False.
*(assuming `is_leap` is in a file `isleap.py`)
* @param year the year to be checked
* @return true if year is a leap year, false otherwise
*/
public boolean isLeap(int year){
return false;
}
Before coding the body of the function, you will write the tests. Following the principle of one test per concept, you will create the following six tests:
- The value returned is
False
for a negative value (given as an example). - If the input value is odd, the value returned is
False
. - If the input value is even and not divisible by 4, then the expected result is
False
. - If the input value is divisible by 4 and divisible by 100, then the expected result is
False
. - If the input value is divisible by 4 and not divisible by 100, then the expected result is
True
. - If the input value is divisible by 400, then the expected result is
True
.
Here is a basic code to get started (assuming is_leap
is in a file isleap.py
):
# Needed imports
import unittest
from isleap import is_leap
class TestIsLeap (unittest.TestCase):
def test_negative_value (self):
self.assertFalse(is_leap(-1000))
# Run the tests
if __name__ == '__main__':
_ = unittest.main(argv=[""], verbosity=2, exit=False)
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class TestLeapYear {
@Test
public void testNegativeYear() {
Assertions.assertFalse(isLeap(-1000));
}
}
3.2 — Write the function
You can now code the logic of the function in accordance with the specifications.
You need to comment out lines in a meaningful way to make the code clearer, using #
.
Make sure that all the tests pass!
4 — Valid password
To be considered sufficiently secure, a password must comply with the following rules:
- Must contain at least 8 characters.
- Must contain at least one uppercase letter.
- Must contain at least one lowercase letter.
- Must contain at least one number.
- Must contain at least one special character (from
!@#$%^&*()
).
Write the function is_strong_password(password)
which takes a string (a.k.a., sequence of characters) as input and returns a Boolean indicating whether the password entered is strong.
To help design the logic of the function, you can break it down into sub-functions, each of which will be tested independently.
A skeleton of the function is provided below, divided into sub-functions.
You must also document and comment on your code.
Finally, you need to check that the rules expressed in the specifications are correctly taken into account by writing unit tests. The test class is not provided, so you will need to write it yourself. Each sub-function must be tested independently with a variety of examples. Once each sub-function is tested, you can test the main function.
This last stage can be done in pairs: each person writes the tests for the other. Then, you can evaluate the correctness of your function by running the tests. Possibly, either the function or the tests will need to be modified to ensure that the function is working properly.
Here is a file to get you started:
5 — Valid password again
Now that you have a function that checks the strength of a password, you will document it using the docstring
format.
You must document the module and each of its functions with valuable information, such as the purpose of the function, the parameters it takes, and the value it returns.
Such information will help other developers understand how to use the function.
Extensions in VSCode exist to automatically generate the skeleton of a function docstring.
Once you have documented the code, you can generate the documentation using the pydoc
or javadoc
tool, depending on the language you are using.
First, open a terminal and navigate to the directory where the file is located.
The file must be named strongpassword.py
.
Then, run the following command:
python -m pydoc -w strongpassword
python -m pydoc -w strongpassword
python3 -m pydoc -w strongpassword
python3 -m pydoc -w strongpassword
pydoc
will generate an HTML file named strongpassword.html
in the same directory.
You can open this file in a web browser to view the documentation.
First, open a terminal and navigate to the directory where the file is located.
The file must be named StrongPassword.java
.
Then, run the following command:
javadoc -d doc StrongPassword.java
javadoc -d doc StrongPassword.java
javadoc -d doc StrongPassword.java
javadoc -d doc StrongPassword.java
javadoc
will generate a directory named doc
containing the documentation.
You can open the index.html
file in a web browser to view the documentation.
Once done, check the generated documentation!
6 — Optimize your solutions
What you can do now is to use AI tools such as GitHub Copilot or ChatGPT, either to generate the solution, or to improve the first solution you came up with! Try to do this for all exercises above, to see the differences with your solutions.
To go further
7 — Takuzu checker
Takuzu is a logic game where you have to complete a pre-filled $n\times n$ grid using only 0s and 1s. A grid is completed correctly if it respects the three rules:
- Each row and column must contain an equal number of 0s and 1s.
- No more than two consecutive similar numbers are allowed.
- Each pair of rows is different, and each pair of columns is different.
An example of a correctly completed $4\times 4$ grid:
0 1 1 0
1 0 0 1
0 0 1 1
1 1 0 0
You are asking to code a Takuzu grid checker. A module skeleton will serve as a basis for your work. You must respect the partitioning of functions and implement each of them. Of course, you will also need to create and complete a test class.
Here is a file to get you started:
- TODO
8 — Mastermind
Get ready to code the Mastermind game. In this game, a codemaker creates a code using 4 digits from 1-6. A codebreaker has to make up to ten guesses in order to break the code. For each guess, the codemaker gives hints to the codebreaker. Hints are made of 2 digits:
- The first digit indicates the number of correct numbers in the correct position.
- The second digit indicates the number of correct numbers in the wrong position.
Multiple occurrences of a value are allowed.
If there are duplicate values, they cannot all get a hint unless
they correspond to the same number of duplicate colours in the hidden code.
For example, if the code is [6, 5, 1, 4]
and the guess is [1, 1, 2, 2]
, the clues are (0, 1)
.
In fact, the value 1
appears twice in the guess, but only once in the code.
Since it is not in the right place, it counts as one in the wrong place.
In addition, the value 2
also appears twice in the guess, but not in the code.
It does not appear in the hints.
In order to progress gradually, we’ll break down the logic of the game into functions and code them as we go along. We’ll use the description provided to document the code and write tests.
We provide a partially implemented Mastermind class. Comments are provided for all methods present, whether implemented or not.
You’ll need to code the methods check_code(code)
, has_remaining_attempts()
, is_right_answer(hints)
and guess_pattern(code, guess)
.
We recommend that you process one function after the other, writing the tests before or just after coding the function.
As the last function is the most complicated, you can, if you wish, break it down into sub-functions, each of which will be tested independently.
You may need to counter values.
The following data structures may be useful: from collections import Counter
Here is a file to get you started:
To go beyond
Looks like this section is empty!
Anything you would have liked to see here? Let us know on the Discord server! Maybe we can add it quickly. Otherwise, it will help us improve the course for next year!