Break tabs in half

31

3

Holy wars have been fought over spaces vs. tabs. (And of course spaces, being objectively superior, won.) —Alex A.

Some people still refuse to accept that which is clearly supreme. You've just received a file using the incorrect, bad, and inferior form of whitespace, and now the contents of the file are tainted and ruined.

You decide you might as well show the person who sent the file to you just how wrong they are—violently.

Description

As the title suggests, your challenge is to take a file that contains one or more tabs:

this is an evil tab    onoes

and ruthlessly shatter them into pieces:

this is an evil tab

                     o
                     n
                     o
                     e
                     s

Note that the Stack Exchange software turns literal tabs into four spaces (because it's right), so tabs within this post will be displayed as four spaces. The input to your program, however, will contain actual tabs.

Challenge

The solution should take a single string as input, which may contain printable ASCII, newlines, and tabs. There will always be at least a single tab in the input.

The output should be the same string, with the following rules applied:

  • Start the cursor at coordinates (0,0) and with a direction of right. The coordinates are (column,row), zero-indexed, and the direction is which way you should move the cursor after printing a character.

  • For each character in the string:

    • If it's a newline, move to coordinates (0,n), where n is the number of newlines in the string so far (including this one), and reset the direction to right.

    • If it's a tab, output two spaces, rotate the cursor direction 90 degrees clockwise, and output two more spaces, effectively "breaking" the tab in half. Here's a visual example, where a tab is represented as ---> and spaces as ·:

      foo--->bar--->baz
      

      becomes

      foo···
           ·
           b
           a
           r
           ·
           ·
       zab··
      
    • Otherwise, simply output the character at the cursor and move the cursor one step in the current direction.

Since you are reading the string from start to end, it is possible that you will have to write "on top" of existing characters—this is okay. For example, the input

foo--->bar


spaces are superior

should result in an output of

foo

     b
spaces are superior
     r

You may choose whether "broken tabs" should overwrite other characters—the original intention was that they do not, but the spec was ambiguous, so this is your decision.

Furthermore, after applying these rules, you may also

  • add or remove as many trailing spaces as you would like.

  • add a maximum of a single trailing newline.

The input will never contain trailing spaces; it will also never contain leading or trailing newlines. You may also always assume that you will never need to write to a column or a row less than 0 (i.e. off the screen).

Test case

Tabs in this test case are represented as ---> because otherwise SE gobbles them up.

Input:

Test case. Here's a tab--->there's a tab--->everywhere a tab--->tab--->this is some more text
blah
blah
blah blah blah blah blah blah--->blaah--->blaah--->blah--->blaaaaah--->blah--->blah--->blah--->blah--->blah

Output:

Test case. Here's a tab
blah
blah                     t
blah blah blah blah blah blah
                        blaablah
                         r     b
                         e     l  b
                      h  'h    a  l
                      a  sa    a  a
                      l   l    h  h
       this is some mobe tbxt

                         haalhalb
     b                   a
     a                   b
     t

        bat a erehwyreve

Fancy animation:

Rules

  • This is , so the shortest code in bytes will win!

Doorknob

Posted 2015-09-20T20:48:43.297

Reputation: 68 138

When you say the cursor has to start at (0,0) do you mean we need to clear the console first, or do you just mean the default position of the cursor by that? – Martin Ender – 2015-09-21T06:26:58.097

18I'm voting to close this question as off-topic because it is full of hatred and blasphemy. – aditsu quit because SE is EVIL – 2015-09-21T06:53:06.073

1Your animation looks so much like the ><> interpreter that I now want to see a self-modifying ><> entry. – Sanchises – 2015-09-21T08:48:30.463

1I liked the hidden message in the opening paragraph but I have to disagree. – wf4 – 2015-09-21T14:41:47.307

@MartinBüttner That just means the default position. – Doorknob – 2015-09-21T16:35:30.097

What are the requirements if the cursor is going backwards or upwards and the bit of text is longer than there is space for before the first row or column is reached. For example the string this<tab>is a<tab>problematic<tab>string!! would not only go into negative columns, but also negative rows. – Tom Carpenter – 2015-09-21T21:25:07.193

@TomCarpenter I already addressed that at the end of the Challenge section: "You may also always assume that you will never need to write to a column or a row less than 0 (i.e. off the screen)." – Doorknob – 2015-09-21T21:26:16.227

@Doorknob I missed that bit. ;) – Tom Carpenter – 2015-09-21T21:26:35.390

