Simple way to create formatted string output that behaves like table

Before I go and reinvent the wheel, I want to check that somebody hasn't already come up with something for this.

I have a list of strings that I need to print out in table format. I get the data from tables that can have somewhat long strings of data in some of the cells.

If I were to try to base my column width based on the longest string, I could end up with huge column widths.

What I'm wondering is if there is something already out there that exists to append string data to another row that still lines up with the current row (basically treat it like a cell that enforces automatic padding)

Example

listObj = ['Pre-Condition:', 'Condition:', 'Output:', 
        'Button is OFF', '-', 'Speed is not on', 
        'Button Enabled is OFF', 'Enabled is ON', 
        'Speed is on', 'Button Active is ON', 'Active is OFF', 
        'Hold steady true north', 'Button States is HOLD', 
        'Button States is ACCELERATOR OVERRIDE AND Set stuff is on <Stuff here>', 
        'Pedal to the medal here guys']

The list above is originally a three by 5 table. So I want to print everything out in columns of three. Where I run into issues is the second to last string item in the list. There are many more like this, as this is a somewhat contrived example. Any help would be greatly appreciated. I've run into this issue before, so I wanted to ask, because I'm sure other people have had this issue as well.

Desired Result

Pre-Condition                 Condition                  Output
Button is OFF                 -                          Speed is not on
Button Enabled is OFF         Active is OFF              Speed is on
Button States is HOLD         Button states is           Pedal to the med
                              ACCELERATOR OVERRIDE       here guys
                              AND Set stuff is on 
                               

Edit: Using list as a variable, I keep shooting myself in the foot with that one

Upvotes: 0

Views: 757

Answers (2)

zwer
zwer

Reputation: 25799

Here's one way to do it in a very flexible manner:

# a simple function to do our line-splitting per value
def split_value(value, width):
    result = []
    while len(value) > width:  # while our string is longer than allowed
        split_index = value.rfind(" ", 0, width)
        if split_index == -1:  # no space in our current chunk, we must do hard-break
            split_index = width - 1  # set the split to our column width point
        result.append(value[:split_index + 1])  # add the current slice as a sub-row
        value = value[split_index + 1:]  # remove the added slice from our data
    if value:  # there are leftovers from slicing, add them as the last piece
        result.append(value)
    return result

# and our main function...
def draw_table(data, columns, table_width, column_border=1):
    column_data = [data[i::columns] for i in range(columns)]  # split the data into columns
    column_width = table_width // columns - column_border  # max characters per column
    column_template = ("{} " * (columns - 1)) + "{}"  # a simple template for our columns
    empty_value = " " * (column_width + column_border)  # what to print when there's no value
    rows = len(max(column_data, key=len))  # in case we have more data in some of the columns
    for row in range(rows):  # lets print our rows
        row_data = [split_value(x[row], column_width) if len(x) > row else []
                    for x in column_data]  # lets populate our row
        subrows = len(max(row_data, key=len))  # number of subrows for the current row
        for subrow in range(subrows):  # lets go through each of them and print them out
            print(column_template.format(*[x[subrow].ljust(column_width+column_border)
                                           if len(x) > subrow else empty_value
                                           for x in row_data]))  # print our (split) row

