Tips on finding the volume of water in a 3d chess board

前端 未结 5 1663
栀梦
栀梦 2021-02-10 07:48

So I have an assignment where I have to recreate a 3d chessboard that is a RxC grid of squares each being a different height. If the chessboard is water tight, and someone pours

相关标签:
5条回答
  • 2021-02-10 08:19

    This is going to take more than simply calculating volume.

    According to the examples you posted, you have test for containment first, then worry about the volume of the container.

    I suggest determining whether or not there is a closed polygon in the board.
    If the polygon is closed, determine its area.
    The height used for volume calculation will be the minimum height of all the bounding walls.
    Finally, the volume will be the minimal height multiplied by the area of the polygon.

    Research on the web for algorithms on determining a closed polygon based on a vector of vertices or line segments.

    0 讨论(0)
  • 2021-02-10 08:22

    Fun question, with many varied solutions. I've been thinking about it this afternoon and I would go for something like flood-fill with a priority queue (min-heap, perhaps). Let's call it the fence.

    You'll also want to keep track of which items have been visited. Mark all items as unvisited, initially.

    Start off by adding all points around the perimeter of your grid to the fence.

    Now you loop like so:

    Pop the front item from the fence. You have selected one of the lowest points on the perimeter.

    • If the item has been visited, discard it and loop again.
    • If it's unvisited, its height becomes your new water level only if it is greater than the current water level.

    You now do a flood-fill from that point. You can do this recursively (depth-first), but I will discuss this using an unordered queue (breadth-first). Let's call this queue the flood. You start by pushing the item onto flood.

    Flooding then goes like this: Loop until there are no items remaining in flood...

    • Pop an item from flood
    • If it is already visited, ignore it and loop again.
    • If the item height is less than or equal to the current water level, compute the height difference and add that to your total volume. Mark the item as visited, then add all unvisited neighbours to flood.
    • If the item height is greater than the current water level, just add it to fence. You'll want to have a way to tell whether the item is already in fence - you don't want to add it again. Maybe you can extend your 'visited' flags to cope with this.

    And that's it. Admittedly it was just a thought experiment while I lay around feeling hungover and seedy, but I reckon it's good.


    As you requested... Some pseudocode.

    Initial setup:

    ## Clear flags.  Note I've added a 'flooding' flag
    for each item in map
        item.visited <- false     # true means item has had its water depth added
        item.fenced <- false      # true means item is in the fence queue
        item.flooding <- false    # true means item is in the flooding queue
    end
    
    ## Set up perimeter
    for each item on edge of map (top, left, right, bottom)
        push item onto fence
    end
    
    waterlevel <- 0
    total <- 0
    

    Now the main chunk of the algorithm

    while fence has items
        item <- pop item from fence
        if item.visited = true then loop again
    
        ## Update water level
        if item.height > waterlevel then waterlevel = item.height
    
        ## Flood-fill item using current water level
        push item onto flood
        item.flooding <- true
    
        while flood has items
            item <- pop item from flood
            depth <- waterlevel - item.height
    
            if depth >= 0 then
                # Item is at or below water level.  Add its depth to total.
                total <- total + depth
                item.visited <- true
    
                # Consider all immediate neighbours of item.
                for each neighbour of item
                    if neighbour.visited = false then
                        if neighbour.flooding = false then
                            push neighbour onto flood
                            neighbour.flooding <- true
                        end
                    end
                end
            else
                # Item is above water
                item.flooding <- false
                if item.fenced = false then
                    push item onto fence
                    item.fenced <- true
                end
            end
    
        end
    end
    
    0 讨论(0)
  • 2021-02-10 08:25

    This is a bit brute-force-ish but would probably work.

    You might try to conceptually break the board into layers for instance:

    -------------------------
    0 | 1 | 1 | 0 | 1 | 1 | 0
    1 | 0 |-1 | 1 | 0 | 0 | 1
    1 | 1 | 1 | 1 | 1 | 1 | 1
    -------------------------
    

    Looking at just the lowest layer. Assuming -1 is the bottom, the board would look like this:

    -------------------------
    0 | 0 | 0 | 0 | 0 | 0 | 0
    0 | 0 |-1 | 0 | 0 | 0 | 0
    0 | 0 | 0 | 0 | 0 | 0 | 0
    -------------------------
    

    For each square, determine if there exists a square to the left, right, top and bottom that all have a greater value. In this case we count 1.

    Then move to the next layer, filling in the "holes" in the last layer.

    -------------------------
    0 | 1 | 1 | 0 | 1 | 1 | 0
    1 | 0 | 0 | 1 | 0 | 0 | 1
    1 | 1 | 1 | 1 | 1 | 1 | 1
    -------------------------
    

    Rinse, repeat. In this layer we count 4 giving us a total of 5.

    -------------------------
    1 | 1 | 1 | 1 | 1 | 1 | 1
    1 | 1 | 1 | 1 | 1 | 1 | 1
    1 | 1 | 1 | 1 | 1 | 1 | 1
    -------------------------
    

    Top layer obviously has none and we're done.

    In pseudo code:

    for each layer l in layers
       for each square in l
          if there exists a square left, right, top and bottom with higher value
              count the square.
    

    Edit

    Of course since there is something seriously wrong with me, when I woke up this morning the first thing I thought of was this problem and immediately broke my solution.

    Let's make one change to the example:

    -------------------------
    0 | 1 | 1 | 0 | 1 | 1 | 0
    1 | 0 |-1 | 1 | 0 | 0 | 1
    1 | 1 | 1 | 1 | 1 | 0 | 1
    -------------------------
                        ^
    

    Open a hole to the outside. With the current algorithm we would get a solution of 4 which is obviously wrong.

    To fix this we need to implement a back tracking algorithm.

    Instead of looking anywhere to the left, right, top and bottom for a higher value, we check just the immediately adjacent squares. If any are at the same height we need to visit that square as well and perform the check again. If we find our way to the outside then the original square (and subsequently all the visited squares) fail. If we hit a dead end, then all the visited squares can be counted.

    With this modification we would get the correct result of 3.

    0 讨论(0)
  • 2021-02-10 08:29

    Here is a piece of working code: the basic idea is slicing the board horizontally into levels, and determining the volume each level can hold (complexity O(x * y * z)):

    #include <stdio.h>
    #include <memory.h>
    
    // The Cell structure for each level
    struct Cell
    {
        unsigned char height; // either 0 or 1
        bool visited; // we only visit the cells that have height of 0
    };
    
    // The recursive function that visits a cell, accumulate the water volume (or if leaked due to being at the border, reset the values); it also
    // looks to its 4 adjacent cells (if valid) recursively.
    // Note that the top level function actually attempts to visit *all* the "connected" cells (and mark them as visited, so they will not be visited again)
    // From the top level, the cells are thus visited in "chunks" (as long as they are connected)
    void VisitCell(Cell* one_level_cells, unsigned short a_w, unsigned short a_h, unsigned short w, unsigned short h, unsigned __int64* sum, bool* leaked)
    {
        Cell& cell = one_level_cells[h * a_w + w];
        if (cell.height == 0 && !cell.visited)
        {
            cell.visited = true;
            if (w == 0 || w + 1 == a_w || h == 0 || h + 1 == a_h)
            {
                // I am at the border while I am not guarding the water, the water for this "chunk" is then leaked!
                *leaked = true;
                *sum = 0;
            }
    
            if (!*leaked)
            {
                // potentially increment the volume, until it's detected leaked at some point
                ++*sum;
            }
    
            if (w < a_w - 1)    VisitCell(one_level_cells, a_w, a_h, w+1, h, sum, leaked);
            if (w > 0)          VisitCell(one_level_cells, a_w, a_h, w-1, h, sum, leaked);
            if (h < a_h - 1)    VisitCell(one_level_cells, a_w, a_h, w, h+1, sum, leaked);
            if (h > 0)          VisitCell(one_level_cells, a_w, a_h, w, h-1, sum, leaked);
        }
    }
    
    //@param int const * const unsigned short *a_board - argument representing the NxM board.
    //@param unsigned short a_w - argument representing the width of the board
    //@param unsigned short a_h - argument representing the height of the board
    //@return - unsigned __int64 - the volume of water the board retains
    
    // complexity: O(a_w * a_h * max_height)
    unsigned __int64 CalculateVolume(const unsigned short *a_board, unsigned short a_w, unsigned short a_h)
    {
        if (!a_board || a_w < 3 || a_h < 3)
        {
            return 0;
        }
        // Basic algorithm: slice the board horizontally into as many levels as the maximum height of the board
        // for each sliced level, determine the water volume cubed so far, and the total volume is the sum of the volume of the individual level
        unsigned __int32 n = a_w * a_h;
        unsigned short max_height = 0;
        for (unsigned __int32 i = 0; i < n; ++i)
        {
            if (max_height < a_board[i])
            {
                max_height = a_board[i];
            }
        }
        unsigned short *board = new unsigned short[n];
        memcpy(board, a_board, n * sizeof(unsigned short));
        Cell* one_level_cells = new Cell[n];
        unsigned __int64 total_volume = 0;
        for (unsigned short i = 0; i < max_height; ++i)
        {
            // form a new current level of cells (and update the copy of the board accordingly)
            unsigned __int64 volume_this_level = 0;
            for (unsigned __int32 j = 0; j < n; ++j)
            {
                if (board[j] > 0)
                {
                    --board[j];
                    one_level_cells[j].height = 1;
                }
                else
                {
                    one_level_cells[j].height = 0;
                }
                one_level_cells[j].visited = false;
            }
    
            // visit all the cells within the current level
            // we mark the cells after being visited, and the cells are visited in "chunks" when they are "connected" together
            // so effectively, most of the top level cell visiting would return immediately, rather than trying to revisit the cells again and again
            for (unsigned short h = 0; h < a_h; ++h)
            {
                for (unsigned short w = 0; w < a_w; ++w)
                {
                    unsigned __int64 sum = 0;
                    bool leaked = false;
                    // NB: the top level function here will attempt to cover *all* the connected cells at the current level (in the recursion)
                    // so even though we are still iterating through all the cells at the top level, most of them should find that the cell has been visited
                    // so the sum here is actually a "chunked" sum in the perception of the top level cells
                    VisitCell(one_level_cells, a_w, a_h, w, h, &sum, &leaked);
                    volume_this_level += sum;
                }
            }
    
            total_volume += volume_this_level;
        }
    
        delete[] one_level_cells;
        delete[] board;
    
        return total_volume;
    }
    
    int main()
    {
        // feel free to play with this board
        unsigned short board[] = {
            2, 2, 2, 2, 2, 2, 2, 2,
            2, 1, 1, 1, 1, 1, 1, 2,
            2, 1, 2, 3, 3, 2, 1, 2,
            2, 1, 3, 1, 1, 3, 1, 2,
            2, 1, 3, 1, 1, 3, 1, 2,
            2, 1, 2, 3, 3, 2, 1, 2,
            2, 1, 1, 1, 1, 1, 1, 2,
            2, 2, 2, 2, 2, 2, 2, 2,
        };
        printf("Volume: %lld\n", CalculateVolume(board, 8, 8));
        return 0;
    }
    
    0 讨论(0)
  • 2021-02-10 08:34

    This is python(2.7) version of the pseudocode by @paddy .Hope this will help someone .

    import heapq
    block =[
    1, 2, 3,
    4 ,2,6,
    7, 0,5,
    11,15, 13,
    14,15,16,
    1,0,1,
    1,1,1]
    cmax =3; 
    rmax =7;
    items =[]
    fence = []
    flood = []
    waterlevel = 0
    total = 0
    class Item(object):
        visited = False
        fenced = False
        flooding = False
        height= 0
        index = 0
    i=0
    ## initializing blocks
    for val in block:    
        item =  Item();
        item.height = val
        item.index = i
        i+=1
        items.append((item.height,item))
    ## find out the edges  
    for i in range (cmax):
        heapq.heappush(fence, items[i])
        heapq.heappush(fence, items[i+(rmax-1)*cmax])
        print items[i][1].height,items[i+(rmax-1)*cmax][1].height
    
    for i in range(1,rmax-1):
        heapq.heappush(fence, items[cmax*i])
        heapq.heappush(fence, items[cmax*i+(cmax-1)])
        print items[cmax*i][1].height,items[cmax*i+(cmax-1)][1].height
    
    ## get neighbour
    def get_neighbour(i):
        c= i%cmax
        r= i//cmax
        neighbour = []
        if (c != 0):
            neighbour.append(items[r*cmax+c-1])
        if (c != (cmax -1)):
            neighbour.append(items[r*cmax+c+1])
        if (r != 0):
            neighbour.append(items[(r-1)*cmax+c])
        if (r != (rmax -1)):    
            neighbour.append(items[(r+1)*cmax+c])
        return neighbour
    
    
    
    while (len(fence)>0):
        item = heapq.heappop(fence)
        if(item[1].visited):
            continue
        if (item[1].height > waterlevel):
            waterlevel = item[1].height
        heapq.heappush(flood,item)
        item[1].flooding = True
        while(len(flood)>0):
            fitem = heapq.heappop(flood)
            depth = waterlevel - fitem[1].height
            if (depth >= 0):
                total += depth
                fitem[1].visited = True
                neighbour = get_neighbour(fitem[1].index)
                for nitem in neighbour:
                    if nitem[1].visited == False :
                        if nitem[1].flooding == False :
                            heapq.heappush(flood,nitem)
                            nitem[1].flooding = True
            else:
                fitem[1].flooding = False
                if fitem[1].fenced == False:
                    heapq.heappush(fence,fitem)
                    fitem[1].fenced = True
    print total
    
    0 讨论(0)
提交回复
热议问题