[algorithm] 나선형으로 반복

친구에게는 NxM 행렬의 요소를 반복 할 수있는 알고리즘이 필요했습니다 (N과 M이 홀수 임). 나는 해결책을 찾았지만 동료 SO’ers가 더 나은 해결책을 찾을 수 있는지 알고 싶었다.

이 질문에 대한 답변으로 솔루션을 게시하고 있습니다.

출력 예 :

3×3 행렬의 경우 출력은 다음과 같아야합니다.

(0, 0) (1, 0) (1, 1) (0, 1) (-1, 1) (-1, 0) (-1, -1) (0, -1) (1, -1 )

3x3 매트릭스

또한이 알고리즘은 비 제곱 행렬을 지원해야하므로 예를 들어 5×3 행렬의 경우 출력은 다음과 같아야합니다.

(0, 0) (1, 0) (1, 1) (0, 1) (-1, 1) (-1, 0) (-1, -1) (0, -1) (1, -1 ) (2, -1) (2, 0) (2, 1) (-2, 1) (-2, 0) (-2, -1)

5x3 매트릭스



답변

내 솔루션은 다음과 같습니다 (파이썬).

def spiral(X, Y):
    x = y = 0
    dx = 0
    dy = -1
    for i in range(max(X, Y)**2):
        if (-X/2 < x <= X/2) and (-Y/2 < y <= Y/2):
            print (x, y)
            # DO STUFF...
        if x == y or (x < 0 and x == -y) or (x > 0 and x == 1-y):
            dx, dy = -dy, dx
        x, y = x+dx, y+dy


답변

C ++ 누군가? 완전성을 위해 게시 된 파이썬에서 빠른 번역

void Spiral( int X, int Y){
    int x,y,dx,dy;
    x = y = dx =0;
    dy = -1;
    int t = std::max(X,Y);
    int maxI = t*t;
    for(int i =0; i < maxI; i++){
        if ((-X/2 <= x) && (x <= X/2) && (-Y/2 <= y) && (y <= Y/2)){
            // DO STUFF...
        }
        if( (x == y) || ((x < 0) && (x == -y)) || ((x > 0) && (x == 1-y))){
            t = dx;
            dx = -dy;
            dy = t;
        }
        x += dx;
        y += dy;
    }
}


답변

let x = 0
let y = 0
let d = 1
let m = 1

while true
  while 2 * x * d < m
    print(x, y)
    x = x + d
  while 2 * y * d < m
    print(x, y)
    y = y + d
  d = -1 * d
  m = m + 1

이 문제에 대한 여러 가지 제안 된 솔루션이 다양한 프로그래밍 언어로 작성되었지만 모두 동일한 복잡한 접근 방식에서 비롯된 것 같습니다. 유도를 사용하여 간결하게 표현할 수있는 나선을 계산하는 더 일반적인 문제를 고려할 것입니다.

기본 사례 : (0, 0)에서 시작하여 1 칸 앞으로 이동, 좌회전, 1 칸 앞으로 이동, 좌회전. 유도 단계 : n + 1 제곱 앞으로 이동, 좌회전, n + 1 제곱 앞으로 이동, 좌회전.

이 문제를 표현하는 수학적 우아함은 해를 계산하기위한 간단한 알고리즘이 있어야한다는 것을 강력하게 암시합니다. 추상화를 염두에두고 알고리즘을 특정 프로그래밍 언어로 구현하지 않고 의사 코드로 구현하기로했습니다.

먼저 4 쌍의 while 루프를 사용하여 나선형의 2 회 반복 만 계산하는 알고리즘을 살펴 보겠습니다. 각 쌍의 구조는 비슷하지만 그 자체로 구별됩니다. 이것은 처음에는 미친 것처럼 보일 수 있지만 (일부 루프는 한 번만 실행 됨) 단계별로 우리는 동일한 4 쌍의 루프에 도달 할 때까지 변환을 수행하므로 다른 루프 안에 배치 된 단일 쌍으로 대체 할 수 있습니다. 이를 통해 조건을 사용하지 않고 반복 계산을위한 일반적인 솔루션을 제공 할 수 있습니다.

let x = 0
let y = 0

//RIGHT, UP
while x < 1
  print(x, y)
  x = x + 1
while y < 1
  print(x, y)
  y = y + 1

//LEFT, LEFT, DOWN, DOWN
while x > -1
  print(x, y)
  x = x - 1
while y > -1
  print(x, y)
  y = y - 1

//RIGHT, RIGHT, RIGHT, UP, UP, UP
while x < 2
  print(x, y)
  x = x + 1
while y < 2
  print(x, y)
  y = y + 1