It's a bit twisted but gets the job done reliably and it's not all that hard to follow if you read the comments. It should produce exactly what you asked for (tho your desired result doesn't seem to fit the data you have in your list):

listObj = ['Pre-Condition:', 'Condition:', 'Output:',
           'Button is OFF', '-', 'Speed is not on',
           'Button Enabled is OFF', 'Enabled is ON',
           'Speed is on', 'Button Active is ON', 'Active is OFF',
           'Hold steady true north', 'Button States is HOLD',
           'Button States is ACCELERATOR OVERRIDE AND Set stuff is on <Stuff here>',
           'Pedal to the medal here guys']

# table with three columns, two spaces between columns and of total width of 80 characters
draw_table(listObj, 3, 80, 2)

Produces:

Pre-Condition:             Condition:                 Output:                   
Button is OFF              -                          Speed is not on           
Button Enabled is OFF      Enabled is ON              Speed is on               
Button Active is ON        Active is OFF              Hold steady true north    
Button States is HOLD      Button States is           Pedal to the medal here   
                           ACCELERATOR OVERRIDE       guys                      
                           AND Set stuff is on                                  
                           <Stuff here>

As a bonus, it supports uneven lists so you can do something like:

listObj = ['Pre-Condition:', 'Condition:', 'Output:',
           'Button is OFF', '-', 'Speed is not on',
           'Button Enabled is OFF', 'Enabled is ON',
           'Speed is on', 'Button Active is ON', 'Active is OFF',
           'Hold steady true north', 'Button States is HOLD',
           'Button States is ACCELERATOR OVERRIDE AND Set stuff is on...',
           'Pedal to the medal here guys', "One extra value to prove the flow"]

 draw_table(listObj, 3, 80, 2)

Which will produce:

Pre-Condition:             Condition:                 Output:                   
Button is OFF              -                          Speed is not on           
Button Enabled is OFF      Enabled is ON              Speed is on               
Button Active is ON        Active is OFF              Hold steady true north    
Button States is HOLD      Button States is           Pedal to the medal here   
                           ACCELERATOR OVERRIDE       guys                      
                           AND Set stuff is on...                               
One extra value to                                                              
prove the flow  

Future upgrades like variable column widths shouldn't be that difficult as the row data splits are external so any size can be added.

Upvotes: 0

corn3lius
corn3lius

Reputation: 4985

This is a pretty hardcoded attempt at what your looking for. There is also very little error checking for ranges. I'll let you handle that :)

mylist = ['Pre-Condition:', 'Condition:', 'Output:', 
        'Button is OFF', '-', 'Speed is not on', 
        'Button Enabled is OFF', 'Enabled is ON', 
        'Speed is on', 'Button Active is ON', 'Active is OFF', 
        'Hold steady true north', 'Button States is HOLD', 
        'Button States is ACCELERATOR OVERRIDE AND Set stuff is on <Stuff here>', 
    'Pedal to the medal here guys']

def printCell(row, cellWidth):
    while row != ["","",""]:
        lineformat = ("{:"+str(cellWidth) + "} | ") * 3
        cells=[]
        for n, cell in enumerate(row):
            p = cellWidth
            if len(cell) > cellWidth :
                p = cell[:cellWidth].rfind(" ")
                if p == -1: 
                    p = cellWidth
                row[n] = cell[p:]
            else:
                row[n] = ""
            cells.append(cell[:p])
        print(lineformat.format(*cells))

def printColumns(alist, colCount, colWidth):
    for n in range(0,len(alist)-1,colCount):
        printCell(alist[n:n+colCount], colWidth)
        print("-" * colWidth * colCount)

if __name__ == "__main__":
    printColumns(mylist,3,30)

Output:

Pre-Condition:                 | Condition:                     | Output:                        | 
------------------------------------------------------------------------------------------
Button is OFF                  | -                              | Speed is not on                | 
------------------------------------------------------------------------------------------
Button Enabled is OFF          | Enabled is ON                  | Speed is on                    | 
------------------------------------------------------------------------------------------
Button Active is ON            | Active is OFF                  | Hold steady true north         | 
------------------------------------------------------------------------------------------
Button States is HOLD          | Button States is ACCELERATOR   | Pedal to the medal here guys   | 
                               |  OVERRIDE AND Set stuff is on  |                                | 
                               |  <Stuff here>                  |                                | 
------------------------------------------------------------------------------------------

Edit

Why not just create a csv file which can be open directly with excel?

import csv
with open('output.csv', 'w') as f:
    csvOut = csv.writer(f, delimiter=',')
    for n in range(0,len(mylist)-1,3):
        csvOut.writerow(mylist[n:n+3])

Side Note

It is bad form to use 'list' as a variable. This can conflict with the built-in list type and should be avoided.

Upvotes: 1

Related Questions