在螺旋中循环

Posted

技术标签:

【中文标题】在螺旋中循环【英文标题】:Looping in a spiral 【发布时间】:2010-09-28 17:42:13 【问题描述】:

一位朋友需要一种算法,可以让他遍历 NxM 矩阵的元素(N 和 M 是奇数)。我想出了一个解决方案,但我想看看我的 SO'ers 是否能想出一个更好的解决方案。

我将发布我的解决方案作为对这个问题的回答。

示例输出:

对于 3x3 矩阵,输出应为:

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

此外,该算法应支持非方阵,例如对于 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)

【问题讨论】:

你能解释一下你想要什么非方阵吗?您的解决方案有一个从 (2,1) 到 (-2,1) 的“跳跃”——这是有意的吗? [例如。对于一个 7x3 矩阵,它会有两个“跳跃”,而对于一个 (2k+1)x3 矩阵,它会有 2k-3 个跳跃?] 是的,跳跃是故意的。我用 5x3 矩阵图像更新了问题。从图片中可以看出,我们跳过了顶行和底行。 好的,那么您自己的代码看起来最干净。尽管这是题外话:你是如何生成这些图像的? :) =)) 我没有生成它们。事实上,我创建它们的方式非常愚蠢。我在 OO.org Calc 中创建了表格,截取了屏幕截图,并在 GIMP 中编辑了屏幕截图。 =)) @Ying:我真的不知道我的朋友为什么需要这个,但他说他想在搜索算法中偏爱靠近中心的矩阵成员。 【参考方案1】:
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 对相同的循环,因此可以用放置在另一个循环内的一对循环来替换。 这将为我们提供一个不使用任何条件的计算 n 次迭代的通用解决方案。

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

我们将进行的第一个转换是引入一个新变量 d,表示方向,它的值可以是 +1 或 -1。每对循环后方向切换。由于我们知道所有点的 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

我们现在可以引入另一个变量 m 来表示我们在每对 while 循环中执行的步数。

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。

这导致了此答案开头显示的解决方案。

编辑:已经有几年了,但我遇到了类似的问题,并在 F# 中编写了以下解决方案,我想分享一下。在我的原始答案中,print 这个词可能用词不当,但希望这个非伪代码版本能够解决 cmets 中关于多功能性和终止条件的任何问题。我添加了示例用例,用于围绕任意点旋转并为迭代 NxM 矩阵找到原始问题的正确解决方案。

let spiral =
    let rec f (x, y) d m = seq 
        let mutable x = x
        let mutable y = y
        while 2 * x * d < m do
            yield x, y
            x <- x + d
        while 2 * y * d < m do
            yield x, y
            y <- y + d
        yield! f (x, y) -d (m + 1)
    
    f (0, 0) 1 1

spiral
|> Seq.take 5
|> List.ofSeq;;
// [(0, 0); (1, 0); (1, 1); (0, 1); (-1, 1)]

spiral
|> Seq.take 5
|> Seq.map (fun (x, y) -> x + 5, y + 5)
|> List.ofSeq;;
// [(5, 5); (6, 5); (6, 6); (5, 6); (4, 6)]

spiral
|> Seq.takeWhile (fun (x, y) -> x * x + y * y < 9)
|> Seq.filter (fun (x, y) -> -2 <= x && x <= 2 && -1 <= y && y <= 1)
|> List.ofSeq;;
// [(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)]

【讨论】:

您的最终解决方案会在什么条件下终止? 这种图案印刷有什么用途? @MerlynMorgan-Graham 当计算机内存或电源耗尽时终止。 虽然最初的问题是关于 NxM 矩阵的,但如果您需要无休止地螺旋向外直到找到某些东西(即,然后中断或返回),这实际上是一个非常有用的答案。当然,就像提到的其他 cmets 一样,您确实需要定义终止条件,否则它将永远运行。 @Mike:这是正确的答案。简单高效。谢谢【参考方案2】:

一个Kotlin 螺旋。

data class Point(val x: Int, val y: Int) 
    operator fun plus(p: Point): Point = Point(x + p.x, y + p.y)

    override fun toString() = "($x, $y)"

    companion object 
        enum class Directions(val d: Point) 
            RIGHT(Point(1, 0)),
            UP(Point(0, 1)),
            LEFT(Point(-1, 0)),
            DOWN(Point(0, -1))
        

        fun spiral() = sequence 
            var p = Point(0, 0)
            // Always start at the origin.
            yield(p)
            // 0, 2, 4, 6 ...
            generateSequence(0)  it + 2 .forEach  n ->
                // For each of the 4 directions
                Directions.values().forEach  d ->
                    // actual length depends slightly on direction
                    val l = n + when (d) 
                        Directions.RIGHT, Directions.UP -> 1
                        Directions.LEFT, Directions.DOWN -> 2
                    
                    // run to the next corner
                    for (i in 1..l) 
                        p += d.d
                        yield(p)
                    
                
            
        
    

