Convert CSV to Table

15

6

The Challenge

Given a CSV input, output a proper unicode table using box characters.

Formatting

The table will be formatted using the following rules:

  • Column width will be equal to the longest value of that column
  • All table data will be left justified
  • Each table will assume the first csv row to be the header
  • The table will use the following characters for its borders:

┌ ┬ ┐ ├ ┼ ┤ └ ┴ ┘ ─ │

Example

Input:
Name,Age,Gender
Shaun,19,Male
Debra,19,Female
Alan,26,Male
George,15,Male

Output:
┌──────┬───┬──────┐
│Name  │Age│Gender│
├──────┼───┼──────┤
│Shaun │19 │Male  │
│Debra │19 │Female│
│Alan  │26 │Male  │
│George│15 │Male  │
└──────┴───┴──────┘

Rules

  • Standard loopholes apply
  • You may submit a full program, a function or a lambda
  • Input can be from a file, a program argument or any acceptable alternative
  • Output can be to a file, returned or any acceptable alternative
  • CSV input should take the same format as used in my example.
  • Shortest answer in bytes wins.

CSV input should take the following form:

Header1,Header2,Header3 newline
Column1,Column2,Column3 newline
Column1,Column2,Column3 optional_newline

Shaun Wild

Posted 2016-11-21T13:05:20.300

Reputation: 2 329

Can you explain the CSV format you expect, and/or limitations in input? CSV in itself is ambiguous as a format specification. – H Walters – 2016-11-21T13:16:58.137

What characters can appear in the CSV fields? – Martin Ender – 2016-11-21T13:18:58.127

2I think there are basically two ways you can go with the CSV definition. If the interesting part of the problem is the output, you can make it as simple as "split on commas" and not have to worry about how to quote commas and how to quote the quote character. Otherwise, you could state a specific method of parsing the CSV ("double quotes toggle a mode in which commas are ignored, two double quotes in a row produces a literal double quote" is a fairly common one, but by no means the only one in existence). – None – 2016-11-21T13:21:13.883

4Err, serious problem: you haven't specified a victory condition. What are the programs meant to be optimized for? Length ([tag:code-golf])? – None – 2016-11-21T13:23:16.167

Relevant information: 1, 2, 3, 4.

– Adám – 2016-11-21T14:00:43.847

1At least the first three links there all define CSV differently (and at least two say that there are lots of different ways to do it). So I'm assuming that "CSV" needs to be more fully defined for use in a question (and that the solutions will try to get away with splitting on commas and not handling escaping because it allows them to be shorter). – None – 2016-11-21T14:26:38.737

2Okay I've edited the question to include specifics about the CSV format I would like everyone to use. – Shaun Wild – 2016-11-21T17:20:32.533

1CRLF? Seriously? That's going to give a fairly large penalty on Unix, where CR means something else in text files. You probably want to just replace that with "newline", allowing the OS-specific newline to be used. – None – 2016-11-21T23:29:20.203

Is input using list or array of strings (and no newlines) valid? – edc65 – 2016-11-29T08:19:21.053

@edc65 Nope.... – Shaun Wild – 2016-11-29T09:22:20.327

So APL and C# answers are invalid – edc65 – 2016-11-29T09:28:33.697

May the last input line have a newline like the other lines? – Adám – 2016-11-29T11:11:47.797

@Adám I will allow it. – Shaun Wild – 2016-11-29T11:13:20.077

Answers

10

Try (Dyalog) APL, 38 43 bytes

Last input line must have a trailing newline.

{{(⊃⍵)⍪⍉⍪↑¨↓⍉↑1↓⍵}s¨',',¨(s←1↓¨⊢⊂⍨⊢=⊃)¯1⌽⍵}

Try it online! In the offline version of Dyalog APL, execute ]boxing ON -style=min for the same effect.

Explanation

{ ... } an anonymous function where represents the argument:

¯1 ⌽ ⍵ rotate the trailing newline to the front

(s ← ... ) define the function s as follows, and apply it

  1 ↓¨ drop the first character of each

  ⊢ ⊂⍨ line, split where

  ⊃ = ⊢ the first character equals the characters in the string

',' ,¨ then prepend a comma to each line

apply the function s to each line

