It is possible to write the function $f(n)$ in an $O(1)$ form (provided you assume all arithmetic operations, including the sqrt function, are $O(1)$).
The spiral consists of sequences of motion up, left, down, or right
in a predictable pattern, so you just have to deduce which sequence of motions the $n$th point is in, how far within that sequence it is, and where that puts it in the coordinate grid.
Here is a non-recursive version of the function $f(n),$ coded in Python 3.
# Find the nth set of coordinates in a spiral through
# lattice points starting at 0,0
from math import sqrt, floor, ceil
import numpy as np
Treating the spiral as a sequence of concentric squares
around the origin, return the inradius of the square that
the nth point is on.
def cycle_number(n):
x = floor(sqrt(n))
return ceil(x/2)
def first_number_in_cycle(k):
return 4k(k-1) + 1
def side_length_in_cycle(k):
return 2*k
def point(x,y):
return np.array([x,y])
corner_vectors = (point(1,-1),point(-1,-1),point(-1,1),point(1,1))
side_directions = (point(-1,0),point(0,1),point(1,0),point(0,-1))
def f(n):
if n == 0:
return point(0,0)
k = cycle_number(n)
distance_from_cycle_start = n - first_number_in_cycle(k)
# Identify which of the four sides (straight sections)
# of the cycle n is in:
side_length = side_length_in_cycle(k)
side = floor(distance_from_cycle_start / side_length)
distance_along_side = 1 + distance_from_cycle_start % side_length
ref_position = k * corner_vectors[side]
return ref_position + distance_along_side * side_directions[side]
#-----------------------------------------------------------
Everything below this point is just to test f(n)
Assume x and y coordinates range from -3 to 3 inclusive
data_array = np.array([[0] * 7] * 7)
def write_data(p, value):
x = p[0]
y = p[1]
data_array[y + 3, x + 3] = value
def print_data():
for row in range(7):
for column in range(7):
print(f' {data_array[row][column]:3}', end='')
print('')
for n in range(49):
p = f(n)
write_data(p, n)
print_data()
Displaying the output in the same coordinate system used in the question
(left-handed Cartesian coordinates with the $y$ axis pointing down and the $x$ axis pointing right), the output looks like this:
30 29 28 27 26 25 48
31 12 11 10 9 24 47
32 13 2 1 8 23 46
33 14 3 0 7 22 45
34 15 4 5 6 21 44
35 16 17 18 19 20 43
36 37 38 39 40 41 42
As a bonus, if you append the following to the Python code shown above,
you get a function sequence_number that takes a pair of coordinates and
returns the value of $n$ such that $f(n)$ is the given pair of coordinates.
That is, sequence_number is the inverse of f.
Using sequence_number, we can produce the same output shown above, but it is computed left to right one row at a time instead of by increasing values of $n.$
def taxicab_distance(u,v):
return max(abs(v[0] - u[0]), abs(v[1] - u[1]))
Return the value of n such that f(n) has the given coordinates
def sequence_number(p):
x = p[0]
y = p[1]
k = max(x, y, -x, -y) # the cycle number
side = (3 if x == k
else 2 if y == k
else 1 if x == -k
else 0)
ref_position = k * corner_vectors[side]
distance_along_side = taxicab_distance(p, ref_position)
side_length = side_length_in_cycle(k)
n = first_number_in_cycle(k) + side * side_length + distance_along_side - 1
return n
#-----------------------------------------------------------
Code to test sequence_number(p)
print('plotting f(n) -> n')
for y in range(-3, 4):
for x in range(-3, 4):
p = vector(x,y)
n = sequence_number(p)
print(f' {n:3}', end='')
print('')