Paul
Paul

Reputation: 5974

Formatting output python

I have some code to print a dictionary in a certain format. However as the number of channels can vary, I want to change this format. But I wonder if it can be done as an easy adjustment to what I have? Here is the code:

band3={'channel11': [10812, 2162, 1972], 'channel10': [10787, 2157, 1967], 'channel3': [10612, 2122, 1932], 'channel2': [10589, 2117, 1927], 'channel1': [10564, 2112, 1922], 'channel7': [10712, 2142, 1952], 'channel6': [10687, 2137, 1947], 'channel5': [10662, 2132, 1942], 'channel4': [10637, 2127, 1937], 'channel9': [10762, 2152, 1962], 'channel8': [10737, 2147, 1957], 'channel12': [10837, 2167, 1977]}

table = [[], [], [], []]
# can't just sort the channel names because 'channel11' < 'channel2'
channel_numbers = []
for channel_name in band3.keys():
    if channel_name.startswith('channel'):
        channel_number = int(channel_name[7:])
        channel_numbers.append(channel_number)
    else:
        raise ValueError("channel name doesn't follow pattern")
channel_numbers.sort()

for channel_number in channel_numbers:
    channel_data = band2['channel%d' % channel_number]
    column =[
              'Channel %d' % channel_number,
               str(channel_data[0]),
               '%s/%s' % (channel_data[1], channel_data[2]),
               str(channel_data[3])
            ]
    cell_widths = map(len, column) #9 5 2 9
    column_widths = max(cell_widths) # 9 or 10
    for i in range(len(cell_widths)): #4
        cell = column[i]
        padded_cell = cell + ' '*(column_widths-len(cell))
        table[i].append(padded_cell)



print('{0} {1}'.format("".ljust(6), ' '.join(table[0])))   
print('{0} {1}'.format("UARFCN".ljust(6), ' '.join(table[1])))   
print('{0} {1}'.format("DL/UL".ljust(6), ' '.join(table[2])))   
print('{0} {1}'.format("RSSI".ljust(6), ' '.join(table[3])))        

Here is the output currently:

       Channel 1 Channel 2 Channel 3 Channel 4 Channel 5 Channel 6 Channel 7 Channel 8 Channel 9 Channel 10 Channel 11 Channel 12
UARFCN 10564     10589     10612     10637     10662     10687     10712     10737     10762     10787      10812      10837     
DL/UL  2112/1922 2117/1927 2122/1932 2127/1937 2132/1942 2137/1947 2142/1952 2147/1957 2152/1962 2157/1967  2162/1972  2167/1977 
RSSI   20        0         0         26        32        0         26        0         0         0          0          15     

Instead of having all data listed on one long line, I want to change it so that after every 4 elements the next bit of data is printed on a new line.

       Channel 1 Channel 2 Channel 3 Channel 4
UARFCN 10564     10589     10612     10637       
DL/UL  2112/1922 2117/1927 2122/1932 2127/1937 
RSSI   20        0         0         26    

       Channel 5 Channel 6 Channel 7 Channel 8 
UARFCN 10662     10687     10712     10737         
DL/UL  2112/1922 2117/1927 2122/1932 2127/1937   
RSSI   32        0         26        0                 

       Channel 9 Channel 10 Channel 11 Channel 12       
UARFCN 10762     10787      10812      10837 
DL/UL  2132/1942 2137/1947 2142/1952 2147/1957 
RSSI   0         0          0          15

Edit for Mar:

names = ["", "UARFCN", "DL/UL", "RSSI"]
lwidth = max(len(l) for l in names)
for i in range(0, len(table[0]), 4):
    for j, head in enumerate(names):
        print(' {0:<{lwidth}} {1}'.format(head, ' '.join(table[j][i:i+4])))
    print

Upvotes: 0

Views: 1105

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1121854

Did you know that str.format() can do justifying for you? Adding <6 as a formatting string tells format to left-adjust your text with spaces to fit in 6 characters: '{0:<6} {1}'. You can do the same with the rest of your table 'columns', including using variable width formatting.

Moreover you can use descriptive naming in your format, plus use some sequence indexing to form a whole row:

table_row = '{label:<{lwidth}} {row[0]:<{cwidth}} {row[1]:<{cwidth}} {row[2]:<{cwidth}} {row[3]:<{cwidth}}'

to create a four-column formatting with the column width given as parameter cwidth. I made the label width variable as well, so you can later on tinker with the labels (add more, use verbose labels, etc.) without breaking your layout.

Next, group your data in dictionaries per label for easier processing:

labels = ('', 'UARFCN', 'DL/UL', 'RSSI')
lwidth = max(len(l) for l in labels)
table = []

channel_numbers = [int(cname[7:]) if cname.startswith('channel') else None for cname in band3]
if None in channel_numbers:
    raise ValueError("channel name doesn't follow pattern")
channel_numbers.sort()

cwidth = 0
for channel_number in channel_numbers:
    channel_data = band2['channel{}'.format(channel_number)]
    entry = dict(zip(labels, (
        'Channel {}'.format(channel_number),
        channel_data[0],
        '{}/{}'.format(*channel_data[1:3]),
        channel_data[3]
    )))
    cwidth = max(cwidth, max(len(str(v)) for v in entry.values()))
    table.append(entry)

Now we have the table data in a list as dictionary entries; it's easier to group them that way. Next we use the itertools grouper recipe:

from itertools import izip_longest

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

# use a empty dictionary as filler at the end
for group in grouper(table, 4, dict.fromkeys(labels, '')):
    for label in labels:
        print(table_row.format(label=label, cwidth=cwidth, lwidth=lwidth,
            row=[entry[label] for entry in group]))
    print()

This then outputs (with reconstructed band2 and band3 input):

       Channel 1  Channel 2  Channel 3  Channel 4 
UARFCN 10564      10589      10612      10637     
DL/UL  2112/1922  2117/1927  2122/1932  2127/1937 
RSSI   20         0          0          26        

       Channel 5  Channel 6  Channel 7  Channel 8 
UARFCN 10662      10687      10712      10737     
DL/UL  2132/1942  2137/1947  2142/1952  2147/1957 
RSSI   32         0          26         0         

       Channel 9  Channel 10 Channel 11 Channel 12
UARFCN 10762      10787      10812      10837     
DL/UL  2152/1962  2157/1967  2162/1972  2167/1977 
RSSI   0          0          0          15        

The dict.fromkeys(labels, '') default makes sure that if you have one row with fewer than 4 entries in your table, the last column(s) are filled with empty strings:

       Channel 1  Channel 2  Channel 3  Channel 4 
UARFCN 10564      10589      10612      10637     
DL/UL  2112/1922  2117/1927  2122/1932  2127/1937 
RSSI   20         0          0          26        

       Channel 5  Channel 6  Channel 7  Channel 8 
UARFCN 10662      10687      10712      10737     
DL/UL  2132/1942  2137/1947  2142/1952  2147/1957 
RSSI   32         0          26         0         

       Channel 9  Channel 10 Channel 11           
UARFCN 10762      10787      10812                
DL/UL  2152/1962  2157/1967  2162/1972            
RSSI   0          0          0                    

Upvotes: 2

Elazar
Elazar

Reputation: 21595

names = ["", "UARFCN", "DL/UL", "RSSI"]
for i in range(0, len(table[0]), 4):
    for j, head in enumerate(names):
        print('{0} {1}'.format(head.ljust(6), ' '.join(table[j][i:i+4])) )
    print()

Upvotes: 1

Related Questions