Myklebost
Myklebost

Reputation: 69

Python: Flexible way to format string output into a table without using a non-standard library

Is there a way to format string output into a table with flexible column width without using any non-standard Python library?

(For example, in the code bellow, longest entry in the 1st column is 'United States' which has 13 characters, so table column width could be just 13[+2] characters instead of fixed width of 20 characters).

There are many good suggestions in answers to this question but they rely on non-standard libraries (Pandas, tabulate, etc.).

Example code using string format specification mini-language:

countries_dict = {'Russia': ['Moscow', 17_098_246], 'Canada': ['Ottawa', 9_984_670], 'China': ['Beijing', 9_596_961], 'United States': ['Washington, D.C.', 9_525_067], 'Brazil': ['Brasília', 8_515_767]}

print('-'*60, '\n| {:<20} | {:<20} | {:<10} |'.format('Country', 'Capital', 'Area'))
print('-'*60)
for k, v in countries_dict.items():
    region, area = v
    print('| {:<20} | {:<20} | {:<10} |'.format(k, region, area))
print('-'*60)

Current code output:

------------------------------------------------------------ 
| Country              | Capital              | Area       |
------------------------------------------------------------
| Russia               | Moscow               | 17098246   |
| Canada               | Ottawa               | 9984670    |
| China                | Beijing              | 9596961    |
| United States        | Washington, D.C.     | 9525067    |
| Brazil               | Brasília             | 8515767    |
------------------------------------------------------------

Desired code output (column width corresponds to length of the longest value, etc.):

----------------------------------------------- 
| Country       | Capital          | Area     |
-----------------------------------------------
| Russia        | Moscow           | 17098246 |
| Canada        | Ottawa           | 9984670  |
| China         | Beijing          | 9596961  |
| United States | Washington, D.C. | 9525067  |
| Brazil        | Brasília         | 8515767  |
-----------------------------------------------

Upvotes: 1

Views: 1454

Answers (1)

mozway
mozway

Reputation: 261840

There are not many ways. To be able to know the column width, you first need to read your data and to calculate the appropriate width per column.

Only then you can generate the table.

Here is a naive implementation:

def mktable(dic, header=None):
    # get max col width
    col_widths = list(map(max, zip(*(map(lambda x: len(str(x)), (k,*v))
                                     for k,v in dic.items()))))

    # default numeric header if missing
    if not header:
        header = range(1, len(col_widths)+1)
    
    header_widths = map(lambda x: len(str(x)), header)
    
    # correct column width if headers are longer
    col_widths = [max(c,h) for c,h in zip(col_widths, header_widths)]
    
    # create separator line
    line = '+%s+' % '+'.join('-'*(w+2) for w in col_widths)
    #line = '-' * (sum(col_widths)+(len(col_widths)-1)*3+4)
    
    # create formating string
    fmt_str = '| %s |' % ' | '.join(f'{{:<{i}}}' for i in col_widths)
    
    # header
    print(line)
    print(fmt_str.format(*header))
    print(line)
    
    # data
    for k, v in countries_dict.items():
        print(fmt_str.format(k, *v))
    
    # footer
    print(line)
mktable(countries_dict, header=('Country', 'Capital', '----- Area -----'))

output:

+---------------+------------------+------------------+
| Country       | Capital          | ----- Area ----- |
+---------------+------------------+------------------+
| Russia        | Moscow           | 17098246         |
| Canada        | Ottawa           | 9984670          |
| China         | Beijing          | 9596961          |
| United States | Washington, D.C. | 9525067          |
| Brazil        | Brasília         | 8515767          |
+---------------+------------------+------------------+

Upvotes: 2

Related Questions