【问题标题】:Determining value based on adjacent cells in matrix根据矩阵中的相邻单元确定值
【发布时间】:2009-11-16 18:28:14
【问题描述】:

输入:一个由任意大小的布尔矩阵表示的迷宫。 (越界算0)

00100
00100
01110
11111
01110
00100

输出:一个漂亮的迷宫表示(邻居被映射到wchar_t):

   ┌─┐
   │1│
  ┌┘1└┐
 ┌┘111└┐
 |11111|
 └┐111┌┘
  └┐1┌┘
   └─┘

编辑:基本上每个 0 都被映射到一个 4 位的值,代表墙壁布局。

我的方法和想法:

我认为一次查看每个单元格是最简单的。然后查看它的邻居以确定放在那里的值。原来我有 8 个布尔输入(所有邻居),这会产生 2^8=256 个不同的场景。我不想对它们都进行硬编码。

有没有更优雅的方法来正确映射值?

【问题讨论】:

  • 查找表将是此处的典型方式,尽管从 bool 数组工作需要一些小技巧。我对替换不是很清楚,但它看起来像 1->1 而 0->{a 4-bit value},所以可能有一些压缩范围。不过,正如 R Pate 在下面所说,这似乎不值得。
  • 您在替换部分是正确的(1->1 而 0->{a 4-bit value})。是的,我当然不得不求助于更简单的解决方案,但我真的很想知道是否有更好的解决方案......
  • 一个被 1 包围的 0 在输出中会是什么样子?
  • 好问题...我相信 '┼' 将是唯一合适的选择。

标签: c++ algorithm


【解决方案1】:

我在my winning entry 中为 2004 年 IOCCC 实现了这一点。我相信您会发现代码记录良好且易于理解。

如果您只是想要答案,我记得,我采用的方法是计算每个单元格周围已占用单元格的位图,并将其用作墙壁字符数组(或等效的字形)的索引。如果像我的解决方案一样,您不允许对角线移动,则位图是 4 位长,因此数组有 2^4=16 个元素。如果确实允许对角线移动,则需要 8 位位图和 256 个条目。