【讨论】:

【参考方案3】:

你的问题看起来像一个叫做螺旋记忆的问题。 在那个问题中,网格上的每个正方形都以螺旋图案分配,从位于原点的数字 1 开始。然后在向外盘旋的同时向上计数。例如:

17  16  15  14  13

18   5   4   3  12

19   6   1   2  11

20   7   8   9  10

21  22  23  ---->

我根据这个螺旋模式计算每个数字的坐标的解决方案发布在下面:

def spiral_pattern(num):
    x = y = 0
    for _ in range(num-1):
        x, y = find_next(x, y)
    yield (x, y)


def find_next(x, y):
    """find the coordinates of the next number"""
    if x == 0 and y == 0:
        return 1, 0

    if abs(x) == abs(y):
        if x > 0 and y > 0:
            x, y = left(x, y)
        elif x < 0 and y > 0:
            x, y = down(x, y)
        elif x < 0 and y < 0:
            x, y = right(x, y)
        elif x > 0 and y < 0:
            x, y = x+1, y
    else:
        if x > y and abs(x) > abs(y):
            x, y = up(x, y)
        elif x < y and abs(x) < abs(y):
            x, y = left(x, y)
        elif x < y and abs(x) > abs(y):
            x, y = down(x, y)
        elif x > y and abs(x) < abs(y):
            x, y = right(x, y)

    return x, y

def up(x, y):
    return x, y+1


def down(x, y):
    return x, y-1


def left(x, y):
    return x-1, y


def right(x, y):
    return x+1, y

【讨论】:

【参考方案4】:

这是一个 Python/numpy 解决方案,可以用螺旋线填充任何矩形。它解决的问题与原始问题略有不同,但这正是我所需要的。

import numpy as np
import matplotlib.pyplot as plt

def spiral(m, n):
    M = np.zeros([m, n], dtype=int)
    i, j = 0, 0 # location of "turtle"
    di, dj = 0, 1 # direction of movement
    h = (np.min([m,n]))/2
    for ii in range(m * n):
        M[i, j] = ii
        if (i < h and (i == j+1 or i+1 == n-j)) or (i >= m-h and (m-i == n-j or m-i == j+1)):
            di, dj = dj, -di # turn clockwise
        i, j = i + di, j + dj
    return M

plt.imshow(spiral(16, 24))

【讨论】:

【参考方案5】:

这是 Python 3 中的一个解决方案,用于以顺时针和逆时针方向以螺旋方式打印连续整数。

import math