Answers

8

MATLAB, 144 bytes

The weapon of choice for dealing with strings is of course a language designed for manipulating numbers [citation needed]. Kidding aside, the great thing about Matlab is that it doesn't care if you assign to an array 'out of bounds': it will simply make a larger matrix. Furthermore, the default matrix element, 0, is rendered as a space instead of a null character the ASCII spec prescribes.

Tabs are simply a jump in coordinates, so no spaces are outputted for a tab.

function o=q(t)
u=2;v=0;x=1;y=1;n=1;for s=t
if s==9 x=x+u-v;y=y+v+u;a=v;v=u;u=-a;elseif s<11
n=n+1;x=1;y=n;else
o(y,x)=s;x=x+u/2;y=y+v/2;end
end

I started out with 209 bytes, but some more careful golfing got rid of most of that; there's a lot of repetition in this code, so I did some trial-and-error which alternatives worked best. I don't think there's much space for more optimization with this code, but I'm always happy to be proven wrong. Edit: Tom Carpenter managed to prove me wrong; he managed to save 9 bytes which I optimized to save a whopping 29 bytes. Last byte saved by assuming there are no control characters (ASCII < 9) in the input - MATLAB strings aren't null-terminated.

Sanchises

Posted 2015-09-20T20:48:43.297

Reputation: 8 530

Doesn't appear to work. I tried this q('hello<tab>my name<tab>is tom<tab>c'), but it something along the lines of Attempted to access o(11,-2); on line 7. Though this may be more to do with an issue in the question - if the cursor is heading backwards and goes beyond the first column what happens to the rest of the line. – Tom Carpenter – 2015-09-21T21:20:26.733

Yep, my bad I missed that bit. I'll go away now ;) – Tom Carpenter – 2015-09-21T21:27:43.020

1You can save another 9 characters by removing the d variable and instead having 4 variables which for a loop to make the [1 0 -1 0] pattern as such: function o=q(t) u=1;v=0;w=-1;z=0;x=0;y=1;n=1;for s=t if s==9 x=x+2*u-2*v;y=y+2*v+2*u;a=z;z=w;w=v;v=u;u=a;elseif s==10 n=n+1;x=0;y=n;else x=x+u;y=y+v;o(y,x)=s;end end (obviously being in the comments it removed all the lines, so you'll have to reformat it like yours to see what I did) – Tom Carpenter – 2015-09-21T22:08:06.493

@TomCarpenter That's... really ugly. I love it. – Sanchises – 2015-09-22T07:42:46.303

5

Python 3, 272 270 266 262 255 253 244 bytes

I=[]
try:
 while 1:I+=[input()]
except:r=m=0
M=sum(map(len,I))
O=[M*[' ']for _ in[0]*M]
for l in I:
 x=b=0;y=r;a=1;r+=1
 for c in l:
  if'\t'==c:a,b=-b,a;x+=a+2*b;y+=b-2*a
  else:O[y][x]=c
  x+=a;y+=b;m=max(m,y)
for l in O[:m+1]:print(*l,sep='')

The \t should be an actual tab character.

The code works somewhat like Zach Gates' answer, first generating a M by M grid where M is the sum of the lengths of the lines. (That's a huge amount of excess, but makes the code shorter.) It then loops through the characters, placing them in the correct spots, keeping track of the bottommost visited row. Finally, it prints all the lines up to that row.

The output contains (usually a huge amount of) trailing spaces and 1 trailing newline.

PurkkaKoodari

Posted 2015-09-20T20:48:43.297

Reputation: 16 699

3

Javascript (ES6), 264 245 bytes

Tried the "create a giant grid of spaces, fill and trim" approach, which ended up 19 bytes shorter than the other.

a=t=>(o=`${' '.repeat(l=t.length)}
`.repeat(l).split`
`.map(q=>q.split``),d=x=y=z=0,s=c=>o[d>2?y--:d==1?y++:y][d?d==2?x--:x:x++]=c,[...t].map(c=>c=='\t'?(s` `,s` `,++d,d%=4,s` `,s` `):c==`
`?(x=d=0,y=++z):s(c)),o.map(p=>p.join``).join`
`.trim())

