Algorithm for adjusting table widths to not have columns smaller than a specified minimum width

I have a bunch of tables, and some of those table columns are very narrow. I’m trying to write a function that takes one table width info, and arrange them so that the minimum width would be something that I specify. The column widths that I get are adding up to 1.0, so the whole table width should also be 1.0 at the end.

These are some of the values that I’m getting:

[7.231920199501247e-2, 0.9276807980049875],
[1.4456630109670987e-2, 0.985543369890329],
[
    1.568627450980392e-2,
    0.6823529411764706,
    0.10196078431372549,
    2.3529411764705882e-2,
    4.7058823529411764e-2,
    7.058823529411765e-2,
    5.8823529411764705e-2
],
[
    5.394190871369295e-2,
    0.1037344398340249,
    5.8091286307053944e-2,
    0.23236514522821577,
    5.394190871369295e-2,
    6.639004149377593e-2,
    9.54356846473029e-2,
    0.17012448132780084,
    0.13278008298755187,
    3.319502074688797e-2
]

Some examples:

  • If the minimum column width is 0.1 and column widths are [0.8, 0.05, 0.05, 0.05, 0.05], the output should be [0.6, 0.1, 0.1, 0.1, 0.1]
  • If the minimum column width is 0.1(10 percent) and the table has 10 columns, all columns should be 0.1.
  • If the minimum column width is 0.1(10 percent) and the table has 11 columns, I shouldn’t change the column widths because that would make the table 110%.

This is the function that I was able to write so far, but sometimes the output exceeds 1.0.

def fix_column_widths(column_widths):

    # If column count multiplied by minimum column width is bigger than 100 percent, leave it.
    if(MIN_COL_WIDTH * len(column_widths) > 1.0):
        return column_widths

    resized_amount = 0.0

    if(columns_need_resize(column_widths)):
        # Start resizing up the small columns to minimum column width
        for i in range(len(column_widths)):
            # If column column width is smaller than minimum column width, make it minimum width and store how much it was resized
            if(column_widths[i] < MIN_COL_WIDTH):
                resized_amount += MIN_COL_WIDTH - column_widths[i]
                column_widths[i] = MIN_COL_WIDTH
        
        # Calculate the total width of the columns that were not resized. This will be used for deciding how much will be needed to reduce from non resized columns.
        non_resized_amount = calculate_non_resized_amount(column_widths)

        # This is probably the place that I need to make changes
        # Start resizing down the columns that won't be smaller than the miminum column width after resizing
        for i in range(len(column_widths)):
            # If (colum width - resize amount) is bigger than minimum column width, resize the column down relatively to other non resized columns
            if(column_widths[i] - resized_amount * (column_widths[i] / non_resized_amount) > MIN_COL_WIDTH):
                column_widths[i] = column_widths[i] - resized_amount * (column_widths[i] / non_resized_amount)

    return column_widths

def columns_need_resize(column_widths):
    for column_width in column_widths:
        if(column_width < MIN_COL_WIDTH):
            return True

    return False

def calculate_non_resized_amount(column_widths):
    non_resized_amount = 0.0

    for column_width in column_widths:
        if(column_width > MIN_COL_WIDTH):
            non_resized_amount += column_width

    return non_resized_amount

What I have here, does not work for this array, it should add up to 1.0, but I get more than 1.0.

[
    5.394190871369295e-2,
    0.1037344398340249,
    5.8091286307053944e-2,
    0.23236514522821577,
    5.394190871369295e-2,
    6.639004149377593e-2,
    9.54356846473029e-2,
    0.17012448132780084,
    0.13278008298755187,
    3.319502074688797e-2
]

How can I improve this function to have it perfectly resize the small columns without exceeding 1.0?

Answer

I would use numpy, as it makes the array operations a bit easier. My proposal is:

import numpy as np
def fix_column_widths(column_widths, minVal = 0.1):
    
    # Check error and trivial cases
    if len(column_widths) * minVal > 1:
        raise ValueError('Number of elements and min value do not match.')
        
    elif len(column_widths) * minVal == 1:
        return np.ones(len(column_widths)) * minVal
    
    # create numpy array from list
    col = np.array(column_widths)
    # find values to fix
    toFix = np.where(col < minVal)
    
    # check if there is anything to do
    if toFix[0].size == 0:
        return col
    
    # find the values we have to renormalize
    good = np.where(col >= minVal)
    
    # calc renormalization factor
    fixSum = col[toFix].sum()
    fixDelta = (toFix[0].size * minVal) - fixSum
    
    goodSum = 1- fixSum
    normalization = (goodSum - fixDelta) / goodSum 
    
    # fix values
    col[good] = col[good] *normalization
    col[toFix] = minVal
    
    # are there new values after normalization which needs to be fixed?
    if np.where(col[good] < minVal)[0].size  > 0:
        # do so: recursive call with sub array
        col[good] = fix_column_widths(col[good], minVal)
    
    return col

This function behaves like this:

in [0.0723192 0.9276808]
out [0.1 0.9]

in [0.01445663 0.98554337]
out [0.1 0.9]

in [0.05394191 0.10373444 0.05809129 0.23236515 0.05394191 0.06639004 0.09543568 0.17012448 0.13278008 0.03319502]
out [0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1]

in [0.01568627 0.68235294 0.10196078 0.02352941 0.04705882 0.07058824 0.05882353]
out [0.1        0.41871658 0.1        0.1        0.1        0.1        0.1]