Eli Fulkerson .com HomeProjectsFormat-table
 

Fairly Tabulate Data into Columns

Description:

This is a function that attempts to fairly tabulate text output into a finite number of columns. It was written with an eye to output on a text-only console. Run the tester function for an example of usage.

Platform:

  • no platform specific code, should work anywhere that has Python
  • Language:

  • Python
  • License:

  • I am placing this code (explicitly the file format-table.py.txt on this website) into the public domain. If you find it useful, I'd like to hear from you. If you build it into a larger application I would appreciate compensation and attribution, but am not requiring either.
  • Code:

    def pad (string, length=10, char=" "):
        """
        This function returns a string of length 'length' that consists of the original
        string 'string' padded with instances of 'char' to the length of 'length'
        """
        string = str(string)
    
        if len(string) > length or len(char) > 1:
            return string
    
        strlen = len(string)
        padlen = abs(length - strlen)
        string = string + (char * padlen)
        return string
    
    def print_table(lines, header=None, totalwidth=80, mincol=4, splitter=("|", "-", "+")):
        """
        This function dynamically draws a table, trying its best to resize the data to be properly
        viewed within specified constraints.
    
        totalwidth is the total number of horizontal characters that the table is allowed to consume
        mincol is the minimum number of characters a column may be wide.  Note that table will still
        exceed totalwidth if there is no way to chop it without going beneath mincol
        """
    
    
        splitter, horiz, cross = splitter
    
        # cheap hack for people who specify the splitter as ""
        if splitter == "":
            splitter = " "
        # cheap hack for people who specify the splitter as ""
        if horiz == "":
            horiz = " "
        # cheap hack for people who specify the splitter as ""
        if cross == "":
            cross = " "
    
        width = {}
        total = {}  # keep the total length here to calculate averages
        average = {}
        fair_average = {}
    
        # first thing, figure out the width of each column
        if header:
            col = 0
            for each in header:
                width[col] = len(each)
                col += 1
    
        num_lines = 0
        for line in lines:
            col = 0
            num_lines += 1
            for each in line:
                try:
                    if len(each) > width[col]:
                        width[col] = len(each)
                except:
                    width[col] = len(each)
    
                try:
                    total[col] += len(each)
                except:
                    total[col] = len(each)
    
                col += 1
        # as a side effect, we now know the number of columns...
        num_columns = col
    
        #figure out an equitable split...
        total_of_averages = 0
        for x in range(0,num_columns):
            average[x] = int( total[x]/num_lines )
            total_of_averages += average[x]
    
        total_of_widths = 0
        for x in range(0, num_columns):
            total_of_widths += width[x]
    
        amount_of_padding = (num_columns * len(splitter)) + len(splitter)
    
        if total_of_widths + amount_of_padding > totalwidth:
            # we are over our limit... we need to cut some stuff.
            # so, what we do, is give everyone a % share based on their percentage.
    
            for x in width:
                percent = float(average[x])/(total_of_averages)
                fair_average[x] = int((totalwidth - amount_of_padding) * percent)
    
            # the fair_average[] array now exists.  now we see if any are smaller than min_col
            # if they are... then they need to steal some from the others.
            total_diff = 0
            for x in fair_average:
                if fair_average[x] < mincol:
                    difference = mincol - fair_average[x]
                    total_diff += difference
                    fair_average[x] = mincol
    
            if total_diff > 0:
                # we stole some bits... they need to go back in somewhere.
                while total_diff > 0:
                    stole_some = False
                    for x in fair_average:
                        if fair_average[x] > mincol:
                            fair_average[x] -= 1
                            total_diff -= 1
                    if not stole_some:
                        break
    
            width = fair_average
    
        # at this point, the width array knows how wide things should be.
        buffer = []
        if header:
            col = 0
            linebuffer = ""
            divider = ""
            for each in header:
                tmp=each
                if len(each) > width[col]:
                    tmp = each[0:width[col]]
                linebuffer += splitter + pad(tmp, width[col])
                divider += cross + horiz * width[col]
                col += 1
            linebuffer += splitter
            divider += cross
            buffer.append(linebuffer)
            buffer.append(divider)
    
        for line in lines:
            linebuffer = ""
            col = 0
            for each in line:
                tmp = each
                if len(each) > width[col]:
                    #tmp = each[0:width[col]]
                    tmp = each[0:width[col]-2] + ".."
    
    
                linebuffer += splitter + pad(tmp, width[col])
                col += 1
            linebuffer += splitter
            buffer.append(linebuffer)
    
        return buffer
    
    
    def print_table_tester():
        """
        throwaway test function for print_table
        """
    
        testheader = ["name", "address", "telephone", "ass"]
        data = []
        data.append(["Bob Smithers", "100 unknown avenue", "912-234-2345912-234-2345912-234-2345", "no"])
        data.append(["912-234-2345912-234-2345912-234-2345", "100 unknown avenue", "912-234-2345912-234-2345912-234-2345", "no"])
        data.append(["912-234-2345912-234-2345912-234-2345", "100 unknown avenue", "912-234-2345912-234-2345912-234-2345", "no"])
        data.append(["Bob Fuson", "100 downtowza", "912-234-2345912-234-2345912-234-2345", "no"])
        data.append(["912-234-2345912-234-2345912-234-2345", "100 unknown avenue", "912-234-2345912-234-2345912-234-2345", "no"])
        data.append(["912-234-2345912-234-2345912-234-2345", "100 unknown avenue", "912-234-2345912-234-2345912-234-2345", "no"])
        data.append(["Bob Fulfghfgherson", "100 unknown avenue", "912-234-2345912-234-2345912-234-2345", "no"])
        data.append(["912-234-2345912-234-2345912-234-2345", "100 unknown avenue", "912-234-2345912-234-2345912-234-2345", "no"])
        data.append(["Bob Fufghfghlkersonasdassssssssssssssssdsdasddddddddddsasdasdasdasdasasdasdasdasdasdasdasdasdsdasdasdsdsdsdsdsddsdasd", "100 unknown avenue", "912-234-2345912-234-2345912-234-2345", "no"])
        data.append(["912-234-2345912-234-2345912-234-2345", "100 downtfghfgwn avenue", "912-234-2345912-234-2345912-234-2345", "no"])
        data.append(["Bob Fufghflkerson", "100 unknown avenue", "912-234-2345912-234-2345912-234-2345", "no"])
    
        blah = print_table(data, testheader,66,4)
        for each in blah:
            print each
    
        blah = print_table(data, testheader,120,4)
        for each in blah:
            print each
    
        blah = print_table(data, testheader,40,4)
        for each in blah:
            print each
    
    print_table_tester()

    Source code without formatting