M12) Writing a Markdown report

Markdown is a lightweight markup language with plain-text formatting syntax. It is often used to format README files, write messages in online discussion forums, and create rich text using a plain-text editor. Listing 1 presents a main function that tests a function matrix_to_markdown that converts a matrix and headers (see Figure 1) to a Markdown table (see Figure 1 (b)).

Listing 1: Python code that uses the matrix_to_markdown function to convert the matrix and headers shown in Figure 1 (a) into the Markdown table shown in Figure 1 (b).
def main():
    data = read_data()
    headers = [
        "OrderId",
        "OrderLine",
        "Quantity",
    ]

    with open("orders.md", "w", encoding="utf-8") as file:
        text = matrix_to_markdown(data, headers)
        file.write(text)

The main function does the following:

  1. Calls the read_data function to return the supplied nested list data.
  2. Initializes the list headers with the table headers: OrderId, OrderLine, and Quantity.
  3. Calls the matrix_to_markdown function to convert the matrix and headers to Markdown format (as shown in Figure 1 (b)) and writes the result to orders.md, replacing any existing contents. The file is closed on leaving the with block. All file operations succeed.

List headers:

\[ \begin{bmatrix} OrderId & OrderLine & Quantity \end{bmatrix}. \]

Nested list data:

\[ \begin{bmatrix} 1 & 1 & 2 & 2 & 3 & 3 \\ A & B & A & B & A & B \\ 10 & 20 & 15 & 25 & 30 & 40 \end{bmatrix}. \]

The nested list has three outer items, one for each attribute. Each inner list contains the values of that attribute for the six data points. Therefore, attributes are selected with the first index and data points with the second index.

(a) Data matrix used in the main function to test the matrix_to_markdown function (columns represent data points and rows represent attributes).
| OrderId | OrderLine | Quantity |
|:---:|:---:|:---:|
| 1 | A | 10 |
| 1 | B | 20 |
| 2 | A | 15 |
| 2 | B | 25 |
| 3 | A | 30 |
| 3 | B | 40 |
(b) Markdown table generated from the data matrix and headers shown in Figure 1 (a).
Figure 1: The matrix_to_markdown function should transform the input data and headers (as shown in Figure 1 (a)) into a Markdown table (as shown in Figure 1 (b)).

In Listing 3, we have four alternative implementations of the matrix_to_markdown function. The function receives a nested list data and a list headers with the column headers and returns a string in Markdown format representing the table. It should handle an empty matrix by returning an empty string. The header is created by the get_header_row function, which returns this string, including the newline after the alignment row:

See Listing 2.

Listing 2: Markdown header and alignment rows returned by the supplied header helper.
| OrderId | OrderLine | Quantity |
|:---:|:---:|:---:|

Which implementation of the matrix_to_markdown function in Listing 3 correctly converts the matrix and headers (e.g., as shown in Figure 1 (a)) to the complete Markdown table written to orders.md (as shown in Figure 1 (b)) when main (in Listing 1) is executed?

Listing 3: Alternative implementations of the matrix_to_markdown function. The function receives a nested list data and a list headers and returns a string in Markdown format representing the table. The function creates the header using get_header_row and then iterates over the data matrix to create the data rows. The string "\n" inserts a newline character (Question 1).
(a) Implementation 1.
def matrix_to_markdown(data, headers):
    if not data:
        return ""

    text = get_header_row(headers)

    for field in range(len(data)):
        text = "|"
        for point in range(len(data[0])):
            value = data[point][field]
            text += " " + str(value) + " |"
        text += "\n"

    return text
(b) Implementation 2.
def matrix_to_markdown(data, headers):
    if not data:
        return ""

    text = get_header_row(headers)

    for point in range(len(data[0])):
        text += "|"
        for field in range(len(data)):
            value = data[point][field]
            text += " " + str(value) + " |"
        text += "\n"

    return text
(c) Implementation 3.
def matrix_to_markdown(data, headers):
    if not data:
        return ""

    text = get_header_row(headers)

    for field in range(len(data)):
        text += "|"
        for point in range(len(data[0])):
            value = data[field][point]
            text += " " + str(value) + " |"
        text += "\n"

    return text
(d) Implementation 4.
def matrix_to_markdown(data, headers):
    if not data:
        return ""

    text = get_header_row(headers)

    for point in range(len(data[0])):
        text += "|"
        for field in range(len(data)):
            value = data[field][point]
            text += " " + str(value) + " |"
        text += "\n"

    return text
  • read_data() is a supplied helper with no arguments. It returns the complete nested list shown in Figure 1 (a); for example, its first inner list is [1, 1, 2, 2, 3, 3].
  • get_header_row(headers) returns the Markdown header and alignment rows, with a newline after each. With headers = ["Item"], it returns "| Item |\n|:---:|\n".
  • main() takes no arguments and writes the converted string to orders.md; it has no explicit return value.
  • len(sequence) returns its number of items; len([2, 4]) is 2.
  • range(stop) visits 0 up to, but excluding, stop; range(0) is empty.
  • str(4) returns the string "4".
  • open(path, "w", encoding="utf-8") opens a text file for writing, replacing its contents, and returns a file object.
  • file.write("Hello\n") writes that text and returns its character count, 6. The with block closes the file when it ends.

Evaluate the bounds of the loops and the way the data matrix is accessed.