By modifying the second-to-last line like so, you can remove the large amount of trailing spaces on every line:

...o.map(p=>p.join``.trimRight())...

Try it here:

a=t=>(o=`${' '.repeat(l=t.length)}
`.repeat(l).split`
`.map(q=>q.split``),d=x=y=z=0,s=c=>o[d>2?y--:d==1?y++:y][d?d==2?x--:x:x++]=c,[...t].map(c=>c=='\t'?(s` `,s` `,++d,d%=4,s` `,s` `):c==`
`?(x=d=0,y=++z):s(c)),o.map(p=>p.join``.trimRight()).join`
`.trim())

function submit(){ O.innerHTML = a(P.value.replace(/\\t|-+>/g,'\t')); }
<p>Input: (Use <code>\t</code> or <code>---></code> for a tab.)</p>
<textarea id=P rows="10" cols="40">this is an evil tab\tonoes</textarea>
<br><button onclick="submit()">Try it!</button>
<p>Output: (Looks best in full-page mode.)</p>
<pre id=O>

Explanation coming soon; suggestions welcome!

ETHproductions

Posted 2015-09-20T20:48:43.297

Reputation: 47 880

3

JavaScript (ES6), 180 183

Using template strings, there are some newlines that are significant and counted.

That's a function returning the requested output (padded with tons of trailing spaces)

There is little to explain: the rows are builded as neeeded. There is not a direction variable, just the 2 offset for x and y, as in clockwise rotation they are easily managed: dx <= -dy, dy <= dx

Test running the snippet below in Firefox

f=s=>[...s].map(c=>c<`
`?(x+=2*(d-e),y+=2*(e+d),[d,e]=[-e,d]):c<' '?(y=++r,e=x=0,d=1):(t=[...(o[y]||'')+' '.repeat(x)],t[x]=c,o[y]=t.join``,x+=d,y+=e),o=[r=x=y=e=0],d=1)&&o.join`
`

// TEST  

// Avoid evil tabs even in this source 
O.innerHTML = f(`Test case. Here's a tab--->there's a tab--->everywhere a tab--->tab--->this is some more text
blah
blah
blah blah blah blah blah blah--->blaah--->blaah--->blah--->blaaaaah--->blah--->blah--->blah--->blah--->blah`
 .replace(/--->/g,'\t'))
<pre id=O></pre>

edc65

Posted 2015-09-20T20:48:43.297

Reputation: 31 086

I wish all languages had [x,y]=[expr1,expr2]... – Sanchises – 2015-09-23T18:38:57.087

1

Python 2, 370 369 368 bytes

Thanks to @sanchises and @edc65 for saving me a byte each.

J=''.join
u=raw_input().replace('\t','  \t  ')
w=u[:]
G=range(len(u))
d,r=0,[[' 'for _ in G]for _ in G]
u=u.split('\n')
for t in G:
 x,y=0,0+t
 for c in u[t]:
  if c=='\t':d=(d+1)%4
  if c!='\t':
   if c.strip():r[y][x]=c
   if d<1:x+=1
   if d==1:y+=1
   if d==2:x-=1
   if d>2:y-=1
r=r[:max(i for i,n in enumerate(r)if J(n).strip())+1]
for i in r:print J(i).rstrip()

It generates the largest grid possible and then loops around, character by character, switching direction at each tab.

Zach Gates

Posted 2015-09-20T20:48:43.297

Reputation: 6 152

Hint: if !d and if d>2 – Sanchises – 2015-09-21T21:39:57.817

!d is not valid syntax. @sanchises Thanks for the d>2 tip, though. – Zach Gates – 2015-09-21T21:42:27.770

Sorry, I don't actually know python :) Just sort of assumed it would work like that. – Sanchises – 2015-09-21T21:46:59.693

I too don't understand python, but if d in 0...3, d==0 -> d<1 – edc65 – 2015-09-21T22:06:47.923

Yep, you're right. Thanks for the byte. @edc65 – Zach Gates – 2015-09-21T22:10:40.603