Алгоритм итерации по внешней спирали на дискретной двумерной сетке от начала координат - PullRequest
29 голосов
/ 14 сентября 2010

Например, вот форма предполагаемой спирали (и каждого шага итерации)

          y
          |
          |
   16 15 14 13 12
   17  4  3  2 11
-- 18  5  0  1 10 --- x
   19  6  7  8  9
   20 21 22 23 24
          |
          |

Где линии - это оси x и y.

Здесь будут фактические значения, которые алгоритм будет «возвращать» при каждой итерации (координаты точек):

[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,2], [1,2], [0,2], [-1,2], [-2,2], [-2,1], [-2,0]..

и т.д.

Я попытался выполнить поиск, но я не совсем уверен, что именно искать, и то, что я пробовал, привело к тупикам.

Я даже не уверен, с чего начать, кроме чего-то грязного, не элегантного и специального, как создание / кодирование новой спирали для каждого слоя.

Может кто-нибудь помочь мне начать?

Кроме того, существует ли способ, который может легко переключаться между по часовой стрелке и против часовой стрелки (ориентация), и с какого направления «запускать» спираль? (вращение)

Кроме того, есть ли способ сделать это рекурсивно?


Моя заявка

У меня есть разреженная сетка, заполненная точками данных, и я хочу добавить новую точку данных в сетку, и она должна быть "как можно ближе" к заданной другой точке.

Для этого я позвоню grid.find_closest_available_point_to(point), который будет перебирать спираль, приведенную выше, и возвращать первую позицию, которая пуста и доступна.

Итак, во-первых, он проверит point+[0,0] (просто ради полноты). Тогда он проверит point+[1,0]. Тогда он проверит point+[1,1]. Затем point+[0,1] и т. Д. И верните первое, для которого позиция в сетке пуста (или уже не занята точкой данных).

Верхняя граница размера сетки отсутствует.

Ответы [ 11 ]

22 голосов
/ 14 сентября 2010

Нет ничего плохого в прямом, "специальном" решении.Он тоже может быть достаточно чистым.
Просто обратите внимание, что спираль построена из сегментов.И вы можете получить следующий сегмент из текущего, повернув его на 90 градусов.И каждые два оборота длина сегмента увеличивается на 1.

edit Иллюстрация, эти сегменты пронумерованы

   ... 11 10
7 7 7 7 6 10
8 3 3 2 6 10
8 4 . 1 6 10
8 4 5 5 5 10
8 9 9 9 9  9

.

    // (di, dj) is a vector - direction in which we move right now
    int di = 1;
    int dj = 0;
    // length of current segment
    int segment_length = 1;

    // current position (i, j) and how much of current segment we passed
    int i = 0;
    int j = 0;
    int segment_passed = 0;
    for (int k = 0; k < NUMBER_OF_POINTS; ++k) {
        // make a step, add 'direction' vector (di, dj) to current position (i, j)
        i += di;
        j += dj;
        ++segment_passed;
        System.out.println(i + " " + j);

        if (segment_passed == segment_length) {
            // done with current segment
            segment_passed = 0;

            // 'rotate' directions
            int buffer = di;
            di = -dj;
            dj = buffer;

            // increase segment length if necessary
            if (dj == 0) {
                ++segment_length;
            }
        }
    }

Toизмените исходное направление, посмотрите на исходные значения di и dj.Чтобы переключить вращение по часовой стрелке, посмотрите, как изменяются эти значения.

15 голосов
/ 23 декабря 2012

Вот удар в C ++, итератор с сохранением состояния.

class SpiralOut{
protected:
    unsigned layer;
    unsigned leg;
public:
    int x, y; //read these as output from next, do not modify.
    SpiralOut():layer(1),leg(0),x(0),y(0){}
    void goNext(){
        switch(leg){
        case 0: ++x; if(x  == layer)  ++leg;                break;
        case 1: ++y; if(y  == layer)  ++leg;                break;
        case 2: --x; if(-x == layer)  ++leg;                break;
        case 3: --y; if(-y == layer){ leg = 0; ++layer; }   break;
        }
    }
};

