27
\$\begingroup\$

Stack Exchange doesn't know how to transpose tables. Let's help.

Given a markdown table, transpose it.

Input assumptions:

  • There will be at least two rows (including header) and two columns
  • Either all cells have no leading space or all cells have exactly one leading space (you must handle both)
  • If the cells have a leading space, then the widest cell in every column has exactly one trailing space, otherwise, the widest cell in every column has no trailing spaces
  • All pipes line up
  • The header-body separator lines of dashes extend the full width of their column, except a leading and tailing space if the table uses this
  • Cell alignment (-:, :-:, etc.) is not used
  • No other extraneous spaces appear (this includes between words)
  • Either all rows have a trailing pipe or no rows have a trailing pipe (you must handle both)
  • Cells contain only printable ASCII, but no pipes (|), dashes (-) or any characters that need special treatment (\, **, etc.)
  • All cells will have at least some non-space content

Output requirements:

  • Trailing pipe on every row or no trailing pipes (must be consist for any one result)
  • Either no leading spaces, or exactly one leading space in every cell (must be consist for any one result)
  • If you produce leading spaces, then the widest cell in each column must have exactly one trailing space
  • All pipes must line up
  • The header-body separator must extend to the full width of the column, save for leading and trailing spaces, if used in that result
  • Trailing spaces (and up to one trailing line break) are acceptable

Test cases

A

Test inputs (you must handle every one of these)

| A header | Another header |
| -------- | -------------- |
| First    | row            |
| Second   | row            |
| A header | Another header
| -------- | --------------
| First    | row           
| Second   | row            
|A header|Another header|
|--------|--------------|
|First   |row           |
|Second  |row           |        
|A header|Another header
|--------|--------------
|First   |row           
|Second  |row           

For any of the above inputs, output must be any one of the below
(not necessarily corresponding 1:1)

| A header       | First | Second |
| -------------- | ----- | ------ |
| Another header | row   | row    |
| A header       | First | Second
| -------------- | ----- | ------
| Another header | row   | row   
|A header      |First|Second|
|--------------|-----|------|
|Another header|row  |row   |
|A header      |First|Second
|--------------|-----|------
|Another header|row  |row   

B

Test inputs (you must handle every one of these)

| A header | Another header | Last column here  |
| -------- | -------------- | ----------------- |
| First    | 0              | more content here |
| Second   | row            | that's it!        |
| A header | Another header | Last column here 
| -------- | -------------- | -----------------
| First    | 0              | more content here
| Second   | row            | that's it!       
|A header|Another header|Last column here |
|--------|--------------|-----------------|
|First   |0             |more content here|
|Second  |row           |that's it!       |
|A header|Another header|Last column here
|--------|--------------|-----------------
|First   |0             |more content here
|Second  |row           |that's it!

For any of the above inputs, output must be any one of the below
(not necessarily corresponding 1:1)

| A header         | First             | Second     |
| ---------------- | ----------------- | ---------- |
| Another header   | 0                 | row        |
| Last column here | more content here | that's it! |
| A header         | First             | Second    
| ---------------- | ----------------- | ----------
| Another header   | 0                 | row       
| Last column here | more content here | that's it!
|A header        |First            |Second    |
|----------------|-----------------|----------|
|Another header  |0                |row       |
|Last column here|more content here|that's it!|
|A header        |First            |Second    
|----------------|-----------------|----------
|Another header  |0                |row       
|Last column here|more content here|that's it!
\$\endgroup\$
14
  • 3
    \$\begingroup\$ Maybe you want testcases with more columns? May the input table only contains header? \$\endgroup\$ – tsh Jan 5 at 8:22
  • 1
    \$\begingroup\$ Don't know about that snippet style of showing examples, it's too fiddly. Posting examples as text is way clearer. Edited post, please feel free to revert back if really want that horrible snippet style! T_T \$\endgroup\$ – Noodle9 Jan 5 at 11:21
  • 1
    \$\begingroup\$ @vrintle I believe that's allowed by standard I/O rules for the site. \$\endgroup\$ – Adám Jan 5 at 19:14
  • 1
    \$\begingroup\$ @Arnauld Yes, fixed. Thanks. \$\endgroup\$ – Adám Jan 6 at 0:50
  • 1
    \$\begingroup\$ @KevinCruijssen Ooh, that's a good question. Since I didn't specify anything but "or any characters that need special treatment" and it would seem that you would need to give dashes special treatment, let's say that they can't. I'll add it. \$\endgroup\$ – Adám Jan 7 at 15:41

10 Answers 10

16
\$\begingroup\$

Python 3.8 (pre-release), 218 bytes

