14

If I have a set of tiles (squares) which can be any number and they are to fill a container (rectangle) of an unknown size how do I work out the maximum size of the tiles without having any of them overlap.

So if I have 2 tiles and the rectangle is 100 * 100 then the max tile size is 50 * 50. This would also be the max size of tile if there was 3 or 4 tiles for this size of rectanlgle, which just so happens to be a square in this example.

If the rectanlge was 100 * 30 and I had 2 tiles, the max size of the square would be 30 * 30, if I have 4 tiles the max size would be 25 * 25.

How can I do this programatically without hogging the processor by going through every possible combination.


I try to summarise a bit better, I have a:

rectangle/bounding box that I need to fill as much as possible without the tiles overlapping.

I know the height and width of the rectangle (but this can change during runtime).

I have X number of tiles (this can change at run time), these are squares.

None of the tiles should overlap, what is the maximum size that each tile can be. They are all to be the same size.

Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171
kenneth
  • 1,055
  • 1
  • 8
  • 15
  • So you know the rectangle size and the number of tiles and you're trying to figure out the maximum tile size? Is that correct? Your function signature would look something like: int GetTileSize(int width, int height, int tileCount) – JerSchneid May 15 '09 at 14:31
  • yes thats correct. I know the height & width of the rectangle and I wish to find the max size for a set number of tiles (squares) – kenneth May 15 '09 at 14:34

10 Answers10

5

Here is an O(1) solution with no loops.

Using the aspect ratio (height/width) of the rectangle, you can come up with an initial guess at the number of tiles in the x and y directions. This gives an upper and lower bound for the total number of tiles: between xy and (x+1)(y+1).

Based on these bounds, there are three possibilities:

  1. The lower bound is correct. Compute the largest tileSize that will result in xy tiles.
  2. The upper bound is correct. Compute the largest tileSize that will result in (x+1)(y+1) tiles
  3. The correct answer lies somewhere between the upper and lower bounds. Solve an equation to determine the correct values of x and y, and then compute the largest tileSize that will result in the correct number of tiles
int GetTileSize(int width, int height, int tileCount)
{
    // quick bailout for invalid input
    if (width*height < tileCount) { return 0; }

    // come up with an initial guess
    double aspect = (double)height/width;
    double xf = sqrtf(tileCount/aspect);
    double yf = xf*aspect;
    int x = max(1.0, floor(xf));
    int y = max(1.0, floor(yf));
    int x_size = floor((double)width/x);
    int y_size = floor((double)height/y);
    int tileSize = min(x_size, y_size);

    // test our guess:
    x = floor((double)width/tileSize);
    y = floor((double)height/tileSize);
    if (x*y < tileCount) // we guessed too high
    {
        if (((x+1)*y < tileCount) && (x*(y+1) < tileCount))
        {
            // case 2: the upper bound is correct
            //         compute the tileSize that will
            //         result in (x+1)*(y+1) tiles
            x_size = floor((double)width/(x+1));
            y_size = floor((double)height/(y+1));
            tileSize = min(x_size, y_size);
        }
        else
        {
            // case 3: solve an equation to determine
            //         the final x and y dimensions
            //         and then compute the tileSize
            //         that results in those dimensions
            int test_x = ceil((double)tileCount/y);
            int test_y = ceil((double)tileCount/x);
            x_size = min(floor((double)width/test_x), floor((double)height/y));
            y_size = min(floor((double)width/x), floor((double)height/test_y));
            tileSize = max(x_size, y_size);
        }
    }

    return tileSize;
}

I have tested this function for all integer widths, heights and tileCounts between 1 and 1000 using the following code:

for (width = 1 to 1000)
{
    for (height = 1 to 1000)
    {
        for (tileCount = 1 to 1000)
        {
            tileSize = GetTileSize(width, height, tileCount);

            // verify that increasing the tileSize by one
            // will result in too few tiles
            x = floor((double)width/(tileSize+1));
            y = floor((double)height/(tileSize+1));
            assert(x*y < tileCount);

            // verify that the computed tileSize actually
            // results in the correct tileCount
            if (tileSize > 0)
            {
                x = floor((double)width/tileSize);
                y = floor((double)height/tileSize);
                assert(x*y >= tileCount);
            }
        }
    }
}
e.James
  • 116,942
  • 41
  • 177
  • 214