def sp(n): # spiral clockwise
    a=[[0 for x in range(n)] for y in range(n)]
    last=1
    for k in range(n//2+1):
      for j in range(k,n-k):
          a[k][j]=last
          last+=1
      for i in range(k+1,n-k):
          a[i][j]=last
          last+=1
      for j in range(n-k-2,k-1,-1):
          a[i][j]=last
          last+=1
      for i in range(n-k-2,k,-1):
          a[i][j]=last
          last+=1

    s=int(math.log(n*n,10))+2 # compute size of cell for printing
    form=":"+str(s)+""
    for i in range(n):
        for j in range(n):
            print(form.format(a[i][j]),end="")
        print("")

sp(3)
# 1 2 3
# 8 9 4
# 7 6 5

sp(4)
#  1  2  3  4
# 12 13 14  5
# 11 16 15  6
# 10  9  8  7

def sp_cc(n): # counterclockwise
    a=[[0 for x in range(n)] for y in range(n)]
    last=1
    for k in range(n//2+1):
      for j in range(n-k-1,k-1,-1):
          a[n-k-1][j]=last
          last+=1
      for i in range(n-k-2,k-1,-1):
          a[i][j]=last
          last+=1
      for j in range(k+1,n-k):
          a[i][j]=last
          last+=1
      for i in range(k+1,n-k-1):
          a[i][j]=last
          last+=1

    s=int(math.log(n*n,10))+2 # compute size of cell for printing
    form=":"+str(s)+""
    for i in range(n):
        for j in range(n):
            print(form.format(a[i][j]),end="")
        print("")

sp_cc(5)
#  9 10 11 12 13
#  8 21 22 23 14
#  7 20 25 24 15
#  6 19 18 17 16
#  5  4  3  2  1

说明

螺旋由同心正方形组成,例如顺时针旋转的 5x5 正方形如下所示:

 5x5        3x3      1x1

>>>>>
^   v       >>>
^   v   +   ^ v   +   >
^   v       <<<
<<<<v

&gt;&gt;&gt;&gt;&gt;表示“向右走5次”或增加列索引5次,v表示向下或增加行索引等)

所有正方形的大小都相同,我在同心正方形上循环。

对于每个正方形,代码有四个循环(每边一个),在每个循环中我们增加或减少列或行索引。 如果i 是行索引,j 是列索引,则可以通过以下方式构造一个 5x5 正方形: - 将j 从 0 增加到 4(5 次) - 将i 从 1 增加到 4(4 次) - 将j 从 3 减少到 0(4 次) - 将i 从 3 减少到 1(3 次)

对于下一个正方形(3x3 和 1x1),我们做同样的事情,但适当地移动初始和最终索引。 我为每个同心正方形使用了索引k,有 n//2 + 1 个同心正方形。

最后,一些关于漂亮打印的数学。

要打印索引:

def spi_cc(n): # counter-clockwise
    a=[[0 for x in range(n)] for y in range(n)]
    ind=[]
    last=n*n
    for k in range(n//2+1):
      for j in range(n-k-1,k-1,-1):
          ind.append((n-k-1,j))
      for i in range(n-k-2,k-1,-1):
          ind.append((i,j))
      for j in range(k+1,n-k):
          ind.append((i,j))
      for i in range(k+1,n-k-1):
          ind.append((i,j))

    print(ind)

spi_cc(5)

【讨论】:

【参考方案6】:

这是我在 c# 中制作方形螺旋的方法,我前一段时间做了这个,我只是想我可以添加它,因为它与其他所有不同,不是最好的,但只是一种不同的方式,我敢肯定它也可以适应非正方形。

这种方法我采用最大步数,而不是最大向量。

这种方法的主要内容是角落,第一步有一些调整,“进步”一步需要走出右下角的“角落”。

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);

【讨论】:

【参考方案7】:

这是 Julia 的答案:我的方法是在原点 (0,0) 周围分配同心正方形(“螺旋”)中的点,其中每个正方形的边长为 m = 2n + 1,以生成带有位置编号的有序字典(从 1 开始作为原点)作为键,对应的坐标作为值。

由于每个螺旋的最大位置在(n,-n),其余点可以通过简单地从该点向后工作来找到,即从右下角以m-1为单位,然后重复垂直的3段m-1 个单位。

下面这个过程是倒序写的,对应的是螺旋如何进行而不是这个反向计数过程,即ra[右升]段递减3(m+1),然后la[左升] 2(m+1) 等等 - 希望这是不言自明的。

import DataStructures: OrderedDict, merge

function spiral(loc::Int)
    s = sqrt(loc-1) |> floor |> Int
    if s % 2 == 0
        s -= 1
    end
    s = (s+1)/2 |> Int
    return s
end

function perimeter(n::Int)
    n > 0 || return OrderedDict([1,[0,0]])
    m = 2n + 1 # width/height of the spiral [square] indexed by n
    # loc_max = m^2
    # loc_min = (2n-1)^2 + 1
    ra = [[m^2-(y+3m-3), [n,n-y]] for y in (m-2):-1:0]
    la = [[m^2-(y+2m-2), [y-n,n]] for y in (m-2):-1:0]
    ld = [[m^2-(y+m-1), [-n,y-n]] for y in (m-2):-1:0]
    rd = [[m^2-y, [n-y,-n]] for y in (m-2):-1:0]
    return OrderedDict(vcat(ra,la,ld,rd))
end

function walk(n)
    cds = OrderedDict(1 => [0,0])
    n > 0 || return cds
    for i in 1:n
        cds = merge(cds, perimeter(i))
    end
    return cds
end

因此,对于您的第一个示例,将 m = 3 代入方程以查找 n 会得到 n = (5-1)/2 = 2,而 walk(2) 会给出位置到坐标的有序字典,您可以通过访问字典的vals 字段:

walk(2)
DataStructures.OrderedDictAny,Any with 25 entries:
  1  => [0,0]
  2  => [1,0]
  3  => [1,1]
  4  => [0,1]
  ⋮  => ⋮

[(co[1],co[2]) for co in walk(2).vals]
25-element ArrayTupleInt64,Int64,1:
 (0,0)  
 (1,0)  
 ⋮       
 (1,-2) 
 (2,-2)

请注意,对于某些功能 [e.g. norm] 最好将坐标保留在数组中而不是 TupleInt,Int,但在这里我根据要求使用列表理解将它们更改为元组 —(x,y)

未指定“支持”非方阵的上下文(请注意,此解决方案仍会计算离网值),但如果您只想过滤到 xy 的范围(这里是x=5,y=3),在计算完完整的螺旋之后,然后intersect这个矩阵与来自walk的值相对应。

grid = [[x,y] for x in -2:2, y in -1:1]
5×3 ArrayArrayInt64,1,2:
 [-2,-1]  [-2,0]  [-2,1]
   ⋮       ⋮       ⋮ 
 [2,-1]   [2,0]   [2,1]

[(co[1],co[2]) for co in intersect(walk(2).vals, grid)]
15-element ArrayTupleInt64,Int64,1:
 (0,0)  
 (1,0)  
 ⋮ 
 (-2,0) 
 (-2,-1)

【讨论】:

【参考方案8】:

Davidont 在 VB.Net 中的出色解决方案

    Public Function Spiral(n As Integer) As RowCol
    ' 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
    ' starts with row 0 col -1
    Dim r As Integer = CInt(Math.Floor((Math.Sqrt(n + 1) - 1) / 2) + 1)

    ' compute radius : inverse arithmetic sum of 8+16+24+...=
    Dim p As Integer = (8 * r * (r - 1)) \ 2
    ' compute total point on radius -1 : arithmetic sum of 8+16+24+...

    Dim en As Integer = r * 2
    ' points by face

    Dim a As Integer = (1 + n - p) Mod (r * 8)
    ' compute the position and shift it so the first is (-r,-r) but (-r+1,-r)
    ' so square can connect

    Dim row As Integer
    Dim col As Integer

    Select Case Math.Floor(a \ (r * 2))
        ' find the face : 0 top, 1 right, 2, bottom, 3 left
        Case 0
            row = a - r
            col = -r
        Case 1
            row = r
            col = (a Mod en) - r
        Case 2
            row = r - (a Mod en)
            col = r
        Case 3
            row = -r
            col = r - (a Mod en)
    End Select

    Return New RowCol(row, col)
End Function

【讨论】:

【参考方案9】:

下面是这个问题的 javascript (ES6) 迭代解决方案:

let spiralMatrix = (x, y, step, count) => 
    let distance = 0;
    let range = 1;
    let direction = 'up';

    for ( let i = 0; i < count; i++ ) 
        console.log('x: '+x+', y: '+y);
        distance++;
        switch ( direction ) 
            case 'up':
                y += step;
                if ( distance >= range ) 
                    direction = 'right';
                    distance = 0;
                
                break;
            case 'right':
                x += step;
                if ( distance >= range ) 
                    direction = 'bottom';
                    distance = 0;
                    range += 1;
                
                break;
            case 'bottom':
                y -= step;
                if ( distance >= range ) 
                    direction = 'left';
                    distance = 0;
                
                break;
            case 'left':
                x -= step;
                if ( distance >= range ) 
                    direction = 'up';
                    distance = 0;
                    range += 1;
                
                break;
            default:
                break;
        
    

使用方法如下:

spiralMatrix(0, 0, 1, 100);

这将创建一个向外的螺旋,从坐标 (x = 0, y = 0) 开始,步长为 1,项目总数等于 100。实现始终按以下顺序开始移动 - 向上,向右,底部,左侧。

请注意,此实现创建方阵。

【讨论】:

【参考方案10】:

TDD,在 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;
    

【讨论】:

@leppie:也许不是——当然还不够短——但我认为这是一个很好的 TDD 演示,而且代码相当干净、易于理解、正确。我会把它留在里面。【参考方案11】:

这是我的解决方案(在 Ruby 中)

def spiral(xDim, yDim)
   sx = xDim / 2
   sy = yDim / 2

   cx = cy = 0
   direction = distance = 1

   yield(cx,cy)
   while(cx.abs <= sx || cy.abs <= sy)
      distance.times  cx += direction; yield(cx,cy) if(cx.abs <= sx && cy.abs <= sy);  
      distance.times  cy += direction; yield(cx,cy) if(cx.abs <= sx && cy.abs <= sy);  
      distance += 1
      direction *= -1
   end
end

spiral(5,3)  |x,y|
   print "(#x,#y),"

【讨论】:

仍然是 O(max(n,m)^2),但风格不错。 direction=-direction 而不是direction*=-1?如果你打高尔夫球,d=-d 也比 d*=-1 短【参考方案12】:

Java 螺旋式“代码高尔夫”尝试,基于 C++ 变体。

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;
    

【讨论】:

【参考方案13】:

这是在平方螺旋中找到位置的 O(1) 解决方案:Fiddle

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;

【讨论】:

从中心开始添加两行。 if (n === 0) return [0, 0, r]; --n; 见小提琴:jsfiddle.net/Wishmesh/nwd9gt1s/2【参考方案14】:

我喜欢 python 的生成器。

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)