{ ... } now apply the following anonymous function:

  1 ↓ ⍵ drop the first element (the row headers)

  ↓ ⍉ ↑ transpose the list of rows into list of columns

  ↑¨ make each element (a list of entries) into a matrix of padded entries

  ⍉ ⍪ make into one-column matrix, then transpose into one-row matrix

  (⊃⍵) ⍪ put the argument's first element (the list of headers) on top`

Note: While the line drawing characters are not explicitly used in my solution, they are part of the APL character set, and would also be counted as single bytes.

Adám

Posted 2016-11-21T13:05:20.300

Reputation: 37 779

See comments above Is input using list or array of strings (and no newlines) valid? Nope. – edc65 – 2016-11-29T09:29:20.727

@edc65 Fixed. Thanks. – Adám – 2016-11-29T11:24:11.650

Hah, that boxed display surely comes in handy :) – Ven – 2016-11-29T12:00:58.997

2

PowerShell 3+, 365 bytes

$d=$input|ipcsv
$h=$d[0].PSObject.Properties.Name|%{$_|Add-Member -type NoteProperty -na c -v(($d.$_+$_|measure Length -ma).Maximum)-pa}
"┌$(($h|%{'─'*$_.c})-join'┬')┐"
"│$(($h|%{$_.PadRight($_.c)})-join'│')│"
"├$(($h|%{'─'*$_.c})-join'┼')┤"
$d|%{$i=$_;"│$(($h|%{$i.$_.PadRight($_.c)})-join'│')│"}
"└$(($h|%{'─'*$_.c})-join'┴')┘"

I feel like this could be improved a lot but I ran out of time. All line endings are \n with no \r, encoding is UTF8 with no BOM.

briantist

Posted 2016-11-21T13:05:20.300

Reputation: 3 110

1

JavaScript (ES6|FireFox), 286 bytes

f=>(d=f.split`
`.map(a=>a.split`,`),s=d[0].map((a,i)=>d.reduce((b,c)=>(n=c[i].length)>b?n:b,0)),d=d.map(a=>`│${a.map((b,i)=>b.padEnd(s[i])).join`│`}│`),d.splice(1,0,(g=h=>h[0]+s.map(a=>'─'.repeat(a)).join(h[1])+h[2])('├┼┤')),g('┌┬┐')+`
${d.join`
`}
`+g('└┴┘'))

Uses padEnd, which is FireFox specific.

Mwr247

Posted 2016-11-21T13:05:20.300

Reputation: 3 494

1Isn't this 288 bytes? – Adám – 2016-11-23T21:18:23.890

1@Adám ...yes... Fixed – Mwr247 – 2016-11-23T21:25:40.240

You use this alot, isn't g('└┴┘') equivalent to g└┴┘ (with backticks after g and at the end)? – NoOneIsHere – 2016-11-23T21:36:33.010

@NoOneIsHere Unfortunately not. Passing it in raw like that treats all 3 as a single character (so h[0] contains all 3, and h[1] is undefined). – Mwr247 – 2016-11-23T21:44:33.883

1padEnd is non-standard. You should specify the necessary execution environment. – Neil – 2016-11-24T21:42:48.627

1Also, there are a couple of places where you write \foo`+bar+`baz`` - you can save a byte by using a template \foo${bar}baz``. – Neil – 2016-11-24T21:46:22.567

1

Racket 578 bytes

