web-dev-qa-db-ja.com

原点から離散2Dグリッド上の外向きスパイラルを反復するためのアルゴリズム

たとえば、目的のスパイラルの形状(および反復の各ステップ)は次のとおりです。

_          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]_など。そして、グリッド内の位置が空である(またはデータポイントによってまだ占有されていない)最初のものを返します。

グリッドサイズに上限はありません。

29
Justin L.

直接的な「アドホック」ソリューションには何の問題もありません。それも十分にきれいにすることができます。
スパイラルはセグメントから構築されていることに注意してください。そして、現在のセグメントから次のセグメントを90度回転させて取得できます。そして、2回転するごとに、セグメントの長さが1ずつ増えます。

編集イラスト、番号が付けられたセグメント

   ... 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;
            }
        }
    }

元の方向を変更するには、didjの元の値を確認します。回転を時計回りに切り替えるには、これらの値がどのように変更されるかを確認してください。

22
Nikita Rybak

これは、ステートフルイテレータである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;
        }
    }
};

それが得るのとほぼ同じくらい効率的であるはずです。

15
mako

これは、 らせん状にループする の答えに基づく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/

10
Nicolas

この問題は、スパイラルコーナーの座標がどのように変化するかを分析することで最もよく理解できます。最初の8つのスパイラルコーナー(原点を除く)のこの表を検討してください。

 x、y | dx、dy | k番目のコーナー| N |署名| 
 ___________________________________________ 
 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 | -

この表を見ると、(k-1)コーナーのX、Yが与えられた場合、k番目のコーナーのX、Yを計算できます。

 N = INT((1 + k)/ 2)
記号= | Nが奇数の場合は+1 
 | -1Nが偶数の場合
 [dx、dy] = | [N * Sign、0] kが奇数の場合
 | [0、N * Sign] kが偶数の場合
 [X(k)、Y(k)] = [X(k-1)+ dx、Y(k-1)+ dy] 

Kとk + 1のスパイラルコーナーの座標がわかっている場合は、最後のポイントのxまたはyに1または-1を追加するだけで、kとk +1の間のすべてのデータポイントを取得できます。それでおしまい。

幸運を。

7

私はいくつかの数学を使用してそれを解決します。これが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_、次の2つのセグメント(= 2ポイント)は_1_、次の2つのセグメント(= 4ポイント)は_2_などです。2つごとに変更されます。セグメントとそのセグメントのポイント部分の数が増えるたびに。それがjの使用目的です。

偶然にも、これは別の情報を取得するために使用できます。_(-1)**j_は、このステップに到達するために座標を減らしている場合は「_1_」の省略形です。_-1_再増加」(各ステップで変更される座標は1つだけであることに注意してください)。 _j%2_についても同じことが言えます。この場合、_1_を_0_に置き換え、_-1_を_1_に置き換えます。これは、2つの値の間でスワップすることを意味します。1つは上または右に「向かう」セグメント用で、もう1つは下または左に向かうセグメント用です。

関数型プログラミングに慣れている場合、これはおなじみの推論です。残りはほんの少しの単純な数学です。

6
Alberto Santini

これは、再帰を使用してかなり簡単な方法で実行できます。 (おそらく無限の)シーケンスを生成およびマッピングするための基本的な2Dベクトル計算とツールが必要です。

// 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));
}
1
Asad Saeeduddin

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
Ely Golden

パラメトリック方程式または極方程式を検索してみてください。どちらもらせん状のものをプロットするのに適しています。 ここにページがあります 写真(および方程式)を含む多くの例があります。それはあなたに何を探すべきかについてのもう少しの考えを与えるはずです。

0
Seth

私はトレーニング演習とほぼ同じように薄くしましたが、出力とスパイラルの向きにいくつかの違いがあり、関数の空間的な複雑さは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

これはあなたが求めていたものとは異なりますが、問題を考えるのに役立つと思います

0
alcuadrado

同様の問題がありましたが、次の新しい座標を見つけるために毎回スパイラル全体をループしたくありませんでした。要件は、最後の座標を知っていることです。

これが私が他の解決策についてたくさん読んで思いついたものです:

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
br3nt

これがアルゴリズムです。時計回りに回転しますが、いくつかの変更を加えるだけで、反時計回りに簡単に回転できます。 1時間弱で完成しました。

// 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)で書かれていますが、実際のロジックはどのプログラミング言語でも適切です。

単一行アルゴリズムには、x入力とy入力に対して2つの変数(sxとsy)しかありません。私は基本的にブラケットをたくさん拡張しました。メモ帳に貼り付けて、x引数/変数名の「sx」とy引数/変数名の「sy」を簡単に変更できます。

`// 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
Daniel Price