【讨论】:

    【解决方案2】:

    Doug T. 的解决方案的启发,我自己编写了以下内容。 基本上我两次遍历矩阵(性能不佳:/)。我第一次在矩阵中的每个1 周围画墙,我用位掩码来做这个。第二次我清理了所有“向内”的墙壁。

    示例设置:

    // Add padding to output-matrix
    int owidth = width+2;
    int oheight = height+2;
    // 4-bit value: 0bWSEN
    static char N = 0x1; // The dash that goes from the center to the north
    static char E = 0x2; // The dash that goes from the center to the east
    static char S = 0x4; // ...
    static char W = 0x8;
    // This is what I will draw around every tile
    char box[] = 
        {S|E, E|W, W|S,
         N|S,  0 , N|S,
         N|E, E|W, W|N };
    

    围墙循环:

    for(unsigned int y = 0; y < height; y++)
        for(unsigned int x = 0; x < width; x++)
        {
            // We ignore walls
            if (! isOne(x, y)) // isOne takes care of out-of-bounds
                continue;
            // Go through neighbourhood
            for(int dy = -1; dy <= 1; dy++)
                for(int dx = -1; dx <= 1; dx++)
                {
                    if (dy == 0 && dx == 0) // Ignore self
                        continue;
    
                    if (! isOne(x+dx, y+dy))
                    {
                        // Draw part of box
                        int ox = x+1, oy = y+1; // output-x and y
                        out[(oy+dy)*owidth+(ox+dx)] |= box[(dy+1)*3 + (dx+1)];
                    }
                }
        }
    

    清理循环:

    // Clean up "pointing edges"
    for(unsigned int y = 0; y < height; y++)
        for(unsigned int x = 0; x < width; x++)
        {
            // We ignore zero's since we're only cleaning walls.
            if (isOne(x, y))
                continue;
    
            int ox = x+1, oy = y+1; // output-x and y
            // Remove edges that points to 'zero'-cells.
            if (! isOne(x  , y-1)) out[y*width+x] &= ~N;
            if (! isOne(x  , y+1)) out[y*width+x] &= ~S;
            if (! isOne(x-1, y  )) out[y*width+x] &= ~W;
            if (! isOne(x+1, y  )) out[y*width+x] &= ~E;
        }
    

    然后我将有一个 16 大小(每个符号一个)的查找列表,每个字符一个条目。

    map<unsigned int, wchar_t> p;
    p[0] = ' ';
    p[N] = '.';
    // ...
    p[N|S] = L'\u2502'; // │
    p[E|W] = L'\u2500'; // ─
    // ...
    p[N|E|S|W] = L'\u253C'; // ┼
    

    这个算法无论如何都不是有效的,O(2*width*height) 不好......它可以通过生成一个 256 大小的循环表来改进,就像其他人建议的那样,这会给我们 O( 1) 执行时。

    【讨论】:

      【解决方案3】:

      您可以通过先查看一些单元格来筛选它,但老实说,256 并没有那么多。编写一个生成它们的程序或手动生成它们并使用查找表,额外的 256 字节(您可以将查找映射到另一个索引以获取实际字符)足够小,不必担心。

      【讨论】:

      • 是的,当我想“......必须有更好的方法来做到这一点......”时,这就是我要做的。所以基本上你说的是没有?
      • 我并不是说没有一个很好的数学公式可以使用,我只是说查找表实际上很优雅!一方面,它们易于理解且易于修复,而且您将摆脱解决实际问题 (tm) 而不是摸不着头脑。
      【解决方案4】:

      您可以“走”墙壁,而不是扫描每一行以查找 1 并绘制每个单元格。

      虽然不在布尔数组的末尾:

      • 扫描直到找到 1

      定义一个名为“Draw”的函数,它将:

      • 将每个相邻的 0(或越界)绘制为一堵墙
      • 将当前的 1 更改为 3(这需要您使用除布尔数组以外的其他内容)
      • 将当前光标切换到相邻的 1
        • 递归到“绘图”
      • 如果不存在这样的相邻节点,则从 Draw 中返回。墙。

      -返回后,继续扫描,直到找到下一个 1 或输入结尾。

      也许不是最有效的,但你说的优雅。递归总是优雅的! (直到你得到一个stackoverflow)。

      这里有一些被破解的代码(不要像我一样使用幻数:))来帮助你:

      int inputIdxToOutputIdx(int idx)
      {
              return (idx + 1);
      }
      
      
      int draw(int x, int y, int arr[6][6], char outBuff[8][8])
      {
              for (int deltaX = -1; deltaX < 2; ++deltaX)
              {       
                      for (int deltaY = -1; deltaY < 2; ++deltaY)
                      {       
                              int currX = (x + deltaX);
                              int currY = (y + deltaY);
                              int drawX = inputIdxToOutputIdx(x) + deltaX; 
                              int drawY = inputIdxToOutputIdx(y) + deltaY; 
                              // if any adjacent to 1 are 0 or off the map,
                              // draw a border
                              arr[x][y] = 3;
                              if (currX > 5 || currY > 5 || currX < 0 || currY < 0 || arr[currX][currY] == 0)
                              {       
                                      printf("Drawing at %i, %i (%i,%i)\n", currX, currY,drawX,drawY);
                                      outBuff[drawX][drawY] = '*';
                              }       
                              else if (arr[x][y] == 1) 
                              {       
                                      draw(currX, currY, arr, outBuff);
                              }       
                      }
              }
      }
      
      
      // make the output buffer size of input + 2
      int printMaze(int arr[6][6], char outBuff[8][8])
      {
              for (int x = 0; x < 6; ++x)
              {
                      for (int y = 0; y < 6; ++y)
                      {
                              // this might be able to be made more efficient.
                              if (arr[x][y] == 1)
                              {
                                      draw(x, y, arr, outBuff);
                              }
                      }
              }
      }
      

      在上面的解决方案中,我只画了'*'。但是,如果您想针对特定情况绘制特定的作品,我会使用 walkytalky 在他的评论中所说的查找表。将一组给定的相邻 1 和 0 映射到给定的一块。即:

      抬头:

      0 1 0
      0 1 1
      0 1 0 
      

      将为中心墙片提供“T”结果。请务必将“地图外”视为 0。

      说完所有内容后,仅使用基于相邻部分的查找表进行直接扫描(不使用递归)可能是您最好的选择,除非您可以使上述解决方案更智能地避免重新扫描已扫描的内容。

      【讨论】:

      • 这看起来很有希望。尽管在我的论文上进行了试运行后,我对操作感到非常困惑。我将如何将相邻的 0 绘制为墙?我将如何处理角落?由多个1 共享的0 怎么样?
      • 那么,我还是要手动创建一个大小为 256 的锁定表?
      • 是的,我不认为这是可以避免的。
      【解决方案5】:

      首先运行矩阵添加以下内核矩阵,将内核的0 居中在每个1 上,并将数字添加到相邻的方格(即,对所有邻居进行二进制表示)。

      1   2   4 
      8   16  32 
      46  128 256  
      

      然后只写规则列表,每个形状一个规则,而不是每个可能的总和的规则。例如,

      s = s_ij  # the sum at the location of interest
      if not s & 16:  # never write a symbol over a 1
          if s & 8 and not (s & 128 or s & 2): 
              c = "|"
          elif s ==128:
              c = “┌─┐”
          # etc
      

      或任何你想要的。

      【讨论】:

      • 我认为数量要少得多。以您的示例为例,有 5 种形状,但有 12 种独特的二进制模式。除了你已经展示过的,还需要哪些额外的形状?一个单杠,可能是八个 L 形,就是这样,对吧?共有 14 种形状。 (或者我可能错过了一些,但可能不是 242。)
      【解决方案6】:

      如果您首先找到哪些瓷砖是墙,然后为您拥有的每种墙类型编写一个特殊情况,例如,如果左侧和/或右侧有一个,但顶部或底部都没有,则它是垂直的。

      我希望这应该缩小一点。 :) 垂直、水平和四个不同的边缘。一旦你检测到哪些是边缘,那就是 6 种情况。

      至少小于 256。 :)

      【讨论】:

      • 我无法真正理解这是如何工作的。您能否提供一些示例代码或伪算法?
      【解决方案7】:

      只是一个快速破解。使用 3x3 数组和一些模运算可能会大大降低内存访问次数,但它可能看起来很难看。警告我没有通过编译器运行它,所以它可能包含拼写错误。

      wchar_t maze_wall(int** input,int rows, int cols){
      
         wchar_t** output;
         int i,j;
         int N,E,S,W,NE,SE,NW,SW;
      
         output = (wchar_t**) malloc(sizeof(wchar_t*)*rows);
         for(i=0;i<cols;i++)
            output[i]= (wchar_t*) malloc(sizeof(wchar_t)*cols);
         for(i=0;i<rows;i++){
            for(j=0;j<cols;j++){
              if(input[i][j] ==1)
                 {output[i][j] = '1'; continue;}
              N=E=S=W=NE=SE=NW=SW=0;
              if(i != 0) /*We are not at the top*/
                 N = input[i-1][j];
              if(i != rows-1) /* We are not at the bottom*/
                S = input[i+1][j];
              if(j != rows-1) /* We are not at the right side*/
                E = input[i][j+1];
              if(j != 0) /* We are not at the left side*/
                W = input[i][j-1];
            /*Expand this for the corners*/
      
            if(N+E+S+W+NE+SE+SW+NE+NW == 0)
                {output[i][j] = ' '; continue;}
      
            /*Fill it in for the other six cases {'└', '┐', '┌', '┘', '-', '|'} */
            } 
         }
         return output; 
      }  
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多