5
\$\begingroup\$

I'm working through Chapter 6 in "Automate the Boring Stuff, 2nd Edition." Here is the chapter project at the end.

Write a function named printTable() that takes a list of lists of strings and displays it in a well-organized table with each column right-justified. Assume that all the inner lists will contain the same number of strings. For example, the value could look like this:

tableData = [['apples', 'oranges', 'cherries', 'banana'],
             ['Alice', 'Bob', 'Carol', 'David'],
             ['dogs', 'cats', 'moose', 'goose']]

Your printTable() function would print the following:

   apples Alice  dogs
  oranges   Bob  cats
 cherries Carol moose
   banana David goose

The first time I tackled this project, here was what I came up with:

#! python3
# Displays a table with each column right-justified.

table_data = [["apples", "oranges", "cherries", "bananas"],
    ["Alice", "Bob", "Carol", "David"],
    ["dogs", "cats", "moose", "goose"]]

def print_table(table_data):
    # Create a new list of 3 "0" values: one for each list in table_data
    col_widths = [0] * len(table_data)
    # Search for the longest string in each list of table_data
    # and put the numbers of characters in the new list
    for y in range(len(table_data)):
        for x in table_data[y]:
            if col_widths[y] < len(x):
                col_widths[y] = len(x)

    # Rotate and print the list of lists
    for x in range(len(table_data[0])):
        for y in range(len(table_data)):
            print(table_data[y][x].rjust(col_widths[y]), end=" ")
        print()

print_table(table_data)

The second time, I found out about the MAX method after several searches on Google. Here is what I came up with using the MAX method.

#! python3
# Displays a table with each column right-justified

table_data = [["apples", "oranges", "cherries", "bananas"],
            ["Alice", "Bob", "Carol", "David"],
            ["dogs", "cats", "moose", "goose"]]

def print_table(table_data):
    # Create a new list of 3 "0" values: one for each list in table_data
    col_widths = [0] * len(table_data)
    # Search for longest string in each list of table_data
    # and put the numbers of characters in new list
    for y in range(len(table_data)):
        x = max(table_data[y], key=len)
        if col_widths[y] < len(x):
            col_widths[y] = len(x)
    
    # Rotate and print the lists of lists
    for x in range(len(table_data[0])):
        for y in range(len(table_data)):
            print(table_data[y][x].rjust(col_widths[y]), end=" ")
        print()

print_table(table_data)

Any feedback would be greatly appreciated. Thanks!

\$\endgroup\$
3
  • 3
    \$\begingroup\$ Welcome to Code Review, I added python and comparative-review tags to your post. To increase odds of receiving detailed answers I suggest you to change the title to one including the description of the task because the current title is too general and can be applied to a lot of questions present on the site. \$\endgroup\$ Jul 19 '20 at 5:41
  • 1
    \$\begingroup\$ Thanks @dariosicily. I edited the title. I appreciate the feedback! \$\endgroup\$
    – Phillip C
    Jul 19 '20 at 6:45
  • \$\begingroup\$ Not really a code review, so suggesting by comment: you could use a library for this. Suggestions here and an example with your data using PrettyTable \$\endgroup\$
    – agtoever
    Jul 19 '20 at 10:13
1
\$\begingroup\$

First, get the width of each column. This can be easily done with a list comprehension:

    width = [max(len(item) for item in column) for column in tableData]

Where max(len(item) for item in column) gets the maximum length of an item in the column. And the [ ... for column in tableData] repeats it for each column.

Then build a format string to format each row in the desired format.

    fmt = ' '.join(f"{{:>{w}}}" for w in width)

Where f"{{:>{w}}}" is a f-string expression. Double braces get replaced by single braces and {w} gets replace by the value of w. For example, with w = 8 the f-string evaluates to "{:>8}". This corresponds to a format to right justify (the >) in a field of width 8. A separate string is created for each column, which are joined by a space (' '). In the example table, width = [8, 5, 5], so fmt is "{:>8} {:>5} {:>5}".

Lastly, print each row of the table using the format.

    for row in zip(*tableData):
        print(fmt.format(*row))

Where for row in zip(*tableData) is a Python idiom for iterating over the rows when you have a list of columns.

Putting it together:

def print_table(table_data):
    width = [max(len(item) for item in col) for col in tableData]

    fmt = ' '.join(f"{{:>{w}}}" for w in width)

    for row in zip(*tableData):
        print(fmt.format(*row))
\$\endgroup\$
1
  • \$\begingroup\$ Thank you! I appreciate the feedback. I'm going to look more into this. I haven't learned list comprehensions yet. \$\endgroup\$
    – Phillip C
    Jul 19 '20 at 23:05
3
\$\begingroup\$

You can transpose the given list of lists using a single line:

zip(*table_data)

That's it. Now, to find the longest word in each column, it would be another 1-liner:

map(len, [max(_, key=len) for _ in table_data])

You are calculating len(table_data) a lot of times. Better to store it as a variable? Although, another comprehension could be:

for row in zip(*table_data):
    for index, cell in enumerate(row):
        print(cell.rjust(col_width[index]), end=" ")

You whole function now becomes:

from typing import List
def print_table(table_data: List):
    col_width = list(map(len, [max(_, key=len) for _ in table_data]))
    for row in zip(*table_data):
        for index, cell in enumerate(row):
            print(cell.rjust(col_width[index]), end=" ")
        print()
\$\endgroup\$
1
  • \$\begingroup\$ Thank you! I'm just using the methods I've read up until chapter 6. I appreciate it! \$\endgroup\$
    – Phillip C
    Jul 19 '20 at 23:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.