【讨论】:

【参考方案15】:

C++ 有人吗?来自python的快速翻译,为了完整性而发布

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;
    

【讨论】:

你也可以像我一样使用 s 和 ds 来检测消除巨大 if 条件的角 这篇文章的编辑是suggested here。尽管修改被拒绝是因为它改变了您帖子的含义,但如果这样做有意义,您可能需要考虑合并建议的更改。【参考方案16】:

这是我的解决方案(在 Python 中):

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

【讨论】:

据我所知,这是最好的写法。唯一可能的改进是通过直接跳过那些不会打印的 (x,y) 使其成为 O(MN) 而不是 O(max(M,N)^2),但这将使代码有点丑。 我正在优化我的解决方案,它与您已经拥有的非常接近。我认为这是一个很好的解决方案。除了 ShreevatsaR 的建议,以及每次迭代不计算 x/2 和 y/2 之类的东西,除了样式之外没有太多需要改进的地方。 matlab 有什么解决方案吗?! 这是否为访问图像缓冲区数据提供了良好的缓存一致性? (这里有很多答案,但关于哪个最适合高性能图像操作的信息并不多) @ideasman42 - 这不会起作用,因为结果总是相同的螺旋形坐标模式。我猜螺旋模式是否缓存一致取决于图像缓冲区的实现。 (我的猜测是它会比其他遍历图像的方式更多地破坏缓存,比如按顺序逐行)。但是生成这些坐标的算法选择可能不会影响缓存。【参考方案17】:

