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 wi
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:
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);
}
}
}
}
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)).
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
tiles_max_height
in code.)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:
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;
}
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?
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.
x = max(rectHeight/numberOfSquares, rectangleLength/numberOfSquares)
if x <= retangleHeight && x <= rectangleLength then
squareSideLength = x
else
squareSideLength = min(rectangleHeight, rectangleLength)