5

This is a packing problem. Optimal solutions are hard to find. See for example Packing N squares in a square.

You can compute an (optimistic) upper bound by dividing the total surface by the number of squares: sqrt(width*height/n).

ubershmekel
  • 11,864
  • 10
  • 72
  • 89
Eric Bainville
  • 9,738
  • 1
  • 25
  • 27
  • 1
    Ah, I hadn't noticed that ... there's an important piece of information missing: is the alignment of the tiles fixed? Some of the solutions given below imply a grid-based solution, but your examples above are not sufficient to determine if that is part of the problem or not. I was (perhaps foolishly) assuming that it was. – Zac Thompson May 18 '09 at 04:11
  • Good link, and my problem will not require any rotation of the square tiles (never even thought about trying that) – kenneth May 18 '09 at 10:13
  • sqrt(width*height/n) can be right for all rectangle layouts. as many nos will produce some reminder when divided by n. and that will be unused space. also from a large no of nos that will produce no reminder will not have a perfect sqrt, that will also give some other unused space. and even try your formula with 900 unit area (100 width & 9 height) and try to fit 9 elements in it with your formula. you just can't create 9 squares of 10*10. I think @ZacThompson answer works perfectly.see this post http://stackoverflow.com/questions/34154643/wpf-resize-item-size-to-have-all-items-visible – Kylo Ren Apr 20 '16 at 05:18
5

Conceptually:

  • start with 1 square
  • For each additional square, if you don't have room in your grid box so far, shrink the existing box just enough to make room for an additional row or column.

pseudocode: given M x N rectangle to fill with K squares

// initial candidate grid within the rectangle
h=1
w=1
maxsquares=1
size=min(M,N) //size of the squares
while K > maxsquares
  if M/(h+1) >= N/(w+1)
    h=h+1
  else
    w=w+1
  endif
  maxsquares=h*w
  size=min(M/h,N/w)
done
print size

There are probably faster ways to jump to the answer for very large K, but I can't think of them. If you know that M and N are integers, there may be even faster methods.

Zac Thompson
  • 12,401
  • 45
  • 57
  • Oddly enough all the time I was thinking of this problem I was alway going from taking a small square and making it bigger, but thinking about taking the biggest possible square and just making it smaller helped me solve it :) – kenneth May 18 '09 at 12:48
3

I've managed to come up with a 'relatively' optimal solution. Partially based on Zac's pseudocode answer.

        //total number of tiles
        var tile_count : Number = numberOfSlides;
        //height of rectangle
        var b : Number = unscaledHeight;
        //width of rectanlge
        var a : Number = unscaledWidth;

        //divide the area but the number of tiles to get the max area a tile could cover
        //this optimal size for a tile will more often than not make the tiles overlap, but
        //a tile can never be bigger than this size
        var maxSize : Number = Math.sqrt((b * a) / tile_count);
        //find the number of whole tiles that can fit into the height
        var numberOfPossibleWholeTilesH : Number = Math.floor(b / maxSize);
        //find the number of whole tiles that can fit into the width
        var numberOfPossibleWholeTilesW : Number = Math.floor(a / maxSize);
        //works out how many whole tiles this configuration can hold
        var total : Number = numberOfPossibleWholeTilesH * numberOfPossibleWholeTilesW;

        //if the number of number of whole tiles that the max size tile ends up with is less than the require number of 
        //tiles, make the maxSize smaller and recaluate
        while(total < tile_count){
            maxSize--;
            numberOfPossibleWholeTilesH = Math.floor(b / maxSize);
            numberOfPossibleWholeTilesW = Math.floor(a / maxSize);
            total = numberOfPossibleWholeTilesH * numberOfPossibleWholeTilesW;
        }

        return maxSize;

What this does is to work out the total area of the rectanlge, then divide it by the required number of tiles. As each tile is a square I can SQRT this so that I get the max size of the optimal tile.