Python 使用Can Berk Güder answer 循环顺时针螺旋代码。

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

【讨论】:

顺时针方向 ? 我引用了 Can Berk Güder。原来的问题是逆时针?。我需要一个顺时针函数,所以我觉得把它留在那里会很有用。【参考方案18】:

我正在分享我为不同目的设计的这段代码;它是关于查找列号“X”和数组元素@螺旋索引“索引”的行号“Y”。此函数采用矩阵的宽度“w”和高度“h”以及所需的“索引”。当然,这个函数可以用来产生同样需要的输出。我认为这是最快的方法(因为它跳过单元格而不是扫描它们)。

    rec BuildSpiralIndex(long w, long h, long index = -1)
      
        long count = 0 , x = -1,  y = -1, dir = 1, phase=0, pos = 0,                            length = 0, totallength = 0;
        bool isVertical = false;
        if(index>=(w*h)) return null;

        do 
                        
            isVertical = (count % 2) != 0;
            length = (isVertical ? h : w) - count/2 - count%2 ;
            totallength += length;
            count++;
         while(totallength<index);

        count--; w--; h--;
        phase = (count / 4); pos = (count%4);
        x = (pos > 1 ? phase : w - phase);
        y = ((pos == 1 || pos == 2) ? h - phase : phase) + (1 * (pos == 3 ? 1 : 0));
        dir = pos > 1 ? -1 : 1;
        if (isVertical) y -= (totallength - index - 1) * dir;
        else x -= (totallength - index -1) * dir;
        return new rec  X = x, Y = y ;
    

【讨论】:

【参考方案19】:

C# 版本,也可以处理非方形尺寸。

private static Point[] TraverseSpiral(int width, int height) 
    int numElements = width * height + 1;
    Point[] points = new Point[numElements];

    int x = 0;
    int y = 0;
    int dx = 1;
    int dy = 0;
    int xLimit = width - 0;
    int yLimit = height - 1;
    int counter = 0;

    int currentLength = 1;
    while (counter < numElements) 
        points[counter] = new Point(x, y);

        x += dx;
        y += dy;

        currentLength++;
        if (dx > 0) 
            if (currentLength >= xLimit) 
                dx = 0;
                dy = 1;
                xLimit--;
                currentLength = 0;
            
         else if (dy > 0) 
            if (currentLength >= yLimit) 
                dx = -1;
                dy = 0;
                yLimit--;
                currentLength = 0;
            
         else if (dx < 0) 
            if (currentLength >= xLimit) 
                dx = 0;
                dy = -1;
                xLimit--;
                currentLength = 0;
            
         else if (dy < 0) 
            if (currentLength >= yLimit) 
                dx = 1;
                dy = 0;
                yLimit--;
                currentLength = 0;
            
        

        counter++;
    

    Array.Reverse(points);
    return points;

【讨论】:

【参考方案20】:

只是为了好玩的 Javascript:

function spiral(x, y) 
  var iy = ix = 0
    , hr = (x - 1) / 2
    , vr = (y - 1) / 2
    , tt = x * y
    , matrix = []
    , step = 1
    , dx = 1
    , dy = 0;

  while(matrix.length < tt) 

    if((ix <= hr && ix >= (hr * -1)) && (iy <= vr && (iy >= (vr * -1)))) 
      console.log(ix, iy);
      matrix.push([ix, iy]);
    

    ix += dx;
    iy += dy;

    // check direction
    if(dx !== 0) 
      // increase step
      if(ix === step && iy === (step * -1)) step++;

      // horizontal range reached
      if(ix === step || (ix === step * -1)) 
        dy = (ix === iy)? (dx * -1) : dx;
        dx = 0;  
      
     else 
      // vertical range reached
      if(iy === step || (iy === step * -1)) 
        dx = (ix === iy)? (dy * -1) : dy;
        dy = 0;
      
    
  

  return matrix;


var sp = spiral(5, 3);

【讨论】:

【参考方案21】:

