Sparkline in unicode: Difference between revisions

From Rosetta Code
Content added Content deleted
m (→‎{{header|REXX}}: noted which Reginas won't supplrt single line comments. -- ~~~~)
m (→‎version 1: added a period to a comment. -- ~~~~)
Line 432: Line 432:
{{trans|NetRexx}}
{{trans|NetRexx}}
{{Works with|ooRexx}}
{{Works with|ooRexx}}
{{Works with|some Reginas --- and is also dependent on a Regina option: ''single_line_comments'' <br> Not all Regina users use the latest version for various reasons <br> Regina 3.3 (and previous) doesn't support single line comments.}}
{{Works with|some Reginas --- and is also dependent on a Regina option: ''single_line_comments'' <br> Not all Regina users use the latest version for various reasons. <br>Regina 3.3 (and previous) doesn't support single line comments.}}
<lang REXX>/* Rexx */
<lang REXX>/* Rexx */



Revision as of 18:47, 8 September 2013

Task
Sparkline in unicode
You are encouraged to solve this task according to the task description, using any language you may know.

A sparkline is a graph of successive values laid out horizontally where the height of the line is proportional to the values in succession.

Use the following series of Unicode characters to create a program that takes a series of numbers separated by one or more whitespace or comma characters and generates a sparkline-type bar graph of the values on a single line of output.

The eight characters: '▁▂▃▄▅▆▇█'
(Unicode values U+2581 through U+2588).

Use your program to show sparklines for the following input, here on this page:

  1. 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
  2. 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
(note the mix of separators in this second case)!
Notes
  • A space is not part of the generated sparkline.
  • The sparkline may be accompanied by simple statistics of the data such as its range.

C++

<lang cpp>#include <iostream>

  1. include <sstream>
  2. include <vector>
  3. include <cmath>
  4. include <algorithm>
  5. include <locale>

class Sparkline {

   public:
       Sparkline(std::wstring &cs) : charset( cs ){
       }
       virtual ~Sparkline(){
       }
       void print(std::string spark){
           const char *delim = ", ";
           std::vector<float> data;
           // Get first non-delimiter
           std::string::size_type last = spark.find_first_not_of(delim, 0);
           // Get end of token
           std::string::size_type pos = spark.find_first_of(delim, last);
           while( pos != std::string::npos || last != std::string::npos ){
               std::string tok = spark.substr(last, pos-last);
               // Convert to float:
               std::stringstream ss(tok);
               float entry;
               ss >> entry;
               data.push_back( entry );
               last = spark.find_first_not_of(delim, pos);
               pos = spark.find_first_of(delim, last);
           }
           // Get range of dataset
           float min = *std::min_element( data.begin(), data.end() );
           float max = *std::max_element( data.begin(), data.end() );
           float skip = (charset.length()-1) / (max - min);
           std::wcout<<L"Min: "<<min<<L"; Max: "<<max<<L"; Range: "<<(max-min)<<std::endl;
           
           std::vector<float>::const_iterator it;
           for(it = data.begin(); it != data.end(); it++){
               float v = ( (*it) - min ) * skip; 
               std::wcout<<charset[ (int)floor( v ) ];
           }
           std::wcout<<std::endl;
           
       }
   private:
       std::wstring &charset;

};

int main( int argc, char **argv ){

   std::wstring charset = L"\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
   // Mainly just set up utf-8, so wcout won't narrow our characters.
   std::locale::global(std::locale("en_US.utf8"));
   Sparkline sl(charset);
   sl.print("1 2 3 4 5 6 7 8 7 6 5 4 3 2 1");
   sl.print("1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5");
   return 0;

}</lang>

Output:
Min: 1; Max: 8; Range: 7
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Min: 0.5; Max: 7.5; Range: 7
▂▁▄▃▆▅█▇

D

Translation of: Python

<lang d>void main() {

   import std.stdio, std.range, std.algorithm, std.conv,
          std.string, std.regex;
   "Numbers please separated by space/commas: ".write;
   /*immutable*/ const numbers = readln
                                 .strip
                                 .splitter(r"[\s,]+".regex)
                                 .array /**/
                                 .to!(real[]);
   immutable mm = numbers.reduce!(min, max);
   "min: %5f; max: %5f".writefln(mm[]);
   immutable bars = iota(9601, 9609).map!(i => i.to!dchar).dtext;
   immutable div = (mm[1] - mm[0]) / (bars.length - 1);
   numbers.map!(n => bars[cast(int)((n - mm[0]) / div)]).writeln;

}</lang> The output is the same as the Python entry (but it only accepts one series of values at a time).

FALSE

<lang false>{

 variables:
 s: sign (1 or -1)
 u: current number
 f: current number fraction length
 v: current number is valid
 t: number of numbers read
 x: biggest fraction
 y: smallest number (without fraction)
 z: biggest number (without fraction)

}

{function a: test if top is 0-9, without popping the value, codes 48-57 are in range} [$$47>\57>~&]a:

{function b: test if top is ',' or ' ', without popping the value} [$$',=\' =|]b:

{function c: read a number from the input, given that the first character of the input is already on the stack} [

 1s:0u:0f:0v: {reset values}
 $'-=[1_s:%^]? {if (it is negative) set the sign value to -1 move to next}
 [a;!][48-u;10*+u:1_v:^]# {while (isnumber) do number = number * 10 + decimal and set valid number and move to next}
 $'.=[ {if (it is a decimal) move forward and read fraction}
   %^
   [a;!][48-u;10*+u:f;1+f:1_v:^]# {while (isnumber) do number = number * 10 + decimal and increase fraction length and set valid number and move to next}
 ]?
 $$'-=\'.=|[0v:]? {if next charachter is a '-' or a '.', set invalid}

]c:

{function d: normalize number/fraction from stack to max fraction and push that number} [

 [$x;=~][1+\10*\]# {while (fraction != max) fraction + 1, value * 10}
 % {pop fraction}

]d:

0t: 0x: 1_v: { nothing read, so we are still valid } ^[b;!][%^]# {read away any initial separators} [$1_=~v;&][ {while input != -1 and valid input, leaving input on the stack}

 c;! {read a number}
 t;1+t:u;s;*f;@ {increase count, push number * sign and fraction length onto the stack and bring input back up}
 f;x;>[f;x:]? {set fraction to biggest of current and previous biggest}
 [b;!][%^]# {while (isseparator) move forward}

]# v;~["error at charachter ",]? {if invalid number, tell them when} v;[ {if last number also valid, do the math}

 %        {pop the -1}
 t;2*1-q: {var q: points to next value}
 0p:      {var p: whether min/max have been set}
 [q;1+t;>][ {while q + 1 > t}
   q;ø        {current number}
   q;ø        {current fraction}
   d;!        {normalize}
   p;[$y;\>[$y:]? $z;>[$z:]?]?      {compare min/max}
   p;~[1_p:$y:$z:]?     {if (first)) set min/max}
   q;1-q:     {move pointer}
 ]#
 t;q: {point q to first value}
 [q;0>][ {while q > 0}
   q;1-øy;-7*z;y;-/ {(number - minvalue) * 7 / (maxvalue - minvalue), should result in 0..7}
   9601+,           {print character}
   q;1-q:           {move pointer}
 ]#

]?</lang> This implementation can only accept one series of numbers at a time.

Output:
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
▂▁▄▃▆▅█▇

Groovy

<lang groovy>def sparkline(List<Number> list) {

   def (min, max) = [list.min(), list.max()]
   def div = (max - min) / 7
   list.collect { (char)(0x2581 + (it-min) * div) }.join()

} def sparkline(String text) { sparkline(text.split(/[ ,]+/).collect { it as Double }) }</lang> Test Code <lang groovy>["1 2 3 4 5 6 7 8 7 6 5 4 3 2 1", "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5"].each { dataset ->

   println "  Dataset: $dataset"
   println "Sparkline: ${sparkline(dataset)}"

}</lang>

Output:
  Dataset: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
Sparkline: ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
  Dataset: 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
Sparkline: ▂▁▄▃▆▅█▇

Haskell

<lang haskell> import Data.Char (chr) import Data.List.Split (splitOneOf)

toSparkLine :: [Double] -> [Char] toSparkLine xs = map cl xs

   where
       top = maximum xs
       bot = minimum xs
       range = top - bot
       cl x = chr $ 0x2581 + round ((x - bot) / range * 7)

makeSparkLine :: String -> (String, Stats) makeSparkLine xs = (toSparkLine parsed, stats parsed)

   where parsed = map read $ filter (not . null) $ splitOneOf " ," xs
         

data Stats = Stats { minValue, maxValue, rangeOfValues :: Double,

   numberOfValues :: Int }

instance Show Stats where

   show (Stats mn mx r n) = "min: " ++ show mn ++ "; max: " ++ show mx ++
       "; range: " ++ show r ++ "; no. of values: " ++ show n
       

stats :: [Double] -> Stats stats xs = Stats { minValue = mn, maxValue = mx,

   rangeOfValues = mx - mn, numberOfValues = length xs }
   where
       mn = minimum xs
       mx = maximum xs

drawSparkLineWithStats :: String -> IO () drawSparkLineWithStats xs = putStrLn sp >> print st

   where (sp, st) = makeSparkLine xs

main :: IO () main = mapM_ drawSparkLineWithStats

   ["1 2 3 4 5 6 7 8 7 6 5 4 3 2 1",
   "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5",
   "3 2 1 0 -1 -2 -3 -4 -3 -2 -1 0 1 2 3",
   "-1000 100 1000 500 200 -400 -700 621 -189 3"]

</lang> Output:

▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
min: 1.0; max: 8.0; range: 7.0; no. of values: 15
▂▁▄▃▆▅█▇
min: 0.5; max: 7.5; range: 7.0; no. of values: 8
█▇▆▅▄▃▂▁▂▃▄▅▆▇█
min: -4.0; max: 3.0; range: 7.0; no. of values: 15
▁▅█▆▅▃▂▇▄▅
min: -1000.0; max: 1000.0; range: 2000.0; no. of values: 10

Java

<lang java> public class Sparkline { String bars="▁▂▃▄▅▆▇█"; public static void main(String[] args) { Sparkline now=new Sparkline(); float[] arr={1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1}; now.display1D(arr); System.out.println(now.getSparkline(arr)); float[] arr1={1.5f, 0.5f, 3.5f, 2.5f, 5.5f, 4.5f, 7.5f, 6.5f}; now.display1D(arr1); System.out.println(now.getSparkline(arr1)); } public void display1D(float[] arr) { for(int i=0;i<arr.length;i++) System.out.print(arr[i]+" "); System.out.println(); } public String getSparkline(float[] arr) { float min=Integer.MAX_VALUE; float max=Integer.MIN_VALUE; for(int i=0;i<arr.length;i++) { if(arr[i]<min) min=arr[i]; if(arr[i]>max) max=arr[i]; } float range=max-min; int num=bars.length()-1; String line=""; for(int i=0;i<arr.length;i++) {

line+=bars.charAt((int)Math.ceil(((arr[i]-min)/range*num))); } return line; } } </lang> Output:

1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 7.0 6.0 5.0 4.0 3.0 2.0 1.0 
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
1.5 0.5 3.5 2.5 5.5 4.5 7.5 6.5 
▂▁▄▃▆▅█▇

NetRexx

<lang NetRexx>/* NetRexx */ options replace format comments java crossref symbols nobinary

runSample(arg) return

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ method sparkline(spark) private static

 spark = spark.changestr(',', ' ')
 bars = '\u2581 \u2582 \u2583 \u2584 \u2585 \u2586 \u2587 \u2588'
 barK = bars.words()
 nmin = spark.word(1)
 nmax = nmin
 -- get min & max values
 loop iw = 1 to spark.words()
   nval = spark.word(iw)
   nmin = nval.min(nmin)
   nmax = nval.max(nmax)
   end iw
 range = nmax - nmin + 1
 slope = 
 loop iw = 1 to spark.words()    
   point = Math.ceil((spark.word(iw) - nmin + 1) / range * barK)
   slope = slope || bars.word(point)
   end iw
 return slope nmin nmax range

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ method runSample(arg) private static

 -- sample data setup
 parse arg vals
 sparks = 0
 sparks[0] = 0
 if vals =  then do
   si = sparks[0] + 1; sparks[0] = si; sparks[si] = 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
   si = sparks[0] + 1; sparks[0] = si; sparks[si] = '1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5'
   end
 else do
   loop until vals = 
     -- split input on a ! character
     parse vals lst '!' vals
     si = sparks[0] + 1; sparks[0] = si; sparks[si] = lst
     end
   end
 -- run the samples
 loop si = 1 to sparks[0]
   vals = sparks[si]
   parse sparkline(vals) slope .
   say 'Input:        ' vals
   say 'Sparkline:    ' slope
   say
   end si
 
 return

</lang>

Output:
Input:         1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
Sparkline:     ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

Input:         1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
Sparkline:     ▂▁▄▃▆▅█▇

Perl 6

<lang perl6>constant @bars = '▁' ... '█'; while prompt 'Numbers separated by anything: ' -> $_ {

   my @numbers = map +*, .comb(/ '-'? \d+ ['.' \d+]? /);
   my ($mn,$mx) = @numbers.minmax.bounds;
   say "min: $mn.fmt('%5f'); max: $mx.fmt('%5f')";
   my $div = ($mx - $mn) / (@bars - 1);
   say @bars[ (@numbers X- $mn) X/ $div ].join;

}</lang>

Output:
Numbers separated by anything: 9 18 27 36 45 54 63 72 63 54 45 36 27 18 9
9 18 27 36 45 54 63 72 63 54 45 36 27 18 9
min: 9.000000; max: 72.000000
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers separated by anything: 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
1.5 0.5 3.5 2.5 5.5 4.5 7.5 6.5
min: 0.500000; max: 7.500000
▂▁▄▃▆▅█▇
Numbers separated by anything: 3 2 1 0 -1 -2 -3 -4 -3 -2 -1 0 1 2 3  
min: -4.000000; max: 3.000000
█▇▆▅▄▃▂▁▂▃▄▅▆▇█
Numbers separated by anything: ^D

Python

<lang python>import re try: raw_input except: raw_input = input

  1. Unicode: 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608

try: bar = u'▁▂▃▄▅▆▇█' except: bar = '▁▂▃▄▅▆▇█' barcount = len(bar) - 1 while True:

   line = raw_input('Numbers please separated by space/commas: ')
   numbers = [float(n) for n in re.split(r'[\s,]+', line.strip())]
   mn, mx = min(numbers), max(numbers)
   extent = mx - mn
   sparkline = .join(bar[int( (n - mn) / extent * barcount)]
                       for n in numbers)
   print('min: %5f; max: %5f' % (mn, mx))
   print(sparkline)</lang>
Output:
Numbers separated by space/commas: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
min: 1.000000; max: 7.000000
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Numbers separated by space/commas: 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
min: 0.500000; max: 7.500000
▂▁▄▃▆▅█▇

REXX

version 1

Translation of: NetRexx
Works with: ooRexx
Works with: [[some Reginas --- and is also dependent on a Regina option: single_line_comments
Not all Regina users use the latest version for various reasons.
Regina 3.3 (and previous) doesn't support single line comments.]]

<lang REXX>/* Rexx */

parse arg aaa call runSample aaa say copies('-', 80) say return

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sparkline:

 procedure
 parse arg spark
 spark = changestr(',', spark, ' ')
 bars = '▁ ▂ ▃ ▄ ▅ ▆ ▇ █'
 barK = words(bars)
 nmin = word(spark, 1)
 nmax = nmin
 -- get min & max values
 do iw = 1 to words(spark)
   nval = word(spark, iw)
   nmin = min(nval, nmin)
   nmax = max(nval, nmax)
   end iw
 range = nmax - nmin + 1
 slope = 
 do iw = 1 to words(spark)
   point = ceiling((word(spark, iw) - nmin + 1) / range * barK)
   slope = slope || word(bars, point)
   end iw
 return slope nmin nmax range

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ceiling: procedure

 parse arg ceil
 return trunc(ceil) + (ceil > 0) * (ceil \= trunc(ceil))

floor: procedure

 parse arg flor
 return trunc(flor) - (flor < 0) * (flor \= trunc(flor))

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ runSample: procedure

 -- sample data setup
 parse arg vals
 sparks = 0
 sparks.0 = 0
 if vals =  then do
   si = sparks.0 + 1; sparks.0 = si; sparks.si = 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
   si = sparks.0 + 1; sparks.0 = si; sparks.si = '1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5'
   end
 else do
   do until vals = 
     -- split input on a ! character
     parse var vals lst '!' vals
     si = sparks.0 + 1; sparks.0 = si; sparks.si = lst
     end
   end
 -- run the samples
 do si = 1 to sparks.0
   vals = sparks.si
   parse value sparkline(vals) with slope .
   say 'Input:        ' vals
   say 'Sparkline:    ' slope
   say
   end si
 
 return

</lang>

Output:
Input:         1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
Sparkline:     ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

Input:         1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
Sparkline:     ▂▁▄▃▆▅█▇

version 2

(A re-work of REXX version 1)
This version works on:

  • all versions of Regina (which may or may not support single line comments)
  • R4 and ROO (which don't support single line comments)
  • older versions of REXX such as PC/REXX and Personal REXX which don't support the changestr BIF

This version also removed some dead code, simplified the program structure and subroutines, added comments.
It should also be noted that CMS and TSO versions of REXX don't support single line comments. <lang rexx>/*REXX program displays a sparkline (spark graph) for a group of values.*/ if arg()==0 then do /*No arguments? Use defaults.*/

                 call sparkGraph  1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
                 call sparkGraph '1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5'
                 end
            else call sparkGraph arg(1)

exit /*stick a fork in it, we're done.*/ /*──────────────────────────────────CEIL subroutine─────────────────────*/ ceil: procedure; parse arg ?; _=trunc(?); return _+(?>0)*(?\=_) /*──────────────────────────────────SPARKGRAPH subroutine───────────────*/ sparkGraph: procedure; parse arg x; say ' input: ' x /*echo values*/ x=translate(x, ' ', ",") /*remove any superfluous commas. */ $='▁▂▃▄▅▆▇█' /*chars to be used for the graph.*/ xmin=word(x,1); xmax=xmin /*assume a minimum and a maximum.*/

   do n=2  to words(x);  _=word(x,n)  /*examine successive words in  X.*/
   xmin=min(_,xmin)                   /*find the minimum value in  X.  */
   xmax=max(_,xmax)                   /*  "   "  maximum   "    "  "   */
   end   /*n*/

z=; do j=1 for words(x) /*build the output spark graph. */

        z=z||substr($,ceil((word(x,j)-xmin+1)/(xmax-xmin+1)*length($)),1)
        end   /*j*/

say 'output: ' z; say /*show the output, + a blank line*/ return</lang> output using the default input(s):

 input:  1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
output:  ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁

 input:  1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
output:  ▂▁▄▃▆▅█▇

Racket

<lang racket>

  1. lang racket (require syntax/parse)

(define bars "▁▂▃▄▅▆▇█") (define bar-count (string-length bars))

(define (sparks str)

 (define ns (map string->number (string-split str #rx"[ ,]" #:repeat? #t)))
 (define mn (apply min ns))
 (define bar-width (/ (- (apply max ns) mn) (- bar-count 1)))
 (apply string (for/list ([n ns]) (string-ref bars (exact-floor (/ (- n mn) bar-width))))))

(sparks "1 2 3 4 5 6 7 8 7 6 5 4 3 2 1") (sparks "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5") </lang> Output: <lang racket> "▁▂▃▄▅▆▇█▇▆▅▄▃▂▁" "▂▁▄▃▆▅█▇" </lang>

Tcl

Works with: Tcl version 8.6

<lang tcl>package require Tcl 8.6

proc extractValues {series} {

   return [regexp -all -inline {\d+(?:\.\d*)?|\.\d+} $series]

} proc renderValue {min max value} {

   set band [expr {int(8*($value-$min)/(($max-$min)*1.01))}]
   return [format "%c" [expr {0x2581 + $band}]]

} proc sparkline {series} {

   set values [extractValues $series]
   set min [tcl::mathfunc::min {*}$values]
   set max [tcl::mathfunc::max {*}$values]
   return [join [lmap v $values {renderValue $min $max $v}] ""]

}</lang> Demonstrating: <lang tcl>set data {

   "1 2 3 4 5 6 7 8 7 6 5 4 3 2 1"
   "1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5"

} foreach series $data {

   puts "Series: $series"
   puts "Sparkline: [sparkline $series]"

}</lang>

Output:
Series: 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1
Sparkline: ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Series: 1.5, 0.5 3.5, 2.5 5.5, 4.5 7.5, 6.5
Sparkline: ▂▁▄▃▆▅█▇