n=[(t:=[x for s in l.split('|')if(x:=s.strip())],max(map(len,t)))for l in open(0)]
del n[1]
j=''.join
f=lambda c:j(f'|{l[c]:{w}}'for l,w in n)
print(f(0),j('|'+w*'-'for _,w in n),*map(f,range(1,len(n[0][0]))),sep='\n')

Try it online!

-12 bytes thanks to basic code golfing advice by @cairdcoinheringaahing :)

-25 bytes thanks to @ovs by using open(0) instead of sys.stdin!

\$\endgroup\$
6
  • 2
    \$\begingroup\$ Welcome to the site, and nice first answer! I've edited in a link to TIO for you, and it looks like this doesn't fully work (see the TIO link)? I'd also recommend for basic golfing to remove all whitespace (i.e. spaces) where possible: Try it online! \$\endgroup\$ – caird coinheringaahing Jan 5 at 12:09
  • 1
    \$\begingroup\$ Also, be sure to check out our Tips for golfing in Python page \$\endgroup\$ – caird coinheringaahing Jan 5 at 12:09
  • \$\begingroup\$ @cairdcoinheringaahing Thanks for the TIO! Ah I see, I think it's that this program assumes a trailing newline at the last line (that's why I discard the last "split" value with [:-1]). I've added an extended TIO with all the sample input cases, it looks fine I think. \$\endgroup\$ – Anakhand Jan 5 at 12:13
  • 3
    \$\begingroup\$ You can replace all of sys.stdin.readlines() with open(0), which doesn't even need an import. \$\endgroup\$ – ovs Jan 5 at 15:43
  • 2
    \$\begingroup\$ And one more byte can be saved by by doing the del n[1] by hand using iterable unpacking: Try it online! \$\endgroup\$ – ovs Jan 5 at 15:49
9
\$\begingroup\$

K (ngn/k), 84 80 75 73 bytes

-11 bytes from golfing

