29
\$\begingroup\$

This challenge is about walking around a string like a snake.

s-n-a-k
.---. e
d <-' a
n-u-o-r

You will be given a string \$ S \$ of length \$ l \$ and a positive integer \$ n \$ where \$ n ^ 2 \ge l + 1 \$.

The string rotates 90° after writing \$ n \$ characters of \$ S \$, or when it hits itself. When \$ S \$ is being written horizontally, characters are delimited by hyphens -. When it is being written vertically, there is no delimiter.

Once all characters of \$ S \$ have been exhausted, continue to fill the snake with hyphens and pipes |, with periods . at corners at the top of the spiral and single-quotes ' at corners by the bottom of the spiral.

The final character of the spiral will be either < or >, depending on the direction the spiral is facing when it finishes.

When the snake has been constructed, output it in any reasonable way. The output may contain any whitespace that does not affect the snake's appearance, e.g. a fixed amount of leading whitespace on each line.

You can assume \$ S \$ will only contain mixed-case letters of the English alphabet. It will never be an empty string, but always have at least 1 character.

Here are some examples to test your program against:

S = snakearound

n = 4:

s-n-a-k
.---. e
d <-' a
n-u-o-r

n = 5:

s-n-a-k-e
.-----. a
| .-> | r
| '---' o
'---d-n-u

n = 6:

s-n-a-k-e-a
.-------. r
| .---. | o
| | <-' | u
| '-----' n
'---------d

n = 7:

s-n-a-k-e-a-r
.---------. o
| .-----. | u
| | .-> | | n
| | '---' | d
| '-------' |
'-----------'

==============

S = noodleman

n = 4:

n-o-o-d
.---. l
| <-' e
'-n-a-m

n = 6:

n-o-o-d-l-e
.-------. m
| .---. | a
| | <-' | n
| '-----' |
'---------'

==============

S = TheQuickBrownFoxJumps

n = 5:

T-h-e-Q-u
x-J-u-m i
o .-> p c
F '---s k
n-w-o-r-B

=============

S = pleaseno

N = 3:

p-l-e
o—> a
n-e-s

This is , so the shortest code per language, measured in bytes, wins.

\$\endgroup\$
11
  • 1
    \$\begingroup\$ Good challenge. I feel like we've spiralize ones before (though the hyphen/pipe is definitely new). That said, the only thing I can find is a similar challenge of my own for doing this triangularly \$\endgroup\$
    – Jonah
    Commented Aug 3, 2023 at 2:05
  • \$\begingroup\$ Typo on the n=7 example? Missing a u it seems. \$\endgroup\$
    – doug
    Commented Aug 3, 2023 at 9:17
  • \$\begingroup\$ Thanks for pointing that out @doug, it’s fixed now \$\endgroup\$ Commented Aug 3, 2023 at 11:02
  • \$\begingroup\$ Will it always end pointing left or right? Can the end never be up or down? \$\endgroup\$ Commented Aug 3, 2023 at 16:52
  • \$\begingroup\$ @CrisLuengo It can’t be up or down because math \$\endgroup\$ Commented Aug 3, 2023 at 17:10

17 Answers 17

10
\$\begingroup\$

K (ngn/k), 337 331 315 290 279 231 224 211 213 bytes

{(x,w)#@[(x*w)#" ";i,*|i;:;@["|-"{1|1=x|-x}@-':i:0,+\(,/-:\1,w:-1+2*x)4!2_&s;(-2_-2+\s;!#y);:;(".''."4!!-2+#s:,/+|1(2*1+)\|!x; y:,/((#l)#("-";"")){x/y}',''l:(0,((#y)>)#+\(0,(-1+#l)#-1 1)+l:1_|1+&x#2)_y)],"<>"2!x]}

Try it online!

-6 : Save a few bytes with shorter names.
-16 : Tidying up
-25 : Cruft
-46 : Golfing gets me back under limits
-7 : Cleaner
-13 : A single statement!
+2 : handle empty string

\$\endgroup\$
2
  • \$\begingroup\$ Needed helper functions to get around limits. \$\endgroup\$
    – doug
    Commented Aug 3, 2023 at 10:01
  • \$\begingroup\$ Bought some room with tidying up. \$\endgroup\$
    – doug
    Commented Aug 3, 2023 at 21:43
6
\$\begingroup\$

Pyth, 78 bytes

ju&K>*hhHH+z>s.i_Psm*Rd"-|"Qs@L_B".'"tQlz.e++b?%klGd\-@K+Hk+j\-<KH__MGStQ]@"<>

Try it online!

\$\endgroup\$
5
\$\begingroup\$

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!