Должно быть примерно настолько же эффективным, насколько это возможно.

10 голосов
/ 16 ноября 2012

Это решение javascript, основанное на ответе Цикл по спирали

var x = 0,
    y = 0,
    delta = [0, -1],
    // spiral width
    width = 6,
    // spiral height
    height = 6;


for (i = Math.pow(Math.max(width, height), 2); i>0; i--) {
    if ((-width/2 < x && x <= width/2) 
            && (-height/2 < y && y <= height/2)) {
        console.debug('POINT', x, y);
    }

    if (x === y 
            || (x < 0 && x === -y) 
            || (x > 0 && x === 1-y)){
        // change direction
        delta = [-delta[1], delta[0]]            
    }

    x += delta[0];
    y += delta[1];        
}

скрипка: http://jsfiddle.net/N9gEC/18/

7 голосов
/ 14 сентября 2010

Эту проблему лучше всего понять, проанализировав, как меняются координаты спиральных углов.Рассмотрим эту таблицу из первых 8 спиральных углов (исключая начало координат):

 x,y   |  dx,dy  | k-th corner | N | Sign |
___________________________________________
1,0    |  1,0    | 1           | 1 |  +
1,1    |  0,1    | 2           | 1 |  +
-1,1   |  -2,0   | 3           | 2 |  -
-1,-1  |  0,-2   | 4           | 2 |  -
2,-1   |  3,0    | 5           | 3 |  +
2,2    |  0,3    | 6           | 3 |  +
-2,2   |  -4,0   | 7           | 4 |  -
-2,-2  |  0,-4   | 8           | 4 |  -

Рассматривая эту таблицу, мы можем вычислить X, Y k-го угла, учитывая X, Y (k-1) угла:

N = INT((1+k)/2)
Sign = | +1 when N is Odd
       | -1 when N is Even
[dx,dy] = | [N*Sign,0]  when k is Odd
          | [0,N*Sign]  when k is Even
[X(k),Y(k)] = [X(k-1)+dx,Y(k-1)+dy]

Теперь, когда вы знаете координаты k и k + 1 спирального угла, вы можете получить все точки данных между k и k + 1, просто добавив 1 или -1 к x или y последней точки.Вот и все.

удачи.

5 голосов
/ 15 сентября 2010

Я бы решил это с помощью математики.Вот код Ruby (с входом и выходом):

(0..($*.pop.to_i)).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 => #{p[0]}, #{p[1]}"
end

Например,

$ ruby spiral.rb 10
p => 0, 0
p => 1, 0
p => 1, 1
p => 0, 1
p => -1, 1
p => -1, 0
p => -1, -1
p => 0, -1
p => 1, -1
p => 2, -1
p => 2, 0

И версия для игры в гольф:

p (0..$*.pop.to_i).map{|i|j=Math.sqrt(i).round;k=(j**2-i).abs-j;[k,-k].map{|l|(l+j**2-i-j%2)*0.5*(-1)**j}.map(&:to_i)}

Редактировать

Сначала попытайтесь подойти к проблеме функционально.Что нужно знать на каждом шаге, чтобы перейти к следующему шагу?

Сосредоточиться на первой диагонали плоскости x = y.k говорит вам, сколько шагов вы должны сделать, прежде чем дотронуться до него: отрицательные значения означают, что вы должны переместить abs(k) шагов по вертикали, а положительные значения означают, что вы должны переместить k шагов по горизонтали.длина сегмента, в котором вы находитесь в данный момент (вершины спирали - когда изменяется наклон сегментов - считаются частью «следующего» сегмента).Это 0 в первый раз, затем 1 для следующих двух сегментов (= 2 балла), затем 2 для следующих двух сегментов (= 4 балла) и т. Д. Он меняет каждые два сегмента и каждый раз числоиз точек часть этих сегментов увеличивается.Вот для чего используется j.

