10
\$\begingroup\$

Your task this time is to implement a variant of the POSIX expand(1) utility which expands tabs to spaces.

Your program is to take a tabstop specification and then read input on standard in and replace tab characters in the input with the appropriate amount of spaces to reach the next tabstop. The result should be written to standard out.

Tabstop specification

A tabstop specification consists of either a single number, or a comma-separated list of tabstops. In the case of a single number, it is repeated as if multiples of it occurred in a comma-separated list (i.e. 4 acts as 4,8,12,16,20,...). Each entry in a comma-separated list is a positive integer optionally prefixed by a +. A + prefix indicates a relative difference to the previous value in the comma-separated list. The first value in the list must be absolute (i.e. unprefixed). The tabstops specify the column of the next non-space character (following the expanded tab), with the leftmost column taken as number 0. Tabs should always expand to at least one space.

Input/output

The tabstop specification is either to be taken as the first command-line parameter to the program, or read from standard in as the first line of input (terminated by a newline), at your discretion. After the tabstop has been read, the remaining input (all input, in the former case) until EOF is to be processed and expanded. The expanded output shall be written to standard out.

All expanded tabstops, and all input, is assumed to be a maximum of 80 columns wide. All expanded tabstops are strictly increasing.


Example

Tabstop specification 4,6,+2,+8 is equivalent to 4,6,8,16, and with both the input

ab<Tab>c
<Tab><Tab>d<Tab>e<Tab>f

is expanded into ( indicates a space)

ab␣␣c
␣␣␣␣␣␣d␣e␣␣␣␣␣␣␣f

01234567890123456   (Ruler for the above, not part of the output)
          1111111

Scoring is pure ; shortest code wins.

\$\endgroup\$

4 Answers 4

2
\$\begingroup\$

GolfScript (77 75 chars)

n/(','/{'+'/{~t++}*~:t}%81,{t*}%+:T;{[0\{.9={;T{1$>}?(.)@-' '*}*\)}/;]n+}/;

I'm quite pleased with the tabspec parsing.

# Split on commas
','/
# For each element:
{
    # Split on '+'
    '+'/
    # We now have either ["val"] or ["" "val"]
    # The clever bit: fold
    # Folding a block over a one-element array gives that element, so ["val"] => "val"
    # Folding a block over a two-element array puts both elements on the stack and executes,
    # so ["" "val"]{~t++}* evaluates as
    #     "" "val" ~t++
    # which evaluates val, adds the previous value, and concatenates with that empty string
    {~t++}*
    # Either way we now have a string containing one value. Eval it and assign to t
    ~:t
}%

Then I add multiples of the last element until I'm guaranteed to have enough to reach the end of the 80 columns:

81,{t*}%+

This gives the desired behaviour when only one tabstop was specified, and is otherwise only relevant in cases which the spec doesn't mention. (NB it makes the list of tab-stops dip back to 0 and then repeat the last parsed element, but that's irrelevant because when it comes to using the list I look for the first element greater than the current position).

The rest is pretty straightforward.

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

C, 228 chars

Here is a C solution to start things off. There's still a lot of golfing to do here (look at all those ifs and fors and putchars...). Tested with the example testcase, as well as with the same input but 4 and 8 for the tab spec.

S[99],i;L,C;main(v){for(v=1;v;)v=scanf("+%d",&C),v=v>0?C+=L:scanf("%d",&C),
v&&(S[L=C]=++i,getchar());for(;i==1&&C<80;)S[C+=L]=1;for(C=L=0;C=~getchar();)
if(C+10)putchar(~C),L+=C+11?1:-L;else for(putchar(32);!S[++L];)putchar(32);}
\$\endgroup\$
2
\$\begingroup\$

Ruby 161 145

Reads the tabstop specification on the first line of input.

