How to fill the bigger square with smaller squares effectively?

Question:

I need to write in python3 a function that fills the bigger square with smaller squares basing on the input.

The input is a list of positive integers. Each integer is the number of squares per row. So the list [3, 8, 5, 2] means I got 4 rows of squares where first one has 3 squares, second one 8 and so on. All squares in all rows are of the same size.

The output should be description of rows distribution in the form of list of lists.

The thing is that on the output there can not be empty rows. So effectively the number of columns can not be greater than the number of rows. The rows can be split though into two or more rows. So for example for the list [3, 8, 5, 2] the function should return [[3], [5, 3], [5], [2]]:

AAA
BBBBB
BBB
CCCCC
DD

and for input [14,13,2,12] it should return [[7,7], [7,6], [2], [7,5]]:

AAAAAAA
AAAAAAA
BBBBBBB
BBBBBB
CC
DDDDDDD
DDDDD

As we can see, the number of rows and columns is in both examples equal. Of course it’s not always possible but the lesser difference between the number of columns and rows, the more efficient the algorythm is – the better the square is filled. In general we aim to get as many columns as possible and as little rows as possible.

And here is the issue – the above examples used 4 input rows – the input list can have a lot of more elements (for example 200 input rows). And the problem is to find optimial way to split the rows (for example if i should split 18 as 9+9 or 6+6+6 or 7+7+4 or maybe 5+5+5+3). Because every time i split the rows basing on available columns (that depend on the number of used rows), I get more output rows and therefore I am able to use more additional available columns – and I fall into some weird loop or recursion.

I’m sorry if there is some easy solution that I don’t see and thank you in advance for help <3

EDIT: Here I include example function that simply ignores the fact that the number of rows increases and just treats the number of input rows as maximum amount of columns possible:

def getSquare(x):
    output = list()
    ln = len(x)
    for i in x:
        if i <= ln:
            output.append([i])
        else:
            split = list()
            nrows = i // ln
            for j in range(nrows):
                split.append(ln)
            if i % ln:
                split.append(i % ln)
            output.append(split)
    return output

print(getSquare([14, 13, 2, 12]))
# returns [[4, 4, 4, 2], [4, 4, 4, 1], [2], [4, 4, 4]]
# so 4 columns and 12 rows 
# columns - maximum number in the matrix
# rows - number of all elements in the matrix (length of flattened output)
# while it should return: [[7,7], [7,6], [2], [7,5]]
# so 7 columns and 7 rows 
#(nr of columns should be not larger but as close to the number of rows as possible)

EDIT2: It doesn’t have to return perfect square – just something as close to square as possible – for example for [4,3,3] it should return [[3,1],[3]
,[3]] while for extreme cases like [1,1,1] it should just return [[1],[1],[1]]

Asked By: Amae Saeki

||

Answers:

list = [3, 8, 5, 2] #As close to a Square as possible

def getSquare(list):
    
    for i in range(1, max(list)+1):

        output = []
        num = 0

        for j in list:

            output.append([i]*(j//i))

            num += j//i

            if j%i != 0:

                output[-1].append(j%i)

                num += 1

        if num == i:

            return output

    i = int( sum(list) ** (1/2) // 1)

    output = []
    
    for j in list:

        output.append([i]*(j//i))

        num += j//i

        if j%i != 0:

            output[-1].append(j%i)

            num += 1

    return output

for i in getSquare(list):

    for j in i:

        print("*"*j)

Here I just repeat the splitting process of the list until i and num become same.

Answered By: KillerRebooted

Essentially when you decrease width, you increase number of rows (vice versa is also true: when you increase width, you decrease number of rows), that’s why you want to as far as it’s possible equalise width and height, because that would get you minimum area of covering square. From this point of view your problem looks like ternary search: minimize maximum of resulting width and height.

So we perform ternary search over width (fix it) and calculate resulting maximum side of square. Calculating function is pretty obvious:

def get_max_side(l, fixed_width):  # O(len(l))
  height = 0
  for i in l:
    height += i // fixed_width
    if i % fixed_width:
      height += 1
  return max(fixed_width, height)

Then use it in a ternary search algorithm to find minimum of get_max_side and restore the answer using found value. Time complexity is O(len(l) * log(max_meaningful_square_width)).

Answered By: fas

I have improved an answer, adding some stop signs to generally speed up the algorithm. It also gives much more efficient answer when no ideal solution was found.

arr = [14, 13, 2, 12]  # as close to a square as possible 

def getSquare(lst):
    output = []
    mx = max(lst)
    for cols in range(min(len(lst), mx), mx + 1):
        out = []
        rows = 0
        for j in lst:
            ln = j // cols
            r = j % cols
            out.append([cols] * ln)
            rows += ln
            if r:
                out[-1].append(r)
                rows += 1
        if rows >= cols:
            output = out
        else:
            break
    return output

for i in getSquare(arr):
    for j in i:
        print("*" * j)
Answered By: Amae Saeki