# 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

• I 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).
– user62131
Nov 21, 2016 at 13:21
• Err, serious problem: you haven't specified a victory condition. What are the programs meant to be optimized for? Length (code-golf)?
– user62131
Nov 21, 2016 at 13:23
• At 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).
– user62131
Nov 21, 2016 at 14:26
• Okay I've edited the question to include specifics about the CSV format I would like everyone to use. Nov 21, 2016 at 17:20
• CRLF? 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.
– user62131
Nov 21, 2016 at 23:29

# 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

s¨ 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.

• See comments above Is input using list or array of strings (and no newlines) valid? Nope. Nov 29, 2016 at 9:29
• @edc65 Fixed. Thanks.
Nov 29, 2016 at 11:24
• Hah, that boxed display surely comes in handy :)
– Ven
Nov 29, 2016 at 12:00

# 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. # 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) # JavaScript, 261 bytes (t,[H,...D]=T=t.split .map(r=>r.split,),w=H.map((_,i)=>Math.max(...T.map(r=>r[i].length))),L=(x,i,_,[[[a,b=a,c=a]],p]=1/i?│:[x,'─'])=>a+w.map((n,j)=>(p?'':x[j]).padEnd(n,p)).join(b)+c+ )=>L┌┬┐+L(H,0)+L├┼┤+D.map(L).join+L└┴┘  Try it online! Ungolfed and explained ( tableCSV, [header, ...data] = table = tableCSV.split(\n).map(row => row.split(,)), widths = header.map((_, i) => Math.max(...table.map(row => row[i].length))), drawLine = ( x, // either row array or single string array i, // either data index or undefined _, // ignored [[[a, b = a, c = a]], pad] = 1 / i ? │ : [x, '─'] // unpack x ) => a + widths.map((n, j) => (pad ? '' : x[j]).padEnd(n, pad)).join(b) + c + \n ) => drawLine┌┬┐ + drawLine(header, 0) + drawLine├┼┤ + data.map(drawLine).join() + drawLine└┴┘  ## 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 │ └──────┴───┴──────┘  # 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. • Isn't this 288 bytes? – Adám Nov 23, 2016 at 21:18 • @Adám ...yes... Fixed Nov 23, 2016 at 21:25 • You use this alot, isn't g('└┴┘') equivalent to g└┴┘ (with backticks after g and at the end)? Nov 23, 2016 at 21:36 • padEnd is non-standard. You should specify the necessary execution environment. – Neil Nov 24, 2016 at 21:42 • Also, 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
Nov 24, 2016 at 21:46

# 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>

# PowerShell for Windows, 256...236 238 bytes

$v=$args-split'
'-ne''|%{,($_-split',')} function z($b,$a){$b[1]+(($v[0]|%{@($a)[$x++]+''|% p*ht($v|%{$_[$x-1]}|% le*|sort -b 1)$b[0]})-join$b[2])+$b[3]} z '─┌┬┐'$v|%{z ' │││'$_;if(!$r++){z '─├┼┤'}}
z '─└┴┘'


Try it online!

Notes:

• The Sort alias defined for Windows only. It need Sort-Object for Linux instead sort or command set-alias sort sort-object before this script (TIO way).
• The p*ht shortcut expands to the PadRight-method for a string.
• The le* shortcut expands to the Length-method for a string.

An input csv contains:

• comma and newline are delimiters only (no comma and no newline inside values|headers)
• spaces in the values|headers are significant letter (keep due justifying)
• at least 2 not empty lines (headers and values)
• an "optional_newline" at the end
• optional empty inner lines
• I don't think the width of the columns is quite right, running the TIO link results in the Age column being 5 chars wide instead of 3. Apr 26 at 1:14
• thanks. fixed 13 hours ago. Apr 26 at 6:17

# JavaScript (V8), 291 bytes

d=>([h]=r=d.split
.map(s=>s.split,),l=h.map((_,i)=>Math.max(...r.map(k=>k[i].length))),s=([[x,y,z]])=>h.reduce((a,_,i)=>a+"─".repeat(l[i])+(i==h.length-1?z:y),x)+
,[f,...t]=r.map(r=>r.reduce((s,x,i)=>s+x.padEnd(l[i])+z,z="│")+
),s┌┬┐+f+s├┼┤+t.join+s└┴┘)


Try it online!

Not exactly a competitive score compared to the three other answers written in JS (???), but its as good as I can do.

Ungolfed/explained:

csvString => (
// convert the string into an array of row content arrays (string[][])
// extracting first element h to save a few bytes off of r[0] later
[h] = rows = d.split(\n).map(s=>s.split(,)),
// get the lengths of the longest entry of each column
columnLengths = h.map((_, i) => Math.max(...rows.map(row => row[i].length))),
// build strings for the top line, header separator, and bottom line
// [[x,y,z]] to extract chars from tagged template literal
// "─" repeated by the length of the column + correct separator if
// last/middle character
buildLine = ([[x,y,z]]) => h.reduce(
(a,_,i) =>
a + "─".repeat(columnLengths[i]) + (i === h.length - 1 ? z : y),
x
) + \n,
// get lines that contain data seperated by "│" - f is header, t is rest
row.reduce((s, x, i) => s + x.padEnd(l[i]) + z, z = "│") + \n
),
// final return value: top row + header + header separator + rest of
// data + bottom row
buildLine┌┬┐ + header + buildLine├┼┤ + rest.join("") + buildLine└┴┘
)


# 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.

• Looks like 318 bytes to me.
Nov 23, 2016 at 21:17
• @Adám You are right, i looked at the chars. Nov 23, 2016 at 21:20
• 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. Aug 11, 2019 at 22:03
• Besides that: 292 bytes Aug 11, 2019 at 22:53

# 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[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  |         └──────────┴───────────────────────────┴─────┘
└──────┴───┴──────┘

• Somehow, I keep getting 697 bytes when counting this.
Nov 24, 2016 at 6:37
• @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 :) Nov 24, 2016 at 8:23
• You have a trailing newline, but even when removing it, it is still 696 bytes.
Nov 24, 2016 at 8:37
• @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 :) Nov 24, 2016 at 8:46
• See comments above Is input using list or array of strings (and no newlines) valid? Nope. Nov 29, 2016 at 9:29

# 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.

# 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