Случайно, это может быть использовано для получения другого бита информации: (-1)**j это просто сокращение от "1, если вы уменьшаете некоторую координату, чтобы добраться доэтот шаг; -1, если вы увеличиваете "(обратите внимание, что только одна координата изменяется на каждом шаге).То же самое относится к j%2, просто замените 1 на 0 и -1 на 1 в этом случае.Это означает, что они меняются между двумя значениями: одно для сегментов, «движущихся вверх» или «вправо», и одно для сегментов, движущихся вниз или влево.

Это привычное рассуждение, если вы привыкли к функциональному программированию: остальноепросто немного математики.

1 голос
/ 27 февраля 2019

Это может быть сделано довольно простым способом с использованием рекурсии.Нам просто нужны базовые двухмерные векторные математические вычисления и инструменты для генерации и отображения (возможно, бесконечных) последовательностей:

// 2D vectors
const add = ([x0, y0]) => ([x1, y1]) => [x0 + x1, y0 + y1];
const rotate = θ => ([x, y]) => [
  Math.round(x * Math.cos(θ) - y * Math.sin(θ)),
  Math.round(x * Math.sin(θ) + y * Math.cos(θ))
];
// Iterables
const fromGen = g => ({ [Symbol.iterator]: g });
const range = n => [...Array(n).keys()];
const map = f => it =>
  fromGen(function*() {
    for (const v of it) {
      yield f(v);
    }
  });

И теперь мы можем рекурсивно выразить спираль, генерируя плоскую линию плюс повернутую (плоскую линию), плюс повернутый (плоская линия, плюс повернутый ...)):

const spiralOut = i => {
  const n = Math.floor(i / 2) + 1;
  const leg = range(n).map(x => [x, 0]);
  const transform = p => add([n, 0])(rotate(Math.PI / 2)(p));

  return fromGen(function*() {
    yield* leg;
    yield* map(transform)(spiralOut(i + 1));
  });
};

, который создает бесконечный список интересующих вас координат. Вот пример содержимого:

const take = n => it =>
  fromGen(function*() {
    for (let v of it) {
      if (--n < 0) break;
      yield v;
    }
  });
const points = [...take(5)(spiralOut(0))];
console.log(points);
// => [[0,0],[1,0],[1,1],[0,1],[-1,1]]

outward spiral

Вы также можете отменить угол поворота, чтобы двигаться в другом направлении, или поиграть с трансформацией и длиной ноги, чтобы получить более сложные формы.

Например, та же самая техника работает и для внутренних спиралей.Это просто немного другое преобразование и немного другая схема изменения длины ветви:

const empty = [];
const append = it1 => it2 =>
  fromGen(function*() {
    yield* it1;
    yield* it2;
  });
const spiralIn = ([w, h]) => {
  const leg = range(w).map(x => [x, 0]);
  const transform = p => add([w - 1, 1])(rotate(Math.PI / 2)(p));

  return w * h === 0
    ? empty
    : append(leg)(
        fromGen(function*() {
          yield* map(transform)(spiralIn([h - 1, w]));
        })
      );
};

, который производит (эта спираль конечна, поэтому нам не нужно take какое-то произвольное число):

const points = [...spiralIn([3, 3])];
console.log(points);
// => [[0,0],[1,0],[2,0],[2,1],[2,2],[1,2],[0,2],[0,1],[1,1]]

inward spiral

Вот и все вместе, как живой фрагмент, если вы хотите поиграть с ним:

// 2D vectors
const add = ([x0, y0]) => ([x1, y1]) => [x0 + x1, y0 + y1];
const rotate = θ => ([x, y]) => [
  Math.round(x * Math.cos(θ) - y * Math.sin(θ)),
  Math.round(x * Math.sin(θ) + y * Math.cos(θ))
];

// Iterables
const fromGen = g => ({ [Symbol.iterator]: g });
const range = n => [...Array(n).keys()];
const map = f => it =>
  fromGen(function*() {
    for (const v of it) {
      yield f(v);
    }
  });
const take = n => it =>
  fromGen(function*() {
    for (let v of it) {
      if (--n < 0) break;
      yield v;
    }
  });
