Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 56 additions & 51 deletions input_output.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,30 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Table of contents\n",
"\n",
"- [String input](#String-input)\n",
"- [File I/O](#File-I/O)\n",
"- [Writing to a file](#Writing-to-a-file)\n",
"- [Context managers](#Context-managers)\n",
"- [Binary I/O](#Binary-I/O)\n",
" - [Bytes and strings](#Bytes-and-strings)\n",
" - [Converting bytes to text](#Converting-bytes-to-text)\n",
"- [Reading/Writing CSV files](#Reading/Writing-CSV-files)\n",
"- [Exercises](#Exercises)\n",
"# Table of Contents\n",
" - [References](#References)\n",
" - [Introduction](#Introduction)\n",
" - [String input and output ](#String-input-and-output)\n",
" - [String output](#String-output)\n",
" - [String input](#String-input)\n",
" - [Warm-up exercises](#Warm-up-exercises)\n",
" - [File I/O](#File-I/O)\n",
" - [Paths](#Paths)\n",
" - [Exercises on Paths](#Exercises-on-Paths)\n",
" - [Reading from a file](#Reading-from-a-file)\n",
" - [Writing to a file](#Writing-to-a-file)\n",
" - [Exercises on file reading and writing](#Exercises-on-file-reading-and-writing)\n",
" - [Context managers](#Context-managers)\n",
" - [Binary I/O](#Binary-I/O)\n",
" - [Bytes and strings](#Bytes-and-strings)\n",
" - [Converting bytes to text ](#Converting-bytes-to-text)\n",
" - [Reading/Writing CSV files](#Reading/Writing-CSV-files)\n",
" - [Exercises](#Exercises)\n",
" - [Exercise 1: CSV to dictionary 🌶️🌶️](#Exercise-1:-CSV-to-dictionary-🌶️🌶️)\n",
" - [Exercise 2: Counting words 🌶️](#Exercise-2:-Counting-words-🌶️)\n",
" - [Exercise 3: Letter statistics 🌶️🌶️](#Exercise-3:-Letter-statistics-🌶️🌶️)\n",
" - [Exercise 4: Translating words 🌶️🌶️](#Exercise-4:-Translating-words-🌶️🌶️)\n",
" - [Exercise 5: Binary format 🌶️🌶️🌶️](#Exercise-5:-Binary-format-🌶️🌶️🌶️)\n"
" - [Exercise 5: Binary format 🌶️🌶️🌶️](#Exercise-5:-Binary-format-🌶️🌶️🌶️)"
]
},
{
Expand Down Expand Up @@ -120,7 +129,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### Exercises"
"### Warm-up exercises"
]
},
{
Expand Down Expand Up @@ -356,7 +365,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### Exercises"
"### Exercises on Paths"
]
},
{
Expand Down Expand Up @@ -505,37 +514,6 @@
"This is the most *pythonic* way to read a file line-by-line instead of reading the full contents at once."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"### Exercises"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"1. Modify the function `solution_read_file` to return the content of the file passed as the `input_file` argument. Return the content as a **list of strings**, one string per line.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%ipytest\n",
"\n",
"from pathlib import Path\n",
"\n",
"def solution_read_file(input_file: Path) -> \"list[str]\": \n",
" \"\"\"Write your solution here\"\"\"\n",
" pass"
]
},
{
"attachments": {},
"cell_type": "markdown",
Expand Down Expand Up @@ -605,7 +583,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### Exercises"
"### Exercises on file reading and writing"
]
},
{
Expand All @@ -622,7 +600,30 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"1. Modify the function `solution_write_file` to write the sentence \"python tutorial 2023\" (**without quotes**) to the file `output_file`, which is available as a `Path` object as argument to the function."
"1. Modify the function `solution_read_file` to return the content of the file passed as the `input_file` argument. Return the content as a **list of strings**, one string per line.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%ipytest\n",
"\n",
"from pathlib import Path\n",
"\n",
"def solution_read_file(input_file: Path) -> \"list[str]\": \n",
" \"\"\"Write your solution here\"\"\"\n",
" pass"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"2. Modify the function `solution_write_file` to write the sentence \"python tutorial 2023\" (**without quotes**) to the file `output_file`, which is available as a `Path` object as argument to the function."
]
},
{
Expand All @@ -647,7 +648,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"1. Modify the function `solution_read_write_file` to read the lines from the file `input_file` and write them in the form `line, length`, to the file `output_file`. Here `line` is the line of text in `input_file` **without the line ending**, `length` is **number of characters** in that line.\n",
"3. Modify the function `solution_read_write_file` to read the lines from the file `input_file` and write them in the form `line, length`, to the file `output_file`. Here `line` is the line of text in `input_file` **without the line ending**, `length` is **number of characters** in that line.\n",
"If `input_file` contains these lines:\n",
" ```\n",
" first\n",
Expand Down Expand Up @@ -1104,7 +1105,7 @@
"tags": []
},
"source": [
"Write a function to read all the lines from [`lines.txt`](./tutorial/tests/data/lines.txt) and count the number of words in the file. The solution should be a single number.\n",
"Write a function to read all the lines from `input_file` and count the number of words in the file. The solution should be a single number.\n",
"\n",
"For example, for the file\n",
"```\n",
Expand All @@ -1118,7 +1119,11 @@
"\n",
"<div class=\"alert alert-block alert-info\">\n",
" <h4><b>Hint</b></h4>\n",
" The file is available as the parameter <code>input_file</code> of <code>solution_exercise2</code> function\n",
" <ul>\n",
" <li>The file is available as the parameter <code>input_file</code> of <code>solution_exercise2</code> function</li>\n",
" <li>A word consists of printable characters without line ends and spaces</li>\n",
" </ul>\n",
" \n",
"</div>\n",
"\n",
"\n"
Expand Down Expand Up @@ -1338,7 +1343,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
"version": "3.10.14"
},
"vscode": {
"interpreter": {
Expand Down
5 changes: 5 additions & 0 deletions tutorial/tests/data/lines2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
this file has
more words in each
line
but it is shorter I think
I hope you have fun solving this exercise
45 changes: 30 additions & 15 deletions tutorial/tests/test_input_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,24 @@ def get_data(name: str, data_dir: str = "data") -> pl.Path:
return (pl.Path.cwd() / f"tutorial/tests/{data_dir}/{name}").resolve()


def reference_print_odd(num: int) -> None:
for i in range(num):
def reference_print_odd(n: int) -> None:
for i in range(n):
if i % 2 != 0:
print(i)


@pytest.mark.parametrize("num", [1, 2, 3, 4, 5])
def test_print_odd(function_to_test, num: int):
@pytest.mark.parametrize("n", [1, 2, 3, 4, 5])
def test_print_odd(function_to_test, n: int):
with StringIO() as solution_stdout, StringIO() as reference_stdout:
with contextlib.redirect_stdout(solution_stdout):
function_to_test(num)
function_to_test(n)
solution_text = solution_stdout.getvalue()

with contextlib.redirect_stdout(reference_stdout):
reference_print_odd(num)
reference_print_odd(n)
reference_text = reference_stdout.getvalue()

assert reference_stdout.getvalue() == solution_stdout.getvalue()
assert solution_text == reference_text


def reference_find_all_files(f: pl.Path) -> List[pl.Path]:
Expand Down Expand Up @@ -73,16 +75,26 @@ def test_write_file(function_to_test, tmp_path: pl.Path):
function_to_test(tmp_user)
reference_write_file(tmp_test)

if not tmp_user.exists():
pytest.fail("Cannot read from inexistent file.")
assert (
tmp_user.exists()
), """The file was not created. Make sure to create the file in the function. Hint: use `open(output_file, "w")`"""

assert tmp_user.read_text() == tmp_test.read_text()
assert (
tmp_user.read_text() == tmp_test.read_text()
), "The file content is not correct."


def reference_read_write_file(input_file: pl.Path, output_file: pl.Path) -> None:
# We open the input and output file at the same time
with input_file.open("r") as read_file, output_file.open("w") as write_file:
# Here we iterate over each line of the input file
for line in read_file.readlines():
write_file.write("{}, {}\n".format(line.strip("\n\r"), len(line)))
# We remove line breaks and write the line to the output file
clean_line = line.strip("\n\r")
# We crete the output line
output_line = f"{clean_line}, {len(clean_line)}\n"
# Finally we write the line and its length to the output file
write_file.write(output_line)


def test_read_write_file(function_to_test, tmp_path: pl.Path):
Expand All @@ -92,6 +104,7 @@ def test_read_write_file(function_to_test, tmp_path: pl.Path):

function_to_test(input_file, output_file)
reference_read_write_file(input_file, test_output_file)
assert output_file.exists(), "The output file was not created."

assert output_file.read_text() == test_output_file.read_text()

Expand All @@ -105,8 +118,9 @@ def reference_exercise1(file: pl.Path) -> Dict[str, List[str]]:
}


def test_exercise1(function_to_test):
f = get_data("example.csv")
@pytest.mark.parametrize("file", ["example.csv"])
def test_exercise1(function_to_test, file: str):
f = get_data(file)
assert function_to_test(f) == reference_exercise1(f)


Expand All @@ -121,8 +135,9 @@ def reference_exercise2(file: pl.Path) -> int:
)


def test_exercise2(function_to_test):
f = get_data("lines.txt")
@pytest.mark.parametrize("file", ["lines.txt", "lines2.txt"])
def test_exercise2(function_to_test, file: str):
f = get_data(file)
assert function_to_test(f) == reference_exercise2(f)


Expand Down