With this optimal size I then check to see how many WHOLE tiles I can fit into the width & height. Multiply these together and if it is less than the required number of tiles then I reduce the optimal size and perform the checking again until all of the tiles fit the rectanlge.

I could optimise this further by doing something like reduce the optimal size by -2 insted of -1 each time and then if all the tiles fit increase by 1 just to make sure that I've not missed a valid size. or I could jump back more than -2, say -10 then if they all tiles fit increase by 5, then if the don't fit reduce by -2 etc until I get an optimal fit.

Check out http://kennethsutherland.com/flex/stackover/SlideSorterOK.html for my example. Thanks for all the various info.

kenneth
  • 1,055
  • 1
  • 8
  • 15
  • I have posted a solution that doesn't use any loops. I realize this question was asked a long time ago, but it was a fun problem to solve :) http://stackoverflow.com/questions/868997/max-square-size-for-unknown-number-inside-rectangle/1656634#1656634 – e.James Nov 01 '09 at 08:08
  • @e.James that answer produces wrong size in some cases. – Kylo Ren Apr 20 '16 at 05:24
1

The following function calculates the maximum-sized tile for the given information.

If the fact that it's written in Python makes it hard for you to understand, let me know in a comment and I'll try to do it up in some other language.

import math
from __future__ import division

def max_tile_size(tile_count, rect_size):
    """
    Determine the maximum sized tile possible.

    Keyword arguments:
    tile_count -- Number of tiles to fit
    rect_size -- 2-tuple of rectangle size as (width, height)
    """

    # If the rectangle is taller than it is wide, reverse its dimensions
    if rect_size[0] < rect_size[1]:
        rect_size = rect_size[1], rect_size[0]

    # Rectangle aspect ratio
    rect_ar = rect_size[0] / rect_size[1]

    # tiles_max_height is the square root of tile_count, rounded up
    tiles_max_height = math.ceil(math.sqrt(tile_count))

    best_tile_size = 0

    # i in the range [1, tile_max_height], inclusive
    for i in range(1, tiles_max_height + 1):

        # tiles_used is the arrangement of tiles (width, height)
        tiles_used = math.ceil(tile_count / i), i

        # tiles_ar is the aspect ratio of this arrangement
        tiles_ar = tiles_used[0] / tiles_used[1]

        # Calculate the size of each tile
        # Tile pattern is flatter than rectangle
        if tile_ar > rect_ar:
            tile_size = rect_size[0] / tiles_used[0]
        # Tile pattern is skinnier than rectangle
        else:
            tile_size = rect_size[1] / tiles_used[1]

        # Check if this is the best answer so far
        if tile_size > best_tile_size:
            best_tile_size = tile_size

    return best_tile_size

print max_tile_size(4, (100, 100))

The algorithm can loosely be described as follows

  • If the rectangle is higher than it is wide, flip it so that it's wider than it is high.
  • Calculate s, the square root of the number of tiles, rounded up. (Named tiles_max_height in code.)
  • Loop where i goes from 1 to s inclusive:
    • Construct a grid of squares that is number of tiles / i squares wide and i squares high. (Round everything up. This "pads" the missing tiles, such as using 2 tiles by 2 tiles when your total number of tiles is 3.)
    • Make this grid as big as possible. (Calculate this using aspect ratios.) Determine the size of one tile.
    • Using that size, determine the total area covered by the tiles.
    • Check if this is the best total area so far; if it is, store the square size
  • Return that square size

This is probably one of the faster algorithms listed here, as it computes the best square size in O(sqrt(n)) for n tiles.


Update

On further consideration, this problem has a simpler solution based on the solution above. Say you are given 30 tiles. Your possible tile arrangements are easy to compute:

  • 30 x 1 (aspect ratio 30.0000)
  • 15 x 2 (aspect ratio 7.5000)
  • 10 x 3 (aspect ratio 3.3333)
  • 8 x 4 (aspect ratio 2.0000)
  • 6 x 5 (aspect ratio 1.2000)
  • 6 x 6 (aspect ratio 1.0000)

