Printing evenly spaced table from a list with a for-loop
Asked Answered
C

3

3

I'm sorry for asking this really elementary question, but I'm really stuck here... I've tried googling this, and using the search function, but can't find what I'm looking for.

I'm trying to print a table of values from a list of objects. But as the "name" string varies a lot in size, it skews the whole table, making it almost unreadable. I've tried adding tabs inbetween with \t, but the columns are still disaligned. Is there something I can add to this print statement to make a nice, straight table?

print "name","level","value"
for i in self.items:
    print i.name, i.lvl, i.value
Charolettecharon answered 14/12, 2012 at 6:42 Comment(0)
C
6

You can try something like this:

In [1]: headers = ["name","level","value"]

In [2]: vals1 = ["Some long name", "a level", "a value"]

In [3]: vals2 = ["An even longer name", "another level", "another value"]

In [4]: max_lens = [len(str(max(i, key=lambda x: len(str(x))))) for i in zip(headers, vals1, vals2)]

In [5]: for row in (headers, vals1, vals2):
   ...:     print '|'.join('{0:{width}}'.format(x, width=y) for x, y in zip(row, max_lens))
   ...:
   ...:
name               |level        |value
Some long name     |a level      |a value
An even longer name|another level|another value

This finds the maximum length of the rows in your data and prints a table that is evenly spaced. In this case, max_lens uses zip to zip together all items in a given 'column' (think all items in the name column, for instance). Then, it finds the length of the longest string (as @Bakuriu points out, these need to be converted to strings in case any of the fields aren't strings) and stores that as the length of the 'column'. Then in the iteration, you specify a width which is going to be equal to the maximum length of that 'column', and pass in the value for that column on that row (hope that makes sense :) ).

The format method makes use of a the very powerful string formatting specification. This is a pretty basic example, but it can be modified to fit much more dynamic situations (such as those where you have numerous rows, etc.).

As for an example of how it could work with your data, you could try the below example. Note that this is not the most readable of code (which is important in Python), so if you were to do something similar, it may be worth it to actually write out some of the for loops so that it is a bit more obvious what is going on (since as I said, it is a bit obfuscated :) ):

In [1]: class MyClass(object):
   ...:     def __init__(self, a, b, c):
   ...:         self.name = a
   ...:         self.level = b
   ...:         self.value = c
   ...:
   ...:

In [2]: headers = ['name', 'level', 'value']

In [3]: vals1 = MyClass('Some long name', 'a level', 10348)

In [4]: vals2 = MyClass('An even longer name', 'another level', 100008484)

In [5]: items = (vals1, vals2)

In [6]: item_lens = [[getattr(item, x) for x in headers] for item in items]

In [7]: max_lens = [len(str(max(i, key=lambda x: len(str(x))))) for i in zip(*[headers] + item_lens)]

In [8]: print '|'.join('{0:{width}}'.format(x, width=y) for x, y in zip(headers, max_lens))
name               |level        |value

In [9]: for i in items:
   ...:     print '|'.join('{0:{width}}'.format(x, width=y) for x, y in zip([getattr(i, x) for x in headers], max_lens))
   ...:
   ...:
Some long name     |a level      |10348
An even longer name|another level|100008484
Correspondent answered 14/12, 2012 at 6:55 Comment(6)
I liked key=lambda x: len(x) :)Kimberleekimberley
@khachik: That was ironic, right? I can never tell with emoticons. Because you should simply be using key=len.Bradfield
@TimPietzcker Haha great point, totally missed the irony (and the better key). I will adjust that first thing tomorrow (and remove that comment :) ).Correspondent
Thanks Tim, and sorry @Kimberleekimberley for the original patronizing comment - my irony sensors are terrible :)Correspondent
A slight enhancement would be to use key=(lambda x: len(str(x))), since (for example) I wouldn't expect a level attribute to be a string.Blintze
@Blintze Great point as well, thanks for pointing that out (and +1 to you for already having accounted for that in your answer :) ). Will adjust now.Correspondent
R
5
print "%20s %20s %20s" % ("name","level","value")
for i in self.items:
    print "%20s %20s %20s" % (i.name, i.lvl, i.value)

Add a format specifier. http://docs.python.org/2/library/stdtypes.html#string-formatting-operations

Reflexive answered 14/12, 2012 at 6:52 Comment(0)
B
2

If you do not have a maximum size for the items, and thus cannot use a format specifier, you can compute the current maximum length and use str.center(or str.rjust):

>>> from collections import namedtuple
>>> Record = namedtuple('Record', ['name', 'lvl', 'value'])
>>> items = [Record('a', 5, 1000), Record('aaaaaa', 15376576, 17.8), Record('aaaaaaaaaaaaaaaa', 7462, 1000)]
>>> max_name_length = len(max(items, key=(lambda x: len(x[0])))[0])
>>> max_lvl_length = len(str(max(items, key=(lambda x: len(str(x[1]))))[1]))
>>> max_value_length = len(str(max(items, key=(lambda x: len(str(x[2]))))[2]))
>>> for item in items:
...     print '%s %s %s' % (item.name.center(max_name_length),
...                         str(item.lvl).center(max_lvl_length),
...                         str(item.value).center(max_value_length))
... 
       a            5     1000
     aaaaaa      15376576 17.8
aaaaaaaaaaaaaaaa   7462   1000
Blintze answered 14/12, 2012 at 6:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.