26
\$\begingroup\$

You have been chosen to make a program that creates some pretty ASCII bar charts. Here is the input format:

[List of words (they can have spaces)] [an integer >= 0]
Bar 3
This is so cool 4
IDK-Why 6

The input will have multiple lines in this format, each one representing one bar in the graph. The output format of a graph will be like this:

 _ _ _ _
|_|_|_|_|
 | | | |
 | | | + [name of 4th bar]
 | | + [name of 3rd bar]
 | + [name of 2nd bar]
 + [name of 1st bar]

Here are some examples:

In:
Cool 4
Kool 6

Out:
   _
  | |
 _| |
| | |
| | |
| | |
|_|_|
 | |
 | + Kool
 + Cool

In:
Graph 5
Bar 3

Out:
 _
| |
| |_
| | | 
| | |
|_|_|
 | |
 | + Bar
 + Graph

In:
Very 4
Large 5
Bar 3
Graph 5

Out:
   _   _
 _| | | | 
| | |_| |
| | | | |
| | | | |
|_|_|_|_|
 | | | |
 | | | + Graph
 | | + Bar
 | + Large
 + Very

In:
Bars can be 0 0
Large-bars_are$nice2 6
average)(@#$ 3
neato 5

Out:
   _
  | |  _
  | | | | 
  | |_| |
  | | | |
  | | | |
 _|_|_|_|
 | | | |
 | | | + neato
 | | + average)(@#$
 | + Large-bars_are$nice2
 + Bars can be 0

Functions or full programs are allowed.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Is the integer a single digit or can it be > 9? \$\endgroup\$
    – edc65
    Jan 5, 2016 at 22:36
  • \$\begingroup\$ >9 is allowed, I just didn't put it as an example because it would be so big. \$\endgroup\$
    – J Atkin
    Jan 5, 2016 at 22:40
  • \$\begingroup\$ Can there be trailing spaces on each line? I.e. make the output rectangular? \$\endgroup\$ Jan 6, 2016 at 8:08
  • \$\begingroup\$ Yes, trailing spaces are allowed \$\endgroup\$
    – J Atkin
    Jan 6, 2016 at 15:04
  • 1
    \$\begingroup\$ Look in the history, @Doorknob removed it, not sure why... \$\endgroup\$
    – J Atkin
    Jan 6, 2016 at 22:17

7 Answers 7

17
\$\begingroup\$

sh + awk + tac, 173

Mostly an awk script that prints the graph bottom up which is then reversed by tac.

awk '{n[NR]=$NF;$NF="";$0=p" + "$0;p=" |"p}1;END{for(print p;p~/\|/;print p (k>($0=0)?"|":""))for(i=k=p="";i<NR;p=p (j>0||k>0?"|":" ")(!k||$0?"_":" ")){j=k;k=n[++i]--}}'|tac

Description

awk, first part, executed for each input line

{
  n[NR]=$NF;         # store the value, here n[1]=0, n[2]=6, n[3]=3, n[4]=5
  $NF="";            # remove the value from the label
  $0=p" + "$0;       # add a prefix (initially empty) and a " + " in the front
  p=" |"p            # grow the prefix for the next string
}1;                  # implicitly print $0

Output

 + Bars can be 0 
 | + Large-bars_are$nice2 
 | | + average)(@#$ 
 | | | + neato 

awk, second part, executed once in the end

END{
  for(print p;p~/\|/;print p (k>($0=0)?"|":""))
    for(i=k=p="";i<NR;p=p (j>0||k>0?"|":" ")(!k||$0?"_":" "))
      {j=k;k=n[++i]--}}

ungolfed:

END{
  print p;           # print the prefix again
  for(;p~/\|/;)      # for each line, bottom up. no more "|" -> we're done
  {
    p="";            # string to be built
    i=k=0;           # i: bar index, k: heigth of the previous bar
    for(;i<NR;)      # for each bar, left to right
    {
      j=k;           # store the previous bars heigth in j
      k=n[++i]--;    # get the current bars remaining height and decrement it
      p=p (j>0||k>0?"|":" ")(!k||$0?"_":" ");
                     # if the bar to the left or this one have some height
                       remaining, draw a border in between them, else a space
                     # if this bars remaining heigth is exactly 0, draw a top
                     # if $0 is set, draw the bottom
    }
    print p (k>0?"|":"");
                     # draw (or not) the rightmost border, finally print
    $0=0;            # unset $0, only to detect first run
  }
}

Output

 | | | |             # the prefix
 _|_|_|_|            # the strings built by the nested loops
  | | | |            |
  | | | |            v
  | |_| |
  | | | |
  | |  _
   _                 # no more "|" in the string, we're done

tac reverses the lines

   _    
  | |  _
  | | | |
  | |_| |
  | | | |
  | | | |
 _|_|_|_|
 | | | |
 | | | + neato 
 | | + average)(@#$ 
 | + Large-bars_are$nice2 
 + Bars can be 0 
\$\endgroup\$
8
\$\begingroup\$

JavaScript (ES6), 270 262 270 287

Bug fix added a missing row of '|' under the bars

l=>{t=p=o='';l=l.split`
`.map(r=>([,b,c]=r.match(/(.*) (\d+)/),[' + '+b,+c>t?t=c:c]));for(++t;t--;o+=`
`)l.map(x=>o+=x[1]<t?'y y':x[1]>t?t?'x x':'x_x':'y_y');return o.replace(/(yx)|(xy)|(xx?)/g,'|').replace(/y+/g,' ')+[...l,' '].map(x=>p+x[p+=' |',0]).reverse().join`
`}

Test Test in Firefox, as Chrome does not support ES6 Destructuring assignment

F=l=>{
  t=p=o='';l=l.split`\n`.map(r=>([,b,c]=r.match(/(.*) (\d+)/),[' + '+b,+c>t?t=c:c]));
  for(++t;t--;o+=`\n`)l.map(x=>o+=x[1]<t?'y y':x[1]>t?t?'x x':'x_x':'y_y');
  return o.replace(/(yx)|(xy)|(xx?)/g,'|').replace(/y+/g,' ')
  +[...l,' '].map(x=>p+x[p+=' |',0]).reverse().join`\n`
}

function test()
{
  var i=I.value
  O.textContent=F(i)
}  
  
test()
textarea { display:block; width:50%; height:5em}
Input
<textarea id=I>Bars can be 0 0
Large-bars_are$nice2 6
average)(@#$ 3
neato 5</textarea>
<button onclick='test()'>go</button><br>
Output
<pre id=O></pre>

\$\endgroup\$
4
  • \$\begingroup\$ In my Jolf answer I'm working on, I used i.match(/.+ |./g). Perhaps that could be of use? \$\endgroup\$ Jan 5, 2016 at 23:59
  • \$\begingroup\$ 'Uncaught ReferenceError: Invalid left-hand side in assignment', followed by a few 'Uncaught ReferenceError: test is not defined' because the first error prevented declaration of test() \$\endgroup\$
    – x13
    Jan 6, 2016 at 13:25
  • \$\begingroup\$ @ThisNameBetterBeAvailable Using what browser? ES6 is not fully supported in Chrome and totally unsopported in most versions of MSIE \$\endgroup\$
    – edc65
    Jan 6, 2016 at 13:57
  • \$\begingroup\$ @ThisNameBetterBeAvailable in this case, problem is Chrome not supporting destructuring assignment: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…. Try a better browser like Firefox \$\endgroup\$
    – edc65
    Jan 6, 2016 at 14:02
2
\$\begingroup\$

421 bytes - Python 2

import sys
l=sys.stdin.read().split('\n')
b=[(' '.join(x[:-1]),int(x[-1])) for x in map(str.split,l[:-1])]
a=len(b)
e=enumerate
m=[' '*(a+1)+'|'*x[1] for i,x in e(b)]+[' '*(len(b)+1)+'|'*b[-1][1]]
h=[' '*i+'+'+'|'*(a-i)+'_'+' '*(x[1]-1)+'_' for i,x in e(b)]
c=m+h
c[::2]=m
c[1::2]=h
c=[''.join(' ' if not x else x for x in l) for l in map(None,*c)]
for i,(n,_) in e(b):
 c[a-i-1]+='\b'*i*2+n
c='\n'.join(c[::-1])
print(c)

Tests

a 1
b 2
c 3
     _
   _| |
 _| | |
|_|_|_|
 | | |
 | | + c
 | + b
 + a
\$\endgroup\$
1
  • \$\begingroup\$ For me, it doesn't give the right output for zero heights (e.g. a 0 b 3) and descending bars (e.g. a 5 b 3 c 0). My Python version is Python 2.7.10. \$\endgroup\$
    – nimi
    Jan 6, 2016 at 21:47
2
\$\begingroup\$

PowerShell, 208 194 190 168 167 164 bytes

(($h=$args|%{$t,$n=$_-split'(?=\d+$)';$r=,"$c + $t"+$r;$c+=' |';$n})|sort)[-1]..0|%{$i=$_
&{-join($h|%{($p,($p=' |'[$i-lt$_])|sort)[-1];' _'[$i-in0,$_]})+$p}}
$c
$r

Try it online!

Unrolled:

# parse input
$heights = $args|%{        
    $title,$number=$_-split'(?=\d+$)'   # the title contains a trailing space. It's allowed by the author
    $rows=,"$columns + $title"+$rows
    $columns+=' |'
    Write-Output $number
}

# draw bars
($heights|sort)[-1]..0|%{   # max($heights)..0
    $lineNum=$_
    &{                      # execute the scriptblock to reset inner variables
        -join(
            $heights|%{
                $currLeftBorder = ' |'[$lineNum -lt $_]
                Write-Output ($prev,$currLeftBorder|sort)[-1]   # max
                Write-Output ' _'[$lineNum -in 0,$_]
                $prev=$currLeftBorder
            }
        )+$prev
    }
}

# draw bottom columns and titles
Write-Output $columns
Write-Output $rows
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Not bad, wouldn't have thought to try it in PS. \$\endgroup\$
    – J Atkin
    Dec 20, 2019 at 2:43
1
\$\begingroup\$

Java, 613 for the printing function

It may be possible to save a few bytes by the "usual" transformations of for(i=0;i<n;i++) to for(;i++<n;), simplifying the ternary conditionals, or with more elegant computations of the "labels" and "heights", but it's a start.

package stackoverflow.codegolf.barchart;

import static java.util.stream.Stream.of;

import java.util.stream.IntStream;


public class BarChartTest
{
    public static void main(String[] args)
    {
        String input0[] = {
            "Cool 4",
            "Kool 6",
        };
        String input1[] = {
            "Graph 5",
            "Bar 3",
        };
        String input2[] = {
            "Very 4",
            "Large 5",
            "Bar 3",
            "Graph 5",
        };
        String input3[] = {
            "Bars can be 0 0",
            "Large-bars_are$nice2 6",
            "average)(@#$ 3",
            "neato 5",
        };
        runTest(input0);
        runTest(input1);
        runTest(input2);
        runTest(input3);
    }

    private static void runTest(String input[])
    {
        System.out.println("In:");
        for (String string : input)
        {
            System.out.println(string);
        }
        System.out.println("Out:");
        BarChartTest b = new BarChartTest();
        b.print(input);
    }

    void p(String a[]){int h[]=of(a).map(this::s).mapToInt(Integer::parseInt).toArray(),M=IntStream.of(h).max().getAsInt(),C=a.length,r,c,y;Object t[]=of(a).map(this::p).toArray();String s="",p=" + ",w="";char n=10,v='|',i=32,u=95,e;for(r=0;r<=M;r++){e=r==M?'_':' ';y=M-r;for(c=0; c<C; c++){s+=h[c]>y?v:c>0?h[c-1]>y?v:e:r==0?e:i;s+=h[c]==y?u:e;}s+=h[C-1]>y?v:e;s+=n;}for(r=0;r<C;r++){for(c=0;c<C-r;c++){s+=" |";}s+=r>0?p+t[C-r]:w;s+=n;}s+=p+t[0];System.out.println(s);}int b(String s){return s.lastIndexOf(" ");}String p(String s){return s.substring(0,b(s));}String s(String s){return s.substring(b(s)+1,s.length());}
}
\$\endgroup\$
0
1
\$\begingroup\$

Haskell, 323 bytes

f x|(p,q)<-unzip$map((\l->(unwords$init l,(read$last l))).words)$lines x=unlines(reverse$init.(zip((zipWith max=<<tail)$0:q++[0])(q++[0])>>=).(!)<$>[0..maximum q])++v p++'\n':(0#p)
v=(>>" |")
_#[]=""
l#(h:t)=(l+1)#t++v[1..l]++" + "++h++"\n"
0!(0,_)=" _"
0!_="|_"
i!(j,k)|i<j=(i==k)?'|'|1<2=(i==k)?' '
i?c|i=c:"_"|1<2=c:" "

Usage example:

*Main> putStr $ f "Bars can be 0 0\nLarge-bars_are$nice2 6\naverage)(@#$ 3\nneato 5"
   _     
  | |  _ 
  | | | |
  | |_| |
  | | | |
  | | | |
 _|_|_|_|
 | | | |
 | | | + neato
 | | + average)(@#$
 | + Large-bars_are$nice2
 + Bars can be 0

How it works (rough overview, details maybe later):

(p,q)<-unzip$map((\l->(unwords$init l,(read$last l))).words)$lines x
           -- breaks the input into a list of labels (-> p), e.g.
           -- ["Bars can be 0","Lagerge-basr_asr$niche",...] and a list of heights
           -- (-> q), e.g. [0,6,3,5]

unlines(reverse$init.(zip((zipWith max=<<tail)$0:q++[0])(q++[0])>>=).(!)<$>[0..maximum q])
           -- builds the bars
v p++"\n"
           -- builds the first row of "|" underneath the zero line
(0#p)
           -- build the label section

The parsing part ((p,q)<-unlines...) takes a lot of bytes, maybe I can it golf further down.

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

Python 2, 345 bytes

B,H=zip(*[(a,int(b))for a,b in[x.rsplit(' ',1)for x in input().split('\n')]])
h=max(H)
L=len(B)
b=['|'*H[0]]*(L*2+1)
for i in range(L):b[2+i*2]='|'*max(H[i],H[min(i+1,L-1)]);b[1+i*2]=('_'+' '*(H[i]-1)+'_')[:H[i]+1]
b=[x.ljust(h+1)for x in b]
for l in zip(*b)[::-1]:print ''.join(l)
print' |'*L
for i in range(-1,-L-1,-1):print' |'*(L+i),'+',B[i]
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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