# Implement grambulation

Consider the following spiral of positive integers:

We now define grambulation as a binary operation $$\\lozenge : \mathbb N \times \mathbb N \to \mathbb N\$$, using this grid. Some example inputs and outputs for grambulation are:

\begin{align*} 1 & \lozenge 9 = 25 \\ 1 & \lozenge 2 = 11 \\ 11 & \lozenge 10 = 25 \\ 9 & \lozenge 1 = 5 \\ 19 & \lozenge 4 = 13 \\ 76 & \lozenge 6 = 62 \\ 17 & \lozenge 17 = 17 \\ 62 & \lozenge 55 = 298 \end{align*}

Feel free to try to figure out the pattern before continuing.

### How to grambulate $$\x\$$ and $$\y\$$

The two coordinates of the inputs, $$\x \lozenge y\$$, in the spiral grid above are found, where $$\1\$$ is located at $$\(0, 0)\$$, $$\2\$$ at $$\(1, 0)\$$ and so on. For $$\x \lozenge y\$$, call these coordinates $$\x'\$$ and $$\y'\$$. You then find the vector from $$\x'\$$ to $$\y'\$$, and calculate the coordinates found by applying this vector to $$\y'\$$.

A worked example: $$\3 \lozenge 11 = 27\$$. First, we calculate our coordinates: $$x' = (1, 1), y' = (2, 0).$$ Next, we see that the vector from $$\x'\$$ to $$\y'\$$ is $$\\vec v = (+1, -1)\$$. Finally, we add this to $$\y'\$$ to get the coordinate $$\(3, -1)\$$, which is the coordinates of $$\27\$$.

Alternatively, a visual demonstration:

Note from the $$\62 \lozenge 55 = 298\$$ example above, our spiral is not limited to integers below $$\121\$$, and in fact, this binary operation is well defined for all pairs of positive integers.

Some properties of grambulation:

• $$\x \lozenge x = x\$$

• $$\x \lozenge y = z \iff z \lozenge y = x\$$

• It is non-associative and non-commutative

• It is non-injective but is surjective

• $$\x^2 \lozenge y^2 = z^2\$$ where $$\x < y\$$ and $$\x, y, z\$$ all have the same parity

Additionally, $$\x^2 \lozenge y^2 = z^2 - 1\$$ where $$\x\$$ and $$\y\$$ have different parities

Further, $$\x^2 \lozenge (x + 2c)^2 = (x + 4c)^2\$$ for $$\x, c \in \mathbb N\$$.

Given two positive integers $$\x, y \in \mathbb N\$$, output $$\x \lozenge y\$$. You may take input and output in any reasonable format or method, and this is , so the shortest code in bytes wins.

Note that the order of inputs does matter ($$\x \lozenge y \ne y \lozenge x\$$ for most $$\x, y \in \mathbb N\$$), but you may take input in either consistent order.

### Test cases

  x   y   x◊y
1   9    25
1   2    11
11   2     1
11  10    25
9   1     5
19   4    13
76   6    62
17  17    17
62  55   298
3  11    27
16 400  1296
182 240   306
249   1   281
281   1   249
32  98   196
88  16   202
60  41   210
59  85   227


And, a visual guide for the last few, on a larger grid:

# MATL, 22 bytes

sQ1YLXIG"I@=&fh]Ew-X{)


Try it online!

As a bonus, see the beautiful pattern of the function for all pairs of inputs from 1 to L. MATL Online times out for L moderately large, but here's the result for L=100 (click here for a view with better resolution):

### How it works

        % Implicit input: row vector of two numbers.
sQ      % Sum, add 1. This is enough for the size of the spiral
1YL     % Matrix with spiral of that size
XI      % Copy into clipboard I
G"      % For each number in the input
I     %   Push the spiral again
@=    %   True for the entri that equals the number
&fh   %   Row and column indices of that entry as a vector of two numbers
]       % End
E       % Multiply by two, element-wise
w-      % Swap, subtract. Gives the coordinates of the result
X{      % Convert to cell array, for indexing
)       % Index into the spiral
% Implcit display


The version with graphical input is as above but replaces the input by a loop over all pairs of values from 1 to L. The results are concatenated and reshaped as a square matrix, which is displayed with the autumn colormap.

• Just out of curiosity, does MATL display the y-axis like that by default? I took me a bit to realize it was flipped from the convention. Commented Apr 6, 2023 at 18:00
• @WheatWizard Ah, yes, it follows MATLAB's convention that images are displayed that way by default ("ij" or "matrix" axes mode). On the other hand, plots have the vertical axis increasing upwards, as usual ("xy" or "Cartesian" axes mode) Commented Apr 6, 2023 at 20:25

# Pyth, 4932 31 bytes

@Jsua_MCG+LheeGUGysQ]]1s-MPBxLJ


Try it online!

Takes input as [y, x].

• Jsua_MCG+LheeGUGysQ]]1: Generate the spiral to a sufficient size, then collapse it to a flat list and save it in J.

