【问题标题】:How to count a cell's neighbors in a cellular automaton with wraparound如何在带环绕的元胞自动机中计算单元格的邻居
【发布时间】:2017-08-25 19:20:21
【问题描述】:

所以我正在制作一个模拟 Life-like 元胞自动机的程序,但我在计算单元格的活邻居时遇到了一些问题。问题是我希望能够改变网格环绕的方式——也就是说,它是否从 从左到右(即圆柱形)环绕,从 从上到下 和 从左到右(即环形),或 根本不(即平坦)——我不知道如何制作我的方法考虑到这一点。这是我目前所拥有的:

public int getLiveNeighbors(int row, int col)
{
    int count = 0;

    // "topology" is an int that represents wraparound:
    // 0 = flat; 1 = cylindrical; 2 = toroidal
    int top = topology != 2 ? row - 1 : (row + ROWS - 1) % ROWS;
    int bottom = topology != 2 ? row + 1 : (row + 1) % ROWS;
    int left = topology != 0 ? (col + COLS - 1) % COLS : col - 1;
    int right = topology != 0 ? (col + 1) % COLS : col + 1;

    for (int r = top; r < bottom + 1; r++)
        for (int c = left; c < right + 1; c++)
            if (!(r == row && c == col) && getCell(r, c).equals(LIVE))
                count++;
}

我认为,关键是for-loop 中的if-语句——必须有某种方法来检查rc 是否在网格的范围内,而请记住,“边界”的定义将根据网格是否/如何环绕而有所不同。在过去,我通过使用三个不同的集合(每个环绕设置一个)来解决这个问题,其中包含八个不同的 if 语句,以分别检查组成原始单元邻域的八个单元中的每一个;正如你可以想象的那样,它不是很漂亮,但至少它有效。

我不太擅长解释我自己的代码,所以我希望这不会太令人困惑——我自己也觉得有点胡思乱想(哈)。如果有人有任何问题,请随时提问!

【问题讨论】:

    标签: java cellular-automata


    【解决方案1】:

    您可能已经有一个像 Board 这样的类,带有像 getCell(x, y) 这样的方法(至少在您的代码中存在这种方法)。

    我只是让这个方法宽松一些,它可以接受大于或等于COLSROWS 的负数xyxy。因此,您可以将col - 1 迭代到col + 1row - 1row + 1(减去colrow)而不关心这些坐标是否“超出范围”。正确查找坐标是Board 的任务。

    使您的代码更难的还有您在一个地方处理不同的拓扑。很难跟上。

    您可以通过实现Board 的不同子类(如CylindricalBoardToroidalBoardFlatBoard)来简化它。每个子类都会以不同的方式实现getCell,但在子类的上下文中它会很容易理解。

    【讨论】:

      【解决方案2】:

      您正在寻找Strategy Pattern

      在一些常见情况下,类仅在行为上有所不同。对于这种情况,最好将算法隔离在不同的类中,以便能够在运行时选择不同的算法。

      在这种情况下,你会想要这样的东西(为了清楚起见,缩写):

      class Point {
          int x;
          int y;
      }
      
      interface WrapStrategy {
          Point moveUp(Point p);
          Point moveDown(Point p);
          Point moveLeft(Point p);
          Point moveRight(Point p);
      }
      
      class CylinderWrapping implements WrapStrategy {
          int height;
          int circumference;
          Point moveUp(Point p) {
              if (p.y <= 0)
                  return null; // cannot move up
              return new Point(p.x, p.y - 1);
          }
          Point moveDown(Point p) {
              if (p.y >= height - 1)
                  return null; // cannot move down
              return new Point(p.x, p.y + 1);
          }
          Point moveLeft(Point p) {
              if (p.x <= 0)
                  return new Point(circumference - 1, p.y);
              return new Point(p.x - 1, p.y);
          }
          Point moveRight(Point p) {
              if (p.x >= circumference - 1)
                  return new Point(0, p.y);
              return new Point(p.x + 1, p.y);
          }
      }
      

      【讨论】:

        【解决方案3】:

        试试这个:

        import java.awt.Point;
        
        public class Neighbours {
        
            public static void main(String[] args) {
                Neighbours inst=new Neighbours();
                int r=3;//<ROWS
                int c=3;//<COLS
                for(int i :new int[]{0,1,2}){
                    inst.type=i;
                    System.out.format("There are %d neighbours of point (%d,%d), topography type %d\n", inst.countLiveNeighbours(r, c), c, r,i);
                }
            }
        
            int ROWS=4;
            int COLS=4;
            int type=0;//0=flat, 1=cylinder, 2=toroid
        
            /**
             * Is x,y a neighbour of r,c?
             * @return coordinates of neighbour or null
             */
            Point neighbour(int x, int y, int r, int c){
                if((x==c)&&(y==r))
                    return null;
                switch (type){
        /*this is wrong for the reasons explained below
                case 0: return ((x<COLS)&&(y<ROWS)) ? new Point (x,y) : null;
                case 1: return y<ROWS ? new Point(x%COLS,y) : null;
                case 2: return new Point(x%COLS,y%ROWS);
        */
         //replacement statements produce the correct behaviour
            case 0: return ((x<COLS)&&(x>-1)&&(y<ROWS)&&(y>-1)) ? new Point (x,y) : null;
            case 1: return ((y<ROWS)&&(y>-1)) ? new Point(Math.floorMod(x,COLS),y) : null;
            case 2: return new Point(Math.floorMod(x,COLS),Math.floorMod(y,ROWS));
                }
                return null;
            }
        
            int countLiveNeighbours(int r, int c){
                int result=0;
                for(int x=c-1; x<c+2; x++)
                    for(int y=r-1; y<r+2; y++){
                        Point p=neighbour(x,y,r,c);
                        if(live(p)){
                            System.out.format("\tpoint (%d,%d)\n",(int)p.getX(),(int)p.getY());
                            result++;
                        }
                    }
                return result;
            }
        
            boolean live(Point p){
                boolean result=true;
                if(p==null)
                    return false;
                //perform tests for liveness here and set result
                return result;
            }
        }
        

        【讨论】:

        • 好的,由于 x 和 y 的负索引情况(网格从 0,0 开始),我在这里走得太快了。此外,Java % 运算符返回的是余数而不是正确的模数。这对负数很重要(例如 -1%4=-1 而不是 3)。 Math.floorMod 产生正确的行为。请参阅上面的替换案例声明。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-16
        • 1970-01-01
        • 2011-08-01
        • 1970-01-01
        • 2016-04-26
        相关资源
        最近更新 更多