我有一个开源库,pixelscan,这是一个 python 库,它提供了以各种空间模式扫描网格上的像素的功能。包括的空间模式有圆形、环形、网格、蛇形和随机游走。还有各种转换(例如,剪辑、交换、旋转、平移)。原来的OP问题可以这样解决

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

产生积分

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

可以将库生成器和转换链接起来,以各种顺序和空间模式更改点。

【讨论】:

【参考方案22】:

这是一个 C++ 解决方案,它表明您可以直接轻松地从之前的坐标计算下一个 (x, y) 坐标 - 无需跟踪当前方向、半径或其他任何内容:

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 以确定您在正方形的哪一侧,这会告诉您向哪个方向移动。

【讨论】:

【参考方案23】:

我和一个朋友一起做了这个,他在 Javascript 上将螺旋线调整为画布的纵横比。我得到的最佳解决方案是逐像素地进行图像演化,填充整个图像。

希望对某人有所帮助。

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);

您可以看到它在 http://jsfiddle.net/hitbyatruck/c4Kd6/ 上运行。请务必更改 javascript vars 和 html 属性上的画布的宽度和高度。

【讨论】:

【参考方案24】:

我真的很喜欢这篇文章的挑战 1+。我通过 Ruby 尝试了这个代码:

对于3X3方阵

(0..8).each do |i|
    j = Math.sqrt(i).round
    k = (j ** 2 - i).abs - j
    p = [k, -k].map |l| (l + j ** 2 - i - (j % 2)) * 0.5 * (-1) ** j.map(&:to_i)
    puts "(#p[0], #p[1]) "
end

输出:

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

对于您在图片中提到的 5X3

iter = (0..19).to_enum
while true
    i = iter.next
    j = Math.sqrt(i).round
    k = (j ** 2 - i).abs - j
    p = [k, -k].map |l| (l + j ** 2 - i - (j % 2)) * 0.5 * (-1) ** j.map(&:to_i)
    print "(#p[0], #p[1]) "
  if i == 11
    5.times i = iter.next
  end
end

为此的输出:

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

【讨论】:

【参考方案25】:

//php实现