Say your rectangle is 100 x 60. Your rectangle's aspect ratio is 1.6667. This is between 1.2 and 2. Now, you only need to calculate the tile sizes for the 8 x 4 and the 6 x 5 arrangements.

The first step still technically takes O(sqrt(n)) though, so this updated method is not asymptotically faster than the first attempt.


Some updates from the comments thread

/*
Changes made:

tiles_used -> tiles_used_columns, tiles_used_rows
    (it was originally a 2-tuple in the form (colums, rows))
*/

/* Determine the maximum sized tile possible. */
private function wesleyGetTileSize() : Number {
    var tile_count : Number = slideCount.value;
    var b : Number = heightOfBox.value;
    var a : Number = widthOfBox.value;
    var ratio : Number;    

    // // If the rectangle is taller than it is wide, reverse its dimensions    

    if (a < b) {
        b = widthOfBox.value;
        a = heightOfBox.value;
    } 

    // Rectangle aspect ratio   
    ratio = a / b;    

    // tiles_max_height is the square root of tile_count, rounded up    
    var tiles_max_height : Number = Math.ceil(Math.sqrt(tile_count))    
    var tiles_used_columns : Number;
    var tiles_used_rows : Number;
    var tiles_ar : Number;
    var tile_size : Number;

    var best_tile_size : Number = 0;    

    // i in the range [1, tile_max_height], inclusive   
    for(var i: Number = 1; i <= tiles_max_height + 1; i++) {       
        // tiles_used is the arrangement of tiles (width, height)        
        tiles_used_columns = Math.ceil(tile_count / i);   
        tiles_used_rows = i;

        // tiles_ar is the aspect ratio of this arrangement        
        tiles_ar = tiles_used_columns / tiles_used_rows;        

        // Calculate the size of each tile        
        // Tile pattern is flatter than rectangle       
        if (tiles_ar > ratio){           
            tile_size = a / tiles_used[0]   ;
        }    
        // Tile pattern is skinnier than rectangle        
        else {            
            tile_size = b / tiles_used[1];
        }        
        // Check if this is the best answer so far        
        if (tile_size > best_tile_size){           
            best_tile_size = tile_size;
        }   
    }

    returnedSize.text = String(best_tile_size);
    return best_tile_size;
}
Wesley
  • 10,652
  • 4
  • 37
  • 52
  • I think tile_dim is meant to be tiles_used. And you don't really need to calculate the area -- checking if you have the biggest tile so far is sufficient. This is good; more efficient than mine. But it makes me even more sure that there is a direct route to the solution that we're just missing :) – Zac Thompson May 17 '09 at 08:21
  • Thanks for the comment; both issues are now fixed. Such is the danger of coding too late at night. For efficiency, this algorithm can definitely be improved. Lookup tables for aspect ratios come to mind. (They would bring the complexity to about constant time, actually.) – Wesley May 17 '09 at 09:23
  • I've tried out your code and it may be the way I've interpretted it into actionscript. Have a look at http://kennethsutherland.com/flex/stackover/SlideSorterw.html for a visual demo and check out http://kennethsutherland.com/flex/stackover/wes/CalcForMaxSizeW.html for just the function and calculator (right click on app for source). So it seems to work, but just for a single row (have I just misinterpretted your code?) thanks – kenneth 21 secs ago – kenneth May 18 '09 at 10:10
  • Take a look at the edited ActionScript I've pasted. I think the two big differences are that your for-loop didn't enclose enough of the code and that you were doing something more complicated with tiles_used. In any case, the braces are now in the right place and the variable has been split in two. Try it out and let me know if it works for you! – Wesley May 18 '09 at 21:43
  • I found a way to get it done in O(1): http://stackoverflow.com/questions/868997/max-square-size-for-unknown-number-inside-rectangle/1656634#1656634 – e.James Nov 01 '09 at 08:33
0

