For example, here is the shape of intended spiral (and each step of the iteration)
y
|
|
16 15 14 13 12
17 4 3 2 11
--
It can be done in a fairly straightforward way using recursion. We just need some basic 2D vector math and tools for generating and mapping over (possibly infinite) sequences:
// 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);
}
});
And now we can express a spiral recursively by generating a flat line, plus a rotated (flat line, plus a rotated (flat line, plus a rotated ...)):
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));
});
};
Which produces an infinite list of the coordinates you're interested in. Here's a sample of the contents:
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]]
You can also negate the rotation angle to go in the other direction, or play around with the transform and leg length to get more complex shapes.
For example, the same technique works for inward spirals as well. It's just a slightly different transform, and a slightly different scheme for changing the length of the leg:
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]));
})
);
};
Which produces (this spiral is finite, so we don't need to take
some arbitrary number):
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]]
Here's the whole thing together as a live snippet if you want play around with it:
// 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));
}
Here's the algorithm. It rotates clockwise, but could easily rotate anticlockwise, with a few alterations. I made it in just under an hour.
// 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);`
It will handle any x / y values (infinite).
It's written in GML (Game Maker Language), but the actual logic is sound in any programming language.
The single line algorithm only has 2 variables (sx and sy) for the x and y inputs. I basically expanded brackets, a lot. It makes it easier for you to paste it into notepad and change 'sx' for your x argument / variable name and 'sy' to your y argument / variable name.
`// 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);`
I know the reply is awfully late :D but i hope it helps future visitors.
There's nothing wrong with direct, "ad-hoc" solution. It can be clean enough too.
Just notice that spiral is built from segments. And you can get next segment from current one rotating it by 90 degrees. And each two rotations, length of segment grows by 1.
edit Illustration, those segments numbered
... 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 change original direction, look at original values of di
and dj
. To switch rotation to clockwise, see how those values are modified.
Here is a Python implementation based on the answer by @mako.
def spiral_iterator(iteration_limit=999):
x = 0
y = 0
layer = 1
leg = 0
iteration = 0
yield 0, 0
while iteration < iteration_limit:
iteration += 1
if leg == 0:
x += 1
if (x == layer):
leg += 1
elif leg == 1:
y += 1
if (y == layer):
leg += 1
elif leg == 2:
x -= 1
if -x == layer:
leg += 1
elif leg == 3:
y -= 1
if -y == layer:
leg = 0
layer += 1
yield x, y
Running this code:
for x, y in spiral_iterator(10):
print(x, y)
Yields:
0 0
1 0
1 1
0 1
-1 1
-1 0
-1 -1
0 -1
1 -1
2 -1
2 0
I would solve it using some math. Here is Ruby code (with input and output):
(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
E.g.
$ 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
And golfed version:
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)}
Edit
First try to approach the problem functionally. What do you need to know, at each step, to get to the next step?
Focus on plane's first diagonal x = y
. k
tells you how many steps you must take before touching it: negative values mean you have to move abs(k)
steps vertically, while positive mean you have to move k
steps horizontally.
Now focus on the length of the segment you're currently in (spiral's vertices - when the inclination of segments change - are considered as part of the "next" segment). It's 0
the first time, then 1
for the next two segments (= 2 points), then 2
for the next two segments (= 4 points), etc. It changes every two segments and each time the number of points part of that segments increase. That's what j
is used for.
Accidentally, this can be used for getting another bit of information: (-1)**j
is just a shorthand to "1
if you're decreasing some coordinate to get to this step; -1
if you're increasing" (Note that only one coordinate is changed at each step). Same holds for j%2
, just replace 1
with 0
and -1
with 1
in this case. This mean they swap between two values: one for segments "heading" up or right and one for those going down or left.
This is a familiar reasoning, if you're used to functional programming: the rest is just a little bit of simple math.
Try searching for either parametric or polar equations. Both are suitable to plotting spirally things. Here's a page that has plenty of examples, with pictures (and equations). It should give you some more ideas of what to look for.