• xLJ: Find the index in J of the two inputs.

• s-MPB: Convert to the target index.

• @: Index into J to get the target number.

# Wolfram Language (Mathematica), 187 bytes

(t=(n=#;{1,-1}(Sum[#[.5π⌊(4k-3)^.5⌋],{k,n-1}]&/@{Sin,Cos}))&;(x=#1;y=#2;s=#1+#2;Which[x>y&&x>-y,(2x-1)^2+s,x<=y&&x>-y,4y^2-s+1,x<=y&&x<=-y,4x^2-s+1,True,(2y-1)^2+s])&@@(2t@#2-t@#1))&


Try it online!

Ungolfed self-explained:

Clear[toXY, fromXY, main];
toXY[n_] := {
Sum[
Sin[.5 π ⌊Sqrt[4 k - 3]⌋], {k,
n - 1}],
-Sum[
Cos[-.5 π ⌊Sqrt[4 k - 3]⌋], {k,
n - 1}]};

fromXY[{x_, y_}] := Which[
x > y && x > -y, (x*2 - 1)^2 + (x + y),
x <= y && x > -y, (y*2)^2 - (x + y) + 1,
x <= y && x <= -y, (x*2)^2 - (x + y) + 1,
True, (y*2 - 1)^2 + (x + y)
];

main[n1_, n2_] := fromXY[2 toXY@n2 - toXY@n1];


# Raku, 74 bytes

{.first(2*.[$^b]-.[$^a]):k given ½,0,|[\+] flat (1,*i...*)Zxx(1,1.5...*)}


Try it online!

• { expr1 given expr2 } is the function body. It evaluates expr1 after assigning the value of expr2 to the $_ topic variable. expr2 here is an expression for the lazy infinite sequence of spiral coordinates, in the form of complex numbers. • (1, *i ... *) is the sequence of 1, i, -1, and -i, repeated infinitely. • (1, 1.5 ... *) is the infinite arithmetic sequence 1, 1.5, 2, 2.5, 3, 3.5, .... • Zxx Zips the two preceding sequences together using the xx replication operator: 1 xx 1, i xx 1.5, -1 xx 2, -i xx 2.5, .... The half-integer replication numbers are rounded down, producing (1), (i), (-1, -1), (-i, -i), (1, 1, 1), (i, i, i), .... • flat flattens that list of lists. • [\+] scans that list with the addition operator +, producing the infinite list of partial sums. This is the spiral, except that it lacks the first origin element 0. • ½, 0, |... pastes the origin 0 onto the front of the spiral, as well as another leading element ½ to make the whole thing 1-indexed instead of 0-indexed. • .[$^a] and .[$^b] are the coordinates in the spiral of the first and second arguments to the function, respectively. • 2 * .[$^b] - .[\$^a] are the coordinates of the grambulated output.
• .first(...):k finds the first index (thanks to the :key adverb) within the spiral where the value is the coordinates found in the previous step.

# JavaScript (ES6), 132 bytes

Expects (p)(q) and returns $$\p\lozenge q\$$.

p=>q=>((g=(t,h=o=[1],v=n=x=y=0)=>n==t|x+[,y]==t?x:g(t,(i=o[o[[x-=h,y-=v]]=++n,[x-v,y+h]])?h:v,i?v:-h))(q),Y=2*y,g([2*x-g(p),Y-y]),n)


Try it online!

### How?

The helper function $$\g\$$ expects a target $$\t\$$ which is either the index or the coordinates of an element in the spiral.

Whenever $$\g\$$ is called, it starts at the origin and walks through the spiral until the target is reached, keeping track of its index $$\n\$$ and its position $$\(x,y)\$$ (all these variables being available in the global scope).

We first invoke $$\g(q)\$$ to get the coordinates $$\(X,Y)\$$ of the 2nd element, then $$\g(p)\$$ to get the coordinates $$\(x,y)\$$ of the 1st element, and finally $$\g([2X-x,2Y-y])\$$ to get the index of $$\p\lozenge q\$$.

# Pyth, 654745383431 30 bytes

-18 bytes by changing the loop structure
-2 bytes by altering some assignment
-7 bytes by switching to integer coordinates
-1 byte from swapping input order, -3 bytes from better list construction
-3 bytes by switching to imaginary coordinates
-1 byte from better representation of i

xJ+bm=+Z^.j).Ey@d2^sQ3_i@LJQ_2


Try it online!

Takes input as a list of integers [y,x]. Generates a list J of spiral coordinates in the imaginary plane, then gets the index in J for 2*J[y] - J[x]. To generate this list, we take advantage of the fact that ceil(2*sqrt(x)) stays the same values for increasingly long intervals, in the exact same way that the direction of travel when traversing this kind of spiral does.

### Explanation

                                  # implicitly assign Z = 0 and Q = eval(input())
J                                # assign J to
m             ^sQ3            # map range(sum(Q)**3) over lambda d
=+Z                          #   add and assign to Z
^.j)                      #   i to the power of
.E                    #   ceiling
y@d2                #   2 * sqrt(d)
+b                              # prepend a char (to offset list)
xJ                                # find the index in J of
@LJQ      # map x and y to their coordinates in J
_i    _2    # double the first and subtract the second


