PY-05: Files
Read the task files below or download the starter files. Use the Canvas assignment to open CodeGrade and submit your work.
Handling files
This assignment is designed to help you become familiar with file handling in Python.
Learning objectives
By completing this assignment, you will be able to:
- Explore the file system using
pathlibandos. - Read and write files safely.
- Parse text-based file formats into Python structures.
- Filter, rename, and inspect files in folders.
- Export structured results to CSV.
Instructions
Complete the seven functions in the downloaded files for offline practice. Work through the files in the order below. Each file states its inputs, output, rules, and examples.
Replace the NotImplementedError line in each file with your code. Keep the function name and parameters. The data/ folder contains the supplied image files and toy routing instance. Use their paths when trying the parsing functions.
The PPM file also supplies show_image(...) for an optional plotting demonstration. Plotting is separate from parsing and earns no additional completion point. The file-system tasks rename or write files, so try them in a separate practice folder. The CSV task assumes at least one experiment, the same input and output keys in each experiment, and values that need no CSV quoting.
For graded submissions, open the Canvas assignment and edit the files already supplied in CodeGrade. Match the exercises by their function names and keep the filenames supplied in that editor.
Select Auto Test, then Run AutoTest to check your work. Read the feedback, revise the relevant file, and run it again. AutoTest does not create a submission. Select Hand-in your work to submit your work before the Canvas due date.
Each question counts as complete when all its checks pass. The tests include cases beyond the examples.
Overview of the exercises
Consider approaching the exercises in the order below:
File system operations
The exercises in this section focus on basic file-system operations, such as inspecting paths, traversing folders, renaming files, and computing file sizes.
files_path_elements.py: Analyze elements of a file path.
Return aFilePathElementsnamedtuple (anchor, name, stem, suffix, ancestors) orNonefor invalid paths.files_filter_by_extension.py: Recursively find files by extension.
Return a list of absolute file paths for files matching a given extension inside a folder and its subfolders.files_filter_rename_files.py: Rename files to snake_case.
Rename all files in a folder to lowercase and replace spaces with_(no-op for invalid folders).files_find_largest.py: Find the largest file in a folder.
Return the path to the largest file in a folder, orNoneif the folder is invalid or empty.
Use cases (practical applications)
The exercises in this section demonstrate practical applications of file parsing and data export.
files_parse_ppm_image.py: Parse a PPM (P3) image into a pixel matrix.
Read a P3.ppmfile and return a 3D pixel matrix[row][col][rgb].files_parse_vrp_instance.py: Parse a VRPTW instance into a dictionary.
Read a VRPTW instance text file and return a dictionary with instance metadata and a list of customers.files_save_experimental_results_to_csv.py: Save experimental results to CSV.
Convert nestedinput/outputdictionaries into a flat CSV file with a single header row.
Notes
- The PPM exercise includes
show_image(...)usingmatplotlibfor manual visualization; plotting is not required by the tests. - Tests pass string paths into your functions; using
pathlib.Path(...)internally is recommended.
Python files
files_path_elements.py
Download files_path_elements.py.
Listing 1 shows the contents of files_path_elements.py.
files_path_elements.py
from pathlib import Path
from collections import namedtuple
FilePathElements = namedtuple(
"FilePathElements", ["anchor", "name", "stem", "suffix", "ancestors"]
)
def analyze_file_path(file_path: str) -> FilePathElements | None:
"""
Analyze elements of a given file path.
A path is considered valid only if it exists on disk. The returned
``ancestors`` are parent directories (as strings), ordered from the
immediate parent directory up to the filesystem root.
Parameters
----------
file_path : str
The file path to analyze.
Returns
-------
namedtuple or None
A namedtuple called with the file path elements:
(anchor, name, stem, suffix, ancestors) if valid, or None
if the file path is invalid.
Examples
--------
>>> analyze_file_path('/user/folder/file.txt')
FilePathElements(anchor='/', name='file.txt', stem='file',
suffix='.txt', ancestors=['/user/folder', '/user', '/'])
"""
raise NotImplementedError("Complete this function.")files_filter_by_extension.py
Download files_filter_by_extension.py.
Listing 2 shows the contents of files_filter_by_extension.py.
files_filter_by_extension.py
import os
from pathlib import Path
def find_files_with_extension(folder_path: str, extension: str) -> list[str]:
"""
Find all files with a specified extension in a folder and its subfolders.
The search is recursive (walks into subfolders). Returned paths are
absolute and sorted lexicographically.
Parameters
----------
folder_path : str
Path to the folder to search in. If the folder does not exist, an empty
list is returned.
extension : str
File extension to match, including the leading dot (e.g. ".txt").
The match is case-insensitive.
Returns
-------
list[str]
Absolute paths (as strings) of all matching files, sorted
lexicographically.
Examples
--------
Assuming the folder structure::
/path/to/folder/
│
├── file1.txt
├── image1.png
│
└── subfolder/
├── file2.txt
├── image2.png
│
└── subsubfolder/
└── file3.txt
Then::
>>> find_files_with_extension('/path/to/folder', '.txt')
['/path/to/folder/file1.txt',
'/path/to/folder/subfolder/file2.txt',
'/path/to/folder/subfolder/subsubfolder/file3.txt']
"""
raise NotImplementedError("Complete this function.")files_filter_rename_files.py
Download files_filter_rename_files.py.
Listing 3 shows the contents of files_filter_rename_files.py.
files_filter_rename_files.py
import os
import shutil
from pathlib import Path
def rename_files_to_snake_case(folder_path: str) -> None:
"""
Rename files in a folder to a simplified snake_case.
Only the files in the top-level of ``folder_path`` are renamed
(non-recursive). The renaming rule is: lowercase the filename and replace
spaces with ``_``.
Parameters
----------
folder_path : str
Path to the folder with files to rename. If the folder does not exist
or is not a directory, this function does nothing.
Examples
--------
>>> rename_files_to_snake_case('/path/to/folder')
"""
raise NotImplementedError("Complete this function.")files_find_largest.py
Download files_find_largest.py.
Listing 4 shows the contents of files_find_largest.py.
files_find_largest.py
import os
def find_largest_file(folder_path: str) -> str | None:
"""
Find the largest file in a given folder.
Only files directly inside ``folder_path`` are considered (non-recursive);
subfolders are ignored. If multiple files share the maximum size, any one
of them may be returned.
Parameters
----------
folder_path : str
Path to the folder.
Returns
-------
str or None
The path of the largest file. None if no files are found or
`folder_path` is invalid.
Examples
--------
>>> find_largest_file('/path/to/folder')
'/path/to/folder/largest_file.txt'
"""
raise NotImplementedError("Complete this function.")files_parse_ppm_image.py
Download files_parse_ppm_image.py.
Listing 5 shows the contents of files_parse_ppm_image.py.
files_parse_ppm_image.py
# This library plots data on a canvas (not required for the assignment).
import matplotlib.pyplot as plt
def show_image(pixels: list[list[list[int]]]) -> None:
"""Plot image.
Parameters
----------
pixels : list of list of list of int
3-D matrix of RGB pixel values.
"""
# Plot image in window
plt.imshow(pixels)
plt.axis("off")
plt.show()
def parse_ppm_image(file_path: str) -> list[list[list[int]]]:
"""Read a PPM file into a 3-D pixel matrix.
This assignment uses a simplified PPM parser (P3/ASCII only). More
information about the PPM format:
http://netpbm.sourceforge.net/doc/ppm.html
Expected file structure (simplified):
- Header is exactly three lines:
1) magic number "P3"
2) "<width> <height>"
3) maximum color value (assumed to be 255)
- After the header, each remaining *line* represents one image row.
Each line contains whitespace-separated integers that form RGB triples.
This intentionally does not implement the full PPM spec with comments or
arbitrary line breaks.
Parameters
----------
file_path : str
Path to the PPM file.
Returns
-------
list of list of list of int
3-D matrix of RGB pixel values.
Examples
--------
>>> parse_ppm_image('path/to/image.ppm')
[[[255, 0, 0], [0, 0, 0]],
[[0, 0, 0], [255, 0, 0]]]
Assuming ``path/to/image.ppm`` is:
```
P3
2 2
255
255 0 0 0 0 0
0 0 0 255 0 0
```
"""
raise NotImplementedError("Complete this function.")
if __name__ == "__main__":
# Run your code in the terminal to see the plotted results.
# 2x2 image
img_pixel_matrix = parse_ppm_image("data/image_2x2.ppm")
show_image(img_pixel_matrix)
img_pixel_matrix = parse_ppm_image("data/image_50x50.ppm")
show_image(img_pixel_matrix)files_parse_vrp_instance.py
Download files_parse_vrp_instance.py.
Listing 6 shows the contents of files_parse_vrp_instance.py.
files_parse_vrp_instance.py
from pprint import pprint
def parse_vrp_instance_from_file(file_path: str) -> dict:
"""
Parse VRP instance data from a file and return the data as a dictionary.
Problem definition reference:
http://dimacs.rutgers.edu/programs/challenge/vrp/vrptw/
The instance has the following structure (as used in this assignment):
- Line 1: name of the instance.
- Line 5: two integers "V Q" (vehicles, capacity).
- Lines 10 and beyond: customer rows with 7 integers:
"i x y d e_i l_i s_i".
This is a deliberately simplified parser for the provided toy instance
format; it assumes these line positions and does not search for headers.
Meaning of symbols:
- ``V``: number of vehicles
- ``Q``: vehicle capacity
- ``i``: customer number
- ``x_i``: x coordinate of customer ``i``
- ``y_i``: y coordinate of customer ``i``
- ``d_i``: demand of customer ``i``
- ``e_i``: earliest time (ready time) of customer ``i``
- ``l_i``: latest time (due date) of customer ``i``
- ``s_i``: service time at customer ``i``
Parameters
----------
file_path : str
Path to the file containing VRP instance data.
Returns
-------
dict
A dictionary with the parsed VRP instance data.
Examples
--------
>>> parse_vrp_instance_from_file('path/to/vrp_instance.txt')
{
'name': 'toy',
'vehicles': 3,
'capacity': 50,
'customers': [
{'id': 0, 'x': 50, 'y': 50, 'demand': 0,
'ready_time': 0, 'due_date': 200, 'service_time': 0},
...
{'id': 6, 'x': 54, 'y': 35, 'demand': 20,
'ready_time': 17, 'due_date': 20, 'service_time': 10}
]
}
"""
raise NotImplementedError("Complete this function.")files_save_experimental_results_to_csv.py
Download files_save_experimental_results_to_csv.py.
Listing 7 shows the contents of files_save_experimental_results_to_csv.py.
files_save_experimental_results_to_csv.py
def save_results_to_csv(experiment_data: list[dict], file_path: str) -> None:
"""
Write VRPTW experimental results' data to a CSV file.
The CSV header order is determined from the first element of
``experiment_data``: first all keys in ``experiment_data[0]['input']`` (in
insertion order), then all keys in ``experiment_data[0]['output']``. Each
subsequent row follows the same header order.
Values are written with ``str(...)`` and are not quoted/escaped.
Parameters
----------
experiment_data : list of dict
A list of dictionaries representing the run of a VRPTW experiment.
Each dict has 'input' and 'output' sub-dicts.
file_path : str
The file path where the CSV file will be written.
Examples
--------
>>> experiment_data = [
{'input': {
'experiment_id': 1,
'vehicle_count': 3,
'customer_count': 20},
'output': {
'total_distance': 100,
'total_time': 50,
'service_level': 95}},
{'input': {
'experiment_id': 2,
'vehicle_count': 4,
'customer_count': 25},
'output': {
'total_distance': 150,
'total_time': 60,
'service_level': 90}}]
>>> save_results_to_csv(experiment_data, 'path/to/vrp_results.csv')
Content of `path/to/vrp_results.csv`:
```csv
experiment_id,vehicle_count,customer_count,total_distance,total_time,service_level
1,3,20,100,50,95
2,4,25,150,60,90
```
"""
# Combine keys from input and output for headers
raise NotImplementedError("Complete this function.")