34
\$\begingroup\$

Input will consist of the following characters:

  • ^: Go up one
  • v: Go down one
  • or k: Go up two
  • or j: Go down two

For example, the following input:

^^▲^v▼▲^^v

would produce the following output:

        ^
   ^   ^ v
  ▲ v ▲

 ^   ▼
^

Escape sequences that move the cursor such as \e[B are not allowed. You must produce the output using spaces and newlines.

Here are a few more test cases.

▲v^v^v^v^v^v^v^v▲

                ▲
▲ ^ ^ ^ ^ ^ ^ ^ 
 v v v v v v v v

^^^^^^^▲▲▲▼▼▼vvvvvv

         ▲

        ▲ ▼

       ▲   ▼

      ^     ▼
     ^       v
    ^         v
   ^           v
  ^             v
 ^               v
^                 v

v^^vv^^vvv^v^v^^^vvvv^^v^^vv

  ^   ^         ^
 ^ v ^ v       ^ v       ^
v   v   v ^ ^ ^   v   ^ ^ v
         v v v     v ^ v   v
                    v
\$\endgroup\$
9
  • 1
    \$\begingroup\$ Are trailing space allowed? Empty lines? \$\endgroup\$
    – xnor
    Sep 26, 2015 at 3:23
  • 3
    \$\begingroup\$ What about languages that don't support Unicode? Can alternative characters be used? \$\endgroup\$
    – Doorknob
    Sep 26, 2015 at 3:24
  • 1
    \$\begingroup\$ @xnor You are allowed trailing spaces and/or empty lines. \$\endgroup\$
    – absinthe
    Sep 26, 2015 at 3:24
  • 2
    \$\begingroup\$ @Doorknob I'll allow j for going down twice and k for going up twice as well. \$\endgroup\$
    – absinthe
    Sep 26, 2015 at 3:25
  • 1
    \$\begingroup\$ @xnor My bad :/ The comment is correct and I edited the rules wrong. Will fix now. \$\endgroup\$
    – absinthe
    Sep 26, 2015 at 8:50

12 Answers 12

16
\$\begingroup\$

Unreadable, 2199 2145 2134 2104 2087 2084 bytes

Supports both k/j as well as / syntax.

In good Unreadable tradition, here is the program formatted in proportional font, to obfuscate the distinction between apostrophes and double-quotes:

'""""""'""""""""'"""'""'""'""'""'"""'"""""'""""""'""'""'""'""'"""'""'""""""'""""""'""""""""'"""'""'""'"""""""'""""""""'"""'""""""""""'""""'""""'""""'""""'""""'""""""'""'""'""'""'""'"""'""'"""'""""""'""'""'""'""'"""'"""""""""'"""""'""""""'""'""'""'""'"""'""""""""'"""""""'""'""'""'""'"""'""""'""""""'""'""'""'""'""'"""'"""""""""'"""""""'""'""'""'""'""'"""'""""""""'"""""""'""'""'""'""'""'"""'""'""'"""'""""""'"""'"""""""""'"""""""'"""'""'"""""""'"""'""""""""'""""""""'""""""""'""""""""'""""""""'"""'"""""""""'""'"""""""'"""'"""""""""'""'""'""'"""""""'"""'"""'""'""'"""'""'"""'"""""""""'"""""""'""'""'""'""'""'"""'"""'""'""'"""'""""""'"""'"""""""""'"""""""'"""'"""""""""'""'""'"""""""'"""'"""""""""'""'""'""'""'"""""""'"""'"""'""'""'"""'""'"""'"""""""""'"""""""'""'""'""'""'""'"""'""'""'"""'"""'"""""'""""""'""'""'""'""'"""'""""""""'"""""""'""'""'""'""'"""'""""'""""'"""""""""'"""""""'""'""'""'"""'""""""'""'""'""'"""'""'"""""""'""'""'""'"""'"""'""""""'""'""'"""'""'"""""""'""'""'"""'""""""'""'"""'""""""""'"""""""'""'"""'"""""'""""""'"""'""""""""'"""""""'"""'""""'""""'""""""'""'""'""'"""'""""""""'"""""""'""'""'""'"""'"""""""""'"""""""'""'""'"""'""""""'""'""'"""'""""""""'"""""""'""'""'"""'"""'""""""'""'"""'""'"""""""'""'"""'""""""'""'"""""""'""""""""'"""'""'"""""""'""'"""'"""""'""""""""'""""""'""'""'""'"""'""'"""""""'""'""'""'"""'""""'""""""'""'"""'""""""""'"""""""'""'"""'""""""'""'""'"""'""'"""""""'""'""'"""'"""""'""""""""'""""""'""'"""'""'"""""""'""'"""'""""'""""""'"""'""'"""""""'""""""""'"""'"""""'""""""""'""""""""'""""""'"""'""""""""'""""""""'"""""""'"""'""""""'"""""""'"""'""'"""""""'"""""""'"""'"""""'""'""'""""""'""'""'"""'""""""""'"""""""'""'""'"""'""""'""""'""""'""""""'""'"""'""'""'""'""'""'""""""'"""'""'""'""'""'"""'"""""'""""""""'""""""""'""""""""'""""""""'""""""""'""""""'""""""""'"""'""""""""'""""""""'"""""""'""""""""'"""'"'"""""""""'""""""'""'""""""'"""'""'""'"""""""'"""'""""""""'"""""""'""'"""""""'"""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'"""""""'""'"""'"""""""'"""""""'"""'""""""'""""""""'"""'""'""'"""""""'"""'"'"""""""'""'"""

This was an amazing challenge. Thank you for posting!

Explanation

To get a feel for what Unreadable can and can’t do, imagine Brainfuck with an infinite tape in both directions, but instead of a memory pointer moving one cell at a time, you can access any memory cell by dereferencing a pointer. This comes in quite handy in this solution, although other arithmetic operations — including modulo — have to be done by hand.

Here is the program as pseudocode with director’s commentary:

// Initialize memory pointer. Why 5 will be explained at the very end!
ptr = 5

// FIRST PASS:
// Read all characters from stdin, store them in memory, and also keep track of the
// current line number at each character.

// We need the +1 here so that EOF, which is -1, ends the loop. We increment ptr by 2
// because we use two memory cells for each input character: one contains the actual
// character (which we store here); the other will contain the line number at which the
// character occurs (updated at the end of this loop body).
while ch = (*(ptr += 2) = read) + 1:

    // At this point, ch will be one more than the actual value.
    // However, the most code-economical way for the following loop is to
    // decrement inside the while condition. This way we get one fewer
    // iteration than the value of ch. Thus, the +1 comes in handy.

    // We are now going to calculate modulo 4 and 5. Why? Because
    // the mod 4 and 5 values of the desired input characters are:
    //
    //  ch  %5  %4
    //  ^   1
    //  v   2
    //  k   3
    //  j   4
    //  ▲   0   2
    //  ▼   0   0
    //
    // As you can see, %5 allows us to differentiate all of them except ▲/▼,
    // so we use %4 to differentiate between those two.

    mod4 = 0      // read Update 2 to find out why mod5 = 0 is missing
    while --ch:
        mod5 = mod5 ? mod5 + 1 : -4
        mod4 = mod4 ? mod4 + 1 : -3

    // At the end of this loop, the value of mod5 is ch % 5, except that it
    // uses negative numbers: -4 instead of 1, -3 instead of 2, etc. up to 0.
    // Similarly, mod4 is ch % 4 with negative numbers.

    // How many lines do we need to go up or down?
    // We deliberately store a value 1 higher here, which serves two purposes.
    // One, as already stated, while loops are shorter in code if the decrement
    // happens inside the while condition. Secondly, the number 1 ('""") is
    // much shorter than 0 ('""""""""'""").
    up = (mod5 ? mod5+1 ? mod5+3 ? 1 : 3 : 2 : mod4 ? 3 : 1)
    dn = (mod5 ? mod5+2 ? mod5+4 ? 1 : 3 : 2 : mod4 ? 1 : 3)

    // As an aside, here’s the reason I made the modulos negative. The -1 instruction
    // is much longer than the +1 instruction. In the above while loop, we only have
    // two negative numbers (-3 and -4). If they were positive, then the conditions in
    // the above ternaries, such as mod5+3, would have to be mod5-3 etc. instead. There
    // are many more of those, so the code would be longer.

    // Update the line numbers. The variables updated here are:
    // curLine = current line number (initially 0)
    // minLine = smallest linenum so far, relative to curLine (always non-positive)
    // maxLine = highest linenum so far, relative to curLine (always non-negative)
    // This way, we will know the vertical extent of our foray at the end.

    while --up:
        curLine--
        minLine ? minLine++ : no-op
        maxLine++

    while --dn:
        curLine++
        minLine--
        maxLine ? maxLine-- : no-op

    // Store the current line number in memory, but +1 (for a later while loop)
    *(ptr + 1) = curLine + 1

// At the end of this, minLine and maxLine are still relative to curLine.
// The real minimum line number is curLine + minLine.
// The real maximum line number is curLine + maxLine.
// The total number of lines to output is maxLine - minLine.

// Calculate the number of lines (into maxLine) and the real minimum
// line number (into curLine) in a single loop. Note that maxLine is
// now off by 1 because it started at 0 and thus the very line in which
// everything began was never counted.
while (++minLine) - 1:
  curLine--
  maxLine++

// Make all the row numbers in memory positive by adding curLine to all of them.
while (++curLine) - 1:
  ptr2 = ptr + 1
  while (ptr2 -= 2) - 2:    // Why -2? Read until end!
    *ptr2++

// Finally, output line by line. At each line, we go through the memory, output the
// characters whose the line number is 0, and decrement that line number. This way,
// characters “come into view” in each line by passing across the line number 0.
while (--maxLine) + 2:    // +2 because maxLine is off by 1
  ptr3 = 5
  while (ptr -= 2) - 5:
    print (*((ptr3 += 2) + 1) = *(ptr3 + 1) - 1) ? 32 : *ptr3   // 32 = space
  ptr = ptr3 + 2
  print 10  // newline

So much for the program logic. Now we need to translate this to Unreadable and use a few more interesting golfing tricks.

Variables are always dereferenced numerically in Unreadable (e.g. a = 1 becomes something like *(1) = 1). Some numerical literals are longer than others; the shortest is 1, followed by 2, etc. To show just how much longer negative numbers are, here are the numbers from -1 to 7:

-1  '""""""""'""""""""'"""  22
 0  '""""""""'"""           13
 1  '"""                     4
 2  '""'"""                  7
 3  '""'""'"""              10
 4  '""'""'""'"""           13
 5  '""'""'""'""'"""        16
 6  '""'""'""'""'""'"""     19
 7  '""'""'""'""'""'""'"""  22

Clearly, we want to allocate variable #1 to the one that occurs most frequently in the code. In the first while loop, this is definitely mod5, which comes up 10 times. But we don’t need mod5 anymore after the first while loop, so we can re-allocate the same memory location to other variables we use later on. These are ptr2 and ptr3. Now the variable is referenced 21 times in total. (If you’re trying to count the number of occurrences yourself, remember to count something like a++ twice, once for getting the value and once for setting it.)

There’s only one other variable that we can re-use; after we calculated the modulo values, ch is no longer needed. up and dn come up the same number of times, so either is fine. Let’s merge ch with up.

This leaves a total of 8 unique variables. We could allocate variables 0 to 7 and then start the memory block (containing the characters and line numbers) at 8. But! Since 7 is the same length in code as −1, we could as well use variables −1 to 6 and start the memory block at 7. This way, every reference to the start position of the memory block is slightly shorter in code! This leaves us with the following assignments:

-1    dn
 0                      ← ptr or minLine?
 1    mod5, ptr2, ptr3
 2    curLine
 3    maxLine
 4                      ← ptr or minLine?
 5    ch, up
 6    mod4
 7... [data block]

Now this explains the initialization at the very top: it’s 5 because it’s 7 (the beginning of the memory block) minus 2 (the obligatory increment in the first while condition). The same goes for the other two occurrences of 5 in the last loop.

Note that, since 0 and 4 are the same length in code, ptr and minLine could be allocated either way around. ... Or could they?

What about the mysterious 2 in the second-last while loop? Shouldn’t this be a 6? We only want to decrement the numbers in the data block, right? Once we reach 6, we’re outside the data block and we should stop! It would be a buffer overflow bug error failure security vulnerability!

Well, think about what happens if we don’t stop. We decrement variables 6 and 4. Variable 6 is mod4. That’s only used in the first while loop and no longer needed here, so no harm done. What about variable 4? What do you think, should variable 4 be ptr or should it be minLine? That’s right, minLine is no longer used at this point either! Thus, variable #4 is minLine and we can safely decrement it and do no damage!

UPDATE 1! Golfed from 2199 to 2145 bytes by realizing that dn can also be merged with mod5, even though mod5 is still used in the calculation of the value for dn! New variable assignment is now:

 0    ptr
 1    mod5, dn, ptr2, ptr3
 2    curLine
 3    maxLine
 4    minLine
 5    ch, up
 6    mod4
 7... [data block]

UPDATE 2! Golfed from 2145 to 2134 bytes by realizing that, since mod5 is now in the same variable as dn, which is counted to 0 in a while loop, mod5 no longer needs to be explicitly initialized to 0.

UPDATE 3! Golfed from 2134 to 2104 bytes by realizing two things. First, although the “negative modulo” idea was worth it for mod5, the same reasoning does not apply to mod4 because we never test against mod4+2 etc. Therefore, changing mod4 ? mod4+1 : -3 to mod4 ? mod4-1 : 3 takes us to 2110 bytes. Second, since mod4 is always 0 or 2, we can initialize mod4 to 2 instead of 0 and reverse the two ternaries (mod4 ? 3 : 1 instead of mod4 ? 1 : 3).

UPDATE 4! Golfed from 2104 to 2087 bytes by realizing that the while loop that calculates the modulo values always runs at least once, and in such a case, Unreadable allows you to re-use the last statement’s value in another expression. Thus, instead of while --ch: [...]; up = (mod5 ? mod5+1 ? [...] we now have up = ((while --ch: [...]) ? mod5+1 ? [...] (and inside that while loop, we calculate mod4 first, so that mod5 is the last statement).

UPDATE 5! Golfed from 2087 to 2084 bytes by realizing that instead of writing out the constants 32 and 10 (space and newline), I can store the number 10 in the (now unused) variable #2 (let’s call it ten). Instead of ptr3 = 5 we write ten = (ptr3 = 5) + 5, then 32 becomes ten+22 and print 10 becomes print ten.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ This is...awful... +1 \$\endgroup\$ Oct 2, 2015 at 16:41
9
\$\begingroup\$

Pyth, 27 bytes

jCm.<.[*5lzd\ =+Ztx"v ^k"dz

Try it online: Demonstration or Test Suite

I use k and j instead of and . There are lots of leading and trailing empty lines. You have to search quite a bit to find the image. Here's a 34 byte version, that removes all leading and trailing empty lines.

j.sCm.<.[*5lzd\ =+Ztx"v ^k"dz]*lzd

Try it online: Demonstration or Test Suite

Explanation:

jCm.<.[*5lzd\ =+Ztx"v ^k"dz  implicit: Z = 0
  m                       z  map each char d from input string z to:
                  x"v ^k"d     find d in the string "v ^k", -1 if not found
                 t             -1, that gives -2 for j, -1 for v, 1 for ^ and 2 for k
              =+Z              add this number to Z
     .[*5lzd\                  append spaces on the left and on the right of d, 
                               creating a 5*len(input_string) long string
   .<           Z              rotate this string to the left by Z chars
jC                           transpose and print on lines
\$\endgroup\$
7
\$\begingroup\$

CJam, 37 bytes

r_,2*:L3*S*f{\_iImd8-g\8>)*L+:L\t}zN*

This prints empty lines before and after the desired output, which has been allowed by the OP.

Try it online in the CJam interpreter.

How it works

r_     e# Read a token from STDIN and push a copy.
,2*:L  e# Compute its length, double it and save it in L.
3*S*   e# Push a string of 6L spaces.
f{     e# For each character C in the input, push C and the string of spaces; then
  \    e#   Swap C with the string of spaces.
  _i   e#   Push a copy of C and cast it to integer.
  Imd  e#   Push quotient and remainder of its division by 18.
  8-g  e#   Push the sign((C%18) - 8). Gives -1 for ^ and ▲, 1 for v and ▼.
  \    e#   Swap the result with the quotient.
  8>)  e#   Push ((C/18) > 1) + 1. Gives 2 for ▲ and ▼, 1 for ^ and v.
  *    e#   Multiply both results. This pushes the correct step value.
  L+:L e#   Add the product to L, updating L.
  \t   e#   Replace the space at index L with C.
}      e# We've built the columns of the output.
z      e# Zip; transpose rows with columns.
N*     e# Join the rows, separating by linefeeds.
\$\endgroup\$
2
  • \$\begingroup\$ I think it would only be fair to explicitly state as a caveat that your solution produces copious amounts of extra newlines before and after the desired output... \$\endgroup\$
    – Timwi
    Sep 30, 2015 at 15:49
  • \$\begingroup\$ Added. (I didn't think it was necessary since the OP explicitly allowed empty lines.) \$\endgroup\$
    – Dennis
    Sep 30, 2015 at 22:10
3
\$\begingroup\$

Python 2, 102

s=input()
j=3*len(s)
exec"w='';i=j=j-1\nfor c in s:i-='kv_^j'.find(c)-2;w+=i and' 'or c\nprint w;"*2*j

Prints line by line.

Loops through characters in the input and tracks the current height. The height is updated by one of +2, +1, -1, -2 as computed by 'kv_^j'.find(c)-2. There's probably a mod chain that's shorter

When the current height equals the line number (which can be negative), we append the current character to the line, and otherwise append a space. Then, we print the line. Actually, it's shorter to start the height at the current line number and subtract the height changes, appending the character when the value hits 0.

The line numbers encompass a large enough range that a sequence of up-two or down-two will stay within it. Actually, there's a good amount of excess. If we had an upper bound on the input length, it would be shorter to write, say j=999.

Surprisingly, i and' 'or c was shorter than the usual [' ',c][i==0]. Note that i can be negative, which cuts out some usual tricks.

\$\endgroup\$
2
\$\begingroup\$

MATLAB, 116

function o=u(a)
x=0;y=1;o='';for c=a b=find(c=='j^ vk')-3;y=y+b;if y<1 o=[zeros(1-y,x);o];y=1;end
x=x+1;o(y,x)=c;end

It's a start. The j and k make it a pain in the neck as I can't find a way to mathematically map from j^vk to [-2 -1 1 2] and with MATLAB not recognising the Unicode (apparently both up and down have a value of 26 in MATLAB. Go figure!), there are a lot of bytes wasted doing the mapping.

By drawing inspiration from @xnors solution, the code can be reduced by another 14 characters by mapping the control character inside the for loop.

There are also many bytes wasted trying to account for if the input string sends the pattern back below the index at which it started (maybe if there was a limit on the string length I could simplify that bit).

And in its readable form:

function o=u(a)
%We start in the top left corner.
x=0; %Although the x coordinate is 1 less than it should be as we add one before storing the character
y=1;
o=''; %Start with a blank array
for c=a
    %Map the current character to [-2 -1 1 2] for 'j^vk' respectively.
    b=find(c=='j^ vk')-3;
    y=y+b; %Offset y by our character
    if y<1 %If it goes out of range of the array
        o=[zeros(1-y,x); o]; %Add enough extra lines to the array. This is a bit of a hack as 0 prints as a space in MATLAB.
        y=1; %Reset the y index as we have now rearranged the array
    end
    x=x+1; %Move to the next x coordinate (this is why we start at x=0
    o(y,x)=c; %Store the control character in the x'th position at the correct height.
end
\$\endgroup\$
4
  • \$\begingroup\$ Would b=[-2 -1 1 2](a==[106 107 94 118]) work? It works in Octave. Or even b=[-2 -1 1 2](a-94==[12 13 0 24]) if you want to shave off one more byte! \$\endgroup\$
    – wchargin
    Sep 26, 2015 at 6:15
  • \$\begingroup\$ @WChargin doesn't work in MATLAB. Unfortunately the behaviour of the == stops that working, and also in MATLAB you can't put a () after a []. \$\endgroup\$ Sep 26, 2015 at 6:25
  • \$\begingroup\$ Hmm…you could change the language to Octave! :) (Octave also has +=, fwiw.) \$\endgroup\$
    – wchargin
    Sep 26, 2015 at 20:50
  • \$\begingroup\$ @WChargin That is cheating=P But I agree, Octave has a lot of shortcuts that Matlab does not have. \$\endgroup\$
    – flawr
    Sep 27, 2015 at 12:06
2
\$\begingroup\$

JavaScript (ES6), 140

Test running the snippet below in a EcmaScript 6 compliant browser (tested on Firefox) .

f=s=>[...s].map(c=>{for(t=r[y+=c>'▲'?2:c>'v'?-2:c>'^'?1:-1]||x;y<0;y++)r=[,...r];r[y]=t+x.slice(t.length)+c,x+=' '},y=0,r=[x=''])&&r.join`
`

// Less golfed

f=s=>(
  y=0,
  x='',
  r=[],
  [...s].forEach( c =>
    {
      y += c > '▲' ? 2 : c > 'v' ? -2 : c > '^' ? 1 : -1;
      t = r[y] || x;
      while (y < 0)
      {
        y++;
        r = [,...r]
      }  
      r[y] = t + x.slice(t.length) + c;
      x += ' '
    }
  ),
  r.join`\n`
)  


//Test

;[
  '^^▲^v▼▲^^v'
, '▲v^v^v^v^v^v^v^v▲'
, '^^^^^^^▲▲▲▼▼▼vvvvvv'
, 'v^^vv^^vvv^v^v^^^vvvv^^v^^vv'  
].forEach(t=>document.write(`${t}<pre>${f(t)}</pre>`))
pre { border:1px solid #777 }

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

GS2, 34 bytes

This one correctly calculates the output bounds so no excess whitespace is produced. Here's my solution in hex

5e 20 76 6a 05 3e 26 ea 30 e0 6d 40 28 26 cf d3
31 e9 d0 4d 42 5e e2 b1 40 2e e8 29 cf d3 5c e9
9a 54

A little explanation is in order. On the stack we have user input as an array of ascii codes. The program starts in a string literal because of the 05. Here we go.

  5e 20 76 6a      # ascii for "^ vj"
  05               # finish string literal and push to stack
  3e               # index - find index in array or -1 if not found
  26               # decrement
ea                 # map array using block of 3 instructions (indented)

  30               # add 
e0                 # create a block of 1 instruction
6d                 # scan (create running total array of array using block)
40                 # duplicate top of stack
28                 # get minimum of array
26                 # decrement
cf                 # pop from stack into register D (this is the "highest" the path goes)

  d3               # push onto stack from register D
  31               # subtract
e9                 # map array using block of 2 instructions

d0                 # push onto stack from register A (unitialized, so it contains stdin)

  4d               # itemize - make singleton array (also is single char string)
  42               # swap top two elements in stack
  5e               # rjust - right justify string
e2                 # make block from 3 instructions
b1                 # zipwith - evaluate block using parallel inputs from two arrays
40                 # duplicate top of stack

  2e               # get length of array/string
e8                 # map array using block of 1 instruction
29                 # get maximum of array
cf                 # pop from stack into register D (this is the "lowest" the path goes)

  d3               # push from register D onto stack
  5c               # ljust - left justify string
e9                 # map array using block of two instructions
9a                 # transpose array of arrays
54                 # show-lines - add a newline to end of each element in array

GS2, 24 bytes

I also have a 24 byte solution that doesn't take as much care calculating output size, and ends up with extra whitespace. I prefer the one with the whitespace kept to a minimum though.

5e 20 76 6a 05 3e 26 ea 30 e0 6d d0 08 4d 42 d1
30 5e d1 5c 09 b1 9a 54
\$\endgroup\$
1
\$\begingroup\$

Crayon, 13 bytes (non-competing)

O"^ vj"\CynIq

Try it online! Uses the real arrows because why not.

Non-competing because Crayon is way newer than this challenge.

How it works

Crayon is a stack-based language designed to be killer at ASCII-art challenges. It is built around the basis of a 2-dimensional output "canvas", and a "crayon", a cursor that travels around this canvas. Anything that is sent to output is drawn on the canvas at the position of the crayon, and in the direction the crayon is facing. By default, the crayon points East (to the right).

O"^ v▼"\CynIq   Implicit: input string is on top of the stack
O               For each char I in the input string:
 "^ v▼"          Push this string.
       \         Swap the top two items (so I is on top).
        C        Take the index of I in the string.
                 This returns 3 for ▼, 2 for v, 0 for ^, and -1 for ▲.
         y       Move the crayon by this number of spaces on the Y-axis (south).
          n      Move the crayon one position north.
                 The crayon has now been translated 2 positions south for ▼,
                 1 south for v, 1 north for ^, and 2 north for ▲.
           Iq    Draw I at the crayon. This automatically moves the crayon forward
                 by the length of I, which is 1 in this case.
\$\endgroup\$
1
\$\begingroup\$

Husk, 30 bytes

↔T' z:mR' λm+a▼¹)∫m-2m€"v ^▲"¹

Try it online! or Verify all tests

Uses a few ideas from the Pyth answer, and the lambda can be shortened.

Since and are in Husk's codepage, you can see the testcases in their full glory!

Explanation

↔T' z:mR' λm+a▼¹)∫m-2m€"v ^▲"¹
                     m€"v ^▲"  get the indices of each char in the string
                  m-2          subtract 2 from each
                 ∫             cumulative sum
          λm+a▼¹)              add abs(minimum) to each
      mR'                      map each n to n spaces
    z:                         join the spaces with the input
 T'                            Transpose. padding with spaces
↔                              reverse
\$\endgroup\$
0
\$\begingroup\$

pb - 136 bytes

^w[B!0]{>}v[3*X]<[X]<b[1]^[Y]^>w[B!0]{t[B]<vw[B=0]{v}>w[T=107]{^^b[T]t[0]}w[T=94]{^b[T]t[0]}w[T=118]{vb[T]t[0]}w[T!0]{vvb[T]t[0]}^[Y]^>}

Uses k and j instead of and .

A couple of notes:

  • Escape sequences that move the cursor such as \e[B are not allowed. You must produce the output using spaces and newlines. I do follow this rule! pb uses the concept of a "brush" to output characters. The brush moves around the "canvas" and can print a character immediately below it. However, the actual implementation prints the character using spaces and newlines.
  • I wasn't going to bother with this challenge even though I thought it would be fun with pb until I saw the ruling that You are allowed trailing spaces and/or empty lines. This is for a couple of reasons:
    • pb can't not have trailing spaces. It always produces rectangular output, padding with spaces if necessary.
    • This program produces a lot of empty lines. It doesn't know how tall the output is going to be when it starts making it, so for an input of length n it starts at Y=3n+1. The -1 is because it's going down 3n from Y=-1, and starting at Y=2n-1 fails for an input of all k.

You can watch this program in action on YouTube! This version is slightly modified in that it only goes down to n-1. It works for this input, but will fail for others. It does, however, capture a lot nicer.

With comments:

^w[B!0]{>}             # Go to the end of the input
v[3*X]                 # Go down 3 times the current X value
<[X]<                  # Go to X=-1 (off screen, won't be printed)
b[1]                   # Leave a non-zero value to find later
^[Y]^>                 # Back to the beginning of the input
w[B!0]{                # For every byte of input:
    t[B]                 # Copy it to T
    <vw[B=0]{v}>         # Go 1 to the right of the character to the left
                         # (either the last one printed or the value at X=-1)
                         # Move the correct amount for each character and print it:
    w[T=107]{^^b[T]t[0]} # k
    w[T=94]{^b[T]t[0]}   # ^
    w[T=118]{vb[T]t[0]}  # v
    w[T!0]{vvb[T]t[0]}   # j (Every other possibility sets T to 0, so if T is not 0
                         #    it must be j. T!0 is shorter than T=106)
    ^[Y]^>               # To the next byte of input to restart the loop
}
\$\endgroup\$
0
\$\begingroup\$

Ceylon, 447 bytes

import ceylon.language{o=null,v=variable,s=shared}s void y(){v L c;v L f;v L l;v Integer i=0;class L(v L?p,v L?n){s v String t="";s L u=>p else(f=p=L(o,this));s L d=>n else(l=n=L(this,o));s void a(Character c)=>t=t+" ".repeat(i-t.size)+c.string;}f=l=c=L(o,o);for(x in process.readLine()else""){switch(x)case('^'){c=c.u;}case('v'){c=c.d;}case('▲'|'k'){c=c.u.u;}case('▼'|'j'){c=c.d.d;}else{}c.a(x);i++;}print(f.t);while(f!=l){f=f.d;print(f.t);}}

Or with line breaks for "readability": import ceylon.language{o=null,v=variable,s=shared}s void y(){v L c;v L f;v L l;v Integer i=0;class L(v L?p,v L?n){s v String t="";s L u=>p else(f=p=L(o,this));s L d=>n else(l=n=L(this,o));s void a(Character c)=>t=t+" ".repeat(i-t.size)+c.string;}f=l=c=L(o,o);for(x in process.readLine()else""){switch(x)case('^'){c=c.u;}case('v'){c=c.d;}case('▲'|'k'){c=c.u.u;}case('▼'|'j'){c=c.d.d;}else{}c.a(x);i++;}print(f.t);while(f!=l){f=f.d;print(f.t);}}

This works with both the ▲/▼ and the j/k input (If we had to support just one of them, the program would be 8 bytes shorter). The last output line is empty when the starting position was on it (i.e. the first input was a or ^ and we never got below that again later). Input which is not one of the specified characters will simply be printed as-is, without switching the line:

v^^vv^^vvv^v^v^^^Hellovvvv^^v^^vv

  ^   ^         ^Hello
 ^ v ^ v       ^      v       ^
v   v   v ^ ^ ^        v   ^ ^ v
         v v v          v ^ v   v
                         v

Here is a formatted version (753 bytes):

shared void y() {
    variable L c;
    variable L f;
    variable L l;
    variable Integer i = 0;
    class L(variable L? p, variable L? n) {
        shared variable String t = "";
        shared L u => p else (f = p = L(null, this));
        shared L d => n else (l = n = L(this, null));
        shared void a(Character c) => t = t + " ".repeat(i - t.size) + c.string;
    }
    f = l = c = L(null, null);
    for (x in process.readLine() else "") {
        switch (x)
        case ('^') { c = c.u; }
        case ('v') { c = c.d; }
        case ('▲' | 'k') { c = c.u.u; }
        case ('▼' | 'j') { c = c.d.d; }
        else {}
        c.a(x);
        i++;
    }
    print(f.t);
    while (f != l) {
        f = f.d;
        print(f.t);
    }
}

This is an almost straight-forward "object-oriented" program ... the (local) class L (line buffer) stores a line of text (in t), as well as (nullable) pointers to the next (n) and previous (p) line. The (not nullable) attributes u (for up) and d (for down) initialize those if needed (with a reverse pointer to itself), and in this case also keep track of the first and last line overall (in the f and l variables).

The a (append) method appends a character to this line, including some spaces eventually necessary.

c is the current line. We parse the input string (using readLine as the input should be on one line) using a switch statement which updates the current line, and then calls the append method.

After parsing is done, we iterate over the lines from the first to the last, printing each of them. (This destroys the f pointer, if it were needed afterwards, we should have used a separate variable for this.)

Some used tricks for golfing:

  • Some stuff which in other languages would be keywords are actually just identifiers in the ceylon.language package, and can be renamed with an alias import – we used this for the annotations shared (used 5×) and variable (used 6×), as well as for the object null (used 4×):

    import ceylon.language{o=null,v=variable,s=shared}
    

    (Trivia: The Formatter in the Ceylon IDE formats some built-in language annotations, between them variable and shared, by putting them in the same line as the annotated declaration, contrasted to custom annotations, which are put on a separate line above the declaration. This makes the formatted version of the golfed program unreadable, therefore I changed the alias-imports back for this version.)

    this, void, case, else are actual keywords and cannot be renamed this way, and Integer, String and Character appear just once each, so there is nothing to be gained by importing.

  • Originally I also had a separate ScreenBuffer class (which kept track of the linked list of line buffers, the current index, and so on), but as there was only ever one object of it, it was optimized away.

  • That Screenbuffer class also had up and down methods, which were called from the parser (and just did currentLine = currentLine.up respectively currentLine = currentLine.down). It showed that directly doing this in the parser's switch is shorter. It also allowed to write currentLine = currentLine.up.up (which later became c = c.u.u) instead of currentLine = currentLine.up;currentLine = currentLine.up.

  • Originally we did pass the current index as an argument into the append method (and even to the parser from the loop) – having it a variable in the containing function is shorter.

  • Originally my printAll method used the current pointer and moved it first up until the current line was empty, and then down while printing each line. This broke when using ▲ and ▼ to jump over lines, so we had to explicitly append something in those jumped lines instead. Keeping track of first/last line proved easier (though it made it necessary to use two print statements, because there is no do-while-loop in Ceylon).

  • Originally I had something like this:

      String? input = process.readLine();
      if(exists input) {
         for(x in input) {
             ...
         }
      }
    

    process.readLine returns null if there is no line which can be read (because the input has been closed), and the Ceylon compiler requires me to checkfor that before I access input. As in this case I want to do nothing, I can equivalently use the else operator which returns its first argument if not null, and otherwise its second argument, saving the variable and the if-statement. (This also would allow us to encode a default input for testing: for (x in process.readLine() else "^^▲^v▼▲^^v") {)

\$\endgroup\$
0
\$\begingroup\$

JavaScript (ES6), 228 bytes

E=(r,p=(' '[M='repeat'](Z=r.length)+',')[M](Z*4),i=Z*2,k=0)=>Z>k?E(r,(p.split(',').map((o,q)=>q==i?o.slice(0,k)+r[k]+o.slice(k++):o)).join`,`,i+(H={'^':-1,k:-2,j:2,v:1})[r[k]],k):p.split(',').join`
`.replace(/\s+\n$|^\s+\n/g,'')

Well, here is a (rather long) recursive solution that passes all of the test cases given. It was a nice challenge. This uses k and j in place of and .

Test Snippet

Although the submission itself can only handle k,j, the following snippet can handle both k,j and ▼,▲.

E=(r,p=(' '[M='repeat'](Z=r.length)+',')[M](Z*4),i=Z*2,k=0)=>Z>k?E(r,(p.split(',').map((o,q)=>q==i?o.slice(0,k)+r[k]+o.slice(k++):o)).join`,`,i+(H={'^':-1,k:-2,j:2,v:1})[r[k]],k):p.split(',').join`
`.replace(/\s+\n$|^\s+\n/g,'')
Input: <input type="text" oninput=o.textContent=E(this.value.replace(/▲/g,'k').replace(/▼/g,'j'))></input>
<pre id='o'></pre>

\$\endgroup\$

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.