18

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]))
mkrieger1
  • 19,194
  • 5
  • 54
  • 65
CopOnTheRun
  • 1,117
  • 2
  • 12
  • 21

5 Answers5

16

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))
poke
  • 369,085
  • 72
  • 557
  • 602
  • 1
    Is old string formatting the only way to embed a variable value within the braces of the new string formatting? – CopOnTheRun Jun 13 '13 at 18:05
  • 1
    No, you could also use the new style formatting there as well, but then you would have to escape the braces, or use nested replacements instead—which I realized would be even better, so see the edit in my answer :) – poke Jun 13 '13 at 18:26
  • Just for clarification, why do you have to set digits equal to itself? You can use already defined variables elsewhere in the `.format()` function. – CopOnTheRun Jun 13 '13 at 18:52
  • 1
    Yeah, my choice of the variable name did not really make that clear (changed it now). In `numLength=digits` the first part is the named parameter name; the second part is the value for the named parameter. So `digits=digits` would set the named parameter `digits` of the function to the value of the variable `digits`. – poke Jun 13 '13 at 18:56
  • @poke I guess you meant numLength=digits on the last line. – Armut Aug 06 '18 at 16:21
  • @Armut Good catch! Thank you very much! – poke Aug 06 '18 at 20:04
12

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)
Next Door Engineer
  • 2,818
  • 4
  • 20
  • 33
  • or, for the OP's example: `print('{0}-{1}:\t{2}'.format(lower[x],upper[x],"*"*num[x]).expandtabs(10))` – zmo Jun 13 '13 at 16:00
1
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

falsetru
  • 357,413
  • 63
  • 732
  • 636
1

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.

CopOnTheRun
  • 1,117
  • 2
  • 12
  • 21
0

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])
Jacek Przemieniecki
  • 5,757
  • 1
  • 12
  • 15