{,/'+({"|",'n$(*x;(n:|/#'x)#"-"),1_x}(~""~)#{(-^*|x)_(^*x)_x}/'"|"\)'x_1}

Try it online!

Takes a list of strings, one for each row. Returns a list of strings, without leading/trailing spaces, and without trailing pipes.

  • x_1 drop the row at index 1 (i.e., the one with all the -'s)
  • (...)' run the code in parenthesis on each row of the input
  • "|"\ split each row on pipes (i.e. get the cells)
  • {(-^*|x)_(^*x)_x}/' strip leading and trailing whitespace from each cell's contents
  • (~""~)# filter down to non-empty cells
  • {...(*x;(n:|/#'x)#"-"),1_x} insert a column of the correct number of -'s
  • {...n$...} pad each row with spaces
  • {"|",'...} prepend a | to each cell
  • ,/'+ transpose the table and concatenate each row
\$\endgroup\$
7
\$\begingroup\$

Ruby 2.7, 144 bytes

->a{(a=(a.map{_1.scan /\| ?\K[^|
]*[^| 
-]/}-[[]]).map{|l|l.map{_1.ljust l.map(&:size).max}}.transpose.map{?|+_1*?|}).insert 1,a[0].tr("^|",?-)}

Try it online!

Takes in an array of lines, and outputs array of lines through the last output format. TIO uses an older version of Ruby, whereas in Ruby 2.7, we've numbered parameters, which saves six bytes!

\$\endgroup\$
6
\$\begingroup\$

JavaScript (ES8), 204 bytes

Expects and returns an array of lines. Uses the last output format.

s=>(m=[,0]).map((r,y)=>m[r&&y].map((s,x)=>(r?s:"").padEnd(w[x],"-"[r])).join`|`,s.map(w=(s,y)=>s.replace(/\| *(.+?) *(?=\||$)/g,(_,s)=>((v=s.length)<w[y]?0:w[y]=v,m[x+=x==1]=m[x++]||[])[y]=s,y+=y<2,x=0)))

Try it online!

Commented

Step 1

Extracting the content of all cells and saving them into the matrix m[].

s.map(w =                   // w is an object used to store the max. width in each row
  (s, y) =>                 // for each string s at position y in the input array:
  s.replace(                //   match in s all occurrences of:
    /\| *(.+?) *(?=\||$)/g, //     "\|"       a pipe
                            //     " *"       followed by optional whitespace
                            //     "(.+?)"    followed by a non-greedy string
                            //                (this is the payload)
                            //     " *"       followed by optional whitespace
                            //     "(?=\||$)" followed by either a pipe or the end of
                            //                the line (not captured)
    (_, s) =>               //     for each payload string s found in there:
      ( (v = s.length)      //       define v as the length of this string
        < w[y] ?            //       if it's smaller than w[y]:
          0                 //         do nothing
        :                   //       else:
          w[y] = v,         //         update w[y] to v
        m[x += x == 1] =    //       increment x if x = 1
          m[x++] || []      //       if undefined, initialize m[x] to an empty array
                            //       increment x afterwards
      )[y] = s,             //       save s in m[x][y]
                            //     initialization:
    y += y < 2,             //       increment y if y = 0 or y = 1
    x = 0                   //       start with x = 0
  )                         //   end of replace()
)                           // end of map()

Step 2

Building the output.

(m = [, 0])      // initialize m[] with m[1] = 0 (reserved for the separator lines)
                 // (this is actually done before step 1)
.map((r, y) =>   // for each row r[] at position y in m[]:
  m[r && y]      //   use m[0] if r = 0, or m[y] otherwise
  .map((s, x) => //   for each string s at position x in this row:
    (r ? s : "") //     use an empty string if r = 0, or s otherwise
    .padEnd(     //     pad it with:
      w[x],      //       w[x] characters
      "-"[r]     //       using hyphens if r = 0, or spaces otherwise
    )            //     end of padEnd()
  ).join`|`      //   end of inner map(); join with pipes
)                // end of outer map()
\$\endgroup\$
0
4
\$\begingroup\$

PHP, 429 420 bytes

$a=preg_split("/
(\W*
)?/",$argv[1]);foreach($a as&$v)$v=array_values(array_filter(explode('|',$v)));for($i=-1;++$i<max(count($a),count($a[0]));)for($j=-1;++$j<=$i;){$t=trim($a[$i][$j]);$u=$a[$i][$j]=trim($a[$j][$i]);$a[$j][$i]=$t;$m[$i]=max(strlen($t),$m[$i]);$m[$j]=max(strlen($u),$m[$j]);}for($k=-1;$b=$a[++$k];print$k?"
":"
$s
")for($l=-1;$b[++$l]!=null;){printf("|%-$m[$l]s",$b[$l]);$s.='|'.str_repeat('-',$m[$l]);}

Try it online!

I'm pretty sure it could be golfed a bit more, when I have time I'll try to max it

EDIT: saved 9 bytes by using an alias for $a[$k] and using double quotes for the printf. Still work to do but 420 is a good number ;)

ungolfed explanation (the outputted newlines also replaced by \n for readability):

$a=preg_split("/\n(\W*\n)?/",$argv[1]); //split input by line removing the dashes under the headers
foreach($a as&$v)$v=array_values(array_filter(explode('|',$v))); //split each line at the pipes, remove empty cells and rearrange indexes if needed
for($i=-1;++$i<max(count($a),count($a[0]));) //looping on largest array dimension
  for($j=-1;++$j<=$i;){ //swapping array values, trimming strings and we keep the largest length for each column
    $t=trim($a[$i][$j]);
    $u=$a[$i][$j]=trim($a[$j][$i]);
    $a[$j][$i]=$t;
    $m[$i]=max(strlen($t),$m[$i]);
    $m[$j]=max(strlen($u),$m[$j]);
  }
for($k=-1;$b=$a[++$k];print$k?"\n":"\n$s\n") //output of the result with the line
  for($l=-1;$b[++$l]!=null;){
    printf("|%-$m[$l]s",$b[$l]);
    $s.='|'.str_repeat('-',$m[$l]);
  }
\$\endgroup\$
3
\$\begingroup\$

Charcoal, 79 71 61 bytes

WS¿¬⁼ιη«≔⟦⟧υFΦ⪪ι|κ«≔⁻Eκμ⌕Aκ ζ≔✂κ⌊ζ⊕⌈ζ¹κP⁺|κM⊕¬υ↓⊞υLκ»MLυ↑|⌈υ↑

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

WS¿¬⁼ιη«

Loop over the input rows, skipping the separator row.

≔⟦⟧υ

Start collecting the output column cell widths.

FΦ⪪ι|κ«

Split the row on |s and loop over the non-empty cells.

≔⁻Eκμ⌕Aκ ζ

Remove the positions of the spaces from the list of all character indices.

≔✂κ⌊ζ⊕⌈ζ¹κ

Get the trimmed cell text.

P⁺|κ

Print the vertical line and text without moving the cursor.

M⊕¬υ↓

Move down one or two lines depending on whether this is the first cell.

⊞υLκ

Push the width of the trimmed text to the list.

»MLυ↑

Move up to the separator row.

|⌈υ

Print the separator with its vertical line.

Move to the first row.

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

Perl 5, 269 228 bytes

sub f{@t=map[split/\s*\|\s*/],pop()=~/^.\s?(.*?)\s*$/mg;@T=map{$c=$_-1;[map$$_[$c],@t]}1..@{splice@t,1,1};@w=map{max map y///c,@$_}@t;splice@T,1,0,[map'-'x$_,@w];join'',map{(map{sprintf'|%*s',-$w[$_],$$r[$_]}0..$#{$r=$_}),$/}@T}

Try it online!

sub f{
  @t=map[split/\s*\|\s*/],       #split at |, trim away spaces before and after
     pop()=~/^.\s?(.*?)\s*$/mg;  #pick part of each line that matter from input
  @T=map{$c=$_-1;[map$$_[$c],@t]}#transpose input table @t
     1..@{splice@t,1,1};         #cols
  @w=map{max map y|||c,@$_}@t;   #find max width of cols in transposed table
  splice@T,1,0,[map'-'x$_,@w];   #insert second line with --- of each width
  join'',                        #join pieces of output into single string
  map{                           #for each line in transposed table @T
    (
      map {
        sprintf '|%*s',          #string right padded...
          -$w[$_],               #...to width of this column
          $$r[$_]                #info in column number $_
      }
      0..$#{$r=$_}               #0..last index of current line in @T
    ),
    $/                           #newline \n (default)
  }
  @T                             #for each line in transposed table @T
}
\$\endgroup\$
2
\$\begingroup\$

Julia 1.0, 146 144 bytes

x->["|"*join(rpad.(i,l),"|") for i=zip(insert!.((n=[strip.(split(i,"|";keepempty=0>1)) for i=x[[1;3:end]]];),2,"-".^(l=maximum.(length,n)))...)]

input and output are arrays of strings

Try it online!
All tests!

Edit: -2 bytes by replacing false by 0>1

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

05AB1E, 45 bytes

ā2ÊÏε'|¡õÚðδÚ}©øćU®ε€gà}©'-ךXšεεR®NèjR„| ì]»

Input as a list of lines. Outputs with spaces, and without trailing |.

Try it online (the header | is to convert the input into a list of lines).

Explanation:

ā            # Push a list in the range [1, (implicit) input-length]
 2Ê          # Check for each that they're NOT equal to 2
   Ï         # And only leave the lines in the input at truthy indices
             # (so we've removed the second line with the dashes)
ε            # Map each remaining line to:
 '|¡        '#  Split it on "|"
    õÚ       #  Remove leading/trailing empty strings, if the line started and/or ended
             #  with a "|"
       δ     #  Map over each part:
      ð Ú    #   Remove leading/trailing spaces
}©           # After the map: store the result in variable `®` (without popping)
  ø          # Zip/transpose, swapping rows/columns
   ć         # Pop and push remainder-list and first item separated to the stack
    U        # Pop this first item, and store it in variable `X`
 ®           # Push the list of variable `®` again
  ε          # Map over each list:
   €         #  Map over each part:
    g        #   Pop and get the length of the part
   à         #  Pop and only leave the maximum length
  }©         # After the map: store this as new value `®` (without popping)
    '-×     '# Transform each integer in the list into that many "-" as string
       š     # Prepend this list to the remainder-list
        Xš   # And then prepend the extracted head `X` back as well
ε            # Map over each list:
 ε           #  Map over each part inside the list:
  R          #   Reverse the current part
   ®         #   Push the list of integers `®`
    Nè       #   Pop and index the map-index into it
      j      #   Pad leading spaces up to that many characters to the reversed part
       R     #   Then reverse it back so the spaces are trailing
        „| ì #   And prepend "| " to the part
]            # Close the nested maps
 »           # Join each inner list by spaces, and then each string by newlines
             # (after which the result is output implicitly)
\$\endgroup\$
1
\$\begingroup\$

APL (Dyalog Extended), 40 bytes

⊃1(,/↑,2↓⊢){'|','-'⍪⍢(1∘⊖)↑⌂deb⍵⊆⍨⍵≠⊃⍵}¨

Try it online!

Adám's method.

64 bytes(old)

{↑,/⍉↑{'|',¨¯1⌽(m∘↑¨1⌽⍵),⍨⊂'-'/⍨m←⌈/≢¨⍵}¨¯1⌽1↓1⌽⌂deb¨'|'(≠⊆⊢)¨⍵}

Try it online!

Takes the input as a list of lines, outputs a character matrix.

-9 bytes using dfns.deb.

Explanation

'|'(≠⊆⊢)¨⍵ split each line on pipes(|)

⌂deb¨ trim whitespace from both sides of each cell

¯1⌽1↓1⌽ drop the row with hyphens

{'|',¨¯1⌽(m∘↑¨1⌽⍵),⍨⊂'-'/⍨m←⌈/≢¨⍵}¨ do the following for each row:

m←⌈/≢¨⍵ set m to the maximum cell length

⊂'-'/⍨ create a cell of hyphens that long

,⍨ join with

m∘↑¨1⌽⍵ input shifted by 1, with each cell padded to length m.

¯1⌽ shift back to original position

'|',¨ prepend a pipe to each cell

Finally,

⍉↑ convert to matrix, and transpose

↑,/ join each row's cells, and convert to matrix again

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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