CopOnTheRun
CopOnTheRun

Reputation: 1207

How do I align text output in Python?

I've got a function which creates a little star table based on some data collected elsewhere in the program. While the table produces the correct output, since the number of characters in each number changes, it un-aligns the table. For example,

70-78: *****
79-87: ***
88-96: ****
97-105: **
106-114: ******
115-123: ****

Is there any way to align the stars so that the output is something like this:

70-78:   *****
79-87:   ***
88-96:   ****
97-105:  **
106-114: ******
115-123: ****

Here's how I currently print the table.

for x in range(numClasses):
    print('{0}-{1}: {2}'.format(lower[x],upper[x],"*"*num[x]))

Upvotes: 20

Views: 59209

Answers (6)

sgauria
sgauria

Reputation: 458

I created a relatively generic function for this kind of task based on the responses above.

def CreateAlignedTableString(table):
  """
  Create a nicely formatted table from a list of tuples or a list of lists.
  """
  max_col_width = [0]*len(table[0])
  for row in table:
    for col in range(len(row)):
      max_col_width[col] = max(max_col_width[col], len(str(row[col])))

  fmt_string = ''
  for col in range(len(table[0])):
    fmt_string += '{:%d} '%(max_col_width[col]+2)
  fmt_string += '\n'

  table_str = '' 
  for row in table:
    table_str += fmt_string.format(*row)

  return table_str

Upvotes: 0

poke
poke

Reputation: 387607

str.format already has the possibility to specify alignment. You can do that using {0:>5}; this would align parameter 0 to the right for 5 characters. We can then dynamically build a format string using the maximum number of digits necessary to display all numbers equally:

>>> lower = [70, 79, 88, 97, 106, 115]
>>> upper = [78, 87, 96, 105, 114, 123]
>>> num = [5, 3, 4, 2, 6, 4]
>>> digits = len(str(max(lower + upper)))
>>> digits
3
>>> f = '{0:>%d}-{1:>%d}: {2}' % (digits, digits)
>>> f
'{0:>3}-{1:>3}: {2}'
>>> for i in range(len(num)):
        print(f.format(lower[i], upper[i], '*' * num[i]))

 70- 78: *****
 79- 87: ***
 88- 96: ****
 97-105: **
106-114: ******
115-123: ****

Actually, you could even use a single format string here with nested fields:

>>> for i in range(len(num)):
        print('{0:>{numLength}}-{1:>{numLength}}: {2}'.format(lower[i], upper[i], '*' * num[i], numLength=digits))

Upvotes: 16

CopOnTheRun
CopOnTheRun

Reputation: 1207

Ok, while the solution I'm using is admittedly ad-hoc, it works, and scales better than the answers so far. Basically it's just the method VR17 suggested, but with a little more so that the tab size scales with the data set, and isn't just hard coded in.

First I made a method that returns the number characters in some number.

def charNum(number):
    return math.floor(math.log(number,10)+1)

Then I used the charNum() function on the last point of my lower and upper data sets. Only the last point had to be used on each list because the last point is the biggest number. I then counted the character that weren't numbers(the dash, semicolon, and space), and adjusted accordingly.
So the final tabLength variable looks like this:

tabLength = charNum(lower[-1])+charNum(upper[-1])+3

I then plugged the tabLength variable into the expandTab() function to get proper spacing. Here's some example output:

1-11:  *******
12-22: *
23-33: ***
34-44: **
45-55: ***
56-66: *

99-249:   *****
250-400:  ****
401-551:  **
552-702:  **
703-853:  *
854-1004: ***

99-200079:      ******
200080-400060:  **
400061-600041:  ****
600042-800022:  **
800023-1000003: *

The only problem I can really see with this is that if I wanted to expand this to a table or something the tabs would be all funky. If I did that though, I'd probably look into ljust and rjustwhich I'm not all that familiar with right now. I'll leave the question open for a little while in case someone comes up with a better answer.

Upvotes: 1

falsetru
falsetru

Reputation: 369054

lower = [70, 79, 88, 97, 106]
upper = [78, 87, 105, 114, 123]
num = [5, 3, 4, 2, 6, 4]

for l, u, n in zip(lower, upper, num):
    print('{0:<9} {1}'.format('{0}-{1}:'.format(l, u), '*' * n))

http://docs.python.org/3/library/string.html#format-specification-mini-language

Upvotes: 1

Jacek Przemieniecki
Jacek Przemieniecki

Reputation: 5967

Easy way (in your case) would be to put a tab instead of space:

for x in range(numClasses):
    print('{0}-{1}:\t{2}'.format(lower[x],upper[x],"*"*num[x]))

Another way would be to use str.ljust:

for x in range(numClasses):
    label = '{0}-{1}:'.format(lower[x], upper[x])
    print(label.ljust(10, ' ') + "*" * num[x])

Upvotes: 0

Next Door Engineer
Next Door Engineer

Reputation: 2886

This should do the trick. I assume there are clever ways.

print '70-78:'.ljust(10) + '*****'

You could also use expandtabs()

print ('70-78'+'\t'+ '*****').expandtabs(10)

Upvotes: 13

Related Questions