1

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  |
-----------------------------------------------
Myklebost
  • 59
  • 8
  • Your question is a bit too broad, you should ask something specific. It seems you have the general logic to form the table, now all you have to do it to read first the data to compute the max length per columns, then produce the table. – mozway Feb 01 '22 at 08:36
  • @mozway I editted the question to make it straightforward. The problem is that what you suggest is not possible to do with string format specification mini-language (I thought of that solution as well but there's no way to 'inject' value lengths into formatting mini-language). – Myklebost Feb 01 '22 at 08:49

1 Answers1

2

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          |
+---------------+------------------+------------------+
mozway
  • 194,879
  • 13
  • 39
  • 75