Could you elaborate on how you define fill? If I follow your description (big if) it seems that many of the cases you describe don't actually fill the rectangle. For example, you say 2 squares in a 100*100 rectangle would be 50*50. If I understand your configuration correctly, they would be placed on the "diagonal" of this rectangle. But then there would be two "gaps" of size 50*50 in that rectangle as well. That isn't what I think of as "filling" the rectangle. I would instead state the problem as what is the largest possible size for 2 (equal sized squares) whose bounding box would be 100*100 (assuming that every square had to be in contact with at least one other square?).

The key point here is that your rectangle seems to be a bounding box and not filled.

Also, can you write a functional interface for this calculation? Do you need to do it for n possible squares given the dimensions of the bounding box?

Michael Tiller
  • 9,291
  • 3
  • 26
  • 41
  • 1
    You cannot fill a rectanlge that is 100 * 100 with 2 squares, all you can do is fill it as much as possible without any of the square tiles overlaping. I'm looking to find the max size of tile for an unknown number of tiles to fill a rectangle (bounding box if you like) that I know its width & height (but these change during runtime, so I can't hard code them) – kenneth May 15 '09 at 14:38
0

Given values:

N - number of tiles
a, b - sides of the rectangle

side of a tile may be calculated using this function:

def maxSize(n: Int, a: Int, b: Int) = {
  var l = 0
  for (i <- 1 until a.min(b)) { // 
    val newL = (a.min(b) / i).min( (a.max(b) * i)/n )
    if (l < newL && ((a.min(b)/newL) * (a.max(b)/newL) >= n )  )
      l = newL
  }
  return l
}

i have supposed that you are not going to make tiles smaller than 1x1, whatever the measure of 1 is

first you start from the size 0:

l = 0

then you iterate from 1 to K columns of tiles where

K = min(a, b)

for every iteration calculate new maximum side of a tile using this formula

val newL = ( a.min(b) / i ).min( (a.max(b) * i)/n )

this formula takes the smaller on of these two values:

1. min(a, b)/i -- maximum length of a tile if there are i columns in the smaller side of the rectangle
2. (i * max(a, b))/n -- maximum length of a tile if there are i columns and n tiles in the bigger side of the rectangle

if the candidate newL is greater than the initial value l and maximum possible number of tiles which can be put in the square without overlaping is greater or equal than number of tiles n then

l = newL

on the end return l