//LEFT, LEFT, LEFT, LEFT, DOWN, DOWN, DOWN, DOWN
while x > -2
  print(x, y)
  x = x - 1
while y > -2
  print(x, y)
  y = y - 1

우리가 할 첫 번째 변형은 방향을 위해 +1 또는 -1 값을 보유하는 새로운 변수 d를 도입하는 것입니다. 방향은 각 루프 쌍 후에 전환됩니다. 모든 점에서 d의 값을 알기 때문에 각 부등식의 각 변에 곱하고 부등식의 방향을 조정하고 d에 대한 상수를 다른 상수에 대한 곱셈을 단순화 할 수 있습니다. 이것은 우리에게 다음을 남깁니다.

let x = 0
let y = 0
let d = 1

//RIGHT, UP
while x * d < 1
  print(x, y)
  x = x + d
while y * d < 1
  print(x, y)
  y = y + d
d = -1 * d

//LEFT, LEFT, DOWN, DOWN
while x * d < 1
  print(x, y)
  x = x + d
while y * d < 1
  print(x, y)
  y = y + d
d = -1 * d

//RIGHT, RIGHT, RIGHT, UP, UP, UP
while x * d < 2
  print(x, y)
  x = x + d
while y * d < 2
  print(x, y)
  y = y + d
d = -1 * d

//LEFT, LEFT, LEFT, LEFT, DOWN, DOWN, DOWN, DOWN
while x * d < 2
  print(x, y)
  x = x + d
while y * d < 2
  print(x, y)
  y = y + d

이제 x * d와 RHS는 모두 정수이므로 불평등의 결과에 영향을주지 않으면 서 RHS에서 0과 1 사이의 실제 값을 뺄 수 있습니다. 더 많은 패턴을 설정하기 위해 다른 모든 한 쌍의 while 루프의 불평등에서 0.5를 뺍니다.

let x = 0
let y = 0
let d = 1

//RIGHT, UP
while x * d < 0.5
  print(x, y)
  x = x + d
while y * d < 0.5
  print(x, y)
  y = y + d
d = -1 * d

//LEFT, LEFT, DOWN, DOWN
while x * d < 1
  print(x, y)
  x = x + d
while y * d < 1
  print(x, y)
  y = y + d
d = -1 * d

//RIGHT, RIGHT, RIGHT, UP, UP, UP
while x * d < 1.5
  print(x, y)
  x = x + d
while y * d < 1.5
  print(x, y)
  y = y + d
d = -1 * d

//LEFT, LEFT, LEFT, LEFT, DOWN, DOWN, DOWN, DOWN
while x * d < 2
  print(x, y)
  x = x + d
while y * d < 2
  print(x, y)
  y = y + d

이제 각 while 루프 쌍에서 수행하는 단계 수에 대해 다른 변수 m을 도입 할 수 있습니다.

let x = 0
let y = 0
let d = 1
let m = 0.5

//RIGHT, UP
while x * d < m
  print(x, y)
  x = x + d
while y * d < m
  print(x, y)
  y = y + d
d = -1 * d
m = m + 0.5

//LEFT, LEFT, DOWN, DOWN
while x * d < m
  print(x, y)
  x = x + d
while y * d < m
  print(x, y)
  y = y + d
d = -1 * d
m = m + 0.5

//RIGHT, RIGHT, RIGHT, UP, UP, UP
while x * d < m
  print(x, y)
  x = x + d
while y * d < m
  print(x, y)
  y = y + d
d = -1 * d
m = m + 0.5

//LEFT, LEFT, LEFT, LEFT, DOWN, DOWN, DOWN, DOWN
while x * d < m
  print(x, y)
  x = x + d
while y * d < m
  print(x, y)
  y = y + d

마지막으로, 각 한 쌍의 while 루프의 구조는 동일하며 다른 루프 안에 배치 된 단일 루프로 축소 될 수 있습니다. 또한 실제 숫자를 사용하지 않기 위해 m의 초기 값을 곱했습니다. 값 m은 증분된다; 각 불평등의 양쪽에 2 씩

이것은이 답변의 시작 부분에 표시된 솔루션으로 이어집니다.


답변

다음은 O는 제곱 된 나선형의 위치를 찾기 위해 (1) 솔루션입니다 : 바이올린