\$\endgroup\$
8
  • \$\begingroup\$ Really nice technique! I was able to reduce it to A 340 byte monstrosity, but it's still quite golfable. \$\endgroup\$ Commented Aug 4, 2023 at 1:08
  • \$\begingroup\$ Here's 330: Try it online! \$\endgroup\$ Commented Aug 4, 2023 at 1:25
  • 2
    \$\begingroup\$ Couldn't help myself—307: Try it online! Now I'm really done lol \$\endgroup\$ Commented Aug 4, 2023 at 3:30
  • 1
    \$\begingroup\$ Two more minor golfs building upon @noodleman's 307-byter: n-1-2*x can be n+~x-x for -1 byte, and I think you can remove *(y+x<n-1) for another -10 bytes (it doesn't affect the output for the existing test cases in the TIO, but I might be missing an edge case for which this was added perhaps 🤔): 296 bytes - Try it online. \$\endgroup\$ Commented Aug 4, 2023 at 7:30
  • 1
    \$\begingroup\$ 268 bytes \$\endgroup\$
    – xnor
    Commented Aug 6, 2023 at 19:25
5
\$\begingroup\$

Python 3.8, 254 247 bytes

lambda n,s:(r:=range,G:=["<>"[n%2]],[(K:=(s+''.join((2*[(n+~h//4)*"-",(n-2-h//4)*"|"],".''.")[h%2][h//2%4]for h in r(4*n))[len(s):])[~(f+1)**2:],G:=['-'.join(K[:f+1])]+[G[~g][::-1]+"- "[f-g>1]+K[f-~g]for g in r(f)])for f in r(1,n)])and'\n'.join(G)

Try it online!

Essentially a port of my Pyth answer.

I'll hand wave a bit of an explanation. We start by assigning G to the center of the spiral, n being even or odd determines if the arrow points left or right. Next we construct the string for the spiral as if s was an empty string, then replace the beginning of that string with s. We then build the rest of the square, each iteration we flip G 180 degrees and add to the top and the right side. After n-1 iterations of this, we return G joined on newlines.

\$\endgroup\$
5
+100
\$\begingroup\$

Python 3.8 (pre-release), 239 209 205 204 199 197 196 bytes

def f(s,n):
 i=n%-2;g="--.\n<-'>"[i:7-i]
 while(i:=i+2)<n:m="--"*i;g=m+f"--.\n.-%s |\n'{m}-'"%g.replace("\n"," |\n| ")
 z=d=0;*a,=g
 for c in s:d+=a[z]in".'";a[z]=c;z+=2*(1,n,-1,-n)[d%4]
 return a

Try it online!

I was able to simplify again by a significant amount. Now the output is a simple array of characters (this is better than before).

The recursive function g makes an empty snake, function f fills in the given string s.

  • minus 30 bytes by simplification of function g
  • minus 4 bytes by inlining the function g (now non-recursive)
  • minus 1 byte thanks to c--
  • minus another 5 bytes thanks to c-- !
  • minus 2 bytes by using f-string and formatting
  • minus 1 byte thanks to c--

Here are my older versions:

Python 3.8 (pre-release), 271 269 257 254 bytes

This new version (built from the 275 bytes solution) stretches the output rules to the maximum, or even beyond? The output array contains iterables as elements, either strings or arrays of strings. By "".join(e) we always get a string out of element e.

  • Minus 2 bytes, thanks to an idea of noodle man!
  • Minus 12 bytes by myself, using new algorithm for spiral
  • Minus 3 bytes by myself - small improvement
g=lambda n:[(m:="-"*(2*n-3))+"-."]+[".|- "[i>0::2]+g(n-2)[i]+" |"for i in range(n-2)]+[f"'{m}'"]if n>2else["--.",">","<-'"][n<2::2]
def f(s,n):
 z=d=0;a=g(n)
 for c in s:l=list(a[z//n]);d+=l[z%n*2]in".'";l[z%n*2]=c;a[z//n]=l;z+=(1,n,-1,-n)[d%4]
 return a

Try it online!

Python 3.8 (pre-release), 361 ... 275 bytes

  • Minus 18 bytes, thanks to noodle man!
  • Minus 18 bytes additionally by myself
  • Minus 9 bytes by myself
  • Minus 9 bytes by myself
  • Minus 11 bytes by myself
  • Minus 20 bytes by returning a 2 dimensional array, thanks to noodle man!
  • Minus 1 byte by myself
g=lambda n:[(m:="-"*(2*n-3))+"-."]+[".|- "[i>0::2]+g(n-2)[i]+" |"for i in range(n-2)]+[f"'{m}'"]if n>2else["--.",">","<-'"][n<2::2]
def f(s,n):
 x,y,k,a=0,0,7,[list(s)for s in g(n)]
 for c in s:k=(7-5*(k>6),-5*(k>1),k)[".'".find(a[y][x])];a[y][x]=c;x+=k//3;y+=k%3-1
 return a

Try it online!

Recursive, but not shortest, unfortunately.

Function g makes an empty snake, function f fills in the string.

\$\endgroup\$
9
  • \$\begingroup\$ if d[x]==".": and the next line can both be if"."==d[x] for 1 byte saved each. \$\endgroup\$ Commented Aug 5, 2023 at 23:00
  • \$\begingroup\$ Here’s 346 bytes: Try it online! \$\endgroup\$ Commented Aug 5, 2023 at 23:33
  • \$\begingroup\$ Try it online! 343 bytes \$\endgroup\$ Commented Aug 5, 2023 at 23:45
  • \$\begingroup\$ I think you can save bytes by returning a 2d array of characters rather than printing the string. \$\endgroup\$ Commented Aug 11, 2023 at 20:14
  • \$\begingroup\$ @noodle man: yes, I see. I hesitated to do so, because the other solution also printed the result. But I think, it is ok by the rules of this challenge. \$\endgroup\$
    – Donat
    Commented Aug 11, 2023 at 21:09
5
\$\begingroup\$

BASH , 580 349 308 303 291 286 283 277 275 272 271 bytes

Found this very entertaining, thanks! First variant was 580 bytes, then 349, ..., now 271:

s=$1 n=$2 N=$[n+n-1];f(){ b=$1;for((j=1;j<n;j++));{((c<N))&&T+=%s;[[ $4 ]]&&h=$4 S[i+$2/2]=-;t=${s:c:1} S[i]=${t:-$b} b=$3;$[i+=$2,c++];S[i-($2/N)]=\ ;};};while((n>0));do f . 2 - \>;$[n-=a];f . $N \|;f \' -2 - \<;$[n--];f \' -$N \|;a=1;done;S[i]=$h;printf $T\\n "${S[@]}"

Try it online!

Squeezed to 271(with 2>/dev/null):

#!/bin/bash
s=$1 n=$2 N=$[n+n-1]
f(){ 
    b=$1
    for((j=1;j<n;j++)); {
        ((c<N))&&T+=%s
        [[ $4 ]]&&h=$4 S[i+$2/2]=-
        t=${s:c:1} S[i]=${t:-$b} b=$3
        $[i+=$2,c++]
        S[i-($2/N)]=\ 
    }
}
while((n>0));do
    f .  2    - \>; $[n-=a]
    f .  $N  \|
    f \' -2   - \<; $[n--]
    f \' -$N \|   ;   a=1
done;S[i]=$h;printf $T\\n "${S[@]}"

First variant was:

#!/bin/bash

s=$1
n=$2
l=${#s}
N=$((n+n-1)) # items per row

# Check if n is in range
(( (n*n)>=(l+1) )) || { echo "n must be 'n^2≥l+1'"; exit 1; }

# Fill the 'area' with spaces
for ((i=0; i<n*N; i++)); { snake[$i]=" "; }

# Create printing template
printf -v row "%${N}s"
row=${row// /%s}

# Setcounters
i=0
c=0
a=0


forward(){
    b='.'
    for ((j=1; j<n; j++)); do
        cut=${s:c:1}
        snake[$i]=${cut:-$b}
        snake[$((i+1))]="-"
        b='-'
        h='>'
        ((i+=2))
        ((c++))
    done
    ((n-=a))
}


down(){
    b='.'
    for ((j=1; j<n; j++)); do
        cut=${s:c:1}
        snake[$i]=${cut:-$b}
        b='|'
        ((i+=N))
        ((c++))
    done
}


back(){
    b="'"
    for ((j=1; j<n; j++)); do
        cut=${s:c:1}
        snake[$i]=${cut:-$b}
        snake[$((i-1))]="-"
        h='<'
        b='-'
        ((i-=2))
        ((c++))
    done
    ((n--))
}


up(){
    b="'"
    for ((j=1; j<n; j++)); do
        cut=${s:c:1}
        snake[$i]=${cut:-$b}
        b='|'
        ((i-=N))
        ((c++))
    done
    a=1
}


print(){
    printf "$row\n" "${snake[@]}"
#    echo $i,$c,$n
}


# Create snake
while ((n>0)); do
    forward
    down
    back
    up
done
snake[$i]=$h; printf "$row\n" "${snake[@]}"

Try it online!

\$\endgroup\$
7
  • 3
    \$\begingroup\$ Welcome to Code Golf and Coding Challenges! I’m glad you enjoyed my challenge. Unfortunately, per the site’s rules this answer is not a valid submission to my challenge, because it does not attempt to have shortened code. Per the site’s rules, a moderator is probably going to delete your post unless you edit it to include a golfed (shortened) version. For starters, you might want to check out codegolf.stackexchange.com/q/15279/108687 \$\endgroup\$ Commented Aug 28, 2023 at 11:33
  • \$\begingroup\$ This is Code Golf, so try to shorten your code. Other than that, I’m glad you found it fun! \$\endgroup\$ Commented Aug 28, 2023 at 13:04
  • 1
    \$\begingroup\$ I reduced your code to 529 bytes by removing unnecessary whitespace. I'm a total bash noob so I'm certain this can be golfed further by someone with more experience than I. Try it online! \$\endgroup\$ Commented Aug 28, 2023 at 14:29
  • 1
    \$\begingroup\$ 254 bytes, you can revert it to a one-liner if you like, it was just bothering me while golfing \$\endgroup\$
    – c--
    Commented Sep 8, 2023 at 14:36
  • \$\begingroup\$ Code like $[i+=$2,c++] isn't working in my BASH_VERSION=5.0.17(1)-release. I recall that older shells did honor math evaluations inside of $[ ] (I think?) ...Is another way to write this ((i+=$2,c++)) ? In any case, interesting! Cheers \$\endgroup\$
    – shellter
    Commented Dec 18, 2023 at 18:39
4
\$\begingroup\$

JavaScript (ES12), 188 bytes

Expects (string)(n). Returns a matrix of characters.

s=>n=>eval(`for(m=[k=x=y=0];~n;)for(d=-2;i=~n&&d++<2;n-=!n|d&!!y)for(;i++<(d&1?w=n*2-1:n||2);x-=d%2,y-=~-d%2)(m[y]||=[..."".padEnd(w)])[x]=d*x+1&1&&s[k++]||"|-..''< >"[i>2?d&1:d-3*~!n];m`)

Attempt This Online!

Alternate formula

I've found the following alternate formula for special characters where each entry appears only once in the lookup string. But in the end, it's just as long as the simpler one.

".<'>|-"[i > 2 ? d & 1 | 4 : !n ^ d * 3 & 2]

Commented

This is a version without eval() for readability.

// s = input string, n = size parameter
s => n => {
  for(
    // m[] is the output matrix
    // k is a pointer into s
    // (x, y) is the current position in the matrix
    m = [k = x = y = 0];
    // stop when n = -1
    ~n;
  )
    for( 
      // d is the direction
      d = -2;
      // stop when n = -1 or d = 2
      // i is the counter for the next loop
      i = ~n && d++ < 2;
      // decrement n if n = 0, or d is odd and y != 0
      n -= !n | d & !!y
    )
      for(
        ;
        // the width is 2n-1 if d is odd, or n if d is even
        // there's a special case for the last iteration
        i++ < (d & 1 ? w = n * 2 - 1 : n || 2);
        // (x, y) <- (x + dx, y + dy)
        x -= d % 2,
        y -= ~-d % 2
      )
        // if m[y] is undefined, initialize it to w spaces
        (m[y] ||= [..."".padEnd(w)])[x] =
          // if either d or x is even, attempt to get the next
          // character from s
          d * x + 1 & 1 && s[k++]
          // if falsy or undefined, use a special character:
          // - "|" or "-" for segments (i > 2)
          // - "." or "'" for corners (i = 2, n ≠ 0)
          // - "<" or ">" for the final character (i = 2, n = 0)
          || "|-..''< >"[i > 2 ? d & 1 : d - 3 * ~!n];
  return m
}
\$\endgroup\$
8
  • \$\begingroup\$ Wow, that's so short! Good job with the explanation. I might give this a small bounty though I'm not sure yet \$\endgroup\$ Commented Aug 5, 2023 at 0:27
  • \$\begingroup\$ Well, I was reluctant to use three nested loops and considered using some convoluted spiral formulas instead. But maybe it was the right decision after all. \$\endgroup\$
    – Arnauld
    Commented Aug 5, 2023 at 9:43
  • \$\begingroup\$ Oh huh, this seems to fail the "pleaseno" test case, outputting a < where there should be a lowercase L. \$\endgroup\$ Commented Aug 5, 2023 at 11:56
  • \$\begingroup\$ Thank you for reporting this. Now fixed. \$\endgroup\$
    – Arnauld
    Commented Aug 5, 2023 at 13:36
  • \$\begingroup\$ Could you explain to me why converting an array to a string outside of the function is not a case of this forbidden loophole? \$\endgroup\$
    – ccprog
    Commented Aug 6, 2023 at 14:12
3
\$\begingroup\$

Charcoal, 61 bytes

F⮌S⊞υιNηF⊖η«F⊖⁻ηι⎇υ⁺⊟υ-²↷²⎇υ⊟υ§.'ιF⁻⁻ηι²⎇υ⊟υ¹↷²⎇υ⊟υ§'.ι¹»§<>η

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

F⮌S⊞υι

Get the reverse of the input into a stack, so that characters can be processed by popping from it.

Nη

Input n.

F⊖η«

Repeat n-1 times. The loop counter is taken as an ascending number for the purposes of calculating which corner character to print, but for the side lengths I show how the actual number of repetitions of the inner loops vary.

F⊖⁻ηι

Repeat from n-1 down to once...

⎇υ⁺⊟υ-²

... output the next character followed by a -, or two -s if none.

↷²

Start moving vertically.

⎇υ⊟υ§.'ι

Output the next character or . or ' appropriately if there are none left.

F⁻⁻ηι²

Repeat from n-2 down to zero...

⎇υ⊟υ¹

... output the next character or | if there are none left.

↷²

Start moving horizontally, but in the other direction.

⎇υ⊟υ§'.ι¹

Output the next character or ' or . appropriately if there are none left.

»§<>η

Output the head of the snake.

\$\endgroup\$
3
\$\begingroup\$

Javascript, 384 371 304 288 263 235 bytes

s=(S,n)=>(t=[...S],A=Array,a=A.from(A(n),r=>A(2*n).fill(e=' ')),x=y=0,p=(b,d,f)=>(a[y][x]=x&1&&t.shift()||b[d],f?.(d)),i=2*n-1,m=d=>(z=d&2,d&1?y+=1-z:x+=1-z,--i?p('-|-|',d,m):n<2?p('>><<',d):(i=d&1?2*--n:n-1,p("..''",++d%4,m))),m(0),a)

-13 bytes saved thanks to Samathingamajig
-67 bytes by folding functions u() and d() into v(±1) and l() and r() into h(±1)
-16 bytes with only one recursive function
-25 bytes because of this
-28 bytes with more golfing

Try out

s=(S,n)=>(t=[...S],A=Array,a=A.from(A(n),r=>A(2*n).fill(e=' ')),x=y=0,p=(b,d,f)=>(a[y][x]=x&1&&t.shift()||b[d],f?.(d)),i=2*n-1,m=d=>(z=d&2,d&1?y+=1-z:x+=1-z,--i?p('-|-|',d,m):n<2?p('>><<',d):(i=d&1?2*--n:n-1,p("..''",++d%4,m))),m(0),a)

;[
  [ 'snake', 4 ],
  [ 'snakearound', 5 ],
  [ 'snakearoundandround', 6 ]
]
.forEach(([ S,n ]) => {
  console.log(s(S,n).map(r=>r.join('')).join('\n'))
})

Ungolfed

s = (S,n) => {
  t = [...S];
  a = Array.from(Array(n), r => Array(2*n).fill(e=' '));
  x = y = 0;
  p = (b,d,f) => {            // set letter in all odd positions until
                              // exhausted, otherwise use snake
    a[y][x] = x&1 && t.shift() || b[d];
    f?.(d);                   // recurse unless end
  }
  i = 2*n-1;                  // counter for going in the same direction
  m = d => {
                              // the next position is set before p is called
    z = d&2;                  // forward or back?
    if (d&1) {                // vertical or horizontal?
      y+=1-z;
    } else {
      x+=1-z;
    }
    if (--i) {               // go on
      p('-|-|', d, m);
    } else if (n<2) {        // stop
      p('>><<', d);
    } else {                 // next direction
      i = d&1 ? 2*--n : n-1; // set going length, decrement n if horizontal
      p("..''", ++d%4, m);
    }
  }
  m(0);
  return a;
}
\$\endgroup\$
3
  • \$\begingroup\$ I was able to get this to 369 by adding ,A=Array (either to the parameters list or after t=[...S]) and then replacing all Arrays with A, and converting all p("something") to p`something` (in the case of \n, you can have a literal newline instead of the characters \\ and n), and also removing s=. On CGCC, you can have an expression that evaluates to a function, you don't need to store it to a variable in your answer (you would, however, if you were doing recursion, but you never use s in your function body) \$\endgroup\$ Commented Aug 3, 2023 at 21:21
  • \$\begingroup\$ @Samathingamajig I think putting the assignment into the first usage of Array like (A=Array).from(...) might save a couple bytes. \$\endgroup\$ Commented Aug 3, 2023 at 22:31
  • \$\begingroup\$ @noodleman nope, same length. \$\endgroup\$
    – ccprog
    Commented Aug 3, 2023 at 22:33
2
\$\begingroup\$

Python3, 425 bytes

R=range
def f(n,s):
 r=[[' ']*2*n for x in R(n)];x=y=X=K=0;Y=C=1
 while 1:
  K+=1
  if s:r[x][y]=['-',s[0]][C%2or X!=0];s=s[C%2or X!=0:];C+=1
  else:r[x][y]='-|'[X!=0]
  if(y+Y==2*n-1or(0<=y+2*Y<2*n-1and' '!=r[x][y+2*Y])or Y<-y)and Y:
   if K==2:r[x][y]='<>'[Y>0];return r
   if r[x][y]in'-|':r[x][y]="'."[Y>0]
   X=Y;Y=0
  elif(x+X==n or' '!=r[x+X][y])and X:
   if r[x][y]in'-|':r[x][y]="'."[X<0]
   Y=-X;X=C=K=0
  x+=X;y+=Y

Try it online!

\$\endgroup\$
4
  • \$\begingroup\$ Couldn't x,y=0,0;X,Y=0,1;C=1;K=0 also just be x=y=X=K=0;Y=C=1? \$\endgroup\$ Commented Aug 3, 2023 at 20:58
  • \$\begingroup\$ You can also use "'" to save 2 bytes, and indexing "'."[Y>0] saves a bit more in the 4 cases you use an array right now \$\endgroup\$
    – anderium
    Commented Aug 3, 2023 at 22:31
  • \$\begingroup\$ Managed to get it to 425 bytes with a few additional tricks: Try it online! \$\endgroup\$
    – anderium
    Commented Aug 3, 2023 at 23:28
  • \$\begingroup\$ @anderium Very clever, thank you. See updated post \$\endgroup\$
    – Ajax1234
    Commented Aug 4, 2023 at 0:29
2
\$\begingroup\$

Perl 5, 236 224 222 214 190 bytes

  • minus 24 bytes, thanks to: good old time!
sub g{my$m="--"x($n=-2+pop);$m?"$m--.\n.-@{[g($n)=~s/\n/ |\n| /gr]} |\n'$m-'":$n?">":"--.\n<-'"}sub f{$a=g$p=pop;$z+=(1,$p,-1,-$p)[($#_+=(substr$a,2*$z,1,$_)=~/[.']/)%4]for$_[$z=0]=~/./g;$a}

Try it online!

I have derived this from my Python answer. Function g makes an empty snake, function f fills in the string.

Here is the same code with a little more spacing for better readability:

sub g{
  my$m="--"x($n=-2+pop);
  $m?"$m--.\n.-@{[ g($n)=~s/\n/ |\n| /gr ]} |\n'$m-'":$n?">":"--.\n<-'"
 }
sub f{
  $a=g$p=pop;
  $z+=(1,$p,-1,-$p)[($#_+=(substr$a,2*$z,1,$_)=~/[.']/)%4] for $_[$z=0]=~/./g;
  $a
}
\$\endgroup\$
2
  • \$\begingroup\$ 214 reduced to 190: Try it online! \$\endgroup\$ Commented Aug 19, 2023 at 15:41
  • \$\begingroup\$ @goodoldtime: Wow! Very nice. Good work! I wouldn't have believed that one can still shorten it by this huge amount. Now shorter than my Python solution, again. \$\endgroup\$
    – Donat
    Commented Aug 19, 2023 at 18:48
2
\$\begingroup\$

JavaScript (Node.js), 193 192 190 188 187 182 177 bytes

s=>n=>s.map(c=>a[d*=/[.']/.test(a[z-=d])?d*d<5?n:-1/n:1,z]=c,d=z=-2,a=[...g(n)])&&a
g=(n,m)=>--n?(m="--".repeat(--n))+`--.
${n?`.-${g(n).split`
`.join` |
| `} |
'`+m:"<"}-'`:">"

Try it online!

Now even shorter than Arnauld's excellent answer!

  • Minus 5 bytes thanks to noodle man!
  • Minus 5 bytes by clever improvement thanks to Arnauld!

This answer has been derived from my Python solution.

Function g makes an empty snake and function f updates it with the chars from string s. The function has to be called by f(s,n) and returns a plain array of single char strings.

\$\endgroup\$
2
  • \$\begingroup\$ You can save 5 bytes by taking s as an array of characters: 182 \$\endgroup\$ Commented Aug 24, 2023 at 20:07
  • \$\begingroup\$ Nice method! 177 bytes \$\endgroup\$
    – Arnauld
    Commented Aug 24, 2023 at 21:35
2
\$\begingroup\$

AWK, 332 321 317 281 273 257 bytes

func R(C){r=++c<A*A?length($1)<c?i<A?C:S%2?"'":".":substr($1,c,1):S%2?"<":">";g[x,y]=S%2?G r:r G}func f(){for(x=c=S=i=0;i<A=$2;R(B="-",G=++i<A?B:""))y=i;while(S++<=A){for(i=S;i++<A;R("|",G=i<A?" ":B))x+=t=S%2*2-1;for(i=S;i++<A;R(B,G=i<A?B:""))y-=t}return G}

Try it online!

  • -11 bytes, replaced condition ? var = val1 : var = val2 with var = condition ? val1 : val2
  • -4 bytes, replaced function with func
  • -36 bytes, thanks to @ceilingcat. Chained =, chained ternary operator and other optimization
  • -8 bytes, thanks to @ceilingcat
  • -16 bytes, thanks to @ceilingcat. Passed ternary operator as second arg for func R(C) and moved incrementation of c in same function to allow more efficient one-liner loop.

Ungolfed

function result(string2)
{
    rep = string2
    if ( i == $2-1 ) step % 2 ? rep = "'" : rep = "."
    if ( length($1) > cpt ) rep = substr($1, cpt+1, 1)
    if ( cpt == ($2 * $2)-1 ) step % 2 ? rep = "<" : rep = ">"
    return rep
}

function snakearound()
{
    x = 0
    y = 0
    cpt = 0
    step = 0
    for(i = 0; i < $2; i++)
    {
        grid[x, y] = result("-")
        if (i+1 < $2) grid[x, y] = grid[x, y] "-"
        cpt++
        y++
    }
    y--
    while(step <= $2)
    {
        step++
        step % 2 ? tmp = 1 : tmp = -1
        for(i = step; i < $2; i++)
        {
            x += tmp
            grid[x, y] = result("|")
            i == $2-1 ? string = "-" : string = " "
            step % 2 ? grid[x, y] = string grid[x, y] : grid[x, y] = grid[x, y] string
            cpt++
        }
        for(i = step; i < $2; i++)
        {
            y -= tmp
            grid[x, y] = result("-")
            i == $2-1 ? string = "" : string = "-"
            step % 2 ? grid[x, y] = string grid[x, y] : grid[x, y] = grid[x, y] string
            cpt++
        }
    }

    for(i = 0; i < $2; i++){
        for(j = 0; j < $2; j++) printf("%s", grid[i, j])
        printf("\n")
    }
}
```
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Returning a matrix of characters is fine. \$\endgroup\$ Commented Aug 18, 2023 at 7:23
  • \$\begingroup\$ @ceilingcat thank ! I wasn't familiar with chaining =, nor was I with chaining ternary operator ! Furthermore you really have an eye for other small optimization ! \$\endgroup\$ Commented Oct 24, 2023 at 6:54
1
\$\begingroup\$

Perl 5, 255 244 bytes

sub r{$a='';$a.=$/while s/.$/$a.=$&;''/emg;$_=$a}sub f{($s=pop.'-'x--($n=2*pop)**2)=~s/.\K/-/g;$k=local$_=($"x$n.$/)x$n;r while s@ +$|>(  )+@$_=substr$s,0,-1+length$&,'';s/^-/$k&2?"'":'.'/e;$k++&1&&y/-/|/;"$_>"@me;$n&2&&y/>/</||r&r;s/.\K.//g;r}

Try it online!

Here's with newlines for better readability:

sub r{$a='';$a.=$/while s/.$/$a.=$&;''/emg;$_=$a}
sub f{
($s=pop.'-'x--($n=2*pop)**2)=~s/.\K/-/g;
$k=local$_=($"x$n.$/)x$n;
r while s@ +$|>(  )+@
$_=substr$s,0,-1+length$&,'';
s/^-/$k&2?"'":'.'/e;
$k++&1&&y/-/|/;
"$_>"
@me;
$n&2&&y/>/</||r&r;
s/.\K.//g;
r
}

I'm sure there are glaring opportunities to golf it further, but at least, for now, it's decently competitive with other answers, considering sigils. I was more interested to play with algorithm -- the square-ish string is actually rotated while being filled. Replace r while... with print && r while... to see intermediate steps.

P.S. It's funny to watch how snake's head moves and serves as anchor where to place the next fragment.

Edit: Just a little bit shorter. Rotation subroutine is also slightly less ugly and can now handle rectangular (not just square) shapes (but of course there ought to be Perl golfing pre-fab somewhere for the purpose, 20-odd years old, so I'm re-inventing the wheel), which fact was immediately used and lead to another couple bytes off.

In fact local can be ditched (-5) and TIO's footer written so that the $_ doesn't hold read-only value while testing. And further couple bytes less if string is temporarily padded with arbitrarily large number of hyphens (1E6, say) instead of value computed from n. But I'm not that much desperate, yet :)

\$\endgroup\$
1
\$\begingroup\$

05AB1E, 86 bytes

LR·<€D¬šIõ¹·nи«'-ýŽ9¦S.Λ¶¡ιн»¶«„ -‚Dð¶:«D'-'|::2äε„|-‚D'|„.'Nè::¹N+ÉiNi'''<.;ë¨'>«]J

Inputs in the order \$n,S\$, where \$S\$ is a list of characters.
Outputs with two trailing newlines. If this is not allowed, a trailing ¨ or ? could be added so it'll output a single trailing newline instead.

Try it online or verify all test cases.

Explanation:

Step 1: Prepare the input character-list by joining by - and adding additional trailing -. Then use the (modifiable) Canvas builtin to put this string itself and all spaces at the correct positions in the output rectangle:

LR·<€D¬š         # Create the list of lengths for the Canvas builtin:
L                #  Push a list in the range [1, first (implicit) input-integer]
 R               #  Reverse it to range [n,1]
  ·              #  Double each inner value
   <             #  Decrease each by 1
    €D           #  Duplicate each value within the list
      ¬š         #  And prepend an additional copy of the first/largest item
Iõ¹·nи«'-ý      '# Create the string to draw for the Canvas builtin:
I                #  Push the second input-list of characters
  ¹·n            #  Push the first input, double it, take the square of that: 4n²
 õ   и           #  Create a list with that many empty strings ""
      «          #  Append it to the input-list
       '-ý      '#  Join this list with "-" delimiter
Ž9¦S             # Create the list of directions for the Canvas builtin:
Ž9¦              #  Push compressed integer 2460
   S             #  Convert it to a list of digits: [2,4,6,0]
.Λ               # Use the (modifiable) Canvas builtin with these three options
¶¡ιн»            # Remove all (0-based) odd-indexed rows
¶¡               # Split it on newlines
  ι              # Uninterleave it into two parts
   н             # Pop and only leave the first part
    »            # Join it back together by newlines

Try just step 1 online.

For more information on how the Canvas builtin works, see this 05AB1E tip of mine.
See this 05AB1E tip of mine (section How to compress large integers?) to understand why Ž9¦ is 2460.

Step 2: Replace all - that are currently at edge or corner positions with |:

¶«               # Append a trailing newline
„ -              # Push string " -"
   Â             # Bifurcate, short for Duplicate & Reverse copy: "- "
    ‚            # Pair them together: [" -","- "]
     D           # Duplicate it
      ð¶:        # Replace all spaces with newlines in the copy: ["\n-","-\n"]
         «       # Merge the two together: [" -","- ","\n-","-\n"]
D                # Duplicate it
 '-'|:           # Replace all "-" with "|" in the copy: [" |","| ","\n|","|\n"]
:                # Replace all [" -","- ","\n-","-\n"] respectively with
                 # [" |","| ","\n|","|\n"]

Try the first two steps online.

Step 3: Replace all | at corner positions with either . (for the top halve) or ' (for the bottom halve):

2ä               # Split the string into two equal-sized parts
  ε              # Map over both parts:
   „|-           #  Push string "|-"
      ‚         #  Bifurcate and pair again: ["|-","-|"]
   D             #  Duplicate it
    '|     :    '#  Replace all "|" with the following in the copy:
      „.'Nè     '#   The map-index'th character of string ".'"
   :             #  Replace all ["|-","-|"] respectively with
                 #  [".-","-."] for the first iteration of the map
                 #  or ["'-","-'"] for the second iteration of the map

Try the first three steps online.

Step 4: And finally, fix the > or <, and output the result:

    i            #   If
¹N+              #   the first input + the (0-based) map-index
   É             #   is odd:
     Ni          #    If it's the second iteration of the map:
       '' '<.;  '#     Replace the first "'" with "<"
      ë          #    Else (it's the first iteration of the map)
       ¨         #     Remove the last character of the first part
        '>«     '#     And append a ">" instead
]                # Close both if-statements and the map
 J               # Join the two modified parts back together
                 # (after which the result is output implicitly with trailing newline)
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Thanks for the detailed explanation! \$\endgroup\$ Commented Aug 11, 2023 at 13:28
1
\$\begingroup\$

Haskell, 333 289 bytes

f s n=h s n 0 0$concatMap(++"\n").g$n
h(c:t)n d z m=take z l++c:drop(z+1)l where e=mod(d+sum[1|a<-".'",m!!z==a])4;l=h t n e(z+2*[1,n,-1,-n]!!e)m
h _ _ _ _ m=m
g 1=[">"]
g 2=["--.","<-'"]
g n=(m++"--."):(map(++" |").h)b++["'-"++m++"'"]where b=g(n-2);m=b>>"--";h(l:k)=[".-"++l]++map("| "++)k

Try it online!

Function g makes an empty snake as a list of strings. Function h fills in the given string s while converting the list of strings to a single string. Function f is the resulting target function.

In function h, d is the direction where to go for the next character of string s and z is the position of the character to replace.

\$\endgroup\$
1
\$\begingroup\$

Java (JDK), 279 278 276 bytes

char[]f(String s,int n){var a=g(n).toCharArray();int z=0,d=0;for(var c:s.toCharArray()){d+=a[z]/2&1;a[z]=c;z+=(d%2<1?1:n)*(d%4<2?2:-2);}return a;}String g(int n){String m;return n>1?(m="--".repeat(n-2))+"--.\n"+(n>2?".-"+g(n-2).replace("\n"," |\n| ")+" |\n'"+m:"<")+"-'":">";}

Try it online!

I have derived this from my JavaScript answer. Java is not really good for code golf, but I think, the result is not too bad.

  • minus 3 bytes, thanks to ceilingcat
\$\endgroup\$
2
  • \$\begingroup\$ @ceilingcat: Yes, of course. Thanks for the hint. \$\endgroup\$
    – Donat
    Commented Aug 28, 2023 at 19:52
  • \$\begingroup\$ I accidentally clicked “Accept” on this answer, it should be fixed now \$\endgroup\$ Commented Oct 26, 2023 at 19:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.