Goodness, it's covered in tabs!

26

3

Space indentation users, unite! We must fight against all the lowly tab users!

Your mission (should you choose to accept it) is to write a program or function that takes in two arguments:

  • A string: This is the input.
  • A positive integer: This the number of spaces per tab.

You must go through every line of the string and replace every tab used for indentation with the given number of spaces, and every tab not used for indentation (e.g. in the middle of a line) with one space.

Note that lines such as \t \tabc are undefined behavior; they were inserted by the evil tab users to complicate your programs.

According to the Tabs Must Die Society, your program must be as short as possible to avoid detection by the evil tab users.

Example

\t is used to represent tabs here.

Input string:

a
\t\tb\tc
d

Input number:

4

Output:

a
        b c
d

The middle line was indented by 8 spaces, 4 per tab (since the given number was 4).

Input string:

\ta\t\tb

Input number:

4

Output:

    a  b

NOTE: This is not a duplicate of the tab expansion challenge; it requires a very different input format and slightly different requirements.

kirbyfan64sos

Posted 2015-09-09T21:16:46.507

Reputation: 8 730

The given output only has 4 spaces instead of 8. Typo? – Conor O'Brien – 2015-09-09T21:21:24.980

@CONORO'BRIEN Yep. Fixed. – kirbyfan64sos – 2015-09-09T21:22:49.450

Can input number be unary for retina ? – TheNumberOne – 2015-09-09T21:26:59.097

1Yes, as long as the question doesn't explicitly ask for decimal numbers (which it doesn't). – Martin Ender – 2015-09-09T21:34:18.430

As said a long time ago on some deleted comments, on one of my questions, different input doesn't make enough difference. The answers on the other question can be slightly modified to work on this one. I'm sorry, but I'm voting to close this one as duplicate. Like or hate it. – Ismael Miguel – 2015-09-09T22:43:25.743

2

possible duplicate of Expand tabs (implement expand(1))

– Ismael Miguel – 2015-09-09T22:43:44.273

Suggested alternate title: My God, it's full of tabs! – Nate Eldredge – 2015-09-10T01:53:26.200

1Can we assume that the input contains only printable ASCII, tabs and newlines? – Dennis – 2015-09-10T02:27:17.577

1@Dennis Sure!!! – kirbyfan64sos – 2015-09-10T02:27:36.723

2Proposed test case: \ta\t\tb, 4 (my previous revision was failing that one) – Dennis – 2015-09-10T04:52:48.940

Can a line only consists of tabs? Or is this an invalid input? – Jakube – 2015-09-10T09:34:07.683

Does the input string contain actual \t (two characters), or the ASCII code 9 (horizontal tab), or at our convenience? – Luis Mendo – 2015-09-10T11:25:32.707

1@IsmaelMiguel This is very different from that challenge. The linked challenge involves calculating tabstops; this is simply "tab = 4 spaces." – Doorknob – 2015-09-10T11:36:26.733

@Doorknob Did the rules changed? – Ismael Miguel – 2015-09-10T12:04:29.497

1@LuisMendo The literal ASCII code 9. – kirbyfan64sos – 2015-09-10T13:55:38.383

@Jakube That's valid input. – kirbyfan64sos – 2015-09-10T13:56:07.303

How come in the first test case, the output of the second line is (8 spaces)b c and not (8 spaces)b c (since "b" and "c" had a tab in between them?) – ASCIIThenANSI – 2015-09-10T14:00:33.233

@ASCIIThenANSI If you mean why there weren't 4 spaces in between b and c, that's because the rules say tabs that aren't indentation are always replaced by 1 space. – kirbyfan64sos – 2015-09-10T14:02:22.077

1@kirbyfan64sos Such a strange rule, since that's not how tabs work. They should be replaced by a number of spaces such that the first character after the tabs lands on a multiple of the input number. – mbomb007 – 2015-09-10T15:57:28.830

@mbomb007 But then this challenge would be too similar to the other tabstop one. Besides, I'm thinking of the scenarios like the way Plan 9 uses tabs (#ifdef\tX instead of #ifdef X). – kirbyfan64sos – 2015-09-10T16:18:11.240

@kirbyfan64sos What should the result be in this example: \t \tHello? Should the second tab be replaced with a single space? – mbomb007 – 2015-09-10T17:37:44.967

@mbomb007 I would say no, since that's technically indentation, but then I'd probably invalidate half the answers here, so I'll count is as "undefined behavior". :) – kirbyfan64sos – 2015-09-10T17:45:26.057