Boris Pavlović
  • 63,078
  • 28
  • 122
  • 148
  • that looks promising :) Being an actionscript coder, just to be sure what does " for (i <- 1 until a.min(b))" do? what would 'i' be at the start? I take it a.min(b) returns the minimum value from a or b? cheers – kenneth May 15 '09 at 15:33
  • for (var i = 0; i <= Math.min(a, b); i++) – Boris Pavlović May 15 '09 at 15:46
  • I see you've edited this between me seeing it and me posting a comment. does 'for (i <- 1 until a.min(b)) { ' start i of at 1, then increment up while i is less than the minimum side of the rectanlge? – kenneth May 15 '09 at 15:48
  • hah, cross commented again. thanks, that what I was thinking. – kenneth May 15 '09 at 15:49
  • I've had just enough time to implement you're code and its not quite right. I've placed a demo at http://kennethsutherland.com/flex/stackover/SlideSorter2.html should you wish to see what happens. This is very similar to what I've managed so far (then my head started to hurt to much so I asked here ) This is the version I'd done. http://kennethsutherland.com/flex/stackover/SlideSorter.html Thanks for your help so far. (got to dash, so won't get to look at this now till monday) – kenneth May 15 '09 at 16:12
  • could you give me argument values for a failed case, please? for example maxSize(17, 1024, 800) doesn't give a correct result – Boris Pavlović May 15 '09 at 17:36
  • 1
    there's a problem with your function. for a rectangle size 650 * 1260 and 11 tiles, side of a tile returned by the function maxSize(11, 650, 1260) is 216 and your result is 229.09 – Boris Pavlović May 15 '09 at 21:23
  • I've tried a few different options then created a test for just the function. see http://kennethsutherland.com/flex/stackover/CalcForMaxSize.html (plus right click for source code to see how I did the function) – kenneth May 18 '09 at 09:16
  • it seems that your implementation is ok. does it yield expected graphical results? – Boris Pavlović May 18 '09 at 13:02
  • It does, check http://kennethsutherland.com/flex/packing/SlideSorter.html and resize your browser/add tiles. I've spent quite a while on this going through some ridicules recursive functions and what I'd call horrid math’s and in the end when you get it working it looks so straight forward! Sometimes just talking/discussing other methods help to clear the head :) Cheers. – kenneth May 18 '09 at 13:55
  • Just re-read the comments (seems like I'm comment spamming or something). Your last comment, if that was at the test calcuator I posted in the comment previously then no it didn't yield the correct results (see link from 2 days ago). If you were meaning the answer I posted (around the same time you posted your comment) then it certainly seems to be working correctly now. – kenneth May 18 '09 at 14:07
0

I assume that the squares can't be rotated. I'm pretty sure that the problem is very hard if you are allowed to rotate them.

So we fill the rectangle by squares by starting in the left-top corner. Then we put squares to the right of that square until we reach the right side of the rectangle, then we do the same with the next row until we arrive at the bottom. This is just like writing text on paper.

Observe that there will never be a situation where there's space left on the right side and on the bottom. If there's space in both directions then we can still increase the size of the squares.

Suppose we already know that 10 squares should be placed on the first row, and that this fits the width perfectly. Then the side length is width/10. So we can place m = height/sidelength squares in the first column. This formula could say that we can place 2.33 squares in the first column. It's not possible to place 0.33 of a square, we can only place 2 squares. The real formula is m = floor(height/sidelength).

A not very fast (but A LOT faster than trying every combination) algorithm is to try to first place 1 square on the first row/column, then see if we can place enough squares in the rectangle. If it doesn't work we try 2 squares on the first row/column, etc. until we can fit the number of tiles you want.

I think there exists an O(1) algorithm if you are allowed to do arithmetic in O(1), but I haven't figured it out so far.

Here's a Ruby version of this algorithm. This algorithm is O(sqrt(# of tiles)) if the rectangle isn't very thin.

def squareside(height, width, tiles)
  n = 0
  while true
    n += 1
    # case 1: the squares fill the height of the rectangle perfectly with n squares
    side = height/n
    m = (width/side).floor # the number of squares that fill the width
    # we're done if we can place enough squares this way
    return side if n*m >= tiles
    # case 2: the squares fill the width of the rectangle perfectly with n squares
    side = width/n
    m = (height/side).floor
    return side if n*m >= tiles
  end
end

You can also use binary search for this algorithm. In that case it's O(log(# of tiles)).

Jules
  • 6,318
  • 2
  • 29
  • 40
-1
x = max(rectHeight/numberOfSquares, rectangleLength/numberOfSquares)

if x <= retangleHeight && x <= rectangleLength then
  squareSideLength = x
else
  squareSideLength = min(rectangleHeight, rectangleLength)
Trampas Kirk
  • 1,436
  • 3
  • 16
  • 21
  • I was thinking like this, but it doesn't work for a 100x100 grid with 4 tiles. With 4 tiles, x would be 25 when it could be 50. – Alex B May 16 '09 at 14:49
-2

Divide the longer side by the number of tiles. Use the shorter side as the tile size. Presto! # of tiles.

Rectagle = 200 x 10
Each tile is 10 x 10 (length of shorter side)
200/10 = 20 (number of tiles needed)
Eric
  • 464
  • 3
  • 7
  • 1
    I think in his description the SIZE of the tiles is unknown (and the tile count is given) – JerSchneid May 15 '09 at 14:34
  • I know the number of tiles, just not what is the optimal size to fill as much as possible of the rectangle that they are in. – kenneth May 15 '09 at 14:45
  • He also said that the size of the container was unknown. Going by that information it would not be possible to calculate anything. I assumed 1 of the 2 variable would be known and chose the container size (which was logical to me) as the known value. If you don't know the size of the container or the size of the tiles it is not possible to calculate them from their #. – Eric May 15 '09 at 14:46
  • your right my inital part of the question was ambiguous. The rectanlge size is known, but it can change. sorry for the confusion. hopefully the examples make it a bit clearer. – kenneth May 15 '09 at 14:56