views:

1811

answers:

11

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.

A: 

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
kenneth
A: 

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
I think in his description the SIZE of the tiles is unknown (and the tile count is given)
JerSchneid
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
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
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
A: 

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ć
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
for (var i = 0; i <= Math.min(a, b); i++)
Boris Pavlović
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
hah, cross commented again. thanks, that what I was thinking.
kenneth
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.htmlThanks for your help so far. (got to dash, so won't get to look at this now till monday)
kenneth
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ć
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ć
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
it seems that your implementation is ok. does it yield expected graphical results?
Boris Pavlović
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
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
Koool!!! I like it :)
Boris Pavlović
+4  A: 

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).

Eric Bainville
Eric, nice link!
VVS
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
Good link, and my problem will not require any rotation of the square tiles (never even thought about trying that)
kenneth
+1  A: 

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
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
A: 
x = max(rectHeight/numberOfSquares, rectangleLength/numberOfSquares)

if x <= retangleHeight && x <= rectangleLength then
  squareSideLength = x
else
  squareSideLength = min(rectangleHeight, rectangleLength)
Trampas Kirk
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
+1  A: 

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
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
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
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
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
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
A: 

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
+1  A: 

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
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
A: 

Kenneth, Or anyone else here that can help...

How the heck can I do this with rectangles. Using sqrt to get the max size makes perfect sense with squares, but I'm struggling with the math to get maxSize of Width AND Height when I want to pack max scaled RECTANGLES into a rectangle.

Any help is greatly appreciated!

Jake Manning
A: 
e.James