# Wolfram Language (Mathematica), 6059 58 bytes

1//.i_/;{1,-2,1}.Tr/@+I^⌈2√Range[{##,i}-1]⌉!=0:>i+1&


Try it online!

Input [x, y].

# Charcoal, 68 bytes

ＮθＮη≔⁰ζ≔⁰εＷ¬ε«⊞υ⟦ⅈⅉ⟧≧⁺⊗¬ΣＫＤ²✳⁺²ζζ✳ζ¹¿›Ｌυ⌈⟦θη⟧≔⊕⌕υＥ§υ⊖η⁻⊗κ§§υ⊖θλε»⎚Ｉε


Try it online! Link is to verbose version of code. Explanation:

ＮθＮη


Input x and y.

≔⁰ζ


Start in an arbitrary direction.

≔⁰ε


Start not knowing x◊y.

Ｗ¬ε«


Repeat until x◊y is known.

⊞υ⟦ⅈⅉ⟧


Save the current position.

≧⁺⊗¬ΣＫＤ²✳⁺²ζζ


Turn if this is a corner.

✳ζ¹


Take one step.

¿›Ｌυ⌈⟦θη⟧


If we know x' and y', then...

≔⊕⌕υＥ§υ⊖η⁻⊗κ§§υ⊖θλε


... see if we can find x◊y.

»⎚Ｉε


Output x◊y.

I tried using complex numbers but that also took 68 bytes:

ＮθＮη≔⁰ζ≔⁰ε≔¹δＷ¬ζ«⊞υεＦ¬№υ⁺ε×δＩ1j≧×Ｉ1jδ≧⁺δε¿›Ｌυ⌈⟦θη⟧≔⊕⌕υ⁻⊗§υ⊖η§υ⊖θζ»Ｉζ


Attempt This Online! Link is to verbose version of code. Explanation:

ＮθＮη


Input x and y.

≔⁰ζ


Start not knowing x◊y.

≔⁰ε≔¹δ


Start at the origin in an arbitrary direction.

Ｗ¬ζ«


Repeat until x◊y is known.

⊞υε


Save the current position.

Ｆ¬№υ⁺ε×δＩ1j


If this is a corner, then...

≧×Ｉ1jδ


... rotate the direction.

≧⁺δε


Take one step.

¿›Ｌυ⌈⟦θη⟧


If we know x' and y', then...

≔⊕⌕υ⁻⊗§υ⊖η§υ⊖θζ


... see whether we know x◊y.

»Ｉζ


Output x◊y.

# Uiua, 47 chars

f←⁅≡/+○[¯+η.÷2×π⌊√-3×4↘1⇡]
+1⊗-∶×2⊃∩f(∵f↘1⇡ⁿ3+)


Try It Online!

takes input as y,x on the stack

# Scala, 395 bytes

395 bytes, it can be golfed much more. Golfed version. Try it online!

import scala.math._
type D=Double
def t(n:D)=(1 to n.toInt-1).map(k=>sin(.5*Pi*floor(sqrt(4*k-3)))).sum-> -(1 to n.toInt-1).map(k=>cos(-.5*Pi*floor(sqrt(4*k-3)))).sum
def f(p:(D,D))={val(x,y)=p
if(x>y&&x> -y)pow(2*x-1,2)+(x+y)else if(x<=y&&x> -y)pow(2*y,2)-(x+y)+1 else if(x<=y&&x<= -y)pow(2*x,2)-(x+y)+1 else pow(2*y-1,2)+(x+y)}
def m(n1:D,n2:D)={val(x,y)=t(n2);val(a,b)=t(n1);f((2*x-a,2*y-b))}


Ungolfed version. Try it online!

import scala.math._

object Main {
def toXY(n: Double): (Double, Double) = {
val x = (1 until n.toInt).map(k => sin(.5 * Pi * floor(sqrt(4 * k - 3)))).sum
val y = -(1 until n.toInt).map(k => cos(-.5 * Pi * floor(sqrt(4 * k - 3)))).sum
(x, y)
}

def fromXY(point: (Double, Double)): Double = {
val (x, y) = point
if(x > y && x > -y) pow(2*x - 1, 2) + (x + y)
else if(x <= y && x > -y) pow(2*y, 2) - (x + y) + 1
else if(x <= y && x <= -y) pow(2*x, 2) - (x + y) + 1
else pow(2*y - 1, 2) + (x + y)
}

def main(n1: Double, n2: Double): Double = {
val (x, y) = toXY(n2)
val (a, b) = toXY(n1)
fromXY((2*x - a, 2*y - b))
}

def main(args: Array[String]): Unit = {
println(main(182, 240))
}
}