i=t=[]
gets.scan(/(\+)?(\d+)/){t<<i=$2.to_i+($1?i:0)}
81.times{|j|t<<j*i}
while gets
$_.sub!$&," "*(t.find{|s|s>i=$`.size}-i)while~/\t/
print
end

edit: Added two lines that makes the last read tabstop repeat so that tabstop specifications of a single number also works correctly

i is a temporary variable for holding the last parsed tabstop. t is the list of tabstobs, parsed from the gets.scan line. For good measure we add 81 multiples of the last parsed tabstop. the while gets loop keeps going until there is no more input. For each line of input we substitute tabs for spaces, one tab at the time because the string moves as we add the spaces and we must recalculate the correct tabstop.

\$\endgroup\$
7
  • \$\begingroup\$ I don’t really know Ruby, but can you write x+($1?i:0) as the shorter $1?x+i:x? \$\endgroup\$
    – Timwi
    Commented Jan 25, 2014 at 0:42
  • \$\begingroup\$ @Timwi Nope! Ruby is a little strange with the ternary operator. Usually you need to put a space in there somewhere, because the colon (:) could also mark the beginning of a symbol, but since a symbol can't start with a digit, :0 is OK without space. Or something. It's weird. The parentheses are crucial also it seems. \$\endgroup\$
    – daniero
    Commented Jan 25, 2014 at 3:44
  • \$\begingroup\$ That tabstop scanning looks buggy to me. In t<<x+($1?i:0);i=x the first statement doesn't change x, does it? I think you need to reverse it as i=x+($1?i:0);t<<i \$\endgroup\$ Commented Jan 25, 2014 at 10:12
  • 1
    \$\begingroup\$ In fact you can save 16 by replacing the first two lines with i=t=[] (since i is guaranteed not to be needed the first time around); simplifying the tab-stop parse to {t<<i=$2.to_i+($1?i:0)}, and eliminating l entirely (i already holds that value). But nice one on not caring about the tab stop being strictly increasing: that saves you 4 chars, and I can borrow it to save 2. \$\endgroup\$ Commented Jan 25, 2014 at 14:06
  • \$\begingroup\$ @PeterTaylor Thanks for the input! It wasn't directly buggy, but certainly a little bloated. I find it too easy to stare oneself blind on code like this. \$\endgroup\$
    – daniero
    Commented Jan 25, 2014 at 18:20
0
\$\begingroup\$

Pascal, 441 B

  • The Pascal programming language is standardized by ISO standards 7185 (“Standard Pascal”) and 10206 (“Extended Pascal”). Since the notion of command-line arguments something highly specific, not universally found on all computing systems, Pascal lacks of a standardized facility to access command-line arguments (if such exist). Therefore the following complete program expects the tabulator specification given on the first line of input.
  • The tabulator stop specification reading algorithm uses the built‑in read procedure. Such will allow and ignore leading blanks. Thus 8, 12, 14, 15 is accepted, too. However, relative tabulator stop specifications are only processed correctly if the plus sign is immediately adjacent to the comma.
  • With respect to (POSIX™ locale) blank characters, the Pascal programming language only guarantees there is a space character. The provision of a tabulator character is an implementation-defined characteristic. Obviously the following program requires that there is a horizontal tabulator character in the utilized implementation.
  • The golfed version omits certain verification steps (non-empty input, first tabulator specification must be non-relative). It is unclear whether such vigorous enforcement is required by the task specification.
program e(input,output);var t:set of 1..80;c,p:1..80;begin
read(p);t:=[p];while','=input^do begin
get(input);if'+'=input^then begin read(c);p:=p+c end else
read(p);t:=t+[p]end;readLn;if[]=t-[p]then begin for c:=1
to 80 do if c mod p=0 then t:=t+[c]end;p:=1;while not EOF
do begin if EOLn then begin writeLn;p:=1 end else begin
if' '=input^then repeat p:=p+1;write(' ')until p-1 in t
else begin write(input^);p:=p+1 end end;get(input)end end.

Ungolfed:

program expand(input, output);
    
    const
        { The character count of the longest line this program can process. }
        lineIndexMaximum = 80;
    
    type
        { A valid character index in an `input` line. }
        lineIndex = 1‥lineIndexMaximum;
        { A set of valid character indices. }
        lineIndices = set of lineIndex;
    
    var
        { This mimics a mechanical typewriter implementation
          that has retractable pins installed catching the carriage. }
        tabulatorStop: lineIndices;
        { Where are we at in the currently processed line. }
        position: lineIndex;
    
    { Processes an input line containing a tabulator stop specification. ┈┈┈ }
    procedure processTabulatorStopSpecification;
        var
            { In Pascal `for`-loop counter variables must be declared
              in the _closest_ containing block. }
            column: lineIndex;
        begin
            { A negative number will be caught by the `lineIndex` data type. }
            read(input, position);
            tabulatorStop ≔ [position];
            
            while input↑ = ',' do
            begin
                { Skip comma. }
                get(input);
                
                { “A `+` prefix indicates a relative difference
                   to the previous value in the comma-separated list.” }
                if input↑ = '+' then
                begin
                    read(input, column);
                    position ≔ position + column;
                end
                else
                begin
                    read(input, position);
                end;
                
                tabulatorStop ≔ tabulatorStop + [position];
            end;
            
            { Discard the rest of the line, especially the end-of-line mark. }
            readLn(input);
            
            { “In the case of a single number, it is repeated as if
               multiples of it occurred in a comma-separated list.” }
            if tabulatorStop − [position] = [] then
            begin
                for column ≔ 1 to lineIndexMaximum do
                begin
                    if column mod position = 0 then
                    begin
                        tabulatorStop ≔ tabulatorStop + [column];
                    end;
                end;
            end;
        end;
    
    { Expands `input` lines according to `tabulatorStop`. ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ }
    procedure expandTabulatorCharacters;
        begin
            position ≔ 1;
            
            while not EOF(input) do
            begin
                if EOLn(input) then
                begin
                    { `Input` is a variable possessing the data type `text`.
                      A `text` file automatically replaces
                      the character (sequence) that indicates the end-of-line
                      with a single space character (`' '`).
                      The only /universally/ valid way to
                      emit an end-of-line character (sequence) is `writeLn`. }
                    writeLn(output);
                    { Reset `position` to the beginning of a new line. }
                    position ≔ 1;
                end
                else
                begin
                    { Note, as this might not be properly displayed,
                      this checks for a tabulator character. }
                    if input↑ = '   ' then
                    begin
                        { “Tabs should always expand to at least one space.”
                          Ergo elect a `repeat … until` loop. }
                        repeat
                        begin
                            write(output, ' ');
                            { Cannot fail: “All expanded tabstops […] is
                              assumed to be a maximum of 80 columns wide.” }
                            position ≔ position + 1;
                        end
                        until position − 1 in tabulatorStop;
                    end
                    else
                    begin
                        write(output, input↑);
                        { Note, fatal error if exceding `lineIndexMaximum`. }
                        position ≔ position + 1;
                    end;
                end;
                { Advance the reading cursor. }
                get(input);
            end;
        end;
    
    { ─── MAIN ───────────────────────────────────────────────────────────── }
    begin
        { Accessing `input↑` is invalid if `input` is an empty file. }
        if not EOF(input) then
        begin
            { “The first value in the list must be absolute […]” }
            if input↑ ≠ '+' then
            begin
                processTabulatorStopSpecification;
                expandTabulatorCharacters;
            end;
        end;
    end.
\$\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.