Python 3, 371 274 255 and 253 bytes
def f(s,n):
for y in(a:=range(n)):
for x in a:r=min(x,y,n+~x,n+~y);i=4*r*(n-r)-2*r+x+y+2*(2*(n+~r)-x-y)*(y>x);H=max(n+~y,y)>x>min(y-2,n-y-2);b=x+1>n/2;print(s[i:]and s[i]or"|-..''><"[H+2*((n+2*~x)*b-~x==y)+4*((n+~x-x)*b+x==n+~y)],end=" -"[H])
print()
Try it online!
Minus 64 bytes thanks to @noodleman and another 11 bytes thanks to @Kevin Cruijssen! I then saved another 22 bytes. Most recent improvement of 6 bytes by @xnor and 13 bytes by me.
Instead of most other solutions (as far as I can tell), I tried calculating the index into the string. I dug up a post on StackOverflow that helped with this, though a lot was just throwing random equations and seeing what they do. Maybe the indexing equation can be simplified with a bit more thought, but it's surprisingly short already.
There are lots of places where -x-1
is replaced with +~x
(or variants). Booleans are heavily used for indexing and cancelling parts of equations with multiplication.
Ungolfed:
def function(string: str, number: int):
for y in range(number):
for x in range(number):
rounds = min(x, y, number - x - 1, number - y - 1)
# total increments after `rounds` revolutions and fix for overcounting
index = 4 * rounds * (number - rounds) - 2 * rounds
index += x + y # east + south increment
index += 2 * (2 * (number - rounds - 1) - x - y) * (y > x) # west + north
# See below for explanation
is_horizontal = max(number - y - 1, y) > x > min(y - 2, number - y - 2)
is_right = x + 1 > number / 2
# Both inlined
is_up_corner = x + 1 + is_right * (number - 2 * (x + 1)) == y
is_down_corner = x + is_right * (number - 1 - 2 * x) == number - y - 1
# is_middle = x + 1 - number % 2 == y == number // 2
# is_middle = is_up_corner and is_down_corner
# is_down = y > number / 2
# is_left_corner = y + is_down * (number - 2 * y) == x + 1
# is_right_corner = y + is_down * (number - 1 - 2 * y) != number - x - 1
print(
# string[index] if index < len(string) else
string[index:] and string[index] or \
# See below for explanation
"|-..''><"[is_horizontal + 2 * is_up_corner + 4 * is_down_corner],
end=" -"[is_horizontal]
)
print()
Explanation of is_horizontal
boolean:
There are effectively four quadrants in which we need to determine whether the coordinate is a horizontal part in the snake, we can do this for the upper right corner with number - y - 1 > x
. On its own this is not enough, because the lower part of the snake is now messed up. Taking y > x
works for the lower right quadrant, but messes up the snake above. Combining these two equations into max(number - y - 1, y) > x
leaves only the left part of the snake to fix. Creating conditions for the other corners you can combine x > min(y - 2, number - y - 2)
into one large expression.
------------. ------------. ------------.
.---------. | .---------. | .---------. |
--.-----. | | --.-----. | | | .-----. | |
----.-> | | | becomes ----.-> | | | becomes | | .-> | | |
----' | ' | | ----'---' | | | | '---' | |
--' | | | ' | --'-------' | | '-------' |
' | | | | | ' '-----------' '-----------'
Explanation of special character indexing into |-..''><
:
There are six special characters that we need to index into, which can be separated into three categories: Straight, corner and tail. By virtue of the equations, we can determine we need a tail if something is both an up- and down-corner. Obviously, if it is not a corner or tail, it should be a straight piece. Using the equation is_up_corner + 2 * is_down_corner
we can differentiate the four cases straight, up-corner, down-corner, and tail. To select the correct straight and tail pieces we still need a more granular distinction. Using is_horizontal
obviously works for straight pieces and additionally it works for the tail, because the left-pointing arrow has an appendage whereas the right-pointing does not. This can be combined with the previous equation by doubling the corners.
253 bytes solution
This solution outputs not a string, but a nested array/matrix of characters. This might be allowed according to the meta post: Strings and arrays of characters are interchangeable.
It's effectively the same as the above, but also returns instead of prints. It unfortunately requires *''.join()
in the inner loop, because you cannot populate an array with two items per iteration. (And by doubling the iterations I only managed to golf to 255.)
lambda s,n:[[*''.join((r:=min(x,y,n+~x,n+~y),i:=4*r*(n-r)-2*r+x+y+2*(2*(n+~r)-x-y)*(y>x),H:=max(n+~y,y)>x>min(y-2,n-y-2),b:=x+1>n/2,s[i:]and s[i]or"|-..''><"[H+2*((n+2*~x)*b-~x==y)+4*((n+~x-x)*b+x==n+~y)])[4]+" -"[H]for x in range(n))]for y in range(n)]
Try it online!