Max square size for unknown number inside rectangle

前端 未结 10 515
陌清茗
陌清茗 2020-12-03 07:58

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

相关标签:
10条回答
  • 2020-12-03 08:35

    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);
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-03 08:35

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

    0 讨论(0)
  • 2020-12-03 08:38

    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;
    }
    
    0 讨论(0)
  • 2020-12-03 08:38

    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?

    0 讨论(0)
  • 2020-12-03 08:39

    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.

    0 讨论(0)
  • 2020-12-03 08:43
    x = max(rectHeight/numberOfSquares, rectangleLength/numberOfSquares)
    
    if x <= retangleHeight && x <= rectangleLength then
      squareSideLength = x
    else
      squareSideLength = min(rectangleHeight, rectangleLength)
    
    0 讨论(0)
提交回复
热议问题