たとえば、目的のスパイラルの形状(および反復の各ステップ)は次のとおりです。
_ 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]
_など。そして、グリッド内の位置が空である(またはデータポイントによってまだ占有されていない)最初のものを返します。
グリッドサイズに上限はありません。
直接的な「アドホック」ソリューションには何の問題もありません。それも十分にきれいにすることができます。
スパイラルはセグメントから構築されていることに注意してください。そして、現在のセグメントから次のセグメントを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;
}
}
}
元の方向を変更するには、di
とdj
の元の値を確認します。回転を時計回りに切り替えるには、これらの値がどのように変更されるかを確認してください。
これは、ステートフルイテレータである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;
}
}
};
それが得るのとほぼ同じくらい効率的であるはずです。
これは、 らせん状にループする の答えに基づく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];
}
この問題は、スパイラルコーナーの座標がどのように変化するかを分析することで最もよく理解できます。最初の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の間のすべてのデータポイントを取得できます。それでおしまい。
幸運を。
私はいくつかの数学を使用してそれを解決します。これが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つは下または左に向かうセグメント用です。
関数型プログラミングに慣れている場合、これはおなじみの推論です。残りはほんの少しの単純な数学です。
これは、再帰を使用してかなり簡単な方法で実行できます。 (おそらく無限の)シーケンスを生成およびマッピングするための基本的な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]]
回転角を無効にして反対方向に移動したり、変形と脚の長さをいじってより複雑な形状を取得したりすることもできます。
たとえば、同じ手法が内側のスパイラルにも機能します。これは、わずかに異なる変換であり、脚の長さを変更するためのわずかに異なるスキームです。
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]]
試してみたい場合は、ライブスニペットとして全体をまとめます。
// 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));
}
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;
}
パラメトリック方程式または極方程式を検索してみてください。どちらもらせん状のものをプロットするのに適しています。 ここにページがあります 写真(および方程式)を含む多くの例があります。それはあなたに何を探すべきかについてのもう少しの考えを与えるはずです。
私はトレーニング演習とほぼ同じように薄くしましたが、出力とスパイラルの向きにいくつかの違いがあり、関数の空間的な複雑さは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
これはあなたが求めていたものとは異なりますが、問題を考えるのに役立つと思います
同様の問題がありましたが、次の新しい座標を見つけるために毎回スパイラル全体をループしたくありませんでした。要件は、最後の座標を知っていることです。
これが私が他の解決策についてたくさん読んで思いついたものです:
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ステートメントのいくつかを削除する可能性があります。いくつかの制限は、スパイラルの方向を変更するためにいくつかの変更が必要であり、非正方形のスパイラルを考慮せず、固定座標の周りをスパイラルすることはできません。
これがアルゴリズムです。時計回りに回転しますが、いくつかの変更を加えるだけで、反時計回りに簡単に回転できます。 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ですが、将来の訪問者に役立つことを願っています。