const empty = [];
const append = it1 => it2 =>
  fromGen(function*() {
    yield* it1;
    yield* it2;
  });

// Outward spiral
const spiralOut = i => {
  const n = Math.floor(i / 2) + 1;
  const leg = range(n).map(x => [x, 0]);
  const transform = p => add([n, 0])(rotate(Math.PI / 2)(p));

  return fromGen(function*() {
    yield* leg;
    yield* map(transform)(spiralOut(i + 1));
  });
};

// Test
{
  const points = [...take(5)(spiralOut(0))];
  console.log(JSON.stringify(points));
}

// Inward spiral
const spiralIn = ([w, h]) => {
  const leg = range(w).map(x => [x, 0]);
  const transform = p => add([w - 1, 1])(rotate(Math.PI / 2)(p));

  return w * h === 0
    ? empty
    : append(leg)(
        fromGen(function*() {
          yield* map(transform)(spiralIn([h - 1, w]));
        })
      );
};

// Test
{
  const points = [...spiralIn([3, 3])];
  console.log(JSON.stringify(points));
}
0 голосов
/ 27 июня 2015

У меня есть алгоритм в java, который выводит аналогичные выходные данные, за исключением того, что он определяет приоритет числа справа, чем число слева.

  public static String[] rationals(int amount){
   String[] numberList=new String[amount];
   int currentNumberLeft=0;
   int newNumberLeft=0;
   int currentNumberRight=0;
   int newNumberRight=0;
   int state=1;
   numberList[0]="("+newNumberLeft+","+newNumberRight+")";
   boolean direction=false;
 for(int count=1;count<amount;count++){
   if(direction==true&&newNumberLeft==state){direction=false;state=(state<=0?(-state)+1:-state);}
   else if(direction==false&&newNumberRight==state){direction=true;}
   if(direction){newNumberLeft=currentNumberLeft+sign(state);}else{newNumberRight=currentNumberRight+sign(state);}
   currentNumberLeft=newNumberLeft;
   currentNumberRight=newNumberRight;
   numberList[count]="("+newNumberLeft+","+newNumberRight+")";
 }
 return numberList;
}
0 голосов
/ 04 сентября 2013

Вот алгоритм. Он вращается по часовой стрелке, но может легко вращаться против часовой стрелки, с некоторыми изменениями. Я сделал это всего за час.

// spiral_get_value(x,y);
sx = argument0;
sy = argument1;
a = max(sqrt(sqr(sx)),sqrt(sqr(sy)));
c = -b;
d = (b*2)+1;
us = (sy==c and sx !=c);
rs = (sx==b and sy !=c);
bs = (sy==b and sx !=b);
ls = (sx==c and sy !=b);
ra = rs*((b)*2);
ba = bs*((b)*4);
la = ls*((b)*6);
ax = (us*sx)+(bs*-sx);
ay = (rs*sy)+(ls*-sy);
add = ra+ba+la+ax+ay;
value = add+sqr(d-2)+b;
return(value);`

Он будет обрабатывать любые значения x / y (бесконечно).

Он написан на GML (Game Maker Language), но настоящая логика звучит на любом языке программирования.

Однострочный алгоритм имеет только 2 переменные (sx и sy) для входов x и y. Я в основном расширил скобки, много. Вам будет проще вставить его в блокнот и заменить «sx» для вашего аргумента x / имени переменной и «sy» на ваш аргумент y / имя переменной.

`// spiral_get_value(x,y);

sx = argument0;  
sy = argument1;

