Sparkline in unicode: Difference between revisions
m (→{{header|NetRexx}}: choose a better input separator) |
(C++ implementation) |
||
Line 16:
* The sparkline may be accompanied by simple statistics of the data such as its range.
=={{header|C++}}==
<lang cpp>#include <iostream>
#include <sstream>
#include <vector>
#include <cmath>
#include <algorithm>
#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>
{{out}}
<pre>Min: 1; Max: 8; Range: 7
▁▂▃▄▅▆▇█▇▆▅▄▃▂▁
Min: 0.5; Max: 7.5; Range: 7
▂▁▄▃▆▅█▇</pre>
=={{header|D}}==
{{trans|Python}}
|
Revision as of 11:53, 4 September 2013
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 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
- (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>
- include <sstream>
- include <vector>
- include <cmath>
- include <algorithm>
- 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
<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
- 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
<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: ▂▁▄▃▆▅█▇
Racket
<lang racket>
- 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
<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: ▂▁▄▃▆▅█▇