(let*((ll(map(λ(x)(string-split x","))ll))(lr list-ref)(sl string-length)(d display)(dl displayln)(nc(length(lr ll 0)))
(nl(for/list((i nc))(apply max(for/list((j ll))(sl(lr j i))))))(pl(λ(sy)(d(lr sy 0))(for((n nc))(for((m(lr nl n)))(d(lr sy 1)))
(if(< n(sub1 nc))(d(lr sy 2))(dl(lr sy 3))))))(g(λ(i n)(for((m(-(lr nl n)(sl i))))(d" ")))))(pl'("┌""─""┬""┐"))
(for((i(lr ll 0))(n(in-naturals)))(d"│")(d i)(g i n))(dl"│")(pl'("├""─""┼""┤"))(for((j(range 1(length ll))))
(for((i(lr ll j))(n nc))(d"│")(d i)(g i n))(dl"│"))(pl'("└" "─" "┴" "┘")))

Ungolfed:

(define(f1 ll)
 (let* ((ll (map (λ (x)(string-split x ",")) ll))  ; use this to convert csv format to list of lists; 
         (lr list-ref)                    ; make short names of standard fns
         (sl string-length)
         (d display)
         (dl displayln)
         (nc (length (lr ll 0)))          ; number of cols; 
         (nl(for/list ((i nc))            ; get list of max string-length for each column
              (apply max
                     (for/list ((j ll))
                       (sl (lr j i))
                       ))))
         (pl (λ (sy)                      ; put lines using sent symbol list
               (d (lr sy 0)) 
               (for ((n nc))
                 (for ((m (lr nl n))) (d (lr sy 1)))
                 (if (< n (sub1 nc))
                     (d (lr sy 2))
                     (dl (lr sy 3))
                     ))))
         (g (λ (i n)                     ; pad with spaces if needed
              (for ((m (- (lr nl n) (sl i)))) (d " ")) ))) 
    ; put line above header: 
    (pl '("┌" "─" "┬" "┐"))

    ; put header: 
    (for ((i (lr ll 0)) (n (in-naturals)))
      (d "│")
      (d i)
      (g i n)
      )
    (dl "│")

    ; put line below header;
    (pl '("├" "─" "┼" "┤"))

    ; put rows: 
    (for ((j (range 1 (length ll))))
      (for ((i (lr ll j))
            (n nc))
        (d "│")
        (d i)
        (g i n)
        )
      (dl "│")
      )

    ; put bottom line: 
    (pl '("└" "─" "┴" "┘"))
    ))

Testing:

(f (list  "Name,Age,Gender"
          "Shaun,19,Male"
          "Debra,19,Female"
          "Alan,26,Male"
          "George,15,Male"))

Output:

┌──────┬───┬──────┐
│Name  │Age│Gender│
├──────┼───┼──────┤
│Shaun │19 │Male  │
│Debra │19 │Female│
│Alan  │26 │Male  │
│George│15 │Male  │
└──────┴───┴──────┘

rnso

Posted 2016-11-21T13:05:20.300

Reputation: 1 635

1

JavaScript (ES6), 281 bytes

Note: input as a single string with newlines - as requested by OP. Other answers use a string list - using a string array in input I can avoid the first split and cut 9 bytes.

l=>(l=l.split`
`.map(r=>r.split`,`.map((w,i)=>(v=w.length)<c[i]?w:(c[i]=v,w)),c=[k=0]),l=l.map(r=>r.map((v,i)=>(v+' '.repeat(c[i]-v.length)))),[h=c.map(x=>'─'.repeat(x)),l.shift(),h,...l,h].map(a=>'│┌├└'[j=a!=h?0:++k]+a.join('│┬┼┴'[j])+'│┐┤┘'[j]).join`
`)

Less golfed

l=>(
  // split input in an array of string arrays
  // meanwhile find the column widths and put them in *c*
  l = l.split`\n`.map(r=>r.split`,`.map((w,i)=>(v=w.length)<c[i]?w:(c[i]=v,w)),c=[]),

  // pad each column to the max column width
  l = l.map(r=>r.map((v,i)=>(v+' '.repeat(c[i]-v.length)))),

  // put in *h* the horizontal lines for top,bottom and head separator
  h = c.map(x => '─'.repeat(x) ),

  // add the *h* line at top, bottom and after head line
  l = [h, l.shift(), h, ...l, h],

  // rebuild a string, joining columns with '|' unless the row is *h*
  // if the row is *h* use different characters to join columns
  k = 0, 
  l.map(a=> '│┌├└'[j=a!=h?0:++k] + a.join('│┬┼┴'[j]) + '│┐┤┘'[j])
  .join`\n`  
)

Test

F=
l=>(l=l.split`
`.map(r=>r.split`,`.map((w,i)=>(v=w.length)<c[i]?w:(c[i]=v,w)),c=[k=0]),l=l.map(r=>r.map((v,i)=>(v+' '.repeat(c[i]-v.length)))),[h=c.map(x=>'─'.repeat(x)),l.shift(),h,...l,h].map(a=>'│┌├└'[j=a!=h?0:++k]+a.join('│┬┼┴'[j])+'│┐┤┘'[j]).join`
`) 
  
function update() {
  O.textContent = F(I.value)
}
update()
#I { width:60%; height: 8em} 
<textarea id=I>Name,Age,Gender
Shaun,19,Male
Debra,19,Female
Alan,26,Male
George,15,Male</textarea><br>
<button onclick='update()'>Go</button>
<pre id=O></pre>

edc65

Posted 2016-11-21T13:05:20.300

Reputation: 31 086

0

Python 3, 318 bytes

-3 bytes for using the % formatting and -1 for abbreviating str.join

L=[c.split(',')for c in input().split('\n')]
m=[max(len(x)for x in c)for c in zip(*L)]
L=[[""]+[d.ljust(n)for d,n in zip(c,m)]+[""]for c in L]
g=["─"*i for i in m]
J=str.join
print('\n'.join(["┌%s┐"%J("┬",g),J("│",L[0]),"├%s┤"%J("┼",g)]+[J("│",L[i])for i in range(1,len(L))]+["└%s┘"%J("┴",g)]))

Requires input enclosed in quotes.

Karl Napf

Posted 2016-11-21T13:05:20.300

Reputation: 4 131

1Looks like 318 bytes to me. – Adám – 2016-11-23T21:17:52.770

1@Adám You are right, i looked at the chars. – Karl Napf – 2016-11-23T21:20:06.033

Does not work, because input() only takes one line on each call.

You will need to call input() until there are no more lines, or read directly from stdin. – movatica – 2019-08-11T22:03:48.823

Besides that: 292 bytes

– movatica – 2019-08-11T22:53:17.223

0

C#, 696 Bytes

Golfed:

string T(string[]f){int w=f.Max(r=>r.Length),a=f.Select(r=>r.Split(',')[0].Length).Max(),b=f.Select(r=>r.Split(',')[1].Length).Max(),c=f.Select(r=>r.Split(',')[2].Length).Max();string o="",n="\r\n",d="",j=string.Concat(Enumerable.Repeat("─",a)),k=string.Concat(Enumerable.Repeat("─",b)),l=string.Concat(Enumerable.Repeat("─",c));Func<string,int,string>z=(q,p)=>{return q.PadRight(p);};d="┌"+j+"┬"+k+"┬"+l+"┐";o+=d+n;var g=f.First().Split(',');o+="|"+z(g[0],a)+"|"+z(g[1],b)+"|"+z(g[2],c)+"|";d="├"+j+"┼"+k+"┼"+l+"┤";o+=n+d+n;for(int i=1;i<f.Length;i++){var h=f[i].Split(',');o+="|"+z(h[0],a)+"|"+z(h[1],b)+"|"+z(h[2],c)+"|"+n;}d="└"+j+"┴"+k+"┴"+l+"┘";o+=d;return o;}

Ungolfed (and nicer, because ^that is no use to anyone):

public string T(string[] c)
{
  int width = c.Max(r => r.Length),
    longestFirstColumn = c.Select(r => r.Split(',')[0].Length).Max(),
    longestSecondColumn = c.Select(r => r.Split(',')[1].Length).Max(),
    longestThirdColumn = c.Select(r => r.Split(',')[2].Length).Max();

  string o = "", lr = "\r\n", border = "",
    firstColumnFiller = string.Concat(Enumerable.Repeat("─", longestFirstColumn)),
    secondColumnFiller = string.Concat(Enumerable.Repeat("─", longestSecondColumn)),
    thirdColumnFiller = string.Concat(Enumerable.Repeat("─", longestThirdColumn));

  Func<string, int, string> padRight = (a, b) => { return a.PadRight(b); };

  border = "┌" + firstColumnFiller
    + "┬" +
    secondColumnFiller + "┬"
    + thirdColumnFiller
    + "┐";

  o += border + lr;

  var firstRow = c.First().Split(',');

  o += "|" + padRight(firstRow[0], longestFirstColumn) +
    "|" + padRight(firstRow[1], longestSecondColumn) +
    "|" + padRight(firstRow[2], longestThirdColumn) + "|";

  border = "├" +
    firstColumnFiller + "┼" +
    secondColumnFiller + "┼" +
    thirdColumnFiller
    + "┤";

  o += lr + border + lr;

  for (int i = 1; i < c.Length; i++)
  {
    var row = c[i].Split(',');

    o += "|" + padRight(row[0], longestFirstColumn) + "|"
    + padRight(row[1], longestSecondColumn) + "|" +
    padRight(row[2], longestThirdColumn) + "|" + lr;
  }

  border = "└" +
    firstColumnFiller + "┴" +
    secondColumnFiller + "┴" +
    thirdColumnFiller
    + "┘";

  o += border;

  return o;
}

Testing:

┌──────┬───┬──────┐         ┌──────────┬───────────────────────────┬─────┐
|Name  |Age|Gender|         |Name      |PPCG Challenge             |Votes|
├──────┼───┼──────┤         ├──────────┼───────────────────────────┼─────┤
|Shaun |19 |Male  |         |Pete Arden| Print all integers        | 4   |
|Debra |19 |Female|         |Pete Arden| Yes of course I'm an adult| 3   |
|Alan  |26 |Male  |         |Pete Arden| 5 Favorite Letters        | 1   |
|George|15 |Male  |         └──────────┴───────────────────────────┴─────┘
└──────┴───┴──────┘

Pete Arden

Posted 2016-11-21T13:05:20.300

Reputation: 1 151

Somehow, I keep getting 697 bytes when counting this. – Adám – 2016-11-24T06:37:34.483

@Adám Just checked again, the Golfed string is 666 columns long in Visual Studio. But neither 666 nor 697 are exactly competitive scores anyway :) – Pete Arden – 2016-11-24T08:23:55.597

You have a trailing newline, but even when removing it, it is still 696 bytes.

– Adám – 2016-11-24T08:37:28.590

@Adám Ah... I've been waiting for a letter count/byte count discrepancy to trip me up. Should have known with these funny symbols in this one ("┼"). Updated, thanks :) – Pete Arden – 2016-11-24T08:46:52.687

See comments above Is input using list or array of strings (and no newlines) valid? Nope. – edc65 – 2016-11-29T09:29:48.450

0

Perl, 273 + 9 (-CS -nlaF, flags) = 282 bytes

$v[$.-1]=[@F];map$l[$_]<($l=length$F[$_])&&($l[$_]=$l),0..$#F}sub p{printf$p,@_}sub o{p
pop,map{$\x$l[$_],$_-$#l?$_[0]:pop}0..$#l}$p=join'%s','',(map"\%-${_}s",@l),$/;($\,$c,@c)=map
chr$_*4+9472,0,.5,3..15;o@c[8,1,0];p($c,map{$_,$c}@$_),$i++||o@c[12,6,4]for@v;o@c[10,3,2];{

Using:

cat file.csv | perl -CS -nlaF, script.pl

Try it on Ideone.

Denis Ibaev

Posted 2016-11-21T13:05:20.300

Reputation: 876

0

PHP, 313 bytes

for(;$r=fgetcsv(STDIN);$a[]=$r)foreach($r as$x=>$s)$e[$x]=max($e[$x],strlen($s));$t=["┬","┌","┐"];eval($L='foreach($e as$i=>$n)echo$t[!$i],str_repeat("─",$n);echo"$t[2]\n";');foreach($a as$k=>$r){foreach($r as$i=>$s)echo"│",str_pad($s,$e[$i]);echo"│\n";$t=["┼","├","┤"];if(!$k)eval($L);}$t=["┴","└","┘"];eval($L);

breakdown

for(;$r=fgetcsv(STDIN);$a[]=$r)                         // read csv from STDIN, append to array $a
    foreach($r as$x=>$s)$e[$x]=max($e[$x],strlen($s));  // remember max length in array $e
                                                        // print top border
$t=["┬","┌","┐"];eval($L='foreach($e as$i=>$n)echo$t[!$i],str_repeat("─",$n);echo"$t[2]\n";');
foreach($a as$k=>$r)
{
    foreach($r as$i=>$s)echo"│",str_pad($s,$e[$i]);echo"│\n";   // print row
    $t=["┼","├","┤"];if(!$k)eval($L);                           // print border below header
}
$t=["┴","└","┘"];eval($L);                              // print bottom border

Test it at ideone

Titus

Posted 2016-11-21T13:05:20.300

Reputation: 13 814

0

APL (Dyalog Extended), 36 25 bytesSBCS

Full program. Assumes that ABCDEFGHIJKLMNOPQRSTUVWXYZ is the CSV file. Prints to stdout.

⌂disp(1↑m)⍪↑¨↓⍉1↓m←⎕CSV⎕A

Try it online!

⎕A the uppercase Alphabet (the shortest-to-reference built-in string)
⎕CSV read that file and convert from CSV to matrix
m← store as m (for matrix)
1↓ drop the first row
 transpose
 split into list of columns
↑¨ mix each list of strings into a matrix
()⍪ stack the following on top of that:
1↑m take the first row of m
⌂disp apply dfns.disp to that (draws line drawing characters)

Adám

Posted 2016-11-21T13:05:20.300

Reputation: 37 779