value = ((((sx==max(sqrt(sqr(sx)),sqrt(sqr(sy))) and sy !=(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy))))))*((max(sqrt(sqr(sx)),sqrt(sqr(sy))))*2))+(((sy==max(sqrt(sqr(sx)),sqrt(sqr(sy))) and sx !=max(sqrt(sqr(sx)),sqrt(sqr(sy)))))*((max(sqrt(sqr(sx)),sqrt(sqr(sy))))*4))+(((sx==(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy)))) and sy !=max(sqrt(sqr(sx)),sqrt(sqr(sy)))))*((max(sqrt(sqr(sx)),sqrt(sqr(sy))))*6))+((((sy==(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy)))) and sx !=(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy))))))*sx)+(((sy==max(sqrt(sqr(sx)),sqrt(sqr(sy))) and sx !=max(sqrt(sqr(sx)),sqrt(sqr(sy)))))*-sx))+(((sx==max(sqrt(sqr(sx)),sqrt(sqr(sy))) and sy !=(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy))))))*sy)+(((sx==(-1*max(sqrt(sqr(sx)),sqrt(sqr(sy)))) and sy !=max(sqrt(sqr(sx)),sqrt(sqr(sy)))))*-sy))+sqr(((max(sqrt(sqr(sx)),sqrt(sqr(sy)))*2)+1)-2)+max(sqrt(sqr(sx)),sqrt(sqr(sy)));

return(value);`

Я знаю, что ответ ужасно поздно: D, но я надеюсь, что он поможет будущим посетителям.

0 голосов
/ 06 июня 2013

У меня была похожая проблема, но я не хотел каждый раз перебирать всю спираль, чтобы найти следующую новую координату.Требование состоит в том, что вы знаете свою последнюю координату.

Вот что я придумал, когда много читал о других решениях:

function getNextCoord(coord) {

    // required info
    var x     = coord.x,
        y     = coord.y,
        level = Math.max(Math.abs(x), Math.abs(y));
        delta = {x:0, y:0};

    // calculate current direction (start up)
    if (-x === level)
        delta.y = 1;    // going up
    else if (y === level)
        delta.x = 1;    // going right
    else if (x === level)        
        delta.y = -1;    // going down
    else if (-y === level)
        delta.x = -1;    // going left

    // check if we need to turn down or left
    if (x > 0 && (x === y || x === -y)) {
        // change direction (clockwise)
        delta = {x: delta.y, 
                 y: -delta.x};
    }

    // move to next coordinate
    x += delta.x;
    y += delta.y;

    return {x: x,
            y: y};
}

coord = {x: 0, y: 0}
for (i = 0; i < 40; i++) {
    console.log('['+ coord.x +', ' + coord.y + ']');
    coord = getNextCoord(coord);  

}

Все еще не уверен, что этосамое элегантное решение.Возможно, некоторые изящные математические выражения могут убрать некоторые операторы if.Некоторым ограничениям потребуется некоторая модификация для изменения направления спирали, она не учитывает неквадратные спирали и не может вращаться вокруг фиксированной координаты.

0 голосов
/ 14 сентября 2010

Я сделал почти то же самое, что и тренировочное упражнение, с некоторыми различиями в выходной и спиральной ориентации, и с дополнительным требованием, чтобы пространственная сложность функций была O (1).

Подумав некоторое время, я пришел к мысли, что, зная, где начинается спираль и для какой позиции я рассчитываю значение, я мог бы упростить задачу, вычтя все полные "круги" спирали, а затем просто посчитайте более простое значение.

Вот моя реализация этого алгоритма в ruby:

def print_spiral(n)
  (0...n).each do |y|
    (0...n).each do |x|
      printf("%02d ", get_value(x, y, n))
    end
    print "\n"
  end
end


def distance_to_border(x, y, n)
  [x, y, n - 1 - x, n - 1 - y].min
end

def get_value(x, y, n)
  dist = distance_to_border(x, y, n)
  initial = n * n - 1

  (0...dist).each do |i|
    initial -= 2 * (n - 2 * i) + 2 * (n - 2 * i - 2)
  end        

  x -= dist
  y -= dist
  n -= dist * 2

  if y == 0 then
    initial - x # If we are in the upper row
  elsif y == n - 1 then
    initial - n - (n - 2) - ((n - 1) - x) # If we are in the lower row
  elsif x == n - 1 then
    initial - n - y + 1# If we are in the right column
  else
    initial - 2 * n - (n - 2) - ((n - 1) - y - 1) # If we are in the left column
  end
end

print_spiral 5

Это не совсем то, о чем вы просили, но я верю, что это поможет вам подумать о вашей проблеме

...