1-1 because tab indentation is better :P (no downvote was actually made) – HyperNeutrino – 2015-09-11T02:12:04.600

@JamesSmith The words -1 because are all that appeared in my Stack Exchange Android notifications, so I had momentarily freaked out. :) – kirbyfan64sos – 2015-09-11T02:44:28.853

2We need an answer in Whitespace. – Kaz Wolfe – 2015-09-11T03:09:35.693

1This is truly evil. – user – 2015-09-14T14:20:14.080

2 space indent master race – Stan Strum – 2018-06-12T18:31:37.247

Answers

7

CJam, 30 24 23 bytes

q{_9=NA=Seasi*' ?@?:N}/

I usually refuse to post malicious code on the internet…

This is a full program that reads the string from STDIN and the number as a command-line argument.

Try it online in the CJam interpreter.

How it works

q                        Read all input from STDIN.
 {                   }/  For each character C in the input:
  _9=                      Push 1 if C is a tab and 0 otherwise.
     NA=                   See below.
        Seasi*             Push a string of W spaces, where W is the integer from
                           the command-line arguments.
              '            Push a spaces character.
                ?          Select the string if NA= pushed a truthy value, the
                           single space otherwise.
                 @         Rotate C on top of the stack.
                  ?        Select the string of spaces or the single space if _9=
                           pushed 1, the character C otherwise.
                   :N      Save the result in N.

What NA= does:

  • For the first character, N will contain its default value, i.e., the string "\n".

    For all subsequent characters, N will contain the result of the last iteration, i.e., the last character from input, a space character or a string of one or more spaces.

  • If N is a string, NA= selects the element at index 10 of N (wrapping around). The result will be a space or a linefeed character. Both are truthy.

    If N is a character, NA= pushes 1 for a linefeed and 0 otherwise.

  • Because of the above, NA= will push a truthy value for the first character, a character preceded by a linefeed or a character preceded by a string of spaces (indentation that has already been replaced).

    In all other cases (including a tabulator that has been replace by a space character), NA= will push a falsy value.

Dennis

Posted 2015-09-09T21:16:46.507

Reputation: 196 637

6Good thing you're doing the Internet a service by removing malicious tabs. ;) – Alex A. – 2015-09-10T04:03:57.670

19

K5, 53 45 bytes

{{,/(b+a&~b:x*&\a:9=y){$[x;x#" ";y]}'y}[x]'y}

In action:

  {{,/(b+a&~b:x*&\a:9=y){$[x;x#" ";y]}'y}[x]'y}[4;(,"a";"\t\tb\tc";,"d")]
(,"a"
 "        b c"
 ,"d")

I just want the record to show that this question is morally reprehensible.

JohnE

Posted 2015-09-09T21:16:46.507

Reputation: 4 632

11-21346106841 for ... this question is morally reprehensible. – TheNumberOne – 2015-09-09T21:44:22.427

3This needs to end up the top voted answer, simply so people will see the footnote. – Geobits – 2015-09-09T21:47:25.120

You can return the result instead of printing it for 3 bytes. – kirbyfan64sos – 2015-09-09T22:06:36.733

1@kirbyfan64sos: I'm printing the result to avoid needing to join the lines. If I can accept and return the result as a list of strings, one for each line, I could save \0:and"\n"\`. – JohnE – 2015-09-09T22:08:12.743

@JohnE I didn't put a rule saying you can't, so I'm guessing you can. :) – kirbyfan64sos – 2015-09-09T22:09:18.647

oK, then- the alternative IO format saves 8 bytes. – JohnE – 2015-09-09T22:17:41.723

8

Perl, 23 bytes

22 bytes code + 1 bytes command line

Hopefully not too cheeky to assume the numeric input can be given via the -i parameter! Ensure to replace \t in the below code with the actual tab character.

s/\G\t/$"x$^I/ge;y/\t/ /

Usage example:

printf "a\n\t\tb\tc\nd" | perl -p entry.pl -i4

Or for convenience:

printf "a\n\t\tb\tc\nd" | perl -pe 's/\G\t/$"x$^I/ge;y/\t/ /' -i4

Explanation:

Using the -p argument will execute the program for every line in the input, then print the result at the end.

In the above example, the regex substitution replaces \G\t with " "x4 (a space repeated four times). \G is a little-known regex construct which matches either the position of first match if the first iteration, or from the position of the previous match if not the first iteration, meaning it will only replace all tabs at the start of the string, and will do so one-by-one. The y/\t/ / simply replaces all remaining tabs with spaces.

Jarmex

Posted 2015-09-09T21:16:46.507

Reputation: 2 045

2

Julia, 69 59 bytes

f(s,n)=(r=replace)(r(s,r"^\t*"m,i->" "^endof(i)n),"\t"," ")

Ungolfed:

function f(s::String, n::Int)
    # Replace the leading indentation tabs
    r1 = replace(s, r"^\t*"m, i -> " "^endof(i)n)

    # Replace any remaining tabs between words
    r2 = replace(r1, r"\t", " ")

    # Return
    r2
end

Saved 10 bytes and fixed an issue thanks to Glen O!

Alex A.

Posted 2015-09-09T21:16:46.507

Reputation: 23 761

Is there any benefit to replacing the leading indentation tabs separately? It seems to me that it should be handled directly by the "remaining tabs" part. Also, your "replace the tabs between text" part will only match a single tab, what if you have "hello\t\t1"? – Glen O – 2015-09-10T03:25:33.430

If we assume all indentation is done with tabs (no "\t \t" situations), you could do this: f(s,n)=(r=replace)(r(s,r"^\t*"m,i->" "^endof(i)n),"\t"," "), which uses a replacement function and will catch all of the indentation tabs in one hit. – Glen O – 2015-09-10T03:50:40.993

@GlenO Wow, that's really genius. Thanks so much! – Alex A. – 2015-09-10T03:55:20.297

I noticed my answer got downvoted. Is there anything I've done wrong? I'd be happy to address any issues. – Alex A. – 2015-09-10T17:47:14.230

I don't see any issue. Maybe it's just one of those vindictive types that downvote because they don't like a language, or things like that? I can't see any flaws. – Glen O – 2015-09-11T02:30:15.827

2

Mathematica, 42 37 bytes

Thanks to @LegionMammal978 for multiple code-saving suggestions. The first parameter, # is for the input text, the second parameter, #2, for the number of spaces per tab.

StringReplace[#,"\t"->" "~Table~{#2}]&

DavidC

Posted 2015-09-09T21:16:46.507

Reputation: 24 524

Also, you can change Table[" ",{#2}] to " "~Table~{#2} to save a byte. Why are you StringJoining the empty string onto it? – LegionMammal978 – 2015-09-11T10:50:16.333

2

Haskell, 82 bytes

n!('\t':x)=([1..n]>>" ")++n!x
n!x=f<$>x
f '\t'=' '
f x=x
g n=unlines.map(n!).lines

Then g 3 "a\n\t\tb\tc\nd" does the thing.

Lynn

Posted 2015-09-09T21:16:46.507

Reputation: 55 648

Some late competition – Laikoni – 2018-06-12T13:38:45.933

1

Stax, 20 bytes

ÜR╧█╧╫≡eX,)~σOÜ¢G╩N‼

Run and debug it

This program reads the first line as the indent width, and the rest of the input as the program.

recursive

Posted 2015-09-09T21:16:46.507

Reputation: 8 616

1

Japt v2.0a0, 17 bytes

r/^\t+/m_çVçÃr\tS

Try it

Shaggy

Posted 2015-09-09T21:16:46.507

Reputation: 24 623

1

Ruby 49 bytes

def r s,t;s.gsub! /^\t/,' '*t;s.gsub!"\t",' ';end

MegaTom

Posted 2015-09-09T21:16:46.507

Reputation: 3 787

2This doesn't work if there are two tabs at the beginning of a line. – Not that Charles – 2015-09-10T02:24:17.083

1

JavaScript (ES6), 70

Using template strings, the newline is significant and counted

(s,n,r=n)=>[...s].map(c=>c<`
`?` `.repeat(r):(r=c<` `?n:1,c)).join``

Test running the snippet below in Firefox.

F=(s,n,r=n)=>[...s].map(c=>c<`
`?` `.repeat(r):(r=c<` `?n:1,c)).join``

// TEST
out=x=>O.innerHTML+=x+'\n\n'

out('Input: "A\\n\\t\\tB\\tC\\nD" 4\nOutput:\n'+F('A\n\t\tB\tC\nD',4))

out('Input: "\\tA\\t\\tB" 4\nOutput:\n'+F('\tA\t\tB', 4))
<pre id=O></pre>

edc65

Posted 2015-09-09T21:16:46.507

Reputation: 31 086

1Wow one downvote! Could be someone who can't read or understand 'Test in Firefox' ? – edc65 – 2015-09-10T17:51:47.613

I suspect language bias. Julia and CJam got downvotes as well. – Dennis – 2015-09-11T20:09:25.077

1

CoffeeScript, 72 bytes

(s,n)->s.replace(/^\t+/mg,(m)->" ".repeat(m.length*n)).replace /\t/g," "

(Trying to golf it at least 2 more bites, so it will beat the ES6 solution... Help appreciated :D)

Usage:

f=(s,n)->s.replace(/^\t+/mg,(m)->" ".repeat(m.length*n)).replace /\t/g," "
str="""
My nice\tString
\tIndent <--
\t\tDouble
""" #\t is recognized as tab by CS
alert(f(str,4))

Bojidar Marinov

Posted 2015-09-09T21:16:46.507

Reputation: 209

1

Retina, 42 bytes

All occurrences of . are spaces, all \t are literal tabs (1 byte), and <empty> represents a blank file. It's just for readability. I'm also not entirely sure that I'm doing the loop correctly, but I think so. Each line should be placed in its own file. I've added 1 byte for each additional file.

Input is assumed to be in Unary on its own line at the end of the input.

(1*)$
_$1
m+`(?<!^|\t)\t
.
(`1$
<empty>
)`\t
\t.
\t|_
<empty>

Explanation

I add a _ before the Unary input to delimit it during replacement, so that I don't remove any trailing ones from the input string. Then, I replace all tabs not at the beginning of a line with a single space. Then, I loop, removing a single 1 and adding a single space after each tab, until I run out of input. Finally, I clean up by removing the tabs and underscore.

mbomb007

Posted 2015-09-09T21:16:46.507

Reputation: 21 944

1

Python, 72 68 bytes

Tabs are literal tabs (1 byte), so r'...' is not needed. Unfortunately, Python requires "fixed-width" look-behinds / look-aheads, so I can't use (?<!^|\t). Uses pretty much the same method as my Retina solution.

import re
lambda s,n:re.sub('\t',' '*n,re.sub('(?<!^)(?<!\t)\t',' ',s))

mbomb007

Posted 2015-09-09T21:16:46.507

Reputation: 21 944

0

Haskell, 75 bytes

s#m|let('\t':r)#n=(' '<$[1..n])++r#n;(x:r)#n=x:r#(m^sum[1|x<' ']);e#_=e=s#m

Try it online! This assumes the input contains only printable chars as well as tabs and newlines, as allowed by OP in the comments.

Explanation:

The outer # function takes a string s and a number m and calls the inner locally defined # function with the same arguments. This is done to keep track of the original m value, as the inner # function changes the number:

  • ('\t':r)#n=(' '<$[1..n])++r#n If you encounter a tab, replace it by n spaces and leave n unchanged.
  • (x:r)#n=x:r#(m^sum[1|x<' ']) If some x which is not a tab is encountered, keep it as is but set n to the original number m if x is a newline and to 1 otherwise. This is done by m^sum[1|x<' ']: m is taken to the power of sum[1|x<' '] which evaluates to 1 when x is smaller than a space (i.e. a newline), so we get m^1 = m. Otherwise it's 0 and we have m^0 = 1.

Laikoni

Posted 2015-09-09T21:16:46.507

Reputation: 23 676

0

Java 11, 134 bytes

n->s->{var r="";for(var p:s.split("\n")){for(;p.charAt(0)==9;p=p.substring(1))r+=" ".repeat(n);r+=p+"\n";}return r.replace('\t',' ');}

Try it online.
NOTE: Java 11 isn't on TIO yet, so " ".repeat(n) has been emulated as repeat(" ",n) instead (with the same byte-count).

Explanation:

n->s->{                 // Method with integer & String parameters and String return-type
  var r="";             //  Result-String, starting empty
  for(var p:s.split("\n")){
                        //  Loop over the rows (split by new-lines)
    for(;p.charAt(0)==9;//   Inner loop as long as the current row starts with a tab
       p=p.substring(1))//     After every iteration: remove the first character (tab)
      r+=" ".repeat(n); //    Append `n` amount of spaces to the result-String
    r+=p+"\n";}         //   Append the rest of the row with a newline to the result
  return r.replace('\t',' ');} 
                        //   Replace any remaining tabs with a space, and return the result

Kevin Cruijssen

Posted 2015-09-09T21:16:46.507

Reputation: 67 575