function spiral($n) 

    $r = intval((sqrt($n + 1) - 1) / 2) + 1;

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

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

    $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

    $pos = array(0, 0, $r);
    switch (intval($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;
    
    return $pos;


for ($i = 0; $i < 168; $i++) 

    echo '<pre>';
    print_r(spiral($i));
    echo '</pre>';

【讨论】:

这是一个很酷的脚本,但发生了奇怪的事情。当您用生成的位置填充矩阵时,它将生成一个中心永远不会填充的矩阵: [x][x][x] [x][0][x] [x][x][x] 技术上,螺旋应该从一个点开始,然后在每个可能的空点处转向边缘,因此,最后不应该有任何 [0] 如果用这个公式绘制 Ulam 螺旋,这很重要。有谁知道如何调整这个?第 4 个位置是它应该转的问题,但继续前进,然后转。【参考方案26】:

我最近遇到了类似的挑战,我必须创建一个二维数组并使用螺旋矩阵算法对结果进行排序和打印。此 C# 代码适用于 N,N 二维数组。为了清楚起见,它很冗长,并且可能会根据您的需要进行重构。

//CREATE A NEW MATRIX OF SIZE 4 ROWS BY 4 COLUMNS - SCALE MATRIX SIZE HERE
SpiralMatrix SM = new SpiralMatrix(4, 4);
string myData = SM.Read();


public class SpiralMatrix

    //LETS BUILD A NEW MATRIX EVERY TIME WE INSTANTIATE OUR CLASS
    public SpiralMatrix(int Rows, int Cols)
    
        Matrix = new String[Rows, Cols];

        int pos = 1;
        for(int r = 0; r<Rows; r++)
            for (int c = 0; c < Cols; c++)
            
                //POPULATE THE MATRIX WITH THE CORRECT ROW,COL COORDINATE
                Matrix[r, c] = pos.ToString();
                pos++;
            
        
    

    //READ MATRIX
    public string Read()
    
        int Row = 0;
        int Col = 0;

        string S = "";
        bool isDone = false;

        //CHECK tO SEE IF POSITION ZERO IS AVAILABLE
        if(PosAvailable(Row, Col))
            S = ConsumePos(Row, Col);
        


        //START READING SPIRAL
        //THIS BLOCK READS A FULL CYCLE OF RIGHT,DOWN,LEFT,UP EVERY ITERATION
        while(!isDone)
        
            bool goNext = false;

            //READ ALL RIGHT SPACES ON THIS PATH PROGRESSION
            while (PosAvailable(Row, Col+1))
            
                //Is ReadRight Avail
                Col++;
                S += ConsumePos(Row, Col);
                goNext = true;
            

            //READ ALL DOWN SPACES ON THIS PATH PROGRESSION
            while(PosAvailable(Row+1, Col))
                //Is ReadDown Avail
                Row++;
                S += ConsumePos(Row, Col);
                goNext = true;
            

            //READ ALL LEFT SPACES ON THIS PATH PROGRESSION
            while(PosAvailable(Row, Col-1))
                //Is ReadLeft Avail
                Col--;
                S += ConsumePos(Row, Col);
                goNext = true;
            

            //READ ALL UP SPACES ON THIS PATH PROGRESSION
            while(PosAvailable(Row-1, Col))
                //Is ReadUp Avail
                Row--;
                S += ConsumePos(Row, Col);
                goNext = true;
            

            if(!goNext)
                //DONE - SET EXIT LOOP FLAG
                isDone = true;
            
        

        return S;
    

    //DETERMINE IF THE POSITION IS AVAILABLE
    public bool PosAvailable(int Row, int Col)
    
        //MAKE SURE WE ARE WITHIN THE BOUNDS OF THE ARRAY
        if (Row < Matrix.GetLength(0) && Row >= 0
            && Col < Matrix.GetLength(1) && Col >= 0)
        
            //CHECK COORDINATE VALUE
            if (Matrix[Row, Col] != ConsumeChar)
                return true;
            else
                return false;
        
        else
        
            //WE ARE OUT OF BOUNDS
            return false;
        
    

    public string ConsumePos(int Row, int Col)
    
        string n = Matrix[Row, Col];
        Matrix[Row, Col] = ConsumeChar;
        return n;
    

    public string ConsumeChar = "X";
    public string[,] Matrix;

【讨论】:

【参考方案27】:

AutoIt 解决方案

#include <Math.au3>
#include <Array.au3>

Func SpiralSearch($xMax,$yMax)
    $x = 0
    $y = 0
    $dx = 0
    $dy = -1
    for $i=0 To _max($xMax, $yMax)^2-1 Step 1
        if -$xMax/2 < $x and $x <= $xMax/2 And -$yMax/2 < $y And $y <= $yMax/2 Then
            MsgBox(0, "We are here ", $x & " " & $y)
        EndIf
        if $x == $y or ($x < 0 and $x == -$y) or ($x > 0 and $x == 1-$y) Then
            _ArraySwap ($dx, $dy)
            $dx=-$dx
        EndIf
        $x += $dx
        $y += $dy
    Next
EndFunc

【讨论】:

【参考方案28】:

这是一个稍微不同的版本 - 尝试在 LUA 中使用 recursioniterators。在每一步,程序都会在矩阵内部进一步下降并循环。我还为螺旋形clockwiseanticlockwise 添加了一个额外的标志。输出从右下角开始,向中心递归循环。

local row, col, clockwise

local SpiralGen
SpiralGen = function(loop)  -- Generator of elements in one loop
    local startpos =  x = col - loop, y = row - loop 
    local IteratePosImpl = function() -- This function calculates returns the cur, next position in a loop. If called without check, it loops infinitely

        local nextpos = x = startpos.x, y = startpos.y        
        local step = clockwise and x = 0, y = -1 or  x = -1, y = 0 

        return function()

            curpos = x = nextpos.x, y = nextpos.y
            nextpos.x = nextpos.x + step.x
            nextpos.y = nextpos.y + step.y
            if (((nextpos.x == loop or nextpos.x == col - loop + 1) and step.y == 0) or 
                ((nextpos.y == loop or nextpos.y == row - loop + 1) and step.x == 0)) then --Hit a corner in the loop

                local tempstep = x = step.x, y = step.y
                step.x = clockwise and tempstep.y or -tempstep.y
                step.y = clockwise and -tempstep.x or tempstep.x
                -- retract next step with new step
                nextpos.x = curpos.x + step.x 
                nextpos.y = curpos.y + step.y

            end         
            return curpos, nextpos
        end
    end
    local IteratePos = IteratePosImpl() -- make an instance
    local curpos, nextpos = IteratePos()
    while (true) do
        if(nextpos.x == startpos.x and nextpos.y == startpos.y) then            
            coroutine.yield(curpos)
            SpiralGen(loop+1) -- Go one step inner, since we're done with this loop
            break -- done with inner loop, get out
        else
            if(curpos.x < loop + 1 or curpos.x > col - loop or curpos.y < loop + 1 or curpos.y > row - loop) then
                break -- done with all elemnts, no place to loop further, break out of recursion
            else
                local curposL = x = curpos.x, y = curpos.y
                curpos, nextpos = IteratePos()
                coroutine.yield(curposL)
            end
        end     
    end 
end


local Spiral = function(rowP, colP, clockwiseP)
    row = rowP
    col = colP
    clockwise = clockwiseP
    return coroutine.wrap(function() SpiralGen(0) end) -- make a coroutine that returns all the values as an iterator
end


--test
for pos in Spiral(10,2,true) do
    print (pos.y, pos.x)
end

for pos in Spiral(10,9,false) do
    print (pos.y, pos.x)
end

【讨论】:

【参考方案29】:

这是我非常非常糟糕的解决方案,它是基于对 Java 的最低限度的了解。在这里,我必须将单位以螺旋形式放置在场上。单位不能放置在其他单位的顶部或山上或海洋中。

要清楚。这不是一个好的解决方案。这是一个非常糟糕的解决方案,只是为了让其他人嘲笑它做得有多糟糕

private void unitPlacementAlgorithm(Position p, Unit u)
    int i = p.getRow();
    int j = p.getColumn();

    int iCounter = 1;
    int jCounter = 0;

    if (getUnitAt(p) == null) 
            unitMap.put(p, u);
     else 
        iWhileLoop(i, j, iCounter, jCounter, -1, u);
    



private void iWhileLoop(int i, int j, int iCounter, int jCounter, int fortegn, Unit u)
    if(iCounter == 3) 
        for(int k = 0; k < 3; k++) 
            if(k == 2)  //This was added to make the looping stop after 9 units
                System.out.println("There is no more room around the city");
                return; 
            
            i--;

            if (getUnitAt(new Position(i, j)) == null 
                && !(getTileAt(new Position(i, j)).getTypeString().equals(GameConstants.OCEANS)) 
                && !(getTileAt(new Position(i, j)).getTypeString().equals(GameConstants.MOUNTAINS))) 
                    unitMap.put(new Position(i, j), u);
                    return;
            
            iCounter--;
        
    

    while (iCounter > 0) 
        if (fortegn > 0) 
            i++;
         else 
            i--;
        

        if (getUnitAt(new Position(i, j)) == null 
            && !(getTileAt(new Position(i, j)).getTypeString().equals(GameConstants.OCEANS)) 
            && !(getTileAt(new Position(i, j)).getTypeString().equals(GameConstants.MOUNTAINS))) 
                unitMap.put(new Position(i, j), u);
                return;
        
        iCounter--;
        jCounter++;
    
    fortegn *= -1;
    jWhileLoop(i, j, iCounter, jCounter, fortegn, u);


private void jWhileLoop(int i, int j, int iCounter, int jCounter,
        int fortegn, Unit u) 
    while (jCounter > 0) 
        if (fortegn > 0) 
            j++;
         else 
            j--;
        

        if (getUnitAt(new Position(i, j)) == null 
            && !(getTileAt(new Position(i, j)).getTypeString().equals(GameConstants.OCEANS)) 
            && !(getTileAt(new Position(i, j)).getTypeString().equals(GameConstants.MOUNTAINS))) 
                unitMap.put(new Position(i, j), u);
                return;

        
        jCounter--;
        iCounter++;
        if (jCounter == 0) 
            iCounter++;
        

    
    iWhileLoop(i, j, iCounter, jCounter, fortegn, u);

感谢任何真正能读到这篇文章的人

额外问题:这个“算法”的运行时间是多少? :P

【讨论】:

+1 因为“这是一个非常糟糕的解决方案,添加的目的是为了让其他人嘲笑它做得有多糟糕”。【参考方案30】:
Here is my attempt for simple C solution. First print the outer spiral and move one block inside..and repeat.

#define ROWS        5
#define COLS        5
//int A[ROWS][COLS] =  1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18 ;
//int A[ROWS][COLS] =  1, 2, 3, 6, 7, 8,  12, 13, 14 ;
//int A[ROWS][COLS] =  1, 2, 3, 4;

int A[ROWS][COLS] =  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 , 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ;


void print_spiral(int rows, int cols)

    int row = 0;
    int offset = 0;

    while (offset < (ROWS - 1)) 
        /* print one outer loop at a time. */
        for (int col = offset; col <= cols; col++) 
            printf("%d ", A[offset][col]);
        

        for (row = offset + 1; row <= rows; row++) 
            printf("%d ", A[row][cols]);
        

        for (int col = cols - 1; col >= offset; col--) 
            printf("%d ", A[rows][col]);
        

        for (row = rows - 1; row >= offset + 1; row--) 
            printf("%d ", A[row][offset]);
        

       /* Move one block inside */
        offset++;
        rows--;
        cols--;
    
    printf("\n");


int _tmain(int argc, _TCHAR* argv[])

    print_spiral(ROWS-1, COLS-1);
    return 0;

【讨论】:

以上是关于在螺旋中循环的主要内容,如果未能解决你的问题,请参考以下文章

Java实现螺旋矩阵

剑指 Offer 29. 顺时针打印矩阵-模拟循环(59. 螺旋矩阵 II)

回顾:螺旋打印数组

Scratch画圆形螺旋 蓝桥杯Scratch国赛真题答案和解析

每日leetcode-数组-54. 螺旋矩阵

求螺旋矩阵指定坐标的值