function spiral(n) {
    // given n an index in the squared spiral
    // p the sum of point in inner square
    // a the position on the current square
    // n = p + a

    var r = Math.floor((Math.sqrt(n + 1) - 1) / 2) + 1;

    // compute radius : inverse arithmetic sum of 8+16+24+...=
    var p = (8 * r * (r - 1)) / 2;
    // compute total point on radius -1 : arithmetic sum of 8+16+24+...

    var en = r * 2;
    // points by face

    var a = (1 + n - p) % (r * 8);
    // compute de position and shift it so the first is (-r,-r) but (-r+1,-r)
    // so square can connect

    var pos = [0, 0, r];
    switch (Math.floor(a / (r * 2))) {
        // find the face : 0 top, 1 right, 2, bottom, 3 left
        case 0:
            {
                pos[0] = a - r;
                pos[1] = -r;
            }
            break;
        case 1:
            {
                pos[0] = r;
                pos[1] = (a % en) - r;

            }
            break;
        case 2:
            {
                pos[0] = r - (a % en);
                pos[1] = r;
            }
            break;
        case 3:
            {
                pos[0] = -r;
                pos[1] = r - (a % en);
            }
            break;
    }
    console.log("n : ", n, " r : ", r, " p : ", p, " a : ", a, "  -->  ", pos);
    return pos;
}


답변

나는 파이썬 생성기를 좋아합니다.

def spiral(N, M):
    x,y = 0,0
    dx, dy = 0, -1

    for dumb in xrange(N*M):
        if abs(x) == abs(y) and [dx,dy] != [1,0] or x>0 and y == 1-x:
            dx, dy = -dy, dx            # corner, change direction

        if abs(x)>N/2 or abs(y)>M/2:    # non-square
            dx, dy = -dy, dx            # change direction
            x, y = -y+dx, x+dy          # jump

        yield x, y
        x, y = x+dx, y+dy

테스트 :

print 'Spiral 3x3:'
for a,b in spiral(3,3):
    print (a,b),

print '\n\nSpiral 5x3:'
for a,b in spiral(5,3):
    print (a,b),

당신은 얻는다 :

Spiral 3x3:
(0, 0) (1, 0) (1, 1) (0, 1) (-1, 1) (-1, 0) (-1, -1) (0, -1) (1, -1)

Spiral 5x3:
(0, 0) (1, 0) (1, 1) (0, 1) (-1, 1) (-1, 0) (-1, -1) (0, -1) (1, -1) (2, -1) (2, 0) (2, 1) (-2, 1) (-2, 0) (-2, -1)


답변

C ++ 변형을 기반으로하는 Java 나선형 “코드 골프”시도.

public static void Spiral(int X, int Y) {
    int x=0, y=0, dx = 0, dy = -1;
    int t = Math.max(X,Y);
    int maxI = t*t;

    for (int i=0; i < maxI; i++){
        if ((-X/2 <= x) && (x <= X/2) && (-Y/2 <= y) && (y <= Y/2)) {
            System.out.println(x+","+y);
            //DO STUFF
        }

        if( (x == y) || ((x < 0) && (x == -y)) || ((x > 0) && (x == 1-y))) {
            t=dx; dx=-dy; dy=t;
        }
        x+=dx; y+=dy;
    }
}


답변

다음은 현재 방향, 반경 또는 다른 것을 추적 할 필요없이 이전 (x, y) 좌표를 이전 좌표에서 직접 쉽고 쉽게 계산할 수 있음을 보여주는 C ++ 솔루션입니다.

void spiral(const int M, const int N)
{
    // Generate an Ulam spiral centered at (0, 0).
    int x = 0;
    int y = 0;

    int end = max(N, M) * max(N, M);
    for(int i = 0; i < end; ++i)
    {
        // Translate coordinates and mask them out.
        int xp = x + N / 2;
        int yp = y + M / 2;
        if(xp >= 0 && xp < N && yp >= 0 && yp < M)
            cout << xp << '\t' << yp << '\n';

        // No need to track (dx, dy) as the other examples do:
        if(abs(x) <= abs(y) && (x != y || x >= 0))
            x += ((y >= 0) ? 1 : -1);
        else
            y += ((x >= 0) ? -1 : 1);
    }
}

나선형으로 첫 번째 N 포인트를 생성하는 것만으로 (원래 문제의 N x M 영역으로의 마스킹 제한없이) 코드가 매우 간단 해집니다.

void spiral(const int N)
{
    int x = 0;
    int y = 0;
    for(int i = 0; i < N; ++i)
    {
        cout << x << '\t' << y << '\n';
        if(abs(x) <= abs(y) && (x != y || x >= 0))
            x += ((y >= 0) ? 1 : -1);
        else
            y += ((x >= 0) ? -1 : 1);
    }
}

요령은 x와 y를 비교하여 현재 사각형의 어느 쪽을 결정하고 어떤 방향으로 이동 할지를 알려줍니다.