Цикл по спирали - PullRequest
       135

Цикл по спирали

142 голосов
/ 29 декабря 2008

Другу был нужен алгоритм, который позволял бы ему проходить по элементам матрицы NxM (N и M нечетные). Я придумал решение, но я хотел посмотреть, смогут ли мои коллеги-SO предложить лучшее решение.

Я публикую свое решение в качестве ответа на этот вопрос.

Пример вывода:

Для матрицы 3x3 вывод должен быть:

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

3x3 matrix

Кроме того, алгоритм должен поддерживать неквадратные матрицы, поэтому, например, для матрицы 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)

5x3 matrix

Ответы [ 32 ]

1 голос
/ 06 декабря 2017

Вот ответ Джулии: мой подход заключается в назначении точек в концентрических квадратах («спиралях») вокруг начала координат (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.OrderedDict{Any,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 Array{Tuple{Int64,Int64},1}:
 (0,0)  
 (1,0)  
 ⋮       
 (1,-2) 
 (2,-2)

Обратите внимание, что для некоторых функций [например, norm] может быть предпочтительнее оставить координаты в массивах, а не Tuple{Int,Int}, но здесь я изменяю их на кортежи - (x,y) - по запросу, используя понимание списка.

Контекст для «поддержки» неквадратной матрицы не указан (обратите внимание, что это решение по-прежнему рассчитывает значения вне сетки), но если вы хотите отфильтровать только диапазон x по y ( здесь для x=5, y=3) после расчета полной спирали, затем intersect эта матрица против значений из walk.

grid = [[x,y] for x in -2:2, y in -1:1]
5×3 Array{Array{Int64,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 Array{Tuple{Int64,Int64},1}:
 (0,0)  
 (1,0)  
 ⋮ 
 (-2,0) 
 (-2,-1)
1 голос
/ 20 октября 2017

Вот итеративное решение этой проблемы в 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. Реализация всегда начинает движение в следующем порядке - вверх, вправо внизу слева

Обратите внимание, что эта реализация создает квадратные матрицы.

1 голос
/ 06 сентября 2013

Это немного другая версия - пытается использовать recursion и iterators в LUA. На каждом шаге программа опускается дальше внутрь матрицы и зацикливается. Я также добавил дополнительный флаг к спирали clockwise или anticlockwise. Выход начинается с правого нижнего угла и рекурсивно зацикливается по направлению к центру.

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
1 голос
/ 06 сентября 2012

Вот 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);
    }
  }

}

Первый пример вопроса (3x3) будет:

var coords = SpiralCoords.GenerateOutTo(1);

Второй пример вопроса (5x3) будет:

var coords = SpiralCoords.GenerateOutTo(2).Where(x => abs(x.Item2) < 2);
1 голос
/ 10 июля 2012

Это в с.

Я случайно выбрал неверные имена переменных. В именах T == вверху, L == слева, B == внизу, R == справа. Таким образом, tli - это верхний левый угол, а brj - нижний правый угол j.

#include<stdio.h>

typedef enum {
   TLTOR = 0,
   RTTOB,
   BRTOL,
   LBTOT
} Direction;

int main() {
   int arr[][3] = {{1,2,3},{4,5,6}, {7,8,9}, {10,11,12}};
   int tli = 0, tlj = 0, bri = 3, brj = 2;
   int i;
   Direction d = TLTOR;

   while (tli < bri || tlj < brj) {
     switch (d) {
     case TLTOR:
    for (i = tlj; i <= brj; i++) {
       printf("%d ", arr[tli][i]);
    }
    tli ++;
    d = RTTOB;
    break;
     case RTTOB:
    for (i = tli; i <= bri; i++) {
       printf("%d ", arr[i][brj]);
    }
    brj --;
    d = BRTOL;
    break;
     case BRTOL:
    for (i = brj; i >= tlj; i--) {
       printf("%d ", arr[bri][i]);
    }
    bri --;
        d = LBTOT;
    break;
     case LBTOT:
    for (i = bri; i >= tli; i--) {
       printf("%d ", arr[i][tlj]);
    }
    tlj ++;
        d = TLTOR;
    break;
 }
   }
   if (tli == bri == tlj == brj) {
      printf("%d\n", arr[tli][tlj]);
   }
}
1 голос
/ 04 марта 2018

Вот решение в 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

(>>>>> означает «перейти 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)
1 голос
/ 09 августа 2015

У меня есть библиотека с открытым исходным кодом, pixelscan , это библиотека Python, которая предоставляет функции для сканирования пикселей на сетке в различных пространственных структурах. Пространственные модели включают в себя круговые, кольца, сетки, змей и случайные прогулки. Существуют также различные преобразования (например, обрезать, поменять, повернуть, перевести). Исходная проблема ОП может быть решена следующим образом

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)

Генераторы библиотек и преобразования могут быть объединены в цепочку для изменения точек в широком диапазоне порядков и пространственных структур.

0 голосов
/ 26 сентября 2016

Python зацикливает спиральный код по часовой стрелке, используя Can Berk Güder ответ .

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
0 голосов
/ 17 августа 2016

Я делюсь этим кодом, который я разработал для другой цели; речь идет о поиске номера столбца "X" и номера строки "Y" элемента массива @ спирального индекса "index". Эта функция принимает ширину «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 };
    }
0 голосов
/ 18 июля 2016

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...