Looping in a spiral

2018-12-31 18:28发布

A friend was in need of an algorithm that would let him loop through the elements of an NxM matrix (N and M are odd). I came up with a solution, but I wanted to see if my fellow SO'ers could come up with a better solution.

I'm posting my solution as an answer to this question.

Example Output:

For a 3x3 matrix, the output should be:

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

3x3 matrix

Furthermore, the algorithm should support non-square matrices, so for example for a 5x3 matrix, the output should be:

(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 matrix

30条回答
流年柔荑漫光年
2楼-- · 2018-12-31 19:18

TDD, in Java.

SpiralTest.java:

import java.awt.Point;
import java.util.List;

import junit.framework.TestCase;

public class SpiralTest extends TestCase {

    public void test3x3() throws Exception {
        assertEquals("(0, 0) (1, 0) (1, 1) (0, 1) (-1, 1) (-1, 0) (-1, -1) (0, -1) (1, -1)", strung(new Spiral(3, 3).spiral()));
    }

    public void test5x3() throws Exception {
        assertEquals("(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)",
                strung(new Spiral(5, 3).spiral()));
    }

    private String strung(List<Point> points) {
        StringBuffer sb = new StringBuffer();
        for (Point point : points)
            sb.append(strung(point));
        return sb.toString().trim();
    }

    private String strung(Point point) {
        return String.format("(%s, %s) ", point.x, point.y);
    }

}

Spiral.java:

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

public class Spiral {
    private enum Direction {
    E(1, 0) {Direction next() {return N;}},
    N(0, 1) {Direction next() {return W;}},
    W(-1, 0) {Direction next() {return S;}},
    S(0, -1) {Direction next() {return E;}},;

        private int dx;
        private int dy;

        Point advance(Point point) {
            return new Point(point.x + dx, point.y + dy);
        }

        abstract Direction next();

        Direction(int dx, int dy) {
            this.dx = dx;
            this.dy = dy;
        }
    };
    private final static Point ORIGIN = new Point(0, 0);
    private final int   width;
    private final int   height;
    private Point       point;
    private Direction   direction   = Direction.E;
    private List<Point> list = new ArrayList<Point>();

    public Spiral(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public List<Point> spiral() {
        point = ORIGIN;
        int steps = 1;
        while (list.size() < width * height) {
            advance(steps);
            advance(steps);
            steps++;
        }
        return list;
    }

    private void advance(int n) {
        for (int i = 0; i < n; ++i) {
            if (inBounds(point))
                list.add(point);
            point = direction.advance(point);
        }
        direction = direction.next();
    }

    private boolean inBounds(Point p) {
        return between(-width / 2, width / 2, p.x) && between(-height / 2, height / 2, p.y);
    }

    private static boolean between(int low, int high, int n) {
        return low <= n && n <= high;
    }
}
查看更多
公子世无双
3楼-- · 2018-12-31 19:18

I have an open source library, pixelscan, that is a python library that provides functions to scan pixels on a grid in a variety of spatial patterns. Spatial patterns included are circular, rings, grids, snakes, and random walks. There are also various transformations (e.g., clip, swap, rotate, translate). The original OP problem can be solved as follows

for x, y in clip(swap(ringscan(0, 0, 0, 2)), miny=-1, maxy=1):
    print x, y

which yields the points

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

The libraries generators and transformations can be chained to change the points in a wide variety of orders and spatial patterns.

查看更多
ら面具成の殇う
4楼-- · 2018-12-31 19:19

This is based on your own solution, but we can be smarter about finding the corners. This makes it easier to see how you might skip over the areas outside if M and N are very different.

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

and a generator based solution that is better than O(max(n,m)^2), It is O(nm+abs(n-m)^2) because it skips whole strips if they are not part of the solution.

def spiral(X,Y):
X = X+1>>1
Y = Y+1>>1
x = y = 0
d = side = 1
while x<X or y<Y:
    if abs(y)<Y:
        for x in range(x, x+side, d):
            if abs(x)<X: yield x,y
        x += d
    else:
        x += side
    if abs(x)<X:
        for y in range(y, y+side, d):
            if abs(y)<Y: yield x,y
        y += d
    else:
        y += side
    d =-d
    side = d-side
查看更多
看风景的人
5楼-- · 2018-12-31 19:20

This is my approach for a square spiral in c#, i made this some time ago, i just thought i could add it, since it's different from all the others, not the best, but just a different way, i am sure it can be adapted for a non-square too.

This approach i take the max number of steps in, instead of the max vector tho.

The main thing about this approach is the corners, there's some adjustments for the first step and the "progress" step needed to go out of the "corner" in the right bottom corner.

private void Spiral(int sequence)
{
    const int x = 0;
    const int y = 1;
    int[,] matrix = new int[2, sequence];
    int dirX, dirY, prevX, prevY, curr;
    dirX = dirY = prevX = prevY = curr = default(int);

    do
    {
        if (curr > 0)
        {
            prevX = matrix[x, curr - 1];
            prevY = matrix[y, curr - 1];
        }

        //Change direction based on the corner.
        if (Math.Abs(prevX) == Math.Abs(prevY) && curr > 0)
        {
            dirX = dirY = 0;

            if (prevY > 0 && prevX > 0)
                dirX = -1;
            else if (prevY > 0 && prevX < 0)
                dirY = -1;
            else if (prevY < 0 && prevX < 0)
                dirX = 1;
            else if (prevY < 0 && prevX > 0) //Move forward
                dirX = 1;
            else if (prevY == 0 && prevX == 0) //For the first step.
                dirX = 1;
        }
        else if (prevY < 0 && prevX > 0 && (Math.Abs(matrix[x, curr - 2]) == Math.Abs(matrix[y, curr - 2]))) //Move forward
        {
            dirX = 0;
            dirY = 1;
        }
        else if (prevX == 1 && prevY == 0) //For the second step.
        {
            dirY = 1;
            dirX = 0;
        }

        matrix[x, curr] = prevX + dirX;
        matrix[y, curr] = prevY + dirY;

        System.Console.Write($"({matrix[x, curr]},{matrix[y, curr]}) ");

    } while (++curr < sequence);
}
查看更多
像晚风撩人
6楼-- · 2018-12-31 19:21

I made this one with a friend that adjusts the spiral to the canvas aspect ratio on Javascript. Best solution I got for a image evolution pixel by pixel, filling the entire image.

Hope it helps some one.

var width = 150;
var height = 50;

var x = -(width - height)/2;
var y = 0;
var dx = 1;
var dy = 0;
var x_limit = (width - height)/2;
var y_limit = 0;
var counter = 0;

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');

setInterval(function(){
   if ((-width/2 < x && x <= width/2)  && (-height/2 < y && y <= height/2)) {
       console.log("[ " + x + " , " +  y + " ]");
       ctx.fillStyle = "#FF0000";
       ctx.fillRect(width/2 + x, height/2 - y,1,1);
   }
   if( dx > 0 ){//Dir right
       if(x > x_limit){
           dx = 0;
           dy = 1;
       }
   }
   else if( dy > 0 ){ //Dir up
       if(y > y_limit){
           dx = -1;
           dy = 0;
       }
   }
   else if(dx < 0){ //Dir left
       if(x < (-1 * x_limit)){
           dx = 0;
           dy = -1;
       }
   }
   else if(dy < 0) { //Dir down
       if(y < (-1 * y_limit)){
           dx = 1;
           dy = 0;
           x_limit += 1;
           y_limit += 1;
       }
   }
   counter += 1;
   //alert (counter);
   x += dx;
   y += dy;      
}, 1);

You can see it working on http://jsfiddle.net/hitbyatruck/c4Kd6/ . Just be sure to change the width and height of the canvas on the javascript vars and on the attributes on the HTML.

查看更多
梦该遗忘
7楼-- · 2018-12-31 19:22

Here's c#, linq'ish.

public static class SpiralCoords
{
  public static IEnumerable<Tuple<int, int>> GenerateOutTo(int radius)
  {
    //TODO trap negative radius.  0 is ok.

    foreach(int r in Enumerable.Range(0, radius + 1))
    {
      foreach(Tuple<int, int> coord in GenerateRing(r))
      {
        yield return coord;
      }
    }
  }

  public static IEnumerable<Tuple<int, int>> GenerateRing(int radius)
  {
    //TODO trap negative radius.  0 is ok.

    Tuple<int, int> currentPoint = Tuple.Create(radius, 0);
    yield return Tuple.Create(currentPoint.Item1, currentPoint.Item2);

    //move up while we can
    while (currentPoint.Item2 < radius)
    {
      currentPoint.Item2 += 1;
      yield return Tuple.Create(currentPoint.Item1, currentPoint.Item2);
    }
    //move left while we can
    while (-radius < currentPoint.Item1)
    {
      currentPoint.Item1 -=1;
      yield return Tuple.Create(currentPoint.Item1, currentPoint.Item2);    
    }
    //move down while we can
    while (-radius < currentPoint.Item2)
    {
      currentPoint.Item2 -= 1;
      yield return Tuple.Create(currentPoint.Item1, currentPoint.Item2);
    }
    //move right while we can
    while (currentPoint.Item1 < radius)
    {
      currentPoint.Item1 +=1;
      yield return Tuple.Create(currentPoint.Item1, currentPoint.Item2);    
    }
    //move up while we can
    while (currentPoint.Item2 < -1)
    {
      currentPoint.Item2 += 1;
      yield return Tuple.Create(currentPoint.Item1, currentPoint.Item2);
    }
  }

}

The question's first example (3x3) would be:

var coords = SpiralCoords.GenerateOutTo(1);

The question's second example (5x3) would be:

var coords = SpiralCoords.GenerateOutTo(2).Where(x => abs(x.Item2) < 2);
查看更多
登录 后发表回答